52711 lines
1.6 MiB
52711 lines
1.6 MiB
/*
|
|
* QuickJS Javascript Engine
|
|
*
|
|
* Copyright (c) 2017-2021 Fabrice Bellard
|
|
* Copyright (c) 2017-2021 Charlie Gordon
|
|
* Copyright (c) 2023 Ben Noordhuis
|
|
* Copyright (c) 2023 Saúl Ibarra Corretgé
|
|
*
|
|
* 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>
|
|
#if !defined(_MSC_VER)
|
|
#include <sys/time.h>
|
|
#if defined(_WIN32)
|
|
#include <timezoneapi.h>
|
|
#endif
|
|
#endif
|
|
#include <time.h>
|
|
#include <fenv.h>
|
|
#include <math.h>
|
|
|
|
#include "cutils.h"
|
|
#include "list.h"
|
|
#include "quickjs.h"
|
|
#include "libregexp.h"
|
|
#include "libbf.h"
|
|
|
|
#if defined(EMSCRIPTEN) || defined(_MSC_VER)
|
|
#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(__NEWLIB__)
|
|
#define NO_TM_GMTOFF
|
|
#endif
|
|
|
|
#if !defined(EMSCRIPTEN) && !defined(__wasi__)
|
|
#include "quickjs-c-atomics.h"
|
|
#define CONFIG_ATOMICS
|
|
#endif
|
|
|
|
// Debug trace system:
|
|
// uncomment one or more DUMP_XXX definition to produce debug output.
|
|
// define the DUMP_XXX symbol as empty or 0 for unconditional output
|
|
// otherwhise the debug output will be produced to the dump stream (currently
|
|
// stdout) if qjs is invoked with -d<bitmask> with the corresponding bit set.
|
|
|
|
//#define DUMP_BYTECODE_FINAL 0x01 /* dump pass 3 final byte code */
|
|
//#define DUMP_BYTECODE_PASS2 0x02 /* dump pass 2 code */
|
|
//#define DUMP_BYTECODE_PASS1 0x04 /* dump pass 1 code */
|
|
//#define DUMP_BYTECODE_HEX 0x10 /* dump bytecode in hex */
|
|
//#define DUMP_BYTECODE_PC2LINE 0x20 /* dump line number table */
|
|
//#define DUMP_BYTECODE_STACK 0x40 /* dump compute_stack_size */
|
|
//#define DUMP_BYTECODE_STEP 0x80 /* dump executed bytecode */
|
|
//#define DUMP_READ_OBJECT 0x100 /* dump the marshalled objects at load time */
|
|
//#define DUMP_FREE 0x200 /* dump every object free */
|
|
//#define DUMP_GC 0x400 /* dump the occurrence of the automatic GC */
|
|
//#define DUMP_GC_FREE 0x800 /* dump objects freed by the GC */
|
|
//#define DUMP_MODULE_RESOLVE 0x1000 /* dump module resolution steps */
|
|
//#define DUMP_PROMISE 0x2000 /* dump promise steps */
|
|
//#define DUMP_LEAKS 0x4000 /* dump leaked objects and strings in JS_FreeRuntime */
|
|
//#define DUMP_ATOM_LEAKS 0x8000 /* dump leaked atoms in JS_FreeRuntime */
|
|
//#define DUMP_MEM 0x10000 /* dump memory usage in JS_FreeRuntime */
|
|
//#define DUMP_OBJECTS 0x20000 /* dump objects in JS_FreeRuntime */
|
|
//#define DUMP_ATOMS 0x40000 /* dump atoms in JS_FreeRuntime */
|
|
//#define DUMP_SHAPES 0x80000 /* dump shapes in JS_FreeRuntime */
|
|
|
|
//#define FORCE_GC_AT_MALLOC /* test the GC by forcing it before each object allocation */
|
|
|
|
#define check_dump_flag(rt, flag) ((rt->dump_flags & (flag +0)) == (flag +0))
|
|
|
|
#define STRINGIFY_(x) #x
|
|
#define STRINGIFY(x) STRINGIFY_(x)
|
|
|
|
#define QJS_VERSION_STRING \
|
|
STRINGIFY(QJS_VERSION_MAJOR) "." STRINGIFY(QJS_VERSION_MINOR) "." STRINGIFY(QJS_VERSION_PATCH) QJS_VERSION_SUFFIX
|
|
|
|
const char* JS_GetVersion(void) {
|
|
return QJS_VERSION_STRING;
|
|
}
|
|
|
|
#undef STRINFIGY_
|
|
#undef STRINGIFY
|
|
|
|
enum {
|
|
/* classid tag */ /* union usage | properties */
|
|
JS_CLASS_OBJECT = 1, /* must be first */
|
|
JS_CLASS_ARRAY, /* u.array | length */
|
|
JS_CLASS_ERROR,
|
|
JS_CLASS_NUMBER, /* u.object_data */
|
|
JS_CLASS_STRING, /* u.object_data */
|
|
JS_CLASS_BOOLEAN, /* u.object_data */
|
|
JS_CLASS_SYMBOL, /* u.object_data */
|
|
JS_CLASS_ARGUMENTS, /* u.array | length */
|
|
JS_CLASS_MAPPED_ARGUMENTS, /* | length */
|
|
JS_CLASS_DATE, /* u.object_data */
|
|
JS_CLASS_MODULE_NS,
|
|
JS_CLASS_C_FUNCTION, /* u.cfunc */
|
|
JS_CLASS_BYTECODE_FUNCTION, /* u.func */
|
|
JS_CLASS_BOUND_FUNCTION, /* u.bound_function */
|
|
JS_CLASS_C_FUNCTION_DATA, /* u.c_function_data_record */
|
|
JS_CLASS_GENERATOR_FUNCTION, /* u.func */
|
|
JS_CLASS_FOR_IN_ITERATOR, /* u.for_in_iterator */
|
|
JS_CLASS_REGEXP, /* u.regexp */
|
|
JS_CLASS_ARRAY_BUFFER, /* u.array_buffer */
|
|
JS_CLASS_SHARED_ARRAY_BUFFER, /* u.array_buffer */
|
|
JS_CLASS_UINT8C_ARRAY, /* u.array (typed_array) */
|
|
JS_CLASS_INT8_ARRAY, /* u.array (typed_array) */
|
|
JS_CLASS_UINT8_ARRAY, /* u.array (typed_array) */
|
|
JS_CLASS_INT16_ARRAY, /* u.array (typed_array) */
|
|
JS_CLASS_UINT16_ARRAY, /* u.array (typed_array) */
|
|
JS_CLASS_INT32_ARRAY, /* u.array (typed_array) */
|
|
JS_CLASS_UINT32_ARRAY, /* u.array (typed_array) */
|
|
JS_CLASS_BIG_INT64_ARRAY, /* u.array (typed_array) */
|
|
JS_CLASS_BIG_UINT64_ARRAY, /* u.array (typed_array) */
|
|
JS_CLASS_FLOAT32_ARRAY, /* u.array (typed_array) */
|
|
JS_CLASS_FLOAT64_ARRAY, /* u.array (typed_array) */
|
|
JS_CLASS_DATAVIEW, /* u.typed_array */
|
|
JS_CLASS_BIG_INT, /* u.object_data */
|
|
JS_CLASS_MAP, /* u.map_state */
|
|
JS_CLASS_SET, /* u.map_state */
|
|
JS_CLASS_WEAKMAP, /* u.map_state */
|
|
JS_CLASS_WEAKSET, /* u.map_state */
|
|
JS_CLASS_MAP_ITERATOR, /* u.map_iterator_data */
|
|
JS_CLASS_SET_ITERATOR, /* u.map_iterator_data */
|
|
JS_CLASS_ARRAY_ITERATOR, /* u.array_iterator_data */
|
|
JS_CLASS_STRING_ITERATOR, /* u.array_iterator_data */
|
|
JS_CLASS_REGEXP_STRING_ITERATOR, /* u.regexp_string_iterator_data */
|
|
JS_CLASS_GENERATOR, /* u.generator_data */
|
|
JS_CLASS_PROXY, /* u.proxy_data */
|
|
JS_CLASS_PROMISE, /* u.promise_data */
|
|
JS_CLASS_PROMISE_RESOLVE_FUNCTION, /* u.promise_function_data */
|
|
JS_CLASS_PROMISE_REJECT_FUNCTION, /* u.promise_function_data */
|
|
JS_CLASS_ASYNC_FUNCTION, /* u.func */
|
|
JS_CLASS_ASYNC_FUNCTION_RESOLVE, /* u.async_function_data */
|
|
JS_CLASS_ASYNC_FUNCTION_REJECT, /* u.async_function_data */
|
|
JS_CLASS_ASYNC_FROM_SYNC_ITERATOR, /* u.async_from_sync_iterator_data */
|
|
JS_CLASS_ASYNC_GENERATOR_FUNCTION, /* u.func */
|
|
JS_CLASS_ASYNC_GENERATOR, /* u.async_generator_data */
|
|
JS_CLASS_WEAK_REF,
|
|
JS_CLASS_FINALIZATION_REGISTRY,
|
|
JS_CLASS_CALL_SITE,
|
|
|
|
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 65535
|
|
#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;
|
|
|
|
#define JS_VALUE_GET_STRING(v) ((JSString *)JS_VALUE_GET_PTR(v))
|
|
|
|
typedef enum {
|
|
JS_GC_PHASE_NONE,
|
|
JS_GC_PHASE_DECREF,
|
|
JS_GC_PHASE_REMOVE_CYCLES,
|
|
} JSGCPhaseEnum;
|
|
|
|
typedef enum OPCodeEnum OPCodeEnum;
|
|
|
|
struct JSRuntime {
|
|
JSMallocFunctions mf;
|
|
JSMallocState malloc_state;
|
|
const char *rt_info;
|
|
|
|
int atom_hash_size; /* power of two */
|
|
int atom_count;
|
|
int atom_size;
|
|
int atom_count_resize; /* resize hash table at this count */
|
|
uint32_t *atom_hash;
|
|
JSAtomStruct **atom_array;
|
|
int atom_free_index; /* 0 = none */
|
|
|
|
JSClassID js_class_id_alloc; /* counter for user defined classes */
|
|
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;
|
|
/* and likewise if inside Error.prepareStackTrace() */
|
|
BOOL in_prepare_stack_trace : 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;
|
|
|
|
/* used to allocate, free and clone SharedArrayBuffers */
|
|
JSSharedArrayBufferFunctions sab_funcs;
|
|
|
|
BOOL can_block : 8; /* TRUE if Atomics.wait can block */
|
|
uint32_t dump_flags : 24;
|
|
|
|
/* Shape hash table */
|
|
int shape_hash_bits;
|
|
int shape_hash_size;
|
|
int shape_hash_count; /* number of hashed shapes */
|
|
JSShape **shape_hash;
|
|
bf_context_t bf_ctx;
|
|
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)
|
|
|
|
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 */
|
|
uint8_t *cur_pc; /* only used in bytecode functions : PC of the
|
|
instruction after the call */
|
|
int arg_count;
|
|
int js_mode;
|
|
/* 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;
|
|
|
|
/* the same structure is used for big integers.
|
|
Big integers are never infinite or NaNs */
|
|
typedef struct JSBigInt {
|
|
JSRefCountHeader header; /* must come first, 32-bit */
|
|
bf_t num;
|
|
} JSBigInt;
|
|
|
|
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 error_ctor;
|
|
JSValue error_prepare_stack;
|
|
int error_stack_trace_limit;
|
|
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 */
|
|
|
|
double time_origin;
|
|
|
|
uint64_t random_state;
|
|
bf_context_t *bf_ctx; /* points to rt->bf_ctx, shared by all contexts */
|
|
/* when the counter reaches zero, JSRutime.interrupt_handler is called */
|
|
int interrupt_counter;
|
|
|
|
struct list_head loaded_modules; /* list of JSModuleDef.link */
|
|
|
|
/* if NULL, RegExp compilation is not supported */
|
|
JSValue (*compile_regexp)(JSContext *ctx, JSValue pattern,
|
|
JSValue flags);
|
|
/* if NULL, eval is not supported */
|
|
JSValue (*eval_internal)(JSContext *ctx, JSValue 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;
|
|
|
|
typedef enum {
|
|
JS_WEAK_REF_KIND_MAP,
|
|
JS_WEAK_REF_KIND_WEAK_REF,
|
|
JS_WEAK_REF_KIND_FINALIZATION_REGISTRY_ENTRY,
|
|
} JSWeakRefKindEnum;
|
|
|
|
typedef struct JSWeakRefRecord {
|
|
JSWeakRefKindEnum kind;
|
|
struct JSWeakRefRecord *next_weak_ref;
|
|
union {
|
|
struct JSMapRecord *map_record;
|
|
struct JSWeakRefData *weak_ref_data;
|
|
struct JSFinRecEntry *fin_rec_entry;
|
|
} u;
|
|
} JSWeakRefRecord;
|
|
|
|
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 */
|
|
JSWeakRefRecord *first_weak_ref;
|
|
#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 is_static_private : 1; /* only used during private class field parsing */
|
|
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;
|
|
|
|
#define IC_CACHE_ITEM_CAPACITY 4
|
|
|
|
typedef struct JSInlineCacheRingSlot {
|
|
/* SoA for space optimization: 56 bytes */
|
|
JSShape* shape[IC_CACHE_ITEM_CAPACITY];
|
|
uint32_t prop_offset[IC_CACHE_ITEM_CAPACITY];
|
|
JSAtom atom;
|
|
uint8_t index;
|
|
} JSInlineCacheRingSlot;
|
|
|
|
typedef struct JSInlineCacheHashSlot {
|
|
JSAtom atom;
|
|
uint32_t index;
|
|
struct JSInlineCacheHashSlot *next;
|
|
} JSInlineCacheHashSlot;
|
|
|
|
typedef struct JSInlineCache {
|
|
uint32_t count;
|
|
uint32_t capacity;
|
|
uint32_t hash_bits;
|
|
JSInlineCacheHashSlot **hash;
|
|
JSInlineCacheRingSlot *cache;
|
|
uint32_t updated_offset;
|
|
BOOL updated;
|
|
} JSInlineCache;
|
|
|
|
static JSInlineCache *init_ic(JSContext *ctx);
|
|
static int rebuild_ic(JSContext *ctx, JSInlineCache *ic);
|
|
static int resize_ic_hash(JSContext *ctx, JSInlineCache *ic);
|
|
static int free_ic(JSRuntime *rt, JSInlineCache *ic);
|
|
static uint32_t add_ic_slot(JSContext *ctx, JSInlineCache *ic, JSAtom atom, JSObject *object,
|
|
uint32_t prop_offset);
|
|
|
|
static int32_t get_ic_prop_offset(JSInlineCache *ic, uint32_t cache_offset,
|
|
JSShape *shape)
|
|
{
|
|
uint32_t i;
|
|
JSInlineCacheRingSlot *cr;
|
|
JSShape *shape_slot;
|
|
assert(cache_offset < ic->capacity);
|
|
cr = ic->cache + cache_offset;
|
|
i = cr->index;
|
|
for (;;) {
|
|
shape_slot = *(cr->shape + i);
|
|
if (likely(shape_slot == shape)) {
|
|
cr->index = i;
|
|
return cr->prop_offset[i];
|
|
}
|
|
|
|
i = (i + 1) % countof(cr->shape);
|
|
if (unlikely(i == cr->index)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
static force_inline JSAtom get_ic_atom(JSInlineCache *ic, uint32_t cache_offset)
|
|
{
|
|
assert(cache_offset < ic->capacity);
|
|
return ic->cache[cache_offset].atom;
|
|
}
|
|
|
|
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 backtrace_barrier : 1; /* stop backtrace on this function */
|
|
/* XXX: 5 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;
|
|
JSInlineCache *ic;
|
|
JSAtom filename;
|
|
int line_num;
|
|
int col_num;
|
|
int source_len;
|
|
int pc2line_len;
|
|
uint8_t *pc2line_buf;
|
|
char *source;
|
|
} 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 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 promise;
|
|
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 */
|
|
};
|
|
|
|
typedef struct JSPromiseData {
|
|
JSPromiseStateEnum promise_state;
|
|
/* 0=fulfill, 1=reject, list of JSPromiseReactionData.link */
|
|
struct list_head promise_reactions[2];
|
|
BOOL is_handled; /* Note: only useful to debug */
|
|
JSValue promise_result;
|
|
} JSPromiseData;
|
|
|
|
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 */
|
|
JSWeakRefRecord *first_weak_ref;
|
|
/* 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 */
|
|
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 */
|
|
};
|
|
|
|
typedef struct JSCallSiteData {
|
|
JSValue filename;
|
|
JSValue func;
|
|
JSValue func_name;
|
|
BOOL native;
|
|
int line_num;
|
|
int col_num;
|
|
} JSCallSiteData;
|
|
|
|
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, JSValue func_obj,
|
|
JSValue this_obj,
|
|
int argc, JSValue *argv, int flags);
|
|
static JSValue js_call_bound_function(JSContext *ctx, JSValue func_obj,
|
|
JSValue this_obj,
|
|
int argc, JSValue *argv, int flags);
|
|
static JSValue JS_CallInternal(JSContext *ctx, JSValue func_obj,
|
|
JSValue this_obj, JSValue new_target,
|
|
int argc, JSValue *argv, int flags);
|
|
static JSValue JS_CallConstructorInternal(JSContext *ctx,
|
|
JSValue func_obj,
|
|
JSValue new_target,
|
|
int argc, JSValue *argv, int flags);
|
|
static JSValue JS_CallFree(JSContext *ctx, JSValue func_obj, JSValue this_obj,
|
|
int argc, JSValue *argv);
|
|
static JSValue JS_InvokeFree(JSContext *ctx, JSValue this_val, JSAtom atom,
|
|
int argc, JSValue *argv);
|
|
static __exception int JS_ToArrayLengthFree(JSContext *ctx, uint32_t *plen,
|
|
JSValue val, BOOL is_array_ctor);
|
|
static JSValue JS_EvalObject(JSContext *ctx, JSValue this_obj,
|
|
JSValue val, int flags, int scope_idx);
|
|
JSValue __attribute__((format(printf, 2, 3))) JS_ThrowInternalError(JSContext *ctx, const char *fmt, ...);
|
|
|
|
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_DumpValue(JSRuntime *rt, JSValue val);
|
|
static __maybe_unused void JS_DumpAtoms(JSRuntime *rt);
|
|
static __maybe_unused void JS_DumpShapes(JSRuntime *rt);
|
|
|
|
static JSValue js_function_apply(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv, int magic);
|
|
static void js_array_finalizer(JSRuntime *rt, JSValue val);
|
|
static void js_array_mark(JSRuntime *rt, JSValue val,
|
|
JS_MarkFunc *mark_func);
|
|
static void js_object_data_finalizer(JSRuntime *rt, JSValue val);
|
|
static void js_object_data_mark(JSRuntime *rt, JSValue val,
|
|
JS_MarkFunc *mark_func);
|
|
static void js_c_function_finalizer(JSRuntime *rt, JSValue val);
|
|
static void js_c_function_mark(JSRuntime *rt, JSValue val,
|
|
JS_MarkFunc *mark_func);
|
|
static void js_bytecode_function_finalizer(JSRuntime *rt, JSValue val);
|
|
static void js_bytecode_function_mark(JSRuntime *rt, JSValue val,
|
|
JS_MarkFunc *mark_func);
|
|
static void js_bound_function_finalizer(JSRuntime *rt, JSValue val);
|
|
static void js_bound_function_mark(JSRuntime *rt, JSValue 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, JSValue 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, JSValue val,
|
|
JS_MarkFunc *mark_func);
|
|
static void js_proxy_finalizer(JSRuntime *rt, JSValue val);
|
|
static void js_proxy_mark(JSRuntime *rt, JSValue val,
|
|
JS_MarkFunc *mark_func);
|
|
static void js_map_finalizer(JSRuntime *rt, JSValue val);
|
|
static void js_map_mark(JSRuntime *rt, JSValue val,
|
|
JS_MarkFunc *mark_func);
|
|
static void js_map_iterator_finalizer(JSRuntime *rt, JSValue val);
|
|
static void js_map_iterator_mark(JSRuntime *rt, JSValue val,
|
|
JS_MarkFunc *mark_func);
|
|
static void js_array_iterator_finalizer(JSRuntime *rt, JSValue val);
|
|
static void js_array_iterator_mark(JSRuntime *rt, JSValue 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, JSValue val,
|
|
JS_MarkFunc *mark_func);
|
|
static void js_generator_finalizer(JSRuntime *rt, JSValue obj);
|
|
static void js_generator_mark(JSRuntime *rt, JSValue val,
|
|
JS_MarkFunc *mark_func);
|
|
static void js_promise_finalizer(JSRuntime *rt, JSValue val);
|
|
static void js_promise_mark(JSRuntime *rt, JSValue 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, JSValue val,
|
|
JS_MarkFunc *mark_func);
|
|
|
|
#define HINT_STRING 0
|
|
#define HINT_NUMBER 1
|
|
#define HINT_NONE 2
|
|
#define HINT_FORCE_ORDINARY (1 << 4) // don't try Symbol.toPrimitive
|
|
static JSValue JS_ToPrimitiveFree(JSContext *ctx, JSValue val, int hint);
|
|
static JSValue JS_ToStringFree(JSContext *ctx, JSValue val);
|
|
static int JS_ToBoolFree(JSContext *ctx, JSValue val);
|
|
static int JS_ToInt32Free(JSContext *ctx, int32_t *pres, JSValue val);
|
|
static int JS_ToFloat64Free(JSContext *ctx, double *pres, JSValue val);
|
|
static int JS_ToUint8ClampFree(JSContext *ctx, int32_t *pres, JSValue val);
|
|
static JSValue js_new_string8_len(JSContext *ctx, const char *buf, int len);
|
|
static JSValue js_compile_regexp(JSContext *ctx, JSValue pattern,
|
|
JSValue flags);
|
|
static JSValue js_regexp_constructor_internal(JSContext *ctx, JSValue 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);
|
|
static JSValue js_promise_all(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv, int magic);
|
|
static JSValue js_promise_then(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv);
|
|
static JSValue js_array_push(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv, int unshift);
|
|
|
|
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, JSValue op1, JSValue op2);
|
|
static BOOL js_same_value_zero(JSContext *ctx, JSValue op1, JSValue op2);
|
|
static JSValue JS_ToObject(JSContext *ctx, JSValue val);
|
|
static JSValue JS_ToObjectFree(JSContext *ctx, JSValue val);
|
|
static JSProperty *add_property(JSContext *ctx,
|
|
JSObject *p, JSAtom prop, int prop_flags);
|
|
static JSValue JS_NewBigInt(JSContext *ctx);
|
|
static inline bf_t *JS_GetBigInt(JSValue val)
|
|
{
|
|
JSBigInt *p = JS_VALUE_GET_PTR(val);
|
|
return &p->num;
|
|
}
|
|
static JSValue JS_CompactBigInt1(JSContext *ctx, JSValue val);
|
|
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, JSValue val);
|
|
static bf_t *JS_ToBigInt1(JSContext *ctx, bf_t *buf, JSValue val);
|
|
static void JS_FreeBigInt(JSContext *ctx, bf_t *a, bf_t *buf);
|
|
JSValue JS_ThrowOutOfMemory(JSContext *ctx);
|
|
static JSValue JS_ThrowTypeErrorRevokedProxy(JSContext *ctx);
|
|
static JSValue js_proxy_getPrototypeOf(JSContext *ctx, JSValue obj);
|
|
static int js_proxy_setPrototypeOf(JSContext *ctx, JSValue obj,
|
|
JSValue proto_val, BOOL throw_flag);
|
|
static int js_proxy_isExtensible(JSContext *ctx, JSValue obj);
|
|
static int js_proxy_preventExtensions(JSContext *ctx, JSValue obj);
|
|
static int js_proxy_isArray(JSContext *ctx, JSValue obj);
|
|
static int JS_CreateProperty(JSContext *ctx, JSObject *p,
|
|
JSAtom prop, JSValue val,
|
|
JSValue getter, JSValue setter,
|
|
int flags);
|
|
static int js_string_memcmp(const JSString *p1, const JSString *p2, int len);
|
|
static void reset_weak_ref(JSRuntime *rt, JSWeakRefRecord **first_weak_ref);
|
|
static BOOL is_valid_weakref_target(JSValue val);
|
|
static void insert_weakref_record(JSValue target, struct JSWeakRefRecord *wr);
|
|
static JSValue js_array_buffer_constructor3(JSContext *ctx,
|
|
JSValue 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, JSValue obj);
|
|
static JSValue js_typed_array_constructor(JSContext *ctx,
|
|
JSValue this_val,
|
|
int argc, JSValue *argv,
|
|
int classid);
|
|
static JSValue js_typed_array_constructor_ta(JSContext *ctx,
|
|
JSValue new_target,
|
|
JSValue src_obj,
|
|
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, JSValue func_obj,
|
|
JSValue this_obj,
|
|
int argc, JSValue *argv,
|
|
int flags);
|
|
static void js_async_function_resolve_finalizer(JSRuntime *rt, JSValue val);
|
|
static void js_async_function_resolve_mark(JSRuntime *rt, JSValue val,
|
|
JS_MarkFunc *mark_func);
|
|
static JSValue JS_EvalInternal(JSContext *ctx, JSValue 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, JSValue specifier);
|
|
static void free_var_ref(JSRuntime *rt, JSVarRef *var_ref);
|
|
static JSValue js_new_promise_capability(JSContext *ctx,
|
|
JSValue *resolving_funcs,
|
|
JSValue ctor);
|
|
static __exception int perform_promise_then(JSContext *ctx,
|
|
JSValue promise,
|
|
JSValue *resolve_reject,
|
|
JSValue *cap_resolving_funcs);
|
|
static JSValue js_promise_resolve(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv, int magic);
|
|
static int js_string_compare(JSContext *ctx,
|
|
const JSString *p1, const JSString *p2);
|
|
static JSValue JS_ToNumber(JSContext *ctx, JSValue val);
|
|
static int JS_SetPropertyValue(JSContext *ctx, JSValue this_obj,
|
|
JSValue prop, JSValue val, int flags);
|
|
static int JS_NumberIsInteger(JSContext *ctx, JSValue val);
|
|
static BOOL JS_NumberIsNegativeOrMinusZero(JSContext *ctx, JSValue 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,
|
|
JSValue obj);
|
|
static __exception int js_get_length64(JSContext *ctx, int64_t *pres,
|
|
JSValue obj);
|
|
static void free_arg_list(JSContext *ctx, JSValue *tab, uint32_t len);
|
|
static JSValue *build_arg_list(JSContext *ctx, uint32_t *plen,
|
|
JSValue array_arg);
|
|
static BOOL js_get_fast_array(JSContext *ctx, JSValue obj,
|
|
JSValue **arrpp, uint32_t *countp);
|
|
static JSValue JS_CreateAsyncFromSyncIterator(JSContext *ctx,
|
|
JSValue sync_iter);
|
|
static void js_c_function_data_finalizer(JSRuntime *rt, JSValue val);
|
|
static void js_c_function_data_mark(JSRuntime *rt, JSValue val,
|
|
JS_MarkFunc *mark_func);
|
|
static JSValue js_c_function_data_call(JSContext *ctx, JSValue func_obj,
|
|
JSValue this_val,
|
|
int argc, JSValue *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, JSValue val, BOOL flag);
|
|
|
|
static JSValue js_new_callsite(JSContext *ctx, JSCallSiteData *csd);
|
|
static void js_new_callsite_data(JSContext *ctx, JSCallSiteData *csd, JSStackFrame *sf);
|
|
static void js_new_callsite_data2(JSContext *ctx, JSCallSiteData *csd, const char *filename, int line_num, int col_num);
|
|
static void _JS_AddIntrinsicCallSite(JSContext *ctx);
|
|
|
|
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 inline BOOL double_is_int32(double d)
|
|
{
|
|
uint64_t u, e;
|
|
JSFloat64Union t;
|
|
|
|
t.d = d;
|
|
u = t.u64;
|
|
|
|
e = ((u >> 52) & 0x7FF) - 1023;
|
|
if (e > 30) {
|
|
// accept 0, INT32_MIN, reject too large, too small, nan, inf, -0
|
|
return !u || (u == 0xc1e0000000000000);
|
|
} else {
|
|
// shift out sign, exponent and whole part bits
|
|
// value is fractional if remaining low bits are non-zero
|
|
return !(u << 12 << e);
|
|
}
|
|
}
|
|
|
|
static JSValue js_float64(double d)
|
|
{
|
|
return __JS_NewFloat64(d);
|
|
}
|
|
|
|
static int compare_u32(uint32_t a, uint32_t b)
|
|
{
|
|
return -(a < b) + (b < a); // -1, 0 or 1
|
|
}
|
|
|
|
static JSValue js_int32(int32_t v)
|
|
{
|
|
return JS_MKVAL(JS_TAG_INT, v);
|
|
}
|
|
|
|
static JSValue js_uint32(uint32_t v)
|
|
{
|
|
if (v <= INT32_MAX)
|
|
return js_int32(v);
|
|
else
|
|
return js_float64(v);
|
|
}
|
|
|
|
static JSValue js_int64(int64_t v)
|
|
{
|
|
if (v >= INT32_MIN && v <= INT32_MAX)
|
|
return js_int32(v);
|
|
else
|
|
return js_float64(v);
|
|
}
|
|
|
|
#define JS_NewInt64(ctx, val) js_int64(val)
|
|
|
|
static JSValue js_number(double d)
|
|
{
|
|
if (double_is_int32(d))
|
|
return js_int32((int32_t)d);
|
|
else
|
|
return js_float64(d);
|
|
}
|
|
|
|
JSValue JS_NewNumber(JSContext *ctx, double d)
|
|
{
|
|
return js_number(d);
|
|
}
|
|
|
|
static JSValue js_bool(JS_BOOL v)
|
|
{
|
|
return JS_MKVAL(JS_TAG_BOOL, (v != 0));
|
|
}
|
|
|
|
static JSValue js_dup(JSValue v)
|
|
{
|
|
if (JS_VALUE_HAS_REF_COUNT(v)) {
|
|
JSRefCountHeader *p = (JSRefCountHeader *)JS_VALUE_GET_PTR(v);
|
|
p->ref_count++;
|
|
}
|
|
return v;
|
|
}
|
|
|
|
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
|
|
if (check_dump_flag(rt, 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);
|
|
}
|
|
|
|
/* 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);
|
|
}
|
|
|
|
/* Throw out of memory in case of error */
|
|
void *js_malloc(JSContext *ctx, size_t size)
|
|
{
|
|
void *ptr;
|
|
ptr = js_malloc_rt(ctx->rt, size);
|
|
if (unlikely(!ptr)) {
|
|
JS_ThrowOutOfMemory(ctx);
|
|
return NULL;
|
|
}
|
|
return ptr;
|
|
}
|
|
|
|
/* Throw out of memory in case of error */
|
|
void *js_mallocz(JSContext *ctx, size_t size)
|
|
{
|
|
void *ptr;
|
|
ptr = js_mallocz_rt(ctx->rt, size);
|
|
if (unlikely(!ptr)) {
|
|
JS_ThrowOutOfMemory(ctx);
|
|
return NULL;
|
|
}
|
|
return ptr;
|
|
}
|
|
|
|
void js_free(JSContext *ctx, void *ptr)
|
|
{
|
|
js_free_rt(ctx->rt, ptr);
|
|
}
|
|
|
|
/* Throw out of memory in case of error */
|
|
void *js_realloc(JSContext *ctx, void *ptr, size_t size)
|
|
{
|
|
void *ret;
|
|
ret = js_realloc_rt(ctx->rt, ptr, size);
|
|
if (unlikely(!ret && size != 0)) {
|
|
JS_ThrowOutOfMemory(ctx);
|
|
return NULL;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/* store extra allocated size in *pslack if successful */
|
|
void *js_realloc2(JSContext *ctx, void *ptr, size_t size, size_t *pslack)
|
|
{
|
|
void *ret;
|
|
ret = js_realloc_rt(ctx->rt, ptr, size);
|
|
if (unlikely(!ret && size != 0)) {
|
|
JS_ThrowOutOfMemory(ctx);
|
|
return NULL;
|
|
}
|
|
if (pslack) {
|
|
size_t new_size = js_malloc_usable_size_rt(ctx->rt, ret);
|
|
*pslack = (new_size > size) ? new_size - size : 0;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
size_t js_malloc_usable_size(JSContext *ctx, const void *ptr)
|
|
{
|
|
return js_malloc_usable_size_rt(ctx->rt, ptr);
|
|
}
|
|
|
|
/* Throw out of memory exception in case of error */
|
|
char *js_strndup(JSContext *ctx, const char *s, size_t n)
|
|
{
|
|
char *ptr;
|
|
ptr = js_malloc(ctx, n + 1);
|
|
if (ptr) {
|
|
memcpy(ptr, s, n);
|
|
ptr[n] = '\0';
|
|
}
|
|
return ptr;
|
|
}
|
|
|
|
char *js_strdup(JSContext *ctx, const char *str)
|
|
{
|
|
return js_strndup(ctx, str, strlen(str));
|
|
}
|
|
|
|
static no_inline int js_realloc_array(JSContext *ctx, void **parray,
|
|
int elem_size, int *psize, int req_size)
|
|
{
|
|
int new_size;
|
|
size_t slack;
|
|
void *new_array;
|
|
/* XXX: potential arithmetic overflow */
|
|
new_size = max_int(req_size, *psize * 3 / 2);
|
|
new_array = js_realloc2(ctx, *parray, new_size * elem_size, &slack);
|
|
if (!new_array)
|
|
return -1;
|
|
new_size += slack / elem_size;
|
|
*psize = new_size;
|
|
*parray = new_array;
|
|
return 0;
|
|
}
|
|
|
|
/* resize the array and update its size if req_size > *psize */
|
|
static inline int js_resize_array(JSContext *ctx, void **parray, int elem_size,
|
|
int *psize, int req_size)
|
|
{
|
|
if (unlikely(req_size > *psize))
|
|
return js_realloc_array(ctx, parray, elem_size, psize, req_size);
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
static inline void js_dbuf_init(JSContext *ctx, DynBuf *s)
|
|
{
|
|
dbuf_init2(s, ctx->rt, (DynBufReallocFunc *)js_realloc_rt);
|
|
}
|
|
|
|
static inline int is_digit(int c) {
|
|
return c >= '0' && c <= '9';
|
|
}
|
|
|
|
static inline int string_get(const JSString *p, int idx) {
|
|
return p->is_wide_char ? p->u.str16[idx] : p->u.str8[idx];
|
|
}
|
|
|
|
typedef struct JSClassShortDef {
|
|
JSAtom class_name;
|
|
JSClassFinalizer *finalizer;
|
|
JSClassGCMark *gc_mark;
|
|
} JSClassShortDef;
|
|
|
|
static JSClassShortDef const js_std_class_def[] = {
|
|
{ JS_ATOM_Object, NULL, NULL }, /* JS_CLASS_OBJECT */
|
|
{ JS_ATOM_Array, js_array_finalizer, js_array_mark }, /* JS_CLASS_ARRAY */
|
|
{ JS_ATOM_Error, NULL, NULL }, /* JS_CLASS_ERROR */
|
|
{ JS_ATOM_Number, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_NUMBER */
|
|
{ JS_ATOM_String, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_STRING */
|
|
{ JS_ATOM_Boolean, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_BOOLEAN */
|
|
{ JS_ATOM_Symbol, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_SYMBOL */
|
|
{ JS_ATOM_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 */
|
|
{ JS_ATOM_BigInt64Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_BIG_INT64_ARRAY */
|
|
{ JS_ATOM_BigUint64Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_BIG_UINT64_ARRAY */
|
|
{ JS_ATOM_Float32Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_FLOAT32_ARRAY */
|
|
{ JS_ATOM_Float64Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_FLOAT64_ARRAY */
|
|
{ JS_ATOM_DataView, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_DATAVIEW */
|
|
{ JS_ATOM_BigInt, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_BIG_INT */
|
|
{ JS_ATOM_Map, js_map_finalizer, js_map_mark }, /* JS_CLASS_MAP */
|
|
{ JS_ATOM_Set, js_map_finalizer, js_map_mark }, /* JS_CLASS_SET */
|
|
{ JS_ATOM_WeakMap, js_map_finalizer, js_map_mark }, /* JS_CLASS_WEAKMAP */
|
|
{ JS_ATOM_WeakSet, js_map_finalizer, js_map_mark }, /* JS_CLASS_WEAKSET */
|
|
{ JS_ATOM_Map_Iterator, js_map_iterator_finalizer, js_map_iterator_mark }, /* JS_CLASS_MAP_ITERATOR */
|
|
{ JS_ATOM_Set_Iterator, js_map_iterator_finalizer, js_map_iterator_mark }, /* JS_CLASS_SET_ITERATOR */
|
|
{ JS_ATOM_Array_Iterator, js_array_iterator_finalizer, js_array_iterator_mark }, /* JS_CLASS_ARRAY_ITERATOR */
|
|
{ JS_ATOM_String_Iterator, js_array_iterator_finalizer, js_array_iterator_mark }, /* JS_CLASS_STRING_ITERATOR */
|
|
{ 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;
|
|
}
|
|
|
|
/* 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);
|
|
}
|
|
|
|
JSRuntime *JS_NewRuntime2(const JSMallocFunctions *mf, void *opaque)
|
|
{
|
|
JSRuntime *rt;
|
|
JSMallocState ms;
|
|
|
|
memset(&ms, 0, sizeof(ms));
|
|
ms.opaque = opaque;
|
|
ms.malloc_limit = 0;
|
|
|
|
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;
|
|
|
|
bf_context_init(&rt->bf_ctx, js_bf_realloc, rt);
|
|
|
|
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->js_class_id_alloc = JS_CLASS_INIT_COUNT;
|
|
|
|
rt->stack_size = JS_DEFAULT_STACK_SIZE;
|
|
#ifdef __ASAN__
|
|
rt->stack_size *= 2; // stack frames are bigger under AddressSanitizer
|
|
#endif
|
|
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;
|
|
}
|
|
|
|
static void *js_def_malloc(JSMallocState *s, size_t size)
|
|
{
|
|
void *ptr;
|
|
|
|
/* Do not allocate zero bytes: behavior is platform dependent */
|
|
assert(size != 0);
|
|
|
|
/* When malloc_limit is 0 (unlimited), malloc_limit - 1 will be SIZE_MAX. */
|
|
if (unlikely(s->malloc_size + size > s->malloc_limit - 1))
|
|
return NULL;
|
|
|
|
ptr = malloc(size);
|
|
if (!ptr)
|
|
return NULL;
|
|
|
|
s->malloc_count++;
|
|
s->malloc_size += js__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__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__malloc_usable_size(ptr);
|
|
if (size == 0) {
|
|
s->malloc_count--;
|
|
s->malloc_size -= old_size + MALLOC_OVERHEAD;
|
|
free(ptr);
|
|
return NULL;
|
|
}
|
|
/* When malloc_limit is 0 (unlimited), malloc_limit - 1 will be SIZE_MAX. */
|
|
if (s->malloc_size + size - old_size > s->malloc_limit - 1)
|
|
return NULL;
|
|
|
|
ptr = realloc(ptr, size);
|
|
if (!ptr)
|
|
return NULL;
|
|
|
|
s->malloc_size += js__malloc_usable_size(ptr) - old_size;
|
|
return ptr;
|
|
}
|
|
|
|
static const JSMallocFunctions def_malloc_funcs = {
|
|
js_def_malloc,
|
|
js_def_free,
|
|
js_def_realloc,
|
|
js__malloc_usable_size
|
|
};
|
|
|
|
JSRuntime *JS_NewRuntime(void)
|
|
{
|
|
return JS_NewRuntime2(&def_malloc_funcs, NULL);
|
|
}
|
|
|
|
void JS_SetMemoryLimit(JSRuntime *rt, size_t limit)
|
|
{
|
|
rt->malloc_state.malloc_limit = limit;
|
|
}
|
|
|
|
void JS_SetDumpFlags(JSRuntime *rt, uint64_t flags)
|
|
{
|
|
rt->dump_flags = flags;
|
|
}
|
|
|
|
/* 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, JSValue *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_dup(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, 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 */
|
|
if (check_dump_flag(rt, DUMP_LEAKS)) {
|
|
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);
|
|
|
|
bf_context_end(&rt->bf_ctx);
|
|
|
|
#ifdef DUMP_ATOM_LEAKS
|
|
/* only the atoms defined in JS_InitAtoms() should be left */
|
|
if (check_dump_flag(rt, DUMP_ATOM_LEAKS)) {
|
|
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 (check_dump_flag(rt, DUMP_LEAKS) && !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");
|
|
}
|
|
if (check_dump_flag(rt, DUMP_LEAKS)) {
|
|
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);
|
|
ctx->bf_ctx = &rt->bf_ctx;
|
|
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;
|
|
ctx->error_ctor = JS_NULL;
|
|
ctx->error_prepare_stack = JS_UNDEFINED;
|
|
ctx->error_stack_trace_limit = 10;
|
|
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_AddIntrinsicRegExp(ctx);
|
|
JS_AddIntrinsicJSON(ctx);
|
|
JS_AddIntrinsicProxy(ctx);
|
|
JS_AddIntrinsicMapSet(ctx);
|
|
JS_AddIntrinsicTypedArrays(ctx);
|
|
JS_AddIntrinsicPromise(ctx);
|
|
JS_AddIntrinsicBigInt(ctx);
|
|
JS_AddIntrinsicWeakRef(ctx);
|
|
|
|
JS_AddPerformance(ctx);
|
|
|
|
return ctx;
|
|
}
|
|
|
|
void *JS_GetContextOpaque(JSContext *ctx)
|
|
{
|
|
return ctx->user_opaque;
|
|
}
|
|
|
|
void JS_SetContextOpaque(JSContext *ctx, void *opaque)
|
|
{
|
|
ctx->user_opaque = opaque;
|
|
}
|
|
|
|
/* set the new value and free the old value after (freeing the value
|
|
can reallocate the object data) */
|
|
static inline void set_value(JSContext *ctx, JSValue *pval, JSValue new_val)
|
|
{
|
|
JSValue old_val;
|
|
old_val = *pval;
|
|
*pval = new_val;
|
|
JS_FreeValue(ctx, old_val);
|
|
}
|
|
|
|
void JS_SetClassProto(JSContext *ctx, JSClassID class_id, JSValue obj)
|
|
{
|
|
JSRuntime *rt = ctx->rt;
|
|
assert(class_id < rt->class_count);
|
|
set_value(ctx, &ctx->class_proto[class_id], obj);
|
|
}
|
|
|
|
JSValue JS_GetClassProto(JSContext *ctx, JSClassID class_id)
|
|
{
|
|
JSRuntime *rt = ctx->rt;
|
|
assert(class_id < rt->class_count);
|
|
return js_dup(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
|
|
&& !m->eval_mark)) {
|
|
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);
|
|
}
|
|
JS_MarkValue(rt, ctx->error_ctor, mark_func);
|
|
JS_MarkValue(rt, ctx->error_prepare_stack, 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
|
|
if (check_dump_flag(rt, DUMP_ATOMS))
|
|
JS_DumpAtoms(ctx->rt);
|
|
#endif
|
|
#ifdef DUMP_SHAPES
|
|
if (check_dump_flag(rt, DUMP_SHAPES))
|
|
JS_DumpShapes(ctx->rt);
|
|
#endif
|
|
#ifdef DUMP_OBJECTS
|
|
if (check_dump_flag(rt, 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
|
|
if (check_dump_flag(rt, 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]);
|
|
}
|
|
JS_FreeValue(ctx, ctx->error_ctor);
|
|
JS_FreeValue(ctx, ctx->error_prepare_stack);
|
|
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 defined(__wasi__)
|
|
rt->stack_limit = 0; /* no limit */
|
|
#else
|
|
if (rt->stack_size == 0) {
|
|
rt->stack_limit = 0; /* no limit */
|
|
} else {
|
|
rt->stack_limit = rt->stack_top - rt->stack_size;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
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));
|
|
}
|
|
|
|
/* 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)
|
|
{
|
|
#ifdef DUMP_ATOM_LEAKS
|
|
return (int32_t)v <= 0;
|
|
#else
|
|
return (int32_t)v < JS_ATOM_END;
|
|
#endif
|
|
}
|
|
|
|
static inline BOOL __JS_AtomIsTaggedInt(JSAtom v)
|
|
{
|
|
return (v & JS_ATOM_TAG_INT) != 0;
|
|
}
|
|
|
|
static inline JSAtom __JS_AtomFromUInt32(uint32_t v)
|
|
{
|
|
return v | JS_ATOM_TAG_INT;
|
|
}
|
|
|
|
static inline uint32_t __JS_AtomToUInt32(JSAtom atom)
|
|
{
|
|
return atom & ~JS_ATOM_TAG_INT;
|
|
}
|
|
|
|
static inline int is_num(int c)
|
|
{
|
|
return c >= '0' && c <= '9';
|
|
}
|
|
|
|
/* return TRUE if the string is a number n with 0 <= n <= 2^32-1 */
|
|
static inline BOOL is_num_string(uint32_t *pval, const JSString *p)
|
|
{
|
|
uint32_t n;
|
|
uint64_t n64;
|
|
int c, i, len;
|
|
|
|
len = p->len;
|
|
if (len == 0 || len > 10)
|
|
return FALSE;
|
|
c = string_get(p, 0);
|
|
if (is_num(c)) {
|
|
if (c == '0') {
|
|
if (len != 1)
|
|
return FALSE;
|
|
n = 0;
|
|
} else {
|
|
n = c - '0';
|
|
for(i = 1; i < len; i++) {
|
|
c = string_get(p, i);
|
|
if (!is_num(c))
|
|
return FALSE;
|
|
n64 = (uint64_t)n * 10 + (c - '0');
|
|
if ((n64 >> 32) != 0)
|
|
return FALSE;
|
|
n = n64;
|
|
}
|
|
}
|
|
*pval = n;
|
|
return TRUE;
|
|
} else {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* XXX: could use faster version ? */
|
|
static inline uint32_t hash_string8(const uint8_t *str, size_t len, uint32_t h)
|
|
{
|
|
size_t i;
|
|
|
|
for(i = 0; i < len; i++)
|
|
h = h * 263 + str[i];
|
|
return h;
|
|
}
|
|
|
|
static inline uint32_t hash_string16(const uint16_t *str,
|
|
size_t len, uint32_t h)
|
|
{
|
|
size_t i;
|
|
|
|
for(i = 0; i < len; i++)
|
|
h = h * 263 + str[i];
|
|
return h;
|
|
}
|
|
|
|
static uint32_t hash_string(const JSString *str, uint32_t h)
|
|
{
|
|
if (str->is_wide_char)
|
|
h = hash_string16(str->u.str16, str->len, h);
|
|
else
|
|
h = hash_string8(str->u.str8, str->len, h);
|
|
return h;
|
|
}
|
|
|
|
static __maybe_unused void JS_DumpString(JSRuntime *rt,
|
|
const JSString *p)
|
|
{
|
|
int i, c, sep;
|
|
|
|
if (p == NULL) {
|
|
printf("<null>");
|
|
return;
|
|
}
|
|
if (p->header.ref_count != 1)
|
|
printf("%d", p->header.ref_count);
|
|
if (p->is_wide_char)
|
|
putchar('L');
|
|
sep = '\"';
|
|
putchar(sep);
|
|
for(i = 0; i < p->len; i++) {
|
|
c = string_get(p, 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();
|
|
}
|
|
return (JSAtomKindEnum){-1}; // pacify compiler
|
|
}
|
|
|
|
static BOOL JS_AtomIsString(JSContext *ctx, JSAtom v)
|
|
{
|
|
return JS_AtomGetKind(ctx, v) == JS_ATOM_KIND_STRING;
|
|
}
|
|
|
|
static JSAtom js_get_atom_index(JSRuntime *rt, JSAtomStruct *p)
|
|
{
|
|
uint32_t i = p->hash_next; /* atom_index */
|
|
if (p->atom_type != JS_ATOM_TYPE_SYMBOL) {
|
|
JSAtomStruct *p1;
|
|
|
|
i = rt->atom_hash[p->hash & (rt->atom_hash_size - 1)];
|
|
p1 = rt->atom_array[i];
|
|
while (p1 != p) {
|
|
assert(i != 0);
|
|
i = p1->hash_next;
|
|
p1 = rt->atom_array[i];
|
|
}
|
|
}
|
|
return i;
|
|
}
|
|
|
|
/* string case (internal). Return JS_ATOM_NULL if error. 'str' is
|
|
freed. */
|
|
static JSAtom __JS_NewAtom(JSRuntime *rt, JSString *str, int atom_type)
|
|
{
|
|
uint32_t h, h1, i;
|
|
JSAtomStruct *p;
|
|
int len;
|
|
|
|
if (atom_type < JS_ATOM_TYPE_SYMBOL) {
|
|
/* str is not NULL */
|
|
if (str->atom_type == atom_type) {
|
|
/* str is the atom, return its index */
|
|
i = js_get_atom_index(rt, str);
|
|
/* reduce string refcount and increase atom's unless constant */
|
|
if (__JS_AtomIsConst(i))
|
|
str->header.ref_count--;
|
|
return i;
|
|
}
|
|
/* try and locate an already registered atom */
|
|
len = str->len;
|
|
h = hash_string(str, atom_type);
|
|
h &= JS_ATOM_HASH_MASK;
|
|
h1 = h & (rt->atom_hash_size - 1);
|
|
i = rt->atom_hash[h1];
|
|
while (i != 0) {
|
|
p = rt->atom_array[i];
|
|
if (p->hash == h &&
|
|
p->atom_type == atom_type &&
|
|
p->len == len &&
|
|
js_string_memcmp(p, 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;
|
|
p->first_weak_ref = NULL;
|
|
|
|
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;
|
|
}
|
|
|
|
// XXX: `str` must be pure ASCII. No UTF-8 encoded strings
|
|
// XXX: `str` must not be the string representation of a small integer
|
|
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);
|
|
}
|
|
|
|
// XXX: `str` must be raw 8-bit contents. No UTF-8 encoded strings
|
|
static JSAtom __JS_FindAtom(JSRuntime *rt, const char *str, size_t len,
|
|
int atom_type)
|
|
{
|
|
uint32_t h, h1, i;
|
|
JSAtomStruct *p;
|
|
|
|
h = hash_string8((const uint8_t *)str, len, JS_ATOM_TYPE_STRING);
|
|
h &= JS_ATOM_HASH_MASK;
|
|
h1 = h & (rt->atom_hash_size - 1);
|
|
i = rt->atom_hash[h1];
|
|
while (i != 0) {
|
|
p = rt->atom_array[i];
|
|
if (p->hash == h &&
|
|
p->atom_type == JS_ATOM_TYPE_STRING &&
|
|
p->len == len &&
|
|
p->is_wide_char == 0 &&
|
|
memcmp(p->u.str8, str, len) == 0) {
|
|
if (!__JS_AtomIsConst(i))
|
|
p->header.ref_count++;
|
|
return i;
|
|
}
|
|
i = p->hash_next;
|
|
}
|
|
return JS_ATOM_NULL;
|
|
}
|
|
|
|
static void JS_FreeAtomStruct(JSRuntime *rt, JSAtomStruct *p)
|
|
{
|
|
uint32_t i = p->hash_next; /* atom_index */
|
|
if (p->atom_type != JS_ATOM_TYPE_SYMBOL) {
|
|
JSAtomStruct *p0, *p1;
|
|
uint32_t h0;
|
|
|
|
h0 = p->hash & (rt->atom_hash_size - 1);
|
|
i = rt->atom_hash[h0];
|
|
p1 = rt->atom_array[i];
|
|
if (p1 == p) {
|
|
rt->atom_hash[h0] = p1->hash_next;
|
|
} else {
|
|
for(;;) {
|
|
assert(i != 0);
|
|
p0 = p1;
|
|
i = p1->hash_next;
|
|
p1 = rt->atom_array[i];
|
|
if (p1 == p) {
|
|
p0->hash_next = p1->hash_next;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/* insert in free atom list */
|
|
rt->atom_array[i] = atom_set_free(rt->atom_free_index);
|
|
rt->atom_free_index = i;
|
|
if (unlikely(p->first_weak_ref)) {
|
|
reset_weak_ref(rt, &p->first_weak_ref);
|
|
}
|
|
/* 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);
|
|
}
|
|
|
|
/* `str` may be pure ASCII or UTF-8 encoded */
|
|
JSAtom JS_NewAtomLen(JSContext *ctx, const char *str, size_t len)
|
|
{
|
|
JSValue val;
|
|
|
|
if (len == 0 || !is_digit(*str)) {
|
|
// TODO(chqrlie): this does not work if `str` has UTF-8 encoded contents
|
|
// bug example: `({ "\u00c3\u00a9": 1 }).\u00e9` evaluates to `1`.
|
|
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));
|
|
}
|
|
|
|
/* `str` may be pure ASCII or UTF-8 encoded */
|
|
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[16];
|
|
size_t len = u32toa(buf, n);
|
|
JSValue val = js_new_string8_len(ctx, buf, len);
|
|
if (JS_IsException(val))
|
|
return JS_ATOM_NULL;
|
|
return __JS_NewAtom(ctx->rt, JS_VALUE_GET_STRING(val),
|
|
JS_ATOM_TYPE_STRING);
|
|
}
|
|
}
|
|
|
|
static JSAtom JS_NewAtomInt64(JSContext *ctx, int64_t n)
|
|
{
|
|
if ((uint64_t)n <= JS_ATOM_MAX_INT) {
|
|
return __JS_AtomFromUInt32((uint32_t)n);
|
|
} else {
|
|
char buf[24];
|
|
size_t len = i64toa(buf, n);
|
|
JSValue val = js_new_string8_len(ctx, buf, len);
|
|
if (JS_IsException(val))
|
|
return JS_ATOM_NULL;
|
|
return __JS_NewAtom(ctx->rt, JS_VALUE_GET_STRING(val),
|
|
JS_ATOM_TYPE_STRING);
|
|
}
|
|
}
|
|
|
|
/* 'p' is freed */
|
|
static JSValue JS_NewSymbolInternal(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_dup(JS_MKPTR(JS_TAG_STRING, p));
|
|
return JS_NewSymbolInternal(ctx, p, atom_type);
|
|
}
|
|
|
|
/* `description` may be pure ASCII or UTF-8 encoded */
|
|
JSValue JS_NewSymbol(JSContext *ctx, const char *description, JS_BOOL is_global)
|
|
{
|
|
JSAtom atom = JS_NewAtom(ctx, description);
|
|
if (atom == JS_ATOM_NULL)
|
|
return JS_EXCEPTION;
|
|
return JS_NewSymbolFromAtom(ctx, atom, is_global ? JS_ATOM_TYPE_GLOBAL_SYMBOL : JS_ATOM_TYPE_SYMBOL);
|
|
}
|
|
|
|
#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 if (atom == JS_ATOM_NULL) {
|
|
snprintf(buf, buf_size, "<null>");
|
|
} else if (atom >= rt->atom_size) {
|
|
assert(atom < rt->atom_size);
|
|
snprintf(buf, buf_size, "<invalid %x>", atom);
|
|
} else {
|
|
JSAtomStruct *p = rt->atom_array[atom];
|
|
*buf = '\0';
|
|
if (atom_is_free(p)) {
|
|
assert(!atom_is_free(p));
|
|
snprintf(buf, buf_size, "<free %x>", atom);
|
|
} else if (p != NULL) {
|
|
JSString *str = p;
|
|
if (str->is_wide_char) {
|
|
/* encode surrogates correctly */
|
|
utf8_encode_buf16(buf, buf_size, str->u.str16, str->len);
|
|
} else {
|
|
/* special case ASCII strings */
|
|
int i, c = 0;
|
|
for(i = 0; i < str->len; i++) {
|
|
c |= str->u.str8[i];
|
|
}
|
|
if (c < 0x80)
|
|
return (const char *)str->u.str8;
|
|
utf8_encode_buf8(buf, buf_size, str->u.str8, str->len);
|
|
}
|
|
}
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
static const char *JS_AtomGetStr(JSContext *ctx, char *buf, int buf_size, JSAtom atom)
|
|
{
|
|
return JS_AtomGetStrRT(ctx->rt, buf, buf_size, atom);
|
|
}
|
|
|
|
static JSValue __JS_AtomToValue(JSContext *ctx, JSAtom atom, BOOL force_string)
|
|
{
|
|
char buf[ATOM_GET_STR_BUF_SIZE];
|
|
|
|
if (__JS_AtomIsTaggedInt(atom)) {
|
|
size_t len = u32toa(buf, __JS_AtomToUInt32(atom));
|
|
return js_new_string8_len(ctx, buf, len);
|
|
} else {
|
|
JSRuntime *rt = ctx->rt;
|
|
JSAtomStruct *p;
|
|
assert(atom < rt->atom_size);
|
|
p = rt->atom_array[atom];
|
|
if (p->atom_type == JS_ATOM_TYPE_STRING) {
|
|
goto ret_string;
|
|
} else if (force_string) {
|
|
if (p->len == 0 && p->is_wide_char != 0) {
|
|
/* no description string */
|
|
p = rt->atom_array[JS_ATOM_empty_string];
|
|
}
|
|
ret_string:
|
|
return js_dup(JS_MKPTR(JS_TAG_STRING, p));
|
|
} else {
|
|
return js_dup(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_int32(__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_float64(-0.0);
|
|
}
|
|
}
|
|
if (!is_num(c)) {
|
|
if (!(c =='I' && (r_end - r) == 8 &&
|
|
!memcmp(r + 1, "nfinity", 7)))
|
|
return JS_UNDEFINED;
|
|
}
|
|
}
|
|
/* 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 */
|
|
/* `str1` may be pure ASCII or UTF-8 encoded */
|
|
// TODO(chqrlie): use string concatenation instead of UTF-8 conversion
|
|
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];
|
|
u32toa(buf, n);
|
|
return js_atom_concat_str(ctx, name, buf);
|
|
}
|
|
|
|
static inline BOOL JS_IsEmptyString(JSValue 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(JSRuntime *rt, JSClassID *pclass_id)
|
|
{
|
|
JSClassID class_id = *pclass_id;
|
|
if (class_id == 0) {
|
|
class_id = rt->js_class_id_alloc++;
|
|
*pclass_id = class_id;
|
|
}
|
|
return class_id;
|
|
}
|
|
|
|
JSClassID JS_GetClassID(JSValue v)
|
|
{
|
|
JSObject *p;
|
|
if (JS_VALUE_GET_TAG(v) != JS_TAG_OBJECT)
|
|
return JS_INVALID_CLASS_ID;
|
|
p = JS_VALUE_GET_OBJ(v);
|
|
return p->class_id;
|
|
}
|
|
|
|
BOOL JS_IsRegisteredClass(JSRuntime *rt, JSClassID class_id)
|
|
{
|
|
return (class_id < rt->class_count &&
|
|
rt->class_array[class_id].class_id != 0);
|
|
}
|
|
|
|
/* create a new object internal class. Return -1 if error, 0 if
|
|
OK. The finalizer can be NULL if none is needed. */
|
|
static int JS_NewClass1(JSRuntime *rt, JSClassID class_id,
|
|
const JSClassDef *class_def, JSAtom name)
|
|
{
|
|
int new_size, i;
|
|
JSClass *cl, *new_class_array;
|
|
struct list_head *el;
|
|
|
|
if (class_id >= (1 << 16))
|
|
return -1;
|
|
if (class_id < rt->class_count &&
|
|
rt->class_array[class_id].class_id != 0)
|
|
return -1;
|
|
|
|
if (class_id >= rt->class_count) {
|
|
new_size = max_int(JS_CLASS_INIT_COUNT,
|
|
max_int(class_id + 1, rt->class_count * 3 / 2));
|
|
|
|
/* reallocate the context class prototype array, if any */
|
|
list_for_each(el, &rt->context_list) {
|
|
JSContext *ctx = list_entry(el, JSContext, link);
|
|
JSValue *new_tab;
|
|
new_tab = js_realloc_rt(rt, ctx->class_proto,
|
|
sizeof(ctx->class_proto[0]) * new_size);
|
|
if (!new_tab)
|
|
return -1;
|
|
for(i = rt->class_count; i < new_size; i++)
|
|
new_tab[i] = JS_NULL;
|
|
ctx->class_proto = new_tab;
|
|
}
|
|
/* reallocate the class array */
|
|
new_class_array = js_realloc_rt(rt, rt->class_array,
|
|
sizeof(JSClass) * new_size);
|
|
if (!new_class_array)
|
|
return -1;
|
|
memset(new_class_array + rt->class_count, 0,
|
|
(new_size - rt->class_count) * sizeof(JSClass));
|
|
rt->class_array = new_class_array;
|
|
rt->class_count = new_size;
|
|
}
|
|
cl = &rt->class_array[class_id];
|
|
cl->class_id = class_id;
|
|
cl->class_name = JS_DupAtomRT(rt, name);
|
|
cl->finalizer = class_def->finalizer;
|
|
cl->gc_mark = class_def->gc_mark;
|
|
cl->call = class_def->call;
|
|
cl->exotic = class_def->exotic;
|
|
return 0;
|
|
}
|
|
|
|
int JS_NewClass(JSRuntime *rt, JSClassID class_id, const JSClassDef *class_def)
|
|
{
|
|
int ret, len;
|
|
JSAtom name;
|
|
|
|
// XXX: class_def->class_name must be raw 8-bit contents. No UTF-8 encoded strings
|
|
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;
|
|
}
|
|
|
|
// XXX: `buf` contains raw 8-bit data, no UTF-8 decoding is performed
|
|
// XXX: no special case for len == 0
|
|
static JSValue js_new_string8_len(JSContext *ctx, const char *buf, int len)
|
|
{
|
|
JSString *str;
|
|
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);
|
|
}
|
|
|
|
// XXX: `buf` contains raw 8-bit data, no UTF-8 decoding is performed
|
|
// XXX: no special case for the empty string
|
|
static inline JSValue js_new_string8(JSContext *ctx, const char *str)
|
|
{
|
|
return js_new_string8_len(ctx, str, strlen(str));
|
|
}
|
|
|
|
static JSValue js_new_string16_len(JSContext *ctx, const uint16_t *buf, int len)
|
|
{
|
|
JSString *str;
|
|
str = js_alloc_string(ctx, len, 1);
|
|
if (!str)
|
|
return JS_EXCEPTION;
|
|
memcpy(str->u.str16, buf, len * 2);
|
|
return JS_MKPTR(JS_TAG_STRING, str);
|
|
}
|
|
|
|
static JSValue js_new_string_char(JSContext *ctx, uint16_t c)
|
|
{
|
|
if (c < 0x100) {
|
|
char ch8 = c;
|
|
return js_new_string8_len(ctx, &ch8, 1);
|
|
} else {
|
|
uint16_t ch16 = c;
|
|
return js_new_string16_len(ctx, &ch16, 1);
|
|
}
|
|
}
|
|
|
|
static JSValue js_sub_string(JSContext *ctx, JSString *p, int start, int end)
|
|
{
|
|
int len = end - start;
|
|
if (start == 0 && end == p->len) {
|
|
return js_dup(JS_MKPTR(JS_TAG_STRING, p));
|
|
}
|
|
if (len <= 0) {
|
|
return JS_AtomToString(ctx, JS_ATOM_empty_string);
|
|
}
|
|
if (p->is_wide_char) {
|
|
JSString *str;
|
|
int i;
|
|
uint16_t c = 0;
|
|
for (i = start; i < end; i++) {
|
|
c |= p->u.str16[i];
|
|
}
|
|
if (c > 0xFF)
|
|
return js_new_string16_len(ctx, p->u.str16 + start, len);
|
|
|
|
str = js_alloc_string(ctx, len, 0);
|
|
if (!str)
|
|
return JS_EXCEPTION;
|
|
for (i = 0; i < len; i++) {
|
|
str->u.str8[i] = p->u.str16[start + i];
|
|
}
|
|
str->u.str8[len] = '\0';
|
|
return JS_MKPTR(JS_TAG_STRING, str);
|
|
} else {
|
|
return js_new_string8_len(ctx, (const char *)(p->u.str8 + start), len);
|
|
}
|
|
}
|
|
|
|
typedef struct StringBuffer {
|
|
JSContext *ctx;
|
|
JSString *str;
|
|
int len;
|
|
int size;
|
|
int is_wide_char;
|
|
int error_status;
|
|
} StringBuffer;
|
|
|
|
/* It is valid to call string_buffer_end() and all string_buffer functions even
|
|
if string_buffer_init() or another string_buffer function returns an error.
|
|
If the error_status is set, string_buffer_end() returns JS_EXCEPTION.
|
|
*/
|
|
static int string_buffer_init2(JSContext *ctx, StringBuffer *s, int size,
|
|
int is_wide)
|
|
{
|
|
s->ctx = ctx;
|
|
s->size = size;
|
|
s->len = 0;
|
|
s->is_wide_char = is_wide;
|
|
s->error_status = 0;
|
|
s->str = js_alloc_string(ctx, size, is_wide);
|
|
if (unlikely(!s->str)) {
|
|
s->size = 0;
|
|
return s->error_status = -1;
|
|
}
|
|
#ifdef DUMP_LEAKS
|
|
/* the StringBuffer may reallocate the JSString, only link it at the end */
|
|
list_del(&s->str->link);
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
static inline int string_buffer_init(JSContext *ctx, StringBuffer *s, int size)
|
|
{
|
|
return string_buffer_init2(ctx, s, size, 0);
|
|
}
|
|
|
|
static void string_buffer_free(StringBuffer *s)
|
|
{
|
|
js_free(s->ctx, s->str);
|
|
s->str = NULL;
|
|
}
|
|
|
|
static int string_buffer_set_error(StringBuffer *s)
|
|
{
|
|
js_free(s->ctx, s->str);
|
|
s->str = NULL;
|
|
s->size = 0;
|
|
s->len = 0;
|
|
return s->error_status = -1;
|
|
}
|
|
|
|
static no_inline int string_buffer_widen(StringBuffer *s, int size)
|
|
{
|
|
JSString *str;
|
|
size_t slack;
|
|
int i;
|
|
|
|
if (s->error_status)
|
|
return -1;
|
|
|
|
str = js_realloc2(s->ctx, s->str, sizeof(JSString) + (size << 1), &slack);
|
|
if (!str)
|
|
return string_buffer_set_error(s);
|
|
size += slack >> 1;
|
|
for(i = s->len; i-- > 0;) {
|
|
str->u.str16[i] = str->u.str8[i];
|
|
}
|
|
s->is_wide_char = 1;
|
|
s->size = size;
|
|
s->str = str;
|
|
return 0;
|
|
}
|
|
|
|
static no_inline int string_buffer_realloc(StringBuffer *s, int new_len, int c)
|
|
{
|
|
JSString *new_str;
|
|
int new_size;
|
|
size_t new_size_bytes, slack;
|
|
|
|
if (s->error_status)
|
|
return -1;
|
|
|
|
if (new_len > JS_STRING_LEN_MAX) {
|
|
JS_ThrowRangeError(s->ctx, "invalid string length");
|
|
return string_buffer_set_error(s);
|
|
}
|
|
new_size = min_int(max_int(new_len, s->size * 3 / 2), JS_STRING_LEN_MAX);
|
|
if (!s->is_wide_char && c >= 0x100) {
|
|
return string_buffer_widen(s, new_size);
|
|
}
|
|
new_size_bytes = sizeof(JSString) + (new_size << s->is_wide_char) + 1 - s->is_wide_char;
|
|
new_str = js_realloc2(s->ctx, s->str, new_size_bytes, &slack);
|
|
if (!new_str)
|
|
return string_buffer_set_error(s);
|
|
new_size = min_int(new_size + (slack >> s->is_wide_char), JS_STRING_LEN_MAX);
|
|
s->size = new_size;
|
|
s->str = new_str;
|
|
return 0;
|
|
}
|
|
|
|
static no_inline int string_buffer_putc_slow(StringBuffer *s, uint32_t c)
|
|
{
|
|
if (unlikely(s->len >= s->size)) {
|
|
if (string_buffer_realloc(s, s->len + 1, c))
|
|
return -1;
|
|
}
|
|
if (s->is_wide_char) {
|
|
s->str->u.str16[s->len++] = c;
|
|
} else if (c < 0x100) {
|
|
s->str->u.str8[s->len++] = c;
|
|
} else {
|
|
if (string_buffer_widen(s, s->size))
|
|
return -1;
|
|
s->str->u.str16[s->len++] = c;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* 0 <= c <= 0xff */
|
|
static int string_buffer_putc8(StringBuffer *s, uint32_t c)
|
|
{
|
|
if (unlikely(s->len >= s->size)) {
|
|
if (string_buffer_realloc(s, s->len + 1, c))
|
|
return -1;
|
|
}
|
|
if (s->is_wide_char) {
|
|
s->str->u.str16[s->len++] = c;
|
|
} else {
|
|
s->str->u.str8[s->len++] = c;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* 0 <= c <= 0xffff */
|
|
static int string_buffer_putc16(StringBuffer *s, uint32_t c)
|
|
{
|
|
if (likely(s->len < s->size)) {
|
|
if (s->is_wide_char) {
|
|
s->str->u.str16[s->len++] = c;
|
|
return 0;
|
|
} else if (c < 0x100) {
|
|
s->str->u.str8[s->len++] = c;
|
|
return 0;
|
|
}
|
|
}
|
|
return string_buffer_putc_slow(s, c);
|
|
}
|
|
|
|
/* 0 <= c <= 0x10ffff */
|
|
static int string_buffer_putc(StringBuffer *s, uint32_t c)
|
|
{
|
|
if (unlikely(c >= 0x10000)) {
|
|
/* surrogate pair */
|
|
if (string_buffer_putc16(s, get_hi_surrogate(c)))
|
|
return -1;
|
|
c = get_lo_surrogate(c);
|
|
}
|
|
return string_buffer_putc16(s, c);
|
|
}
|
|
|
|
static int string_getc(const JSString *p, int *pidx)
|
|
{
|
|
int idx, c, c1;
|
|
idx = *pidx;
|
|
if (p->is_wide_char) {
|
|
c = p->u.str16[idx++];
|
|
if (is_hi_surrogate(c) && idx < p->len) {
|
|
c1 = p->u.str16[idx];
|
|
if (is_lo_surrogate(c1)) {
|
|
c = from_surrogate(c, c1);
|
|
idx++;
|
|
}
|
|
}
|
|
} else {
|
|
c = p->u.str8[idx++];
|
|
}
|
|
*pidx = idx;
|
|
return c;
|
|
}
|
|
|
|
static int string_buffer_write8(StringBuffer *s, const uint8_t *p, int len)
|
|
{
|
|
int i;
|
|
|
|
if (s->len + len > s->size) {
|
|
if (string_buffer_realloc(s, s->len + len, 0))
|
|
return -1;
|
|
}
|
|
if (s->is_wide_char) {
|
|
for (i = 0; i < len; i++) {
|
|
s->str->u.str16[s->len + i] = p[i];
|
|
}
|
|
s->len += len;
|
|
} else {
|
|
memcpy(&s->str->u.str8[s->len], p, len);
|
|
s->len += len;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int string_buffer_write16(StringBuffer *s, const uint16_t *p, int len)
|
|
{
|
|
int c = 0, i;
|
|
|
|
for (i = 0; i < len; i++) {
|
|
c |= p[i];
|
|
}
|
|
if (s->len + len > s->size) {
|
|
if (string_buffer_realloc(s, s->len + len, c))
|
|
return -1;
|
|
} else if (!s->is_wide_char && c >= 0x100) {
|
|
if (string_buffer_widen(s, s->size))
|
|
return -1;
|
|
}
|
|
if (s->is_wide_char) {
|
|
memcpy(&s->str->u.str16[s->len], p, len << 1);
|
|
s->len += len;
|
|
} else {
|
|
for (i = 0; i < len; i++) {
|
|
s->str->u.str8[s->len + i] = p[i];
|
|
}
|
|
s->len += len;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* appending an ASCII string */
|
|
static int string_buffer_puts8(StringBuffer *s, const char *str)
|
|
{
|
|
return string_buffer_write8(s, (const uint8_t *)str, strlen(str));
|
|
}
|
|
|
|
static int string_buffer_concat(StringBuffer *s, const JSString *p,
|
|
uint32_t from, uint32_t to)
|
|
{
|
|
if (to <= from)
|
|
return 0;
|
|
if (p->is_wide_char)
|
|
return string_buffer_write16(s, p->u.str16 + from, to - from);
|
|
else
|
|
return string_buffer_write8(s, p->u.str8 + from, to - from);
|
|
}
|
|
|
|
static int string_buffer_concat_value(StringBuffer *s, JSValue 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)
|
|
{
|
|
JSString *str;
|
|
size_t len;
|
|
int kind;
|
|
|
|
if (buf_len <= 0) {
|
|
return JS_AtomToString(ctx, JS_ATOM_empty_string);
|
|
}
|
|
/* Compute string kind and length: 7-bit, 8-bit, 16-bit, 16-bit UTF-16 */
|
|
kind = utf8_scan(buf, buf_len, &len);
|
|
if (len > JS_STRING_LEN_MAX)
|
|
return JS_ThrowRangeError(ctx, "invalid string length");
|
|
|
|
switch (kind) {
|
|
case UTF8_PLAIN_ASCII:
|
|
str = js_alloc_string(ctx, len, 0);
|
|
if (!str)
|
|
return JS_EXCEPTION;
|
|
memcpy(str->u.str8, buf, len);
|
|
str->u.str8[len] = '\0';
|
|
break;
|
|
case UTF8_NON_ASCII:
|
|
/* buf contains non-ASCII code-points, but limited to 8-bit values */
|
|
str = js_alloc_string(ctx, len, 0);
|
|
if (!str)
|
|
return JS_EXCEPTION;
|
|
utf8_decode_buf8(str->u.str8, len + 1, buf, buf_len);
|
|
break;
|
|
default:
|
|
// This causes a potential problem in JS_ThrowError if message is invalid
|
|
//if (kind & UTF8_HAS_ERRORS)
|
|
// return JS_ThrowRangeError(ctx, "invalid UTF-8 sequence");
|
|
str = js_alloc_string(ctx, len, 1);
|
|
if (!str)
|
|
return JS_EXCEPTION;
|
|
utf8_decode_buf16(str->u.str16, len, buf, buf_len);
|
|
break;
|
|
}
|
|
return JS_MKPTR(JS_TAG_STRING, str);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/* `str` may be pure ASCII or UTF-8 encoded */
|
|
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, JSValue val1, BOOL cesu8)
|
|
{
|
|
JSValue val;
|
|
JSString *str, *str_new;
|
|
int pos, len, c, c1;
|
|
JSObject *p;
|
|
uint8_t *q;
|
|
|
|
if (JS_VALUE_GET_TAG(val1) == JS_TAG_STRING) {
|
|
val = js_dup(val1);
|
|
goto go;
|
|
}
|
|
|
|
val = JS_ToString(ctx, val1);
|
|
if (!JS_IsException(val))
|
|
goto go;
|
|
|
|
// Stringification can fail when there is an exception pending,
|
|
// e.g. a stack overflow InternalError. Special-case exception
|
|
// objects to make debugging easier, look up the .message property
|
|
// and stringify that.
|
|
if (JS_VALUE_GET_TAG(val1) != JS_TAG_OBJECT)
|
|
goto fail;
|
|
|
|
p = JS_VALUE_GET_OBJ(val1);
|
|
if (p->class_id != JS_CLASS_ERROR)
|
|
goto fail;
|
|
|
|
val = JS_GetProperty(ctx, val1, JS_ATOM_message);
|
|
if (JS_VALUE_GET_TAG(val) != JS_TAG_STRING) {
|
|
JS_FreeValue(ctx, val);
|
|
goto fail;
|
|
}
|
|
|
|
go:
|
|
str = JS_VALUE_GET_STRING(val);
|
|
len = str->len;
|
|
if (!str->is_wide_char) {
|
|
const uint8_t *src = str->u.str8;
|
|
int count;
|
|
|
|
/* count the number of non-ASCII characters */
|
|
/* Scanning the whole string is required for ASCII strings,
|
|
and computing the number of non-ASCII bytes is less expensive
|
|
than testing each byte, hence this method is faster for ASCII
|
|
strings, which is the most common case.
|
|
*/
|
|
count = 0;
|
|
for (pos = 0; pos < len; pos++) {
|
|
count += src[pos] >> 7;
|
|
}
|
|
if (count == 0) {
|
|
if (plen)
|
|
*plen = len;
|
|
return (const char *)src;
|
|
}
|
|
str_new = js_alloc_string(ctx, len + count, 0);
|
|
if (!str_new)
|
|
goto fail;
|
|
q = str_new->u.str8;
|
|
for (pos = 0; pos < len; pos++) {
|
|
c = src[pos];
|
|
if (c < 0x80) {
|
|
*q++ = c;
|
|
} else {
|
|
*q++ = (c >> 6) | 0xc0;
|
|
*q++ = (c & 0x3f) | 0x80;
|
|
}
|
|
}
|
|
} else {
|
|
const uint16_t *src = str->u.str16;
|
|
/* Allocate 3 bytes per 16 bit code point. Surrogate pairs may
|
|
produce 4 bytes but use 2 code points.
|
|
*/
|
|
str_new = js_alloc_string(ctx, len * 3, 0);
|
|
if (!str_new)
|
|
goto fail;
|
|
q = str_new->u.str8;
|
|
pos = 0;
|
|
while (pos < len) {
|
|
c = src[pos++];
|
|
if (c < 0x80) {
|
|
*q++ = c;
|
|
} else {
|
|
if (is_hi_surrogate(c)) {
|
|
if (pos < len && !cesu8) {
|
|
c1 = src[pos];
|
|
if (is_lo_surrogate(c1)) {
|
|
pos++;
|
|
c = from_surrogate(c, c1);
|
|
} else {
|
|
/* Keep unmatched surrogate code points */
|
|
/* c = 0xfffd; */ /* error */
|
|
}
|
|
} else {
|
|
/* Keep unmatched surrogate code points */
|
|
/* c = 0xfffd; */ /* error */
|
|
}
|
|
}
|
|
q += utf8_encode(q, c);
|
|
}
|
|
}
|
|
}
|
|
|
|
*q = '\0';
|
|
str_new->len = q - str_new->u.str8;
|
|
JS_FreeValue(ctx, val);
|
|
if (plen)
|
|
*plen = str_new->len;
|
|
return (const char *)str_new->u.str8;
|
|
fail:
|
|
if (plen)
|
|
*plen = 0;
|
|
return NULL;
|
|
}
|
|
|
|
void JS_FreeCString(JSContext *ctx, const char *ptr)
|
|
{
|
|
JSString *p;
|
|
if (!ptr)
|
|
return;
|
|
/* purposely removing constness */
|
|
p = container_of(ptr, JSString, u);
|
|
JS_FreeValue(ctx, JS_MKPTR(JS_TAG_STRING, p));
|
|
}
|
|
|
|
static int memcmp16_8(const uint16_t *src1, const uint8_t *src2, int len)
|
|
{
|
|
int c, i;
|
|
for(i = 0; i < len; i++) {
|
|
c = src1[i] - src2[i];
|
|
if (c != 0)
|
|
return c;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int memcmp16(const uint16_t *src1, const uint16_t *src2, int len)
|
|
{
|
|
int c, i;
|
|
for(i = 0; i < len; i++) {
|
|
c = src1[i] - src2[i];
|
|
if (c != 0)
|
|
return c;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int js_string_memcmp(const JSString *p1, const JSString *p2, int len)
|
|
{
|
|
int res;
|
|
|
|
if (likely(!p1->is_wide_char)) {
|
|
if (likely(!p2->is_wide_char))
|
|
res = memcmp(p1->u.str8, p2->u.str8, len);
|
|
else
|
|
res = -memcmp16_8(p2->u.str16, p1->u.str8, len);
|
|
} else {
|
|
if (!p2->is_wide_char)
|
|
res = memcmp16_8(p1->u.str16, p2->u.str8, len);
|
|
else
|
|
res = memcmp16(p1->u.str16, p2->u.str16, len);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
/* return < 0, 0 or > 0 */
|
|
static int js_string_compare(JSContext *ctx,
|
|
const JSString *p1, const JSString *p2)
|
|
{
|
|
int res, len;
|
|
len = min_int(p1->len, p2->len);
|
|
res = js_string_memcmp(p1, p2, len);
|
|
if (res == 0)
|
|
res = compare_u32(p1->len, p2->len);
|
|
return res;
|
|
}
|
|
|
|
static void copy_str16(uint16_t *dst, const JSString *p, int offset, int len)
|
|
{
|
|
if (p->is_wide_char) {
|
|
memcpy(dst, p->u.str16 + offset, len * 2);
|
|
} else {
|
|
const uint8_t *src1 = p->u.str8 + offset;
|
|
int i;
|
|
|
|
for(i = 0; i < len; i++)
|
|
dst[i] = src1[i];
|
|
}
|
|
}
|
|
|
|
static JSValue JS_ConcatString1(JSContext *ctx,
|
|
const JSString *p1, const JSString *p2)
|
|
{
|
|
JSString *p;
|
|
uint32_t len;
|
|
int is_wide_char;
|
|
|
|
len = p1->len + p2->len;
|
|
if (len > JS_STRING_LEN_MAX)
|
|
return JS_ThrowRangeError(ctx, "invalid string length");
|
|
is_wide_char = p1->is_wide_char | p2->is_wide_char;
|
|
p = js_alloc_string(ctx, len, is_wide_char);
|
|
if (!p)
|
|
return JS_EXCEPTION;
|
|
if (!is_wide_char) {
|
|
memcpy(p->u.str8, p1->u.str8, p1->len);
|
|
memcpy(p->u.str8 + p1->len, p2->u.str8, p2->len);
|
|
p->u.str8[len] = '\0';
|
|
} else {
|
|
copy_str16(p->u.str16, p1, 0, p1->len);
|
|
copy_str16(p->u.str16 + p1->len, p2, 0, p2->len);
|
|
}
|
|
return JS_MKPTR(JS_TAG_STRING, p);
|
|
}
|
|
|
|
/* op1 and op2 are converted to strings. For convience, op1 or op2 =
|
|
JS_EXCEPTION are accepted and return JS_EXCEPTION. */
|
|
static JSValue JS_ConcatString(JSContext *ctx, JSValue op1, JSValue op2)
|
|
{
|
|
JSValue ret;
|
|
JSString *p1, *p2;
|
|
|
|
if (unlikely(JS_VALUE_GET_TAG(op1) != JS_TAG_STRING)) {
|
|
op1 = JS_ToStringFree(ctx, op1);
|
|
if (JS_IsException(op1)) {
|
|
JS_FreeValue(ctx, op2);
|
|
return JS_EXCEPTION;
|
|
}
|
|
}
|
|
if (unlikely(JS_VALUE_GET_TAG(op2) != JS_TAG_STRING)) {
|
|
op2 = JS_ToStringFree(ctx, op2);
|
|
if (JS_IsException(op2)) {
|
|
JS_FreeValue(ctx, op1);
|
|
return JS_EXCEPTION;
|
|
}
|
|
}
|
|
p1 = JS_VALUE_GET_STRING(op1);
|
|
p2 = JS_VALUE_GET_STRING(op2);
|
|
|
|
/* XXX: could also check if p1 is empty */
|
|
if (p2->len == 0) {
|
|
goto ret_op1;
|
|
}
|
|
if (p1->header.ref_count == 1 && p1->is_wide_char == p2->is_wide_char
|
|
&& js_malloc_usable_size(ctx, p1) >= sizeof(*p1) + ((p1->len + p2->len) << p2->is_wide_char) + 1 - p1->is_wide_char) {
|
|
/* Concatenate in place in available space at the end of p1 */
|
|
if (p1->is_wide_char) {
|
|
memcpy(p1->u.str16 + p1->len, p2->u.str16, p2->len << 1);
|
|
p1->len += p2->len;
|
|
} else {
|
|
memcpy(p1->u.str8 + p1->len, p2->u.str8, p2->len);
|
|
p1->len += p2->len;
|
|
p1->u.str8[p1->len] = '\0';
|
|
}
|
|
ret_op1:
|
|
JS_FreeValue(ctx, op2);
|
|
return op1;
|
|
}
|
|
ret = JS_ConcatString1(ctx, p1, p2);
|
|
JS_FreeValue(ctx, op1);
|
|
JS_FreeValue(ctx, op2);
|
|
return ret;
|
|
}
|
|
|
|
/* Shape support */
|
|
|
|
static inline size_t get_shape_size(size_t hash_size, size_t prop_size)
|
|
{
|
|
return hash_size * sizeof(uint32_t) + sizeof(JSShape) +
|
|
prop_size * sizeof(JSShapeProperty);
|
|
}
|
|
|
|
static inline JSShape *get_shape_from_alloc(void *sh_alloc, size_t hash_size)
|
|
{
|
|
return (JSShape *)(void *)((uint32_t *)sh_alloc + hash_size);
|
|
}
|
|
|
|
static inline uint32_t *prop_hash_end(JSShape *sh)
|
|
{
|
|
return (uint32_t *)sh;
|
|
}
|
|
|
|
static inline void *get_alloc_from_shape(JSShape *sh)
|
|
{
|
|
return prop_hash_end(sh) - ((intptr_t)sh->prop_hash_mask + 1);
|
|
}
|
|
|
|
static inline JSShapeProperty *get_shape_prop(JSShape *sh)
|
|
{
|
|
return sh->prop;
|
|
}
|
|
|
|
static int init_shape_hash(JSRuntime *rt)
|
|
{
|
|
rt->shape_hash_bits = 4; /* 16 shapes */
|
|
rt->shape_hash_size = 1 << rt->shape_hash_bits;
|
|
rt->shape_hash_count = 0;
|
|
rt->shape_hash = js_mallocz_rt(rt, sizeof(rt->shape_hash[0]) *
|
|
rt->shape_hash_size);
|
|
if (!rt->shape_hash)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
/* same magic hash multiplier as the Linux kernel */
|
|
static uint32_t shape_hash(uint32_t h, uint32_t val)
|
|
{
|
|
return (h + val) * 0x9e370001;
|
|
}
|
|
|
|
/* truncate the shape hash to 'hash_bits' bits */
|
|
static uint32_t get_shape_hash(uint32_t h, int hash_bits)
|
|
{
|
|
return h >> (32 - hash_bits);
|
|
}
|
|
|
|
static uint32_t shape_initial_hash(JSObject *proto)
|
|
{
|
|
uint32_t h;
|
|
h = shape_hash(1, (uintptr_t)proto);
|
|
if (sizeof(proto) > 4)
|
|
h = shape_hash(h, (uint64_t)(uintptr_t)proto >> 32);
|
|
return h;
|
|
}
|
|
|
|
static int resize_shape_hash(JSRuntime *rt, int new_shape_hash_bits)
|
|
{
|
|
int new_shape_hash_size, i;
|
|
uint32_t h;
|
|
JSShape **new_shape_hash, *sh, *sh_next;
|
|
|
|
new_shape_hash_size = 1 << new_shape_hash_bits;
|
|
new_shape_hash = js_mallocz_rt(rt, sizeof(rt->shape_hash[0]) *
|
|
new_shape_hash_size);
|
|
if (!new_shape_hash)
|
|
return -1;
|
|
for(i = 0; i < rt->shape_hash_size; i++) {
|
|
for(sh = rt->shape_hash[i]; sh != NULL; sh = sh_next) {
|
|
sh_next = sh->shape_hash_next;
|
|
h = get_shape_hash(sh->hash, new_shape_hash_bits);
|
|
sh->shape_hash_next = new_shape_hash[h];
|
|
new_shape_hash[h] = sh;
|
|
}
|
|
}
|
|
js_free_rt(rt, rt->shape_hash);
|
|
rt->shape_hash_bits = new_shape_hash_bits;
|
|
rt->shape_hash_size = new_shape_hash_size;
|
|
rt->shape_hash = new_shape_hash;
|
|
return 0;
|
|
}
|
|
|
|
static void js_shape_hash_link(JSRuntime *rt, JSShape *sh)
|
|
{
|
|
uint32_t h;
|
|
h = get_shape_hash(sh->hash, rt->shape_hash_bits);
|
|
sh->shape_hash_next = rt->shape_hash[h];
|
|
rt->shape_hash[h] = sh;
|
|
rt->shape_hash_count++;
|
|
}
|
|
|
|
static void js_shape_hash_unlink(JSRuntime *rt, JSShape *sh)
|
|
{
|
|
uint32_t h;
|
|
JSShape **psh;
|
|
|
|
h = get_shape_hash(sh->hash, rt->shape_hash_bits);
|
|
psh = &rt->shape_hash[h];
|
|
while (*psh != sh)
|
|
psh = &(*psh)->shape_hash_next;
|
|
*psh = sh->shape_hash_next;
|
|
rt->shape_hash_count--;
|
|
}
|
|
|
|
/* create a new empty shape with prototype 'proto' */
|
|
static no_inline JSShape *js_new_shape2(JSContext *ctx, JSObject *proto,
|
|
int hash_size, int prop_size)
|
|
{
|
|
JSRuntime *rt = ctx->rt;
|
|
void *sh_alloc;
|
|
JSShape *sh;
|
|
|
|
/* resize the shape hash table if necessary */
|
|
if (2 * (rt->shape_hash_count + 1) > rt->shape_hash_size) {
|
|
resize_shape_hash(rt, rt->shape_hash_bits + 1);
|
|
}
|
|
|
|
sh_alloc = js_malloc(ctx, get_shape_size(hash_size, prop_size));
|
|
if (!sh_alloc)
|
|
return NULL;
|
|
sh = get_shape_from_alloc(sh_alloc, hash_size);
|
|
sh->header.ref_count = 1;
|
|
add_gc_object(rt, &sh->header, JS_GC_OBJ_TYPE_SHAPE);
|
|
if (proto)
|
|
js_dup(JS_MKPTR(JS_TAG_OBJECT, proto));
|
|
sh->proto = proto;
|
|
memset(prop_hash_end(sh) - hash_size, 0, sizeof(prop_hash_end(sh)[0]) *
|
|
hash_size);
|
|
sh->prop_hash_mask = hash_size - 1;
|
|
sh->prop_size = prop_size;
|
|
sh->prop_count = 0;
|
|
sh->deleted_prop_count = 0;
|
|
|
|
/* insert in the hash table */
|
|
sh->hash = shape_initial_hash(proto);
|
|
sh->is_hashed = TRUE;
|
|
sh->has_small_array_index = FALSE;
|
|
js_shape_hash_link(ctx->rt, sh);
|
|
return sh;
|
|
}
|
|
|
|
static JSShape *js_new_shape(JSContext *ctx, JSObject *proto)
|
|
{
|
|
return js_new_shape2(ctx, proto, JS_PROP_INITIAL_HASH_SIZE,
|
|
JS_PROP_INITIAL_SIZE);
|
|
}
|
|
|
|
/* The shape is cloned. The new shape is not inserted in the shape
|
|
hash table */
|
|
static JSShape *js_clone_shape(JSContext *ctx, JSShape *sh1)
|
|
{
|
|
JSShape *sh;
|
|
void *sh_alloc, *sh_alloc1;
|
|
size_t size;
|
|
JSShapeProperty *pr;
|
|
uint32_t i, hash_size;
|
|
|
|
hash_size = sh1->prop_hash_mask + 1;
|
|
size = get_shape_size(hash_size, sh1->prop_size);
|
|
sh_alloc = js_malloc(ctx, size);
|
|
if (!sh_alloc)
|
|
return NULL;
|
|
sh_alloc1 = get_alloc_from_shape(sh1);
|
|
memcpy(sh_alloc, sh_alloc1, size);
|
|
sh = get_shape_from_alloc(sh_alloc, hash_size);
|
|
sh->header.ref_count = 1;
|
|
add_gc_object(ctx->rt, &sh->header, JS_GC_OBJ_TYPE_SHAPE);
|
|
sh->is_hashed = FALSE;
|
|
if (sh->proto) {
|
|
js_dup(JS_MKPTR(JS_TAG_OBJECT, sh->proto));
|
|
}
|
|
for(i = 0, pr = get_shape_prop(sh); i < sh->prop_count; i++, pr++) {
|
|
JS_DupAtom(ctx, pr->atom);
|
|
}
|
|
return sh;
|
|
}
|
|
|
|
static JSShape *js_dup_shape(JSShape *sh)
|
|
{
|
|
sh->header.ref_count++;
|
|
return sh;
|
|
}
|
|
|
|
static void js_free_shape0(JSRuntime *rt, JSShape *sh)
|
|
{
|
|
uint32_t i;
|
|
JSShapeProperty *pr;
|
|
|
|
assert(sh->header.ref_count == 0);
|
|
if (sh->is_hashed)
|
|
js_shape_hash_unlink(rt, sh);
|
|
if (sh->proto != NULL) {
|
|
JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_OBJECT, sh->proto));
|
|
}
|
|
pr = get_shape_prop(sh);
|
|
for(i = 0; i < sh->prop_count; i++) {
|
|
JS_FreeAtomRT(rt, pr->atom);
|
|
pr++;
|
|
}
|
|
remove_gc_object(&sh->header);
|
|
js_free_rt(rt, get_alloc_from_shape(sh));
|
|
}
|
|
|
|
static void js_free_shape(JSRuntime *rt, JSShape *sh)
|
|
{
|
|
if (unlikely(--sh->header.ref_count <= 0)) {
|
|
js_free_shape0(rt, sh);
|
|
}
|
|
}
|
|
|
|
static void js_free_shape_null(JSRuntime *rt, JSShape *sh)
|
|
{
|
|
if (sh)
|
|
js_free_shape(rt, sh);
|
|
}
|
|
|
|
/* make space to hold at least 'count' properties */
|
|
static no_inline int resize_properties(JSContext *ctx, JSShape **psh,
|
|
JSObject *p, uint32_t count)
|
|
{
|
|
JSShape *sh;
|
|
uint32_t new_size, new_hash_size, new_hash_mask, i;
|
|
JSShapeProperty *pr;
|
|
void *sh_alloc;
|
|
intptr_t h;
|
|
|
|
sh = *psh;
|
|
new_size = max_int(count, sh->prop_size * 3 / 2);
|
|
/* Reallocate prop array first to avoid crash or size inconsistency
|
|
in case of memory allocation failure */
|
|
if (p) {
|
|
JSProperty *new_prop;
|
|
new_prop = js_realloc(ctx, p->prop, sizeof(new_prop[0]) * new_size);
|
|
if (unlikely(!new_prop))
|
|
return -1;
|
|
p->prop = new_prop;
|
|
}
|
|
new_hash_size = sh->prop_hash_mask + 1;
|
|
while (new_hash_size < new_size)
|
|
new_hash_size = 2 * new_hash_size;
|
|
if (new_hash_size != (sh->prop_hash_mask + 1)) {
|
|
JSShape *old_sh;
|
|
/* resize the hash table and the properties */
|
|
old_sh = sh;
|
|
sh_alloc = js_malloc(ctx, get_shape_size(new_hash_size, new_size));
|
|
if (!sh_alloc)
|
|
return -1;
|
|
sh = get_shape_from_alloc(sh_alloc, new_hash_size);
|
|
list_del(&old_sh->header.link);
|
|
/* copy all the fields and the properties */
|
|
memcpy(sh, old_sh,
|
|
sizeof(JSShape) + sizeof(sh->prop[0]) * old_sh->prop_count);
|
|
list_add_tail(&sh->header.link, &ctx->rt->gc_obj_list);
|
|
new_hash_mask = new_hash_size - 1;
|
|
sh->prop_hash_mask = new_hash_mask;
|
|
memset(prop_hash_end(sh) - new_hash_size, 0,
|
|
sizeof(prop_hash_end(sh)[0]) * new_hash_size);
|
|
for(i = 0, pr = sh->prop; i < sh->prop_count; i++, pr++) {
|
|
if (pr->atom != JS_ATOM_NULL) {
|
|
h = ((uintptr_t)pr->atom & new_hash_mask);
|
|
pr->hash_next = prop_hash_end(sh)[-h - 1];
|
|
prop_hash_end(sh)[-h - 1] = i + 1;
|
|
}
|
|
}
|
|
js_free(ctx, get_alloc_from_shape(old_sh));
|
|
} else {
|
|
/* only resize the properties */
|
|
list_del(&sh->header.link);
|
|
sh_alloc = js_realloc(ctx, get_alloc_from_shape(sh),
|
|
get_shape_size(new_hash_size, new_size));
|
|
if (unlikely(!sh_alloc)) {
|
|
/* insert again in the GC list */
|
|
list_add_tail(&sh->header.link, &ctx->rt->gc_obj_list);
|
|
return -1;
|
|
}
|
|
sh = get_shape_from_alloc(sh_alloc, new_hash_size);
|
|
list_add_tail(&sh->header.link, &ctx->rt->gc_obj_list);
|
|
}
|
|
*psh = sh;
|
|
sh->prop_size = new_size;
|
|
return 0;
|
|
}
|
|
|
|
/* remove the deleted properties. */
|
|
static int compact_properties(JSContext *ctx, JSObject *p)
|
|
{
|
|
JSShape *sh, *old_sh;
|
|
void *sh_alloc;
|
|
intptr_t h;
|
|
uint32_t new_hash_size, i, j, new_hash_mask, new_size;
|
|
JSShapeProperty *old_pr, *pr;
|
|
JSProperty *prop, *new_prop;
|
|
|
|
sh = p->shape;
|
|
assert(!sh->is_hashed);
|
|
|
|
new_size = max_int(JS_PROP_INITIAL_SIZE,
|
|
sh->prop_count - sh->deleted_prop_count);
|
|
assert(new_size <= sh->prop_size);
|
|
|
|
new_hash_size = sh->prop_hash_mask + 1;
|
|
while ((new_hash_size / 2) >= new_size)
|
|
new_hash_size = new_hash_size / 2;
|
|
new_hash_mask = new_hash_size - 1;
|
|
|
|
/* resize the hash table and the properties */
|
|
old_sh = sh;
|
|
sh_alloc = js_malloc(ctx, get_shape_size(new_hash_size, new_size));
|
|
if (!sh_alloc)
|
|
return -1;
|
|
sh = get_shape_from_alloc(sh_alloc, new_hash_size);
|
|
list_del(&old_sh->header.link);
|
|
memcpy(sh, old_sh, sizeof(JSShape));
|
|
list_add_tail(&sh->header.link, &ctx->rt->gc_obj_list);
|
|
|
|
memset(prop_hash_end(sh) - new_hash_size, 0,
|
|
sizeof(prop_hash_end(sh)[0]) * new_hash_size);
|
|
|
|
j = 0;
|
|
old_pr = old_sh->prop;
|
|
pr = sh->prop;
|
|
prop = p->prop;
|
|
for(i = 0; i < sh->prop_count; i++) {
|
|
if (old_pr->atom != JS_ATOM_NULL) {
|
|
pr->atom = old_pr->atom;
|
|
pr->flags = old_pr->flags;
|
|
h = ((uintptr_t)old_pr->atom & new_hash_mask);
|
|
pr->hash_next = prop_hash_end(sh)[-h - 1];
|
|
prop_hash_end(sh)[-h - 1] = j + 1;
|
|
prop[j] = prop[i];
|
|
j++;
|
|
pr++;
|
|
}
|
|
old_pr++;
|
|
}
|
|
assert(j == (sh->prop_count - sh->deleted_prop_count));
|
|
sh->prop_hash_mask = new_hash_mask;
|
|
sh->prop_size = new_size;
|
|
sh->deleted_prop_count = 0;
|
|
sh->prop_count = j;
|
|
|
|
p->shape = sh;
|
|
js_free(ctx, get_alloc_from_shape(old_sh));
|
|
|
|
/* reduce the size of the object properties */
|
|
new_prop = js_realloc(ctx, p->prop, sizeof(new_prop[0]) * new_size);
|
|
if (new_prop)
|
|
p->prop = new_prop;
|
|
return 0;
|
|
}
|
|
|
|
static int add_shape_property(JSContext *ctx, JSShape **psh,
|
|
JSObject *p, JSAtom atom, int prop_flags)
|
|
{
|
|
JSRuntime *rt = ctx->rt;
|
|
JSShape *sh = *psh;
|
|
JSShapeProperty *pr, *prop;
|
|
uint32_t hash_mask, new_shape_hash = 0;
|
|
intptr_t h;
|
|
|
|
/* update the shape hash */
|
|
if (sh->is_hashed) {
|
|
js_shape_hash_unlink(rt, sh);
|
|
new_shape_hash = shape_hash(shape_hash(sh->hash, atom), prop_flags);
|
|
}
|
|
|
|
if (unlikely(sh->prop_count >= sh->prop_size)) {
|
|
if (resize_properties(ctx, psh, p, sh->prop_count + 1)) {
|
|
/* in case of error, reinsert in the hash table.
|
|
sh is still valid if resize_properties() failed */
|
|
if (sh->is_hashed)
|
|
js_shape_hash_link(rt, sh);
|
|
return -1;
|
|
}
|
|
sh = *psh;
|
|
}
|
|
if (sh->is_hashed) {
|
|
sh->hash = new_shape_hash;
|
|
js_shape_hash_link(rt, sh);
|
|
}
|
|
/* Initialize the new shape property.
|
|
The object property at p->prop[sh->prop_count] is uninitialized */
|
|
prop = get_shape_prop(sh);
|
|
pr = &prop[sh->prop_count++];
|
|
pr->atom = JS_DupAtom(ctx, atom);
|
|
pr->flags = prop_flags;
|
|
sh->has_small_array_index |= __JS_AtomIsTaggedInt(atom);
|
|
/* add in hash table */
|
|
hash_mask = sh->prop_hash_mask;
|
|
h = atom & hash_mask;
|
|
pr->hash_next = prop_hash_end(sh)[-h - 1];
|
|
prop_hash_end(sh)[-h - 1] = sh->prop_count;
|
|
return 0;
|
|
}
|
|
|
|
/* find a hashed empty shape matching the prototype. Return NULL if
|
|
not found */
|
|
static JSShape *find_hashed_shape_proto(JSRuntime *rt, JSObject *proto)
|
|
{
|
|
JSShape *sh1;
|
|
uint32_t h, h1;
|
|
|
|
h = shape_initial_hash(proto);
|
|
h1 = get_shape_hash(h, rt->shape_hash_bits);
|
|
for(sh1 = rt->shape_hash[h1]; sh1 != NULL; sh1 = sh1->shape_hash_next) {
|
|
if (sh1->hash == h &&
|
|
sh1->proto == proto &&
|
|
sh1->prop_count == 0) {
|
|
return sh1;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* find a hashed shape matching sh + (prop, prop_flags). Return NULL if
|
|
not found */
|
|
static JSShape *find_hashed_shape_prop(JSRuntime *rt, JSShape *sh,
|
|
JSAtom atom, int prop_flags)
|
|
{
|
|
JSShape *sh1;
|
|
uint32_t h, h1, i, n;
|
|
|
|
h = sh->hash;
|
|
h = shape_hash(h, atom);
|
|
h = shape_hash(h, prop_flags);
|
|
h1 = get_shape_hash(h, rt->shape_hash_bits);
|
|
for(sh1 = rt->shape_hash[h1]; sh1 != NULL; sh1 = sh1->shape_hash_next) {
|
|
/* we test the hash first so that the rest is done only if the
|
|
shapes really match */
|
|
if (sh1->hash == h &&
|
|
sh1->proto == sh->proto &&
|
|
sh1->prop_count == ((n = sh->prop_count) + 1)) {
|
|
for(i = 0; i < n; i++) {
|
|
if (unlikely(sh1->prop[i].atom != sh->prop[i].atom) ||
|
|
unlikely(sh1->prop[i].flags != sh->prop[i].flags))
|
|
goto next;
|
|
}
|
|
if (unlikely(sh1->prop[n].atom != atom) ||
|
|
unlikely(sh1->prop[n].flags != prop_flags))
|
|
goto next;
|
|
return sh1;
|
|
}
|
|
next: ;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static __maybe_unused void JS_DumpShape(JSRuntime *rt, int i, JSShape *sh)
|
|
{
|
|
char atom_buf[ATOM_GET_STR_BUF_SIZE];
|
|
int j;
|
|
|
|
/* XXX: should output readable class prototype */
|
|
printf("%5d %3d%c %14p %5d %5d", i,
|
|
sh->header.ref_count, " *"[sh->is_hashed],
|
|
(void *)sh->proto, sh->prop_size, sh->prop_count);
|
|
for(j = 0; j < sh->prop_count; j++) {
|
|
printf(" %s", JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf),
|
|
sh->prop[j].atom));
|
|
}
|
|
printf("\n");
|
|
}
|
|
|
|
static __maybe_unused void JS_DumpShapes(JSRuntime *rt)
|
|
{
|
|
int i;
|
|
JSShape *sh;
|
|
struct list_head *el;
|
|
JSObject *p;
|
|
JSGCObjectHeader *gp;
|
|
|
|
printf("JSShapes: {\n");
|
|
printf("%5s %4s %14s %5s %5s %s\n", "SLOT", "REFS", "PROTO", "SIZE", "COUNT", "PROPS");
|
|
for(i = 0; i < rt->shape_hash_size; i++) {
|
|
for(sh = rt->shape_hash[i]; sh != NULL; sh = sh->shape_hash_next) {
|
|
JS_DumpShape(rt, i, sh);
|
|
assert(sh->is_hashed);
|
|
}
|
|
}
|
|
/* dump non-hashed shapes */
|
|
list_for_each(el, &rt->gc_obj_list) {
|
|
gp = list_entry(el, JSGCObjectHeader, link);
|
|
if (gp->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT) {
|
|
p = (JSObject *)gp;
|
|
if (!p->shape->is_hashed) {
|
|
JS_DumpShape(rt, -1, p->shape);
|
|
}
|
|
}
|
|
}
|
|
printf("}\n");
|
|
}
|
|
|
|
static JSValue JS_NewObjectFromShape(JSContext *ctx, JSShape *sh, JSClassID class_id)
|
|
{
|
|
JSObject *p;
|
|
|
|
js_trigger_gc(ctx->rt, sizeof(JSObject));
|
|
p = js_malloc(ctx, sizeof(JSObject));
|
|
if (unlikely(!p))
|
|
goto fail;
|
|
p->class_id = class_id;
|
|
p->extensible = TRUE;
|
|
p->free_mark = 0;
|
|
p->is_exotic = 0;
|
|
p->fast_array = 0;
|
|
p->is_constructor = 0;
|
|
p->is_uncatchable_error = 0;
|
|
p->tmp_mark = 0;
|
|
p->is_HTMLDDA = 0;
|
|
p->first_weak_ref = NULL;
|
|
p->u.opaque = NULL;
|
|
p->shape = sh;
|
|
p->prop = js_malloc(ctx, sizeof(JSProperty) * sh->prop_size);
|
|
if (unlikely(!p->prop)) {
|
|
js_free(ctx, p);
|
|
fail:
|
|
js_free_shape(ctx->rt, sh);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
switch(class_id) {
|
|
case JS_CLASS_OBJECT:
|
|
break;
|
|
case JS_CLASS_ARRAY:
|
|
{
|
|
JSProperty *pr;
|
|
p->is_exotic = 1;
|
|
p->fast_array = 1;
|
|
p->u.array.u.values = NULL;
|
|
p->u.array.count = 0;
|
|
p->u.array.u1.size = 0;
|
|
/* the length property is always the first one */
|
|
if (likely(sh == ctx->array_shape)) {
|
|
pr = &p->prop[0];
|
|
} else {
|
|
/* only used for the first array */
|
|
/* cannot fail */
|
|
pr = add_property(ctx, p, JS_ATOM_length,
|
|
JS_PROP_WRITABLE | JS_PROP_LENGTH);
|
|
}
|
|
pr->u.value = js_int32(0);
|
|
}
|
|
break;
|
|
case JS_CLASS_C_FUNCTION:
|
|
p->prop[0].u.value = JS_UNDEFINED;
|
|
break;
|
|
case JS_CLASS_ARGUMENTS:
|
|
case JS_CLASS_UINT8C_ARRAY:
|
|
case JS_CLASS_INT8_ARRAY:
|
|
case JS_CLASS_UINT8_ARRAY:
|
|
case JS_CLASS_INT16_ARRAY:
|
|
case JS_CLASS_UINT16_ARRAY:
|
|
case JS_CLASS_INT32_ARRAY:
|
|
case JS_CLASS_UINT32_ARRAY:
|
|
case JS_CLASS_BIG_INT64_ARRAY:
|
|
case JS_CLASS_BIG_UINT64_ARRAY:
|
|
case JS_CLASS_FLOAT32_ARRAY:
|
|
case JS_CLASS_FLOAT64_ARRAY:
|
|
p->is_exotic = 1;
|
|
p->fast_array = 1;
|
|
p->u.array.u.ptr = NULL;
|
|
p->u.array.count = 0;
|
|
break;
|
|
case JS_CLASS_DATAVIEW:
|
|
p->u.array.u.ptr = NULL;
|
|
p->u.array.count = 0;
|
|
break;
|
|
case JS_CLASS_NUMBER:
|
|
case JS_CLASS_STRING:
|
|
case JS_CLASS_BOOLEAN:
|
|
case JS_CLASS_SYMBOL:
|
|
case JS_CLASS_DATE:
|
|
case JS_CLASS_BIG_INT:
|
|
p->u.object_data = JS_UNDEFINED;
|
|
goto set_exotic;
|
|
case JS_CLASS_REGEXP:
|
|
p->u.regexp.pattern = NULL;
|
|
p->u.regexp.bytecode = NULL;
|
|
goto set_exotic;
|
|
default:
|
|
set_exotic:
|
|
if (ctx->rt->class_array[class_id].exotic) {
|
|
p->is_exotic = 1;
|
|
}
|
|
break;
|
|
}
|
|
p->header.ref_count = 1;
|
|
add_gc_object(ctx->rt, &p->header, JS_GC_OBJ_TYPE_JS_OBJECT);
|
|
return JS_MKPTR(JS_TAG_OBJECT, p);
|
|
}
|
|
|
|
static JSObject *get_proto_obj(JSValue proto_val)
|
|
{
|
|
if (JS_VALUE_GET_TAG(proto_val) != JS_TAG_OBJECT)
|
|
return NULL;
|
|
else
|
|
return JS_VALUE_GET_OBJ(proto_val);
|
|
}
|
|
|
|
/* WARNING: proto must be an object or JS_NULL */
|
|
JSValue JS_NewObjectProtoClass(JSContext *ctx, JSValue proto_val,
|
|
JSClassID class_id)
|
|
{
|
|
JSShape *sh;
|
|
JSObject *proto;
|
|
|
|
proto = get_proto_obj(proto_val);
|
|
sh = find_hashed_shape_proto(ctx->rt, proto);
|
|
if (likely(sh)) {
|
|
sh = js_dup_shape(sh);
|
|
} else {
|
|
sh = js_new_shape(ctx, proto);
|
|
if (!sh)
|
|
return JS_EXCEPTION;
|
|
}
|
|
return JS_NewObjectFromShape(ctx, sh, class_id);
|
|
}
|
|
|
|
static int JS_SetObjectData(JSContext *ctx, JSValue obj, JSValue val)
|
|
{
|
|
JSObject *p;
|
|
|
|
if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) {
|
|
p = JS_VALUE_GET_OBJ(obj);
|
|
switch(p->class_id) {
|
|
case JS_CLASS_NUMBER:
|
|
case JS_CLASS_STRING:
|
|
case JS_CLASS_BOOLEAN:
|
|
case JS_CLASS_SYMBOL:
|
|
case JS_CLASS_DATE:
|
|
case JS_CLASS_BIG_INT:
|
|
JS_FreeValue(ctx, p->u.object_data);
|
|
p->u.object_data = val;
|
|
return 0;
|
|
}
|
|
}
|
|
JS_FreeValue(ctx, val);
|
|
if (!JS_IsException(obj))
|
|
JS_ThrowTypeError(ctx, "invalid object type");
|
|
return -1;
|
|
}
|
|
|
|
JSValue JS_NewObjectClass(JSContext *ctx, int class_id)
|
|
{
|
|
return JS_NewObjectProtoClass(ctx, ctx->class_proto[class_id], class_id);
|
|
}
|
|
|
|
JSValue JS_NewObjectProto(JSContext *ctx, JSValue proto)
|
|
{
|
|
return JS_NewObjectProtoClass(ctx, proto, JS_CLASS_OBJECT);
|
|
}
|
|
|
|
JSValue JS_NewArray(JSContext *ctx)
|
|
{
|
|
return JS_NewObjectFromShape(ctx, js_dup_shape(ctx->array_shape),
|
|
JS_CLASS_ARRAY);
|
|
}
|
|
|
|
JSValue JS_NewObject(JSContext *ctx)
|
|
{
|
|
/* inline JS_NewObjectClass(ctx, JS_CLASS_OBJECT); */
|
|
return JS_NewObjectProtoClass(ctx, ctx->class_proto[JS_CLASS_OBJECT], JS_CLASS_OBJECT);
|
|
}
|
|
|
|
static void js_function_set_properties(JSContext *ctx, JSValue func_obj,
|
|
JSAtom name, int len)
|
|
{
|
|
/* ES6 feature non compatible with ES5.1: length is configurable */
|
|
JS_DefinePropertyValue(ctx, func_obj, JS_ATOM_length, js_int32(len),
|
|
JS_PROP_CONFIGURABLE);
|
|
JS_DefinePropertyValue(ctx, func_obj, JS_ATOM_name,
|
|
JS_AtomToString(ctx, name), JS_PROP_CONFIGURABLE);
|
|
}
|
|
|
|
static BOOL js_class_has_bytecode(JSClassID class_id)
|
|
{
|
|
return (class_id == JS_CLASS_BYTECODE_FUNCTION ||
|
|
class_id == JS_CLASS_GENERATOR_FUNCTION ||
|
|
class_id == JS_CLASS_ASYNC_FUNCTION ||
|
|
class_id == JS_CLASS_ASYNC_GENERATOR_FUNCTION);
|
|
}
|
|
|
|
/* return NULL without exception if not a function or no bytecode */
|
|
static JSFunctionBytecode *JS_GetFunctionBytecode(JSValue val)
|
|
{
|
|
JSObject *p;
|
|
if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT)
|
|
return NULL;
|
|
p = JS_VALUE_GET_OBJ(val);
|
|
if (!js_class_has_bytecode(p->class_id))
|
|
return NULL;
|
|
return p->u.func.function_bytecode;
|
|
}
|
|
|
|
static void js_method_set_home_object(JSContext *ctx, JSValue func_obj,
|
|
JSValue home_obj)
|
|
{
|
|
JSObject *p, *p1;
|
|
JSFunctionBytecode *b;
|
|
|
|
if (JS_VALUE_GET_TAG(func_obj) != JS_TAG_OBJECT)
|
|
return;
|
|
p = JS_VALUE_GET_OBJ(func_obj);
|
|
if (!js_class_has_bytecode(p->class_id))
|
|
return;
|
|
b = p->u.func.function_bytecode;
|
|
if (b->need_home_object) {
|
|
p1 = p->u.func.home_object;
|
|
if (p1) {
|
|
JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p1));
|
|
}
|
|
if (JS_VALUE_GET_TAG(home_obj) == JS_TAG_OBJECT)
|
|
p1 = JS_VALUE_GET_OBJ(js_dup(home_obj));
|
|
else
|
|
p1 = NULL;
|
|
p->u.func.home_object = p1;
|
|
}
|
|
}
|
|
|
|
static JSValue js_get_function_name(JSContext *ctx, JSAtom name)
|
|
{
|
|
JSValue name_str;
|
|
|
|
name_str = JS_AtomToString(ctx, name);
|
|
if (JS_AtomSymbolHasDescription(ctx, name)) {
|
|
name_str = JS_ConcatString3(ctx, "[", name_str, "]");
|
|
}
|
|
return name_str;
|
|
}
|
|
|
|
/* Modify the name of a method according to the atom and
|
|
'flags'. 'flags' is a bitmask of JS_PROP_HAS_GET and
|
|
JS_PROP_HAS_SET. Also set the home object of the method.
|
|
Return < 0 if exception. */
|
|
static int js_method_set_properties(JSContext *ctx, JSValue func_obj,
|
|
JSAtom name, int flags, JSValue home_obj)
|
|
{
|
|
JSValue name_str;
|
|
|
|
name_str = js_get_function_name(ctx, name);
|
|
if (flags & JS_PROP_HAS_GET) {
|
|
name_str = JS_ConcatString3(ctx, "get ", name_str, "");
|
|
} else if (flags & JS_PROP_HAS_SET) {
|
|
name_str = JS_ConcatString3(ctx, "set ", name_str, "");
|
|
}
|
|
if (JS_IsException(name_str))
|
|
return -1;
|
|
if (JS_DefinePropertyValue(ctx, func_obj, JS_ATOM_name, name_str,
|
|
JS_PROP_CONFIGURABLE) < 0)
|
|
return -1;
|
|
js_method_set_home_object(ctx, func_obj, home_obj);
|
|
return 0;
|
|
}
|
|
|
|
/* Note: at least 'length' arguments will be readable in 'argv' */
|
|
/* `name` may be NULL, pure ASCII or UTF-8 encoded */
|
|
static JSValue JS_NewCFunction3(JSContext *ctx, JSCFunction *func,
|
|
const char *name,
|
|
int length, JSCFunctionEnum cproto, int magic,
|
|
JSValue proto_val)
|
|
{
|
|
JSValue func_obj;
|
|
JSObject *p;
|
|
JSAtom name_atom;
|
|
|
|
func_obj = JS_NewObjectProtoClass(ctx, proto_val, JS_CLASS_C_FUNCTION);
|
|
if (JS_IsException(func_obj))
|
|
return func_obj;
|
|
p = JS_VALUE_GET_OBJ(func_obj);
|
|
p->u.cfunc.realm = JS_DupContext(ctx);
|
|
p->u.cfunc.c_function.generic = func;
|
|
p->u.cfunc.length = length;
|
|
p->u.cfunc.cproto = cproto;
|
|
p->u.cfunc.magic = magic;
|
|
p->is_constructor = (cproto == JS_CFUNC_constructor ||
|
|
cproto == JS_CFUNC_constructor_magic ||
|
|
cproto == JS_CFUNC_constructor_or_func ||
|
|
cproto == JS_CFUNC_constructor_or_func_magic);
|
|
if (!name)
|
|
name = "";
|
|
name_atom = JS_NewAtom(ctx, name);
|
|
js_function_set_properties(ctx, func_obj, name_atom, length);
|
|
JS_FreeAtom(ctx, name_atom);
|
|
return func_obj;
|
|
}
|
|
|
|
/* Note: at least 'length' arguments will be readable in 'argv' */
|
|
JSValue JS_NewCFunction2(JSContext *ctx, JSCFunction *func,
|
|
const char *name,
|
|
int length, JSCFunctionEnum cproto, int magic)
|
|
{
|
|
return JS_NewCFunction3(ctx, func, name, length, cproto, magic,
|
|
ctx->function_proto);
|
|
}
|
|
|
|
typedef struct JSCFunctionDataRecord {
|
|
JSCFunctionData *func;
|
|
uint8_t length;
|
|
uint8_t data_len;
|
|
uint16_t magic;
|
|
JSValue data[0];
|
|
} JSCFunctionDataRecord;
|
|
|
|
static void js_c_function_data_finalizer(JSRuntime *rt, JSValue val)
|
|
{
|
|
JSCFunctionDataRecord *s = JS_GetOpaque(val, JS_CLASS_C_FUNCTION_DATA);
|
|
int i;
|
|
|
|
if (s) {
|
|
for(i = 0; i < s->data_len; i++) {
|
|
JS_FreeValueRT(rt, s->data[i]);
|
|
}
|
|
js_free_rt(rt, s);
|
|
}
|
|
}
|
|
|
|
static void js_c_function_data_mark(JSRuntime *rt, JSValue val,
|
|
JS_MarkFunc *mark_func)
|
|
{
|
|
JSCFunctionDataRecord *s = JS_GetOpaque(val, JS_CLASS_C_FUNCTION_DATA);
|
|
int i;
|
|
|
|
if (s) {
|
|
for(i = 0; i < s->data_len; i++) {
|
|
JS_MarkValue(rt, s->data[i], mark_func);
|
|
}
|
|
}
|
|
}
|
|
|
|
static JSValue js_c_function_data_call(JSContext *ctx, JSValue func_obj,
|
|
JSValue this_val,
|
|
int argc, JSValue *argv, int flags)
|
|
{
|
|
JSCFunctionDataRecord *s = JS_GetOpaque(func_obj, JS_CLASS_C_FUNCTION_DATA);
|
|
JSValue *arg_buf;
|
|
int i;
|
|
|
|
/* XXX: could add the function on the stack for debug */
|
|
if (unlikely(argc < s->length)) {
|
|
arg_buf = alloca(sizeof(arg_buf[0]) * s->length);
|
|
for(i = 0; i < argc; i++)
|
|
arg_buf[i] = argv[i];
|
|
for(i = argc; i < s->length; i++)
|
|
arg_buf[i] = JS_UNDEFINED;
|
|
} else {
|
|
arg_buf = argv;
|
|
}
|
|
|
|
return s->func(ctx, this_val, argc, arg_buf, s->magic, s->data);
|
|
}
|
|
|
|
JSValue JS_NewCFunctionData(JSContext *ctx, JSCFunctionData *func,
|
|
int length, int magic, int data_len,
|
|
JSValue *data)
|
|
{
|
|
JSCFunctionDataRecord *s;
|
|
JSValue func_obj;
|
|
int i;
|
|
|
|
func_obj = JS_NewObjectProtoClass(ctx, ctx->function_proto,
|
|
JS_CLASS_C_FUNCTION_DATA);
|
|
if (JS_IsException(func_obj))
|
|
return func_obj;
|
|
s = js_malloc(ctx, sizeof(*s) + data_len * sizeof(JSValue));
|
|
if (!s) {
|
|
JS_FreeValue(ctx, func_obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
s->func = func;
|
|
s->length = length;
|
|
s->data_len = data_len;
|
|
s->magic = magic;
|
|
for(i = 0; i < data_len; i++)
|
|
s->data[i] = js_dup(data[i]);
|
|
JS_SetOpaque(func_obj, s);
|
|
js_function_set_properties(ctx, func_obj,
|
|
JS_ATOM_empty_string, length);
|
|
return func_obj;
|
|
}
|
|
|
|
static JSContext *js_autoinit_get_realm(JSProperty *pr)
|
|
{
|
|
return (JSContext *)(pr->u.init.realm_and_id & ~3);
|
|
}
|
|
|
|
static JSAutoInitIDEnum js_autoinit_get_id(JSProperty *pr)
|
|
{
|
|
return pr->u.init.realm_and_id & 3;
|
|
}
|
|
|
|
static void js_autoinit_free(JSRuntime *rt, JSProperty *pr)
|
|
{
|
|
JS_FreeContext(js_autoinit_get_realm(pr));
|
|
}
|
|
|
|
static void js_autoinit_mark(JSRuntime *rt, JSProperty *pr,
|
|
JS_MarkFunc *mark_func)
|
|
{
|
|
mark_func(rt, &js_autoinit_get_realm(pr)->header);
|
|
}
|
|
|
|
static void free_property(JSRuntime *rt, JSProperty *pr, int prop_flags)
|
|
{
|
|
if (unlikely(prop_flags & JS_PROP_TMASK)) {
|
|
if ((prop_flags & JS_PROP_TMASK) == JS_PROP_GETSET) {
|
|
if (pr->u.getset.getter)
|
|
JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.getter));
|
|
if (pr->u.getset.setter)
|
|
JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.setter));
|
|
} else if ((prop_flags & JS_PROP_TMASK) == JS_PROP_VARREF) {
|
|
free_var_ref(rt, pr->u.var_ref);
|
|
} else if ((prop_flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) {
|
|
js_autoinit_free(rt, pr);
|
|
}
|
|
} else {
|
|
JS_FreeValueRT(rt, pr->u.value);
|
|
}
|
|
}
|
|
|
|
static force_inline JSShapeProperty *find_own_property1(JSObject *p,
|
|
JSAtom atom)
|
|
{
|
|
JSShape *sh;
|
|
JSShapeProperty *pr, *prop;
|
|
intptr_t h;
|
|
sh = p->shape;
|
|
h = (uintptr_t)atom & sh->prop_hash_mask;
|
|
h = prop_hash_end(sh)[-h - 1];
|
|
prop = get_shape_prop(sh);
|
|
while (h) {
|
|
pr = &prop[h - 1];
|
|
if (likely(pr->atom == atom)) {
|
|
return pr;
|
|
}
|
|
h = pr->hash_next;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static force_inline JSShapeProperty *find_own_property(JSProperty **ppr,
|
|
JSObject *p,
|
|
JSAtom atom)
|
|
{
|
|
JSShape *sh;
|
|
JSShapeProperty *pr, *prop;
|
|
intptr_t h;
|
|
sh = p->shape;
|
|
h = (uintptr_t)atom & sh->prop_hash_mask;
|
|
h = prop_hash_end(sh)[-h - 1];
|
|
prop = get_shape_prop(sh);
|
|
while (h) {
|
|
pr = &prop[h - 1];
|
|
if (likely(pr->atom == atom)) {
|
|
*ppr = &p->prop[h - 1];
|
|
/* the compiler should be able to assume that pr != NULL here */
|
|
return pr;
|
|
}
|
|
h = pr->hash_next;
|
|
}
|
|
*ppr = NULL;
|
|
return NULL;
|
|
}
|
|
|
|
static force_inline JSShapeProperty* find_own_property_ic(JSProperty** ppr, JSObject* p,
|
|
JSAtom atom, uint32_t* offset)
|
|
{
|
|
JSShape* sh;
|
|
JSShapeProperty *pr, *prop;
|
|
intptr_t h, i;
|
|
sh = p->shape;
|
|
h = (uintptr_t)atom & sh->prop_hash_mask;
|
|
h = prop_hash_end(sh)[-h - 1];
|
|
prop = get_shape_prop(sh);
|
|
while (h) {
|
|
i = h - 1;
|
|
pr = &prop[i];
|
|
if (likely(pr->atom == atom)) {
|
|
*ppr = &p->prop[i];
|
|
*offset = i;
|
|
/* the compiler should be able to assume that pr != NULL here */
|
|
return pr;
|
|
}
|
|
h = pr->hash_next;
|
|
}
|
|
*ppr = NULL;
|
|
return NULL;
|
|
}
|
|
|
|
/* indicate that the object may be part of a function prototype cycle */
|
|
static void set_cycle_flag(JSContext *ctx, JSValue obj)
|
|
{
|
|
}
|
|
|
|
static void free_var_ref(JSRuntime *rt, JSVarRef *var_ref)
|
|
{
|
|
if (var_ref) {
|
|
assert(var_ref->header.ref_count > 0);
|
|
if (--var_ref->header.ref_count == 0) {
|
|
if (var_ref->is_detached) {
|
|
JS_FreeValueRT(rt, var_ref->value);
|
|
remove_gc_object(&var_ref->header);
|
|
} else {
|
|
list_del(&var_ref->header.link); /* still on the stack */
|
|
}
|
|
js_free_rt(rt, var_ref);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void js_array_finalizer(JSRuntime *rt, JSValue val)
|
|
{
|
|
JSObject *p = JS_VALUE_GET_OBJ(val);
|
|
int i;
|
|
|
|
for(i = 0; i < p->u.array.count; i++) {
|
|
JS_FreeValueRT(rt, p->u.array.u.values[i]);
|
|
}
|
|
js_free_rt(rt, p->u.array.u.values);
|
|
}
|
|
|
|
static void js_array_mark(JSRuntime *rt, JSValue val,
|
|
JS_MarkFunc *mark_func)
|
|
{
|
|
JSObject *p = JS_VALUE_GET_OBJ(val);
|
|
int i;
|
|
|
|
for(i = 0; i < p->u.array.count; i++) {
|
|
JS_MarkValue(rt, p->u.array.u.values[i], mark_func);
|
|
}
|
|
}
|
|
|
|
static void js_object_data_finalizer(JSRuntime *rt, JSValue val)
|
|
{
|
|
JSObject *p = JS_VALUE_GET_OBJ(val);
|
|
JS_FreeValueRT(rt, p->u.object_data);
|
|
p->u.object_data = JS_UNDEFINED;
|
|
}
|
|
|
|
static void js_object_data_mark(JSRuntime *rt, JSValue val,
|
|
JS_MarkFunc *mark_func)
|
|
{
|
|
JSObject *p = JS_VALUE_GET_OBJ(val);
|
|
JS_MarkValue(rt, p->u.object_data, mark_func);
|
|
}
|
|
|
|
static void js_c_function_finalizer(JSRuntime *rt, JSValue val)
|
|
{
|
|
JSObject *p = JS_VALUE_GET_OBJ(val);
|
|
|
|
if (p->u.cfunc.realm)
|
|
JS_FreeContext(p->u.cfunc.realm);
|
|
}
|
|
|
|
static void js_c_function_mark(JSRuntime *rt, JSValue val,
|
|
JS_MarkFunc *mark_func)
|
|
{
|
|
JSObject *p = JS_VALUE_GET_OBJ(val);
|
|
|
|
if (p->u.cfunc.realm)
|
|
mark_func(rt, &p->u.cfunc.realm->header);
|
|
}
|
|
|
|
static void js_bytecode_function_finalizer(JSRuntime *rt, JSValue val)
|
|
{
|
|
JSObject *p1, *p = JS_VALUE_GET_OBJ(val);
|
|
JSFunctionBytecode *b;
|
|
JSVarRef **var_refs;
|
|
int i;
|
|
|
|
p1 = p->u.func.home_object;
|
|
if (p1) {
|
|
JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_OBJECT, p1));
|
|
}
|
|
b = p->u.func.function_bytecode;
|
|
if (b) {
|
|
var_refs = p->u.func.var_refs;
|
|
if (var_refs) {
|
|
for(i = 0; i < b->closure_var_count; i++)
|
|
free_var_ref(rt, var_refs[i]);
|
|
js_free_rt(rt, var_refs);
|
|
}
|
|
JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_FUNCTION_BYTECODE, b));
|
|
}
|
|
}
|
|
|
|
static void js_bytecode_function_mark(JSRuntime *rt, JSValue val,
|
|
JS_MarkFunc *mark_func)
|
|
{
|
|
JSObject *p = JS_VALUE_GET_OBJ(val);
|
|
JSVarRef **var_refs = p->u.func.var_refs;
|
|
JSFunctionBytecode *b = p->u.func.function_bytecode;
|
|
int i;
|
|
|
|
if (p->u.func.home_object) {
|
|
JS_MarkValue(rt, JS_MKPTR(JS_TAG_OBJECT, p->u.func.home_object),
|
|
mark_func);
|
|
}
|
|
if (b) {
|
|
if (var_refs) {
|
|
for(i = 0; i < b->closure_var_count; i++) {
|
|
JSVarRef *var_ref = var_refs[i];
|
|
if (var_ref && var_ref->is_detached) {
|
|
mark_func(rt, &var_ref->header);
|
|
}
|
|
}
|
|
}
|
|
/* must mark the function bytecode because template objects may be
|
|
part of a cycle */
|
|
JS_MarkValue(rt, JS_MKPTR(JS_TAG_FUNCTION_BYTECODE, b), mark_func);
|
|
}
|
|
}
|
|
|
|
static void js_bound_function_finalizer(JSRuntime *rt, JSValue val)
|
|
{
|
|
JSObject *p = JS_VALUE_GET_OBJ(val);
|
|
JSBoundFunction *bf = p->u.bound_function;
|
|
int i;
|
|
|
|
JS_FreeValueRT(rt, bf->func_obj);
|
|
JS_FreeValueRT(rt, bf->this_val);
|
|
for(i = 0; i < bf->argc; i++) {
|
|
JS_FreeValueRT(rt, bf->argv[i]);
|
|
}
|
|
js_free_rt(rt, bf);
|
|
}
|
|
|
|
static void js_bound_function_mark(JSRuntime *rt, JSValue val,
|
|
JS_MarkFunc *mark_func)
|
|
{
|
|
JSObject *p = JS_VALUE_GET_OBJ(val);
|
|
JSBoundFunction *bf = p->u.bound_function;
|
|
int i;
|
|
|
|
JS_MarkValue(rt, bf->func_obj, mark_func);
|
|
JS_MarkValue(rt, bf->this_val, mark_func);
|
|
for(i = 0; i < bf->argc; i++)
|
|
JS_MarkValue(rt, bf->argv[i], mark_func);
|
|
}
|
|
|
|
static void js_for_in_iterator_finalizer(JSRuntime *rt, JSValue val)
|
|
{
|
|
JSObject *p = JS_VALUE_GET_OBJ(val);
|
|
JSForInIterator *it = p->u.for_in_iterator;
|
|
JS_FreeValueRT(rt, it->obj);
|
|
js_free_rt(rt, it);
|
|
}
|
|
|
|
static void js_for_in_iterator_mark(JSRuntime *rt, JSValue val,
|
|
JS_MarkFunc *mark_func)
|
|
{
|
|
JSObject *p = JS_VALUE_GET_OBJ(val);
|
|
JSForInIterator *it = p->u.for_in_iterator;
|
|
JS_MarkValue(rt, it->obj, mark_func);
|
|
}
|
|
|
|
static void free_object(JSRuntime *rt, JSObject *p)
|
|
{
|
|
int i;
|
|
JSClassFinalizer *finalizer;
|
|
JSShape *sh;
|
|
JSShapeProperty *pr;
|
|
|
|
p->free_mark = 1; /* used to tell the object is invalid when
|
|
freeing cycles */
|
|
/* free all the fields */
|
|
sh = p->shape;
|
|
pr = get_shape_prop(sh);
|
|
for(i = 0; i < sh->prop_count; i++) {
|
|
free_property(rt, &p->prop[i], pr->flags);
|
|
pr++;
|
|
}
|
|
js_free_rt(rt, p->prop);
|
|
/* as an optimization we destroy the shape immediately without
|
|
putting it in gc_zero_ref_count_list */
|
|
js_free_shape(rt, sh);
|
|
|
|
/* fail safe */
|
|
p->shape = NULL;
|
|
p->prop = NULL;
|
|
|
|
if (unlikely(p->first_weak_ref)) {
|
|
reset_weak_ref(rt, &p->first_weak_ref);
|
|
}
|
|
|
|
finalizer = rt->class_array[p->class_id].finalizer;
|
|
if (finalizer)
|
|
(*finalizer)(rt, JS_MKPTR(JS_TAG_OBJECT, p));
|
|
|
|
/* fail safe */
|
|
p->class_id = 0;
|
|
p->u.opaque = NULL;
|
|
p->u.func.var_refs = NULL;
|
|
p->u.func.home_object = NULL;
|
|
|
|
remove_gc_object(&p->header);
|
|
if (rt->gc_phase == JS_GC_PHASE_REMOVE_CYCLES && p->header.ref_count != 0) {
|
|
list_add_tail(&p->header.link, &rt->gc_zero_ref_count_list);
|
|
} else {
|
|
js_free_rt(rt, p);
|
|
}
|
|
}
|
|
|
|
static void free_gc_object(JSRuntime *rt, JSGCObjectHeader *gp)
|
|
{
|
|
switch(gp->gc_obj_type) {
|
|
case JS_GC_OBJ_TYPE_JS_OBJECT:
|
|
free_object(rt, (JSObject *)gp);
|
|
break;
|
|
case JS_GC_OBJ_TYPE_FUNCTION_BYTECODE:
|
|
free_function_bytecode(rt, (JSFunctionBytecode *)gp);
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
}
|
|
|
|
static void free_zero_refcount(JSRuntime *rt)
|
|
{
|
|
struct list_head *el;
|
|
JSGCObjectHeader *p;
|
|
|
|
rt->gc_phase = JS_GC_PHASE_DECREF;
|
|
for(;;) {
|
|
el = rt->gc_zero_ref_count_list.next;
|
|
if (el == &rt->gc_zero_ref_count_list)
|
|
break;
|
|
p = list_entry(el, JSGCObjectHeader, link);
|
|
assert(p->ref_count == 0);
|
|
free_gc_object(rt, p);
|
|
}
|
|
rt->gc_phase = JS_GC_PHASE_NONE;
|
|
}
|
|
|
|
/* called with the ref_count of 'v' reaches zero. */
|
|
void __JS_FreeValueRT(JSRuntime *rt, JSValue v)
|
|
{
|
|
uint32_t tag = JS_VALUE_GET_TAG(v);
|
|
|
|
#ifdef DUMP_FREE
|
|
if (check_dump_flag(rt, DUMP_FREE)) {
|
|
/* Prevent invalid object access during GC */
|
|
if ((rt->gc_phase != JS_GC_PHASE_REMOVE_CYCLES)
|
|
|| (tag != JS_TAG_OBJECT && tag != JS_TAG_FUNCTION_BYTECODE)) {
|
|
printf("Freeing ");
|
|
if (tag == JS_TAG_OBJECT) {
|
|
JS_DumpObject(rt, JS_VALUE_GET_OBJ(v));
|
|
} else {
|
|
JS_DumpValue(rt, v);
|
|
printf("\n");
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
switch(tag) {
|
|
case JS_TAG_STRING:
|
|
{
|
|
JSString *p = JS_VALUE_GET_STRING(v);
|
|
if (p->atom_type) {
|
|
JS_FreeAtomStruct(rt, p);
|
|
} else {
|
|
#ifdef DUMP_LEAKS
|
|
list_del(&p->link);
|
|
#endif
|
|
js_free_rt(rt, p);
|
|
}
|
|
}
|
|
break;
|
|
case JS_TAG_OBJECT:
|
|
case JS_TAG_FUNCTION_BYTECODE:
|
|
{
|
|
JSGCObjectHeader *p = JS_VALUE_GET_PTR(v);
|
|
if (rt->gc_phase != JS_GC_PHASE_REMOVE_CYCLES) {
|
|
list_del(&p->link);
|
|
list_add(&p->link, &rt->gc_zero_ref_count_list);
|
|
if (rt->gc_phase == JS_GC_PHASE_NONE) {
|
|
free_zero_refcount(rt);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case JS_TAG_MODULE:
|
|
abort(); /* never freed here */
|
|
break;
|
|
case JS_TAG_BIG_INT:
|
|
{
|
|
JSBigInt *bf = JS_VALUE_GET_PTR(v);
|
|
bf_delete(&bf->num);
|
|
js_free_rt(rt, bf);
|
|
}
|
|
break;
|
|
case JS_TAG_SYMBOL:
|
|
{
|
|
JSAtomStruct *p = JS_VALUE_GET_PTR(v);
|
|
JS_FreeAtomStruct(rt, p);
|
|
}
|
|
break;
|
|
default:
|
|
printf("__JS_FreeValue: unknown tag=%d\n", tag);
|
|
abort();
|
|
}
|
|
}
|
|
|
|
void __JS_FreeValue(JSContext *ctx, JSValue v)
|
|
{
|
|
__JS_FreeValueRT(ctx->rt, v);
|
|
}
|
|
|
|
/* garbage collection */
|
|
|
|
static void add_gc_object(JSRuntime *rt, JSGCObjectHeader *h,
|
|
JSGCObjectTypeEnum type)
|
|
{
|
|
h->mark = 0;
|
|
h->gc_obj_type = type;
|
|
list_add_tail(&h->link, &rt->gc_obj_list);
|
|
}
|
|
|
|
static void remove_gc_object(JSGCObjectHeader *h)
|
|
{
|
|
list_del(&h->link);
|
|
}
|
|
|
|
void JS_MarkValue(JSRuntime *rt, JSValue val, JS_MarkFunc *mark_func)
|
|
{
|
|
if (JS_VALUE_HAS_REF_COUNT(val)) {
|
|
switch(JS_VALUE_GET_TAG(val)) {
|
|
case JS_TAG_OBJECT:
|
|
case JS_TAG_FUNCTION_BYTECODE:
|
|
mark_func(rt, JS_VALUE_GET_PTR(val));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void mark_children(JSRuntime *rt, JSGCObjectHeader *gp,
|
|
JS_MarkFunc *mark_func)
|
|
{
|
|
switch(gp->gc_obj_type) {
|
|
case JS_GC_OBJ_TYPE_JS_OBJECT:
|
|
{
|
|
JSObject *p = (JSObject *)gp;
|
|
JSShapeProperty *prs;
|
|
JSShape *sh;
|
|
int i;
|
|
sh = p->shape;
|
|
mark_func(rt, &sh->header);
|
|
/* mark all the fields */
|
|
prs = get_shape_prop(sh);
|
|
for(i = 0; i < sh->prop_count; i++) {
|
|
JSProperty *pr = &p->prop[i];
|
|
if (prs->atom != JS_ATOM_NULL) {
|
|
if (prs->flags & JS_PROP_TMASK) {
|
|
if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) {
|
|
if (pr->u.getset.getter)
|
|
mark_func(rt, &pr->u.getset.getter->header);
|
|
if (pr->u.getset.setter)
|
|
mark_func(rt, &pr->u.getset.setter->header);
|
|
} else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) {
|
|
if (pr->u.var_ref->is_detached) {
|
|
/* Note: the tag does not matter
|
|
provided it is a GC object */
|
|
mark_func(rt, &pr->u.var_ref->header);
|
|
}
|
|
} else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) {
|
|
js_autoinit_mark(rt, pr, mark_func);
|
|
}
|
|
} else {
|
|
JS_MarkValue(rt, pr->u.value, mark_func);
|
|
}
|
|
}
|
|
prs++;
|
|
}
|
|
|
|
if (p->class_id != JS_CLASS_OBJECT) {
|
|
JSClassGCMark *gc_mark;
|
|
gc_mark = rt->class_array[p->class_id].gc_mark;
|
|
if (gc_mark)
|
|
gc_mark(rt, JS_MKPTR(JS_TAG_OBJECT, p), mark_func);
|
|
}
|
|
}
|
|
break;
|
|
case JS_GC_OBJ_TYPE_FUNCTION_BYTECODE:
|
|
/* the template objects can be part of a cycle */
|
|
{
|
|
JSShape **shape, *(*shapes)[IC_CACHE_ITEM_CAPACITY];
|
|
JSFunctionBytecode *b = (JSFunctionBytecode *)gp;
|
|
int i;
|
|
for(i = 0; i < b->cpool_count; i++) {
|
|
JS_MarkValue(rt, b->cpool[i], mark_func);
|
|
}
|
|
if (b->realm)
|
|
mark_func(rt, &b->realm->header);
|
|
if (b->ic) {
|
|
for (i = 0; i < b->ic->count; i++) {
|
|
shapes = &b->ic->cache[i].shape;
|
|
for (shape = *shapes; shape != endof(*shapes); shape++)
|
|
if (*shape)
|
|
mark_func(rt, &(*shape)->header);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case JS_GC_OBJ_TYPE_VAR_REF:
|
|
{
|
|
JSVarRef *var_ref = (JSVarRef *)gp;
|
|
/* only detached variable referenced are taken into account */
|
|
assert(var_ref->is_detached);
|
|
JS_MarkValue(rt, *var_ref->pvalue, mark_func);
|
|
}
|
|
break;
|
|
case JS_GC_OBJ_TYPE_ASYNC_FUNCTION:
|
|
{
|
|
JSAsyncFunctionData *s = (JSAsyncFunctionData *)gp;
|
|
if (s->is_active)
|
|
async_func_mark(rt, &s->func_state, mark_func);
|
|
JS_MarkValue(rt, s->resolving_funcs[0], mark_func);
|
|
JS_MarkValue(rt, s->resolving_funcs[1], mark_func);
|
|
}
|
|
break;
|
|
case JS_GC_OBJ_TYPE_SHAPE:
|
|
{
|
|
JSShape *sh = (JSShape *)gp;
|
|
if (sh->proto != NULL) {
|
|
mark_func(rt, &sh->proto->header);
|
|
}
|
|
}
|
|
break;
|
|
case JS_GC_OBJ_TYPE_JS_CONTEXT:
|
|
{
|
|
JSContext *ctx = (JSContext *)gp;
|
|
JS_MarkContext(rt, ctx, mark_func);
|
|
}
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
}
|
|
|
|
static void gc_decref_child(JSRuntime *rt, JSGCObjectHeader *p)
|
|
{
|
|
assert(p->ref_count > 0);
|
|
p->ref_count--;
|
|
if (p->ref_count == 0 && p->mark == 1) {
|
|
list_del(&p->link);
|
|
list_add_tail(&p->link, &rt->tmp_obj_list);
|
|
}
|
|
}
|
|
|
|
static void gc_decref(JSRuntime *rt)
|
|
{
|
|
struct list_head *el, *el1;
|
|
JSGCObjectHeader *p;
|
|
|
|
init_list_head(&rt->tmp_obj_list);
|
|
|
|
/* decrement the refcount of all the children of all the GC
|
|
objects and move the GC objects with zero refcount to
|
|
tmp_obj_list */
|
|
list_for_each_safe(el, el1, &rt->gc_obj_list) {
|
|
p = list_entry(el, JSGCObjectHeader, link);
|
|
assert(p->mark == 0);
|
|
mark_children(rt, p, gc_decref_child);
|
|
p->mark = 1;
|
|
if (p->ref_count == 0) {
|
|
list_del(&p->link);
|
|
list_add_tail(&p->link, &rt->tmp_obj_list);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void gc_scan_incref_child(JSRuntime *rt, JSGCObjectHeader *p)
|
|
{
|
|
p->ref_count++;
|
|
if (p->ref_count == 1) {
|
|
/* ref_count was 0: remove from tmp_obj_list and add at the
|
|
end of gc_obj_list */
|
|
list_del(&p->link);
|
|
list_add_tail(&p->link, &rt->gc_obj_list);
|
|
p->mark = 0; /* reset the mark for the next GC call */
|
|
}
|
|
}
|
|
|
|
static void gc_scan_incref_child2(JSRuntime *rt, JSGCObjectHeader *p)
|
|
{
|
|
p->ref_count++;
|
|
}
|
|
|
|
static void gc_scan(JSRuntime *rt)
|
|
{
|
|
struct list_head *el;
|
|
JSGCObjectHeader *p;
|
|
|
|
/* keep the objects with a refcount > 0 and their children. */
|
|
list_for_each(el, &rt->gc_obj_list) {
|
|
p = list_entry(el, JSGCObjectHeader, link);
|
|
assert(p->ref_count > 0);
|
|
p->mark = 0; /* reset the mark for the next GC call */
|
|
mark_children(rt, p, gc_scan_incref_child);
|
|
}
|
|
|
|
/* restore the refcount of the objects to be deleted. */
|
|
list_for_each(el, &rt->tmp_obj_list) {
|
|
p = list_entry(el, JSGCObjectHeader, link);
|
|
mark_children(rt, p, gc_scan_incref_child2);
|
|
}
|
|
}
|
|
|
|
static void gc_free_cycles(JSRuntime *rt)
|
|
{
|
|
struct list_head *el, *el1;
|
|
JSGCObjectHeader *p;
|
|
#ifdef DUMP_GC_FREE
|
|
BOOL header_done = FALSE;
|
|
#endif
|
|
|
|
rt->gc_phase = JS_GC_PHASE_REMOVE_CYCLES;
|
|
|
|
for(;;) {
|
|
el = rt->tmp_obj_list.next;
|
|
if (el == &rt->tmp_obj_list)
|
|
break;
|
|
p = list_entry(el, JSGCObjectHeader, link);
|
|
/* Only need to free the GC object associated with JS
|
|
values. The rest will be automatically removed because they
|
|
must be referenced by them. */
|
|
switch(p->gc_obj_type) {
|
|
case JS_GC_OBJ_TYPE_JS_OBJECT:
|
|
case JS_GC_OBJ_TYPE_FUNCTION_BYTECODE:
|
|
#ifdef DUMP_GC_FREE
|
|
if (check_dump_flag(rt, DUMP_GC_FREE)) {
|
|
if (!header_done) {
|
|
printf("Freeing cycles:\n");
|
|
JS_DumpObjectHeader(rt);
|
|
header_done = TRUE;
|
|
}
|
|
JS_DumpGCObject(rt, p);
|
|
}
|
|
#endif
|
|
free_gc_object(rt, p);
|
|
break;
|
|
default:
|
|
list_del(&p->link);
|
|
list_add_tail(&p->link, &rt->gc_zero_ref_count_list);
|
|
break;
|
|
}
|
|
}
|
|
rt->gc_phase = JS_GC_PHASE_NONE;
|
|
|
|
list_for_each_safe(el, el1, &rt->gc_zero_ref_count_list) {
|
|
p = list_entry(el, JSGCObjectHeader, link);
|
|
assert(p->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT ||
|
|
p->gc_obj_type == JS_GC_OBJ_TYPE_FUNCTION_BYTECODE);
|
|
js_free_rt(rt, p);
|
|
}
|
|
|
|
init_list_head(&rt->gc_zero_ref_count_list);
|
|
}
|
|
|
|
void JS_RunGC(JSRuntime *rt)
|
|
{
|
|
/* decrement the reference of the children of each object. mark =
|
|
1 after this pass. */
|
|
gc_decref(rt);
|
|
|
|
/* keep the GC objects with a non zero refcount and their childs */
|
|
gc_scan(rt);
|
|
|
|
/* free the GC objects in a cycle */
|
|
gc_free_cycles(rt);
|
|
}
|
|
|
|
/* Return false if not an object or if the object has already been
|
|
freed (zombie objects are visible in finalizers when freeing
|
|
cycles). */
|
|
BOOL JS_IsLiveObject(JSRuntime *rt, JSValue obj)
|
|
{
|
|
JSObject *p;
|
|
if (!JS_IsObject(obj))
|
|
return FALSE;
|
|
p = JS_VALUE_GET_OBJ(obj);
|
|
return !p->free_mark;
|
|
}
|
|
|
|
/* Compute memory used by various object types */
|
|
/* XXX: poor man's approach to handling multiply referenced objects */
|
|
typedef struct JSMemoryUsage_helper {
|
|
double memory_used_count;
|
|
double str_count;
|
|
double str_size;
|
|
int64_t js_func_count;
|
|
double js_func_size;
|
|
int64_t js_func_code_size;
|
|
int64_t js_func_pc2line_count;
|
|
int64_t js_func_pc2line_size;
|
|
} JSMemoryUsage_helper;
|
|
|
|
static void compute_value_size(JSValue val, JSMemoryUsage_helper *hp);
|
|
|
|
static void compute_jsstring_size(JSString *str, JSMemoryUsage_helper *hp)
|
|
{
|
|
if (!str->atom_type) { /* atoms are handled separately */
|
|
double s_ref_count = str->header.ref_count;
|
|
hp->str_count += 1 / s_ref_count;
|
|
hp->str_size += ((sizeof(*str) + (str->len << str->is_wide_char) +
|
|
1 - str->is_wide_char) / s_ref_count);
|
|
}
|
|
}
|
|
|
|
static void compute_bytecode_size(JSFunctionBytecode *b, JSMemoryUsage_helper *hp)
|
|
{
|
|
int memory_used_count, js_func_size, i;
|
|
|
|
memory_used_count = 0;
|
|
js_func_size = sizeof(*b);
|
|
if (b->vardefs) {
|
|
js_func_size += (b->arg_count + b->var_count) * sizeof(*b->vardefs);
|
|
}
|
|
if (b->cpool) {
|
|
js_func_size += b->cpool_count * sizeof(*b->cpool);
|
|
for (i = 0; i < b->cpool_count; i++) {
|
|
JSValue val = b->cpool[i];
|
|
compute_value_size(val, hp);
|
|
}
|
|
}
|
|
if (b->closure_var) {
|
|
js_func_size += b->closure_var_count * sizeof(*b->closure_var);
|
|
}
|
|
if (b->byte_code_buf) {
|
|
hp->js_func_code_size += b->byte_code_len;
|
|
}
|
|
memory_used_count++;
|
|
js_func_size += b->source_len + 1;
|
|
if (b->pc2line_len) {
|
|
memory_used_count++;
|
|
hp->js_func_pc2line_count += 1;
|
|
hp->js_func_pc2line_size += b->pc2line_len;
|
|
}
|
|
hp->js_func_size += js_func_size;
|
|
hp->js_func_count += 1;
|
|
hp->memory_used_count += memory_used_count;
|
|
}
|
|
|
|
static void compute_value_size(JSValue val, JSMemoryUsage_helper *hp)
|
|
{
|
|
switch(JS_VALUE_GET_TAG(val)) {
|
|
case JS_TAG_STRING:
|
|
compute_jsstring_size(JS_VALUE_GET_STRING(val), hp);
|
|
break;
|
|
case JS_TAG_BIG_INT:
|
|
/* should track JSBigInt usage */
|
|
break;
|
|
}
|
|
}
|
|
|
|
void JS_ComputeMemoryUsage(JSRuntime *rt, JSMemoryUsage *s)
|
|
{
|
|
struct list_head *el, *el1;
|
|
int i;
|
|
JSMemoryUsage_helper mem = { 0 }, *hp = &mem;
|
|
|
|
memset(s, 0, sizeof(*s));
|
|
s->malloc_count = rt->malloc_state.malloc_count;
|
|
s->malloc_size = rt->malloc_state.malloc_size;
|
|
s->malloc_limit = rt->malloc_state.malloc_limit;
|
|
|
|
s->memory_used_count = 2; /* rt + rt->class_array */
|
|
s->memory_used_size = sizeof(JSRuntime) + sizeof(JSClass) * rt->class_count;
|
|
|
|
list_for_each(el, &rt->context_list) {
|
|
JSContext *ctx = list_entry(el, JSContext, link);
|
|
JSShape *sh = ctx->array_shape;
|
|
s->memory_used_count += 2; /* ctx + ctx->class_proto */
|
|
s->memory_used_size += sizeof(JSContext) +
|
|
sizeof(JSValue) * rt->class_count;
|
|
s->binary_object_count += ctx->binary_object_count;
|
|
s->binary_object_size += ctx->binary_object_size;
|
|
|
|
/* the hashed shapes are counted separately */
|
|
if (sh && !sh->is_hashed) {
|
|
int hash_size = sh->prop_hash_mask + 1;
|
|
s->shape_count++;
|
|
s->shape_size += get_shape_size(hash_size, sh->prop_size);
|
|
}
|
|
list_for_each(el1, &ctx->loaded_modules) {
|
|
JSModuleDef *m = list_entry(el1, JSModuleDef, link);
|
|
s->memory_used_count += 1;
|
|
s->memory_used_size += sizeof(*m);
|
|
if (m->req_module_entries) {
|
|
s->memory_used_count += 1;
|
|
s->memory_used_size += m->req_module_entries_count * sizeof(*m->req_module_entries);
|
|
}
|
|
if (m->export_entries) {
|
|
s->memory_used_count += 1;
|
|
s->memory_used_size += m->export_entries_count * sizeof(*m->export_entries);
|
|
for (i = 0; i < m->export_entries_count; i++) {
|
|
JSExportEntry *me = &m->export_entries[i];
|
|
if (me->export_type == JS_EXPORT_TYPE_LOCAL && me->u.local.var_ref) {
|
|
/* potential multiple count */
|
|
s->memory_used_count += 1;
|
|
compute_value_size(me->u.local.var_ref->value, hp);
|
|
}
|
|
}
|
|
}
|
|
if (m->star_export_entries) {
|
|
s->memory_used_count += 1;
|
|
s->memory_used_size += m->star_export_entries_count * sizeof(*m->star_export_entries);
|
|
}
|
|
if (m->import_entries) {
|
|
s->memory_used_count += 1;
|
|
s->memory_used_size += m->import_entries_count * sizeof(*m->import_entries);
|
|
}
|
|
compute_value_size(m->module_ns, hp);
|
|
compute_value_size(m->func_obj, hp);
|
|
}
|
|
}
|
|
|
|
list_for_each(el, &rt->gc_obj_list) {
|
|
JSGCObjectHeader *gp = list_entry(el, JSGCObjectHeader, link);
|
|
JSObject *p;
|
|
JSShape *sh;
|
|
JSShapeProperty *prs;
|
|
|
|
/* XXX: could count the other GC object types too */
|
|
if (gp->gc_obj_type == JS_GC_OBJ_TYPE_FUNCTION_BYTECODE) {
|
|
compute_bytecode_size((JSFunctionBytecode *)gp, hp);
|
|
continue;
|
|
} else if (gp->gc_obj_type != JS_GC_OBJ_TYPE_JS_OBJECT) {
|
|
continue;
|
|
}
|
|
p = (JSObject *)gp;
|
|
sh = p->shape;
|
|
s->obj_count++;
|
|
if (p->prop) {
|
|
s->memory_used_count++;
|
|
s->prop_size += sh->prop_size * sizeof(*p->prop);
|
|
s->prop_count += sh->prop_count;
|
|
prs = get_shape_prop(sh);
|
|
for(i = 0; i < sh->prop_count; i++) {
|
|
JSProperty *pr = &p->prop[i];
|
|
if (prs->atom != JS_ATOM_NULL && !(prs->flags & JS_PROP_TMASK)) {
|
|
compute_value_size(pr->u.value, hp);
|
|
}
|
|
prs++;
|
|
}
|
|
}
|
|
/* the hashed shapes are counted separately */
|
|
if (!sh->is_hashed) {
|
|
int hash_size = sh->prop_hash_mask + 1;
|
|
s->shape_count++;
|
|
s->shape_size += get_shape_size(hash_size, sh->prop_size);
|
|
}
|
|
|
|
switch(p->class_id) {
|
|
case JS_CLASS_ARRAY: /* u.array | length */
|
|
case JS_CLASS_ARGUMENTS: /* u.array | length */
|
|
s->array_count++;
|
|
if (p->fast_array) {
|
|
s->fast_array_count++;
|
|
if (p->u.array.u.values) {
|
|
s->memory_used_count++;
|
|
s->memory_used_size += p->u.array.count *
|
|
sizeof(*p->u.array.u.values);
|
|
s->fast_array_elements += p->u.array.count;
|
|
for (i = 0; i < p->u.array.count; i++) {
|
|
compute_value_size(p->u.array.u.values[i], hp);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case JS_CLASS_NUMBER: /* u.object_data */
|
|
case JS_CLASS_STRING: /* u.object_data */
|
|
case JS_CLASS_BOOLEAN: /* u.object_data */
|
|
case JS_CLASS_SYMBOL: /* u.object_data */
|
|
case JS_CLASS_DATE: /* u.object_data */
|
|
case JS_CLASS_BIG_INT: /* u.object_data */
|
|
compute_value_size(p->u.object_data, hp);
|
|
break;
|
|
case JS_CLASS_C_FUNCTION: /* u.cfunc */
|
|
s->c_func_count++;
|
|
break;
|
|
case JS_CLASS_BYTECODE_FUNCTION: /* u.func */
|
|
{
|
|
JSFunctionBytecode *b = p->u.func.function_bytecode;
|
|
JSVarRef **var_refs = p->u.func.var_refs;
|
|
/* home_object: object will be accounted for in list scan */
|
|
if (var_refs) {
|
|
s->memory_used_count++;
|
|
s->js_func_size += b->closure_var_count * sizeof(*var_refs);
|
|
for (i = 0; i < b->closure_var_count; i++) {
|
|
if (var_refs[i]) {
|
|
double ref_count = var_refs[i]->header.ref_count;
|
|
s->memory_used_count += 1 / ref_count;
|
|
s->js_func_size += sizeof(*var_refs[i]) / ref_count;
|
|
/* handle non object closed values */
|
|
if (var_refs[i]->pvalue == &var_refs[i]->value) {
|
|
/* potential multiple count */
|
|
compute_value_size(var_refs[i]->value, hp);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case JS_CLASS_BOUND_FUNCTION: /* u.bound_function */
|
|
{
|
|
JSBoundFunction *bf = p->u.bound_function;
|
|
/* func_obj and this_val are objects */
|
|
for (i = 0; i < bf->argc; i++) {
|
|
compute_value_size(bf->argv[i], hp);
|
|
}
|
|
s->memory_used_count += 1;
|
|
s->memory_used_size += sizeof(*bf) + bf->argc * sizeof(*bf->argv);
|
|
}
|
|
break;
|
|
case JS_CLASS_C_FUNCTION_DATA: /* u.c_function_data_record */
|
|
{
|
|
JSCFunctionDataRecord *fd = p->u.c_function_data_record;
|
|
if (fd) {
|
|
for (i = 0; i < fd->data_len; i++) {
|
|
compute_value_size(fd->data[i], hp);
|
|
}
|
|
s->memory_used_count += 1;
|
|
s->memory_used_size += sizeof(*fd) + fd->data_len * sizeof(*fd->data);
|
|
}
|
|
}
|
|
break;
|
|
case JS_CLASS_REGEXP: /* u.regexp */
|
|
compute_jsstring_size(p->u.regexp.pattern, hp);
|
|
compute_jsstring_size(p->u.regexp.bytecode, hp);
|
|
break;
|
|
|
|
case JS_CLASS_FOR_IN_ITERATOR: /* u.for_in_iterator */
|
|
{
|
|
JSForInIterator *it = p->u.for_in_iterator;
|
|
if (it) {
|
|
compute_value_size(it->obj, hp);
|
|
s->memory_used_count += 1;
|
|
s->memory_used_size += sizeof(*it);
|
|
}
|
|
}
|
|
break;
|
|
case JS_CLASS_ARRAY_BUFFER: /* u.array_buffer */
|
|
case JS_CLASS_SHARED_ARRAY_BUFFER: /* u.array_buffer */
|
|
{
|
|
JSArrayBuffer *abuf = p->u.array_buffer;
|
|
if (abuf) {
|
|
s->memory_used_count += 1;
|
|
s->memory_used_size += sizeof(*abuf);
|
|
if (abuf->data) {
|
|
s->memory_used_count += 1;
|
|
s->memory_used_size += abuf->byte_length;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case JS_CLASS_GENERATOR: /* u.generator_data */
|
|
case JS_CLASS_UINT8C_ARRAY: /* u.typed_array / u.array */
|
|
case JS_CLASS_INT8_ARRAY: /* u.typed_array / u.array */
|
|
case JS_CLASS_UINT8_ARRAY: /* u.typed_array / u.array */
|
|
case JS_CLASS_INT16_ARRAY: /* u.typed_array / u.array */
|
|
case JS_CLASS_UINT16_ARRAY: /* u.typed_array / u.array */
|
|
case JS_CLASS_INT32_ARRAY: /* u.typed_array / u.array */
|
|
case JS_CLASS_UINT32_ARRAY: /* u.typed_array / u.array */
|
|
case JS_CLASS_BIG_INT64_ARRAY: /* u.typed_array / u.array */
|
|
case JS_CLASS_BIG_UINT64_ARRAY: /* u.typed_array / u.array */
|
|
case JS_CLASS_FLOAT32_ARRAY: /* u.typed_array / u.array */
|
|
case JS_CLASS_FLOAT64_ARRAY: /* u.typed_array / u.array */
|
|
case JS_CLASS_DATAVIEW: /* u.typed_array */
|
|
case JS_CLASS_MAP: /* u.map_state */
|
|
case JS_CLASS_SET: /* u.map_state */
|
|
case JS_CLASS_WEAKMAP: /* u.map_state */
|
|
case JS_CLASS_WEAKSET: /* u.map_state */
|
|
case JS_CLASS_MAP_ITERATOR: /* u.map_iterator_data */
|
|
case JS_CLASS_SET_ITERATOR: /* u.map_iterator_data */
|
|
case JS_CLASS_ARRAY_ITERATOR: /* u.array_iterator_data */
|
|
case JS_CLASS_STRING_ITERATOR: /* u.array_iterator_data */
|
|
case JS_CLASS_PROXY: /* u.proxy_data */
|
|
case JS_CLASS_PROMISE: /* u.promise_data */
|
|
case JS_CLASS_PROMISE_RESOLVE_FUNCTION: /* u.promise_function_data */
|
|
case JS_CLASS_PROMISE_REJECT_FUNCTION: /* u.promise_function_data */
|
|
case JS_CLASS_ASYNC_FUNCTION_RESOLVE: /* u.async_function_data */
|
|
case JS_CLASS_ASYNC_FUNCTION_REJECT: /* u.async_function_data */
|
|
case JS_CLASS_ASYNC_FROM_SYNC_ITERATOR: /* u.async_from_sync_iterator_data */
|
|
case JS_CLASS_ASYNC_GENERATOR: /* u.async_generator_data */
|
|
/* TODO */
|
|
default:
|
|
/* XXX: class definition should have an opaque block size */
|
|
if (p->u.opaque) {
|
|
s->memory_used_count += 1;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
s->obj_size += s->obj_count * sizeof(JSObject);
|
|
|
|
/* hashed shapes */
|
|
s->memory_used_count++; /* rt->shape_hash */
|
|
s->memory_used_size += sizeof(rt->shape_hash[0]) * rt->shape_hash_size;
|
|
for(i = 0; i < rt->shape_hash_size; i++) {
|
|
JSShape *sh;
|
|
for(sh = rt->shape_hash[i]; sh != NULL; sh = sh->shape_hash_next) {
|
|
int hash_size = sh->prop_hash_mask + 1;
|
|
s->shape_count++;
|
|
s->shape_size += get_shape_size(hash_size, sh->prop_size);
|
|
}
|
|
}
|
|
|
|
/* atoms */
|
|
s->memory_used_count += 2; /* rt->atom_array, rt->atom_hash */
|
|
s->atom_count = rt->atom_count;
|
|
s->atom_size = sizeof(rt->atom_array[0]) * rt->atom_size +
|
|
sizeof(rt->atom_hash[0]) * rt->atom_hash_size;
|
|
for(i = 0; i < rt->atom_size; i++) {
|
|
JSAtomStruct *p = rt->atom_array[i];
|
|
if (!atom_is_free(p)) {
|
|
s->atom_size += (sizeof(*p) + (p->len << p->is_wide_char) +
|
|
1 - p->is_wide_char);
|
|
}
|
|
}
|
|
s->str_count = round(mem.str_count);
|
|
s->str_size = round(mem.str_size);
|
|
s->js_func_count = mem.js_func_count;
|
|
s->js_func_size = round(mem.js_func_size);
|
|
s->js_func_code_size = mem.js_func_code_size;
|
|
s->js_func_pc2line_count = mem.js_func_pc2line_count;
|
|
s->js_func_pc2line_size = mem.js_func_pc2line_size;
|
|
s->memory_used_count += round(mem.memory_used_count) +
|
|
s->atom_count + s->str_count +
|
|
s->obj_count + s->shape_count +
|
|
s->js_func_count + s->js_func_pc2line_count;
|
|
s->memory_used_size += s->atom_size + s->str_size +
|
|
s->obj_size + s->prop_size + s->shape_size +
|
|
s->js_func_size + s->js_func_code_size + s->js_func_pc2line_size;
|
|
}
|
|
|
|
void JS_DumpMemoryUsage(FILE *fp, const JSMemoryUsage *s, JSRuntime *rt)
|
|
{
|
|
fprintf(fp, "QuickJS-ng memory usage -- %s version, %d-bit, %s Endian, malloc limit: %"PRId64"\n\n",
|
|
JS_GetVersion(), (int)sizeof(void *) * 8, is_be() ? "Big" : "Little", s->malloc_limit);
|
|
if (rt) {
|
|
static const struct {
|
|
const char *name;
|
|
size_t size;
|
|
} object_types[] = {
|
|
{ "JSRuntime", sizeof(JSRuntime) },
|
|
{ "JSContext", sizeof(JSContext) },
|
|
{ "JSObject", sizeof(JSObject) },
|
|
{ "JSString", sizeof(JSString) },
|
|
{ "JSFunctionBytecode", sizeof(JSFunctionBytecode) },
|
|
};
|
|
int i, usage_size_ok = 0;
|
|
for(i = 0; i < countof(object_types); i++) {
|
|
unsigned int size = object_types[i].size;
|
|
void *p = js_malloc_rt(rt, size);
|
|
if (p) {
|
|
unsigned int size1 = js_malloc_usable_size_rt(rt, p);
|
|
if (size1 >= size) {
|
|
usage_size_ok = 1;
|
|
fprintf(fp, " %3u + %-2u %s\n",
|
|
size, size1 - size, object_types[i].name);
|
|
}
|
|
js_free_rt(rt, p);
|
|
}
|
|
}
|
|
if (!usage_size_ok) {
|
|
fprintf(fp, " malloc_usable_size unavailable\n");
|
|
}
|
|
{
|
|
int obj_classes[JS_CLASS_INIT_COUNT + 1] = { 0 };
|
|
int class_id;
|
|
struct list_head *el;
|
|
list_for_each(el, &rt->gc_obj_list) {
|
|
JSGCObjectHeader *gp = list_entry(el, JSGCObjectHeader, link);
|
|
JSObject *p;
|
|
if (gp->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT) {
|
|
p = (JSObject *)gp;
|
|
obj_classes[min_uint32(p->class_id, JS_CLASS_INIT_COUNT)]++;
|
|
}
|
|
}
|
|
fprintf(fp, "\n" "JSObject classes\n");
|
|
if (obj_classes[0])
|
|
fprintf(fp, " %5d %2.0d %s\n", obj_classes[0], 0, "none");
|
|
for (class_id = 1; class_id < JS_CLASS_INIT_COUNT; class_id++) {
|
|
if (obj_classes[class_id]) {
|
|
char buf[ATOM_GET_STR_BUF_SIZE];
|
|
fprintf(fp, " %5d %2.0d %s\n", obj_classes[class_id], class_id,
|
|
JS_AtomGetStrRT(rt, buf, sizeof(buf), js_std_class_def[class_id - 1].class_name));
|
|
}
|
|
}
|
|
if (obj_classes[JS_CLASS_INIT_COUNT])
|
|
fprintf(fp, " %5d %2.0d %s\n", obj_classes[JS_CLASS_INIT_COUNT], 0, "other");
|
|
}
|
|
fprintf(fp, "\n");
|
|
}
|
|
fprintf(fp, "%-20s %8s %8s\n", "NAME", "COUNT", "SIZE");
|
|
|
|
if (s->malloc_count) {
|
|
fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per block)\n",
|
|
"memory allocated", s->malloc_count, s->malloc_size,
|
|
(double)s->malloc_size / s->malloc_count);
|
|
fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%d overhead, %0.1f average slack)\n",
|
|
"memory used", s->memory_used_count, s->memory_used_size,
|
|
MALLOC_OVERHEAD, ((double)(s->malloc_size - s->memory_used_size) /
|
|
s->memory_used_count));
|
|
}
|
|
if (s->atom_count) {
|
|
fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per atom)\n",
|
|
"atoms", s->atom_count, s->atom_size,
|
|
(double)s->atom_size / s->atom_count);
|
|
}
|
|
if (s->str_count) {
|
|
fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per string)\n",
|
|
"strings", s->str_count, s->str_size,
|
|
(double)s->str_size / s->str_count);
|
|
}
|
|
if (s->obj_count) {
|
|
fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per object)\n",
|
|
"objects", s->obj_count, s->obj_size,
|
|
(double)s->obj_size / s->obj_count);
|
|
fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per object)\n",
|
|
" properties", s->prop_count, s->prop_size,
|
|
(double)s->prop_count / s->obj_count);
|
|
fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per shape)\n",
|
|
" shapes", s->shape_count, s->shape_size,
|
|
(double)s->shape_size / s->shape_count);
|
|
}
|
|
if (s->js_func_count) {
|
|
fprintf(fp, "%-20s %8"PRId64" %8"PRId64"\n",
|
|
"bytecode functions", s->js_func_count, s->js_func_size);
|
|
fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per function)\n",
|
|
" bytecode", s->js_func_count, s->js_func_code_size,
|
|
(double)s->js_func_code_size / s->js_func_count);
|
|
if (s->js_func_pc2line_count) {
|
|
fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per function)\n",
|
|
" pc2line", s->js_func_pc2line_count,
|
|
s->js_func_pc2line_size,
|
|
(double)s->js_func_pc2line_size / s->js_func_pc2line_count);
|
|
}
|
|
}
|
|
if (s->c_func_count) {
|
|
fprintf(fp, "%-20s %8"PRId64"\n", "C functions", s->c_func_count);
|
|
}
|
|
if (s->array_count) {
|
|
fprintf(fp, "%-20s %8"PRId64"\n", "arrays", s->array_count);
|
|
if (s->fast_array_count) {
|
|
fprintf(fp, "%-20s %8"PRId64"\n", " fast arrays", s->fast_array_count);
|
|
fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per fast array)\n",
|
|
" elements", s->fast_array_elements,
|
|
s->fast_array_elements * (int)sizeof(JSValue),
|
|
(double)s->fast_array_elements / s->fast_array_count);
|
|
}
|
|
}
|
|
if (s->binary_object_count) {
|
|
fprintf(fp, "%-20s %8"PRId64" %8"PRId64"\n",
|
|
"binary objects", s->binary_object_count, s->binary_object_size);
|
|
}
|
|
}
|
|
|
|
JSValue JS_GetGlobalObject(JSContext *ctx)
|
|
{
|
|
return js_dup(ctx->global_obj);
|
|
}
|
|
|
|
/* WARNING: obj is freed */
|
|
JSValue JS_Throw(JSContext *ctx, JSValue obj)
|
|
{
|
|
JSRuntime *rt = ctx->rt;
|
|
JS_FreeValue(ctx, rt->current_exception);
|
|
rt->current_exception = obj;
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
/* return the pending exception (cannot be called twice). */
|
|
JSValue JS_GetException(JSContext *ctx)
|
|
{
|
|
JSValue val;
|
|
JSRuntime *rt = ctx->rt;
|
|
val = rt->current_exception;
|
|
rt->current_exception = JS_NULL;
|
|
return val;
|
|
}
|
|
|
|
static void dbuf_put_leb128(DynBuf *s, uint32_t v)
|
|
{
|
|
uint32_t a;
|
|
for(;;) {
|
|
a = v & 0x7f;
|
|
v >>= 7;
|
|
if (v != 0) {
|
|
dbuf_putc(s, a | 0x80);
|
|
} else {
|
|
dbuf_putc(s, a);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void dbuf_put_sleb128(DynBuf *s, int32_t v1)
|
|
{
|
|
uint32_t v = v1;
|
|
dbuf_put_leb128(s, (2 * v) ^ -(v >> 31));
|
|
}
|
|
|
|
static int get_leb128(uint32_t *pval, const uint8_t *buf,
|
|
const uint8_t *buf_end)
|
|
{
|
|
const uint8_t *ptr = buf;
|
|
uint32_t v, a, i;
|
|
v = 0;
|
|
for(i = 0; i < 5; i++) {
|
|
if (unlikely(ptr >= buf_end))
|
|
break;
|
|
a = *ptr++;
|
|
v |= (a & 0x7f) << (i * 7);
|
|
if (!(a & 0x80)) {
|
|
*pval = v;
|
|
return ptr - buf;
|
|
}
|
|
}
|
|
*pval = 0;
|
|
return -1;
|
|
}
|
|
|
|
static int get_sleb128(int32_t *pval, const uint8_t *buf,
|
|
const uint8_t *buf_end)
|
|
{
|
|
int ret;
|
|
uint32_t val;
|
|
ret = get_leb128(&val, buf, buf_end);
|
|
if (ret < 0) {
|
|
*pval = 0;
|
|
return -1;
|
|
}
|
|
*pval = (val >> 1) ^ -(val & 1);
|
|
return ret;
|
|
}
|
|
|
|
static int find_line_num(JSContext *ctx, JSFunctionBytecode *b,
|
|
uint32_t pc_value, int *col)
|
|
{
|
|
const uint8_t *p_end, *p;
|
|
int new_line_num, new_col_num, line_num, col_num, pc, v, ret;
|
|
unsigned int op;
|
|
|
|
*col = 1;
|
|
p = b->pc2line_buf;
|
|
p_end = p + b->pc2line_len;
|
|
pc = 0;
|
|
line_num = b->line_num;
|
|
col_num = b->col_num;
|
|
while (p < p_end) {
|
|
op = *p++;
|
|
if (op == 0) {
|
|
uint32_t val;
|
|
ret = get_leb128(&val, p, p_end);
|
|
if (ret < 0)
|
|
goto fail;
|
|
pc += val;
|
|
p += ret;
|
|
ret = get_sleb128(&v, p, p_end);
|
|
if (ret < 0)
|
|
goto fail;
|
|
p += ret;
|
|
new_line_num = line_num + v;
|
|
} else {
|
|
op -= PC2LINE_OP_FIRST;
|
|
pc += (op / PC2LINE_RANGE);
|
|
new_line_num = line_num + (op % PC2LINE_RANGE) + PC2LINE_BASE;
|
|
}
|
|
ret = get_sleb128(&v, p, p_end);
|
|
if (ret < 0)
|
|
goto fail;
|
|
p += ret;
|
|
new_col_num = col_num + v;
|
|
if (pc_value < pc)
|
|
break;
|
|
line_num = new_line_num;
|
|
col_num = new_col_num;
|
|
}
|
|
*col = col_num;
|
|
return line_num;
|
|
fail:
|
|
/* should never happen */
|
|
return b->line_num;
|
|
}
|
|
|
|
/* in order to avoid executing arbitrary code during the stack trace
|
|
generation, we only look at simple 'name' properties containing a
|
|
string. */
|
|
static const char *get_func_name(JSContext *ctx, JSValue func)
|
|
{
|
|
JSProperty *pr;
|
|
JSShapeProperty *prs;
|
|
JSValue val;
|
|
|
|
if (JS_VALUE_GET_TAG(func) != JS_TAG_OBJECT)
|
|
return NULL;
|
|
prs = find_own_property(&pr, JS_VALUE_GET_OBJ(func), JS_ATOM_name);
|
|
if (!prs)
|
|
return NULL;
|
|
if ((prs->flags & JS_PROP_TMASK) != JS_PROP_NORMAL)
|
|
return NULL;
|
|
val = pr->u.value;
|
|
if (JS_VALUE_GET_TAG(val) != JS_TAG_STRING)
|
|
return NULL;
|
|
return JS_ToCString(ctx, val);
|
|
}
|
|
|
|
#define JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL (1 << 0)
|
|
/* only taken into account if filename is provided */
|
|
#define JS_BACKTRACE_FLAG_SINGLE_LEVEL (1 << 1)
|
|
|
|
/* if filename != NULL, an additional level is added with the filename
|
|
and line number information (used for parse error). */
|
|
static void build_backtrace(JSContext *ctx, JSValue error_obj,
|
|
const char *filename, int line_num, int col_num,
|
|
int backtrace_flags)
|
|
{
|
|
JSStackFrame *sf;
|
|
JSValue stack, prepare, saved_exception;
|
|
DynBuf dbuf;
|
|
const char *func_name_str;
|
|
const char *str1;
|
|
JSObject *p;
|
|
JSFunctionBytecode *b;
|
|
BOOL backtrace_barrier, has_prepare;
|
|
JSRuntime *rt;
|
|
JSCallSiteData csd[64];
|
|
uint32_t i;
|
|
int stack_trace_limit;
|
|
|
|
stack_trace_limit = ctx->error_stack_trace_limit;
|
|
stack_trace_limit = min_int(stack_trace_limit, countof(csd));
|
|
stack_trace_limit = max_int(stack_trace_limit, 0);
|
|
rt = ctx->rt;
|
|
has_prepare = FALSE;
|
|
i = 0;
|
|
|
|
if (!rt->in_prepare_stack_trace && !JS_IsNull(ctx->error_ctor)) {
|
|
prepare = js_dup(ctx->error_prepare_stack);
|
|
has_prepare = JS_IsFunction(ctx, prepare);
|
|
rt->in_prepare_stack_trace = TRUE;
|
|
}
|
|
|
|
if (has_prepare) {
|
|
saved_exception = rt->current_exception;
|
|
rt->current_exception = JS_NULL;
|
|
if (stack_trace_limit == 0)
|
|
goto done;
|
|
if (filename)
|
|
js_new_callsite_data2(ctx, &csd[i++], filename, line_num, col_num);
|
|
} else {
|
|
js_dbuf_init(ctx, &dbuf);
|
|
if (stack_trace_limit == 0)
|
|
goto done;
|
|
if (filename) {
|
|
i++;
|
|
dbuf_printf(&dbuf, " at %s", filename);
|
|
if (line_num != -1)
|
|
dbuf_printf(&dbuf, ":%d:%d", line_num, col_num);
|
|
dbuf_putc(&dbuf, '\n');
|
|
}
|
|
}
|
|
|
|
if (filename && (backtrace_flags & JS_BACKTRACE_FLAG_SINGLE_LEVEL))
|
|
goto done;
|
|
|
|
for (sf = rt->current_stack_frame; sf != NULL && i < stack_trace_limit; sf = sf->prev_frame) {
|
|
if (backtrace_flags & JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL) {
|
|
backtrace_flags &= ~JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL;
|
|
continue;
|
|
}
|
|
|
|
p = JS_VALUE_GET_OBJ(sf->cur_func);
|
|
b = NULL;
|
|
backtrace_barrier = FALSE;
|
|
|
|
if (js_class_has_bytecode(p->class_id)) {
|
|
b = p->u.func.function_bytecode;
|
|
backtrace_barrier = b->backtrace_barrier;
|
|
}
|
|
|
|
if (has_prepare) {
|
|
js_new_callsite_data(ctx, &csd[i], sf);
|
|
} else {
|
|
/* func_name_str is UTF-8 encoded if needed */
|
|
func_name_str = get_func_name(ctx, sf->cur_func);
|
|
if (!func_name_str || func_name_str[0] == '\0')
|
|
str1 = "<anonymous>";
|
|
else
|
|
str1 = func_name_str;
|
|
dbuf_printf(&dbuf, " at %s", str1);
|
|
JS_FreeCString(ctx, func_name_str);
|
|
|
|
if (b) {
|
|
const char *atom_str;
|
|
int line_num1, col_num1;
|
|
|
|
/* Bytecode functions must have cur_pc set in the stack frame. */
|
|
if (sf->cur_pc == NULL)
|
|
abort();
|
|
|
|
line_num1 = find_line_num(ctx, b,
|
|
sf->cur_pc - b->byte_code_buf - 1,
|
|
&col_num1);
|
|
atom_str = JS_AtomToCString(ctx, b->filename);
|
|
dbuf_printf(&dbuf, " (%s", atom_str ? atom_str : "<null>");
|
|
JS_FreeCString(ctx, atom_str);
|
|
if (line_num1 != -1)
|
|
dbuf_printf(&dbuf, ":%d:%d", line_num1, col_num1);
|
|
dbuf_putc(&dbuf, ')');
|
|
} else {
|
|
dbuf_printf(&dbuf, " (native)");
|
|
}
|
|
dbuf_putc(&dbuf, '\n');
|
|
}
|
|
i++;
|
|
|
|
/* stop backtrace if JS_EVAL_FLAG_BACKTRACE_BARRIER was used */
|
|
if (backtrace_barrier)
|
|
break;
|
|
}
|
|
done:
|
|
if (has_prepare) {
|
|
int j = 0;
|
|
stack = JS_NewArray(ctx);
|
|
if (JS_IsException(stack)) {
|
|
stack = JS_NULL;
|
|
} else {
|
|
for (; j < i; j++) {
|
|
JSValue v = js_new_callsite(ctx, &csd[j]);
|
|
if (JS_IsException(v))
|
|
break;
|
|
if (JS_DefinePropertyValueUint32(ctx, stack, j, v, JS_PROP_C_W_E) < 0) {
|
|
JS_FreeValue(ctx, v);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// Clear the csd's we didn't use in case of error.
|
|
for (int k = j; k < i; k++) {
|
|
JS_FreeValue(ctx, csd[k].filename);
|
|
JS_FreeValue(ctx, csd[k].func);
|
|
JS_FreeValue(ctx, csd[k].func_name);
|
|
}
|
|
JSValue args[] = {
|
|
error_obj,
|
|
stack,
|
|
};
|
|
JSValue stack2 = JS_Call(ctx, prepare, ctx->error_ctor, countof(args), args);
|
|
JS_FreeValue(ctx, stack);
|
|
if (JS_IsException(stack2))
|
|
stack = JS_NULL;
|
|
else
|
|
stack = stack2;
|
|
JS_FreeValue(ctx, prepare);
|
|
JS_FreeValue(ctx, rt->current_exception);
|
|
rt->current_exception = saved_exception;
|
|
} else {
|
|
if (dbuf_error(&dbuf))
|
|
stack = JS_NULL;
|
|
else
|
|
stack = JS_NewStringLen(ctx, (char *)dbuf.buf, dbuf.size);
|
|
dbuf_free(&dbuf);
|
|
}
|
|
|
|
rt->in_prepare_stack_trace = FALSE;
|
|
JS_DefinePropertyValue(ctx, error_obj, JS_ATOM_stack, stack, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
|
|
}
|
|
|
|
/* Note: it is important that no exception is returned by this function */
|
|
static BOOL is_backtrace_needed(JSContext *ctx, JSValue obj)
|
|
{
|
|
JSObject *p;
|
|
if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)
|
|
return FALSE;
|
|
p = JS_VALUE_GET_OBJ(obj);
|
|
if (p->class_id != JS_CLASS_ERROR)
|
|
return FALSE;
|
|
if (find_own_property1(p, JS_ATOM_stack))
|
|
return FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
JSValue JS_NewError(JSContext *ctx)
|
|
{
|
|
return JS_NewObjectClass(ctx, JS_CLASS_ERROR);
|
|
}
|
|
|
|
/* fmt and arguments may be pure ASCII or UTF-8 encoded contents */
|
|
static JSValue JS_ThrowError2(JSContext *ctx, JSErrorEnum error_num,
|
|
const char *fmt, va_list ap, BOOL add_backtrace)
|
|
{
|
|
char buf[256];
|
|
JSValue obj, ret, msg;
|
|
|
|
vsnprintf(buf, sizeof(buf), fmt, ap);
|
|
obj = JS_NewObjectProtoClass(ctx, ctx->native_error_proto[error_num],
|
|
JS_CLASS_ERROR);
|
|
if (unlikely(JS_IsException(obj))) {
|
|
/* out of memory: throw JS_NULL to avoid recursing */
|
|
obj = JS_NULL;
|
|
} else {
|
|
msg = JS_NewString(ctx, buf);
|
|
if (JS_IsException(msg))
|
|
msg = JS_NewString(ctx, "Invalid error message");
|
|
if (!JS_IsException(msg)) {
|
|
JS_DefinePropertyValue(ctx, obj, JS_ATOM_message, msg,
|
|
JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
|
|
}
|
|
}
|
|
if (add_backtrace) {
|
|
build_backtrace(ctx, obj, NULL, 0, 0, 0);
|
|
}
|
|
ret = JS_Throw(ctx, obj);
|
|
return ret;
|
|
}
|
|
|
|
static JSValue JS_ThrowError(JSContext *ctx, JSErrorEnum error_num,
|
|
const char *fmt, va_list ap)
|
|
{
|
|
JSRuntime *rt = ctx->rt;
|
|
JSStackFrame *sf;
|
|
BOOL add_backtrace;
|
|
|
|
/* the backtrace is added later if called from a bytecode function */
|
|
sf = rt->current_stack_frame;
|
|
add_backtrace = !rt->in_out_of_memory &&
|
|
(!sf || (JS_GetFunctionBytecode(sf->cur_func) == NULL));
|
|
return JS_ThrowError2(ctx, error_num, fmt, ap, add_backtrace);
|
|
}
|
|
|
|
JSValue __attribute__((format(printf, 2, 3))) JS_ThrowSyntaxError(JSContext *ctx, const char *fmt, ...)
|
|
{
|
|
JSValue val;
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
val = JS_ThrowError(ctx, JS_SYNTAX_ERROR, fmt, ap);
|
|
va_end(ap);
|
|
return val;
|
|
}
|
|
|
|
JSValue __attribute__((format(printf, 2, 3))) JS_ThrowTypeError(JSContext *ctx, const char *fmt, ...)
|
|
{
|
|
JSValue val;
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
val = JS_ThrowError(ctx, JS_TYPE_ERROR, fmt, ap);
|
|
va_end(ap);
|
|
return val;
|
|
}
|
|
|
|
static int __attribute__((format(printf, 3, 4))) JS_ThrowTypeErrorOrFalse(JSContext *ctx, int flags, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
|
|
if ((flags & JS_PROP_THROW) ||
|
|
((flags & JS_PROP_THROW_STRICT) && is_strict_mode(ctx))) {
|
|
va_start(ap, fmt);
|
|
JS_ThrowError(ctx, JS_TYPE_ERROR, fmt, ap);
|
|
va_end(ap);
|
|
return -1;
|
|
} else {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* never use it directly */
|
|
static JSValue __attribute__((format(printf, 3, 4))) __JS_ThrowTypeErrorAtom(JSContext *ctx, JSAtom atom, const char *fmt, ...)
|
|
{
|
|
char buf[ATOM_GET_STR_BUF_SIZE];
|
|
return JS_ThrowTypeError(ctx, fmt,
|
|
JS_AtomGetStr(ctx, buf, sizeof(buf), atom));
|
|
}
|
|
|
|
/* never use it directly */
|
|
static JSValue __attribute__((format(printf, 3, 4))) __JS_ThrowSyntaxErrorAtom(JSContext *ctx, JSAtom atom, const char *fmt, ...)
|
|
{
|
|
char buf[ATOM_GET_STR_BUF_SIZE];
|
|
return JS_ThrowSyntaxError(ctx, fmt,
|
|
JS_AtomGetStr(ctx, buf, sizeof(buf), atom));
|
|
}
|
|
|
|
/* %s is replaced by 'atom'. The macro is used so that gcc can check
|
|
the format string. */
|
|
#define JS_ThrowTypeErrorAtom(ctx, fmt, atom) __JS_ThrowTypeErrorAtom(ctx, atom, fmt, "")
|
|
#define JS_ThrowSyntaxErrorAtom(ctx, fmt, atom) __JS_ThrowSyntaxErrorAtom(ctx, atom, fmt, "")
|
|
|
|
static int JS_ThrowTypeErrorReadOnly(JSContext *ctx, int flags, JSAtom atom)
|
|
{
|
|
if ((flags & JS_PROP_THROW) ||
|
|
((flags & JS_PROP_THROW_STRICT) && is_strict_mode(ctx))) {
|
|
JS_ThrowTypeErrorAtom(ctx, "'%s' is read-only", atom);
|
|
return -1;
|
|
} else {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
JSValue __attribute__((format(printf, 2, 3))) JS_ThrowReferenceError(JSContext *ctx, const char *fmt, ...)
|
|
{
|
|
JSValue val;
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
val = JS_ThrowError(ctx, JS_REFERENCE_ERROR, fmt, ap);
|
|
va_end(ap);
|
|
return val;
|
|
}
|
|
|
|
JSValue __attribute__((format(printf, 2, 3))) JS_ThrowRangeError(JSContext *ctx, const char *fmt, ...)
|
|
{
|
|
JSValue val;
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
val = JS_ThrowError(ctx, JS_RANGE_ERROR, fmt, ap);
|
|
va_end(ap);
|
|
return val;
|
|
}
|
|
|
|
JSValue __attribute__((format(printf, 2, 3))) JS_ThrowInternalError(JSContext *ctx, const char *fmt, ...)
|
|
{
|
|
JSValue val;
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
val = JS_ThrowError(ctx, JS_INTERNAL_ERROR, fmt, ap);
|
|
va_end(ap);
|
|
return val;
|
|
}
|
|
|
|
JSValue JS_ThrowOutOfMemory(JSContext *ctx)
|
|
{
|
|
JSRuntime *rt = ctx->rt;
|
|
if (!rt->in_out_of_memory) {
|
|
rt->in_out_of_memory = TRUE;
|
|
JS_ThrowInternalError(ctx, "out of memory");
|
|
rt->in_out_of_memory = FALSE;
|
|
}
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue JS_ThrowStackOverflow(JSContext *ctx)
|
|
{
|
|
return JS_ThrowRangeError(ctx, "Maximum call stack size exceeded");
|
|
}
|
|
|
|
static JSValue JS_ThrowTypeErrorNotAnObject(JSContext *ctx)
|
|
{
|
|
return JS_ThrowTypeError(ctx, "not an object");
|
|
}
|
|
|
|
static JSValue JS_ThrowTypeErrorNotASymbol(JSContext *ctx)
|
|
{
|
|
return JS_ThrowTypeError(ctx, "not a symbol");
|
|
}
|
|
|
|
static JSValue JS_ThrowReferenceErrorNotDefined(JSContext *ctx, JSAtom name)
|
|
{
|
|
char buf[ATOM_GET_STR_BUF_SIZE];
|
|
return JS_ThrowReferenceError(ctx, "%s is not defined",
|
|
JS_AtomGetStr(ctx, buf, sizeof(buf), name));
|
|
}
|
|
|
|
static JSValue JS_ThrowReferenceErrorUninitialized(JSContext *ctx, JSAtom name)
|
|
{
|
|
char buf[ATOM_GET_STR_BUF_SIZE];
|
|
return JS_ThrowReferenceError(ctx, "%s is not initialized",
|
|
name == JS_ATOM_NULL ? "lexical variable" :
|
|
JS_AtomGetStr(ctx, buf, sizeof(buf), name));
|
|
}
|
|
|
|
static JSValue JS_ThrowReferenceErrorUninitialized2(JSContext *ctx,
|
|
JSFunctionBytecode *b,
|
|
int idx, BOOL is_ref)
|
|
{
|
|
JSAtom atom = JS_ATOM_NULL;
|
|
if (is_ref) {
|
|
atom = b->closure_var[idx].var_name;
|
|
} else {
|
|
/* not present if the function is stripped and contains no eval() */
|
|
if (b->vardefs)
|
|
atom = b->vardefs[b->arg_count + idx].var_name;
|
|
}
|
|
return JS_ThrowReferenceErrorUninitialized(ctx, atom);
|
|
}
|
|
|
|
static JSValue JS_ThrowTypeErrorInvalidClass(JSContext *ctx, int class_id)
|
|
{
|
|
JSRuntime *rt = ctx->rt;
|
|
JSAtom name;
|
|
name = rt->class_array[class_id].class_name;
|
|
return JS_ThrowTypeErrorAtom(ctx, "%s object expected", name);
|
|
}
|
|
|
|
static no_inline __exception int __js_poll_interrupts(JSContext *ctx)
|
|
{
|
|
JSRuntime *rt = ctx->rt;
|
|
ctx->interrupt_counter = JS_INTERRUPT_COUNTER_INIT;
|
|
if (rt->interrupt_handler) {
|
|
if (rt->interrupt_handler(rt, rt->interrupt_opaque)) {
|
|
/* XXX: should set a specific flag to avoid catching */
|
|
JS_ThrowInternalError(ctx, "interrupted");
|
|
JS_SetUncatchableError(ctx, ctx->rt->current_exception, TRUE);
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static inline __exception int js_poll_interrupts(JSContext *ctx)
|
|
{
|
|
if (unlikely(--ctx->interrupt_counter <= 0)) {
|
|
return __js_poll_interrupts(ctx);
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* return -1 (exception) or TRUE/FALSE */
|
|
static int JS_SetPrototypeInternal(JSContext *ctx, JSValue obj,
|
|
JSValue proto_val,
|
|
BOOL throw_flag)
|
|
{
|
|
JSObject *proto, *p, *p1;
|
|
JSShape *sh;
|
|
|
|
if (throw_flag) {
|
|
if (JS_VALUE_GET_TAG(obj) == JS_TAG_NULL ||
|
|
JS_VALUE_GET_TAG(obj) == JS_TAG_UNDEFINED)
|
|
goto not_obj;
|
|
} else {
|
|
if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)
|
|
goto not_obj;
|
|
}
|
|
p = JS_VALUE_GET_OBJ(obj);
|
|
if (JS_VALUE_GET_TAG(proto_val) != JS_TAG_OBJECT) {
|
|
if (JS_VALUE_GET_TAG(proto_val) != JS_TAG_NULL) {
|
|
not_obj:
|
|
JS_ThrowTypeErrorNotAnObject(ctx);
|
|
return -1;
|
|
}
|
|
proto = NULL;
|
|
} else {
|
|
proto = JS_VALUE_GET_OBJ(proto_val);
|
|
}
|
|
|
|
if (throw_flag && JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)
|
|
return TRUE;
|
|
|
|
if (unlikely(p->class_id == JS_CLASS_PROXY))
|
|
return js_proxy_setPrototypeOf(ctx, obj, proto_val, throw_flag);
|
|
sh = p->shape;
|
|
if (sh->proto == proto)
|
|
return TRUE;
|
|
if (p == JS_VALUE_GET_OBJ(ctx->class_proto[JS_CLASS_OBJECT])) {
|
|
if (throw_flag) {
|
|
JS_ThrowTypeError(ctx, "'Immutable prototype object \'Object.prototype\' cannot have their prototype set'");
|
|
return -1;
|
|
}
|
|
return FALSE;
|
|
}
|
|
if (!p->extensible) {
|
|
if (throw_flag) {
|
|
JS_ThrowTypeError(ctx, "object is not extensible");
|
|
return -1;
|
|
} else {
|
|
return FALSE;
|
|
}
|
|
}
|
|
if (proto) {
|
|
/* check if there is a cycle */
|
|
p1 = proto;
|
|
do {
|
|
if (p1 == p) {
|
|
if (throw_flag) {
|
|
JS_ThrowTypeError(ctx, "circular prototype chain");
|
|
return -1;
|
|
} else {
|
|
return FALSE;
|
|
}
|
|
}
|
|
/* Note: for Proxy objects, proto is NULL */
|
|
p1 = p1->shape->proto;
|
|
} while (p1 != NULL);
|
|
js_dup(proto_val);
|
|
}
|
|
|
|
if (js_shape_prepare_update(ctx, p, NULL))
|
|
return -1;
|
|
sh = p->shape;
|
|
if (sh->proto)
|
|
JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, sh->proto));
|
|
sh->proto = proto;
|
|
return TRUE;
|
|
}
|
|
|
|
/* return -1 (exception) or TRUE/FALSE */
|
|
int JS_SetPrototype(JSContext *ctx, JSValue obj, JSValue proto_val)
|
|
{
|
|
return JS_SetPrototypeInternal(ctx, obj, proto_val, TRUE);
|
|
}
|
|
|
|
/* Only works for primitive types, otherwise return JS_NULL. */
|
|
static JSValue JS_GetPrototypePrimitive(JSContext *ctx, JSValue val)
|
|
{
|
|
switch(JS_VALUE_GET_NORM_TAG(val)) {
|
|
case JS_TAG_BIG_INT:
|
|
val = ctx->class_proto[JS_CLASS_BIG_INT];
|
|
break;
|
|
case JS_TAG_INT:
|
|
case JS_TAG_FLOAT64:
|
|
val = ctx->class_proto[JS_CLASS_NUMBER];
|
|
break;
|
|
case JS_TAG_BOOL:
|
|
val = ctx->class_proto[JS_CLASS_BOOLEAN];
|
|
break;
|
|
case JS_TAG_STRING:
|
|
val = ctx->class_proto[JS_CLASS_STRING];
|
|
break;
|
|
case JS_TAG_SYMBOL:
|
|
val = ctx->class_proto[JS_CLASS_SYMBOL];
|
|
break;
|
|
case JS_TAG_OBJECT:
|
|
case JS_TAG_NULL:
|
|
case JS_TAG_UNDEFINED:
|
|
default:
|
|
val = JS_NULL;
|
|
break;
|
|
}
|
|
return val;
|
|
}
|
|
|
|
/* Return an Object, JS_NULL or JS_EXCEPTION in case of Proxy object. */
|
|
JSValue JS_GetPrototype(JSContext *ctx, JSValue obj)
|
|
{
|
|
JSValue val;
|
|
if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) {
|
|
JSObject *p;
|
|
p = JS_VALUE_GET_OBJ(obj);
|
|
if (unlikely(p->class_id == JS_CLASS_PROXY)) {
|
|
val = js_proxy_getPrototypeOf(ctx, obj);
|
|
} else {
|
|
p = p->shape->proto;
|
|
if (!p)
|
|
val = JS_NULL;
|
|
else
|
|
val = js_dup(JS_MKPTR(JS_TAG_OBJECT, p));
|
|
}
|
|
} else {
|
|
val = js_dup(JS_GetPrototypePrimitive(ctx, obj));
|
|
}
|
|
return val;
|
|
}
|
|
|
|
static JSValue JS_GetPrototypeFree(JSContext *ctx, JSValue obj)
|
|
{
|
|
JSValue obj1;
|
|
obj1 = JS_GetPrototype(ctx, obj);
|
|
JS_FreeValue(ctx, obj);
|
|
return obj1;
|
|
}
|
|
|
|
/* return TRUE, FALSE or (-1) in case of exception */
|
|
static int JS_OrdinaryIsInstanceOf(JSContext *ctx, JSValue val,
|
|
JSValue obj)
|
|
{
|
|
JSValue obj_proto;
|
|
JSObject *proto;
|
|
const JSObject *p, *proto1;
|
|
BOOL ret;
|
|
|
|
if (!JS_IsFunction(ctx, obj))
|
|
return FALSE;
|
|
p = JS_VALUE_GET_OBJ(obj);
|
|
if (p->class_id == JS_CLASS_BOUND_FUNCTION) {
|
|
JSBoundFunction *s = p->u.bound_function;
|
|
return JS_IsInstanceOf(ctx, val, s->func_obj);
|
|
}
|
|
|
|
/* Only explicitly boxed values are instances of constructors */
|
|
if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT)
|
|
return FALSE;
|
|
obj_proto = JS_GetProperty(ctx, obj, JS_ATOM_prototype);
|
|
if (JS_VALUE_GET_TAG(obj_proto) != JS_TAG_OBJECT) {
|
|
if (!JS_IsException(obj_proto))
|
|
JS_ThrowTypeError(ctx, "operand 'prototype' property is not an object");
|
|
ret = -1;
|
|
goto done;
|
|
}
|
|
proto = JS_VALUE_GET_OBJ(obj_proto);
|
|
p = JS_VALUE_GET_OBJ(val);
|
|
for(;;) {
|
|
proto1 = p->shape->proto;
|
|
if (!proto1) {
|
|
/* slow case if proxy in the prototype chain */
|
|
if (unlikely(p->class_id == JS_CLASS_PROXY)) {
|
|
JSValue obj1;
|
|
obj1 = js_dup(JS_MKPTR(JS_TAG_OBJECT, (JSObject *)p));
|
|
for(;;) {
|
|
obj1 = JS_GetPrototypeFree(ctx, obj1);
|
|
if (JS_IsException(obj1)) {
|
|
ret = -1;
|
|
break;
|
|
}
|
|
if (JS_IsNull(obj1)) {
|
|
ret = FALSE;
|
|
break;
|
|
}
|
|
if (proto == JS_VALUE_GET_OBJ(obj1)) {
|
|
JS_FreeValue(ctx, obj1);
|
|
ret = TRUE;
|
|
break;
|
|
}
|
|
/* must check for timeout to avoid infinite loop */
|
|
if (js_poll_interrupts(ctx)) {
|
|
JS_FreeValue(ctx, obj1);
|
|
ret = -1;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
ret = FALSE;
|
|
}
|
|
break;
|
|
}
|
|
p = proto1;
|
|
if (proto == p) {
|
|
ret = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
done:
|
|
JS_FreeValue(ctx, obj_proto);
|
|
return ret;
|
|
}
|
|
|
|
/* return TRUE, FALSE or (-1) in case of exception */
|
|
int JS_IsInstanceOf(JSContext *ctx, JSValue val, JSValue obj)
|
|
{
|
|
JSValue method;
|
|
|
|
if (!JS_IsObject(obj))
|
|
goto fail;
|
|
method = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_hasInstance);
|
|
if (JS_IsException(method))
|
|
return -1;
|
|
if (!JS_IsNull(method) && !JS_IsUndefined(method)) {
|
|
JSValue ret;
|
|
ret = JS_CallFree(ctx, method, obj, 1, &val);
|
|
return JS_ToBoolFree(ctx, ret);
|
|
}
|
|
|
|
/* legacy case */
|
|
if (!JS_IsFunction(ctx, obj)) {
|
|
fail:
|
|
JS_ThrowTypeError(ctx, "invalid 'instanceof' right operand");
|
|
return -1;
|
|
}
|
|
return JS_OrdinaryIsInstanceOf(ctx, val, obj);
|
|
}
|
|
|
|
/* return the value associated to the autoinit property or an exception */
|
|
typedef JSValue JSAutoInitFunc(JSContext *ctx, JSObject *p, JSAtom atom, void *opaque);
|
|
|
|
static JSAutoInitFunc *js_autoinit_func_table[] = {
|
|
js_instantiate_prototype, /* JS_AUTOINIT_ID_PROTOTYPE */
|
|
js_module_ns_autoinit, /* JS_AUTOINIT_ID_MODULE_NS */
|
|
JS_InstantiateFunctionListItem2, /* JS_AUTOINIT_ID_PROP */
|
|
};
|
|
|
|
/* warning: 'prs' is reallocated after it */
|
|
static int JS_AutoInitProperty(JSContext *ctx, JSObject *p, JSAtom prop,
|
|
JSProperty *pr, JSShapeProperty *prs)
|
|
{
|
|
JSValue val;
|
|
JSContext *realm;
|
|
JSAutoInitFunc *func;
|
|
|
|
if (js_shape_prepare_update(ctx, p, &prs))
|
|
return -1;
|
|
|
|
realm = js_autoinit_get_realm(pr);
|
|
func = js_autoinit_func_table[js_autoinit_get_id(pr)];
|
|
/* 'func' shall not modify the object properties 'pr' */
|
|
val = func(realm, p, prop, pr->u.init.opaque);
|
|
js_autoinit_free(ctx->rt, pr);
|
|
prs->flags &= ~JS_PROP_TMASK;
|
|
pr->u.value = JS_UNDEFINED;
|
|
if (JS_IsException(val))
|
|
return -1;
|
|
pr->u.value = val;
|
|
return 0;
|
|
}
|
|
|
|
static JSValue JS_GetPropertyInternal2(JSContext *ctx, JSValue obj,
|
|
JSAtom prop, JSValue this_obj,
|
|
JSInlineCache* ic, BOOL throw_ref_error)
|
|
{
|
|
JSObject *p;
|
|
JSProperty *pr;
|
|
JSShapeProperty *prs;
|
|
uint32_t tag, offset, proto_depth;
|
|
|
|
offset = proto_depth = 0;
|
|
tag = JS_VALUE_GET_TAG(obj);
|
|
if (unlikely(tag != JS_TAG_OBJECT)) {
|
|
switch(tag) {
|
|
case JS_TAG_NULL:
|
|
return JS_ThrowTypeErrorAtom(ctx, "cannot read property '%s' of null", prop);
|
|
case JS_TAG_UNDEFINED:
|
|
return JS_ThrowTypeErrorAtom(ctx, "cannot read property '%s' of undefined", prop);
|
|
case JS_TAG_EXCEPTION:
|
|
return JS_EXCEPTION;
|
|
case JS_TAG_STRING:
|
|
{
|
|
JSString *p1 = JS_VALUE_GET_STRING(obj);
|
|
if (__JS_AtomIsTaggedInt(prop)) {
|
|
uint32_t idx, ch;
|
|
idx = __JS_AtomToUInt32(prop);
|
|
if (idx < p1->len) {
|
|
ch = string_get(p1, idx);
|
|
return js_new_string_char(ctx, ch);
|
|
}
|
|
} else if (prop == JS_ATOM_length) {
|
|
return js_int32(p1->len);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
/* cannot raise an exception */
|
|
p = JS_VALUE_GET_OBJ(JS_GetPrototypePrimitive(ctx, obj));
|
|
if (!p)
|
|
return JS_UNDEFINED;
|
|
} else {
|
|
p = JS_VALUE_GET_OBJ(obj);
|
|
}
|
|
|
|
for(;;) {
|
|
prs = find_own_property_ic(&pr, p, prop, &offset);
|
|
if (prs) {
|
|
/* found */
|
|
if (unlikely(prs->flags & JS_PROP_TMASK)) {
|
|
if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) {
|
|
if (unlikely(!pr->u.getset.getter)) {
|
|
return JS_UNDEFINED;
|
|
} else {
|
|
JSValue func = JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.getter);
|
|
/* Note: the field could be removed in the getter */
|
|
func = js_dup(func);
|
|
return JS_CallFree(ctx, func, this_obj, 0, NULL);
|
|
}
|
|
} else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) {
|
|
JSValue val = *pr->u.var_ref->pvalue;
|
|
if (unlikely(JS_IsUninitialized(val)))
|
|
return JS_ThrowReferenceErrorUninitialized(ctx, prs->atom);
|
|
return js_dup(val);
|
|
} else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) {
|
|
/* Instantiate property and retry */
|
|
if (JS_AutoInitProperty(ctx, p, prop, pr, prs))
|
|
return JS_EXCEPTION;
|
|
continue;
|
|
}
|
|
} else {
|
|
if (ic && proto_depth == 0 && p->shape->is_hashed) {
|
|
ic->updated = TRUE;
|
|
ic->updated_offset = add_ic_slot(ctx, ic, prop, p, offset);
|
|
}
|
|
return js_dup(pr->u.value);
|
|
}
|
|
}
|
|
if (unlikely(p->is_exotic)) {
|
|
/* exotic behaviors */
|
|
if (p->fast_array) {
|
|
if (__JS_AtomIsTaggedInt(prop)) {
|
|
uint32_t idx = __JS_AtomToUInt32(prop);
|
|
if (idx < p->u.array.count) {
|
|
/* we avoid duplicating the code */
|
|
return JS_GetPropertyUint32(ctx, JS_MKPTR(JS_TAG_OBJECT, p), idx);
|
|
} else if (p->class_id >= JS_CLASS_UINT8C_ARRAY &&
|
|
p->class_id <= JS_CLASS_FLOAT64_ARRAY) {
|
|
return JS_UNDEFINED;
|
|
}
|
|
} else if (p->class_id >= JS_CLASS_UINT8C_ARRAY &&
|
|
p->class_id <= JS_CLASS_FLOAT64_ARRAY) {
|
|
int ret;
|
|
ret = JS_AtomIsNumericIndex(ctx, prop);
|
|
if (ret != 0) {
|
|
if (ret < 0)
|
|
return JS_EXCEPTION;
|
|
return JS_UNDEFINED;
|
|
}
|
|
}
|
|
} else {
|
|
const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic;
|
|
if (em) {
|
|
if (em->get_property) {
|
|
JSValue obj1, retval;
|
|
/* XXX: should pass throw_ref_error */
|
|
/* Note: if 'p' is a prototype, it can be
|
|
freed in the called function */
|
|
obj1 = js_dup(JS_MKPTR(JS_TAG_OBJECT, p));
|
|
retval = em->get_property(ctx, obj1, prop, this_obj);
|
|
JS_FreeValue(ctx, obj1);
|
|
return retval;
|
|
}
|
|
if (em->get_own_property) {
|
|
JSPropertyDescriptor desc;
|
|
int ret;
|
|
JSValue obj1;
|
|
|
|
/* Note: if 'p' is a prototype, it can be
|
|
freed in the called function */
|
|
obj1 = js_dup(JS_MKPTR(JS_TAG_OBJECT, p));
|
|
ret = em->get_own_property(ctx, &desc, obj1, prop);
|
|
JS_FreeValue(ctx, obj1);
|
|
if (ret < 0)
|
|
return JS_EXCEPTION;
|
|
if (ret) {
|
|
if (desc.flags & JS_PROP_GETSET) {
|
|
JS_FreeValue(ctx, desc.setter);
|
|
return JS_CallFree(ctx, desc.getter, this_obj, 0, NULL);
|
|
} else {
|
|
return desc.value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
proto_depth++;
|
|
p = p->shape->proto;
|
|
if (!p)
|
|
break;
|
|
}
|
|
if (unlikely(throw_ref_error)) {
|
|
return JS_ThrowReferenceErrorNotDefined(ctx, prop);
|
|
} else {
|
|
return JS_UNDEFINED;
|
|
}
|
|
}
|
|
|
|
static JSValue JS_GetPropertyInternal(JSContext *ctx, JSValue obj,
|
|
JSAtom prop, JSValue this_obj,
|
|
BOOL throw_ref_error)
|
|
{
|
|
return JS_GetPropertyInternal2(ctx, obj, prop, this_obj, NULL, throw_ref_error);
|
|
}
|
|
|
|
JSValue JS_GetProperty(JSContext *ctx, JSValue this_obj, JSAtom prop)
|
|
{
|
|
return JS_GetPropertyInternal2(ctx, this_obj, prop, this_obj, NULL, FALSE);
|
|
}
|
|
|
|
static JSValue JS_GetPropertyInternalWithIC(JSContext *ctx, JSValue obj,
|
|
JSAtom prop, JSValue this_obj,
|
|
JSInlineCache *ic, int32_t offset,
|
|
BOOL throw_ref_error)
|
|
{
|
|
uint32_t tag;
|
|
JSObject *p;
|
|
tag = JS_VALUE_GET_TAG(obj);
|
|
if (unlikely(tag != JS_TAG_OBJECT))
|
|
goto slow_path;
|
|
p = JS_VALUE_GET_OBJ(obj);
|
|
offset = get_ic_prop_offset(ic, offset, p->shape);
|
|
if (likely(offset >= 0))
|
|
return js_dup(p->prop[offset].u.value);
|
|
slow_path:
|
|
return JS_GetPropertyInternal2(ctx, obj, prop, this_obj, ic, throw_ref_error);
|
|
}
|
|
|
|
static JSValue JS_ThrowTypeErrorPrivateNotFound(JSContext *ctx, JSAtom atom)
|
|
{
|
|
return JS_ThrowTypeErrorAtom(ctx, "private class field '%s' does not exist",
|
|
atom);
|
|
}
|
|
|
|
/* Private fields can be added even on non extensible objects or
|
|
Proxies */
|
|
static int JS_DefinePrivateField(JSContext *ctx, JSValue obj,
|
|
JSValue name, JSValue val)
|
|
{
|
|
JSObject *p;
|
|
JSShapeProperty *prs;
|
|
JSProperty *pr;
|
|
JSAtom prop;
|
|
|
|
if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) {
|
|
JS_ThrowTypeErrorNotAnObject(ctx);
|
|
goto fail;
|
|
}
|
|
/* safety check */
|
|
if (unlikely(JS_VALUE_GET_TAG(name) != JS_TAG_SYMBOL)) {
|
|
JS_ThrowTypeErrorNotASymbol(ctx);
|
|
goto fail;
|
|
}
|
|
prop = js_symbol_to_atom(ctx, name);
|
|
p = JS_VALUE_GET_OBJ(obj);
|
|
prs = find_own_property(&pr, p, prop);
|
|
if (prs) {
|
|
JS_ThrowTypeErrorAtom(ctx, "private class field '%s' already exists",
|
|
prop);
|
|
goto fail;
|
|
}
|
|
pr = add_property(ctx, p, prop, JS_PROP_C_W_E);
|
|
if (unlikely(!pr)) {
|
|
fail:
|
|
JS_FreeValue(ctx, val);
|
|
return -1;
|
|
}
|
|
pr->u.value = val;
|
|
return 0;
|
|
}
|
|
|
|
static JSValue JS_GetPrivateField(JSContext *ctx, JSValue obj,
|
|
JSValue name)
|
|
{
|
|
JSObject *p;
|
|
JSShapeProperty *prs;
|
|
JSProperty *pr;
|
|
JSAtom prop;
|
|
|
|
if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT))
|
|
return JS_ThrowTypeErrorNotAnObject(ctx);
|
|
/* safety check */
|
|
if (unlikely(JS_VALUE_GET_TAG(name) != JS_TAG_SYMBOL))
|
|
return JS_ThrowTypeErrorNotASymbol(ctx);
|
|
prop = js_symbol_to_atom(ctx, name);
|
|
p = JS_VALUE_GET_OBJ(obj);
|
|
prs = find_own_property(&pr, p, prop);
|
|
if (!prs) {
|
|
JS_ThrowTypeErrorPrivateNotFound(ctx, prop);
|
|
return JS_EXCEPTION;
|
|
}
|
|
return js_dup(pr->u.value);
|
|
}
|
|
|
|
static int JS_SetPrivateField(JSContext *ctx, JSValue obj,
|
|
JSValue name, JSValue val)
|
|
{
|
|
JSObject *p;
|
|
JSShapeProperty *prs;
|
|
JSProperty *pr;
|
|
JSAtom prop;
|
|
|
|
if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) {
|
|
JS_ThrowTypeErrorNotAnObject(ctx);
|
|
goto fail;
|
|
}
|
|
/* safety check */
|
|
if (unlikely(JS_VALUE_GET_TAG(name) != JS_TAG_SYMBOL)) {
|
|
JS_ThrowTypeErrorNotASymbol(ctx);
|
|
goto fail;
|
|
}
|
|
prop = js_symbol_to_atom(ctx, name);
|
|
p = JS_VALUE_GET_OBJ(obj);
|
|
prs = find_own_property(&pr, p, prop);
|
|
if (!prs) {
|
|
JS_ThrowTypeErrorPrivateNotFound(ctx, prop);
|
|
fail:
|
|
JS_FreeValue(ctx, val);
|
|
return -1;
|
|
}
|
|
set_value(ctx, &pr->u.value, val);
|
|
return 0;
|
|
}
|
|
|
|
static int JS_AddBrand(JSContext *ctx, JSValue obj, JSValue home_obj)
|
|
{
|
|
JSObject *p, *p1;
|
|
JSShapeProperty *prs;
|
|
JSProperty *pr;
|
|
JSValue brand;
|
|
JSAtom brand_atom;
|
|
|
|
if (unlikely(JS_VALUE_GET_TAG(home_obj) != JS_TAG_OBJECT)) {
|
|
JS_ThrowTypeErrorNotAnObject(ctx);
|
|
return -1;
|
|
}
|
|
p = JS_VALUE_GET_OBJ(home_obj);
|
|
prs = find_own_property(&pr, p, JS_ATOM_Private_brand);
|
|
if (!prs) {
|
|
brand = JS_NewSymbolFromAtom(ctx, JS_ATOM_brand, JS_ATOM_TYPE_PRIVATE);
|
|
if (JS_IsException(brand))
|
|
return -1;
|
|
/* if the brand is not present, add it */
|
|
pr = add_property(ctx, p, JS_ATOM_Private_brand, JS_PROP_C_W_E);
|
|
if (!pr) {
|
|
JS_FreeValue(ctx, brand);
|
|
return -1;
|
|
}
|
|
pr->u.value = js_dup(brand);
|
|
} else {
|
|
brand = js_dup(pr->u.value);
|
|
}
|
|
brand_atom = js_symbol_to_atom(ctx, brand);
|
|
|
|
if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) {
|
|
JS_ThrowTypeErrorNotAnObject(ctx);
|
|
JS_FreeAtom(ctx, brand_atom);
|
|
return -1;
|
|
}
|
|
p1 = JS_VALUE_GET_OBJ(obj);
|
|
prs = find_own_property(&pr, p1, brand_atom);
|
|
if (unlikely(prs)) {
|
|
JS_FreeAtom(ctx, brand_atom);
|
|
JS_ThrowTypeError(ctx, "private method is already present");
|
|
return -1;
|
|
}
|
|
pr = add_property(ctx, p1, brand_atom, JS_PROP_C_W_E);
|
|
JS_FreeAtom(ctx, brand_atom);
|
|
if (!pr)
|
|
return -1;
|
|
pr->u.value = JS_UNDEFINED;
|
|
return 0;
|
|
}
|
|
|
|
static int JS_CheckBrand(JSContext *ctx, JSValue obj, JSValue func)
|
|
{
|
|
JSObject *p, *p1, *home_obj;
|
|
JSShapeProperty *prs;
|
|
JSProperty *pr;
|
|
JSValue brand;
|
|
|
|
/* get the home object of 'func' */
|
|
if (unlikely(JS_VALUE_GET_TAG(func) != JS_TAG_OBJECT)) {
|
|
not_obj:
|
|
JS_ThrowTypeErrorNotAnObject(ctx);
|
|
return -1;
|
|
}
|
|
p1 = JS_VALUE_GET_OBJ(func);
|
|
if (!js_class_has_bytecode(p1->class_id))
|
|
goto not_obj;
|
|
home_obj = p1->u.func.home_object;
|
|
if (!home_obj)
|
|
goto not_obj;
|
|
prs = find_own_property(&pr, home_obj, JS_ATOM_Private_brand);
|
|
if (!prs) {
|
|
JS_ThrowTypeError(ctx, "expecting <brand> private field");
|
|
return -1;
|
|
}
|
|
brand = pr->u.value;
|
|
/* safety check */
|
|
if (unlikely(JS_VALUE_GET_TAG(brand) != JS_TAG_SYMBOL))
|
|
goto not_obj;
|
|
|
|
/* get the brand array of 'obj' */
|
|
if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT))
|
|
goto not_obj;
|
|
p = JS_VALUE_GET_OBJ(obj);
|
|
prs = find_own_property(&pr, p, js_symbol_to_atom(ctx, brand));
|
|
if (!prs) {
|
|
JS_ThrowTypeError(ctx, "invalid brand on object");
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static uint32_t js_string_obj_get_length(JSContext *ctx,
|
|
JSValue obj)
|
|
{
|
|
JSObject *p;
|
|
JSString *p1;
|
|
uint32_t len = 0;
|
|
|
|
/* This is a class exotic method: obj class_id is JS_CLASS_STRING */
|
|
p = JS_VALUE_GET_OBJ(obj);
|
|
if (JS_VALUE_GET_TAG(p->u.object_data) == JS_TAG_STRING) {
|
|
p1 = JS_VALUE_GET_STRING(p->u.object_data);
|
|
len = p1->len;
|
|
}
|
|
return len;
|
|
}
|
|
|
|
static int num_keys_cmp(const void *p1, const void *p2, void *opaque)
|
|
{
|
|
JSContext *ctx = opaque;
|
|
JSAtom atom1 = ((const JSPropertyEnum *)p1)->atom;
|
|
JSAtom atom2 = ((const JSPropertyEnum *)p2)->atom;
|
|
uint32_t v1, v2;
|
|
BOOL atom1_is_integer, atom2_is_integer;
|
|
|
|
atom1_is_integer = JS_AtomIsArrayIndex(ctx, &v1, atom1);
|
|
atom2_is_integer = JS_AtomIsArrayIndex(ctx, &v2, atom2);
|
|
assert(atom1_is_integer && atom2_is_integer);
|
|
if (v1 < v2)
|
|
return -1;
|
|
else if (v1 == v2)
|
|
return 0;
|
|
else
|
|
return 1;
|
|
}
|
|
|
|
static void js_free_prop_enum(JSContext *ctx, JSPropertyEnum *tab, uint32_t len)
|
|
{
|
|
uint32_t i;
|
|
if (tab) {
|
|
for(i = 0; i < len; i++)
|
|
JS_FreeAtom(ctx, tab[i].atom);
|
|
js_free(ctx, tab);
|
|
}
|
|
}
|
|
|
|
/* return < 0 in case if exception, 0 if OK. ptab and its atoms must
|
|
be freed by the user. */
|
|
static int __exception JS_GetOwnPropertyNamesInternal(JSContext *ctx,
|
|
JSPropertyEnum **ptab,
|
|
uint32_t *plen,
|
|
JSObject *p, int flags)
|
|
{
|
|
int i, j;
|
|
JSShape *sh;
|
|
JSShapeProperty *prs;
|
|
JSPropertyEnum *tab_atom, *tab_exotic;
|
|
JSAtom atom;
|
|
uint32_t num_keys_count, str_keys_count, sym_keys_count, atom_count;
|
|
uint32_t num_index, str_index, sym_index, exotic_count, exotic_keys_count;
|
|
BOOL is_enumerable, num_sorted;
|
|
uint32_t num_key;
|
|
JSAtomKindEnum kind;
|
|
|
|
/* clear pointer for consistency in case of failure */
|
|
*ptab = NULL;
|
|
*plen = 0;
|
|
|
|
/* compute the number of returned properties */
|
|
num_keys_count = 0;
|
|
str_keys_count = 0;
|
|
sym_keys_count = 0;
|
|
exotic_keys_count = 0;
|
|
exotic_count = 0;
|
|
tab_exotic = NULL;
|
|
sh = p->shape;
|
|
for(i = 0, prs = get_shape_prop(sh); i < sh->prop_count; i++, prs++) {
|
|
atom = prs->atom;
|
|
if (atom != JS_ATOM_NULL) {
|
|
is_enumerable = ((prs->flags & JS_PROP_ENUMERABLE) != 0);
|
|
kind = JS_AtomGetKind(ctx, atom);
|
|
if ((!(flags & JS_GPN_ENUM_ONLY) || is_enumerable) &&
|
|
((flags >> kind) & 1) != 0) {
|
|
/* need to raise an exception in case of the module
|
|
name space (implicit GetOwnProperty) */
|
|
if (unlikely((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) &&
|
|
(flags & (JS_GPN_SET_ENUM | JS_GPN_ENUM_ONLY))) {
|
|
JSVarRef *var_ref = p->prop[i].u.var_ref;
|
|
if (unlikely(JS_IsUninitialized(*var_ref->pvalue))) {
|
|
JS_ThrowReferenceErrorUninitialized(ctx, prs->atom);
|
|
return -1;
|
|
}
|
|
}
|
|
if (JS_AtomIsArrayIndex(ctx, &num_key, atom)) {
|
|
num_keys_count++;
|
|
} else if (kind == JS_ATOM_KIND_STRING) {
|
|
str_keys_count++;
|
|
} else {
|
|
sym_keys_count++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (p->is_exotic) {
|
|
if (p->fast_array) {
|
|
if (flags & JS_GPN_STRING_MASK) {
|
|
num_keys_count += p->u.array.count;
|
|
}
|
|
} else if (p->class_id == JS_CLASS_STRING) {
|
|
if (flags & JS_GPN_STRING_MASK) {
|
|
num_keys_count += js_string_obj_get_length(ctx, JS_MKPTR(JS_TAG_OBJECT, p));
|
|
}
|
|
} else {
|
|
const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic;
|
|
if (em && em->get_own_property_names) {
|
|
if (em->get_own_property_names(ctx, &tab_exotic, &exotic_count,
|
|
JS_MKPTR(JS_TAG_OBJECT, p)))
|
|
return -1;
|
|
for(i = 0; i < exotic_count; i++) {
|
|
atom = tab_exotic[i].atom;
|
|
kind = JS_AtomGetKind(ctx, atom);
|
|
if (((flags >> kind) & 1) != 0) {
|
|
is_enumerable = FALSE;
|
|
if (flags & (JS_GPN_SET_ENUM | JS_GPN_ENUM_ONLY)) {
|
|
JSPropertyDescriptor desc;
|
|
int res;
|
|
/* set the "is_enumerable" field if necessary */
|
|
res = JS_GetOwnPropertyInternal(ctx, &desc, p, atom);
|
|
if (res < 0) {
|
|
js_free_prop_enum(ctx, tab_exotic, exotic_count);
|
|
return -1;
|
|
}
|
|
if (res) {
|
|
is_enumerable =
|
|
((desc.flags & JS_PROP_ENUMERABLE) != 0);
|
|
js_free_desc(ctx, &desc);
|
|
}
|
|
tab_exotic[i].is_enumerable = is_enumerable;
|
|
}
|
|
if (!(flags & JS_GPN_ENUM_ONLY) || is_enumerable) {
|
|
exotic_keys_count++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* fill them */
|
|
|
|
atom_count = num_keys_count + str_keys_count + sym_keys_count + exotic_keys_count;
|
|
/* avoid allocating 0 bytes */
|
|
tab_atom = js_malloc(ctx, sizeof(tab_atom[0]) * max_int(atom_count, 1));
|
|
if (!tab_atom) {
|
|
js_free_prop_enum(ctx, tab_exotic, exotic_count);
|
|
return -1;
|
|
}
|
|
|
|
num_index = 0;
|
|
str_index = num_keys_count;
|
|
sym_index = str_index + str_keys_count;
|
|
|
|
num_sorted = TRUE;
|
|
sh = p->shape;
|
|
for(i = 0, prs = get_shape_prop(sh); i < sh->prop_count; i++, prs++) {
|
|
atom = prs->atom;
|
|
if (atom != JS_ATOM_NULL) {
|
|
is_enumerable = ((prs->flags & JS_PROP_ENUMERABLE) != 0);
|
|
kind = JS_AtomGetKind(ctx, atom);
|
|
if ((!(flags & JS_GPN_ENUM_ONLY) || is_enumerable) &&
|
|
((flags >> kind) & 1) != 0) {
|
|
if (JS_AtomIsArrayIndex(ctx, &num_key, atom)) {
|
|
j = num_index++;
|
|
num_sorted = FALSE;
|
|
} else if (kind == JS_ATOM_KIND_STRING) {
|
|
j = str_index++;
|
|
} else {
|
|
j = sym_index++;
|
|
}
|
|
tab_atom[j].atom = JS_DupAtom(ctx, atom);
|
|
tab_atom[j].is_enumerable = is_enumerable;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (p->is_exotic) {
|
|
int len;
|
|
if (p->fast_array) {
|
|
if (flags & JS_GPN_STRING_MASK) {
|
|
len = p->u.array.count;
|
|
goto add_array_keys;
|
|
}
|
|
} else if (p->class_id == JS_CLASS_STRING) {
|
|
if (flags & JS_GPN_STRING_MASK) {
|
|
len = js_string_obj_get_length(ctx, JS_MKPTR(JS_TAG_OBJECT, p));
|
|
add_array_keys:
|
|
for(i = 0; i < len; i++) {
|
|
tab_atom[num_index].atom = __JS_AtomFromUInt32(i);
|
|
if (tab_atom[num_index].atom == JS_ATOM_NULL) {
|
|
js_free_prop_enum(ctx, tab_atom, num_index);
|
|
return -1;
|
|
}
|
|
tab_atom[num_index].is_enumerable = TRUE;
|
|
num_index++;
|
|
}
|
|
}
|
|
} else {
|
|
/* Note: exotic keys are not reordered and comes after the object own properties. */
|
|
for(i = 0; i < exotic_count; i++) {
|
|
atom = tab_exotic[i].atom;
|
|
is_enumerable = tab_exotic[i].is_enumerable;
|
|
kind = JS_AtomGetKind(ctx, atom);
|
|
if ((!(flags & JS_GPN_ENUM_ONLY) || is_enumerable) &&
|
|
((flags >> kind) & 1) != 0) {
|
|
tab_atom[sym_index].atom = atom;
|
|
tab_atom[sym_index].is_enumerable = is_enumerable;
|
|
sym_index++;
|
|
} else {
|
|
JS_FreeAtom(ctx, atom);
|
|
}
|
|
}
|
|
js_free(ctx, tab_exotic);
|
|
}
|
|
}
|
|
|
|
assert(num_index == num_keys_count);
|
|
assert(str_index == num_keys_count + str_keys_count);
|
|
assert(sym_index == atom_count);
|
|
|
|
if (num_keys_count != 0 && !num_sorted) {
|
|
rqsort(tab_atom, num_keys_count, sizeof(tab_atom[0]), num_keys_cmp,
|
|
ctx);
|
|
}
|
|
*ptab = tab_atom;
|
|
*plen = atom_count;
|
|
return 0;
|
|
}
|
|
|
|
int JS_GetOwnPropertyNames(JSContext *ctx, JSPropertyEnum **ptab,
|
|
uint32_t *plen, JSValue obj, int flags)
|
|
{
|
|
if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) {
|
|
JS_ThrowTypeErrorNotAnObject(ctx);
|
|
return -1;
|
|
}
|
|
return JS_GetOwnPropertyNamesInternal(ctx, ptab, plen,
|
|
JS_VALUE_GET_OBJ(obj), flags);
|
|
}
|
|
|
|
/* Return -1 if exception,
|
|
FALSE if the property does not exist, TRUE if it exists. If TRUE is
|
|
returned, the property descriptor 'desc' is filled present. */
|
|
static int JS_GetOwnPropertyInternal(JSContext *ctx, JSPropertyDescriptor *desc,
|
|
JSObject *p, JSAtom prop)
|
|
{
|
|
JSShapeProperty *prs;
|
|
JSProperty *pr;
|
|
|
|
retry:
|
|
prs = find_own_property(&pr, p, prop);
|
|
if (prs) {
|
|
if (desc) {
|
|
desc->flags = prs->flags & JS_PROP_C_W_E;
|
|
desc->getter = JS_UNDEFINED;
|
|
desc->setter = JS_UNDEFINED;
|
|
desc->value = JS_UNDEFINED;
|
|
if (unlikely(prs->flags & JS_PROP_TMASK)) {
|
|
if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) {
|
|
desc->flags |= JS_PROP_GETSET;
|
|
if (pr->u.getset.getter)
|
|
desc->getter = js_dup(JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.getter));
|
|
if (pr->u.getset.setter)
|
|
desc->setter = js_dup(JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.setter));
|
|
} else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) {
|
|
JSValue val = *pr->u.var_ref->pvalue;
|
|
if (unlikely(JS_IsUninitialized(val))) {
|
|
JS_ThrowReferenceErrorUninitialized(ctx, prs->atom);
|
|
return -1;
|
|
}
|
|
desc->value = js_dup(val);
|
|
} else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) {
|
|
/* Instantiate property and retry */
|
|
if (JS_AutoInitProperty(ctx, p, prop, pr, prs))
|
|
return -1;
|
|
goto retry;
|
|
}
|
|
} else {
|
|
desc->value = js_dup(pr->u.value);
|
|
}
|
|
} else {
|
|
/* for consistency, send the exception even if desc is NULL */
|
|
if (unlikely((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF)) {
|
|
if (unlikely(JS_IsUninitialized(*pr->u.var_ref->pvalue))) {
|
|
JS_ThrowReferenceErrorUninitialized(ctx, prs->atom);
|
|
return -1;
|
|
}
|
|
} else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) {
|
|
/* nothing to do: delay instantiation until actual value and/or attributes are read */
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
if (p->is_exotic) {
|
|
if (p->fast_array) {
|
|
/* specific case for fast arrays */
|
|
if (__JS_AtomIsTaggedInt(prop)) {
|
|
uint32_t idx;
|
|
idx = __JS_AtomToUInt32(prop);
|
|
if (idx < p->u.array.count) {
|
|
if (desc) {
|
|
desc->flags = JS_PROP_WRITABLE | JS_PROP_ENUMERABLE |
|
|
JS_PROP_CONFIGURABLE;
|
|
desc->getter = JS_UNDEFINED;
|
|
desc->setter = JS_UNDEFINED;
|
|
desc->value = JS_GetPropertyUint32(ctx, JS_MKPTR(JS_TAG_OBJECT, p), idx);
|
|
}
|
|
return TRUE;
|
|
}
|
|
}
|
|
} else {
|
|
const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic;
|
|
if (em && em->get_own_property) {
|
|
return em->get_own_property(ctx, desc,
|
|
JS_MKPTR(JS_TAG_OBJECT, p), prop);
|
|
}
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
int JS_GetOwnProperty(JSContext *ctx, JSPropertyDescriptor *desc,
|
|
JSValue obj, JSAtom prop)
|
|
{
|
|
if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) {
|
|
JS_ThrowTypeErrorNotAnObject(ctx);
|
|
return -1;
|
|
}
|
|
return JS_GetOwnPropertyInternal(ctx, desc, JS_VALUE_GET_OBJ(obj), prop);
|
|
}
|
|
|
|
void JS_FreePropertyEnum(JSContext *ctx, JSPropertyEnum *tab,
|
|
uint32_t len)
|
|
{
|
|
js_free_prop_enum(ctx, tab, len);
|
|
}
|
|
|
|
/* return -1 if exception (Proxy object only) or TRUE/FALSE */
|
|
int JS_IsExtensible(JSContext *ctx, JSValue obj)
|
|
{
|
|
JSObject *p;
|
|
|
|
if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT))
|
|
return FALSE;
|
|
p = JS_VALUE_GET_OBJ(obj);
|
|
if (unlikely(p->class_id == JS_CLASS_PROXY))
|
|
return js_proxy_isExtensible(ctx, obj);
|
|
else
|
|
return p->extensible;
|
|
}
|
|
|
|
/* return -1 if exception (Proxy object only) or TRUE/FALSE */
|
|
int JS_PreventExtensions(JSContext *ctx, JSValue obj)
|
|
{
|
|
JSObject *p;
|
|
|
|
if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT))
|
|
return FALSE;
|
|
p = JS_VALUE_GET_OBJ(obj);
|
|
if (unlikely(p->class_id == JS_CLASS_PROXY))
|
|
return js_proxy_preventExtensions(ctx, obj);
|
|
p->extensible = FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
/* return -1 if exception otherwise TRUE or FALSE */
|
|
int JS_HasProperty(JSContext *ctx, JSValue obj, JSAtom prop)
|
|
{
|
|
JSObject *p;
|
|
int ret;
|
|
JSValue obj1;
|
|
|
|
if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT))
|
|
return FALSE;
|
|
p = JS_VALUE_GET_OBJ(obj);
|
|
for(;;) {
|
|
if (p->is_exotic) {
|
|
const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic;
|
|
if (em && em->has_property) {
|
|
/* has_property can free the prototype */
|
|
obj1 = js_dup(JS_MKPTR(JS_TAG_OBJECT, p));
|
|
ret = em->has_property(ctx, obj1, prop);
|
|
JS_FreeValue(ctx, obj1);
|
|
return ret;
|
|
}
|
|
}
|
|
/* JS_GetOwnPropertyInternal can free the prototype */
|
|
js_dup(JS_MKPTR(JS_TAG_OBJECT, p));
|
|
ret = JS_GetOwnPropertyInternal(ctx, NULL, p, prop);
|
|
JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p));
|
|
if (ret != 0)
|
|
return ret;
|
|
if (p->class_id >= JS_CLASS_UINT8C_ARRAY &&
|
|
p->class_id <= JS_CLASS_FLOAT64_ARRAY) {
|
|
ret = JS_AtomIsNumericIndex(ctx, prop);
|
|
if (ret != 0) {
|
|
if (ret < 0)
|
|
return -1;
|
|
return FALSE;
|
|
}
|
|
}
|
|
p = p->shape->proto;
|
|
if (!p)
|
|
break;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/* val must be a symbol */
|
|
static JSAtom js_symbol_to_atom(JSContext *ctx, JSValue val)
|
|
{
|
|
JSAtomStruct *p = JS_VALUE_GET_PTR(val);
|
|
return js_get_atom_index(ctx->rt, p);
|
|
}
|
|
|
|
/* return JS_ATOM_NULL in case of exception */
|
|
JSAtom JS_ValueToAtom(JSContext *ctx, JSValue val)
|
|
{
|
|
JSAtom atom;
|
|
uint32_t tag;
|
|
tag = JS_VALUE_GET_TAG(val);
|
|
if (tag == JS_TAG_INT &&
|
|
(uint32_t)JS_VALUE_GET_INT(val) <= JS_ATOM_MAX_INT) {
|
|
/* fast path for integer values */
|
|
atom = __JS_AtomFromUInt32(JS_VALUE_GET_INT(val));
|
|
} else if (tag == JS_TAG_SYMBOL) {
|
|
JSAtomStruct *p = JS_VALUE_GET_PTR(val);
|
|
atom = JS_DupAtom(ctx, js_get_atom_index(ctx->rt, p));
|
|
} else {
|
|
JSValue str;
|
|
str = JS_ToPropertyKey(ctx, val);
|
|
if (JS_IsException(str))
|
|
return JS_ATOM_NULL;
|
|
if (JS_VALUE_GET_TAG(str) == JS_TAG_SYMBOL) {
|
|
atom = js_symbol_to_atom(ctx, str);
|
|
} else {
|
|
atom = JS_NewAtomStr(ctx, JS_VALUE_GET_STRING(str));
|
|
}
|
|
}
|
|
return atom;
|
|
}
|
|
|
|
static BOOL js_get_fast_array_element(JSContext *ctx, JSObject *p,
|
|
uint32_t idx, JSValue *pval)
|
|
{
|
|
switch(p->class_id) {
|
|
case JS_CLASS_ARRAY:
|
|
case JS_CLASS_ARGUMENTS:
|
|
if (unlikely(idx >= p->u.array.count)) return FALSE;
|
|
*pval = js_dup(p->u.array.u.values[idx]);
|
|
return TRUE;
|
|
case JS_CLASS_INT8_ARRAY:
|
|
if (unlikely(idx >= p->u.array.count)) return FALSE;
|
|
*pval = js_int32(p->u.array.u.int8_ptr[idx]);
|
|
return TRUE;
|
|
case JS_CLASS_UINT8C_ARRAY:
|
|
case JS_CLASS_UINT8_ARRAY:
|
|
if (unlikely(idx >= p->u.array.count)) return FALSE;
|
|
*pval = js_int32(p->u.array.u.uint8_ptr[idx]);
|
|
return TRUE;
|
|
case JS_CLASS_INT16_ARRAY:
|
|
if (unlikely(idx >= p->u.array.count)) return FALSE;
|
|
*pval = js_int32(p->u.array.u.int16_ptr[idx]);
|
|
return TRUE;
|
|
case JS_CLASS_UINT16_ARRAY:
|
|
if (unlikely(idx >= p->u.array.count)) return FALSE;
|
|
*pval = js_int32(p->u.array.u.uint16_ptr[idx]);
|
|
return TRUE;
|
|
case JS_CLASS_INT32_ARRAY:
|
|
if (unlikely(idx >= p->u.array.count)) return FALSE;
|
|
*pval = js_int32(p->u.array.u.int32_ptr[idx]);
|
|
return TRUE;
|
|
case JS_CLASS_UINT32_ARRAY:
|
|
if (unlikely(idx >= p->u.array.count)) return FALSE;
|
|
*pval = js_uint32(p->u.array.u.uint32_ptr[idx]);
|
|
return TRUE;
|
|
case JS_CLASS_BIG_INT64_ARRAY:
|
|
if (unlikely(idx >= p->u.array.count)) return FALSE;
|
|
*pval = JS_NewBigInt64(ctx, p->u.array.u.int64_ptr[idx]);
|
|
return TRUE;
|
|
case JS_CLASS_BIG_UINT64_ARRAY:
|
|
if (unlikely(idx >= p->u.array.count)) return FALSE;
|
|
*pval = JS_NewBigUint64(ctx, p->u.array.u.uint64_ptr[idx]);
|
|
return TRUE;
|
|
case JS_CLASS_FLOAT32_ARRAY:
|
|
if (unlikely(idx >= p->u.array.count)) return FALSE;
|
|
*pval = js_float64(p->u.array.u.float_ptr[idx]);
|
|
return TRUE;
|
|
case JS_CLASS_FLOAT64_ARRAY:
|
|
if (unlikely(idx >= p->u.array.count)) return FALSE;
|
|
*pval = js_float64(p->u.array.u.double_ptr[idx]);
|
|
return TRUE;
|
|
default:
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
static JSValue JS_GetPropertyValue(JSContext *ctx, JSValue this_obj,
|
|
JSValue prop)
|
|
{
|
|
JSAtom atom;
|
|
JSValue ret;
|
|
|
|
if (likely(JS_VALUE_GET_TAG(this_obj) == JS_TAG_OBJECT &&
|
|
JS_VALUE_GET_TAG(prop) == JS_TAG_INT)) {
|
|
JSObject *p = JS_VALUE_GET_OBJ(this_obj);
|
|
uint32_t idx = JS_VALUE_GET_INT(prop);
|
|
JSValue val;
|
|
/* fast path for array and typed array access */
|
|
if (js_get_fast_array_element(ctx, p, idx, &val))
|
|
return val;
|
|
}
|
|
atom = JS_ValueToAtom(ctx, prop);
|
|
JS_FreeValue(ctx, prop);
|
|
if (unlikely(atom == JS_ATOM_NULL))
|
|
return JS_EXCEPTION;
|
|
ret = JS_GetProperty(ctx, this_obj, atom);
|
|
JS_FreeAtom(ctx, atom);
|
|
return ret;
|
|
}
|
|
|
|
JSValue JS_GetPropertyUint32(JSContext *ctx, JSValue this_obj,
|
|
uint32_t idx)
|
|
{
|
|
return JS_GetPropertyInt64(ctx, this_obj, idx);
|
|
}
|
|
|
|
/* Check if an object has a generalized numeric property. Return value:
|
|
-1 for exception, *pval set to JS_EXCEPTION
|
|
TRUE if property exists, stored into *pval,
|
|
FALSE if property does not exist. *pval set to JS_UNDEFINED.
|
|
*/
|
|
static int JS_TryGetPropertyInt64(JSContext *ctx, JSValue obj, int64_t idx, JSValue *pval)
|
|
{
|
|
JSValue val;
|
|
JSAtom prop;
|
|
int present;
|
|
|
|
if (likely(JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT &&
|
|
(uint64_t)idx <= INT32_MAX)) {
|
|
/* fast path for array and typed array access */
|
|
JSObject *p = JS_VALUE_GET_OBJ(obj);
|
|
if (js_get_fast_array_element(ctx, p, idx, pval))
|
|
return TRUE;
|
|
}
|
|
val = JS_EXCEPTION;
|
|
present = -1;
|
|
prop = JS_NewAtomInt64(ctx, idx);
|
|
if (likely(prop != JS_ATOM_NULL)) {
|
|
present = JS_HasProperty(ctx, obj, prop);
|
|
if (present > 0) {
|
|
val = JS_GetProperty(ctx, obj, prop);
|
|
if (unlikely(JS_IsException(val)))
|
|
present = -1;
|
|
} else if (present == FALSE) {
|
|
val = JS_UNDEFINED;
|
|
}
|
|
JS_FreeAtom(ctx, prop);
|
|
}
|
|
*pval = val;
|
|
return present;
|
|
}
|
|
|
|
JSValue JS_GetPropertyInt64(JSContext *ctx, JSValue obj, int64_t idx)
|
|
{
|
|
JSAtom prop;
|
|
JSValue val;
|
|
|
|
if (likely(JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT &&
|
|
(uint64_t)idx <= INT32_MAX)) {
|
|
/* fast path for array and typed array access */
|
|
JSObject *p = JS_VALUE_GET_OBJ(obj);
|
|
if (js_get_fast_array_element(ctx, p, idx, &val))
|
|
return val;
|
|
}
|
|
prop = JS_NewAtomInt64(ctx, idx);
|
|
if (prop == JS_ATOM_NULL)
|
|
return JS_EXCEPTION;
|
|
|
|
val = JS_GetProperty(ctx, obj, prop);
|
|
JS_FreeAtom(ctx, prop);
|
|
return val;
|
|
}
|
|
|
|
/* `prop` may be pure ASCII or UTF-8 encoded */
|
|
JSValue JS_GetPropertyStr(JSContext *ctx, JSValue this_obj,
|
|
const char *prop)
|
|
{
|
|
JSAtom atom;
|
|
JSValue ret;
|
|
atom = JS_NewAtom(ctx, prop);
|
|
ret = JS_GetProperty(ctx, this_obj, atom);
|
|
JS_FreeAtom(ctx, atom);
|
|
return ret;
|
|
}
|
|
|
|
/* Note: the property value is not initialized. Return NULL if memory
|
|
error. */
|
|
static JSProperty *add_property(JSContext *ctx,
|
|
JSObject *p, JSAtom prop, int prop_flags)
|
|
{
|
|
JSShape *sh, *new_sh;
|
|
|
|
sh = p->shape;
|
|
if (sh->is_hashed) {
|
|
/* try to find an existing shape */
|
|
new_sh = find_hashed_shape_prop(ctx->rt, sh, prop, prop_flags);
|
|
if (new_sh) {
|
|
/* matching shape found: use it */
|
|
/* the property array may need to be resized */
|
|
if (new_sh->prop_size != sh->prop_size) {
|
|
JSProperty *new_prop;
|
|
new_prop = js_realloc(ctx, p->prop, sizeof(p->prop[0]) *
|
|
new_sh->prop_size);
|
|
if (!new_prop)
|
|
return NULL;
|
|
p->prop = new_prop;
|
|
}
|
|
p->shape = js_dup_shape(new_sh);
|
|
js_free_shape(ctx->rt, sh);
|
|
return &p->prop[new_sh->prop_count - 1];
|
|
} else if (sh->header.ref_count != 1) {
|
|
/* if the shape is shared, clone it */
|
|
new_sh = js_clone_shape(ctx, sh);
|
|
if (!new_sh)
|
|
return NULL;
|
|
/* hash the cloned shape */
|
|
new_sh->is_hashed = TRUE;
|
|
js_shape_hash_link(ctx->rt, new_sh);
|
|
js_free_shape(ctx->rt, p->shape);
|
|
p->shape = new_sh;
|
|
}
|
|
}
|
|
assert(p->shape->header.ref_count == 1);
|
|
if (add_shape_property(ctx, &p->shape, p, prop, prop_flags))
|
|
return NULL;
|
|
return &p->prop[p->shape->prop_count - 1];
|
|
}
|
|
|
|
/* can be called on Array or Arguments objects. return < 0 if
|
|
memory alloc error. */
|
|
static no_inline __exception int convert_fast_array_to_array(JSContext *ctx,
|
|
JSObject *p)
|
|
{
|
|
JSProperty *pr;
|
|
JSShape *sh;
|
|
JSValue *tab;
|
|
uint32_t i, len, new_count;
|
|
|
|
if (js_shape_prepare_update(ctx, p, NULL))
|
|
return -1;
|
|
len = p->u.array.count;
|
|
/* resize the properties once to simplify the error handling */
|
|
sh = p->shape;
|
|
new_count = sh->prop_count + len;
|
|
if (new_count > sh->prop_size) {
|
|
if (resize_properties(ctx, &p->shape, p, new_count))
|
|
return -1;
|
|
}
|
|
|
|
tab = p->u.array.u.values;
|
|
for(i = 0; i < len; i++) {
|
|
/* add_property cannot fail here but
|
|
__JS_AtomFromUInt32(i) fails for i > INT32_MAX */
|
|
pr = add_property(ctx, p, __JS_AtomFromUInt32(i), JS_PROP_C_W_E);
|
|
pr->u.value = *tab++;
|
|
}
|
|
js_free(ctx, p->u.array.u.values);
|
|
p->u.array.count = 0;
|
|
p->u.array.u.values = NULL; /* fail safe */
|
|
p->u.array.u1.size = 0;
|
|
p->fast_array = 0;
|
|
return 0;
|
|
}
|
|
|
|
static int delete_property(JSContext *ctx, JSObject *p, JSAtom atom)
|
|
{
|
|
JSShape *sh;
|
|
JSShapeProperty *pr, *lpr, *prop;
|
|
JSProperty *pr1;
|
|
uint32_t lpr_idx;
|
|
intptr_t h, h1;
|
|
|
|
redo:
|
|
sh = p->shape;
|
|
h1 = atom & sh->prop_hash_mask;
|
|
h = prop_hash_end(sh)[-h1 - 1];
|
|
prop = get_shape_prop(sh);
|
|
lpr = NULL;
|
|
lpr_idx = 0; /* prevent warning */
|
|
while (h != 0) {
|
|
pr = &prop[h - 1];
|
|
if (likely(pr->atom == atom)) {
|
|
/* found ! */
|
|
if (!(pr->flags & JS_PROP_CONFIGURABLE))
|
|
return FALSE;
|
|
/* realloc the shape if needed */
|
|
if (lpr)
|
|
lpr_idx = lpr - get_shape_prop(sh);
|
|
if (js_shape_prepare_update(ctx, p, &pr))
|
|
return -1;
|
|
sh = p->shape;
|
|
/* remove property */
|
|
if (lpr) {
|
|
lpr = get_shape_prop(sh) + lpr_idx;
|
|
lpr->hash_next = pr->hash_next;
|
|
} else {
|
|
prop_hash_end(sh)[-h1 - 1] = pr->hash_next;
|
|
}
|
|
sh->deleted_prop_count++;
|
|
/* free the entry */
|
|
pr1 = &p->prop[h - 1];
|
|
free_property(ctx->rt, pr1, pr->flags);
|
|
JS_FreeAtom(ctx, pr->atom);
|
|
/* put default values */
|
|
pr->flags = 0;
|
|
pr->atom = JS_ATOM_NULL;
|
|
pr1->u.value = JS_UNDEFINED;
|
|
|
|
/* compact the properties if too many deleted properties */
|
|
if (sh->deleted_prop_count >= 8 &&
|
|
sh->deleted_prop_count >= ((unsigned)sh->prop_count / 2)) {
|
|
compact_properties(ctx, p);
|
|
}
|
|
return TRUE;
|
|
}
|
|
lpr = pr;
|
|
h = pr->hash_next;
|
|
}
|
|
|
|
if (p->is_exotic) {
|
|
if (p->fast_array) {
|
|
uint32_t idx;
|
|
if (JS_AtomIsArrayIndex(ctx, &idx, atom) &&
|
|
idx < p->u.array.count) {
|
|
if (p->class_id == JS_CLASS_ARRAY ||
|
|
p->class_id == JS_CLASS_ARGUMENTS) {
|
|
/* Special case deleting the last element of a fast Array */
|
|
if (idx == p->u.array.count - 1) {
|
|
JS_FreeValue(ctx, p->u.array.u.values[idx]);
|
|
p->u.array.count = idx;
|
|
return TRUE;
|
|
}
|
|
if (convert_fast_array_to_array(ctx, p))
|
|
return -1;
|
|
goto redo;
|
|
} else {
|
|
return FALSE;
|
|
}
|
|
}
|
|
} else {
|
|
const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic;
|
|
if (em && em->delete_property) {
|
|
return em->delete_property(ctx, JS_MKPTR(JS_TAG_OBJECT, p), atom);
|
|
}
|
|
}
|
|
}
|
|
/* not found */
|
|
return TRUE;
|
|
}
|
|
|
|
static int call_setter(JSContext *ctx, JSObject *setter,
|
|
JSValue this_obj, JSValue val, int flags)
|
|
{
|
|
JSValue ret, func;
|
|
if (likely(setter)) {
|
|
func = JS_MKPTR(JS_TAG_OBJECT, setter);
|
|
/* Note: the field could be removed in the setter */
|
|
func = js_dup(func);
|
|
ret = JS_CallFree(ctx, func, this_obj, 1, &val);
|
|
JS_FreeValue(ctx, val);
|
|
if (JS_IsException(ret))
|
|
return -1;
|
|
JS_FreeValue(ctx, ret);
|
|
return TRUE;
|
|
} else {
|
|
JS_FreeValue(ctx, val);
|
|
if ((flags & JS_PROP_THROW) ||
|
|
((flags & JS_PROP_THROW_STRICT) && is_strict_mode(ctx))) {
|
|
JS_ThrowTypeError(ctx, "no setter for property");
|
|
return -1;
|
|
}
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* set the array length and remove the array elements if necessary. */
|
|
static int set_array_length(JSContext *ctx, JSObject *p, JSValue val,
|
|
int flags)
|
|
{
|
|
uint32_t len, idx, cur_len;
|
|
int i, ret;
|
|
|
|
/* Note: this call can reallocate the properties of 'p' */
|
|
ret = JS_ToArrayLengthFree(ctx, &len, val, FALSE);
|
|
if (ret)
|
|
return -1;
|
|
/* JS_ToArrayLengthFree() must be done before the read-only test */
|
|
if (unlikely(!(p->shape->prop[0].flags & JS_PROP_WRITABLE)))
|
|
return JS_ThrowTypeErrorReadOnly(ctx, flags, JS_ATOM_length);
|
|
|
|
if (likely(p->fast_array)) {
|
|
uint32_t old_len = p->u.array.count;
|
|
if (len < old_len) {
|
|
for(i = len; i < old_len; i++) {
|
|
JS_FreeValue(ctx, p->u.array.u.values[i]);
|
|
}
|
|
p->u.array.count = len;
|
|
}
|
|
p->prop[0].u.value = js_uint32(len);
|
|
} else {
|
|
/* Note: length is always a uint32 because the object is an
|
|
array */
|
|
JS_ToUint32(ctx, &cur_len, p->prop[0].u.value);
|
|
if (len < cur_len) {
|
|
uint32_t d;
|
|
JSShape *sh;
|
|
JSShapeProperty *pr;
|
|
|
|
d = cur_len - len;
|
|
sh = p->shape;
|
|
if (d <= sh->prop_count) {
|
|
JSAtom atom;
|
|
|
|
/* faster to iterate */
|
|
while (cur_len > len) {
|
|
atom = JS_NewAtomUInt32(ctx, cur_len - 1);
|
|
ret = delete_property(ctx, p, atom);
|
|
JS_FreeAtom(ctx, atom);
|
|
if (unlikely(!ret)) {
|
|
/* unlikely case: property is not
|
|
configurable */
|
|
break;
|
|
}
|
|
cur_len--;
|
|
}
|
|
} else {
|
|
/* faster to iterate thru all the properties. Need two
|
|
passes in case one of the property is not
|
|
configurable */
|
|
cur_len = len;
|
|
for(i = 0, pr = get_shape_prop(sh); i < sh->prop_count;
|
|
i++, pr++) {
|
|
if (pr->atom != JS_ATOM_NULL &&
|
|
JS_AtomIsArrayIndex(ctx, &idx, pr->atom)) {
|
|
if (idx >= cur_len &&
|
|
!(pr->flags & JS_PROP_CONFIGURABLE)) {
|
|
cur_len = idx + 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
for(i = 0, pr = get_shape_prop(sh); i < sh->prop_count;
|
|
i++, pr++) {
|
|
if (pr->atom != JS_ATOM_NULL &&
|
|
JS_AtomIsArrayIndex(ctx, &idx, pr->atom)) {
|
|
if (idx >= cur_len) {
|
|
/* remove the property */
|
|
delete_property(ctx, p, pr->atom);
|
|
/* WARNING: the shape may have been modified */
|
|
sh = p->shape;
|
|
pr = get_shape_prop(sh) + i;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
cur_len = len;
|
|
}
|
|
set_value(ctx, &p->prop[0].u.value, js_uint32(cur_len));
|
|
if (unlikely(cur_len > len)) {
|
|
return JS_ThrowTypeErrorOrFalse(ctx, flags, "not configurable");
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/* return -1 if exception */
|
|
static int expand_fast_array(JSContext *ctx, JSObject *p, uint32_t new_len)
|
|
{
|
|
uint32_t new_size;
|
|
size_t slack;
|
|
JSValue *new_array_prop;
|
|
/* XXX: potential arithmetic overflow */
|
|
new_size = max_int(new_len, p->u.array.u1.size * 3 / 2);
|
|
new_array_prop = js_realloc2(ctx, p->u.array.u.values, sizeof(JSValue) * new_size, &slack);
|
|
if (!new_array_prop)
|
|
return -1;
|
|
new_size += slack / sizeof(*new_array_prop);
|
|
p->u.array.u.values = new_array_prop;
|
|
p->u.array.u1.size = new_size;
|
|
return 0;
|
|
}
|
|
|
|
/* Preconditions: 'p' must be of class JS_CLASS_ARRAY, p->fast_array =
|
|
TRUE and p->extensible = TRUE */
|
|
static int add_fast_array_element(JSContext *ctx, JSObject *p,
|
|
JSValue val, int flags)
|
|
{
|
|
uint32_t new_len, array_len;
|
|
/* extend the array by one */
|
|
/* XXX: convert to slow array if new_len > 2^31-1 elements */
|
|
new_len = p->u.array.count + 1;
|
|
/* update the length if necessary. We assume that if the length is
|
|
not an integer, then if it >= 2^31. */
|
|
if (likely(JS_VALUE_GET_TAG(p->prop[0].u.value) == JS_TAG_INT)) {
|
|
array_len = JS_VALUE_GET_INT(p->prop[0].u.value);
|
|
if (new_len > array_len) {
|
|
if (unlikely(!(get_shape_prop(p->shape)->flags & JS_PROP_WRITABLE))) {
|
|
JS_FreeValue(ctx, val);
|
|
return JS_ThrowTypeErrorReadOnly(ctx, flags, JS_ATOM_length);
|
|
}
|
|
p->prop[0].u.value = js_int32(new_len);
|
|
}
|
|
}
|
|
if (unlikely(new_len > p->u.array.u1.size)) {
|
|
if (expand_fast_array(ctx, p, new_len)) {
|
|
JS_FreeValue(ctx, val);
|
|
return -1;
|
|
}
|
|
}
|
|
p->u.array.u.values[new_len - 1] = val;
|
|
p->u.array.count = new_len;
|
|
return TRUE;
|
|
}
|
|
|
|
static void js_free_desc(JSContext *ctx, JSPropertyDescriptor *desc)
|
|
{
|
|
JS_FreeValue(ctx, desc->getter);
|
|
JS_FreeValue(ctx, desc->setter);
|
|
JS_FreeValue(ctx, desc->value);
|
|
}
|
|
|
|
/* return -1 in case of exception or TRUE or FALSE. Warning: 'val' is
|
|
freed by the function. 'flags' is a bitmask of JS_PROP_NO_ADD,
|
|
JS_PROP_THROW or JS_PROP_THROW_STRICT. If JS_PROP_NO_ADD is set,
|
|
the new property is not added and an error is raised.
|
|
'obj' must be an object when obj != this_obj.
|
|
*/
|
|
static int JS_SetPropertyInternal2(JSContext *ctx, JSValue obj,
|
|
JSAtom prop, JSValue val, JSValue this_obj,
|
|
int flags, JSInlineCache *ic)
|
|
{
|
|
JSObject *p, *p1;
|
|
JSShapeProperty *prs;
|
|
JSProperty *pr;
|
|
JSPropertyDescriptor desc;
|
|
int ret;
|
|
uint32_t offset = 0;
|
|
|
|
switch(JS_VALUE_GET_TAG(this_obj)) {
|
|
case JS_TAG_NULL:
|
|
JS_ThrowTypeErrorAtom(ctx, "cannot set property '%s' of null", prop);
|
|
goto fail;
|
|
case JS_TAG_UNDEFINED:
|
|
JS_ThrowTypeErrorAtom(ctx, "cannot set property '%s' of undefined", prop);
|
|
goto fail;
|
|
case JS_TAG_OBJECT:
|
|
p = JS_VALUE_GET_OBJ(this_obj);
|
|
p1 = JS_VALUE_GET_OBJ(obj);
|
|
if (p == p1)
|
|
break;
|
|
goto retry2;
|
|
default:
|
|
if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)
|
|
obj = JS_GetPrototypePrimitive(ctx, obj);
|
|
p = NULL;
|
|
p1 = JS_VALUE_GET_OBJ(obj);
|
|
goto prototype_lookup;
|
|
}
|
|
|
|
retry:
|
|
prs = find_own_property_ic(&pr, p1, prop, &offset);
|
|
if (prs) {
|
|
if (likely((prs->flags & (JS_PROP_TMASK | JS_PROP_WRITABLE |
|
|
JS_PROP_LENGTH)) == JS_PROP_WRITABLE)) {
|
|
/* fast case */
|
|
if (ic && p->shape->is_hashed) {
|
|
ic->updated = TRUE;
|
|
ic->updated_offset = add_ic_slot(ctx, ic, prop, p, offset);
|
|
}
|
|
set_value(ctx, &pr->u.value, val);
|
|
return TRUE;
|
|
} else if (prs->flags & JS_PROP_LENGTH) {
|
|
assert(p->class_id == JS_CLASS_ARRAY);
|
|
assert(prop == JS_ATOM_length);
|
|
return set_array_length(ctx, p, val, flags);
|
|
} else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) {
|
|
return call_setter(ctx, pr->u.getset.setter, this_obj, val, flags);
|
|
} else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) {
|
|
/* JS_PROP_WRITABLE is always true for variable
|
|
references, but they are write protected in module name
|
|
spaces. */
|
|
if (p->class_id == JS_CLASS_MODULE_NS)
|
|
goto read_only_prop;
|
|
set_value(ctx, pr->u.var_ref->pvalue, val);
|
|
return TRUE;
|
|
} else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) {
|
|
/* Instantiate property and retry (potentially useless) */
|
|
if (JS_AutoInitProperty(ctx, p, prop, pr, prs))
|
|
goto fail;
|
|
goto retry;
|
|
} else {
|
|
goto read_only_prop;
|
|
}
|
|
}
|
|
|
|
for(;;) {
|
|
if (p1->is_exotic) {
|
|
if (p1->fast_array) {
|
|
if (__JS_AtomIsTaggedInt(prop)) {
|
|
uint32_t idx = __JS_AtomToUInt32(prop);
|
|
if (idx < p1->u.array.count) {
|
|
if (unlikely(p == p1))
|
|
return JS_SetPropertyValue(ctx, this_obj, js_int32(idx), val, flags);
|
|
else
|
|
break;
|
|
} else if (p1->class_id >= JS_CLASS_UINT8C_ARRAY &&
|
|
p1->class_id <= JS_CLASS_FLOAT64_ARRAY) {
|
|
goto typed_array_oob;
|
|
}
|
|
} else if (p1->class_id >= JS_CLASS_UINT8C_ARRAY &&
|
|
p1->class_id <= JS_CLASS_FLOAT64_ARRAY) {
|
|
ret = JS_AtomIsNumericIndex(ctx, prop);
|
|
if (ret != 0) {
|
|
if (ret < 0)
|
|
goto fail;
|
|
typed_array_oob:
|
|
// per spec: evaluate value for side effects
|
|
if (p1->class_id == JS_CLASS_BIG_INT64_ARRAY ||
|
|
p1->class_id == JS_CLASS_BIG_UINT64_ARRAY) {
|
|
int64_t v;
|
|
if (JS_ToBigInt64Free(ctx, &v, val))
|
|
return -1;
|
|
} else {
|
|
val = JS_ToNumberFree(ctx, val);
|
|
JS_FreeValue(ctx, val);
|
|
if (JS_IsException(val))
|
|
return -1;
|
|
}
|
|
return TRUE;
|
|
}
|
|
}
|
|
} else {
|
|
const JSClassExoticMethods *em = ctx->rt->class_array[p1->class_id].exotic;
|
|
if (em) {
|
|
JSValue obj1;
|
|
if (em->set_property) {
|
|
/* set_property can free the prototype */
|
|
obj1 = js_dup(JS_MKPTR(JS_TAG_OBJECT, p1));
|
|
ret = em->set_property(ctx, obj1, prop,
|
|
val, this_obj, flags);
|
|
JS_FreeValue(ctx, obj1);
|
|
JS_FreeValue(ctx, val);
|
|
return ret;
|
|
}
|
|
if (em->get_own_property) {
|
|
/* get_own_property can free the prototype */
|
|
obj1 = js_dup(JS_MKPTR(JS_TAG_OBJECT, p1));
|
|
ret = em->get_own_property(ctx, &desc,
|
|
obj1, prop);
|
|
JS_FreeValue(ctx, obj1);
|
|
if (ret < 0)
|
|
goto fail;
|
|
if (ret) {
|
|
if (desc.flags & JS_PROP_GETSET) {
|
|
JSObject *setter;
|
|
if (JS_IsUndefined(desc.setter))
|
|
setter = NULL;
|
|
else
|
|
setter = JS_VALUE_GET_OBJ(desc.setter);
|
|
ret = call_setter(ctx, setter, this_obj, val, flags);
|
|
JS_FreeValue(ctx, desc.getter);
|
|
JS_FreeValue(ctx, desc.setter);
|
|
return ret;
|
|
} else {
|
|
JS_FreeValue(ctx, desc.value);
|
|
if (!(desc.flags & JS_PROP_WRITABLE))
|
|
goto read_only_prop;
|
|
if (likely(p == p1)) {
|
|
ret = JS_DefineProperty(ctx, this_obj, prop, val,
|
|
JS_UNDEFINED, JS_UNDEFINED,
|
|
JS_PROP_HAS_VALUE);
|
|
JS_FreeValue(ctx, val);
|
|
return ret;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
p1 = p1->shape->proto;
|
|
prototype_lookup:
|
|
if (!p1)
|
|
break;
|
|
|
|
retry2:
|
|
prs = find_own_property(&pr, p1, prop);
|
|
if (prs) {
|
|
if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) {
|
|
return call_setter(ctx, pr->u.getset.setter, this_obj, val, flags);
|
|
} else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) {
|
|
/* Instantiate property and retry (potentially useless) */
|
|
if (JS_AutoInitProperty(ctx, p1, prop, pr, prs))
|
|
return -1;
|
|
goto retry2;
|
|
} else if (!(prs->flags & JS_PROP_WRITABLE)) {
|
|
goto read_only_prop;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (unlikely(flags & JS_PROP_NO_ADD)) {
|
|
JS_ThrowReferenceErrorNotDefined(ctx, prop);
|
|
goto fail;
|
|
}
|
|
|
|
if (unlikely(!p)) {
|
|
ret = JS_ThrowTypeErrorOrFalse(ctx, flags, "not an object");
|
|
goto done;
|
|
}
|
|
|
|
if (unlikely(!p->extensible)) {
|
|
ret = JS_ThrowTypeErrorOrFalse(ctx, flags, "object is not extensible");
|
|
goto done;
|
|
}
|
|
|
|
if (p == JS_VALUE_GET_OBJ(obj)) {
|
|
if (p->is_exotic) {
|
|
if (p->class_id == JS_CLASS_ARRAY && p->fast_array &&
|
|
__JS_AtomIsTaggedInt(prop)) {
|
|
uint32_t idx = __JS_AtomToUInt32(prop);
|
|
if (idx == p->u.array.count) {
|
|
/* fast case */
|
|
return add_fast_array_element(ctx, p, val, flags);
|
|
}
|
|
}
|
|
goto generic_create_prop;
|
|
} else {
|
|
pr = add_property(ctx, p, prop, JS_PROP_C_W_E);
|
|
if (!pr)
|
|
goto fail;
|
|
pr->u.value = val;
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
// TODO(bnoordhuis) return JSProperty slot and update in place
|
|
// when plain property (not is_exotic/setter/etc.) to avoid
|
|
// calling find_own_property() thrice?
|
|
ret = JS_GetOwnPropertyInternal(ctx, &desc, p, prop);
|
|
if (ret < 0)
|
|
goto fail;
|
|
|
|
if (ret) {
|
|
JS_FreeValue(ctx, desc.value);
|
|
if (desc.flags & JS_PROP_GETSET) {
|
|
JS_FreeValue(ctx, desc.getter);
|
|
JS_FreeValue(ctx, desc.setter);
|
|
ret = JS_ThrowTypeErrorOrFalse(ctx, flags, "setter is forbidden");
|
|
goto done;
|
|
} else if (!(desc.flags & JS_PROP_WRITABLE) ||
|
|
p->class_id == JS_CLASS_MODULE_NS) {
|
|
read_only_prop:
|
|
ret = JS_ThrowTypeErrorReadOnly(ctx, flags, prop);
|
|
goto done;
|
|
}
|
|
ret = JS_DefineProperty(ctx, this_obj, prop, val,
|
|
JS_UNDEFINED, JS_UNDEFINED,
|
|
JS_PROP_HAS_VALUE);
|
|
} else {
|
|
generic_create_prop:
|
|
ret = JS_CreateProperty(ctx, p, prop, val, JS_UNDEFINED, JS_UNDEFINED,
|
|
flags |
|
|
JS_PROP_HAS_VALUE |
|
|
JS_PROP_HAS_ENUMERABLE |
|
|
JS_PROP_HAS_WRITABLE |
|
|
JS_PROP_HAS_CONFIGURABLE |
|
|
JS_PROP_C_W_E);
|
|
}
|
|
|
|
done:
|
|
JS_FreeValue(ctx, val);
|
|
return ret;
|
|
fail:
|
|
JS_FreeValue(ctx, val);
|
|
return -1;
|
|
}
|
|
|
|
static int JS_SetPropertyInternal(JSContext *ctx, JSValue this_obj,
|
|
JSAtom prop, JSValue val, int flags)
|
|
{
|
|
return JS_SetPropertyInternal2(ctx, this_obj, prop, val, this_obj,
|
|
flags, NULL);
|
|
}
|
|
|
|
int JS_SetProperty(JSContext *ctx, JSValue this_obj, JSAtom prop, JSValue val)
|
|
{
|
|
return JS_SetPropertyInternal2(ctx, this_obj, prop, val, this_obj, JS_PROP_THROW, NULL);
|
|
}
|
|
|
|
static int JS_SetPropertyInternalWithIC(JSContext *ctx, JSValue this_obj,
|
|
JSAtom prop, JSValue val, int flags,
|
|
JSInlineCache *ic, int32_t offset) {
|
|
uint32_t tag;
|
|
JSObject *p;
|
|
tag = JS_VALUE_GET_TAG(this_obj);
|
|
if (unlikely(tag != JS_TAG_OBJECT))
|
|
goto slow_path;
|
|
p = JS_VALUE_GET_OBJ(this_obj);
|
|
offset = get_ic_prop_offset(ic, offset, p->shape);
|
|
if (likely(offset >= 0)) {
|
|
set_value(ctx, &p->prop[offset].u.value, val);
|
|
return TRUE;
|
|
}
|
|
slow_path:
|
|
return JS_SetPropertyInternal2(ctx, this_obj, prop, val, this_obj,
|
|
flags, ic);
|
|
}
|
|
|
|
/* flags can be JS_PROP_THROW or JS_PROP_THROW_STRICT */
|
|
static int JS_SetPropertyValue(JSContext *ctx, JSValue this_obj,
|
|
JSValue prop, JSValue val, int flags)
|
|
{
|
|
if (likely(JS_VALUE_GET_TAG(this_obj) == JS_TAG_OBJECT &&
|
|
JS_VALUE_GET_TAG(prop) == JS_TAG_INT)) {
|
|
JSObject *p;
|
|
uint32_t idx;
|
|
double d;
|
|
int32_t v;
|
|
|
|
/* fast path for array access */
|
|
p = JS_VALUE_GET_OBJ(this_obj);
|
|
idx = JS_VALUE_GET_INT(prop);
|
|
switch(p->class_id) {
|
|
case JS_CLASS_ARRAY:
|
|
if (unlikely(idx >= (uint32_t)p->u.array.count)) {
|
|
JSObject *p1;
|
|
JSShape *sh1;
|
|
|
|
/* fast path to add an element to the array */
|
|
if (idx != (uint32_t)p->u.array.count ||
|
|
!p->fast_array || !p->extensible)
|
|
goto slow_path;
|
|
/* check if prototype chain has a numeric property */
|
|
p1 = p->shape->proto;
|
|
while (p1 != NULL) {
|
|
sh1 = p1->shape;
|
|
if (p1->class_id == JS_CLASS_ARRAY) {
|
|
if (unlikely(!p1->fast_array))
|
|
goto slow_path;
|
|
} else if (p1->class_id == JS_CLASS_OBJECT) {
|
|
if (unlikely(sh1->has_small_array_index))
|
|
goto slow_path;
|
|
} else {
|
|
goto slow_path;
|
|
}
|
|
p1 = sh1->proto;
|
|
}
|
|
/* add element */
|
|
return add_fast_array_element(ctx, p, val, flags);
|
|
}
|
|
set_value(ctx, &p->u.array.u.values[idx], val);
|
|
break;
|
|
case JS_CLASS_ARGUMENTS:
|
|
if (unlikely(idx >= (uint32_t)p->u.array.count))
|
|
goto slow_path;
|
|
set_value(ctx, &p->u.array.u.values[idx], val);
|
|
break;
|
|
case JS_CLASS_UINT8C_ARRAY:
|
|
if (JS_ToUint8ClampFree(ctx, &v, val))
|
|
goto ta_cvt_fail;
|
|
/* Note: the conversion can detach the typed array, so the
|
|
array bound check must be done after */
|
|
if (unlikely(idx >= (uint32_t)p->u.array.count))
|
|
goto ta_out_of_bound;
|
|
p->u.array.u.uint8_ptr[idx] = v;
|
|
break;
|
|
case JS_CLASS_INT8_ARRAY:
|
|
case JS_CLASS_UINT8_ARRAY:
|
|
if (JS_ToInt32Free(ctx, &v, val))
|
|
goto ta_cvt_fail;
|
|
if (unlikely(idx >= (uint32_t)p->u.array.count))
|
|
goto ta_out_of_bound;
|
|
p->u.array.u.uint8_ptr[idx] = v;
|
|
break;
|
|
case JS_CLASS_INT16_ARRAY:
|
|
case JS_CLASS_UINT16_ARRAY:
|
|
if (JS_ToInt32Free(ctx, &v, val))
|
|
goto ta_cvt_fail;
|
|
if (unlikely(idx >= (uint32_t)p->u.array.count))
|
|
goto ta_out_of_bound;
|
|
p->u.array.u.uint16_ptr[idx] = v;
|
|
break;
|
|
case JS_CLASS_INT32_ARRAY:
|
|
case JS_CLASS_UINT32_ARRAY:
|
|
if (JS_ToInt32Free(ctx, &v, val))
|
|
goto ta_cvt_fail;
|
|
if (unlikely(idx >= (uint32_t)p->u.array.count))
|
|
goto ta_out_of_bound;
|
|
p->u.array.u.uint32_ptr[idx] = v;
|
|
break;
|
|
case JS_CLASS_BIG_INT64_ARRAY:
|
|
case JS_CLASS_BIG_UINT64_ARRAY:
|
|
/* XXX: need specific conversion function */
|
|
{
|
|
int64_t v;
|
|
if (JS_ToBigInt64Free(ctx, &v, val))
|
|
goto ta_cvt_fail;
|
|
if (unlikely(idx >= (uint32_t)p->u.array.count))
|
|
goto ta_out_of_bound;
|
|
p->u.array.u.uint64_ptr[idx] = v;
|
|
}
|
|
break;
|
|
case JS_CLASS_FLOAT32_ARRAY:
|
|
if (JS_ToFloat64Free(ctx, &d, val))
|
|
goto ta_cvt_fail;
|
|
if (unlikely(idx >= (uint32_t)p->u.array.count))
|
|
goto ta_out_of_bound;
|
|
p->u.array.u.float_ptr[idx] = d;
|
|
break;
|
|
case JS_CLASS_FLOAT64_ARRAY:
|
|
if (JS_ToFloat64Free(ctx, &d, val)) {
|
|
ta_cvt_fail:
|
|
if (flags & JS_PROP_REFLECT_DEFINE_PROPERTY) {
|
|
JS_FreeValue(ctx, JS_GetException(ctx));
|
|
return FALSE;
|
|
}
|
|
return -1;
|
|
}
|
|
if (unlikely(idx >= (uint32_t)p->u.array.count)) {
|
|
ta_out_of_bound:
|
|
if (typed_array_is_detached(ctx, p))
|
|
if (!(flags & JS_PROP_DEFINE_PROPERTY))
|
|
return TRUE; // per spec: no OOB exception
|
|
return JS_ThrowTypeErrorOrFalse(ctx, flags, "out-of-bound numeric index");
|
|
}
|
|
p->u.array.u.double_ptr[idx] = d;
|
|
break;
|
|
default:
|
|
goto slow_path;
|
|
}
|
|
return TRUE;
|
|
} else {
|
|
JSAtom atom;
|
|
int ret;
|
|
slow_path:
|
|
atom = JS_ValueToAtom(ctx, prop);
|
|
JS_FreeValue(ctx, prop);
|
|
if (unlikely(atom == JS_ATOM_NULL)) {
|
|
JS_FreeValue(ctx, val);
|
|
return -1;
|
|
}
|
|
ret = JS_SetPropertyInternal(ctx, this_obj, atom, val, flags);
|
|
JS_FreeAtom(ctx, atom);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
int JS_SetPropertyUint32(JSContext *ctx, JSValue this_obj,
|
|
uint32_t idx, JSValue val)
|
|
{
|
|
return JS_SetPropertyValue(ctx, this_obj, js_uint32(idx), val,
|
|
JS_PROP_THROW);
|
|
}
|
|
|
|
int JS_SetPropertyInt64(JSContext *ctx, JSValue this_obj,
|
|
int64_t idx, JSValue val)
|
|
{
|
|
JSAtom prop;
|
|
int res;
|
|
|
|
if ((uint64_t)idx <= INT32_MAX) {
|
|
/* fast path for fast arrays */
|
|
return JS_SetPropertyValue(ctx, this_obj, js_int32(idx), val,
|
|
JS_PROP_THROW);
|
|
}
|
|
prop = JS_NewAtomInt64(ctx, idx);
|
|
if (prop == JS_ATOM_NULL) {
|
|
JS_FreeValue(ctx, val);
|
|
return -1;
|
|
}
|
|
res = JS_SetProperty(ctx, this_obj, prop, val);
|
|
JS_FreeAtom(ctx, prop);
|
|
return res;
|
|
}
|
|
|
|
/* `prop` may be pure ASCII or UTF-8 encoded */
|
|
int JS_SetPropertyStr(JSContext *ctx, JSValue this_obj,
|
|
const char *prop, JSValue val)
|
|
{
|
|
JSAtom atom;
|
|
int ret;
|
|
atom = JS_NewAtom(ctx, prop);
|
|
ret = JS_SetPropertyInternal(ctx, this_obj, atom, val, JS_PROP_THROW);
|
|
JS_FreeAtom(ctx, atom);
|
|
return ret;
|
|
}
|
|
|
|
/* compute the property flags. For each flag: (JS_PROP_HAS_x forces
|
|
it, otherwise def_flags is used)
|
|
Note: makes assumption about the bit pattern of the flags
|
|
*/
|
|
static int get_prop_flags(int flags, int def_flags)
|
|
{
|
|
int mask;
|
|
mask = (flags >> JS_PROP_HAS_SHIFT) & JS_PROP_C_W_E;
|
|
return (flags & mask) | (def_flags & ~mask);
|
|
}
|
|
|
|
static int JS_CreateProperty(JSContext *ctx, JSObject *p,
|
|
JSAtom prop, JSValue val,
|
|
JSValue getter, JSValue setter,
|
|
int flags)
|
|
{
|
|
JSProperty *pr;
|
|
int ret, prop_flags;
|
|
|
|
/* add a new property or modify an existing exotic one */
|
|
if (p->is_exotic) {
|
|
if (p->class_id == JS_CLASS_ARRAY) {
|
|
uint32_t idx, len;
|
|
|
|
if (p->fast_array) {
|
|
if (__JS_AtomIsTaggedInt(prop)) {
|
|
idx = __JS_AtomToUInt32(prop);
|
|
if (idx == p->u.array.count) {
|
|
if (!p->extensible)
|
|
goto not_extensible;
|
|
if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET))
|
|
goto convert_to_array;
|
|
prop_flags = get_prop_flags(flags, 0);
|
|
if (prop_flags != JS_PROP_C_W_E)
|
|
goto convert_to_array;
|
|
return add_fast_array_element(ctx, p,
|
|
js_dup(val), flags);
|
|
} else {
|
|
goto convert_to_array;
|
|
}
|
|
} else if (JS_AtomIsArrayIndex(ctx, &idx, prop)) {
|
|
/* convert the fast array to normal array */
|
|
convert_to_array:
|
|
if (convert_fast_array_to_array(ctx, p))
|
|
return -1;
|
|
goto generic_array;
|
|
}
|
|
} else if (JS_AtomIsArrayIndex(ctx, &idx, prop)) {
|
|
JSProperty *plen;
|
|
JSShapeProperty *pslen;
|
|
generic_array:
|
|
/* update the length field */
|
|
plen = &p->prop[0];
|
|
JS_ToUint32(ctx, &len, plen->u.value);
|
|
if ((idx + 1) > len) {
|
|
pslen = get_shape_prop(p->shape);
|
|
if (unlikely(!(pslen->flags & JS_PROP_WRITABLE)))
|
|
return JS_ThrowTypeErrorReadOnly(ctx, flags, JS_ATOM_length);
|
|
/* XXX: should update the length after defining
|
|
the property */
|
|
len = idx + 1;
|
|
set_value(ctx, &plen->u.value, js_uint32(len));
|
|
}
|
|
}
|
|
} else if (p->class_id >= JS_CLASS_UINT8C_ARRAY &&
|
|
p->class_id <= JS_CLASS_FLOAT64_ARRAY) {
|
|
ret = JS_AtomIsNumericIndex(ctx, prop);
|
|
if (ret != 0) {
|
|
if (ret < 0)
|
|
return -1;
|
|
return JS_ThrowTypeErrorOrFalse(ctx, flags, "cannot create numeric index in typed array");
|
|
}
|
|
} else if (!(flags & JS_PROP_NO_EXOTIC)) {
|
|
const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic;
|
|
if (em) {
|
|
if (em->define_own_property) {
|
|
return em->define_own_property(ctx, JS_MKPTR(JS_TAG_OBJECT, p),
|
|
prop, val, getter, setter, flags);
|
|
}
|
|
ret = JS_IsExtensible(ctx, JS_MKPTR(JS_TAG_OBJECT, p));
|
|
if (ret < 0)
|
|
return -1;
|
|
if (!ret)
|
|
goto not_extensible;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!p->extensible) {
|
|
not_extensible:
|
|
return JS_ThrowTypeErrorOrFalse(ctx, flags, "object is not extensible");
|
|
}
|
|
|
|
if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) {
|
|
prop_flags = (flags & (JS_PROP_CONFIGURABLE | JS_PROP_ENUMERABLE)) |
|
|
JS_PROP_GETSET;
|
|
} else {
|
|
prop_flags = flags & JS_PROP_C_W_E;
|
|
}
|
|
pr = add_property(ctx, p, prop, prop_flags);
|
|
if (unlikely(!pr))
|
|
return -1;
|
|
if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) {
|
|
pr->u.getset.getter = NULL;
|
|
if ((flags & JS_PROP_HAS_GET) && JS_IsFunction(ctx, getter)) {
|
|
pr->u.getset.getter =
|
|
JS_VALUE_GET_OBJ(js_dup(getter));
|
|
}
|
|
pr->u.getset.setter = NULL;
|
|
if ((flags & JS_PROP_HAS_SET) && JS_IsFunction(ctx, setter)) {
|
|
pr->u.getset.setter =
|
|
JS_VALUE_GET_OBJ(js_dup(setter));
|
|
}
|
|
} else {
|
|
if (flags & JS_PROP_HAS_VALUE) {
|
|
pr->u.value = js_dup(val);
|
|
} else {
|
|
pr->u.value = JS_UNDEFINED;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/* return FALSE if not OK */
|
|
static BOOL check_define_prop_flags(int prop_flags, int flags)
|
|
{
|
|
BOOL has_accessor, is_getset;
|
|
|
|
if (!(prop_flags & JS_PROP_CONFIGURABLE)) {
|
|
if ((flags & (JS_PROP_HAS_CONFIGURABLE | JS_PROP_CONFIGURABLE)) ==
|
|
(JS_PROP_HAS_CONFIGURABLE | JS_PROP_CONFIGURABLE)) {
|
|
return FALSE;
|
|
}
|
|
if ((flags & JS_PROP_HAS_ENUMERABLE) &&
|
|
(flags & JS_PROP_ENUMERABLE) != (prop_flags & JS_PROP_ENUMERABLE))
|
|
return FALSE;
|
|
}
|
|
if (flags & (JS_PROP_HAS_VALUE | JS_PROP_HAS_WRITABLE |
|
|
JS_PROP_HAS_GET | JS_PROP_HAS_SET)) {
|
|
if (!(prop_flags & JS_PROP_CONFIGURABLE)) {
|
|
has_accessor = ((flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) != 0);
|
|
is_getset = ((prop_flags & JS_PROP_TMASK) == JS_PROP_GETSET);
|
|
if (has_accessor != is_getset)
|
|
return FALSE;
|
|
if (!has_accessor && !is_getset && !(prop_flags & JS_PROP_WRITABLE)) {
|
|
/* not writable: cannot set the writable bit */
|
|
if ((flags & (JS_PROP_HAS_WRITABLE | JS_PROP_WRITABLE)) ==
|
|
(JS_PROP_HAS_WRITABLE | JS_PROP_WRITABLE))
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/* ensure that the shape can be safely modified */
|
|
static int js_shape_prepare_update(JSContext *ctx, JSObject *p,
|
|
JSShapeProperty **pprs)
|
|
{
|
|
JSShape *sh;
|
|
uint32_t idx = 0; /* prevent warning */
|
|
|
|
sh = p->shape;
|
|
if (sh->is_hashed) {
|
|
if (sh->header.ref_count != 1) {
|
|
if (pprs)
|
|
idx = *pprs - get_shape_prop(sh);
|
|
/* clone the shape (the resulting one is no longer hashed) */
|
|
sh = js_clone_shape(ctx, sh);
|
|
if (!sh)
|
|
return -1;
|
|
js_free_shape(ctx->rt, p->shape);
|
|
p->shape = sh;
|
|
if (pprs)
|
|
*pprs = get_shape_prop(sh) + idx;
|
|
} else {
|
|
js_shape_hash_unlink(ctx->rt, sh);
|
|
sh->is_hashed = FALSE;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int js_update_property_flags(JSContext *ctx, JSObject *p,
|
|
JSShapeProperty **pprs, int flags)
|
|
{
|
|
if (flags != (*pprs)->flags) {
|
|
if (js_shape_prepare_update(ctx, p, pprs))
|
|
return -1;
|
|
(*pprs)->flags = flags;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* allowed flags:
|
|
JS_PROP_CONFIGURABLE, JS_PROP_WRITABLE, JS_PROP_ENUMERABLE
|
|
JS_PROP_HAS_GET, JS_PROP_HAS_SET, JS_PROP_HAS_VALUE,
|
|
JS_PROP_HAS_CONFIGURABLE, JS_PROP_HAS_WRITABLE, JS_PROP_HAS_ENUMERABLE,
|
|
JS_PROP_THROW, JS_PROP_NO_EXOTIC.
|
|
If JS_PROP_THROW is set, return an exception instead of FALSE.
|
|
if JS_PROP_NO_EXOTIC is set, do not call the exotic
|
|
define_own_property callback.
|
|
return -1 (exception), FALSE or TRUE.
|
|
*/
|
|
int JS_DefineProperty(JSContext *ctx, JSValue this_obj,
|
|
JSAtom prop, JSValue val,
|
|
JSValue getter, JSValue setter, int flags)
|
|
{
|
|
JSObject *p;
|
|
JSShapeProperty *prs;
|
|
JSProperty *pr;
|
|
int mask, res;
|
|
|
|
if (JS_VALUE_GET_TAG(this_obj) != JS_TAG_OBJECT) {
|
|
JS_ThrowTypeErrorNotAnObject(ctx);
|
|
return -1;
|
|
}
|
|
p = JS_VALUE_GET_OBJ(this_obj);
|
|
|
|
redo_prop_update:
|
|
prs = find_own_property(&pr, p, prop);
|
|
if (prs) {
|
|
/* the range of the Array length property is always tested before */
|
|
if ((prs->flags & JS_PROP_LENGTH) && (flags & JS_PROP_HAS_VALUE)) {
|
|
uint32_t array_length;
|
|
if (JS_ToArrayLengthFree(ctx, &array_length,
|
|
js_dup(val), FALSE)) {
|
|
return -1;
|
|
}
|
|
/* this code relies on the fact that Uint32 are never allocated */
|
|
val = js_uint32(array_length);
|
|
/* prs may have been modified */
|
|
prs = find_own_property(&pr, p, prop);
|
|
assert(prs != NULL);
|
|
}
|
|
/* property already exists */
|
|
if (!check_define_prop_flags(prs->flags, flags)) {
|
|
not_configurable:
|
|
return JS_ThrowTypeErrorOrFalse(ctx, flags, "property is not configurable");
|
|
}
|
|
|
|
if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) {
|
|
/* Instantiate property and retry */
|
|
if (JS_AutoInitProperty(ctx, p, prop, pr, prs))
|
|
return -1;
|
|
goto redo_prop_update;
|
|
}
|
|
|
|
if (flags & (JS_PROP_HAS_VALUE | JS_PROP_HAS_WRITABLE |
|
|
JS_PROP_HAS_GET | JS_PROP_HAS_SET)) {
|
|
if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) {
|
|
JSObject *new_getter, *new_setter;
|
|
|
|
if (JS_IsFunction(ctx, getter)) {
|
|
new_getter = JS_VALUE_GET_OBJ(getter);
|
|
} else {
|
|
new_getter = NULL;
|
|
}
|
|
if (JS_IsFunction(ctx, setter)) {
|
|
new_setter = JS_VALUE_GET_OBJ(setter);
|
|
} else {
|
|
new_setter = NULL;
|
|
}
|
|
|
|
if ((prs->flags & JS_PROP_TMASK) != JS_PROP_GETSET) {
|
|
if (js_shape_prepare_update(ctx, p, &prs))
|
|
return -1;
|
|
/* convert to getset */
|
|
if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) {
|
|
free_var_ref(ctx->rt, pr->u.var_ref);
|
|
} else {
|
|
JS_FreeValue(ctx, pr->u.value);
|
|
}
|
|
prs->flags = (prs->flags &
|
|
(JS_PROP_CONFIGURABLE | JS_PROP_ENUMERABLE)) |
|
|
JS_PROP_GETSET;
|
|
pr->u.getset.getter = NULL;
|
|
pr->u.getset.setter = NULL;
|
|
} else {
|
|
if (!(prs->flags & JS_PROP_CONFIGURABLE)) {
|
|
if ((flags & JS_PROP_HAS_GET) &&
|
|
new_getter != pr->u.getset.getter) {
|
|
goto not_configurable;
|
|
}
|
|
if ((flags & JS_PROP_HAS_SET) &&
|
|
new_setter != pr->u.getset.setter) {
|
|
goto not_configurable;
|
|
}
|
|
}
|
|
}
|
|
if (flags & JS_PROP_HAS_GET) {
|
|
if (pr->u.getset.getter)
|
|
JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.getter));
|
|
if (new_getter)
|
|
js_dup(getter);
|
|
pr->u.getset.getter = new_getter;
|
|
}
|
|
if (flags & JS_PROP_HAS_SET) {
|
|
if (pr->u.getset.setter)
|
|
JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.setter));
|
|
if (new_setter)
|
|
js_dup(setter);
|
|
pr->u.getset.setter = new_setter;
|
|
}
|
|
} else {
|
|
if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) {
|
|
/* convert to data descriptor */
|
|
if (js_shape_prepare_update(ctx, p, &prs))
|
|
return -1;
|
|
if (pr->u.getset.getter)
|
|
JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.getter));
|
|
if (pr->u.getset.setter)
|
|
JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.setter));
|
|
prs->flags &= ~(JS_PROP_TMASK | JS_PROP_WRITABLE);
|
|
pr->u.value = JS_UNDEFINED;
|
|
} else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) {
|
|
/* Note: JS_PROP_VARREF is always writable */
|
|
} else {
|
|
if ((prs->flags & (JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE)) == 0 &&
|
|
(flags & JS_PROP_HAS_VALUE)) {
|
|
if (!js_same_value(ctx, val, pr->u.value)) {
|
|
goto not_configurable;
|
|
} else {
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) {
|
|
if (flags & JS_PROP_HAS_VALUE) {
|
|
if (p->class_id == JS_CLASS_MODULE_NS) {
|
|
/* JS_PROP_WRITABLE is always true for variable
|
|
references, but they are write protected in module name
|
|
spaces. */
|
|
if (!js_same_value(ctx, val, *pr->u.var_ref->pvalue))
|
|
goto not_configurable;
|
|
}
|
|
/* update the reference */
|
|
set_value(ctx, pr->u.var_ref->pvalue,
|
|
js_dup(val));
|
|
}
|
|
/* if writable is set to false, no longer a
|
|
reference (for mapped arguments) */
|
|
if ((flags & (JS_PROP_HAS_WRITABLE | JS_PROP_WRITABLE)) == JS_PROP_HAS_WRITABLE) {
|
|
JSValue val1;
|
|
if (js_shape_prepare_update(ctx, p, &prs))
|
|
return -1;
|
|
val1 = js_dup(*pr->u.var_ref->pvalue);
|
|
free_var_ref(ctx->rt, pr->u.var_ref);
|
|
pr->u.value = val1;
|
|
prs->flags &= ~(JS_PROP_TMASK | JS_PROP_WRITABLE);
|
|
}
|
|
} else if (prs->flags & JS_PROP_LENGTH) {
|
|
if (flags & JS_PROP_HAS_VALUE) {
|
|
/* Note: no JS code is executable because
|
|
'val' is guaranted to be a Uint32 */
|
|
res = set_array_length(ctx, p, js_dup(val),
|
|
flags);
|
|
} else {
|
|
res = TRUE;
|
|
}
|
|
/* still need to reset the writable flag if
|
|
needed. The JS_PROP_LENGTH is kept because the
|
|
Uint32 test is still done if the length
|
|
property is read-only. */
|
|
if ((flags & (JS_PROP_HAS_WRITABLE | JS_PROP_WRITABLE)) ==
|
|
JS_PROP_HAS_WRITABLE) {
|
|
prs = get_shape_prop(p->shape);
|
|
if (js_update_property_flags(ctx, p, &prs,
|
|
prs->flags & ~JS_PROP_WRITABLE))
|
|
return -1;
|
|
}
|
|
return res;
|
|
} else {
|
|
if (flags & JS_PROP_HAS_VALUE) {
|
|
JS_FreeValue(ctx, pr->u.value);
|
|
pr->u.value = js_dup(val);
|
|
}
|
|
if (flags & JS_PROP_HAS_WRITABLE) {
|
|
if (js_update_property_flags(ctx, p, &prs,
|
|
(prs->flags & ~JS_PROP_WRITABLE) |
|
|
(flags & JS_PROP_WRITABLE)))
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
mask = 0;
|
|
if (flags & JS_PROP_HAS_CONFIGURABLE)
|
|
mask |= JS_PROP_CONFIGURABLE;
|
|
if (flags & JS_PROP_HAS_ENUMERABLE)
|
|
mask |= JS_PROP_ENUMERABLE;
|
|
if (js_update_property_flags(ctx, p, &prs,
|
|
(prs->flags & ~mask) | (flags & mask)))
|
|
return -1;
|
|
return TRUE;
|
|
}
|
|
|
|
/* handle modification of fast array elements */
|
|
if (p->fast_array) {
|
|
uint32_t idx;
|
|
uint32_t prop_flags;
|
|
if (p->class_id == JS_CLASS_ARRAY) {
|
|
if (__JS_AtomIsTaggedInt(prop)) {
|
|
idx = __JS_AtomToUInt32(prop);
|
|
if (idx < p->u.array.count) {
|
|
prop_flags = get_prop_flags(flags, JS_PROP_C_W_E);
|
|
if (prop_flags != JS_PROP_C_W_E)
|
|
goto convert_to_slow_array;
|
|
if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) {
|
|
convert_to_slow_array:
|
|
if (convert_fast_array_to_array(ctx, p))
|
|
return -1;
|
|
else
|
|
goto redo_prop_update;
|
|
}
|
|
if (flags & JS_PROP_HAS_VALUE) {
|
|
set_value(ctx, &p->u.array.u.values[idx], js_dup(val));
|
|
}
|
|
return TRUE;
|
|
}
|
|
}
|
|
} else if (p->class_id >= JS_CLASS_UINT8C_ARRAY &&
|
|
p->class_id <= JS_CLASS_FLOAT64_ARRAY) {
|
|
JSValue num;
|
|
int ret;
|
|
|
|
if (!__JS_AtomIsTaggedInt(prop)) {
|
|
/* slow path with to handle all numeric indexes */
|
|
num = JS_AtomIsNumericIndex1(ctx, prop);
|
|
if (JS_IsUndefined(num))
|
|
goto typed_array_done;
|
|
if (JS_IsException(num))
|
|
return -1;
|
|
ret = JS_NumberIsInteger(ctx, num);
|
|
if (ret < 0) {
|
|
JS_FreeValue(ctx, num);
|
|
return -1;
|
|
}
|
|
if (!ret) {
|
|
JS_FreeValue(ctx, num);
|
|
return JS_ThrowTypeErrorOrFalse(ctx, flags, "non integer index in typed array");
|
|
}
|
|
ret = JS_NumberIsNegativeOrMinusZero(ctx, num);
|
|
JS_FreeValue(ctx, num);
|
|
if (ret) {
|
|
return JS_ThrowTypeErrorOrFalse(ctx, flags, "negative index in typed array");
|
|
}
|
|
if (!__JS_AtomIsTaggedInt(prop))
|
|
goto typed_array_oob;
|
|
}
|
|
idx = __JS_AtomToUInt32(prop);
|
|
/* if the typed array is detached, p->u.array.count = 0 */
|
|
if (idx >= typed_array_get_length(ctx, p)) {
|
|
typed_array_oob:
|
|
return JS_ThrowTypeErrorOrFalse(ctx, flags, "out-of-bound index in typed array");
|
|
}
|
|
prop_flags = get_prop_flags(flags, JS_PROP_ENUMERABLE | JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
|
|
if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET) ||
|
|
prop_flags != (JS_PROP_ENUMERABLE | JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE)) {
|
|
return JS_ThrowTypeErrorOrFalse(ctx, flags, "invalid descriptor flags");
|
|
}
|
|
if (flags & JS_PROP_HAS_VALUE) {
|
|
return JS_SetPropertyValue(ctx, this_obj, js_int32(idx), js_dup(val), flags);
|
|
}
|
|
return TRUE;
|
|
typed_array_done: ;
|
|
}
|
|
}
|
|
|
|
return JS_CreateProperty(ctx, p, prop, val, getter, setter, flags);
|
|
}
|
|
|
|
static int JS_DefineAutoInitProperty(JSContext *ctx, JSValue this_obj,
|
|
JSAtom prop, JSAutoInitIDEnum id,
|
|
void *opaque, int flags)
|
|
{
|
|
JSObject *p;
|
|
JSProperty *pr;
|
|
|
|
if (JS_VALUE_GET_TAG(this_obj) != JS_TAG_OBJECT)
|
|
return FALSE;
|
|
|
|
p = JS_VALUE_GET_OBJ(this_obj);
|
|
|
|
if (find_own_property(&pr, p, prop)) {
|
|
/* property already exists */
|
|
abort();
|
|
return FALSE;
|
|
}
|
|
|
|
/* Specialized CreateProperty */
|
|
pr = add_property(ctx, p, prop, (flags & JS_PROP_C_W_E) | JS_PROP_AUTOINIT);
|
|
if (unlikely(!pr))
|
|
return -1;
|
|
pr->u.init.realm_and_id = (uintptr_t)JS_DupContext(ctx);
|
|
assert((pr->u.init.realm_and_id & 3) == 0);
|
|
assert(id <= 3);
|
|
pr->u.init.realm_and_id |= id;
|
|
pr->u.init.opaque = opaque;
|
|
return TRUE;
|
|
}
|
|
|
|
/* shortcut to add or redefine a new property value */
|
|
int JS_DefinePropertyValue(JSContext *ctx, JSValue this_obj,
|
|
JSAtom prop, JSValue val, int flags)
|
|
{
|
|
int ret;
|
|
ret = JS_DefineProperty(ctx, this_obj, prop, val, JS_UNDEFINED, JS_UNDEFINED,
|
|
flags | JS_PROP_HAS_VALUE | JS_PROP_HAS_CONFIGURABLE | JS_PROP_HAS_WRITABLE | JS_PROP_HAS_ENUMERABLE);
|
|
JS_FreeValue(ctx, val);
|
|
return ret;
|
|
}
|
|
|
|
int JS_DefinePropertyValueValue(JSContext *ctx, JSValue this_obj,
|
|
JSValue prop, JSValue val, int flags)
|
|
{
|
|
JSAtom atom;
|
|
int ret;
|
|
atom = JS_ValueToAtom(ctx, prop);
|
|
JS_FreeValue(ctx, prop);
|
|
if (unlikely(atom == JS_ATOM_NULL)) {
|
|
JS_FreeValue(ctx, val);
|
|
return -1;
|
|
}
|
|
ret = JS_DefinePropertyValue(ctx, this_obj, atom, val, flags);
|
|
JS_FreeAtom(ctx, atom);
|
|
return ret;
|
|
}
|
|
|
|
int JS_DefinePropertyValueUint32(JSContext *ctx, JSValue this_obj,
|
|
uint32_t idx, JSValue val, int flags)
|
|
{
|
|
return JS_DefinePropertyValueValue(ctx, this_obj, js_uint32(idx),
|
|
val, flags);
|
|
}
|
|
|
|
int JS_DefinePropertyValueInt64(JSContext *ctx, JSValue this_obj,
|
|
int64_t idx, JSValue val, int flags)
|
|
{
|
|
return JS_DefinePropertyValueValue(ctx, this_obj, JS_NewInt64(ctx, idx),
|
|
val, flags);
|
|
}
|
|
|
|
/* `prop` may be pure ASCII or UTF-8 encoded */
|
|
int JS_DefinePropertyValueStr(JSContext *ctx, JSValue this_obj,
|
|
const char *prop, JSValue val, int flags)
|
|
{
|
|
JSAtom atom;
|
|
int ret;
|
|
atom = JS_NewAtom(ctx, prop);
|
|
ret = JS_DefinePropertyValue(ctx, this_obj, atom, val, flags);
|
|
JS_FreeAtom(ctx, atom);
|
|
return ret;
|
|
}
|
|
|
|
/* shortcut to add getter & setter */
|
|
int JS_DefinePropertyGetSet(JSContext *ctx, JSValue this_obj,
|
|
JSAtom prop, JSValue getter, JSValue setter,
|
|
int flags)
|
|
{
|
|
int ret;
|
|
ret = JS_DefineProperty(ctx, this_obj, prop, JS_UNDEFINED, getter, setter,
|
|
flags | JS_PROP_HAS_GET | JS_PROP_HAS_SET |
|
|
JS_PROP_HAS_CONFIGURABLE | JS_PROP_HAS_ENUMERABLE);
|
|
JS_FreeValue(ctx, getter);
|
|
JS_FreeValue(ctx, setter);
|
|
return ret;
|
|
}
|
|
|
|
static int JS_CreateDataPropertyUint32(JSContext *ctx, JSValue this_obj,
|
|
int64_t idx, JSValue val, int flags)
|
|
{
|
|
return JS_DefinePropertyValueValue(ctx, this_obj, JS_NewInt64(ctx, idx),
|
|
val, flags | JS_PROP_CONFIGURABLE |
|
|
JS_PROP_ENUMERABLE | JS_PROP_WRITABLE);
|
|
}
|
|
|
|
|
|
/* return TRUE if 'obj' has a non empty 'name' string */
|
|
static BOOL js_object_has_name(JSContext *ctx, JSValue obj)
|
|
{
|
|
JSProperty *pr;
|
|
JSShapeProperty *prs;
|
|
JSValue val;
|
|
JSString *p;
|
|
|
|
prs = find_own_property(&pr, JS_VALUE_GET_OBJ(obj), JS_ATOM_name);
|
|
if (!prs)
|
|
return FALSE;
|
|
if ((prs->flags & JS_PROP_TMASK) != JS_PROP_NORMAL)
|
|
return TRUE;
|
|
val = pr->u.value;
|
|
if (JS_VALUE_GET_TAG(val) != JS_TAG_STRING)
|
|
return TRUE;
|
|
p = JS_VALUE_GET_STRING(val);
|
|
return (p->len != 0);
|
|
}
|
|
|
|
static int JS_DefineObjectName(JSContext *ctx, JSValue obj,
|
|
JSAtom name, int flags)
|
|
{
|
|
if (name != JS_ATOM_NULL
|
|
&& JS_IsObject(obj)
|
|
&& !js_object_has_name(ctx, obj)
|
|
&& JS_DefinePropertyValue(ctx, obj, JS_ATOM_name, JS_AtomToString(ctx, name), flags) < 0) {
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int JS_DefineObjectNameComputed(JSContext *ctx, JSValue obj,
|
|
JSValue str, int flags)
|
|
{
|
|
if (JS_IsObject(obj) &&
|
|
!js_object_has_name(ctx, obj)) {
|
|
JSAtom prop;
|
|
JSValue name_str;
|
|
prop = JS_ValueToAtom(ctx, str);
|
|
if (prop == JS_ATOM_NULL)
|
|
return -1;
|
|
name_str = js_get_function_name(ctx, prop);
|
|
JS_FreeAtom(ctx, prop);
|
|
if (JS_IsException(name_str))
|
|
return -1;
|
|
if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_name, name_str, flags) < 0)
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#define DEFINE_GLOBAL_LEX_VAR (1 << 7)
|
|
#define DEFINE_GLOBAL_FUNC_VAR (1 << 6)
|
|
|
|
static JSValue JS_ThrowSyntaxErrorVarRedeclaration(JSContext *ctx, JSAtom prop)
|
|
{
|
|
return JS_ThrowSyntaxErrorAtom(ctx, "redeclaration of '%s'", prop);
|
|
}
|
|
|
|
/* flags is 0, DEFINE_GLOBAL_LEX_VAR or DEFINE_GLOBAL_FUNC_VAR */
|
|
/* XXX: could support exotic global object. */
|
|
static int JS_CheckDefineGlobalVar(JSContext *ctx, JSAtom prop, int flags)
|
|
{
|
|
JSObject *p;
|
|
JSShapeProperty *prs;
|
|
|
|
p = JS_VALUE_GET_OBJ(ctx->global_obj);
|
|
prs = find_own_property1(p, prop);
|
|
/* XXX: should handle JS_PROP_AUTOINIT */
|
|
if (flags & DEFINE_GLOBAL_LEX_VAR) {
|
|
if (prs && !(prs->flags & JS_PROP_CONFIGURABLE))
|
|
goto fail_redeclaration;
|
|
} else {
|
|
if (!prs && !p->extensible)
|
|
goto define_error;
|
|
if (flags & DEFINE_GLOBAL_FUNC_VAR) {
|
|
if (prs) {
|
|
if (!(prs->flags & JS_PROP_CONFIGURABLE) &&
|
|
((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET ||
|
|
((prs->flags & (JS_PROP_WRITABLE | JS_PROP_ENUMERABLE)) !=
|
|
(JS_PROP_WRITABLE | JS_PROP_ENUMERABLE)))) {
|
|
define_error:
|
|
JS_ThrowTypeErrorAtom(ctx, "cannot define variable '%s'",
|
|
prop);
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/* check if there already is a lexical declaration */
|
|
p = JS_VALUE_GET_OBJ(ctx->global_var_obj);
|
|
prs = find_own_property1(p, prop);
|
|
if (prs) {
|
|
fail_redeclaration:
|
|
JS_ThrowSyntaxErrorVarRedeclaration(ctx, prop);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* def_flags is (0, DEFINE_GLOBAL_LEX_VAR) |
|
|
JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE */
|
|
/* XXX: could support exotic global object. */
|
|
static int JS_DefineGlobalVar(JSContext *ctx, JSAtom prop, int def_flags)
|
|
{
|
|
JSObject *p;
|
|
JSShapeProperty *prs;
|
|
JSProperty *pr;
|
|
JSValue val;
|
|
int flags;
|
|
|
|
if (def_flags & DEFINE_GLOBAL_LEX_VAR) {
|
|
p = JS_VALUE_GET_OBJ(ctx->global_var_obj);
|
|
flags = JS_PROP_ENUMERABLE | (def_flags & JS_PROP_WRITABLE) |
|
|
JS_PROP_CONFIGURABLE;
|
|
val = JS_UNINITIALIZED;
|
|
} else {
|
|
p = JS_VALUE_GET_OBJ(ctx->global_obj);
|
|
flags = JS_PROP_ENUMERABLE | JS_PROP_WRITABLE |
|
|
(def_flags & JS_PROP_CONFIGURABLE);
|
|
val = JS_UNDEFINED;
|
|
}
|
|
prs = find_own_property1(p, prop);
|
|
if (prs)
|
|
return 0;
|
|
if (!p->extensible)
|
|
return 0;
|
|
pr = add_property(ctx, p, prop, flags);
|
|
if (unlikely(!pr))
|
|
return -1;
|
|
pr->u.value = val;
|
|
return 0;
|
|
}
|
|
|
|
/* 'def_flags' is 0 or JS_PROP_CONFIGURABLE. */
|
|
/* XXX: could support exotic global object. */
|
|
static int JS_DefineGlobalFunction(JSContext *ctx, JSAtom prop,
|
|
JSValue func, int def_flags)
|
|
{
|
|
|
|
JSObject *p;
|
|
JSShapeProperty *prs;
|
|
int flags;
|
|
|
|
p = JS_VALUE_GET_OBJ(ctx->global_obj);
|
|
prs = find_own_property1(p, prop);
|
|
flags = JS_PROP_HAS_VALUE | JS_PROP_THROW;
|
|
if (!prs || (prs->flags & JS_PROP_CONFIGURABLE)) {
|
|
flags |= JS_PROP_ENUMERABLE | JS_PROP_WRITABLE | def_flags |
|
|
JS_PROP_HAS_CONFIGURABLE | JS_PROP_HAS_WRITABLE | JS_PROP_HAS_ENUMERABLE;
|
|
}
|
|
if (JS_DefineProperty(ctx, ctx->global_obj, prop, func,
|
|
JS_UNDEFINED, JS_UNDEFINED, flags) < 0)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
static JSValue JS_GetGlobalVar(JSContext *ctx, JSAtom prop,
|
|
BOOL throw_ref_error)
|
|
{
|
|
JSObject *p;
|
|
JSShapeProperty *prs;
|
|
JSProperty *pr;
|
|
|
|
/* no exotic behavior is possible in global_var_obj */
|
|
p = JS_VALUE_GET_OBJ(ctx->global_var_obj);
|
|
prs = find_own_property(&pr, p, prop);
|
|
if (prs) {
|
|
/* XXX: should handle JS_PROP_TMASK properties */
|
|
if (unlikely(JS_IsUninitialized(pr->u.value)))
|
|
return JS_ThrowReferenceErrorUninitialized(ctx, prs->atom);
|
|
return js_dup(pr->u.value);
|
|
}
|
|
return JS_GetPropertyInternal(ctx, ctx->global_obj, prop,
|
|
ctx->global_obj, throw_ref_error);
|
|
}
|
|
|
|
/* construct a reference to a global variable */
|
|
static int JS_GetGlobalVarRef(JSContext *ctx, JSAtom prop, JSValue *sp)
|
|
{
|
|
JSObject *p;
|
|
JSShapeProperty *prs;
|
|
JSProperty *pr;
|
|
|
|
/* no exotic behavior is possible in global_var_obj */
|
|
p = JS_VALUE_GET_OBJ(ctx->global_var_obj);
|
|
prs = find_own_property(&pr, p, prop);
|
|
if (prs) {
|
|
/* XXX: should handle JS_PROP_AUTOINIT properties? */
|
|
/* XXX: conformance: do these tests in
|
|
OP_put_var_ref/OP_get_var_ref ? */
|
|
if (unlikely(JS_IsUninitialized(pr->u.value))) {
|
|
JS_ThrowReferenceErrorUninitialized(ctx, prs->atom);
|
|
return -1;
|
|
}
|
|
if (unlikely(!(prs->flags & JS_PROP_WRITABLE))) {
|
|
return JS_ThrowTypeErrorReadOnly(ctx, JS_PROP_THROW, prop);
|
|
}
|
|
sp[0] = js_dup(ctx->global_var_obj);
|
|
} else {
|
|
int ret;
|
|
ret = JS_HasProperty(ctx, ctx->global_obj, prop);
|
|
if (ret < 0)
|
|
return -1;
|
|
if (ret) {
|
|
sp[0] = js_dup(ctx->global_obj);
|
|
} else {
|
|
sp[0] = JS_UNDEFINED;
|
|
}
|
|
}
|
|
sp[1] = JS_AtomToValue(ctx, prop);
|
|
return 0;
|
|
}
|
|
|
|
/* use for strict variable access: test if the variable exists */
|
|
static int JS_CheckGlobalVar(JSContext *ctx, JSAtom prop)
|
|
{
|
|
JSObject *p;
|
|
JSShapeProperty *prs;
|
|
int ret;
|
|
|
|
/* no exotic behavior is possible in global_var_obj */
|
|
p = JS_VALUE_GET_OBJ(ctx->global_var_obj);
|
|
prs = find_own_property1(p, prop);
|
|
if (prs) {
|
|
ret = TRUE;
|
|
} else {
|
|
ret = JS_HasProperty(ctx, ctx->global_obj, prop);
|
|
if (ret < 0)
|
|
return -1;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/* flag = 0: normal variable write
|
|
flag = 1: initialize lexical variable
|
|
flag = 2: normal variable write, strict check was done before
|
|
*/
|
|
static int JS_SetGlobalVar(JSContext *ctx, JSAtom prop, JSValue val,
|
|
int flag)
|
|
{
|
|
JSObject *p;
|
|
JSShapeProperty *prs;
|
|
JSProperty *pr;
|
|
int flags;
|
|
|
|
/* no exotic behavior is possible in global_var_obj */
|
|
p = JS_VALUE_GET_OBJ(ctx->global_var_obj);
|
|
prs = find_own_property(&pr, p, prop);
|
|
if (prs) {
|
|
/* XXX: should handle JS_PROP_AUTOINIT properties? */
|
|
if (flag != 1) {
|
|
if (unlikely(JS_IsUninitialized(pr->u.value))) {
|
|
JS_FreeValue(ctx, val);
|
|
JS_ThrowReferenceErrorUninitialized(ctx, prs->atom);
|
|
return -1;
|
|
}
|
|
if (unlikely(!(prs->flags & JS_PROP_WRITABLE))) {
|
|
JS_FreeValue(ctx, val);
|
|
return JS_ThrowTypeErrorReadOnly(ctx, JS_PROP_THROW, prop);
|
|
}
|
|
}
|
|
set_value(ctx, &pr->u.value, val);
|
|
return 0;
|
|
}
|
|
flags = JS_PROP_THROW_STRICT;
|
|
if (is_strict_mode(ctx))
|
|
flags |= JS_PROP_NO_ADD;
|
|
return JS_SetPropertyInternal(ctx, ctx->global_obj, prop, val, flags);
|
|
}
|
|
|
|
/* return -1, FALSE or TRUE. return FALSE if not configurable or
|
|
invalid object. return -1 in case of exception.
|
|
flags can be 0, JS_PROP_THROW or JS_PROP_THROW_STRICT */
|
|
int JS_DeleteProperty(JSContext *ctx, JSValue obj, JSAtom prop, int flags)
|
|
{
|
|
JSValue obj1;
|
|
JSObject *p;
|
|
int res;
|
|
|
|
obj1 = JS_ToObject(ctx, obj);
|
|
if (JS_IsException(obj1))
|
|
return -1;
|
|
p = JS_VALUE_GET_OBJ(obj1);
|
|
res = delete_property(ctx, p, prop);
|
|
JS_FreeValue(ctx, obj1);
|
|
if (res != FALSE)
|
|
return res;
|
|
if ((flags & JS_PROP_THROW) ||
|
|
((flags & JS_PROP_THROW_STRICT) && is_strict_mode(ctx))) {
|
|
JS_ThrowTypeError(ctx, "could not delete property");
|
|
return -1;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
int JS_DeletePropertyInt64(JSContext *ctx, JSValue obj, int64_t idx, int flags)
|
|
{
|
|
JSAtom prop;
|
|
int res;
|
|
|
|
if ((uint64_t)idx <= JS_ATOM_MAX_INT) {
|
|
/* fast path for fast arrays */
|
|
return JS_DeleteProperty(ctx, obj, __JS_AtomFromUInt32(idx), flags);
|
|
}
|
|
prop = JS_NewAtomInt64(ctx, idx);
|
|
if (prop == JS_ATOM_NULL)
|
|
return -1;
|
|
res = JS_DeleteProperty(ctx, obj, prop, flags);
|
|
JS_FreeAtom(ctx, prop);
|
|
return res;
|
|
}
|
|
|
|
BOOL JS_IsFunction(JSContext *ctx, JSValue val)
|
|
{
|
|
JSObject *p;
|
|
if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT)
|
|
return FALSE;
|
|
p = JS_VALUE_GET_OBJ(val);
|
|
switch(p->class_id) {
|
|
case JS_CLASS_BYTECODE_FUNCTION:
|
|
return TRUE;
|
|
case JS_CLASS_PROXY:
|
|
return p->u.proxy_data->is_func;
|
|
default:
|
|
return (ctx->rt->class_array[p->class_id].call != NULL);
|
|
}
|
|
}
|
|
|
|
BOOL JS_IsCFunction(JSContext *ctx, JSValue val, JSCFunction *func, int magic)
|
|
{
|
|
JSObject *p;
|
|
if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT)
|
|
return FALSE;
|
|
p = JS_VALUE_GET_OBJ(val);
|
|
if (p->class_id == JS_CLASS_C_FUNCTION)
|
|
return (p->u.cfunc.c_function.generic == func && p->u.cfunc.magic == magic);
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL JS_IsConstructor(JSContext *ctx, JSValue val)
|
|
{
|
|
JSObject *p;
|
|
if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT)
|
|
return FALSE;
|
|
p = JS_VALUE_GET_OBJ(val);
|
|
return p->is_constructor;
|
|
}
|
|
|
|
BOOL JS_SetConstructorBit(JSContext *ctx, JSValue func_obj, BOOL val)
|
|
{
|
|
JSObject *p;
|
|
if (JS_VALUE_GET_TAG(func_obj) != JS_TAG_OBJECT)
|
|
return FALSE;
|
|
p = JS_VALUE_GET_OBJ(func_obj);
|
|
p->is_constructor = val;
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL JS_IsError(JSContext *ctx, JSValue val)
|
|
{
|
|
JSObject *p;
|
|
if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT)
|
|
return FALSE;
|
|
p = JS_VALUE_GET_OBJ(val);
|
|
return (p->class_id == JS_CLASS_ERROR);
|
|
}
|
|
|
|
/* used to avoid catching interrupt exceptions */
|
|
BOOL JS_IsUncatchableError(JSContext *ctx, JSValue val)
|
|
{
|
|
JSObject *p;
|
|
if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT)
|
|
return FALSE;
|
|
p = JS_VALUE_GET_OBJ(val);
|
|
return p->class_id == JS_CLASS_ERROR && p->is_uncatchable_error;
|
|
}
|
|
|
|
void JS_SetUncatchableError(JSContext *ctx, JSValue val, BOOL flag)
|
|
{
|
|
JSObject *p;
|
|
if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT)
|
|
return;
|
|
p = JS_VALUE_GET_OBJ(val);
|
|
if (p->class_id == JS_CLASS_ERROR)
|
|
p->is_uncatchable_error = flag;
|
|
}
|
|
|
|
void JS_ResetUncatchableError(JSContext *ctx)
|
|
{
|
|
JS_SetUncatchableError(ctx, ctx->rt->current_exception, FALSE);
|
|
}
|
|
|
|
void JS_SetOpaque(JSValue obj, void *opaque)
|
|
{
|
|
JSObject *p;
|
|
if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) {
|
|
p = JS_VALUE_GET_OBJ(obj);
|
|
p->u.opaque = opaque;
|
|
}
|
|
}
|
|
|
|
/* return NULL if not an object of class class_id */
|
|
void *JS_GetOpaque(JSValue obj, JSClassID class_id)
|
|
{
|
|
JSObject *p;
|
|
if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)
|
|
return NULL;
|
|
p = JS_VALUE_GET_OBJ(obj);
|
|
if (p->class_id != class_id)
|
|
return NULL;
|
|
return p->u.opaque;
|
|
}
|
|
|
|
void *JS_GetOpaque2(JSContext *ctx, JSValue obj, JSClassID class_id)
|
|
{
|
|
void *p = JS_GetOpaque(obj, class_id);
|
|
if (unlikely(!p)) {
|
|
JS_ThrowTypeErrorInvalidClass(ctx, class_id);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
void *JS_GetAnyOpaque(JSValue obj, JSClassID *class_id)
|
|
{
|
|
JSObject *p;
|
|
if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) {
|
|
*class_id = 0;
|
|
return NULL;
|
|
}
|
|
p = JS_VALUE_GET_OBJ(obj);
|
|
*class_id = p->class_id;
|
|
return p->u.opaque;
|
|
}
|
|
|
|
static JSValue JS_ToPrimitiveFree(JSContext *ctx, JSValue val, int hint)
|
|
{
|
|
int i;
|
|
BOOL force_ordinary;
|
|
|
|
JSAtom method_name;
|
|
JSValue method, ret;
|
|
if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT)
|
|
return val;
|
|
force_ordinary = hint & HINT_FORCE_ORDINARY;
|
|
hint &= ~HINT_FORCE_ORDINARY;
|
|
if (!force_ordinary) {
|
|
method = JS_GetProperty(ctx, val, JS_ATOM_Symbol_toPrimitive);
|
|
if (JS_IsException(method))
|
|
goto exception;
|
|
/* ECMA says *If exoticToPrim is not undefined* but tests in
|
|
test262 use null as a non callable converter */
|
|
if (!JS_IsUndefined(method) && !JS_IsNull(method)) {
|
|
JSAtom atom;
|
|
JSValue arg;
|
|
switch(hint) {
|
|
case HINT_STRING:
|
|
atom = JS_ATOM_string;
|
|
break;
|
|
case HINT_NUMBER:
|
|
atom = JS_ATOM_number;
|
|
break;
|
|
default:
|
|
case HINT_NONE:
|
|
atom = JS_ATOM_default;
|
|
break;
|
|
}
|
|
arg = JS_AtomToString(ctx, atom);
|
|
ret = JS_CallFree(ctx, method, val, 1, &arg);
|
|
JS_FreeValue(ctx, arg);
|
|
if (JS_IsException(ret))
|
|
goto exception;
|
|
JS_FreeValue(ctx, val);
|
|
if (JS_VALUE_GET_TAG(ret) != JS_TAG_OBJECT)
|
|
return ret;
|
|
JS_FreeValue(ctx, ret);
|
|
return JS_ThrowTypeError(ctx, "toPrimitive");
|
|
}
|
|
}
|
|
if (hint != HINT_STRING)
|
|
hint = HINT_NUMBER;
|
|
for(i = 0; i < 2; i++) {
|
|
if ((i ^ hint) == 0) {
|
|
method_name = JS_ATOM_toString;
|
|
} else {
|
|
method_name = JS_ATOM_valueOf;
|
|
}
|
|
method = JS_GetProperty(ctx, val, method_name);
|
|
if (JS_IsException(method))
|
|
goto exception;
|
|
if (JS_IsFunction(ctx, method)) {
|
|
ret = JS_CallFree(ctx, method, val, 0, NULL);
|
|
if (JS_IsException(ret))
|
|
goto exception;
|
|
if (JS_VALUE_GET_TAG(ret) != JS_TAG_OBJECT) {
|
|
JS_FreeValue(ctx, val);
|
|
return ret;
|
|
}
|
|
JS_FreeValue(ctx, ret);
|
|
} else {
|
|
JS_FreeValue(ctx, method);
|
|
}
|
|
}
|
|
JS_ThrowTypeError(ctx, "toPrimitive");
|
|
exception:
|
|
JS_FreeValue(ctx, val);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue JS_ToPrimitive(JSContext *ctx, JSValue val, int hint)
|
|
{
|
|
return JS_ToPrimitiveFree(ctx, js_dup(val), hint);
|
|
}
|
|
|
|
void JS_SetIsHTMLDDA(JSContext *ctx, JSValue obj)
|
|
{
|
|
JSObject *p;
|
|
if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)
|
|
return;
|
|
p = JS_VALUE_GET_OBJ(obj);
|
|
p->is_HTMLDDA = TRUE;
|
|
}
|
|
|
|
static inline BOOL JS_IsHTMLDDA(JSContext *ctx, JSValue obj)
|
|
{
|
|
JSObject *p;
|
|
if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)
|
|
return FALSE;
|
|
p = JS_VALUE_GET_OBJ(obj);
|
|
return p->is_HTMLDDA;
|
|
}
|
|
|
|
static int JS_ToBoolFree(JSContext *ctx, JSValue val)
|
|
{
|
|
uint32_t tag = JS_VALUE_GET_TAG(val);
|
|
switch(tag) {
|
|
case JS_TAG_INT:
|
|
return JS_VALUE_GET_INT(val) != 0;
|
|
case JS_TAG_BOOL:
|
|
case JS_TAG_NULL:
|
|
case JS_TAG_UNDEFINED:
|
|
return JS_VALUE_GET_INT(val);
|
|
case JS_TAG_EXCEPTION:
|
|
return -1;
|
|
case JS_TAG_STRING:
|
|
{
|
|
BOOL ret = JS_VALUE_GET_STRING(val)->len != 0;
|
|
JS_FreeValue(ctx, val);
|
|
return ret;
|
|
}
|
|
case JS_TAG_BIG_INT:
|
|
{
|
|
JSBigInt *p = JS_VALUE_GET_PTR(val);
|
|
BOOL ret;
|
|
ret = p->num.expn != BF_EXP_ZERO && p->num.expn != BF_EXP_NAN;
|
|
JS_FreeValue(ctx, val);
|
|
return ret;
|
|
}
|
|
case JS_TAG_OBJECT:
|
|
{
|
|
JSObject *p = JS_VALUE_GET_OBJ(val);
|
|
BOOL ret;
|
|
ret = !p->is_HTMLDDA;
|
|
JS_FreeValue(ctx, val);
|
|
return ret;
|
|
}
|
|
break;
|
|
default:
|
|
if (JS_TAG_IS_FLOAT64(tag)) {
|
|
double d = JS_VALUE_GET_FLOAT64(val);
|
|
return !isnan(d) && d != 0;
|
|
} else {
|
|
JS_FreeValue(ctx, val);
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
int JS_ToBool(JSContext *ctx, JSValue val)
|
|
{
|
|
return JS_ToBoolFree(ctx, js_dup(val));
|
|
}
|
|
|
|
/* pc points to pure ASCII or UTF-8, null terminated contents */
|
|
static int skip_spaces(const char *pc)
|
|
{
|
|
const uint8_t *p, *p_next, *p_start;
|
|
uint32_t c;
|
|
|
|
p = p_start = (const uint8_t *)pc;
|
|
for (;;) {
|
|
c = *p++;
|
|
if (c < 0x80) {
|
|
if (!((c >= 0x09 && c <= 0x0d) || (c == 0x20)))
|
|
break;
|
|
} else {
|
|
c = utf8_decode(p - 1, &p_next);
|
|
/* no need to test for invalid UTF-8, 0xFFFD is not a space */
|
|
if (!lre_is_space(c))
|
|
break;
|
|
p = p_next;
|
|
}
|
|
}
|
|
return p - 1 - p_start;
|
|
}
|
|
|
|
static inline int to_digit(int c)
|
|
{
|
|
if (c >= '0' && c <= '9')
|
|
return c - '0';
|
|
else if (c >= 'A' && c <= 'Z')
|
|
return c - 'A' + 10;
|
|
else if (c >= 'a' && c <= 'z')
|
|
return c - 'a' + 10;
|
|
else
|
|
return 36;
|
|
}
|
|
|
|
/* XXX: remove */
|
|
static double js_strtod(const char *str, int radix, BOOL is_float)
|
|
{
|
|
double d;
|
|
int c;
|
|
|
|
if (!is_float || radix != 10) {
|
|
const char *p = str;
|
|
uint64_t n_max, n;
|
|
int int_exp, is_neg;
|
|
|
|
is_neg = 0;
|
|
if (*p == '-') {
|
|
is_neg = 1;
|
|
p++;
|
|
}
|
|
|
|
/* skip leading zeros */
|
|
while (*p == '0')
|
|
p++;
|
|
n = 0;
|
|
if (radix == 10)
|
|
n_max = ((uint64_t)-1 - 9) / 10; /* most common case */
|
|
else
|
|
n_max = ((uint64_t)-1 - (radix - 1)) / radix;
|
|
/* XXX: could be more precise */
|
|
int_exp = 0;
|
|
while ((c = to_digit(*p)) < radix) {
|
|
if (n <= n_max) {
|
|
n = n * radix + c;
|
|
} else {
|
|
if (radix == 10)
|
|
goto strtod_case;
|
|
int_exp++;
|
|
}
|
|
p++;
|
|
}
|
|
d = n;
|
|
if (int_exp != 0) {
|
|
d *= pow(radix, int_exp);
|
|
}
|
|
if (is_neg)
|
|
d = -d;
|
|
} else {
|
|
strtod_case:
|
|
d = strtod(str, NULL);
|
|
}
|
|
return d;
|
|
}
|
|
|
|
static JSValue js_string_to_bigint(JSContext *ctx, const char *buf, int radix)
|
|
{
|
|
bf_t *a;
|
|
int ret;
|
|
JSValue val;
|
|
val = JS_NewBigInt(ctx);
|
|
if (JS_IsException(val))
|
|
return val;
|
|
a = JS_GetBigInt(val);
|
|
ret = bf_atof(a, buf, NULL, radix, BF_PREC_INF, BF_RNDZ);
|
|
if (ret & BF_ST_MEM_ERROR) {
|
|
JS_FreeValue(ctx, val);
|
|
return JS_ThrowOutOfMemory(ctx);
|
|
}
|
|
return JS_CompactBigInt1(ctx, val);
|
|
}
|
|
|
|
/* `js_atof(ctx, p, end, pp, radix, flags)`
|
|
Return an exception in case of memory error.
|
|
Return `JS_NAN` if invalid syntax.
|
|
- `p` points to a null terminated UTF-8 encoded char array
|
|
- `end` points to the end of the array.
|
|
- `pp` if not null receives a pointer to the next character
|
|
- `radix` must be in range 2 to 36, else return `JS_NAN`
|
|
- `flags` is a combination of the flags below
|
|
There is a null byte at `*end`, but there might be embedded null bytes
|
|
between `p` and `end` which must produce `JS_NAN` if the
|
|
`ATOD_NO_TRAILING_CHARS` flag is not present.
|
|
*/
|
|
|
|
#define ATOD_TRIM_SPACES (1 << 0) /* trim white space */
|
|
#define ATOD_ACCEPT_EMPTY (1 << 1) /* accept an empty string, value is 0 */
|
|
#define ATOD_ACCEPT_FLOAT (1 << 2) /* parse decimal floating point syntax */
|
|
#define ATOD_ACCEPT_INFINITY (1 << 3) /* parse Infinity as a float point number */
|
|
#define ATOD_ACCEPT_BIN_OCT (1 << 4) /* accept 0o and 0b prefixes */
|
|
#define ATOD_ACCEPT_HEX_PREFIX (1 << 5) /* accept 0x prefix for radix 16 */
|
|
#define ATOD_ACCEPT_UNDERSCORES (1 << 6) /* accept _ between digits as a digit separator */
|
|
#define ATOD_ACCEPT_SUFFIX (1 << 7) /* allow 'n' suffix to produce BigInt */
|
|
#define ATOD_WANT_BIG_INT (1 << 8) /* return type must be BigInt */
|
|
#define ATOD_DECIMAL_AFTER_SIGN (1 << 9) /* only accept decimal number after sign */
|
|
#define ATOD_NO_TRAILING_CHARS (1 << 10) /* do not accept trailing characters */
|
|
|
|
static JSValue js_atof(JSContext *ctx, const char *p, const char *end,
|
|
const char **pp, int radix, int flags)
|
|
{
|
|
const char *p_start;
|
|
int sep;
|
|
BOOL is_float;
|
|
char buf1[64], *buf = buf1;
|
|
size_t i, j, len;
|
|
JSValue val = JS_NAN;
|
|
double d;
|
|
char sign;
|
|
|
|
if (radix < 2 || radix > 36)
|
|
goto done;
|
|
|
|
/* optional separator between digits */
|
|
sep = (flags & ATOD_ACCEPT_UNDERSCORES) ? '_' : 256;
|
|
sign = 0;
|
|
if (flags & ATOD_TRIM_SPACES)
|
|
p += skip_spaces(p);
|
|
if (p == end && (flags & ATOD_ACCEPT_EMPTY)) {
|
|
if (pp) *pp = p;
|
|
if (flags & ATOD_WANT_BIG_INT)
|
|
return JS_NewBigInt64(ctx, 0);
|
|
else
|
|
return js_int32(0);
|
|
}
|
|
if (*p == '+' || *p == '-') {
|
|
sign = *p;
|
|
p++;
|
|
if (flags & ATOD_DECIMAL_AFTER_SIGN)
|
|
flags &= ~(ATOD_ACCEPT_HEX_PREFIX | ATOD_ACCEPT_BIN_OCT);
|
|
}
|
|
if (p[0] == '0') {
|
|
if ((p[1] == 'x' || p[1] == 'X') &&
|
|
((flags & ATOD_ACCEPT_HEX_PREFIX) || radix == 16)) {
|
|
p += 2;
|
|
radix = 16;
|
|
} else if (flags & ATOD_ACCEPT_BIN_OCT) {
|
|
if (p[1] == 'o' || p[1] == 'O') {
|
|
p += 2;
|
|
radix = 8;
|
|
} else if (p[1] == 'b' || p[1] == 'B') {
|
|
p += 2;
|
|
radix = 2;
|
|
}
|
|
}
|
|
} else {
|
|
if (*p == 'I' && (flags & ATOD_ACCEPT_INFINITY) && strstart(p, "Infinity", &p)) {
|
|
d = INF;
|
|
if (sign == '-')
|
|
d = -d;
|
|
val = js_float64(d);
|
|
goto done;
|
|
}
|
|
}
|
|
is_float = FALSE;
|
|
p_start = p;
|
|
while (to_digit(*p) < radix) {
|
|
p++;
|
|
if (*p == sep && to_digit(p[1]) < radix)
|
|
p++;
|
|
}
|
|
if ((flags & ATOD_ACCEPT_FLOAT) && radix == 10) {
|
|
if (*p == '.' && (p > p_start || to_digit(p[1]) < radix)) {
|
|
is_float = TRUE;
|
|
p++;
|
|
while (to_digit(*p) < radix) {
|
|
p++;
|
|
if (*p == sep && to_digit(p[1]) < radix)
|
|
p++;
|
|
}
|
|
}
|
|
if (p > p_start && (*p == 'e' || *p == 'E')) {
|
|
i = 1;
|
|
if (p[1] == '+' || p[1] == '-') {
|
|
i++;
|
|
}
|
|
if (is_digit(p[i])) {
|
|
is_float = TRUE;
|
|
p += i + 1;
|
|
while (is_digit(*p) || (*p == sep && is_digit(p[1])))
|
|
p++;
|
|
}
|
|
}
|
|
}
|
|
if (p == p_start)
|
|
goto done;
|
|
|
|
len = p - p_start;
|
|
if (unlikely((len + 2) > sizeof(buf1))) {
|
|
buf = js_malloc_rt(ctx->rt, len + 2); /* no exception raised */
|
|
if (!buf) {
|
|
if (pp) *pp = p;
|
|
return JS_ThrowOutOfMemory(ctx);
|
|
}
|
|
}
|
|
/* remove the separators and the radix prefix */
|
|
j = 0;
|
|
if (sign == '-')
|
|
buf[j++] = '-';
|
|
for (i = 0; i < len; i++) {
|
|
if (p_start[i] != '_')
|
|
buf[j++] = p_start[i];
|
|
}
|
|
buf[j] = '\0';
|
|
|
|
if (flags & ATOD_ACCEPT_SUFFIX) {
|
|
if (*p == 'n') {
|
|
p++;
|
|
flags |= ATOD_WANT_BIG_INT;
|
|
}
|
|
}
|
|
|
|
if (flags & ATOD_WANT_BIG_INT) {
|
|
if (!is_float)
|
|
val = js_string_to_bigint(ctx, buf, radix);
|
|
} else {
|
|
d = js_strtod(buf, radix, is_float);
|
|
val = js_number(d); /* return int or float64 */
|
|
}
|
|
|
|
done:
|
|
if (flags & ATOD_NO_TRAILING_CHARS) {
|
|
if (flags & ATOD_TRIM_SPACES)
|
|
p += skip_spaces(p);
|
|
if (p != end) {
|
|
JS_FreeValue(ctx, val);
|
|
val = JS_NAN;
|
|
}
|
|
}
|
|
if (buf != buf1)
|
|
js_free_rt(ctx->rt, buf);
|
|
if (pp) *pp = p;
|
|
return val;
|
|
}
|
|
|
|
typedef enum JSToNumberHintEnum {
|
|
TON_FLAG_NUMBER,
|
|
TON_FLAG_NUMERIC,
|
|
} JSToNumberHintEnum;
|
|
|
|
static JSValue JS_ToNumberHintFree(JSContext *ctx, JSValue val,
|
|
JSToNumberHintEnum flag)
|
|
{
|
|
uint32_t tag;
|
|
JSValue ret;
|
|
|
|
redo:
|
|
tag = JS_VALUE_GET_NORM_TAG(val);
|
|
switch(tag) {
|
|
case JS_TAG_BIG_INT:
|
|
if (flag != TON_FLAG_NUMERIC) {
|
|
JS_FreeValue(ctx, val);
|
|
return JS_ThrowTypeError(ctx, "cannot convert BigInt to number");
|
|
}
|
|
ret = val;
|
|
break;
|
|
case JS_TAG_FLOAT64:
|
|
case JS_TAG_INT:
|
|
case JS_TAG_EXCEPTION:
|
|
ret = val;
|
|
break;
|
|
case JS_TAG_BOOL:
|
|
case JS_TAG_NULL:
|
|
ret = js_int32(JS_VALUE_GET_INT(val));
|
|
break;
|
|
case JS_TAG_UNDEFINED:
|
|
ret = JS_NAN;
|
|
break;
|
|
case JS_TAG_OBJECT:
|
|
val = JS_ToPrimitiveFree(ctx, val, HINT_NUMBER);
|
|
if (JS_IsException(val))
|
|
return JS_EXCEPTION;
|
|
goto redo;
|
|
case JS_TAG_STRING:
|
|
{
|
|
const char *str;
|
|
size_t len;
|
|
int flags;
|
|
|
|
str = JS_ToCStringLen(ctx, &len, val);
|
|
JS_FreeValue(ctx, val);
|
|
if (!str)
|
|
return JS_EXCEPTION;
|
|
flags = ATOD_TRIM_SPACES | ATOD_ACCEPT_EMPTY |
|
|
ATOD_ACCEPT_FLOAT | ATOD_ACCEPT_INFINITY |
|
|
ATOD_ACCEPT_HEX_PREFIX | ATOD_ACCEPT_BIN_OCT |
|
|
ATOD_DECIMAL_AFTER_SIGN | ATOD_NO_TRAILING_CHARS;
|
|
ret = js_atof(ctx, str, str + len, NULL, 10, flags);
|
|
JS_FreeCString(ctx, str);
|
|
}
|
|
break;
|
|
case JS_TAG_SYMBOL:
|
|
JS_FreeValue(ctx, val);
|
|
return JS_ThrowTypeError(ctx, "cannot convert symbol to number");
|
|
default:
|
|
JS_FreeValue(ctx, val);
|
|
ret = JS_NAN;
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static JSValue JS_ToNumberFree(JSContext *ctx, JSValue val)
|
|
{
|
|
return JS_ToNumberHintFree(ctx, val, TON_FLAG_NUMBER);
|
|
}
|
|
|
|
static JSValue JS_ToNumericFree(JSContext *ctx, JSValue val)
|
|
{
|
|
return JS_ToNumberHintFree(ctx, val, TON_FLAG_NUMERIC);
|
|
}
|
|
|
|
static JSValue JS_ToNumeric(JSContext *ctx, JSValue val)
|
|
{
|
|
return JS_ToNumericFree(ctx, js_dup(val));
|
|
}
|
|
|
|
static __exception int __JS_ToFloat64Free(JSContext *ctx, double *pres,
|
|
JSValue val)
|
|
{
|
|
double d;
|
|
uint32_t tag;
|
|
|
|
val = JS_ToNumberFree(ctx, val);
|
|
if (JS_IsException(val)) {
|
|
*pres = JS_FLOAT64_NAN;
|
|
return -1;
|
|
}
|
|
tag = JS_VALUE_GET_NORM_TAG(val);
|
|
switch(tag) {
|
|
case JS_TAG_INT:
|
|
d = JS_VALUE_GET_INT(val);
|
|
break;
|
|
case JS_TAG_FLOAT64:
|
|
d = JS_VALUE_GET_FLOAT64(val);
|
|
break;
|
|
case JS_TAG_BIG_INT:
|
|
{
|
|
JSBigInt *p = JS_VALUE_GET_PTR(val);
|
|
/* XXX: there can be a double rounding issue with some
|
|
primitives (such as JS_ToUint8ClampFree()), but it is
|
|
not critical to fix it. */
|
|
bf_get_float64(&p->num, &d, BF_RNDN);
|
|
JS_FreeValue(ctx, val);
|
|
}
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
*pres = d;
|
|
return 0;
|
|
}
|
|
|
|
static inline int JS_ToFloat64Free(JSContext *ctx, double *pres, JSValue val)
|
|
{
|
|
uint32_t tag;
|
|
|
|
tag = JS_VALUE_GET_TAG(val);
|
|
if (tag <= JS_TAG_NULL) {
|
|
*pres = JS_VALUE_GET_INT(val);
|
|
return 0;
|
|
} else if (JS_TAG_IS_FLOAT64(tag)) {
|
|
*pres = JS_VALUE_GET_FLOAT64(val);
|
|
return 0;
|
|
} else {
|
|
return __JS_ToFloat64Free(ctx, pres, val);
|
|
}
|
|
}
|
|
|
|
int JS_ToFloat64(JSContext *ctx, double *pres, JSValue val)
|
|
{
|
|
return JS_ToFloat64Free(ctx, pres, js_dup(val));
|
|
}
|
|
|
|
static JSValue JS_ToNumber(JSContext *ctx, JSValue val)
|
|
{
|
|
return JS_ToNumberFree(ctx, js_dup(val));
|
|
}
|
|
|
|
/* same as JS_ToNumber() but return 0 in case of NaN/Undefined */
|
|
static __maybe_unused JSValue JS_ToIntegerFree(JSContext *ctx, JSValue val)
|
|
{
|
|
uint32_t tag;
|
|
JSValue ret;
|
|
|
|
redo:
|
|
tag = JS_VALUE_GET_NORM_TAG(val);
|
|
switch(tag) {
|
|
case JS_TAG_INT:
|
|
case JS_TAG_BOOL:
|
|
case JS_TAG_NULL:
|
|
case JS_TAG_UNDEFINED:
|
|
ret = js_int32(JS_VALUE_GET_INT(val));
|
|
break;
|
|
case JS_TAG_FLOAT64:
|
|
{
|
|
double d = JS_VALUE_GET_FLOAT64(val);
|
|
if (isnan(d)) {
|
|
ret = js_int32(0);
|
|
} else {
|
|
/* convert -0 to +0 */
|
|
d = trunc(d) + 0.0;
|
|
ret = js_number(d);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
val = JS_ToNumberFree(ctx, val);
|
|
if (JS_IsException(val))
|
|
return val;
|
|
goto redo;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/* Note: the integer value is satured to 32 bits */
|
|
static int JS_ToInt32SatFree(JSContext *ctx, int *pres, JSValue val)
|
|
{
|
|
uint32_t tag;
|
|
int ret;
|
|
|
|
redo:
|
|
tag = JS_VALUE_GET_NORM_TAG(val);
|
|
switch(tag) {
|
|
case JS_TAG_INT:
|
|
case JS_TAG_BOOL:
|
|
case JS_TAG_NULL:
|
|
case JS_TAG_UNDEFINED:
|
|
ret = JS_VALUE_GET_INT(val);
|
|
break;
|
|
case JS_TAG_EXCEPTION:
|
|
*pres = 0;
|
|
return -1;
|
|
case JS_TAG_FLOAT64:
|
|
{
|
|
double d = JS_VALUE_GET_FLOAT64(val);
|
|
if (isnan(d)) {
|
|
ret = 0;
|
|
} else {
|
|
if (d < INT32_MIN)
|
|
ret = INT32_MIN;
|
|
else if (d > INT32_MAX)
|
|
ret = INT32_MAX;
|
|
else
|
|
ret = (int)d;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
val = JS_ToNumberFree(ctx, val);
|
|
if (JS_IsException(val)) {
|
|
*pres = 0;
|
|
return -1;
|
|
}
|
|
goto redo;
|
|
}
|
|
*pres = ret;
|
|
return 0;
|
|
}
|
|
|
|
int JS_ToInt32Sat(JSContext *ctx, int *pres, JSValue val)
|
|
{
|
|
return JS_ToInt32SatFree(ctx, pres, js_dup(val));
|
|
}
|
|
|
|
int JS_ToInt32Clamp(JSContext *ctx, int *pres, JSValue val,
|
|
int min, int max, int min_offset)
|
|
{
|
|
int res = JS_ToInt32SatFree(ctx, pres, js_dup(val));
|
|
if (res == 0) {
|
|
if (*pres < min) {
|
|
*pres += min_offset;
|
|
if (*pres < min)
|
|
*pres = min;
|
|
} else {
|
|
if (*pres > max)
|
|
*pres = max;
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
static int JS_ToInt64SatFree(JSContext *ctx, int64_t *pres, JSValue val)
|
|
{
|
|
uint32_t tag;
|
|
|
|
redo:
|
|
tag = JS_VALUE_GET_NORM_TAG(val);
|
|
switch(tag) {
|
|
case JS_TAG_INT:
|
|
case JS_TAG_BOOL:
|
|
case JS_TAG_NULL:
|
|
case JS_TAG_UNDEFINED:
|
|
*pres = JS_VALUE_GET_INT(val);
|
|
return 0;
|
|
case JS_TAG_EXCEPTION:
|
|
*pres = 0;
|
|
return -1;
|
|
case JS_TAG_FLOAT64:
|
|
{
|
|
double d = JS_VALUE_GET_FLOAT64(val);
|
|
if (isnan(d)) {
|
|
*pres = 0;
|
|
} else {
|
|
if (d < INT64_MIN)
|
|
*pres = INT64_MIN;
|
|
else if (d >= 0x1p63)
|
|
*pres = INT64_MAX;
|
|
else
|
|
*pres = (int64_t)d;
|
|
}
|
|
}
|
|
return 0;
|
|
default:
|
|
val = JS_ToNumberFree(ctx, val);
|
|
if (JS_IsException(val)) {
|
|
*pres = 0;
|
|
return -1;
|
|
}
|
|
goto redo;
|
|
}
|
|
}
|
|
|
|
int JS_ToInt64Sat(JSContext *ctx, int64_t *pres, JSValue val)
|
|
{
|
|
return JS_ToInt64SatFree(ctx, pres, js_dup(val));
|
|
}
|
|
|
|
int JS_ToInt64Clamp(JSContext *ctx, int64_t *pres, JSValue val,
|
|
int64_t min, int64_t max, int64_t neg_offset)
|
|
{
|
|
int res = JS_ToInt64SatFree(ctx, pres, js_dup(val));
|
|
if (res == 0) {
|
|
if (*pres < 0)
|
|
*pres += neg_offset;
|
|
if (*pres < min)
|
|
*pres = min;
|
|
else if (*pres > max)
|
|
*pres = max;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
/* Same as JS_ToInt32Free() but with a 64 bit result. Return (<0, 0)
|
|
in case of exception */
|
|
static int JS_ToInt64Free(JSContext *ctx, int64_t *pres, JSValue val)
|
|
{
|
|
uint32_t tag;
|
|
int64_t ret;
|
|
|
|
redo:
|
|
tag = JS_VALUE_GET_NORM_TAG(val);
|
|
switch(tag) {
|
|
case JS_TAG_INT:
|
|
case JS_TAG_BOOL:
|
|
case JS_TAG_NULL:
|
|
case JS_TAG_UNDEFINED:
|
|
ret = JS_VALUE_GET_INT(val);
|
|
break;
|
|
case JS_TAG_FLOAT64:
|
|
{
|
|
JSFloat64Union u;
|
|
double d;
|
|
int e;
|
|
d = JS_VALUE_GET_FLOAT64(val);
|
|
u.d = d;
|
|
/* we avoid doing fmod(x, 2^64) */
|
|
e = (u.u64 >> 52) & 0x7ff;
|
|
if (likely(e <= (1023 + 62))) {
|
|
/* fast case */
|
|
ret = (int64_t)d;
|
|
} else if (e <= (1023 + 62 + 53)) {
|
|
uint64_t v;
|
|
/* remainder modulo 2^64 */
|
|
v = (u.u64 & (((uint64_t)1 << 52) - 1)) | ((uint64_t)1 << 52);
|
|
ret = v << ((e - 1023) - 52);
|
|
/* take the sign into account */
|
|
if (u.u64 >> 63)
|
|
if (ret != INT64_MIN)
|
|
ret = -ret;
|
|
} else {
|
|
ret = 0; /* also handles NaN and +inf */
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
val = JS_ToNumberFree(ctx, val);
|
|
if (JS_IsException(val)) {
|
|
*pres = 0;
|
|
return -1;
|
|
}
|
|
goto redo;
|
|
}
|
|
*pres = ret;
|
|
return 0;
|
|
}
|
|
|
|
int JS_ToInt64(JSContext *ctx, int64_t *pres, JSValue val)
|
|
{
|
|
return JS_ToInt64Free(ctx, pres, js_dup(val));
|
|
}
|
|
|
|
int JS_ToInt64Ext(JSContext *ctx, int64_t *pres, JSValue val)
|
|
{
|
|
if (JS_IsBigInt(ctx, val))
|
|
return JS_ToBigInt64(ctx, pres, val);
|
|
else
|
|
return JS_ToInt64(ctx, pres, val);
|
|
}
|
|
|
|
/* return (<0, 0) in case of exception */
|
|
static int JS_ToInt32Free(JSContext *ctx, int32_t *pres, JSValue val)
|
|
{
|
|
uint32_t tag;
|
|
int32_t ret;
|
|
|
|
redo:
|
|
tag = JS_VALUE_GET_NORM_TAG(val);
|
|
switch(tag) {
|
|
case JS_TAG_INT:
|
|
case JS_TAG_BOOL:
|
|
case JS_TAG_NULL:
|
|
case JS_TAG_UNDEFINED:
|
|
ret = JS_VALUE_GET_INT(val);
|
|
break;
|
|
case JS_TAG_FLOAT64:
|
|
{
|
|
JSFloat64Union u;
|
|
double d;
|
|
int e;
|
|
d = JS_VALUE_GET_FLOAT64(val);
|
|
u.d = d;
|
|
/* we avoid doing fmod(x, 2^32) */
|
|
e = (u.u64 >> 52) & 0x7ff;
|
|
if (likely(e <= (1023 + 30))) {
|
|
/* fast case */
|
|
ret = (int32_t)d;
|
|
} else if (e <= (1023 + 30 + 53)) {
|
|
uint64_t v;
|
|
/* remainder modulo 2^32 */
|
|
v = (u.u64 & (((uint64_t)1 << 52) - 1)) | ((uint64_t)1 << 52);
|
|
v = v << ((e - 1023) - 52 + 32);
|
|
ret = v >> 32;
|
|
/* take the sign into account */
|
|
if (u.u64 >> 63)
|
|
if (ret != INT32_MIN)
|
|
ret = -ret;
|
|
} else {
|
|
ret = 0; /* also handles NaN and +inf */
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
val = JS_ToNumberFree(ctx, val);
|
|
if (JS_IsException(val)) {
|
|
*pres = 0;
|
|
return -1;
|
|
}
|
|
goto redo;
|
|
}
|
|
*pres = ret;
|
|
return 0;
|
|
}
|
|
|
|
int JS_ToInt32(JSContext *ctx, int32_t *pres, JSValue val)
|
|
{
|
|
return JS_ToInt32Free(ctx, pres, js_dup(val));
|
|
}
|
|
|
|
static inline int JS_ToUint32Free(JSContext *ctx, uint32_t *pres, JSValue val)
|
|
{
|
|
return JS_ToInt32Free(ctx, (int32_t *)pres, val);
|
|
}
|
|
|
|
static int JS_ToUint8ClampFree(JSContext *ctx, int32_t *pres, JSValue val)
|
|
{
|
|
uint32_t tag;
|
|
int res;
|
|
|
|
redo:
|
|
tag = JS_VALUE_GET_NORM_TAG(val);
|
|
switch(tag) {
|
|
case JS_TAG_INT:
|
|
case JS_TAG_BOOL:
|
|
case JS_TAG_NULL:
|
|
case JS_TAG_UNDEFINED:
|
|
res = JS_VALUE_GET_INT(val);
|
|
res = max_int(0, min_int(255, res));
|
|
break;
|
|
case JS_TAG_FLOAT64:
|
|
{
|
|
double d = JS_VALUE_GET_FLOAT64(val);
|
|
if (isnan(d)) {
|
|
res = 0;
|
|
} else {
|
|
if (d < 0)
|
|
res = 0;
|
|
else if (d > 255)
|
|
res = 255;
|
|
else
|
|
res = lrint(d);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
val = JS_ToNumberFree(ctx, val);
|
|
if (JS_IsException(val)) {
|
|
*pres = 0;
|
|
return -1;
|
|
}
|
|
goto redo;
|
|
}
|
|
*pres = res;
|
|
return 0;
|
|
}
|
|
|
|
static __exception int JS_ToArrayLengthFree(JSContext *ctx, uint32_t *plen,
|
|
JSValue val, BOOL is_array_ctor)
|
|
{
|
|
uint32_t tag, len;
|
|
|
|
tag = JS_VALUE_GET_TAG(val);
|
|
switch(tag) {
|
|
case JS_TAG_INT:
|
|
case JS_TAG_BOOL:
|
|
case JS_TAG_NULL:
|
|
{
|
|
int v;
|
|
v = JS_VALUE_GET_INT(val);
|
|
if (v < 0)
|
|
goto fail;
|
|
len = v;
|
|
}
|
|
break;
|
|
case JS_TAG_BIG_INT:
|
|
{
|
|
JSBigInt *p = JS_VALUE_GET_PTR(val);
|
|
bf_t a;
|
|
BOOL res;
|
|
bf_get_int32((int32_t *)&len, &p->num, BF_GET_INT_MOD);
|
|
bf_init(ctx->bf_ctx, &a);
|
|
bf_set_ui(&a, len);
|
|
res = bf_cmp_eq(&a, &p->num);
|
|
bf_delete(&a);
|
|
JS_FreeValue(ctx, val);
|
|
if (!res)
|
|
goto fail;
|
|
}
|
|
break;
|
|
default:
|
|
if (JS_TAG_IS_FLOAT64(tag)) {
|
|
double d;
|
|
d = JS_VALUE_GET_FLOAT64(val);
|
|
if (!(d >= 0 && d <= UINT32_MAX))
|
|
goto fail;
|
|
len = (uint32_t)d;
|
|
if (len != d)
|
|
goto fail;
|
|
} else {
|
|
uint32_t len1;
|
|
|
|
if (is_array_ctor) {
|
|
val = JS_ToNumberFree(ctx, val);
|
|
if (JS_IsException(val))
|
|
return -1;
|
|
/* cannot recurse because val is a number */
|
|
if (JS_ToArrayLengthFree(ctx, &len, val, TRUE))
|
|
return -1;
|
|
} else {
|
|
/* legacy behavior: must do the conversion twice and compare */
|
|
if (JS_ToUint32(ctx, &len, val)) {
|
|
JS_FreeValue(ctx, val);
|
|
return -1;
|
|
}
|
|
val = JS_ToNumberFree(ctx, val);
|
|
if (JS_IsException(val))
|
|
return -1;
|
|
/* cannot recurse because val is a number */
|
|
if (JS_ToArrayLengthFree(ctx, &len1, val, FALSE))
|
|
return -1;
|
|
if (len1 != len) {
|
|
fail:
|
|
JS_ThrowRangeError(ctx, "invalid array length");
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
*plen = len;
|
|
return 0;
|
|
}
|
|
|
|
#define MAX_SAFE_INTEGER (((int64_t)1 << 53) - 1)
|
|
|
|
static BOOL is_safe_integer(double d)
|
|
{
|
|
return isfinite(d) && floor(d) == d &&
|
|
fabs(d) <= (double)MAX_SAFE_INTEGER;
|
|
}
|
|
|
|
int JS_ToIndex(JSContext *ctx, uint64_t *plen, JSValue val)
|
|
{
|
|
int64_t v;
|
|
if (JS_ToInt64Sat(ctx, &v, val))
|
|
return -1;
|
|
if (v < 0 || v > MAX_SAFE_INTEGER) {
|
|
JS_ThrowRangeError(ctx, "invalid array index");
|
|
*plen = 0;
|
|
return -1;
|
|
}
|
|
*plen = v;
|
|
return 0;
|
|
}
|
|
|
|
/* convert a value to a length between 0 and MAX_SAFE_INTEGER.
|
|
return -1 for exception */
|
|
static __exception int JS_ToLengthFree(JSContext *ctx, int64_t *plen,
|
|
JSValue val)
|
|
{
|
|
int res = JS_ToInt64Clamp(ctx, plen, val, 0, MAX_SAFE_INTEGER, 0);
|
|
JS_FreeValue(ctx, val);
|
|
return res;
|
|
}
|
|
|
|
/* Note: can return an exception */
|
|
static int JS_NumberIsInteger(JSContext *ctx, JSValue val)
|
|
{
|
|
double d;
|
|
if (!JS_IsNumber(val))
|
|
return FALSE;
|
|
if (unlikely(JS_ToFloat64(ctx, &d, val)))
|
|
return -1;
|
|
return isfinite(d) && floor(d) == d;
|
|
}
|
|
|
|
static BOOL JS_NumberIsNegativeOrMinusZero(JSContext *ctx, JSValue val)
|
|
{
|
|
uint32_t tag;
|
|
|
|
tag = JS_VALUE_GET_NORM_TAG(val);
|
|
switch(tag) {
|
|
case JS_TAG_INT:
|
|
{
|
|
int v;
|
|
v = JS_VALUE_GET_INT(val);
|
|
return (v < 0);
|
|
}
|
|
case JS_TAG_FLOAT64:
|
|
{
|
|
JSFloat64Union u;
|
|
u.d = JS_VALUE_GET_FLOAT64(val);
|
|
return (u.u64 >> 63);
|
|
}
|
|
case JS_TAG_BIG_INT:
|
|
{
|
|
JSBigInt *p = JS_VALUE_GET_PTR(val);
|
|
/* Note: integer zeros are not necessarily positive */
|
|
return p->num.sign && !bf_is_zero(&p->num);
|
|
}
|
|
default:
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
static JSValue js_bigint_to_string1(JSContext *ctx, JSValue val, int radix)
|
|
{
|
|
JSValue ret;
|
|
bf_t a_s, *a;
|
|
char *str;
|
|
int saved_sign;
|
|
|
|
a = JS_ToBigInt(ctx, &a_s, val);
|
|
if (!a)
|
|
return JS_EXCEPTION;
|
|
saved_sign = a->sign;
|
|
if (a->expn == BF_EXP_ZERO)
|
|
a->sign = 0;
|
|
// TODO(chqrlie) bf_ftoa should return the string length to the caller
|
|
str = bf_ftoa(NULL, a, radix, 0, BF_RNDZ | BF_FTOA_FORMAT_FRAC |
|
|
BF_FTOA_JS_QUIRKS);
|
|
a->sign = saved_sign;
|
|
JS_FreeBigInt(ctx, a, &a_s);
|
|
if (!str)
|
|
return JS_ThrowOutOfMemory(ctx);
|
|
ret = js_new_string8(ctx, str);
|
|
bf_free(ctx->bf_ctx, str);
|
|
return ret;
|
|
}
|
|
|
|
static JSValue js_bigint_to_string(JSContext *ctx, JSValue val)
|
|
{
|
|
return js_bigint_to_string1(ctx, val, 10);
|
|
}
|
|
|
|
/*---- floating point number to string conversions ----*/
|
|
|
|
/* JavaScript rounding is specified as round to nearest tie away
|
|
from zero (RNDNA), but in `printf` the "ties" case is not
|
|
specified (in most cases it is RNDN, round to nearest, tie to even),
|
|
so we must round manually. We generate 2 extra places and make
|
|
an extra call to snprintf if these are exactly '50'.
|
|
We set the current rounding mode to FE_DOWNWARD to check if the
|
|
last 2 places become '49'. If not, we must round up, which is
|
|
performed in place using the string digits.
|
|
|
|
Note that we cannot rely on snprintf for rounding up:
|
|
the code below fails on macOS for `0.5.toFixed(0)`: gives `0` expected `1`
|
|
fesetround(FE_UPWARD);
|
|
snprintf(dest, size, "%.*f", n_digits, d);
|
|
fesetround(FE_TONEAREST);
|
|
*/
|
|
|
|
/* `js_fcvt` minimum buffer length:
|
|
- up to 21 digits in integral part
|
|
- 1 potential decimal point
|
|
- up to 102 decimals
|
|
- 1 null terminator
|
|
*/
|
|
#define JS_FCVT_BUF_SIZE (21+1+102+1)
|
|
|
|
/* `js_ecvt` minimum buffer length:
|
|
- 1 leading digit
|
|
- 1 potential decimal point
|
|
- up to 102 decimals
|
|
- 5 exponent characters (from 'e-324' to 'e+308')
|
|
- 1 null terminator
|
|
*/
|
|
#define JS_ECVT_BUF_SIZE (1+1+102+5+1)
|
|
|
|
/* `js_dtoa` minimum buffer length:
|
|
- 8 byte prefix
|
|
- either JS_FCVT_BUF_SIZE or JS_ECVT_BUF_SIZE
|
|
- JS_FCVT_BUF_SIZE is larger than JS_ECVT_BUF_SIZE
|
|
*/
|
|
#define JS_DTOA_BUF_SIZE (8+JS_FCVT_BUF_SIZE)
|
|
|
|
/* `js_ecvt1`: compute the digits and decimal point spot for a double
|
|
- `d` is finite, positive or zero
|
|
- `n_digits` number of significant digits in range 1..103
|
|
- `buf` receives the printf result
|
|
- `buf` has a fixed format: n_digits with a decimal point at offset 1
|
|
and exponent 'e{+/-}xx[x]' at offset n_digits+1
|
|
Return n_digits
|
|
Store the position of the decimal point into `*decpt`
|
|
*/
|
|
static int js_ecvt1(double d, int n_digits,
|
|
char dest[minimum_length(JS_ECVT_BUF_SIZE)],
|
|
size_t size, int *decpt)
|
|
{
|
|
/* d is positive, ensure decimal point is always present */
|
|
snprintf(dest, size, "%#.*e", n_digits - 1, d);
|
|
/* dest contents:
|
|
0: first digit
|
|
1: '.' decimal point (locale specific)
|
|
2..n_digits: (n_digits-1) additional digits
|
|
n_digits+1: 'e' exponent mark
|
|
n_digits+2..: exponent sign, value and null terminator
|
|
*/
|
|
/* extract the exponent (actually the position of the decimal point) */
|
|
*decpt = 1 + atoi(dest + n_digits + 2);
|
|
return n_digits;
|
|
}
|
|
|
|
/* `js_ecvt`: compute the digits and decimal point spot for a double
|
|
with proper javascript rounding. We cannot use `ecvt` for multiple
|
|
resasons: portability, because of the number of digits is typically
|
|
limited to 17, finally because the default rounding is inadequate.
|
|
`d` is finite and positive or zero.
|
|
`n_digits` number of significant digits in range 1..101
|
|
or 0 for automatic (only as many digits as necessary)
|
|
Return the number of digits produced in `dest`.
|
|
Store the position of the decimal point into `*decpt`
|
|
*/
|
|
static int js_ecvt(double d, int n_digits,
|
|
char dest[minimum_length(JS_ECVT_BUF_SIZE)],
|
|
size_t size, int *decpt)
|
|
{
|
|
int i;
|
|
|
|
if (n_digits == 0) {
|
|
/* find the minimum number of digits (XXX: inefficient but simple) */
|
|
// TODO(chqrlie) use direct method from quickjs-printf
|
|
unsigned int n_digits_min = 1;
|
|
unsigned int n_digits_max = 17;
|
|
for (;;) {
|
|
n_digits = (n_digits_min + n_digits_max) / 2;
|
|
js_ecvt1(d, n_digits, dest, size, decpt);
|
|
if (n_digits_min == n_digits_max)
|
|
return n_digits;
|
|
/* dest contents:
|
|
0: first digit
|
|
1: '.' decimal point (locale specific)
|
|
2..n_digits: (n_digits-1) additional digits
|
|
n_digits+1: 'e' exponent mark
|
|
n_digits+2..: exponent sign, value and null terminator
|
|
*/
|
|
if (strtod(dest, NULL) == d) {
|
|
unsigned int n0 = n_digits;
|
|
/* enough digits */
|
|
/* strip the trailing zeros */
|
|
while (dest[n_digits] == '0')
|
|
n_digits--;
|
|
if (n_digits == n_digits_min)
|
|
return n_digits;
|
|
/* done if trailing zeros and not denormal or huge */
|
|
if (n_digits < n0 && d > 3e-308 && d < 8e307)
|
|
return n_digits;
|
|
n_digits_max = n_digits;
|
|
} else {
|
|
/* need at least one more digit */
|
|
n_digits_min = n_digits + 1;
|
|
}
|
|
}
|
|
} else {
|
|
#if defined(FE_DOWNWARD) && defined(FE_TONEAREST)
|
|
/* generate 2 extra digits: 99% chances to avoid 2 calls */
|
|
js_ecvt1(d, n_digits + 2, dest, size, decpt);
|
|
if (dest[n_digits + 1] < '5')
|
|
return n_digits; /* truncate the 2 extra digits */
|
|
if (dest[n_digits + 1] == '5' && dest[n_digits + 2] == '0') {
|
|
/* close to half-way: try rounding toward 0 */
|
|
fesetround(FE_DOWNWARD);
|
|
js_ecvt1(d, n_digits + 2, dest, size, decpt);
|
|
fesetround(FE_TONEAREST);
|
|
if (dest[n_digits + 1] < '5')
|
|
return n_digits; /* truncate the 2 extra digits */
|
|
}
|
|
/* round up in the string */
|
|
for(i = n_digits;; i--) {
|
|
/* ignore the locale specific decimal point */
|
|
if (is_digit(dest[i])) {
|
|
if (dest[i]++ < '9')
|
|
break;
|
|
dest[i] = '0';
|
|
if (i == 0) {
|
|
dest[0] = '1';
|
|
(*decpt)++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return n_digits; /* truncate the 2 extra digits */
|
|
#else
|
|
/* No disambiguation available, eg: __wasi__ targets */
|
|
return js_ecvt1(d, n_digits, dest, size, decpt);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/* `js_fcvt`: convert a floating point value to %f format using RNDNA
|
|
`d` is finite and positive or zero.
|
|
`n_digits` number of decimal places in range 0..100
|
|
Return the number of characters produced in `dest`.
|
|
*/
|
|
static size_t js_fcvt(double d, int n_digits,
|
|
char dest[minimum_length(JS_FCVT_BUF_SIZE)], size_t size)
|
|
{
|
|
#if defined(FE_DOWNWARD) && defined(FE_TONEAREST)
|
|
int i, n1, n2;
|
|
/* generate 2 extra digits: 99% chances to avoid 2 calls */
|
|
n1 = snprintf(dest, size, "%.*f", n_digits + 2, d) - 2;
|
|
if (dest[n1] >= '5') {
|
|
if (dest[n1] == '5' && dest[n1 + 1] == '0') {
|
|
/* close to half-way: try rounding toward 0 */
|
|
fesetround(FE_DOWNWARD);
|
|
n1 = snprintf(dest, size, "%.*f", n_digits + 2, d) - 2;
|
|
fesetround(FE_TONEAREST);
|
|
}
|
|
if (dest[n1] >= '5') { /* number should be rounded up */
|
|
/* d is either exactly half way or greater: round the string manually */
|
|
for (i = n1 - 1;; i--) {
|
|
/* ignore the locale specific decimal point */
|
|
if (is_digit(dest[i])) {
|
|
if (dest[i]++ < '9')
|
|
break;
|
|
dest[i] = '0';
|
|
if (i == 0) {
|
|
dest[0] = '1';
|
|
dest[n1] = '0';
|
|
dest[n1 - n_digits - 1] = '0';
|
|
dest[n1 - n_digits] = '.';
|
|
n1++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/* truncate the extra 2 digits and the decimal point if !n_digits */
|
|
n1 -= !n_digits;
|
|
//dest[n1] = '\0'; // optional
|
|
return n1;
|
|
#else
|
|
/* No disambiguation available, eg: __wasi__ targets */
|
|
return snprintf(dest, size, "%.*f", n_digits, d);
|
|
#endif
|
|
}
|
|
|
|
static JSValue js_dtoa_infinite(JSContext *ctx, double d)
|
|
{
|
|
// TODO(chqrlie) use atoms for NaN and Infinite?
|
|
if (isnan(d))
|
|
return js_new_string8(ctx, "NaN");
|
|
if (d < 0)
|
|
return js_new_string8(ctx, "-Infinity");
|
|
else
|
|
return js_new_string8(ctx, "Infinity");
|
|
}
|
|
|
|
#define JS_DTOA_TOSTRING 0 /* use as many digits as necessary */
|
|
#define JS_DTOA_EXPONENTIAL 1 /* use exponential notation either fixed or variable digits */
|
|
#define JS_DTOA_FIXED 2 /* force fixed number of fractional digits */
|
|
#define JS_DTOA_PRECISION 3 /* use n_digits significant digits (1 <= n_digits <= 101) */
|
|
|
|
/* `js_dtoa`: convert a floating point number to a string
|
|
- `mode`: one of the 4 supported formats
|
|
- `n_digits`: digit number according to mode
|
|
- TOSTRING: 0 only. As many digits as necessary
|
|
- EXPONENTIAL: 0 as many decimals as necessary
|
|
- 1..101 number of significant digits
|
|
- FIXED: 0..100 number of decimal places
|
|
- PRECISION: 1..101 number of significant digits
|
|
*/
|
|
// XXX: should use libbf or quickjs-printf.
|
|
static JSValue js_dtoa(JSContext *ctx, double d, int n_digits, int mode)
|
|
{
|
|
char buf[JS_DTOA_BUF_SIZE];
|
|
size_t len;
|
|
char *start;
|
|
int sign, decpt, exp, i, k, n, n_max;
|
|
|
|
if (!isfinite(d))
|
|
return js_dtoa_infinite(ctx, d);
|
|
|
|
sign = (d < 0);
|
|
start = buf + 8;
|
|
d = fabs(d); /* also converts -0 to 0 */
|
|
|
|
if (mode != JS_DTOA_EXPONENTIAL && n_digits == 0) {
|
|
/* fast path for exact integers in variable format:
|
|
clip to MAX_SAFE_INTEGER because to ensure insignificant
|
|
digits are generated as 0.
|
|
used for JS_DTOA_TOSTRING and JS_DTOA_FIXED without decimals.
|
|
*/
|
|
if (d <= (double)MAX_SAFE_INTEGER) {
|
|
uint64_t u64 = (uint64_t)d;
|
|
if (d == u64) {
|
|
len = u64toa(start, u64);
|
|
goto done;
|
|
}
|
|
}
|
|
}
|
|
if (mode == JS_DTOA_FIXED) {
|
|
len = js_fcvt(d, n_digits, start, sizeof(buf) - 8);
|
|
// TODO(chqrlie) patch the locale specific decimal point
|
|
goto done;
|
|
}
|
|
|
|
n_max = (n_digits > 0) ? n_digits : 21;
|
|
/* the number has k digits (1 <= k <= n_max) */
|
|
k = js_ecvt(d, n_digits, start, sizeof(buf) - 8, &decpt);
|
|
/* buffer contents:
|
|
0: first digit
|
|
1: '.' decimal point
|
|
2..k: (k-1) additional digits
|
|
*/
|
|
n = decpt; /* d=10^(n-k)*(buf1) i.e. d= < x.yyyy 10^(n-1) */
|
|
if (mode != JS_DTOA_EXPONENTIAL) {
|
|
/* mode is JS_DTOA_PRECISION or JS_DTOA_TOSTRING */
|
|
if (n >= 1 && n <= n_max) {
|
|
/* between 1 and n_max digits before the decimal point */
|
|
if (k <= n) {
|
|
/* all digits before the point, append zeros */
|
|
start[1] = start[0];
|
|
start++;
|
|
for(i = k; i < n; i++)
|
|
start[i] = '0';
|
|
len = n;
|
|
} else {
|
|
/* k > n: move digits before the point */
|
|
for(i = 1; i < n; i++)
|
|
start[i] = start[i + 1];
|
|
start[i] = '.';
|
|
len = 1 + k;
|
|
}
|
|
goto done;
|
|
}
|
|
if (n >= -5 && n <= 0) {
|
|
/* insert -n leading 0 decimals and a '0.' prefix */
|
|
n = -n;
|
|
start[1] = start[0];
|
|
start -= n + 1;
|
|
start[0] = '0';
|
|
start[1] = '.';
|
|
for(i = 0; i < n; i++)
|
|
start[2 + i] = '0';
|
|
len = 2 + k + n;
|
|
goto done;
|
|
}
|
|
}
|
|
/* exponential notation */
|
|
exp = n - 1;
|
|
/* count the digits and the decimal point if at least one decimal */
|
|
len = k + (k > 1);
|
|
start[1] = '.'; /* patch the locale specific decimal point */
|
|
start[len] = 'e';
|
|
start[len + 1] = '+';
|
|
if (exp < 0) {
|
|
start[len + 1] = '-';
|
|
exp = -exp;
|
|
}
|
|
len += 2 + 1 + (exp > 9) + (exp > 99);
|
|
for (i = len - 1; exp > 9;) {
|
|
int quo = exp / 10;
|
|
start[i--] = (char)('0' + exp % 10);
|
|
exp = quo;
|
|
}
|
|
start[i] = (char)('0' + exp);
|
|
|
|
done:
|
|
start[-1] = '-'; /* prepend the sign if negative */
|
|
return js_new_string8_len(ctx, start - sign, len + sign);
|
|
}
|
|
|
|
/* `js_dtoa_radix`: convert a floating point number using a specific base
|
|
- `d` must be finite
|
|
- `radix` must be in range 2..36
|
|
*/
|
|
static JSValue js_dtoa_radix(JSContext *ctx, double d, int radix)
|
|
{
|
|
char buf[2200], *ptr, *ptr2, *ptr3;
|
|
int sign, digit;
|
|
double frac, d0;
|
|
int64_t n0;
|
|
|
|
if (!isfinite(d))
|
|
return js_dtoa_infinite(ctx, d);
|
|
|
|
sign = (d < 0);
|
|
d = fabs(d);
|
|
d0 = trunc(d);
|
|
n0 = 0;
|
|
frac = d - d0;
|
|
ptr2 = buf + 1100; /* ptr2 points to the end of the string */
|
|
ptr = ptr2; /* ptr points to the beginning of the string */
|
|
if (d0 <= MAX_SAFE_INTEGER) {
|
|
int64_t n = n0 = (int64_t)d0;
|
|
while (n >= radix) {
|
|
digit = n % radix;
|
|
n = n / radix;
|
|
*--ptr = digits36[digit];
|
|
}
|
|
*--ptr = digits36[(size_t)n];
|
|
} else {
|
|
/* no decimals */
|
|
while (d0 >= radix) {
|
|
digit = fmod(d0, radix);
|
|
d0 = trunc(d0 / radix);
|
|
if (d0 >= MAX_SAFE_INTEGER)
|
|
digit = 0;
|
|
*--ptr = digits36[digit];
|
|
}
|
|
*--ptr = digits36[(size_t)d0];
|
|
goto done;
|
|
}
|
|
if (frac != 0) {
|
|
double log2_radix = log2(radix);
|
|
double prec = 1023 + 51; // handle subnormals
|
|
*ptr2++ = '.';
|
|
while (frac != 0 && n0 <= MAX_SAFE_INTEGER/2 && prec > 0) {
|
|
frac *= radix;
|
|
digit = trunc(frac);
|
|
frac -= digit;
|
|
*ptr2++ = digits36[digit];
|
|
n0 = n0 * radix + digit;
|
|
prec -= log2_radix;
|
|
}
|
|
if (frac * radix >= radix / 2) {
|
|
/* round up the string representation manually */
|
|
char nine = digits36[radix - 1];
|
|
while (ptr2[-1] == nine) {
|
|
/* strip trailing '9' or equivalent digits */
|
|
ptr2--;
|
|
}
|
|
if (ptr2[-1] == '.') {
|
|
/* strip the 'decimal' point */
|
|
ptr2--;
|
|
/* increment the integral part */
|
|
for (ptr3 = ptr2;;) {
|
|
if (ptr3[-1] != nine) {
|
|
ptr3[-1] = (ptr3[-1] == '9') ? 'a' : ptr3[-1] + 1;
|
|
break;
|
|
}
|
|
*--ptr3 = '0';
|
|
if (ptr3 <= ptr) {
|
|
/* prepend a '1' if number was all nines */
|
|
*--ptr = '1';
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
/* increment the last fractional digit */
|
|
ptr2[-1] = (ptr2[-1] == '9') ? 'a' : ptr2[-1] + 1;
|
|
}
|
|
} else {
|
|
/* strip trailing fractional zeros */
|
|
while (ptr2[-1] == '0')
|
|
ptr2--;
|
|
/* strip the 'decimal' point if last */
|
|
ptr2 -= (ptr2[-1] == '.');
|
|
}
|
|
}
|
|
done:
|
|
ptr[-1] = '-';
|
|
ptr -= sign;
|
|
return js_new_string8_len(ctx, ptr, ptr2 - ptr);
|
|
}
|
|
|
|
JSValue JS_ToStringInternal(JSContext *ctx, JSValue val, BOOL is_ToPropertyKey)
|
|
{
|
|
uint32_t tag;
|
|
const char *str;
|
|
char buf[32];
|
|
size_t len;
|
|
|
|
tag = JS_VALUE_GET_NORM_TAG(val);
|
|
switch(tag) {
|
|
case JS_TAG_STRING:
|
|
return js_dup(val);
|
|
case JS_TAG_INT:
|
|
len = i32toa(buf, JS_VALUE_GET_INT(val));
|
|
return js_new_string8_len(ctx, buf, len);
|
|
case JS_TAG_BOOL:
|
|
return JS_AtomToString(ctx, JS_VALUE_GET_BOOL(val) ?
|
|
JS_ATOM_true : JS_ATOM_false);
|
|
case JS_TAG_NULL:
|
|
return JS_AtomToString(ctx, JS_ATOM_null);
|
|
case JS_TAG_UNDEFINED:
|
|
return JS_AtomToString(ctx, JS_ATOM_undefined);
|
|
case JS_TAG_EXCEPTION:
|
|
return JS_EXCEPTION;
|
|
case JS_TAG_OBJECT:
|
|
{
|
|
JSValue val1, ret;
|
|
val1 = JS_ToPrimitive(ctx, val, HINT_STRING);
|
|
if (JS_IsException(val1))
|
|
return val1;
|
|
ret = JS_ToStringInternal(ctx, val1, is_ToPropertyKey);
|
|
JS_FreeValue(ctx, val1);
|
|
return ret;
|
|
}
|
|
break;
|
|
case JS_TAG_FUNCTION_BYTECODE:
|
|
return js_new_string8(ctx, "[function bytecode]");
|
|
case JS_TAG_SYMBOL:
|
|
if (is_ToPropertyKey) {
|
|
return js_dup(val);
|
|
} else {
|
|
return JS_ThrowTypeError(ctx, "cannot convert symbol to string");
|
|
}
|
|
case JS_TAG_FLOAT64:
|
|
return js_dtoa(ctx, JS_VALUE_GET_FLOAT64(val), 0, JS_DTOA_TOSTRING);
|
|
case JS_TAG_BIG_INT:
|
|
return js_bigint_to_string(ctx, val);
|
|
default:
|
|
return js_new_string8(ctx, "[unsupported type]");
|
|
}
|
|
}
|
|
|
|
JSValue JS_ToString(JSContext *ctx, JSValue val)
|
|
{
|
|
return JS_ToStringInternal(ctx, val, FALSE);
|
|
}
|
|
|
|
static JSValue JS_ToStringFree(JSContext *ctx, JSValue val)
|
|
{
|
|
JSValue ret;
|
|
ret = JS_ToString(ctx, val);
|
|
JS_FreeValue(ctx, val);
|
|
return ret;
|
|
}
|
|
|
|
static JSValue JS_ToLocaleStringFree(JSContext *ctx, JSValue val)
|
|
{
|
|
if (JS_IsUndefined(val) || JS_IsNull(val))
|
|
return JS_ToStringFree(ctx, val);
|
|
return JS_InvokeFree(ctx, val, JS_ATOM_toLocaleString, 0, NULL);
|
|
}
|
|
|
|
JSValue JS_ToPropertyKey(JSContext *ctx, JSValue val)
|
|
{
|
|
return JS_ToStringInternal(ctx, val, TRUE);
|
|
}
|
|
|
|
static JSValue JS_ToStringCheckObject(JSContext *ctx, JSValue val)
|
|
{
|
|
uint32_t tag = JS_VALUE_GET_TAG(val);
|
|
if (tag == JS_TAG_NULL || tag == JS_TAG_UNDEFINED)
|
|
return JS_ThrowTypeError(ctx, "null or undefined are forbidden");
|
|
return JS_ToString(ctx, val);
|
|
}
|
|
|
|
static JSValue JS_ToQuotedString(JSContext *ctx, JSValue val1)
|
|
{
|
|
JSValue val;
|
|
JSString *p;
|
|
int i;
|
|
uint32_t c;
|
|
StringBuffer b_s, *b = &b_s;
|
|
char buf[16];
|
|
|
|
val = JS_ToStringCheckObject(ctx, val1);
|
|
if (JS_IsException(val))
|
|
return val;
|
|
p = JS_VALUE_GET_STRING(val);
|
|
|
|
if (string_buffer_init(ctx, b, p->len + 2))
|
|
goto fail;
|
|
|
|
if (string_buffer_putc8(b, '\"'))
|
|
goto fail;
|
|
for(i = 0; i < p->len; ) {
|
|
c = string_getc(p, &i);
|
|
switch(c) {
|
|
case '\t':
|
|
c = 't';
|
|
goto quote;
|
|
case '\r':
|
|
c = 'r';
|
|
goto quote;
|
|
case '\n':
|
|
c = 'n';
|
|
goto quote;
|
|
case '\b':
|
|
c = 'b';
|
|
goto quote;
|
|
case '\f':
|
|
c = 'f';
|
|
goto quote;
|
|
case '\"':
|
|
case '\\':
|
|
quote:
|
|
if (string_buffer_putc8(b, '\\'))
|
|
goto fail;
|
|
if (string_buffer_putc8(b, c))
|
|
goto fail;
|
|
break;
|
|
default:
|
|
if (c < 32 || is_surrogate(c)) {
|
|
snprintf(buf, sizeof(buf), "\\u%04x", c);
|
|
if (string_buffer_write8(b, (uint8_t*)buf, 6))
|
|
goto fail;
|
|
} else {
|
|
if (string_buffer_putc(b, c))
|
|
goto fail;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (string_buffer_putc8(b, '\"'))
|
|
goto fail;
|
|
JS_FreeValue(ctx, val);
|
|
return string_buffer_end(b);
|
|
fail:
|
|
JS_FreeValue(ctx, val);
|
|
string_buffer_free(b);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static __maybe_unused void JS_DumpObjectHeader(JSRuntime *rt)
|
|
{
|
|
printf("%14s %4s %4s %14s %10s %s\n",
|
|
"ADDRESS", "REFS", "SHRF", "PROTO", "CLASS", "PROPS");
|
|
}
|
|
|
|
/* for debug only: dump an object without side effect */
|
|
static __maybe_unused void JS_DumpObject(JSRuntime *rt, JSObject *p)
|
|
{
|
|
uint32_t i;
|
|
char atom_buf[ATOM_GET_STR_BUF_SIZE];
|
|
JSShape *sh;
|
|
JSShapeProperty *prs;
|
|
JSProperty *pr;
|
|
BOOL is_first = TRUE;
|
|
|
|
/* XXX: should encode atoms with special characters */
|
|
sh = p->shape; /* the shape can be NULL while freeing an object */
|
|
printf("%14p %4d ",
|
|
(void *)p,
|
|
p->header.ref_count);
|
|
if (sh) {
|
|
printf("%3d%c %14p ",
|
|
sh->header.ref_count,
|
|
" *"[sh->is_hashed],
|
|
(void *)sh->proto);
|
|
} else {
|
|
printf("%3s %14s ", "-", "-");
|
|
}
|
|
printf("%10s ",
|
|
JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), rt->class_array[p->class_id].class_name));
|
|
if (p->is_exotic && p->fast_array) {
|
|
printf("[ ");
|
|
for(i = 0; i < p->u.array.count; i++) {
|
|
if (i != 0)
|
|
printf(", ");
|
|
switch (p->class_id) {
|
|
case JS_CLASS_ARRAY:
|
|
case JS_CLASS_ARGUMENTS:
|
|
JS_DumpValue(rt, p->u.array.u.values[i]);
|
|
break;
|
|
case JS_CLASS_UINT8C_ARRAY:
|
|
case JS_CLASS_INT8_ARRAY:
|
|
case JS_CLASS_UINT8_ARRAY:
|
|
case JS_CLASS_INT16_ARRAY:
|
|
case JS_CLASS_UINT16_ARRAY:
|
|
case JS_CLASS_INT32_ARRAY:
|
|
case JS_CLASS_UINT32_ARRAY:
|
|
case JS_CLASS_BIG_INT64_ARRAY:
|
|
case JS_CLASS_BIG_UINT64_ARRAY:
|
|
case JS_CLASS_FLOAT32_ARRAY:
|
|
case JS_CLASS_FLOAT64_ARRAY:
|
|
{
|
|
int size = 1 << typed_array_size_log2(p->class_id);
|
|
const uint8_t *b = p->u.array.u.uint8_ptr + i * size;
|
|
while (size-- > 0)
|
|
printf("%02X", *b++);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
printf(" ] ");
|
|
}
|
|
|
|
if (sh) {
|
|
printf("{ ");
|
|
for(i = 0, prs = get_shape_prop(sh); i < sh->prop_count; i++, prs++) {
|
|
if (prs->atom != JS_ATOM_NULL) {
|
|
pr = &p->prop[i];
|
|
if (!is_first)
|
|
printf(", ");
|
|
printf("%s: ",
|
|
JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), prs->atom));
|
|
if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) {
|
|
printf("[getset %p %p]", (void *)pr->u.getset.getter,
|
|
(void *)pr->u.getset.setter);
|
|
} else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) {
|
|
printf("[varref %p]", (void *)pr->u.var_ref);
|
|
} else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) {
|
|
printf("[autoinit %p %d %p]",
|
|
(void *)js_autoinit_get_realm(pr),
|
|
js_autoinit_get_id(pr),
|
|
(void *)pr->u.init.opaque);
|
|
} else {
|
|
JS_DumpValue(rt, pr->u.value);
|
|
}
|
|
is_first = FALSE;
|
|
}
|
|
}
|
|
printf(" }");
|
|
}
|
|
|
|
if (js_class_has_bytecode(p->class_id)) {
|
|
JSFunctionBytecode *b = p->u.func.function_bytecode;
|
|
JSVarRef **var_refs;
|
|
if (b->closure_var_count) {
|
|
var_refs = p->u.func.var_refs;
|
|
printf(" Closure:");
|
|
for(i = 0; i < b->closure_var_count; i++) {
|
|
printf(" ");
|
|
JS_DumpValue(rt, var_refs[i]->value);
|
|
}
|
|
if (p->u.func.home_object) {
|
|
printf(" HomeObject: ");
|
|
JS_DumpValue(rt, JS_MKPTR(JS_TAG_OBJECT, p->u.func.home_object));
|
|
}
|
|
}
|
|
}
|
|
printf("\n");
|
|
}
|
|
|
|
static __maybe_unused void JS_DumpGCObject(JSRuntime *rt, JSGCObjectHeader *p)
|
|
{
|
|
if (p->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT) {
|
|
JS_DumpObject(rt, (JSObject *)p);
|
|
} else {
|
|
printf("%14p %4d ",
|
|
(void *)p,
|
|
p->ref_count);
|
|
switch(p->gc_obj_type) {
|
|
case JS_GC_OBJ_TYPE_FUNCTION_BYTECODE:
|
|
printf("[function bytecode]");
|
|
break;
|
|
case JS_GC_OBJ_TYPE_SHAPE:
|
|
printf("[shape]");
|
|
break;
|
|
case JS_GC_OBJ_TYPE_VAR_REF:
|
|
printf("[var_ref]");
|
|
break;
|
|
case JS_GC_OBJ_TYPE_ASYNC_FUNCTION:
|
|
printf("[async_function]");
|
|
break;
|
|
case JS_GC_OBJ_TYPE_JS_CONTEXT:
|
|
printf("[js_context]");
|
|
break;
|
|
default:
|
|
printf("[unknown %d]", p->gc_obj_type);
|
|
break;
|
|
}
|
|
printf("\n");
|
|
}
|
|
}
|
|
|
|
static __maybe_unused void JS_DumpValue(JSRuntime *rt, JSValue val)
|
|
{
|
|
uint32_t tag = JS_VALUE_GET_NORM_TAG(val);
|
|
const char *str;
|
|
|
|
switch(tag) {
|
|
case JS_TAG_INT:
|
|
printf("%d", JS_VALUE_GET_INT(val));
|
|
break;
|
|
case JS_TAG_BOOL:
|
|
if (JS_VALUE_GET_BOOL(val))
|
|
str = "true";
|
|
else
|
|
str = "false";
|
|
goto print_str;
|
|
case JS_TAG_NULL:
|
|
str = "null";
|
|
goto print_str;
|
|
case JS_TAG_EXCEPTION:
|
|
str = "exception";
|
|
goto print_str;
|
|
case JS_TAG_UNINITIALIZED:
|
|
str = "uninitialized";
|
|
goto print_str;
|
|
case JS_TAG_UNDEFINED:
|
|
str = "undefined";
|
|
print_str:
|
|
printf("%s", str);
|
|
break;
|
|
case JS_TAG_FLOAT64:
|
|
printf("%.14g", JS_VALUE_GET_FLOAT64(val));
|
|
break;
|
|
case JS_TAG_BIG_INT:
|
|
{
|
|
JSBigInt *p = JS_VALUE_GET_PTR(val);
|
|
char *str;
|
|
str = bf_ftoa(NULL, &p->num, 10, 0,
|
|
BF_RNDZ | BF_FTOA_FORMAT_FRAC);
|
|
printf("%sn", str);
|
|
bf_realloc(&rt->bf_ctx, str, 0);
|
|
}
|
|
break;
|
|
case JS_TAG_STRING:
|
|
{
|
|
JSString *p;
|
|
p = JS_VALUE_GET_STRING(val);
|
|
JS_DumpString(rt, p);
|
|
}
|
|
break;
|
|
case JS_TAG_FUNCTION_BYTECODE:
|
|
{
|
|
JSFunctionBytecode *b = JS_VALUE_GET_PTR(val);
|
|
char buf[ATOM_GET_STR_BUF_SIZE];
|
|
if (b->func_name) {
|
|
printf("[bytecode %s]", JS_AtomGetStrRT(rt, buf, sizeof(buf), b->func_name));
|
|
} else {
|
|
printf("[bytecode (anonymous)]");
|
|
}
|
|
}
|
|
break;
|
|
case JS_TAG_OBJECT:
|
|
{
|
|
JSObject *p = JS_VALUE_GET_OBJ(val);
|
|
JSAtom atom = rt->class_array[p->class_id].class_name;
|
|
char atom_buf[ATOM_GET_STR_BUF_SIZE];
|
|
printf("[%s %p]",
|
|
JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), atom), (void *)p);
|
|
}
|
|
break;
|
|
case JS_TAG_SYMBOL:
|
|
{
|
|
JSAtomStruct *p = JS_VALUE_GET_PTR(val);
|
|
char atom_buf[ATOM_GET_STR_BUF_SIZE];
|
|
printf("Symbol(%s)",
|
|
JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), js_get_atom_index(rt, p)));
|
|
}
|
|
break;
|
|
case JS_TAG_MODULE:
|
|
printf("[module]");
|
|
break;
|
|
default:
|
|
printf("[unknown tag %d]", tag);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* return -1 if exception (proxy case) or TRUE/FALSE */
|
|
int JS_IsArray(JSContext *ctx, JSValue val)
|
|
{
|
|
JSObject *p;
|
|
if (JS_VALUE_GET_TAG(val) == JS_TAG_OBJECT) {
|
|
p = JS_VALUE_GET_OBJ(val);
|
|
if (unlikely(p->class_id == JS_CLASS_PROXY))
|
|
return js_proxy_isArray(ctx, val);
|
|
else
|
|
return p->class_id == JS_CLASS_ARRAY;
|
|
} else {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
static double js_math_pow(double a, double b)
|
|
{
|
|
if (unlikely(!isfinite(b)) && fabs(a) == 1) {
|
|
/* not compatible with IEEE 754 */
|
|
return JS_FLOAT64_NAN;
|
|
} else {
|
|
return pow(a, b);
|
|
}
|
|
}
|
|
|
|
JSValue JS_NewBigInt64(JSContext *ctx, int64_t v)
|
|
{
|
|
JSValue val;
|
|
bf_t *a;
|
|
val = JS_NewBigInt(ctx);
|
|
if (JS_IsException(val))
|
|
return val;
|
|
a = JS_GetBigInt(val);
|
|
if (bf_set_si(a, v)) {
|
|
JS_FreeValue(ctx, val);
|
|
return JS_ThrowOutOfMemory(ctx);
|
|
}
|
|
return val;
|
|
}
|
|
|
|
JSValue JS_NewBigUint64(JSContext *ctx, uint64_t v)
|
|
{
|
|
JSValue val;
|
|
bf_t *a;
|
|
val = JS_NewBigInt(ctx);
|
|
if (JS_IsException(val))
|
|
return val;
|
|
a = JS_GetBigInt(val);
|
|
if (bf_set_ui(a, v)) {
|
|
JS_FreeValue(ctx, val);
|
|
return JS_ThrowOutOfMemory(ctx);
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
/* if the returned bigint is allocated it is equal to
|
|
'buf'. Otherwise it is a pointer to the bigint in 'val'. Return
|
|
NULL in case of error. */
|
|
// TODO(bnoordhuis) Merge with JS_ToBigInt()
|
|
static bf_t *JS_ToBigInt1(JSContext *ctx, bf_t *buf, JSValue val)
|
|
{
|
|
uint32_t tag;
|
|
bf_t *r;
|
|
JSBigInt *p;
|
|
|
|
tag = JS_VALUE_GET_NORM_TAG(val);
|
|
switch(tag) {
|
|
case JS_TAG_INT:
|
|
case JS_TAG_BOOL:
|
|
case JS_TAG_NULL:
|
|
r = buf;
|
|
bf_init(ctx->bf_ctx, r);
|
|
if (bf_set_si(r, JS_VALUE_GET_INT(val)))
|
|
goto fail;
|
|
break;
|
|
case JS_TAG_FLOAT64:
|
|
r = buf;
|
|
bf_init(ctx->bf_ctx, r);
|
|
if (bf_set_float64(r, JS_VALUE_GET_FLOAT64(val))) {
|
|
fail:
|
|
bf_delete(r);
|
|
return NULL;
|
|
}
|
|
break;
|
|
case JS_TAG_BIG_INT:
|
|
p = JS_VALUE_GET_PTR(val);
|
|
r = &p->num;
|
|
break;
|
|
case JS_TAG_UNDEFINED:
|
|
default:
|
|
r = buf;
|
|
bf_init(ctx->bf_ctx, r);
|
|
bf_set_nan(r);
|
|
break;
|
|
}
|
|
return r;
|
|
}
|
|
|
|
/* return NaN if bad bigint literal */
|
|
static JSValue JS_StringToBigInt(JSContext *ctx, JSValue val)
|
|
{
|
|
const char *str;
|
|
size_t len;
|
|
int flags;
|
|
|
|
str = JS_ToCStringLen(ctx, &len, val);
|
|
JS_FreeValue(ctx, val);
|
|
if (!str)
|
|
return JS_EXCEPTION;
|
|
flags = ATOD_WANT_BIG_INT |
|
|
ATOD_TRIM_SPACES | ATOD_ACCEPT_EMPTY |
|
|
ATOD_ACCEPT_HEX_PREFIX | ATOD_ACCEPT_BIN_OCT |
|
|
ATOD_DECIMAL_AFTER_SIGN | ATOD_NO_TRAILING_CHARS;
|
|
val = js_atof(ctx, str, str + len, NULL, 10, flags);
|
|
JS_FreeCString(ctx, str);
|
|
return val;
|
|
}
|
|
|
|
static JSValue JS_StringToBigIntErr(JSContext *ctx, JSValue val)
|
|
{
|
|
val = JS_StringToBigInt(ctx, val);
|
|
if (JS_VALUE_IS_NAN(val))
|
|
return JS_ThrowSyntaxError(ctx, "invalid BigInt literal");
|
|
return val;
|
|
}
|
|
|
|
/* if the returned bigint is allocated it is equal to
|
|
'buf'. Otherwise it is a pointer to the bigint in 'val'. */
|
|
static bf_t *JS_ToBigIntFree(JSContext *ctx, bf_t *buf, JSValue val)
|
|
{
|
|
uint32_t tag;
|
|
bf_t *r;
|
|
JSBigInt *p;
|
|
|
|
redo:
|
|
tag = JS_VALUE_GET_NORM_TAG(val);
|
|
switch(tag) {
|
|
case JS_TAG_INT:
|
|
case JS_TAG_NULL:
|
|
case JS_TAG_UNDEFINED:
|
|
case JS_TAG_FLOAT64:
|
|
goto fail;
|
|
case JS_TAG_BOOL:
|
|
r = buf;
|
|
bf_init(ctx->bf_ctx, r);
|
|
bf_set_si(r, JS_VALUE_GET_INT(val));
|
|
break;
|
|
case JS_TAG_BIG_INT:
|
|
p = JS_VALUE_GET_PTR(val);
|
|
r = &p->num;
|
|
break;
|
|
case JS_TAG_STRING:
|
|
val = JS_StringToBigIntErr(ctx, val);
|
|
if (JS_IsException(val))
|
|
return NULL;
|
|
goto redo;
|
|
case JS_TAG_OBJECT:
|
|
val = JS_ToPrimitiveFree(ctx, val, HINT_NUMBER);
|
|
if (JS_IsException(val))
|
|
return NULL;
|
|
goto redo;
|
|
default:
|
|
fail:
|
|
JS_FreeValue(ctx, val);
|
|
JS_ThrowTypeError(ctx, "cannot convert to BigInt");
|
|
return NULL;
|
|
}
|
|
return r;
|
|
}
|
|
|
|
static bf_t *JS_ToBigInt(JSContext *ctx, bf_t *buf, JSValue val)
|
|
{
|
|
return JS_ToBigIntFree(ctx, buf, js_dup(val));
|
|
}
|
|
|
|
static __maybe_unused JSValue JS_ToBigIntValueFree(JSContext *ctx, JSValue val)
|
|
{
|
|
if (JS_VALUE_GET_TAG(val) == JS_TAG_BIG_INT) {
|
|
return val;
|
|
} else {
|
|
bf_t a_s, *a, *r;
|
|
int ret;
|
|
JSValue res;
|
|
|
|
res = JS_NewBigInt(ctx);
|
|
if (JS_IsException(res))
|
|
return JS_EXCEPTION;
|
|
a = JS_ToBigIntFree(ctx, &a_s, val);
|
|
if (!a) {
|
|
JS_FreeValue(ctx, res);
|
|
return JS_EXCEPTION;
|
|
}
|
|
r = JS_GetBigInt(res);
|
|
ret = bf_set(r, a);
|
|
JS_FreeBigInt(ctx, a, &a_s);
|
|
if (ret) {
|
|
JS_FreeValue(ctx, res);
|
|
return JS_ThrowOutOfMemory(ctx);
|
|
}
|
|
return JS_CompactBigInt(ctx, res);
|
|
}
|
|
}
|
|
|
|
/* free the bf_t allocated by JS_ToBigInt */
|
|
static void JS_FreeBigInt(JSContext *ctx, bf_t *a, bf_t *buf)
|
|
{
|
|
if (a == buf) {
|
|
bf_delete(a);
|
|
} else {
|
|
JSBigInt *p = (JSBigInt *)((uint8_t *)a - offsetof(JSBigInt, num));
|
|
JS_FreeValue(ctx, JS_MKPTR(JS_TAG_BIG_INT, p));
|
|
}
|
|
}
|
|
|
|
/* XXX: merge with JS_ToInt64Free with a specific flag */
|
|
static int JS_ToBigInt64Free(JSContext *ctx, int64_t *pres, JSValue val)
|
|
{
|
|
bf_t a_s, *a;
|
|
|
|
a = JS_ToBigIntFree(ctx, &a_s, val);
|
|
if (!a) {
|
|
*pres = 0;
|
|
return -1;
|
|
}
|
|
bf_get_int64(pres, a, BF_GET_INT_MOD);
|
|
JS_FreeBigInt(ctx, a, &a_s);
|
|
return 0;
|
|
}
|
|
|
|
int JS_ToBigInt64(JSContext *ctx, int64_t *pres, JSValue val)
|
|
{
|
|
return JS_ToBigInt64Free(ctx, pres, js_dup(val));
|
|
}
|
|
|
|
static JSValue JS_NewBigInt(JSContext *ctx)
|
|
{
|
|
JSBigInt *p;
|
|
p = js_malloc(ctx, sizeof(*p));
|
|
if (!p)
|
|
return JS_EXCEPTION;
|
|
p->header.ref_count = 1;
|
|
bf_init(ctx->bf_ctx, &p->num);
|
|
return JS_MKPTR(JS_TAG_BIG_INT, p);
|
|
}
|
|
|
|
static JSValue JS_CompactBigInt1(JSContext *ctx, JSValue val)
|
|
{
|
|
if (JS_VALUE_GET_TAG(val) != JS_TAG_BIG_INT)
|
|
return val; /* fail safe */
|
|
bf_t *a = JS_GetBigInt(val);
|
|
if (a->expn == BF_EXP_ZERO && a->sign) {
|
|
JSBigInt *p = JS_VALUE_GET_PTR(val);
|
|
assert(p->header.ref_count == 1);
|
|
a->sign = 0;
|
|
}
|
|
return val;
|
|
}
|
|
|
|
/* Nnormalize the zero representation. Could also be used to convert the bigint
|
|
to a short bigint value. The reference count of the value must be
|
|
1. Cannot fail */
|
|
static JSValue JS_CompactBigInt(JSContext *ctx, JSValue val)
|
|
{
|
|
return JS_CompactBigInt1(ctx, val);
|
|
}
|
|
|
|
static JSValue throw_bf_exception(JSContext *ctx, int status)
|
|
{
|
|
const char *str;
|
|
if (status & BF_ST_MEM_ERROR)
|
|
return JS_ThrowOutOfMemory(ctx);
|
|
if (status & BF_ST_DIVIDE_ZERO) {
|
|
str = "division by zero";
|
|
} else if (status & BF_ST_INVALID_OP) {
|
|
str = "invalid operation";
|
|
} else {
|
|
str = "integer overflow";
|
|
}
|
|
return JS_ThrowRangeError(ctx, "%s", str);
|
|
}
|
|
|
|
static int js_unary_arith_bigint(JSContext *ctx,
|
|
JSValue *pres, OPCodeEnum op, JSValue op1)
|
|
{
|
|
bf_t a_s, *r, *a;
|
|
int ret, v;
|
|
JSValue res;
|
|
|
|
if (op == OP_plus) {
|
|
JS_ThrowTypeError(ctx, "BigInt argument with unary +");
|
|
JS_FreeValue(ctx, op1);
|
|
return -1;
|
|
}
|
|
res = JS_NewBigInt(ctx);
|
|
if (JS_IsException(res)) {
|
|
JS_FreeValue(ctx, op1);
|
|
return -1;
|
|
}
|
|
r = JS_GetBigInt(res);
|
|
a = JS_ToBigIntFree(ctx, &a_s, op1); // infallible, always a bigint
|
|
ret = 0;
|
|
switch(op) {
|
|
case OP_inc:
|
|
case OP_dec:
|
|
v = 2 * (op - OP_dec) - 1;
|
|
ret = bf_add_si(r, a, v, BF_PREC_INF, BF_RNDZ);
|
|
break;
|
|
case OP_plus:
|
|
ret = bf_set(r, a);
|
|
break;
|
|
case OP_neg:
|
|
ret = bf_set(r, a);
|
|
bf_neg(r);
|
|
break;
|
|
case OP_not:
|
|
ret = bf_add_si(r, a, 1, BF_PREC_INF, BF_RNDZ);
|
|
bf_neg(r);
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
JS_FreeBigInt(ctx, a, &a_s);
|
|
if (unlikely(ret)) {
|
|
JS_FreeValue(ctx, res);
|
|
throw_bf_exception(ctx, ret);
|
|
return -1;
|
|
}
|
|
res = JS_CompactBigInt(ctx, res);
|
|
*pres = res;
|
|
return 0;
|
|
}
|
|
|
|
static no_inline __exception int js_unary_arith_slow(JSContext *ctx,
|
|
JSValue *sp,
|
|
OPCodeEnum op)
|
|
{
|
|
JSValue op1;
|
|
int v;
|
|
uint32_t tag;
|
|
|
|
op1 = sp[-1];
|
|
/* fast path for float64 */
|
|
if (JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op1)))
|
|
goto handle_float64;
|
|
|
|
op1 = JS_ToNumericFree(ctx, op1);
|
|
if (JS_IsException(op1))
|
|
goto exception;
|
|
tag = JS_VALUE_GET_TAG(op1);
|
|
switch(tag) {
|
|
case JS_TAG_INT:
|
|
{
|
|
int64_t v64;
|
|
v64 = JS_VALUE_GET_INT(op1);
|
|
switch(op) {
|
|
case OP_inc:
|
|
case OP_dec:
|
|
v = 2 * (op - OP_dec) - 1;
|
|
v64 += v;
|
|
break;
|
|
case OP_plus:
|
|
break;
|
|
case OP_neg:
|
|
if (v64 == 0) {
|
|
sp[-1] = js_float64(-0.0);
|
|
return 0;
|
|
} else {
|
|
v64 = -v64;
|
|
}
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
sp[-1] = JS_NewInt64(ctx, v64);
|
|
}
|
|
break;
|
|
case JS_TAG_BIG_INT:
|
|
if (js_unary_arith_bigint(ctx, sp - 1, op, op1))
|
|
goto exception;
|
|
break;
|
|
default:
|
|
handle_float64:
|
|
{
|
|
double d = JS_VALUE_GET_FLOAT64(op1);
|
|
switch(op) {
|
|
case OP_inc:
|
|
case OP_dec:
|
|
v = 2 * (op - OP_dec) - 1;
|
|
d += v;
|
|
break;
|
|
case OP_plus:
|
|
break;
|
|
case OP_neg:
|
|
d = -d;
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
sp[-1] = js_float64(d);
|
|
}
|
|
break;
|
|
}
|
|
return 0;
|
|
exception:
|
|
sp[-1] = JS_UNDEFINED;
|
|
return -1;
|
|
}
|
|
|
|
static __exception int js_post_inc_slow(JSContext *ctx,
|
|
JSValue *sp, OPCodeEnum op)
|
|
{
|
|
JSValue op1;
|
|
|
|
/* XXX: allow custom operators */
|
|
op1 = sp[-1];
|
|
op1 = JS_ToNumericFree(ctx, op1);
|
|
if (JS_IsException(op1)) {
|
|
sp[-1] = JS_UNDEFINED;
|
|
return -1;
|
|
}
|
|
sp[-1] = op1;
|
|
sp[0] = js_dup(op1);
|
|
return js_unary_arith_slow(ctx, sp + 1, op - OP_post_dec + OP_dec);
|
|
}
|
|
|
|
static no_inline int js_not_slow(JSContext *ctx, JSValue *sp)
|
|
{
|
|
JSValue op1;
|
|
|
|
op1 = JS_ToNumericFree(ctx, sp[-1]);
|
|
if (JS_IsException(op1))
|
|
goto exception;
|
|
if (JS_VALUE_GET_TAG(op1) == JS_TAG_BIG_INT) {
|
|
if (js_unary_arith_bigint(ctx, sp - 1, OP_not, op1))
|
|
goto exception;
|
|
} else {
|
|
int32_t v1;
|
|
if (unlikely(JS_ToInt32Free(ctx, &v1, op1)))
|
|
goto exception;
|
|
sp[-1] = js_int32(~v1);
|
|
}
|
|
return 0;
|
|
exception:
|
|
sp[-1] = JS_UNDEFINED;
|
|
return -1;
|
|
}
|
|
|
|
static int js_binary_arith_bigint(JSContext *ctx, OPCodeEnum op,
|
|
JSValue *pres, JSValue op1, JSValue op2)
|
|
{
|
|
bf_t a_s, b_s, *r, *a, *b;
|
|
int ret;
|
|
JSValue res;
|
|
|
|
a = JS_ToBigIntFree(ctx, &a_s, op1);
|
|
if (!a) {
|
|
JS_FreeValue(ctx, op2);
|
|
return -1;
|
|
}
|
|
b = JS_ToBigIntFree(ctx, &b_s, op2);
|
|
if (!b) {
|
|
JS_FreeBigInt(ctx, a, &a_s);
|
|
return -1;
|
|
}
|
|
res = JS_NewBigInt(ctx);
|
|
if (JS_IsException(res)) {
|
|
JS_FreeBigInt(ctx, a, &a_s);
|
|
JS_FreeBigInt(ctx, b, &b_s);
|
|
return -1;
|
|
}
|
|
r = JS_GetBigInt(res);
|
|
ret = 0;
|
|
switch(op) {
|
|
case OP_add:
|
|
ret = bf_add(r, a, b, BF_PREC_INF, BF_RNDZ);
|
|
break;
|
|
case OP_sub:
|
|
ret = bf_sub(r, a, b, BF_PREC_INF, BF_RNDZ);
|
|
break;
|
|
case OP_mul:
|
|
ret = bf_mul(r, a, b, BF_PREC_INF, BF_RNDZ);
|
|
break;
|
|
case OP_div:
|
|
{
|
|
bf_t rem_s, *rem = &rem_s;
|
|
bf_init(ctx->bf_ctx, rem);
|
|
ret = bf_divrem(r, rem, a, b, BF_PREC_INF, BF_RNDZ, BF_RNDZ);
|
|
bf_delete(rem);
|
|
}
|
|
break;
|
|
case OP_mod:
|
|
ret = bf_rem(r, a, b, BF_PREC_INF, BF_RNDZ,
|
|
BF_RNDZ) & BF_ST_INVALID_OP;
|
|
break;
|
|
case OP_pow:
|
|
if (b->sign) {
|
|
ret = BF_ST_INVALID_OP;
|
|
} else {
|
|
ret = bf_pow(r, a, b, BF_PREC_INF, BF_RNDZ | BF_POW_JS_QUIRKS);
|
|
}
|
|
break;
|
|
|
|
/* logical operations */
|
|
case OP_shl:
|
|
case OP_sar:
|
|
{
|
|
slimb_t v2;
|
|
#if LIMB_BITS == 32
|
|
bf_get_int32(&v2, b, 0);
|
|
if (v2 == INT32_MIN)
|
|
v2 = INT32_MIN + 1;
|
|
#else
|
|
bf_get_int64(&v2, b, 0);
|
|
if (v2 == INT64_MIN)
|
|
v2 = INT64_MIN + 1;
|
|
#endif
|
|
if (op == OP_sar)
|
|
v2 = -v2;
|
|
ret = bf_set(r, a);
|
|
ret |= bf_mul_2exp(r, v2, BF_PREC_INF, BF_RNDZ);
|
|
if (v2 < 0) {
|
|
ret |= bf_rint(r, BF_RNDD) & (BF_ST_OVERFLOW | BF_ST_MEM_ERROR);
|
|
}
|
|
}
|
|
break;
|
|
case OP_and:
|
|
ret = bf_logic_and(r, a, b);
|
|
break;
|
|
case OP_or:
|
|
ret = bf_logic_or(r, a, b);
|
|
break;
|
|
case OP_xor:
|
|
ret = bf_logic_xor(r, a, b);
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
JS_FreeBigInt(ctx, a, &a_s);
|
|
JS_FreeBigInt(ctx, b, &b_s);
|
|
if (unlikely(ret)) {
|
|
JS_FreeValue(ctx, res);
|
|
throw_bf_exception(ctx, ret);
|
|
return -1;
|
|
}
|
|
*pres = JS_CompactBigInt(ctx, res);
|
|
return 0;
|
|
}
|
|
|
|
static no_inline __exception int js_binary_arith_slow(JSContext *ctx, JSValue *sp,
|
|
OPCodeEnum op)
|
|
{
|
|
JSValue op1, op2;
|
|
uint32_t tag1, tag2;
|
|
double d1, d2;
|
|
|
|
op1 = sp[-2];
|
|
op2 = sp[-1];
|
|
tag1 = JS_VALUE_GET_NORM_TAG(op1);
|
|
tag2 = JS_VALUE_GET_NORM_TAG(op2);
|
|
/* fast path for float operations */
|
|
if (tag1 == JS_TAG_FLOAT64 && tag2 == JS_TAG_FLOAT64) {
|
|
d1 = JS_VALUE_GET_FLOAT64(op1);
|
|
d2 = JS_VALUE_GET_FLOAT64(op2);
|
|
goto handle_float64;
|
|
}
|
|
|
|
op1 = JS_ToNumericFree(ctx, op1);
|
|
if (JS_IsException(op1)) {
|
|
JS_FreeValue(ctx, op2);
|
|
goto exception;
|
|
}
|
|
op2 = JS_ToNumericFree(ctx, op2);
|
|
if (JS_IsException(op2)) {
|
|
JS_FreeValue(ctx, op1);
|
|
goto exception;
|
|
}
|
|
tag1 = JS_VALUE_GET_NORM_TAG(op1);
|
|
tag2 = JS_VALUE_GET_NORM_TAG(op2);
|
|
|
|
if (tag1 == JS_TAG_INT && tag2 == JS_TAG_INT) {
|
|
int32_t v1, v2;
|
|
int64_t v;
|
|
v1 = JS_VALUE_GET_INT(op1);
|
|
v2 = JS_VALUE_GET_INT(op2);
|
|
switch(op) {
|
|
case OP_sub:
|
|
v = (int64_t)v1 - (int64_t)v2;
|
|
break;
|
|
case OP_mul:
|
|
v = (int64_t)v1 * (int64_t)v2;
|
|
if (v == 0 && (v1 | v2) < 0) {
|
|
sp[-2] = js_float64(-0.0);
|
|
return 0;
|
|
}
|
|
break;
|
|
case OP_div:
|
|
sp[-2] = js_float64((double)v1 / (double)v2);
|
|
return 0;
|
|
case OP_mod:
|
|
if (v1 < 0 || v2 <= 0) {
|
|
sp[-2] = js_number(fmod(v1, v2));
|
|
return 0;
|
|
} else {
|
|
v = (int64_t)v1 % (int64_t)v2;
|
|
}
|
|
break;
|
|
case OP_pow:
|
|
sp[-2] = js_number(js_math_pow(v1, v2));
|
|
return 0;
|
|
default:
|
|
abort();
|
|
}
|
|
sp[-2] = JS_NewInt64(ctx, v);
|
|
} else if (tag1 == JS_TAG_BIG_INT || tag2 == JS_TAG_BIG_INT) {
|
|
if (js_binary_arith_bigint(ctx, op, sp - 2, op1, op2))
|
|
goto exception;
|
|
} else {
|
|
double dr;
|
|
/* float64 result */
|
|
if (JS_ToFloat64Free(ctx, &d1, op1)) {
|
|
JS_FreeValue(ctx, op2);
|
|
goto exception;
|
|
}
|
|
if (JS_ToFloat64Free(ctx, &d2, op2))
|
|
goto exception;
|
|
handle_float64:
|
|
switch(op) {
|
|
case OP_sub:
|
|
dr = d1 - d2;
|
|
break;
|
|
case OP_mul:
|
|
dr = d1 * d2;
|
|
break;
|
|
case OP_div:
|
|
dr = d1 / d2;
|
|
break;
|
|
case OP_mod:
|
|
dr = fmod(d1, d2);
|
|
break;
|
|
case OP_pow:
|
|
dr = js_math_pow(d1, d2);
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
sp[-2] = js_float64(dr);
|
|
}
|
|
return 0;
|
|
exception:
|
|
sp[-2] = JS_UNDEFINED;
|
|
sp[-1] = JS_UNDEFINED;
|
|
return -1;
|
|
}
|
|
|
|
static no_inline __exception int js_add_slow(JSContext *ctx, JSValue *sp)
|
|
{
|
|
JSValue op1, op2;
|
|
uint32_t tag1, tag2;
|
|
|
|
op1 = sp[-2];
|
|
op2 = sp[-1];
|
|
|
|
tag1 = JS_VALUE_GET_NORM_TAG(op1);
|
|
tag2 = JS_VALUE_GET_NORM_TAG(op2);
|
|
/* fast path for float64 */
|
|
if (tag1 == JS_TAG_FLOAT64 && tag2 == JS_TAG_FLOAT64) {
|
|
double d1, d2;
|
|
d1 = JS_VALUE_GET_FLOAT64(op1);
|
|
d2 = JS_VALUE_GET_FLOAT64(op2);
|
|
sp[-2] = js_float64(d1 + d2);
|
|
return 0;
|
|
}
|
|
|
|
if (tag1 == JS_TAG_OBJECT || tag2 == JS_TAG_OBJECT) {
|
|
op1 = JS_ToPrimitiveFree(ctx, op1, HINT_NONE);
|
|
if (JS_IsException(op1)) {
|
|
JS_FreeValue(ctx, op2);
|
|
goto exception;
|
|
}
|
|
|
|
op2 = JS_ToPrimitiveFree(ctx, op2, HINT_NONE);
|
|
if (JS_IsException(op2)) {
|
|
JS_FreeValue(ctx, op1);
|
|
goto exception;
|
|
}
|
|
tag1 = JS_VALUE_GET_NORM_TAG(op1);
|
|
tag2 = JS_VALUE_GET_NORM_TAG(op2);
|
|
}
|
|
|
|
if (tag1 == JS_TAG_STRING || tag2 == JS_TAG_STRING) {
|
|
sp[-2] = JS_ConcatString(ctx, op1, op2);
|
|
if (JS_IsException(sp[-2]))
|
|
goto exception;
|
|
return 0;
|
|
}
|
|
|
|
op1 = JS_ToNumericFree(ctx, op1);
|
|
if (JS_IsException(op1)) {
|
|
JS_FreeValue(ctx, op2);
|
|
goto exception;
|
|
}
|
|
op2 = JS_ToNumericFree(ctx, op2);
|
|
if (JS_IsException(op2)) {
|
|
JS_FreeValue(ctx, op1);
|
|
goto exception;
|
|
}
|
|
tag1 = JS_VALUE_GET_NORM_TAG(op1);
|
|
tag2 = JS_VALUE_GET_NORM_TAG(op2);
|
|
|
|
if (tag1 == JS_TAG_INT && tag2 == JS_TAG_INT) {
|
|
int32_t v1, v2;
|
|
int64_t v;
|
|
v1 = JS_VALUE_GET_INT(op1);
|
|
v2 = JS_VALUE_GET_INT(op2);
|
|
v = (int64_t)v1 + (int64_t)v2;
|
|
sp[-2] = JS_NewInt64(ctx, v);
|
|
} else if (tag1 == JS_TAG_BIG_INT || tag2 == JS_TAG_BIG_INT) {
|
|
if (js_binary_arith_bigint(ctx, OP_add, sp - 2, op1, op2))
|
|
goto exception;
|
|
} else {
|
|
double d1, d2;
|
|
/* float64 result */
|
|
if (JS_ToFloat64Free(ctx, &d1, op1)) {
|
|
JS_FreeValue(ctx, op2);
|
|
goto exception;
|
|
}
|
|
if (JS_ToFloat64Free(ctx, &d2, op2))
|
|
goto exception;
|
|
sp[-2] = js_float64(d1 + d2);
|
|
}
|
|
return 0;
|
|
exception:
|
|
sp[-2] = JS_UNDEFINED;
|
|
sp[-1] = JS_UNDEFINED;
|
|
return -1;
|
|
}
|
|
|
|
static no_inline __exception int js_binary_logic_slow(JSContext *ctx,
|
|
JSValue *sp,
|
|
OPCodeEnum op)
|
|
{
|
|
JSValue op1, op2;
|
|
uint32_t tag1, tag2;
|
|
uint32_t v1, v2, r;
|
|
|
|
op1 = sp[-2];
|
|
op2 = sp[-1];
|
|
tag1 = JS_VALUE_GET_NORM_TAG(op1);
|
|
tag2 = JS_VALUE_GET_NORM_TAG(op2);
|
|
|
|
op1 = JS_ToNumericFree(ctx, op1);
|
|
if (JS_IsException(op1)) {
|
|
JS_FreeValue(ctx, op2);
|
|
goto exception;
|
|
}
|
|
op2 = JS_ToNumericFree(ctx, op2);
|
|
if (JS_IsException(op2)) {
|
|
JS_FreeValue(ctx, op1);
|
|
goto exception;
|
|
}
|
|
|
|
tag1 = JS_VALUE_GET_TAG(op1);
|
|
tag2 = JS_VALUE_GET_TAG(op2);
|
|
if (tag1 == JS_TAG_BIG_INT || tag2 == JS_TAG_BIG_INT) {
|
|
if (tag1 != tag2) {
|
|
JS_FreeValue(ctx, op1);
|
|
JS_FreeValue(ctx, op2);
|
|
JS_ThrowTypeError(ctx, "both operands must be BigInt");
|
|
goto exception;
|
|
} else if (js_binary_arith_bigint(ctx, op, sp - 2, op1, op2)) {
|
|
goto exception;
|
|
}
|
|
} else {
|
|
if (unlikely(JS_ToInt32Free(ctx, (int32_t *)&v1, op1))) {
|
|
JS_FreeValue(ctx, op2);
|
|
goto exception;
|
|
}
|
|
if (unlikely(JS_ToInt32Free(ctx, (int32_t *)&v2, op2)))
|
|
goto exception;
|
|
switch(op) {
|
|
case OP_shl:
|
|
r = v1 << (v2 & 0x1f);
|
|
break;
|
|
case OP_sar:
|
|
r = (int)v1 >> (v2 & 0x1f);
|
|
break;
|
|
case OP_and:
|
|
r = v1 & v2;
|
|
break;
|
|
case OP_or:
|
|
r = v1 | v2;
|
|
break;
|
|
case OP_xor:
|
|
r = v1 ^ v2;
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
sp[-2] = js_int32(r);
|
|
}
|
|
return 0;
|
|
exception:
|
|
sp[-2] = JS_UNDEFINED;
|
|
sp[-1] = JS_UNDEFINED;
|
|
return -1;
|
|
}
|
|
|
|
static int js_compare_bigint(JSContext *ctx, OPCodeEnum op,
|
|
JSValue op1, JSValue op2)
|
|
{
|
|
bf_t a_s, b_s, *a, *b;
|
|
int res;
|
|
|
|
a = JS_ToBigInt1(ctx, &a_s, op1);
|
|
if (!a) {
|
|
JS_FreeValue(ctx, op2);
|
|
return -1;
|
|
}
|
|
b = JS_ToBigInt1(ctx, &b_s, op2);
|
|
if (!b) {
|
|
if (a == &a_s)
|
|
bf_delete(a);
|
|
JS_FreeValue(ctx, op1);
|
|
return -1;
|
|
}
|
|
switch(op) {
|
|
case OP_lt:
|
|
res = bf_cmp_lt(a, b); /* if NaN return false */
|
|
break;
|
|
case OP_lte:
|
|
res = bf_cmp_le(a, b); /* if NaN return false */
|
|
break;
|
|
case OP_gt:
|
|
res = bf_cmp_lt(b, a); /* if NaN return false */
|
|
break;
|
|
case OP_gte:
|
|
res = bf_cmp_le(b, a); /* if NaN return false */
|
|
break;
|
|
case OP_eq:
|
|
res = bf_cmp_eq(a, b); /* if NaN return false */
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
if (a == &a_s)
|
|
bf_delete(a);
|
|
if (b == &b_s)
|
|
bf_delete(b);
|
|
JS_FreeValue(ctx, op1);
|
|
JS_FreeValue(ctx, op2);
|
|
return res;
|
|
}
|
|
|
|
static no_inline int js_relational_slow(JSContext *ctx, JSValue *sp,
|
|
OPCodeEnum op)
|
|
{
|
|
JSValue op1, op2;
|
|
int res;
|
|
uint32_t tag1, tag2;
|
|
|
|
op1 = sp[-2];
|
|
op2 = sp[-1];
|
|
tag1 = JS_VALUE_GET_NORM_TAG(op1);
|
|
tag2 = JS_VALUE_GET_NORM_TAG(op2);
|
|
|
|
op1 = JS_ToPrimitiveFree(ctx, op1, HINT_NUMBER);
|
|
if (JS_IsException(op1)) {
|
|
JS_FreeValue(ctx, op2);
|
|
goto exception;
|
|
}
|
|
op2 = JS_ToPrimitiveFree(ctx, op2, HINT_NUMBER);
|
|
if (JS_IsException(op2)) {
|
|
JS_FreeValue(ctx, op1);
|
|
goto exception;
|
|
}
|
|
tag1 = JS_VALUE_GET_NORM_TAG(op1);
|
|
tag2 = JS_VALUE_GET_NORM_TAG(op2);
|
|
|
|
if (tag1 == JS_TAG_STRING && tag2 == JS_TAG_STRING) {
|
|
JSString *p1, *p2;
|
|
p1 = JS_VALUE_GET_STRING(op1);
|
|
p2 = JS_VALUE_GET_STRING(op2);
|
|
res = js_string_compare(ctx, p1, p2);
|
|
switch(op) {
|
|
case OP_lt:
|
|
res = (res < 0);
|
|
break;
|
|
case OP_lte:
|
|
res = (res <= 0);
|
|
break;
|
|
case OP_gt:
|
|
res = (res > 0);
|
|
break;
|
|
default:
|
|
case OP_gte:
|
|
res = (res >= 0);
|
|
break;
|
|
}
|
|
JS_FreeValue(ctx, op1);
|
|
JS_FreeValue(ctx, op2);
|
|
} else if ((tag1 <= JS_TAG_NULL || tag1 == JS_TAG_FLOAT64) &&
|
|
(tag2 <= JS_TAG_NULL || tag2 == JS_TAG_FLOAT64)) {
|
|
/* fast path for float64/int */
|
|
goto float64_compare;
|
|
} else {
|
|
if (((tag1 == JS_TAG_BIG_INT && tag2 == JS_TAG_STRING) ||
|
|
(tag2 == JS_TAG_BIG_INT && tag1 == JS_TAG_STRING))) {
|
|
if (tag1 == JS_TAG_STRING) {
|
|
op1 = JS_StringToBigInt(ctx, op1);
|
|
if (JS_VALUE_GET_TAG(op1) != JS_TAG_BIG_INT)
|
|
goto invalid_bigint_string;
|
|
}
|
|
if (tag2 == JS_TAG_STRING) {
|
|
op2 = JS_StringToBigInt(ctx, op2);
|
|
if (JS_VALUE_GET_TAG(op2) != JS_TAG_BIG_INT) {
|
|
invalid_bigint_string:
|
|
JS_FreeValue(ctx, op1);
|
|
JS_FreeValue(ctx, op2);
|
|
res = FALSE;
|
|
goto done;
|
|
}
|
|
}
|
|
} else {
|
|
op1 = JS_ToNumericFree(ctx, op1);
|
|
if (JS_IsException(op1)) {
|
|
JS_FreeValue(ctx, op2);
|
|
goto exception;
|
|
}
|
|
op2 = JS_ToNumericFree(ctx, op2);
|
|
if (JS_IsException(op2)) {
|
|
JS_FreeValue(ctx, op1);
|
|
goto exception;
|
|
}
|
|
}
|
|
|
|
tag1 = JS_VALUE_GET_NORM_TAG(op1);
|
|
tag2 = JS_VALUE_GET_NORM_TAG(op2);
|
|
|
|
if (tag1 == JS_TAG_BIG_INT || tag2 == JS_TAG_BIG_INT) {
|
|
res = js_compare_bigint(ctx, op, op1, op2);
|
|
if (res < 0)
|
|
goto exception;
|
|
} else {
|
|
double d1, d2;
|
|
|
|
float64_compare:
|
|
/* can use floating point comparison */
|
|
if (tag1 == JS_TAG_FLOAT64) {
|
|
d1 = JS_VALUE_GET_FLOAT64(op1);
|
|
} else {
|
|
d1 = JS_VALUE_GET_INT(op1);
|
|
}
|
|
if (tag2 == JS_TAG_FLOAT64) {
|
|
d2 = JS_VALUE_GET_FLOAT64(op2);
|
|
} else {
|
|
d2 = JS_VALUE_GET_INT(op2);
|
|
}
|
|
switch(op) {
|
|
case OP_lt:
|
|
res = (d1 < d2); /* if NaN return false */
|
|
break;
|
|
case OP_lte:
|
|
res = (d1 <= d2); /* if NaN return false */
|
|
break;
|
|
case OP_gt:
|
|
res = (d1 > d2); /* if NaN return false */
|
|
break;
|
|
default:
|
|
case OP_gte:
|
|
res = (d1 >= d2); /* if NaN return false */
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
done:
|
|
sp[-2] = js_bool(res);
|
|
return 0;
|
|
exception:
|
|
sp[-2] = JS_UNDEFINED;
|
|
sp[-1] = JS_UNDEFINED;
|
|
return -1;
|
|
}
|
|
|
|
static BOOL tag_is_number(uint32_t tag)
|
|
{
|
|
return (tag == JS_TAG_INT || tag == JS_TAG_BIG_INT ||
|
|
tag == JS_TAG_FLOAT64);
|
|
}
|
|
|
|
static no_inline __exception int js_eq_slow(JSContext *ctx, JSValue *sp,
|
|
BOOL is_neq)
|
|
{
|
|
JSValue op1, op2;
|
|
int res;
|
|
uint32_t tag1, tag2;
|
|
|
|
op1 = sp[-2];
|
|
op2 = sp[-1];
|
|
redo:
|
|
tag1 = JS_VALUE_GET_NORM_TAG(op1);
|
|
tag2 = JS_VALUE_GET_NORM_TAG(op2);
|
|
if (tag_is_number(tag1) && tag_is_number(tag2)) {
|
|
if (tag1 == JS_TAG_INT && tag2 == JS_TAG_INT) {
|
|
res = JS_VALUE_GET_INT(op1) == JS_VALUE_GET_INT(op2);
|
|
} else if ((tag1 == JS_TAG_FLOAT64 &&
|
|
(tag2 == JS_TAG_INT || tag2 == JS_TAG_FLOAT64)) ||
|
|
(tag2 == JS_TAG_FLOAT64 &&
|
|
(tag1 == JS_TAG_INT || tag1 == JS_TAG_FLOAT64))) {
|
|
double d1, d2;
|
|
if (tag1 == JS_TAG_FLOAT64) {
|
|
d1 = JS_VALUE_GET_FLOAT64(op1);
|
|
} else {
|
|
d1 = JS_VALUE_GET_INT(op1);
|
|
}
|
|
if (tag2 == JS_TAG_FLOAT64) {
|
|
d2 = JS_VALUE_GET_FLOAT64(op2);
|
|
} else {
|
|
d2 = JS_VALUE_GET_INT(op2);
|
|
}
|
|
res = (d1 == d2);
|
|
} else {
|
|
res = js_compare_bigint(ctx, OP_eq, op1, op2);
|
|
if (res < 0)
|
|
goto exception;
|
|
}
|
|
} else if (tag1 == tag2) {
|
|
res = js_strict_eq2(ctx, op1, op2, JS_EQ_STRICT);
|
|
} else if ((tag1 == JS_TAG_NULL && tag2 == JS_TAG_UNDEFINED) ||
|
|
(tag2 == JS_TAG_NULL && tag1 == JS_TAG_UNDEFINED)) {
|
|
res = TRUE;
|
|
} else if ((tag1 == JS_TAG_STRING && tag_is_number(tag2)) ||
|
|
(tag2 == JS_TAG_STRING && tag_is_number(tag1))) {
|
|
|
|
if ((tag1 == JS_TAG_BIG_INT || tag2 == JS_TAG_BIG_INT)) {
|
|
if (tag1 == JS_TAG_STRING) {
|
|
op1 = JS_StringToBigInt(ctx, op1);
|
|
if (JS_VALUE_GET_TAG(op1) != JS_TAG_BIG_INT)
|
|
goto invalid_bigint_string;
|
|
}
|
|
if (tag2 == JS_TAG_STRING) {
|
|
op2 = JS_StringToBigInt(ctx, op2);
|
|
if (JS_VALUE_GET_TAG(op2) != JS_TAG_BIG_INT) {
|
|
invalid_bigint_string:
|
|
JS_FreeValue(ctx, op1);
|
|
JS_FreeValue(ctx, op2);
|
|
res = FALSE;
|
|
goto done;
|
|
}
|
|
}
|
|
} else {
|
|
op1 = JS_ToNumericFree(ctx, op1);
|
|
if (JS_IsException(op1)) {
|
|
JS_FreeValue(ctx, op2);
|
|
goto exception;
|
|
}
|
|
op2 = JS_ToNumericFree(ctx, op2);
|
|
if (JS_IsException(op2)) {
|
|
JS_FreeValue(ctx, op1);
|
|
goto exception;
|
|
}
|
|
}
|
|
res = js_strict_eq(ctx, op1, op2);
|
|
} else if (tag1 == JS_TAG_BOOL) {
|
|
op1 = js_int32(JS_VALUE_GET_INT(op1));
|
|
goto redo;
|
|
} else if (tag2 == JS_TAG_BOOL) {
|
|
op2 = js_int32(JS_VALUE_GET_INT(op2));
|
|
goto redo;
|
|
} else if ((tag1 == JS_TAG_OBJECT &&
|
|
(tag_is_number(tag2) || tag2 == JS_TAG_STRING || tag2 == JS_TAG_SYMBOL)) ||
|
|
(tag2 == JS_TAG_OBJECT &&
|
|
(tag_is_number(tag1) || tag1 == JS_TAG_STRING || tag1 == JS_TAG_SYMBOL))) {
|
|
op1 = JS_ToPrimitiveFree(ctx, op1, HINT_NONE);
|
|
if (JS_IsException(op1)) {
|
|
JS_FreeValue(ctx, op2);
|
|
goto exception;
|
|
}
|
|
op2 = JS_ToPrimitiveFree(ctx, op2, HINT_NONE);
|
|
if (JS_IsException(op2)) {
|
|
JS_FreeValue(ctx, op1);
|
|
goto exception;
|
|
}
|
|
goto redo;
|
|
} else {
|
|
/* IsHTMLDDA object is equivalent to undefined for '==' and '!=' */
|
|
if ((JS_IsHTMLDDA(ctx, op1) &&
|
|
(tag2 == JS_TAG_NULL || tag2 == JS_TAG_UNDEFINED)) ||
|
|
(JS_IsHTMLDDA(ctx, op2) &&
|
|
(tag1 == JS_TAG_NULL || tag1 == JS_TAG_UNDEFINED))) {
|
|
res = TRUE;
|
|
} else {
|
|
res = FALSE;
|
|
}
|
|
JS_FreeValue(ctx, op1);
|
|
JS_FreeValue(ctx, op2);
|
|
}
|
|
done:
|
|
sp[-2] = js_bool(res ^ is_neq);
|
|
return 0;
|
|
exception:
|
|
sp[-2] = JS_UNDEFINED;
|
|
sp[-1] = JS_UNDEFINED;
|
|
return -1;
|
|
}
|
|
|
|
static no_inline int js_shr_slow(JSContext *ctx, JSValue *sp)
|
|
{
|
|
JSValue op1, op2;
|
|
uint32_t v1, v2, r;
|
|
|
|
op1 = sp[-2];
|
|
op2 = sp[-1];
|
|
op1 = JS_ToNumericFree(ctx, op1);
|
|
if (JS_IsException(op1)) {
|
|
JS_FreeValue(ctx, op2);
|
|
goto exception;
|
|
}
|
|
op2 = JS_ToNumericFree(ctx, op2);
|
|
if (JS_IsException(op2)) {
|
|
JS_FreeValue(ctx, op1);
|
|
goto exception;
|
|
}
|
|
|
|
if ((JS_VALUE_GET_TAG(op1) == JS_TAG_BIG_INT ||
|
|
JS_VALUE_GET_TAG(op2) == JS_TAG_BIG_INT)) {
|
|
JS_ThrowTypeError(ctx, "BigInt operands are forbidden for >>>");
|
|
JS_FreeValue(ctx, op1);
|
|
JS_FreeValue(ctx, op2);
|
|
goto exception;
|
|
}
|
|
/* cannot give an exception */
|
|
JS_ToUint32Free(ctx, &v1, op1);
|
|
JS_ToUint32Free(ctx, &v2, op2);
|
|
r = v1 >> (v2 & 0x1f);
|
|
sp[-2] = js_uint32(r);
|
|
return 0;
|
|
exception:
|
|
sp[-2] = JS_UNDEFINED;
|
|
sp[-1] = JS_UNDEFINED;
|
|
return -1;
|
|
}
|
|
|
|
/* XXX: Should take JSValue arguments */
|
|
static BOOL js_strict_eq2(JSContext *ctx, JSValue op1, JSValue op2,
|
|
JSStrictEqModeEnum eq_mode)
|
|
{
|
|
BOOL res;
|
|
int tag1, tag2;
|
|
double d1, d2;
|
|
|
|
tag1 = JS_VALUE_GET_NORM_TAG(op1);
|
|
tag2 = JS_VALUE_GET_NORM_TAG(op2);
|
|
switch(tag1) {
|
|
case JS_TAG_BOOL:
|
|
if (tag1 != tag2) {
|
|
res = FALSE;
|
|
} else {
|
|
res = JS_VALUE_GET_INT(op1) == JS_VALUE_GET_INT(op2);
|
|
goto done_no_free;
|
|
}
|
|
break;
|
|
case JS_TAG_NULL:
|
|
case JS_TAG_UNDEFINED:
|
|
res = (tag1 == tag2);
|
|
break;
|
|
case JS_TAG_STRING:
|
|
{
|
|
JSString *p1, *p2;
|
|
if (tag1 != tag2) {
|
|
res = FALSE;
|
|
} else {
|
|
p1 = JS_VALUE_GET_STRING(op1);
|
|
p2 = JS_VALUE_GET_STRING(op2);
|
|
res = (js_string_compare(ctx, p1, p2) == 0);
|
|
}
|
|
}
|
|
break;
|
|
case JS_TAG_SYMBOL:
|
|
{
|
|
JSAtomStruct *p1, *p2;
|
|
if (tag1 != tag2) {
|
|
res = FALSE;
|
|
} else {
|
|
p1 = JS_VALUE_GET_PTR(op1);
|
|
p2 = JS_VALUE_GET_PTR(op2);
|
|
res = (p1 == p2);
|
|
}
|
|
}
|
|
break;
|
|
case JS_TAG_OBJECT:
|
|
if (tag1 != tag2)
|
|
res = FALSE;
|
|
else
|
|
res = JS_VALUE_GET_OBJ(op1) == JS_VALUE_GET_OBJ(op2);
|
|
break;
|
|
case JS_TAG_INT:
|
|
d1 = JS_VALUE_GET_INT(op1);
|
|
if (tag2 == JS_TAG_INT) {
|
|
d2 = JS_VALUE_GET_INT(op2);
|
|
goto number_test;
|
|
} else if (tag2 == JS_TAG_FLOAT64) {
|
|
d2 = JS_VALUE_GET_FLOAT64(op2);
|
|
goto number_test;
|
|
} else {
|
|
res = FALSE;
|
|
}
|
|
break;
|
|
case JS_TAG_FLOAT64:
|
|
d1 = JS_VALUE_GET_FLOAT64(op1);
|
|
if (tag2 == JS_TAG_FLOAT64) {
|
|
d2 = JS_VALUE_GET_FLOAT64(op2);
|
|
} else if (tag2 == JS_TAG_INT) {
|
|
d2 = JS_VALUE_GET_INT(op2);
|
|
} else {
|
|
res = FALSE;
|
|
break;
|
|
}
|
|
number_test:
|
|
if (unlikely(eq_mode >= JS_EQ_SAME_VALUE)) {
|
|
JSFloat64Union u1, u2;
|
|
/* NaN is not always normalized, so this test is necessary */
|
|
if (isnan(d1) || isnan(d2)) {
|
|
res = isnan(d1) == isnan(d2);
|
|
} else if (eq_mode == JS_EQ_SAME_VALUE_ZERO) {
|
|
res = (d1 == d2); /* +0 == -0 */
|
|
} else {
|
|
u1.d = d1;
|
|
u2.d = d2;
|
|
res = (u1.u64 == u2.u64); /* +0 != -0 */
|
|
}
|
|
} else {
|
|
res = (d1 == d2); /* if NaN return false and +0 == -0 */
|
|
}
|
|
goto done_no_free;
|
|
case JS_TAG_BIG_INT:
|
|
{
|
|
bf_t a_s, *a, b_s, *b;
|
|
if (tag1 != tag2) {
|
|
res = FALSE;
|
|
break;
|
|
}
|
|
a = JS_ToBigInt1(ctx, &a_s, op1);
|
|
b = JS_ToBigInt1(ctx, &b_s, op2);
|
|
res = bf_cmp_eq(a, b);
|
|
if (a == &a_s)
|
|
bf_delete(a);
|
|
if (b == &b_s)
|
|
bf_delete(b);
|
|
}
|
|
break;
|
|
default:
|
|
res = FALSE;
|
|
break;
|
|
}
|
|
JS_FreeValue(ctx, op1);
|
|
JS_FreeValue(ctx, op2);
|
|
done_no_free:
|
|
return res;
|
|
}
|
|
|
|
static BOOL js_strict_eq(JSContext *ctx, JSValue op1, JSValue op2)
|
|
{
|
|
return js_strict_eq2(ctx, op1, op2, JS_EQ_STRICT);
|
|
}
|
|
|
|
static BOOL js_same_value(JSContext *ctx, JSValue op1, JSValue op2)
|
|
{
|
|
return js_strict_eq2(ctx, js_dup(op1), js_dup(op2), JS_EQ_SAME_VALUE);
|
|
}
|
|
|
|
static BOOL js_same_value_zero(JSContext *ctx, JSValue op1, JSValue op2)
|
|
{
|
|
return js_strict_eq2(ctx, js_dup(op1), js_dup(op2), JS_EQ_SAME_VALUE_ZERO);
|
|
}
|
|
|
|
static no_inline int js_strict_eq_slow(JSContext *ctx, JSValue *sp,
|
|
BOOL is_neq)
|
|
{
|
|
BOOL res;
|
|
res = js_strict_eq(ctx, sp[-2], sp[-1]);
|
|
sp[-2] = js_bool(res ^ is_neq);
|
|
return 0;
|
|
}
|
|
|
|
static __exception int js_operator_in(JSContext *ctx, JSValue *sp)
|
|
{
|
|
JSValue op1, op2;
|
|
JSAtom atom;
|
|
int ret;
|
|
|
|
op1 = sp[-2];
|
|
op2 = sp[-1];
|
|
|
|
if (JS_VALUE_GET_TAG(op2) != JS_TAG_OBJECT) {
|
|
JS_ThrowTypeError(ctx, "invalid 'in' operand");
|
|
return -1;
|
|
}
|
|
atom = JS_ValueToAtom(ctx, op1);
|
|
if (unlikely(atom == JS_ATOM_NULL))
|
|
return -1;
|
|
ret = JS_HasProperty(ctx, op2, atom);
|
|
JS_FreeAtom(ctx, atom);
|
|
if (ret < 0)
|
|
return -1;
|
|
JS_FreeValue(ctx, op1);
|
|
JS_FreeValue(ctx, op2);
|
|
sp[-2] = js_bool(ret);
|
|
return 0;
|
|
}
|
|
|
|
static __exception int js_has_unscopable(JSContext *ctx, JSValue obj,
|
|
JSAtom atom)
|
|
{
|
|
JSValue arr, val;
|
|
int ret;
|
|
|
|
arr = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_unscopables);
|
|
if (JS_IsException(arr))
|
|
return -1;
|
|
ret = 0;
|
|
if (JS_IsObject(arr)) {
|
|
val = JS_GetProperty(ctx, arr, atom);
|
|
ret = JS_ToBoolFree(ctx, val);
|
|
}
|
|
JS_FreeValue(ctx, arr);
|
|
return ret;
|
|
}
|
|
|
|
static __exception int js_operator_instanceof(JSContext *ctx, JSValue *sp)
|
|
{
|
|
JSValue op1, op2;
|
|
BOOL ret;
|
|
|
|
op1 = sp[-2];
|
|
op2 = sp[-1];
|
|
ret = JS_IsInstanceOf(ctx, op1, op2);
|
|
if (ret < 0)
|
|
return ret;
|
|
JS_FreeValue(ctx, op1);
|
|
JS_FreeValue(ctx, op2);
|
|
sp[-2] = js_bool(ret);
|
|
return 0;
|
|
}
|
|
|
|
static __exception int js_operator_typeof(JSContext *ctx, JSValue op1)
|
|
{
|
|
JSAtom atom;
|
|
uint32_t tag;
|
|
|
|
tag = JS_VALUE_GET_NORM_TAG(op1);
|
|
switch(tag) {
|
|
case JS_TAG_BIG_INT:
|
|
atom = JS_ATOM_bigint;
|
|
break;
|
|
case JS_TAG_INT:
|
|
case JS_TAG_FLOAT64:
|
|
atom = JS_ATOM_number;
|
|
break;
|
|
case JS_TAG_UNDEFINED:
|
|
atom = JS_ATOM_undefined;
|
|
break;
|
|
case JS_TAG_BOOL:
|
|
atom = JS_ATOM_boolean;
|
|
break;
|
|
case JS_TAG_STRING:
|
|
atom = JS_ATOM_string;
|
|
break;
|
|
case JS_TAG_OBJECT:
|
|
{
|
|
JSObject *p;
|
|
p = JS_VALUE_GET_OBJ(op1);
|
|
if (unlikely(p->is_HTMLDDA))
|
|
atom = JS_ATOM_undefined;
|
|
else if (JS_IsFunction(ctx, op1))
|
|
atom = JS_ATOM_function;
|
|
else
|
|
goto obj_type;
|
|
}
|
|
break;
|
|
case JS_TAG_NULL:
|
|
obj_type:
|
|
atom = JS_ATOM_object;
|
|
break;
|
|
case JS_TAG_SYMBOL:
|
|
atom = JS_ATOM_symbol;
|
|
break;
|
|
default:
|
|
atom = JS_ATOM_unknown;
|
|
break;
|
|
}
|
|
return atom;
|
|
}
|
|
|
|
static __exception int js_operator_delete(JSContext *ctx, JSValue *sp)
|
|
{
|
|
JSValue op1, op2;
|
|
JSAtom atom;
|
|
int ret;
|
|
|
|
op1 = sp[-2];
|
|
op2 = sp[-1];
|
|
atom = JS_ValueToAtom(ctx, op2);
|
|
if (unlikely(atom == JS_ATOM_NULL))
|
|
return -1;
|
|
ret = JS_DeleteProperty(ctx, op1, atom, JS_PROP_THROW_STRICT);
|
|
JS_FreeAtom(ctx, atom);
|
|
if (unlikely(ret < 0))
|
|
return -1;
|
|
JS_FreeValue(ctx, op1);
|
|
JS_FreeValue(ctx, op2);
|
|
sp[-2] = js_bool(ret);
|
|
return 0;
|
|
}
|
|
|
|
static JSValue js_throw_type_error(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
return JS_ThrowTypeError(ctx, "invalid property access");
|
|
}
|
|
|
|
/* XXX: not 100% compatible, but mozilla seems to use a similar
|
|
implementation to ensure that caller in non strict mode does not
|
|
throw (ES5 compatibility) */
|
|
static JSValue js_function_proto_caller(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSFunctionBytecode *b = JS_GetFunctionBytecode(this_val);
|
|
if (!b || (b->js_mode & JS_MODE_STRICT) || !b->has_prototype) {
|
|
return js_throw_type_error(ctx, this_val, 0, NULL);
|
|
}
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
static JSValue js_function_proto_fileName(JSContext *ctx,
|
|
JSValue this_val)
|
|
{
|
|
JSFunctionBytecode *b = JS_GetFunctionBytecode(this_val);
|
|
if (b) {
|
|
return JS_AtomToString(ctx, b->filename);
|
|
}
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
static JSValue js_function_proto_int32(JSContext *ctx,
|
|
JSValue this_val,
|
|
int magic)
|
|
{
|
|
JSFunctionBytecode *b = JS_GetFunctionBytecode(this_val);
|
|
if (b) {
|
|
int *field = (int *) ((char *)b + magic);
|
|
return js_int32(*field);
|
|
}
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
static int js_arguments_define_own_property(JSContext *ctx,
|
|
JSValue this_obj,
|
|
JSAtom prop, JSValue val,
|
|
JSValue getter, JSValue setter, int flags)
|
|
{
|
|
JSObject *p;
|
|
uint32_t idx;
|
|
p = JS_VALUE_GET_OBJ(this_obj);
|
|
/* convert to normal array when redefining an existing numeric field */
|
|
if (p->fast_array && JS_AtomIsArrayIndex(ctx, &idx, prop) &&
|
|
idx < p->u.array.count) {
|
|
if (convert_fast_array_to_array(ctx, p))
|
|
return -1;
|
|
}
|
|
/* run the default define own property */
|
|
return JS_DefineProperty(ctx, this_obj, prop, val, getter, setter,
|
|
flags | JS_PROP_NO_EXOTIC);
|
|
}
|
|
|
|
static const JSClassExoticMethods js_arguments_exotic_methods = {
|
|
.define_own_property = js_arguments_define_own_property,
|
|
};
|
|
|
|
static JSValue js_build_arguments(JSContext *ctx, int argc, JSValue *argv)
|
|
{
|
|
JSValue val, *tab;
|
|
JSProperty *pr;
|
|
JSObject *p;
|
|
int i;
|
|
|
|
val = JS_NewObjectProtoClass(ctx, ctx->class_proto[JS_CLASS_OBJECT],
|
|
JS_CLASS_ARGUMENTS);
|
|
if (JS_IsException(val))
|
|
return val;
|
|
p = JS_VALUE_GET_OBJ(val);
|
|
|
|
/* add the length field (cannot fail) */
|
|
pr = add_property(ctx, p, JS_ATOM_length,
|
|
JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
|
|
pr->u.value = js_int32(argc);
|
|
|
|
/* initialize the fast array part */
|
|
tab = NULL;
|
|
if (argc > 0) {
|
|
tab = js_malloc(ctx, sizeof(tab[0]) * argc);
|
|
if (!tab) {
|
|
JS_FreeValue(ctx, val);
|
|
return JS_EXCEPTION;
|
|
}
|
|
for(i = 0; i < argc; i++) {
|
|
tab[i] = js_dup(argv[i]);
|
|
}
|
|
}
|
|
p->u.array.u.values = tab;
|
|
p->u.array.count = argc;
|
|
|
|
JS_DefinePropertyValue(ctx, val, JS_ATOM_Symbol_iterator,
|
|
js_dup(ctx->array_proto_values),
|
|
JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE);
|
|
/* add callee property to throw a TypeError in strict mode */
|
|
JS_DefineProperty(ctx, val, JS_ATOM_callee, JS_UNDEFINED,
|
|
ctx->throw_type_error, ctx->throw_type_error,
|
|
JS_PROP_HAS_GET | JS_PROP_HAS_SET);
|
|
return val;
|
|
}
|
|
|
|
#define GLOBAL_VAR_OFFSET 0x40000000
|
|
#define ARGUMENT_VAR_OFFSET 0x20000000
|
|
|
|
/* legacy arguments object: add references to the function arguments */
|
|
static JSValue js_build_mapped_arguments(JSContext *ctx, int argc,
|
|
JSValue *argv,
|
|
JSStackFrame *sf, int arg_count)
|
|
{
|
|
JSValue val;
|
|
JSProperty *pr;
|
|
JSObject *p;
|
|
int i;
|
|
|
|
val = JS_NewObjectProtoClass(ctx, ctx->class_proto[JS_CLASS_OBJECT],
|
|
JS_CLASS_MAPPED_ARGUMENTS);
|
|
if (JS_IsException(val))
|
|
return val;
|
|
p = JS_VALUE_GET_OBJ(val);
|
|
|
|
/* add the length field (cannot fail) */
|
|
pr = add_property(ctx, p, JS_ATOM_length,
|
|
JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
|
|
pr->u.value = js_int32(argc);
|
|
|
|
for(i = 0; i < arg_count; i++) {
|
|
JSVarRef *var_ref;
|
|
var_ref = get_var_ref(ctx, sf, i, TRUE);
|
|
if (!var_ref)
|
|
goto fail;
|
|
pr = add_property(ctx, p, __JS_AtomFromUInt32(i), JS_PROP_C_W_E | JS_PROP_VARREF);
|
|
if (!pr) {
|
|
free_var_ref(ctx->rt, var_ref);
|
|
goto fail;
|
|
}
|
|
pr->u.var_ref = var_ref;
|
|
}
|
|
|
|
/* the arguments not mapped to the arguments of the function can
|
|
be normal properties */
|
|
for(i = arg_count; i < argc; i++) {
|
|
if (JS_DefinePropertyValueUint32(ctx, val, i,
|
|
js_dup(argv[i]),
|
|
JS_PROP_C_W_E) < 0)
|
|
goto fail;
|
|
}
|
|
|
|
JS_DefinePropertyValue(ctx, val, JS_ATOM_Symbol_iterator,
|
|
js_dup(ctx->array_proto_values),
|
|
JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE);
|
|
/* callee returns this function in non strict mode */
|
|
JS_DefinePropertyValue(ctx, val, JS_ATOM_callee,
|
|
js_dup(ctx->rt->current_stack_frame->cur_func),
|
|
JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE);
|
|
return val;
|
|
fail:
|
|
JS_FreeValue(ctx, val);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue js_build_rest(JSContext *ctx, int first, int argc, JSValue *argv)
|
|
{
|
|
JSValue val;
|
|
int i, ret;
|
|
|
|
val = JS_NewArray(ctx);
|
|
if (JS_IsException(val))
|
|
return val;
|
|
for (i = first; i < argc; i++) {
|
|
ret = JS_DefinePropertyValueUint32(ctx, val, i - first,
|
|
js_dup(argv[i]),
|
|
JS_PROP_C_W_E);
|
|
if (ret < 0) {
|
|
JS_FreeValue(ctx, val);
|
|
return JS_EXCEPTION;
|
|
}
|
|
}
|
|
return val;
|
|
}
|
|
|
|
static JSValue build_for_in_iterator(JSContext *ctx, JSValue obj)
|
|
{
|
|
JSObject *p;
|
|
JSPropertyEnum *tab_atom;
|
|
int i;
|
|
JSValue enum_obj, obj1;
|
|
JSForInIterator *it;
|
|
uint32_t tag, tab_atom_count;
|
|
|
|
tag = JS_VALUE_GET_TAG(obj);
|
|
if (tag != JS_TAG_OBJECT && tag != JS_TAG_NULL && tag != JS_TAG_UNDEFINED) {
|
|
obj = JS_ToObjectFree(ctx, obj);
|
|
}
|
|
|
|
it = js_malloc(ctx, sizeof(*it));
|
|
if (!it) {
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
enum_obj = JS_NewObjectProtoClass(ctx, JS_NULL, JS_CLASS_FOR_IN_ITERATOR);
|
|
if (JS_IsException(enum_obj)) {
|
|
js_free(ctx, it);
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
it->is_array = FALSE;
|
|
it->obj = obj;
|
|
it->idx = 0;
|
|
p = JS_VALUE_GET_OBJ(enum_obj);
|
|
p->u.for_in_iterator = it;
|
|
|
|
if (tag == JS_TAG_NULL || tag == JS_TAG_UNDEFINED)
|
|
return enum_obj;
|
|
|
|
/* fast path: assume no enumerable properties in the prototype chain */
|
|
obj1 = js_dup(obj);
|
|
for(;;) {
|
|
obj1 = JS_GetPrototypeFree(ctx, obj1);
|
|
if (JS_IsNull(obj1))
|
|
break;
|
|
if (JS_IsException(obj1))
|
|
goto fail;
|
|
if (JS_GetOwnPropertyNamesInternal(ctx, &tab_atom, &tab_atom_count,
|
|
JS_VALUE_GET_OBJ(obj1),
|
|
JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY)) {
|
|
JS_FreeValue(ctx, obj1);
|
|
goto fail;
|
|
}
|
|
js_free_prop_enum(ctx, tab_atom, tab_atom_count);
|
|
if (tab_atom_count != 0) {
|
|
JS_FreeValue(ctx, obj1);
|
|
goto slow_path;
|
|
}
|
|
/* must check for timeout to avoid infinite loop */
|
|
if (js_poll_interrupts(ctx)) {
|
|
JS_FreeValue(ctx, obj1);
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
p = JS_VALUE_GET_OBJ(obj);
|
|
|
|
if (p->fast_array) {
|
|
JSShape *sh;
|
|
JSShapeProperty *prs;
|
|
/* check that there are no enumerable normal fields */
|
|
sh = p->shape;
|
|
for(i = 0, prs = get_shape_prop(sh); i < sh->prop_count; i++, prs++) {
|
|
if (prs->flags & JS_PROP_ENUMERABLE)
|
|
goto normal_case;
|
|
}
|
|
/* for fast arrays, we only store the number of elements */
|
|
it->is_array = TRUE;
|
|
it->array_length = p->u.array.count;
|
|
} else {
|
|
normal_case:
|
|
if (JS_GetOwnPropertyNamesInternal(ctx, &tab_atom, &tab_atom_count, p,
|
|
JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY))
|
|
goto fail;
|
|
for(i = 0; i < tab_atom_count; i++) {
|
|
JS_SetPropertyInternal(ctx, enum_obj, tab_atom[i].atom, JS_NULL, 0);
|
|
}
|
|
js_free_prop_enum(ctx, tab_atom, tab_atom_count);
|
|
}
|
|
return enum_obj;
|
|
|
|
slow_path:
|
|
/* non enumerable properties hide the enumerables ones in the
|
|
prototype chain */
|
|
obj1 = js_dup(obj);
|
|
for(;;) {
|
|
if (JS_GetOwnPropertyNamesInternal(ctx, &tab_atom, &tab_atom_count,
|
|
JS_VALUE_GET_OBJ(obj1),
|
|
JS_GPN_STRING_MASK | JS_GPN_SET_ENUM)) {
|
|
JS_FreeValue(ctx, obj1);
|
|
goto fail;
|
|
}
|
|
for(i = 0; i < tab_atom_count; i++) {
|
|
JS_DefinePropertyValue(ctx, enum_obj, tab_atom[i].atom, JS_NULL,
|
|
(tab_atom[i].is_enumerable ?
|
|
JS_PROP_ENUMERABLE : 0));
|
|
}
|
|
js_free_prop_enum(ctx, tab_atom, tab_atom_count);
|
|
obj1 = JS_GetPrototypeFree(ctx, obj1);
|
|
if (JS_IsNull(obj1))
|
|
break;
|
|
if (JS_IsException(obj1))
|
|
goto fail;
|
|
/* must check for timeout to avoid infinite loop */
|
|
if (js_poll_interrupts(ctx)) {
|
|
JS_FreeValue(ctx, obj1);
|
|
goto fail;
|
|
}
|
|
}
|
|
return enum_obj;
|
|
|
|
fail:
|
|
JS_FreeValue(ctx, enum_obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
/* obj -> enum_obj */
|
|
static __exception int js_for_in_start(JSContext *ctx, JSValue *sp)
|
|
{
|
|
sp[-1] = build_for_in_iterator(ctx, sp[-1]);
|
|
if (JS_IsException(sp[-1]))
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
/* enum_obj -> enum_obj value done */
|
|
static __exception int js_for_in_next(JSContext *ctx, JSValue *sp)
|
|
{
|
|
JSValue enum_obj;
|
|
JSObject *p;
|
|
JSAtom prop;
|
|
JSForInIterator *it;
|
|
int ret;
|
|
|
|
enum_obj = sp[-1];
|
|
/* fail safe */
|
|
if (JS_VALUE_GET_TAG(enum_obj) != JS_TAG_OBJECT)
|
|
goto done;
|
|
p = JS_VALUE_GET_OBJ(enum_obj);
|
|
if (p->class_id != JS_CLASS_FOR_IN_ITERATOR)
|
|
goto done;
|
|
it = p->u.for_in_iterator;
|
|
|
|
for(;;) {
|
|
if (it->is_array) {
|
|
if (it->idx >= it->array_length)
|
|
goto done;
|
|
prop = __JS_AtomFromUInt32(it->idx);
|
|
it->idx++;
|
|
} else {
|
|
JSShape *sh = p->shape;
|
|
JSShapeProperty *prs;
|
|
if (it->idx >= sh->prop_count)
|
|
goto done;
|
|
prs = get_shape_prop(sh) + it->idx;
|
|
prop = prs->atom;
|
|
it->idx++;
|
|
if (prop == JS_ATOM_NULL || !(prs->flags & JS_PROP_ENUMERABLE))
|
|
continue;
|
|
}
|
|
// check if the property was deleted unless we're dealing with a proxy
|
|
JSValue obj = it->obj;
|
|
if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) {
|
|
JSObject *p = JS_VALUE_GET_OBJ(obj);
|
|
if (p->class_id == JS_CLASS_PROXY)
|
|
break;
|
|
}
|
|
ret = JS_HasProperty(ctx, obj, prop);
|
|
if (ret < 0)
|
|
return ret;
|
|
if (ret)
|
|
break;
|
|
}
|
|
/* return the property */
|
|
sp[0] = JS_AtomToValue(ctx, prop);
|
|
sp[1] = JS_FALSE;
|
|
return 0;
|
|
done:
|
|
/* return the end */
|
|
sp[0] = JS_UNDEFINED;
|
|
sp[1] = JS_TRUE;
|
|
return 0;
|
|
}
|
|
|
|
static JSValue JS_GetIterator2(JSContext *ctx, JSValue obj,
|
|
JSValue method)
|
|
{
|
|
JSValue enum_obj;
|
|
|
|
enum_obj = JS_Call(ctx, method, obj, 0, NULL);
|
|
if (JS_IsException(enum_obj))
|
|
return enum_obj;
|
|
if (!JS_IsObject(enum_obj)) {
|
|
JS_FreeValue(ctx, enum_obj);
|
|
return JS_ThrowTypeErrorNotAnObject(ctx);
|
|
}
|
|
return enum_obj;
|
|
}
|
|
|
|
static JSValue JS_GetIterator(JSContext *ctx, JSValue obj, BOOL is_async)
|
|
{
|
|
JSValue method, ret, sync_iter;
|
|
|
|
if (is_async) {
|
|
method = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_asyncIterator);
|
|
if (JS_IsException(method))
|
|
return method;
|
|
if (JS_IsUndefined(method) || JS_IsNull(method)) {
|
|
method = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_iterator);
|
|
if (JS_IsException(method))
|
|
return method;
|
|
sync_iter = JS_GetIterator2(ctx, obj, method);
|
|
JS_FreeValue(ctx, method);
|
|
if (JS_IsException(sync_iter))
|
|
return sync_iter;
|
|
ret = JS_CreateAsyncFromSyncIterator(ctx, sync_iter);
|
|
JS_FreeValue(ctx, sync_iter);
|
|
return ret;
|
|
}
|
|
} else {
|
|
method = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_iterator);
|
|
if (JS_IsException(method))
|
|
return method;
|
|
}
|
|
if (!JS_IsFunction(ctx, method)) {
|
|
JS_FreeValue(ctx, method);
|
|
return JS_ThrowTypeError(ctx, "value is not iterable");
|
|
}
|
|
ret = JS_GetIterator2(ctx, obj, method);
|
|
JS_FreeValue(ctx, method);
|
|
return ret;
|
|
}
|
|
|
|
/* return *pdone = 2 if the iterator object is not parsed */
|
|
static JSValue JS_IteratorNext2(JSContext *ctx, JSValue enum_obj,
|
|
JSValue method,
|
|
int argc, JSValue *argv, int *pdone)
|
|
{
|
|
JSValue obj;
|
|
|
|
/* fast path for the built-in iterators (avoid creating the
|
|
intermediate result object) */
|
|
if (JS_IsObject(method)) {
|
|
JSObject *p = JS_VALUE_GET_OBJ(method);
|
|
if (p->class_id == JS_CLASS_C_FUNCTION &&
|
|
p->u.cfunc.cproto == JS_CFUNC_iterator_next) {
|
|
JSCFunctionType func;
|
|
JSValue args[1];
|
|
|
|
/* in case the function expects one argument */
|
|
if (argc == 0) {
|
|
args[0] = JS_UNDEFINED;
|
|
argv = args;
|
|
}
|
|
func = p->u.cfunc.c_function;
|
|
return func.iterator_next(ctx, enum_obj, argc, argv,
|
|
pdone, p->u.cfunc.magic);
|
|
}
|
|
}
|
|
obj = JS_Call(ctx, method, enum_obj, argc, argv);
|
|
if (JS_IsException(obj))
|
|
goto fail;
|
|
if (!JS_IsObject(obj)) {
|
|
JS_FreeValue(ctx, obj);
|
|
JS_ThrowTypeError(ctx, "iterator must return an object");
|
|
goto fail;
|
|
}
|
|
*pdone = 2;
|
|
return obj;
|
|
fail:
|
|
*pdone = FALSE;
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue JS_IteratorNext(JSContext *ctx, JSValue enum_obj,
|
|
JSValue method,
|
|
int argc, JSValue *argv, BOOL *pdone)
|
|
{
|
|
JSValue obj, value, done_val;
|
|
int done;
|
|
|
|
obj = JS_IteratorNext2(ctx, enum_obj, method, argc, argv, &done);
|
|
if (JS_IsException(obj))
|
|
goto fail;
|
|
if (done != 2) {
|
|
*pdone = done;
|
|
return obj;
|
|
} else {
|
|
done_val = JS_GetProperty(ctx, obj, JS_ATOM_done);
|
|
if (JS_IsException(done_val))
|
|
goto fail;
|
|
*pdone = JS_ToBoolFree(ctx, done_val);
|
|
value = JS_UNDEFINED;
|
|
if (!*pdone) {
|
|
value = JS_GetProperty(ctx, obj, JS_ATOM_value);
|
|
}
|
|
JS_FreeValue(ctx, obj);
|
|
return value;
|
|
}
|
|
fail:
|
|
JS_FreeValue(ctx, obj);
|
|
*pdone = FALSE;
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
/* return < 0 in case of exception */
|
|
static int JS_IteratorClose(JSContext *ctx, JSValue enum_obj,
|
|
BOOL is_exception_pending)
|
|
{
|
|
JSValue method, ret, ex_obj;
|
|
int res;
|
|
|
|
if (is_exception_pending) {
|
|
ex_obj = ctx->rt->current_exception;
|
|
ctx->rt->current_exception = JS_NULL;
|
|
res = -1;
|
|
} else {
|
|
ex_obj = JS_UNDEFINED;
|
|
res = 0;
|
|
}
|
|
method = JS_GetProperty(ctx, enum_obj, JS_ATOM_return);
|
|
if (JS_IsException(method)) {
|
|
res = -1;
|
|
goto done;
|
|
}
|
|
if (JS_IsUndefined(method) || JS_IsNull(method)) {
|
|
goto done;
|
|
}
|
|
ret = JS_CallFree(ctx, method, enum_obj, 0, NULL);
|
|
if (!is_exception_pending) {
|
|
if (JS_IsException(ret)) {
|
|
res = -1;
|
|
} else if (!JS_IsObject(ret)) {
|
|
JS_ThrowTypeErrorNotAnObject(ctx);
|
|
res = -1;
|
|
}
|
|
}
|
|
JS_FreeValue(ctx, ret);
|
|
done:
|
|
if (is_exception_pending) {
|
|
JS_Throw(ctx, ex_obj);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
/* obj -> enum_rec (3 slots) */
|
|
static __exception int js_for_of_start(JSContext *ctx, JSValue *sp,
|
|
BOOL is_async)
|
|
{
|
|
JSValue op1, obj, method;
|
|
op1 = sp[-1];
|
|
obj = JS_GetIterator(ctx, op1, is_async);
|
|
if (JS_IsException(obj))
|
|
return -1;
|
|
JS_FreeValue(ctx, op1);
|
|
sp[-1] = obj;
|
|
method = JS_GetProperty(ctx, obj, JS_ATOM_next);
|
|
if (JS_IsException(method))
|
|
return -1;
|
|
sp[0] = method;
|
|
return 0;
|
|
}
|
|
|
|
/* enum_rec [objs] -> enum_rec [objs] value done. There are 'offset'
|
|
objs. If 'done' is true or in case of exception, 'enum_rec' is set
|
|
to undefined. If 'done' is true, 'value' is always set to
|
|
undefined. */
|
|
static __exception int js_for_of_next(JSContext *ctx, JSValue *sp, int offset)
|
|
{
|
|
JSValue value = JS_UNDEFINED;
|
|
int done = 1;
|
|
|
|
if (likely(!JS_IsUndefined(sp[offset]))) {
|
|
value = JS_IteratorNext(ctx, sp[offset], sp[offset + 1], 0, NULL, &done);
|
|
if (JS_IsException(value))
|
|
done = -1;
|
|
if (done) {
|
|
/* value is JS_UNDEFINED or JS_EXCEPTION */
|
|
/* replace the iteration object with undefined */
|
|
JS_FreeValue(ctx, sp[offset]);
|
|
sp[offset] = JS_UNDEFINED;
|
|
if (done < 0) {
|
|
return -1;
|
|
} else {
|
|
JS_FreeValue(ctx, value);
|
|
value = JS_UNDEFINED;
|
|
}
|
|
}
|
|
}
|
|
sp[0] = value;
|
|
sp[1] = js_bool(done);
|
|
return 0;
|
|
}
|
|
|
|
static JSValue JS_IteratorGetCompleteValue(JSContext *ctx, JSValue obj,
|
|
BOOL *pdone)
|
|
{
|
|
JSValue done_val, value;
|
|
BOOL done;
|
|
done_val = JS_GetProperty(ctx, obj, JS_ATOM_done);
|
|
if (JS_IsException(done_val))
|
|
goto fail;
|
|
done = JS_ToBoolFree(ctx, done_val);
|
|
value = JS_GetProperty(ctx, obj, JS_ATOM_value);
|
|
if (JS_IsException(value))
|
|
goto fail;
|
|
*pdone = done;
|
|
return value;
|
|
fail:
|
|
*pdone = FALSE;
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static __exception int js_iterator_get_value_done(JSContext *ctx, JSValue *sp)
|
|
{
|
|
JSValue obj, value;
|
|
BOOL done;
|
|
obj = sp[-1];
|
|
if (!JS_IsObject(obj)) {
|
|
JS_ThrowTypeError(ctx, "iterator must return an object");
|
|
return -1;
|
|
}
|
|
value = JS_IteratorGetCompleteValue(ctx, obj, &done);
|
|
if (JS_IsException(value))
|
|
return -1;
|
|
JS_FreeValue(ctx, obj);
|
|
sp[-1] = value;
|
|
sp[0] = js_bool(done);
|
|
return 0;
|
|
}
|
|
|
|
static JSValue js_create_iterator_result(JSContext *ctx,
|
|
JSValue val,
|
|
BOOL done)
|
|
{
|
|
JSValue obj;
|
|
obj = JS_NewObject(ctx);
|
|
if (JS_IsException(obj)) {
|
|
JS_FreeValue(ctx, val);
|
|
return obj;
|
|
}
|
|
if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_value,
|
|
val, JS_PROP_C_W_E) < 0) {
|
|
goto fail;
|
|
}
|
|
if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_done,
|
|
js_bool(done), JS_PROP_C_W_E) < 0) {
|
|
fail:
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
return obj;
|
|
}
|
|
|
|
static JSValue js_array_iterator_next(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv,
|
|
BOOL *pdone, int magic);
|
|
|
|
static JSValue js_create_array_iterator(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv, int magic);
|
|
|
|
static BOOL js_is_fast_array(JSContext *ctx, JSValue obj)
|
|
{
|
|
/* Try and handle fast arrays explicitly */
|
|
if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) {
|
|
JSObject *p = JS_VALUE_GET_OBJ(obj);
|
|
if (p->class_id == JS_CLASS_ARRAY && p->fast_array) {
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/* Access an Array's internal JSValue array if available */
|
|
static BOOL js_get_fast_array(JSContext *ctx, JSValue obj,
|
|
JSValue **arrpp, uint32_t *countp)
|
|
{
|
|
/* Try and handle fast arrays explicitly */
|
|
if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) {
|
|
JSObject *p = JS_VALUE_GET_OBJ(obj);
|
|
if (p->class_id == JS_CLASS_ARRAY && p->fast_array) {
|
|
*countp = p->u.array.count;
|
|
*arrpp = p->u.array.u.values;
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static __exception int js_append_enumerate(JSContext *ctx, JSValue *sp)
|
|
{
|
|
JSValue iterator, enumobj, method, value;
|
|
int is_array_iterator;
|
|
JSValue *arrp;
|
|
uint32_t i, count32, pos;
|
|
|
|
if (JS_VALUE_GET_TAG(sp[-2]) != JS_TAG_INT) {
|
|
JS_ThrowInternalError(ctx, "invalid index for append");
|
|
return -1;
|
|
}
|
|
|
|
pos = JS_VALUE_GET_INT(sp[-2]);
|
|
|
|
/* XXX: further optimisations:
|
|
- use ctx->array_proto_values?
|
|
- check if array_iterator_prototype next method is built-in and
|
|
avoid constructing actual iterator object?
|
|
- build this into js_for_of_start and use in all `for (x of o)` loops
|
|
*/
|
|
iterator = JS_GetProperty(ctx, sp[-1], JS_ATOM_Symbol_iterator);
|
|
if (JS_IsException(iterator))
|
|
return -1;
|
|
/* Used to squelch a -Wcast-function-type warning. */
|
|
JSCFunctionType ft = { .generic_magic = js_create_array_iterator };
|
|
is_array_iterator = JS_IsCFunction(ctx, iterator,
|
|
ft.generic,
|
|
JS_ITERATOR_KIND_VALUE);
|
|
JS_FreeValue(ctx, iterator);
|
|
|
|
enumobj = JS_GetIterator(ctx, sp[-1], FALSE);
|
|
if (JS_IsException(enumobj))
|
|
return -1;
|
|
method = JS_GetProperty(ctx, enumobj, JS_ATOM_next);
|
|
if (JS_IsException(method)) {
|
|
JS_FreeValue(ctx, enumobj);
|
|
return -1;
|
|
}
|
|
/* Used to squelch a -Wcast-function-type warning. */
|
|
JSCFunctionType ft2 = { .iterator_next = js_array_iterator_next };
|
|
if (is_array_iterator
|
|
&& JS_IsCFunction(ctx, method, ft2.generic, 0)
|
|
&& js_get_fast_array(ctx, sp[-1], &arrp, &count32)) {
|
|
uint32_t len;
|
|
if (js_get_length32(ctx, &len, sp[-1]))
|
|
goto exception;
|
|
/* if len > count32, the elements >= count32 might be read in
|
|
the prototypes and might have side effects */
|
|
if (len != count32)
|
|
goto general_case;
|
|
/* Handle fast arrays explicitly */
|
|
for (i = 0; i < count32; i++) {
|
|
if (JS_DefinePropertyValueUint32(ctx, sp[-3], pos++,
|
|
js_dup(arrp[i]), JS_PROP_C_W_E) < 0)
|
|
goto exception;
|
|
}
|
|
} else {
|
|
general_case:
|
|
for (;;) {
|
|
BOOL done;
|
|
value = JS_IteratorNext(ctx, enumobj, method, 0, NULL, &done);
|
|
if (JS_IsException(value))
|
|
goto exception;
|
|
if (done) {
|
|
/* value is JS_UNDEFINED */
|
|
break;
|
|
}
|
|
if (JS_DefinePropertyValueUint32(ctx, sp[-3], pos++, value, JS_PROP_C_W_E) < 0)
|
|
goto exception;
|
|
}
|
|
}
|
|
/* Note: could raise an error if too many elements */
|
|
sp[-2] = js_int32(pos);
|
|
JS_FreeValue(ctx, enumobj);
|
|
JS_FreeValue(ctx, method);
|
|
return 0;
|
|
|
|
exception:
|
|
JS_IteratorClose(ctx, enumobj, TRUE);
|
|
JS_FreeValue(ctx, enumobj);
|
|
JS_FreeValue(ctx, method);
|
|
return -1;
|
|
}
|
|
|
|
static __exception int JS_CopyDataProperties(JSContext *ctx,
|
|
JSValue target,
|
|
JSValue source,
|
|
JSValue excluded,
|
|
BOOL setprop)
|
|
{
|
|
JSPropertyEnum *tab_atom;
|
|
JSValue val;
|
|
uint32_t i, tab_atom_count;
|
|
JSObject *p;
|
|
JSObject *pexcl = NULL;
|
|
int ret, gpn_flags;
|
|
JSPropertyDescriptor desc;
|
|
BOOL is_enumerable;
|
|
|
|
if (JS_VALUE_GET_TAG(source) != JS_TAG_OBJECT)
|
|
return 0;
|
|
|
|
if (JS_VALUE_GET_TAG(excluded) == JS_TAG_OBJECT)
|
|
pexcl = JS_VALUE_GET_OBJ(excluded);
|
|
|
|
p = JS_VALUE_GET_OBJ(source);
|
|
|
|
gpn_flags = JS_GPN_STRING_MASK | JS_GPN_SYMBOL_MASK | JS_GPN_ENUM_ONLY;
|
|
if (p->is_exotic) {
|
|
const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic;
|
|
/* cannot use JS_GPN_ENUM_ONLY with e.g. proxies because it
|
|
introduces a visible change */
|
|
if (em && em->get_own_property_names) {
|
|
gpn_flags &= ~JS_GPN_ENUM_ONLY;
|
|
}
|
|
}
|
|
if (JS_GetOwnPropertyNamesInternal(ctx, &tab_atom, &tab_atom_count, p,
|
|
gpn_flags))
|
|
return -1;
|
|
|
|
for (i = 0; i < tab_atom_count; i++) {
|
|
if (pexcl) {
|
|
ret = JS_GetOwnPropertyInternal(ctx, NULL, pexcl, tab_atom[i].atom);
|
|
if (ret) {
|
|
if (ret < 0)
|
|
goto exception;
|
|
continue;
|
|
}
|
|
}
|
|
if (!(gpn_flags & JS_GPN_ENUM_ONLY)) {
|
|
/* test if the property is enumerable */
|
|
ret = JS_GetOwnPropertyInternal(ctx, &desc, p, tab_atom[i].atom);
|
|
if (ret < 0)
|
|
goto exception;
|
|
if (!ret)
|
|
continue;
|
|
is_enumerable = (desc.flags & JS_PROP_ENUMERABLE) != 0;
|
|
js_free_desc(ctx, &desc);
|
|
if (!is_enumerable)
|
|
continue;
|
|
}
|
|
val = JS_GetProperty(ctx, source, tab_atom[i].atom);
|
|
if (JS_IsException(val))
|
|
goto exception;
|
|
if (setprop)
|
|
ret = JS_SetProperty(ctx, target, tab_atom[i].atom, val);
|
|
else
|
|
ret = JS_DefinePropertyValue(ctx, target, tab_atom[i].atom, val,
|
|
JS_PROP_C_W_E);
|
|
if (ret < 0)
|
|
goto exception;
|
|
}
|
|
js_free_prop_enum(ctx, tab_atom, tab_atom_count);
|
|
return 0;
|
|
exception:
|
|
js_free_prop_enum(ctx, tab_atom, tab_atom_count);
|
|
return -1;
|
|
}
|
|
|
|
/* only valid inside C functions */
|
|
static JSValue JS_GetActiveFunction(JSContext *ctx)
|
|
{
|
|
return ctx->rt->current_stack_frame->cur_func;
|
|
}
|
|
|
|
static JSVarRef *get_var_ref(JSContext *ctx, JSStackFrame *sf,
|
|
int var_idx, BOOL is_arg)
|
|
{
|
|
JSVarRef *var_ref;
|
|
struct list_head *el;
|
|
|
|
list_for_each(el, &sf->var_ref_list) {
|
|
var_ref = list_entry(el, JSVarRef, header.link);
|
|
if (var_ref->var_idx == var_idx && var_ref->is_arg == is_arg) {
|
|
var_ref->header.ref_count++;
|
|
return var_ref;
|
|
}
|
|
}
|
|
/* create a new one */
|
|
var_ref = js_malloc(ctx, sizeof(JSVarRef));
|
|
if (!var_ref)
|
|
return NULL;
|
|
var_ref->header.ref_count = 1;
|
|
var_ref->is_detached = FALSE;
|
|
var_ref->is_arg = is_arg;
|
|
var_ref->var_idx = var_idx;
|
|
list_add_tail(&var_ref->header.link, &sf->var_ref_list);
|
|
if (is_arg)
|
|
var_ref->pvalue = &sf->arg_buf[var_idx];
|
|
else
|
|
var_ref->pvalue = &sf->var_buf[var_idx];
|
|
var_ref->value = JS_UNDEFINED;
|
|
return var_ref;
|
|
}
|
|
|
|
static JSValue js_closure2(JSContext *ctx, JSValue func_obj,
|
|
JSFunctionBytecode *b,
|
|
JSVarRef **cur_var_refs,
|
|
JSStackFrame *sf)
|
|
{
|
|
JSObject *p;
|
|
JSVarRef **var_refs;
|
|
int i;
|
|
|
|
p = JS_VALUE_GET_OBJ(func_obj);
|
|
p->u.func.function_bytecode = b;
|
|
p->u.func.home_object = NULL;
|
|
p->u.func.var_refs = NULL;
|
|
if (b->closure_var_count) {
|
|
var_refs = js_mallocz(ctx, sizeof(var_refs[0]) * b->closure_var_count);
|
|
if (!var_refs)
|
|
goto fail;
|
|
p->u.func.var_refs = var_refs;
|
|
for(i = 0; i < b->closure_var_count; i++) {
|
|
JSClosureVar *cv = &b->closure_var[i];
|
|
JSVarRef *var_ref;
|
|
if (cv->is_local) {
|
|
/* reuse the existing variable reference if it already exists */
|
|
var_ref = get_var_ref(ctx, sf, cv->var_idx, cv->is_arg);
|
|
if (!var_ref)
|
|
goto fail;
|
|
} else {
|
|
var_ref = cur_var_refs[cv->var_idx];
|
|
var_ref->header.ref_count++;
|
|
}
|
|
var_refs[i] = var_ref;
|
|
}
|
|
}
|
|
return func_obj;
|
|
fail:
|
|
/* bfunc is freed when func_obj is freed */
|
|
JS_FreeValue(ctx, func_obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue js_instantiate_prototype(JSContext *ctx, JSObject *p, JSAtom atom, void *opaque)
|
|
{
|
|
JSValue obj, this_val;
|
|
int ret;
|
|
|
|
this_val = JS_MKPTR(JS_TAG_OBJECT, p);
|
|
obj = JS_NewObject(ctx);
|
|
if (JS_IsException(obj))
|
|
return JS_EXCEPTION;
|
|
set_cycle_flag(ctx, obj);
|
|
set_cycle_flag(ctx, this_val);
|
|
ret = JS_DefinePropertyValue(ctx, obj, JS_ATOM_constructor,
|
|
js_dup(this_val),
|
|
JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
|
|
if (ret < 0) {
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
return obj;
|
|
}
|
|
|
|
static const uint16_t func_kind_to_class_id[] = {
|
|
[JS_FUNC_NORMAL] = JS_CLASS_BYTECODE_FUNCTION,
|
|
[JS_FUNC_GENERATOR] = JS_CLASS_GENERATOR_FUNCTION,
|
|
[JS_FUNC_ASYNC] = JS_CLASS_ASYNC_FUNCTION,
|
|
[JS_FUNC_ASYNC_GENERATOR] = JS_CLASS_ASYNC_GENERATOR_FUNCTION,
|
|
};
|
|
|
|
static JSValue js_closure(JSContext *ctx, JSValue bfunc,
|
|
JSVarRef **cur_var_refs,
|
|
JSStackFrame *sf)
|
|
{
|
|
JSFunctionBytecode *b;
|
|
JSValue func_obj;
|
|
JSAtom name_atom;
|
|
|
|
b = JS_VALUE_GET_PTR(bfunc);
|
|
func_obj = JS_NewObjectClass(ctx, func_kind_to_class_id[b->func_kind]);
|
|
if (JS_IsException(func_obj)) {
|
|
JS_FreeValue(ctx, bfunc);
|
|
return JS_EXCEPTION;
|
|
}
|
|
func_obj = js_closure2(ctx, func_obj, b, cur_var_refs, sf);
|
|
if (JS_IsException(func_obj)) {
|
|
/* bfunc has been freed */
|
|
goto fail;
|
|
}
|
|
name_atom = b->func_name;
|
|
if (name_atom == JS_ATOM_NULL)
|
|
name_atom = JS_ATOM_empty_string;
|
|
js_function_set_properties(ctx, func_obj, name_atom,
|
|
b->defined_arg_count);
|
|
|
|
if (b->func_kind & JS_FUNC_GENERATOR) {
|
|
JSValue proto;
|
|
int proto_class_id;
|
|
/* generators have a prototype field which is used as
|
|
prototype for the generator object */
|
|
if (b->func_kind == JS_FUNC_ASYNC_GENERATOR)
|
|
proto_class_id = JS_CLASS_ASYNC_GENERATOR;
|
|
else
|
|
proto_class_id = JS_CLASS_GENERATOR;
|
|
proto = JS_NewObjectProto(ctx, ctx->class_proto[proto_class_id]);
|
|
if (JS_IsException(proto))
|
|
goto fail;
|
|
JS_DefinePropertyValue(ctx, func_obj, JS_ATOM_prototype, proto,
|
|
JS_PROP_WRITABLE);
|
|
} else if (b->has_prototype) {
|
|
/* add the 'prototype' property: delay instantiation to avoid
|
|
creating cycles for every javascript function. The prototype
|
|
object is created on the fly when first accessed */
|
|
JS_SetConstructorBit(ctx, func_obj, TRUE);
|
|
JS_DefineAutoInitProperty(ctx, func_obj, JS_ATOM_prototype,
|
|
JS_AUTOINIT_ID_PROTOTYPE, NULL,
|
|
JS_PROP_WRITABLE);
|
|
}
|
|
return func_obj;
|
|
fail:
|
|
/* bfunc is freed when func_obj is freed */
|
|
JS_FreeValue(ctx, func_obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
#define JS_DEFINE_CLASS_HAS_HERITAGE (1 << 0)
|
|
|
|
static int js_op_define_class(JSContext *ctx, JSValue *sp,
|
|
JSAtom class_name, int class_flags,
|
|
JSVarRef **cur_var_refs,
|
|
JSStackFrame *sf, BOOL is_computed_name)
|
|
{
|
|
JSValue bfunc, parent_class, proto = JS_UNDEFINED;
|
|
JSValue ctor = JS_UNDEFINED, parent_proto = JS_UNDEFINED;
|
|
JSFunctionBytecode *b;
|
|
|
|
parent_class = sp[-2];
|
|
bfunc = sp[-1];
|
|
|
|
if (class_flags & JS_DEFINE_CLASS_HAS_HERITAGE) {
|
|
if (JS_IsNull(parent_class)) {
|
|
parent_proto = JS_NULL;
|
|
parent_class = js_dup(ctx->function_proto);
|
|
} else {
|
|
if (!JS_IsConstructor(ctx, parent_class)) {
|
|
JS_ThrowTypeError(ctx, "parent class must be constructor");
|
|
goto fail;
|
|
}
|
|
parent_proto = JS_GetProperty(ctx, parent_class, JS_ATOM_prototype);
|
|
if (JS_IsException(parent_proto))
|
|
goto fail;
|
|
if (!JS_IsNull(parent_proto) && !JS_IsObject(parent_proto)) {
|
|
JS_ThrowTypeError(ctx, "parent prototype must be an object or null");
|
|
goto fail;
|
|
}
|
|
}
|
|
} else {
|
|
/* parent_class is JS_UNDEFINED in this case */
|
|
parent_proto = js_dup(ctx->class_proto[JS_CLASS_OBJECT]);
|
|
parent_class = js_dup(ctx->function_proto);
|
|
}
|
|
proto = JS_NewObjectProto(ctx, parent_proto);
|
|
if (JS_IsException(proto))
|
|
goto fail;
|
|
|
|
b = JS_VALUE_GET_PTR(bfunc);
|
|
assert(b->func_kind == JS_FUNC_NORMAL);
|
|
ctor = JS_NewObjectProtoClass(ctx, parent_class,
|
|
JS_CLASS_BYTECODE_FUNCTION);
|
|
if (JS_IsException(ctor))
|
|
goto fail;
|
|
ctor = js_closure2(ctx, ctor, b, cur_var_refs, sf);
|
|
bfunc = JS_UNDEFINED;
|
|
if (JS_IsException(ctor))
|
|
goto fail;
|
|
js_method_set_home_object(ctx, ctor, proto);
|
|
JS_SetConstructorBit(ctx, ctor, TRUE);
|
|
|
|
JS_DefinePropertyValue(ctx, ctor, JS_ATOM_length,
|
|
js_int32(b->defined_arg_count),
|
|
JS_PROP_CONFIGURABLE);
|
|
|
|
if (is_computed_name) {
|
|
if (JS_DefineObjectNameComputed(ctx, ctor, sp[-3],
|
|
JS_PROP_CONFIGURABLE) < 0)
|
|
goto fail;
|
|
} else {
|
|
if (JS_DefineObjectName(ctx, ctor, class_name, JS_PROP_CONFIGURABLE) < 0)
|
|
goto fail;
|
|
}
|
|
|
|
/* the constructor property must be first. It can be overriden by
|
|
computed property names */
|
|
if (JS_DefinePropertyValue(ctx, proto, JS_ATOM_constructor,
|
|
js_dup(ctor),
|
|
JS_PROP_CONFIGURABLE |
|
|
JS_PROP_WRITABLE | JS_PROP_THROW) < 0)
|
|
goto fail;
|
|
/* set the prototype property */
|
|
if (JS_DefinePropertyValue(ctx, ctor, JS_ATOM_prototype,
|
|
js_dup(proto), JS_PROP_THROW) < 0)
|
|
goto fail;
|
|
set_cycle_flag(ctx, ctor);
|
|
set_cycle_flag(ctx, proto);
|
|
|
|
JS_FreeValue(ctx, parent_proto);
|
|
JS_FreeValue(ctx, parent_class);
|
|
|
|
sp[-2] = ctor;
|
|
sp[-1] = proto;
|
|
return 0;
|
|
fail:
|
|
JS_FreeValue(ctx, parent_class);
|
|
JS_FreeValue(ctx, parent_proto);
|
|
JS_FreeValue(ctx, bfunc);
|
|
JS_FreeValue(ctx, proto);
|
|
JS_FreeValue(ctx, ctor);
|
|
sp[-2] = JS_UNDEFINED;
|
|
sp[-1] = JS_UNDEFINED;
|
|
return -1;
|
|
}
|
|
|
|
static void close_var_refs(JSRuntime *rt, JSStackFrame *sf)
|
|
{
|
|
struct list_head *el, *el1;
|
|
JSVarRef *var_ref;
|
|
int var_idx;
|
|
|
|
list_for_each_safe(el, el1, &sf->var_ref_list) {
|
|
var_ref = list_entry(el, JSVarRef, header.link);
|
|
var_idx = var_ref->var_idx;
|
|
if (var_ref->is_arg)
|
|
var_ref->value = js_dup(sf->arg_buf[var_idx]);
|
|
else
|
|
var_ref->value = js_dup(sf->var_buf[var_idx]);
|
|
var_ref->pvalue = &var_ref->value;
|
|
/* the reference is no longer to a local variable */
|
|
var_ref->is_detached = TRUE;
|
|
add_gc_object(rt, &var_ref->header, JS_GC_OBJ_TYPE_VAR_REF);
|
|
}
|
|
}
|
|
|
|
static void close_lexical_var(JSContext *ctx, JSStackFrame *sf, int idx, int is_arg)
|
|
{
|
|
struct list_head *el, *el1;
|
|
JSVarRef *var_ref;
|
|
int var_idx = idx;
|
|
|
|
list_for_each_safe(el, el1, &sf->var_ref_list) {
|
|
var_ref = list_entry(el, JSVarRef, header.link);
|
|
if (var_idx == var_ref->var_idx && var_ref->is_arg == is_arg) {
|
|
var_ref->value = js_dup(sf->var_buf[var_idx]);
|
|
var_ref->pvalue = &var_ref->value;
|
|
list_del(&var_ref->header.link);
|
|
/* the reference is no longer to a local variable */
|
|
var_ref->is_detached = TRUE;
|
|
add_gc_object(ctx->rt, &var_ref->header, JS_GC_OBJ_TYPE_VAR_REF);
|
|
}
|
|
}
|
|
}
|
|
|
|
#define JS_CALL_FLAG_COPY_ARGV (1 << 1)
|
|
#define JS_CALL_FLAG_GENERATOR (1 << 2)
|
|
|
|
static JSValue js_call_c_function(JSContext *ctx, JSValue func_obj,
|
|
JSValue this_obj,
|
|
int argc, JSValue *argv, int flags)
|
|
{
|
|
JSRuntime *rt = ctx->rt;
|
|
JSCFunctionType func;
|
|
JSObject *p;
|
|
JSStackFrame sf_s, *sf = &sf_s, *prev_sf;
|
|
JSValue ret_val;
|
|
JSValue *arg_buf;
|
|
int arg_count, i;
|
|
JSCFunctionEnum cproto;
|
|
|
|
p = JS_VALUE_GET_OBJ(func_obj);
|
|
cproto = p->u.cfunc.cproto;
|
|
arg_count = p->u.cfunc.length;
|
|
|
|
/* better to always check stack overflow */
|
|
if (js_check_stack_overflow(rt, sizeof(arg_buf[0]) * arg_count))
|
|
return JS_ThrowStackOverflow(ctx);
|
|
|
|
prev_sf = rt->current_stack_frame;
|
|
sf->prev_frame = prev_sf;
|
|
rt->current_stack_frame = sf;
|
|
ctx = p->u.cfunc.realm; /* change the current realm */
|
|
|
|
sf->js_mode = 0;
|
|
sf->cur_func = func_obj;
|
|
sf->arg_count = argc;
|
|
arg_buf = argv;
|
|
|
|
if (unlikely(argc < arg_count)) {
|
|
/* ensure that at least argc_count arguments are readable */
|
|
arg_buf = alloca(sizeof(arg_buf[0]) * arg_count);
|
|
for(i = 0; i < argc; i++)
|
|
arg_buf[i] = argv[i];
|
|
for(i = argc; i < arg_count; i++)
|
|
arg_buf[i] = JS_UNDEFINED;
|
|
sf->arg_count = arg_count;
|
|
}
|
|
sf->arg_buf = arg_buf;
|
|
|
|
func = p->u.cfunc.c_function;
|
|
switch(cproto) {
|
|
case JS_CFUNC_constructor:
|
|
case JS_CFUNC_constructor_or_func:
|
|
if (!(flags & JS_CALL_FLAG_CONSTRUCTOR)) {
|
|
if (cproto == JS_CFUNC_constructor) {
|
|
not_a_constructor:
|
|
ret_val = JS_ThrowTypeError(ctx, "must be called with new");
|
|
break;
|
|
} else {
|
|
this_obj = JS_UNDEFINED;
|
|
}
|
|
}
|
|
/* here this_obj is new_target */
|
|
/* fall thru */
|
|
case JS_CFUNC_generic:
|
|
ret_val = func.generic(ctx, this_obj, argc, arg_buf);
|
|
break;
|
|
case JS_CFUNC_constructor_magic:
|
|
case JS_CFUNC_constructor_or_func_magic:
|
|
if (!(flags & JS_CALL_FLAG_CONSTRUCTOR)) {
|
|
if (cproto == JS_CFUNC_constructor_magic) {
|
|
goto not_a_constructor;
|
|
} else {
|
|
this_obj = JS_UNDEFINED;
|
|
}
|
|
}
|
|
/* fall thru */
|
|
case JS_CFUNC_generic_magic:
|
|
ret_val = func.generic_magic(ctx, this_obj, argc, arg_buf,
|
|
p->u.cfunc.magic);
|
|
break;
|
|
case JS_CFUNC_getter:
|
|
ret_val = func.getter(ctx, this_obj);
|
|
break;
|
|
case JS_CFUNC_setter:
|
|
ret_val = func.setter(ctx, this_obj, arg_buf[0]);
|
|
break;
|
|
case JS_CFUNC_getter_magic:
|
|
ret_val = func.getter_magic(ctx, this_obj, p->u.cfunc.magic);
|
|
break;
|
|
case JS_CFUNC_setter_magic:
|
|
ret_val = func.setter_magic(ctx, this_obj, arg_buf[0], p->u.cfunc.magic);
|
|
break;
|
|
case JS_CFUNC_f_f:
|
|
{
|
|
double d1;
|
|
|
|
if (unlikely(JS_ToFloat64(ctx, &d1, arg_buf[0]))) {
|
|
ret_val = JS_EXCEPTION;
|
|
break;
|
|
}
|
|
ret_val = js_number(func.f_f(d1));
|
|
}
|
|
break;
|
|
case JS_CFUNC_f_f_f:
|
|
{
|
|
double d1, d2;
|
|
|
|
if (unlikely(JS_ToFloat64(ctx, &d1, arg_buf[0]))) {
|
|
ret_val = JS_EXCEPTION;
|
|
break;
|
|
}
|
|
if (unlikely(JS_ToFloat64(ctx, &d2, arg_buf[1]))) {
|
|
ret_val = JS_EXCEPTION;
|
|
break;
|
|
}
|
|
ret_val = js_number(func.f_f_f(d1, d2));
|
|
}
|
|
break;
|
|
case JS_CFUNC_iterator_next:
|
|
{
|
|
int done;
|
|
ret_val = func.iterator_next(ctx, this_obj, argc, arg_buf,
|
|
&done, p->u.cfunc.magic);
|
|
if (!JS_IsException(ret_val) && done != 2) {
|
|
ret_val = js_create_iterator_result(ctx, ret_val, done);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
|
|
rt->current_stack_frame = sf->prev_frame;
|
|
return ret_val;
|
|
}
|
|
|
|
static JSValue js_call_bound_function(JSContext *ctx, JSValue func_obj,
|
|
JSValue this_obj,
|
|
int argc, JSValue *argv, int flags)
|
|
{
|
|
JSObject *p;
|
|
JSBoundFunction *bf;
|
|
JSValue *arg_buf, new_target;
|
|
int arg_count, i;
|
|
|
|
p = JS_VALUE_GET_OBJ(func_obj);
|
|
bf = p->u.bound_function;
|
|
arg_count = bf->argc + argc;
|
|
if (js_check_stack_overflow(ctx->rt, sizeof(JSValue) * arg_count))
|
|
return JS_ThrowStackOverflow(ctx);
|
|
arg_buf = alloca(sizeof(JSValue) * arg_count);
|
|
for(i = 0; i < bf->argc; i++) {
|
|
arg_buf[i] = bf->argv[i];
|
|
}
|
|
for(i = 0; i < argc; i++) {
|
|
arg_buf[bf->argc + i] = argv[i];
|
|
}
|
|
if (flags & JS_CALL_FLAG_CONSTRUCTOR) {
|
|
new_target = this_obj;
|
|
if (js_same_value(ctx, func_obj, new_target))
|
|
new_target = bf->func_obj;
|
|
return JS_CallConstructor2(ctx, bf->func_obj, new_target,
|
|
arg_count, arg_buf);
|
|
} else {
|
|
return JS_Call(ctx, bf->func_obj, bf->this_val,
|
|
arg_count, arg_buf);
|
|
}
|
|
}
|
|
|
|
/* argument of OP_special_object */
|
|
typedef enum {
|
|
OP_SPECIAL_OBJECT_ARGUMENTS,
|
|
OP_SPECIAL_OBJECT_MAPPED_ARGUMENTS,
|
|
OP_SPECIAL_OBJECT_THIS_FUNC,
|
|
OP_SPECIAL_OBJECT_NEW_TARGET,
|
|
OP_SPECIAL_OBJECT_HOME_OBJECT,
|
|
OP_SPECIAL_OBJECT_VAR_OBJECT,
|
|
OP_SPECIAL_OBJECT_IMPORT_META,
|
|
} OPSpecialObjectEnum;
|
|
|
|
#define FUNC_RET_AWAIT 0
|
|
#define FUNC_RET_YIELD 1
|
|
#define FUNC_RET_YIELD_STAR 2
|
|
|
|
#if defined(DUMP_BYTECODE_FINAL) || \
|
|
defined(DUMP_BYTECODE_PASS2) || \
|
|
defined(DUMP_BYTECODE_PASS1) || \
|
|
defined(DUMP_BYTECODE_STACK) || \
|
|
defined(DUMP_BYTECODE_STEP) || \
|
|
defined(DUMP_READ_OBJECT)
|
|
#define DUMP_BYTECODE
|
|
#endif
|
|
|
|
#ifdef DUMP_BYTECODE
|
|
static void dump_single_byte_code(JSContext *ctx, const uint8_t *pc,
|
|
JSFunctionBytecode *b, int start_pos);
|
|
static void print_func_name(JSFunctionBytecode *b);
|
|
#endif
|
|
|
|
/* argv[] is modified if (flags & JS_CALL_FLAG_COPY_ARGV) = 0. */
|
|
static JSValue JS_CallInternal(JSContext *caller_ctx, JSValue func_obj,
|
|
JSValue this_obj, JSValue new_target,
|
|
int argc, JSValue *argv, int flags)
|
|
{
|
|
JSRuntime *rt = caller_ctx->rt;
|
|
JSContext *ctx;
|
|
JSObject *p;
|
|
JSFunctionBytecode *b;
|
|
JSStackFrame sf_s, *sf = &sf_s;
|
|
uint8_t *pc;
|
|
int opcode, arg_allocated_size, i;
|
|
JSValue *local_buf, *stack_buf, *var_buf, *arg_buf, *sp, ret_val, *pval;
|
|
JSVarRef **var_refs;
|
|
size_t alloca_size;
|
|
JSInlineCache *ic;
|
|
|
|
#ifdef DUMP_BYTECODE_STEP
|
|
#define DUMP_BYTECODE_OR_DONT(pc) \
|
|
if (check_dump_flag(ctx->rt, DUMP_BYTECODE_STEP)) dump_single_byte_code(ctx, pc, b, 0);
|
|
#else
|
|
#define DUMP_BYTECODE_OR_DONT(pc)
|
|
#endif
|
|
|
|
#if !DIRECT_DISPATCH
|
|
#define SWITCH(pc) DUMP_BYTECODE_OR_DONT(pc) switch (opcode = *pc++)
|
|
#define CASE(op) case op
|
|
#define DEFAULT default
|
|
#define BREAK break
|
|
#else
|
|
static const void * const dispatch_table[256] = {
|
|
#define DEF(id, size, n_pop, n_push, f) && case_OP_ ## id,
|
|
#define def(id, size, n_pop, n_push, f)
|
|
#include "quickjs-opcode.h"
|
|
[ OP_COUNT ... 255 ] = &&case_default
|
|
};
|
|
#define SWITCH(pc) DUMP_BYTECODE_OR_DONT(pc) goto *dispatch_table[opcode = *pc++];
|
|
#define CASE(op) case_ ## op
|
|
#define DEFAULT case_default
|
|
#define BREAK SWITCH(pc)
|
|
#endif
|
|
|
|
if (js_poll_interrupts(caller_ctx))
|
|
return JS_EXCEPTION;
|
|
if (unlikely(JS_VALUE_GET_TAG(func_obj) != JS_TAG_OBJECT)) {
|
|
if (flags & JS_CALL_FLAG_GENERATOR) {
|
|
JSAsyncFunctionState *s = JS_VALUE_GET_PTR(func_obj);
|
|
/* func_obj get contains a pointer to JSFuncAsyncState */
|
|
/* the stack frame is already allocated */
|
|
sf = &s->frame;
|
|
p = JS_VALUE_GET_OBJ(sf->cur_func);
|
|
b = p->u.func.function_bytecode;
|
|
ctx = b->realm;
|
|
var_refs = p->u.func.var_refs;
|
|
local_buf = arg_buf = sf->arg_buf;
|
|
var_buf = sf->var_buf;
|
|
stack_buf = sf->var_buf + b->var_count;
|
|
sp = sf->cur_sp;
|
|
sf->cur_sp = NULL; /* cur_sp is NULL if the function is running */
|
|
pc = sf->cur_pc;
|
|
sf->prev_frame = rt->current_stack_frame;
|
|
rt->current_stack_frame = sf;
|
|
ic = b->ic;
|
|
if (s->throw_flag)
|
|
goto exception;
|
|
else
|
|
goto restart;
|
|
} else {
|
|
goto not_a_function;
|
|
}
|
|
}
|
|
p = JS_VALUE_GET_OBJ(func_obj);
|
|
if (unlikely(p->class_id != JS_CLASS_BYTECODE_FUNCTION)) {
|
|
JSClassCall *call_func;
|
|
call_func = rt->class_array[p->class_id].call;
|
|
if (!call_func) {
|
|
not_a_function:
|
|
return JS_ThrowTypeError(caller_ctx, "not a function");
|
|
}
|
|
return call_func(caller_ctx, func_obj, this_obj, argc,
|
|
argv, flags);
|
|
}
|
|
b = p->u.func.function_bytecode;
|
|
|
|
if (unlikely(argc < b->arg_count || (flags & JS_CALL_FLAG_COPY_ARGV))) {
|
|
arg_allocated_size = b->arg_count;
|
|
} else {
|
|
arg_allocated_size = 0;
|
|
}
|
|
|
|
alloca_size = sizeof(JSValue) * (arg_allocated_size + b->var_count +
|
|
b->stack_size);
|
|
if (js_check_stack_overflow(rt, alloca_size))
|
|
return JS_ThrowStackOverflow(caller_ctx);
|
|
|
|
sf->js_mode = b->js_mode;
|
|
arg_buf = argv;
|
|
sf->arg_count = argc;
|
|
sf->cur_func = func_obj;
|
|
init_list_head(&sf->var_ref_list);
|
|
var_refs = p->u.func.var_refs;
|
|
|
|
local_buf = alloca(alloca_size);
|
|
if (unlikely(arg_allocated_size)) {
|
|
int n = min_int(argc, b->arg_count);
|
|
arg_buf = local_buf;
|
|
for(i = 0; i < n; i++)
|
|
arg_buf[i] = JS_DupValue(caller_ctx, argv[i]);
|
|
for(; i < b->arg_count; i++)
|
|
arg_buf[i] = JS_UNDEFINED;
|
|
sf->arg_count = b->arg_count;
|
|
}
|
|
var_buf = local_buf + arg_allocated_size;
|
|
sf->var_buf = var_buf;
|
|
sf->arg_buf = arg_buf;
|
|
|
|
for(i = 0; i < b->var_count; i++)
|
|
var_buf[i] = JS_UNDEFINED;
|
|
|
|
stack_buf = var_buf + b->var_count;
|
|
sp = stack_buf;
|
|
pc = b->byte_code_buf;
|
|
/* sf->cur_pc must we set to pc before any recursive calls to JS_CallInternal. */
|
|
sf->cur_pc = NULL;
|
|
sf->prev_frame = rt->current_stack_frame;
|
|
rt->current_stack_frame = sf;
|
|
ctx = b->realm; /* set the current realm */
|
|
ic = b->ic;
|
|
|
|
#ifdef DUMP_BYTECODE_STEP
|
|
if (check_dump_flag(ctx->rt, DUMP_BYTECODE_STEP))
|
|
print_func_name(b);
|
|
#endif
|
|
|
|
restart:
|
|
for(;;) {
|
|
int call_argc;
|
|
JSValue *call_argv;
|
|
|
|
SWITCH(pc) {
|
|
CASE(OP_push_i32):
|
|
*sp++ = js_int32(get_u32(pc));
|
|
pc += 4;
|
|
BREAK;
|
|
CASE(OP_push_const):
|
|
*sp++ = js_dup(b->cpool[get_u32(pc)]);
|
|
pc += 4;
|
|
BREAK;
|
|
CASE(OP_push_minus1):
|
|
CASE(OP_push_0):
|
|
CASE(OP_push_1):
|
|
CASE(OP_push_2):
|
|
CASE(OP_push_3):
|
|
CASE(OP_push_4):
|
|
CASE(OP_push_5):
|
|
CASE(OP_push_6):
|
|
CASE(OP_push_7):
|
|
*sp++ = js_int32(opcode - OP_push_0);
|
|
BREAK;
|
|
CASE(OP_push_i8):
|
|
*sp++ = js_int32(get_i8(pc));
|
|
pc += 1;
|
|
BREAK;
|
|
CASE(OP_push_i16):
|
|
*sp++ = js_int32(get_i16(pc));
|
|
pc += 2;
|
|
BREAK;
|
|
CASE(OP_push_const8):
|
|
*sp++ = js_dup(b->cpool[*pc++]);
|
|
BREAK;
|
|
CASE(OP_fclosure8):
|
|
*sp++ = js_closure(ctx, js_dup(b->cpool[*pc++]), var_refs, sf);
|
|
if (unlikely(JS_IsException(sp[-1])))
|
|
goto exception;
|
|
BREAK;
|
|
CASE(OP_push_empty_string):
|
|
*sp++ = JS_AtomToString(ctx, JS_ATOM_empty_string);
|
|
BREAK;
|
|
CASE(OP_get_length):
|
|
{
|
|
JSValue val;
|
|
|
|
sf->cur_pc = pc;
|
|
val = JS_GetProperty(ctx, sp[-1], JS_ATOM_length);
|
|
if (unlikely(JS_IsException(val)))
|
|
goto exception;
|
|
JS_FreeValue(ctx, sp[-1]);
|
|
sp[-1] = val;
|
|
}
|
|
BREAK;
|
|
CASE(OP_push_atom_value):
|
|
*sp++ = JS_AtomToValue(ctx, get_u32(pc));
|
|
pc += 4;
|
|
BREAK;
|
|
CASE(OP_undefined):
|
|
*sp++ = JS_UNDEFINED;
|
|
BREAK;
|
|
CASE(OP_null):
|
|
*sp++ = JS_NULL;
|
|
BREAK;
|
|
CASE(OP_push_this):
|
|
/* OP_push_this is only called at the start of a function */
|
|
{
|
|
JSValue val;
|
|
if (!(b->js_mode & JS_MODE_STRICT)) {
|
|
uint32_t tag = JS_VALUE_GET_TAG(this_obj);
|
|
if (likely(tag == JS_TAG_OBJECT))
|
|
goto normal_this;
|
|
if (tag == JS_TAG_NULL || tag == JS_TAG_UNDEFINED) {
|
|
val = js_dup(ctx->global_obj);
|
|
} else {
|
|
val = JS_ToObject(ctx, this_obj);
|
|
if (JS_IsException(val))
|
|
goto exception;
|
|
}
|
|
} else {
|
|
normal_this:
|
|
val = js_dup(this_obj);
|
|
}
|
|
*sp++ = val;
|
|
}
|
|
BREAK;
|
|
CASE(OP_push_false):
|
|
*sp++ = JS_FALSE;
|
|
BREAK;
|
|
CASE(OP_push_true):
|
|
*sp++ = JS_TRUE;
|
|
BREAK;
|
|
CASE(OP_object):
|
|
*sp++ = JS_NewObject(ctx);
|
|
if (unlikely(JS_IsException(sp[-1])))
|
|
goto exception;
|
|
BREAK;
|
|
CASE(OP_special_object):
|
|
{
|
|
int arg = *pc++;
|
|
switch(arg) {
|
|
case OP_SPECIAL_OBJECT_ARGUMENTS:
|
|
*sp++ = js_build_arguments(ctx, argc, argv);
|
|
if (unlikely(JS_IsException(sp[-1])))
|
|
goto exception;
|
|
break;
|
|
case OP_SPECIAL_OBJECT_MAPPED_ARGUMENTS:
|
|
*sp++ = js_build_mapped_arguments(ctx, argc, argv,
|
|
sf, min_int(argc, b->arg_count));
|
|
if (unlikely(JS_IsException(sp[-1])))
|
|
goto exception;
|
|
break;
|
|
case OP_SPECIAL_OBJECT_THIS_FUNC:
|
|
*sp++ = js_dup(sf->cur_func);
|
|
break;
|
|
case OP_SPECIAL_OBJECT_NEW_TARGET:
|
|
*sp++ = js_dup(new_target);
|
|
break;
|
|
case OP_SPECIAL_OBJECT_HOME_OBJECT:
|
|
{
|
|
JSObject *p1;
|
|
p1 = p->u.func.home_object;
|
|
if (unlikely(!p1))
|
|
*sp++ = JS_UNDEFINED;
|
|
else
|
|
*sp++ = js_dup(JS_MKPTR(JS_TAG_OBJECT, p1));
|
|
}
|
|
break;
|
|
case OP_SPECIAL_OBJECT_VAR_OBJECT:
|
|
*sp++ = JS_NewObjectProto(ctx, JS_NULL);
|
|
if (unlikely(JS_IsException(sp[-1])))
|
|
goto exception;
|
|
break;
|
|
case OP_SPECIAL_OBJECT_IMPORT_META:
|
|
*sp++ = js_import_meta(ctx);
|
|
if (unlikely(JS_IsException(sp[-1])))
|
|
goto exception;
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
}
|
|
BREAK;
|
|
CASE(OP_rest):
|
|
{
|
|
int first = get_u16(pc);
|
|
pc += 2;
|
|
*sp++ = js_build_rest(ctx, first, argc, argv);
|
|
if (unlikely(JS_IsException(sp[-1])))
|
|
goto exception;
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_drop):
|
|
JS_FreeValue(ctx, sp[-1]);
|
|
sp--;
|
|
BREAK;
|
|
CASE(OP_nip):
|
|
JS_FreeValue(ctx, sp[-2]);
|
|
sp[-2] = sp[-1];
|
|
sp--;
|
|
BREAK;
|
|
CASE(OP_nip1): /* a b c -> b c */
|
|
JS_FreeValue(ctx, sp[-3]);
|
|
sp[-3] = sp[-2];
|
|
sp[-2] = sp[-1];
|
|
sp--;
|
|
BREAK;
|
|
CASE(OP_dup):
|
|
sp[0] = js_dup(sp[-1]);
|
|
sp++;
|
|
BREAK;
|
|
CASE(OP_dup2): /* a b -> a b a b */
|
|
sp[0] = js_dup(sp[-2]);
|
|
sp[1] = js_dup(sp[-1]);
|
|
sp += 2;
|
|
BREAK;
|
|
CASE(OP_dup3): /* a b c -> a b c a b c */
|
|
sp[0] = js_dup(sp[-3]);
|
|
sp[1] = js_dup(sp[-2]);
|
|
sp[2] = js_dup(sp[-1]);
|
|
sp += 3;
|
|
BREAK;
|
|
CASE(OP_dup1): /* a b -> a a b */
|
|
sp[0] = sp[-1];
|
|
sp[-1] = js_dup(sp[-2]);
|
|
sp++;
|
|
BREAK;
|
|
CASE(OP_insert2): /* obj a -> a obj a (dup_x1) */
|
|
sp[0] = sp[-1];
|
|
sp[-1] = sp[-2];
|
|
sp[-2] = js_dup(sp[0]);
|
|
sp++;
|
|
BREAK;
|
|
CASE(OP_insert3): /* obj prop a -> a obj prop a (dup_x2) */
|
|
sp[0] = sp[-1];
|
|
sp[-1] = sp[-2];
|
|
sp[-2] = sp[-3];
|
|
sp[-3] = js_dup(sp[0]);
|
|
sp++;
|
|
BREAK;
|
|
CASE(OP_insert4): /* this obj prop a -> a this obj prop a */
|
|
sp[0] = sp[-1];
|
|
sp[-1] = sp[-2];
|
|
sp[-2] = sp[-3];
|
|
sp[-3] = sp[-4];
|
|
sp[-4] = js_dup(sp[0]);
|
|
sp++;
|
|
BREAK;
|
|
CASE(OP_perm3): /* obj a b -> a obj b (213) */
|
|
{
|
|
JSValue tmp;
|
|
tmp = sp[-2];
|
|
sp[-2] = sp[-3];
|
|
sp[-3] = tmp;
|
|
}
|
|
BREAK;
|
|
CASE(OP_rot3l): /* x a b -> a b x (231) */
|
|
{
|
|
JSValue tmp;
|
|
tmp = sp[-3];
|
|
sp[-3] = sp[-2];
|
|
sp[-2] = sp[-1];
|
|
sp[-1] = tmp;
|
|
}
|
|
BREAK;
|
|
CASE(OP_rot4l): /* x a b c -> a b c x */
|
|
{
|
|
JSValue tmp;
|
|
tmp = sp[-4];
|
|
sp[-4] = sp[-3];
|
|
sp[-3] = sp[-2];
|
|
sp[-2] = sp[-1];
|
|
sp[-1] = tmp;
|
|
}
|
|
BREAK;
|
|
CASE(OP_rot5l): /* x a b c d -> a b c d x */
|
|
{
|
|
JSValue tmp;
|
|
tmp = sp[-5];
|
|
sp[-5] = sp[-4];
|
|
sp[-4] = sp[-3];
|
|
sp[-3] = sp[-2];
|
|
sp[-2] = sp[-1];
|
|
sp[-1] = tmp;
|
|
}
|
|
BREAK;
|
|
CASE(OP_rot3r): /* a b x -> x a b (312) */
|
|
{
|
|
JSValue tmp;
|
|
tmp = sp[-1];
|
|
sp[-1] = sp[-2];
|
|
sp[-2] = sp[-3];
|
|
sp[-3] = tmp;
|
|
}
|
|
BREAK;
|
|
CASE(OP_perm4): /* obj prop a b -> a obj prop b */
|
|
{
|
|
JSValue tmp;
|
|
tmp = sp[-2];
|
|
sp[-2] = sp[-3];
|
|
sp[-3] = sp[-4];
|
|
sp[-4] = tmp;
|
|
}
|
|
BREAK;
|
|
CASE(OP_perm5): /* this obj prop a b -> a this obj prop b */
|
|
{
|
|
JSValue tmp;
|
|
tmp = sp[-2];
|
|
sp[-2] = sp[-3];
|
|
sp[-3] = sp[-4];
|
|
sp[-4] = sp[-5];
|
|
sp[-5] = tmp;
|
|
}
|
|
BREAK;
|
|
CASE(OP_swap): /* a b -> b a */
|
|
{
|
|
JSValue tmp;
|
|
tmp = sp[-2];
|
|
sp[-2] = sp[-1];
|
|
sp[-1] = tmp;
|
|
}
|
|
BREAK;
|
|
CASE(OP_swap2): /* a b c d -> c d a b */
|
|
{
|
|
JSValue tmp1, tmp2;
|
|
tmp1 = sp[-4];
|
|
tmp2 = sp[-3];
|
|
sp[-4] = sp[-2];
|
|
sp[-3] = sp[-1];
|
|
sp[-2] = tmp1;
|
|
sp[-1] = tmp2;
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_fclosure):
|
|
{
|
|
JSValue bfunc = js_dup(b->cpool[get_u32(pc)]);
|
|
pc += 4;
|
|
*sp++ = js_closure(ctx, bfunc, var_refs, sf);
|
|
if (unlikely(JS_IsException(sp[-1])))
|
|
goto exception;
|
|
}
|
|
BREAK;
|
|
CASE(OP_call0):
|
|
CASE(OP_call1):
|
|
CASE(OP_call2):
|
|
CASE(OP_call3):
|
|
call_argc = opcode - OP_call0;
|
|
goto has_call_argc;
|
|
CASE(OP_call):
|
|
CASE(OP_tail_call):
|
|
{
|
|
call_argc = get_u16(pc);
|
|
pc += 2;
|
|
goto has_call_argc;
|
|
has_call_argc:
|
|
call_argv = sp - call_argc;
|
|
sf->cur_pc = pc;
|
|
ret_val = JS_CallInternal(ctx, call_argv[-1], JS_UNDEFINED,
|
|
JS_UNDEFINED, call_argc, call_argv, 0);
|
|
if (unlikely(JS_IsException(ret_val)))
|
|
goto exception;
|
|
if (opcode == OP_tail_call)
|
|
goto done;
|
|
for(i = -1; i < call_argc; i++)
|
|
JS_FreeValue(ctx, call_argv[i]);
|
|
sp -= call_argc + 1;
|
|
*sp++ = ret_val;
|
|
}
|
|
BREAK;
|
|
CASE(OP_call_constructor):
|
|
{
|
|
call_argc = get_u16(pc);
|
|
pc += 2;
|
|
call_argv = sp - call_argc;
|
|
sf->cur_pc = pc;
|
|
ret_val = JS_CallConstructorInternal(ctx, call_argv[-2],
|
|
call_argv[-1],
|
|
call_argc, call_argv, 0);
|
|
if (unlikely(JS_IsException(ret_val)))
|
|
goto exception;
|
|
for(i = -2; i < call_argc; i++)
|
|
JS_FreeValue(ctx, call_argv[i]);
|
|
sp -= call_argc + 2;
|
|
*sp++ = ret_val;
|
|
}
|
|
BREAK;
|
|
CASE(OP_call_method):
|
|
CASE(OP_tail_call_method):
|
|
{
|
|
call_argc = get_u16(pc);
|
|
pc += 2;
|
|
call_argv = sp - call_argc;
|
|
sf->cur_pc = pc;
|
|
ret_val = JS_CallInternal(ctx, call_argv[-1], call_argv[-2],
|
|
JS_UNDEFINED, call_argc, call_argv, 0);
|
|
if (unlikely(JS_IsException(ret_val)))
|
|
goto exception;
|
|
if (opcode == OP_tail_call_method)
|
|
goto done;
|
|
for(i = -2; i < call_argc; i++)
|
|
JS_FreeValue(ctx, call_argv[i]);
|
|
sp -= call_argc + 2;
|
|
*sp++ = ret_val;
|
|
}
|
|
BREAK;
|
|
CASE(OP_array_from):
|
|
{
|
|
int i, ret;
|
|
|
|
call_argc = get_u16(pc);
|
|
pc += 2;
|
|
ret_val = JS_NewArray(ctx);
|
|
if (unlikely(JS_IsException(ret_val)))
|
|
goto exception;
|
|
call_argv = sp - call_argc;
|
|
for(i = 0; i < call_argc; i++) {
|
|
ret = JS_DefinePropertyValue(ctx, ret_val, __JS_AtomFromUInt32(i), call_argv[i],
|
|
JS_PROP_C_W_E | JS_PROP_THROW);
|
|
call_argv[i] = JS_UNDEFINED;
|
|
if (ret < 0) {
|
|
JS_FreeValue(ctx, ret_val);
|
|
goto exception;
|
|
}
|
|
}
|
|
sp -= call_argc;
|
|
*sp++ = ret_val;
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_apply):
|
|
{
|
|
int magic;
|
|
magic = get_u16(pc);
|
|
pc += 2;
|
|
sf->cur_pc = pc;
|
|
|
|
ret_val = js_function_apply(ctx, sp[-3], 2, &sp[-2], magic);
|
|
if (unlikely(JS_IsException(ret_val)))
|
|
goto exception;
|
|
JS_FreeValue(ctx, sp[-3]);
|
|
JS_FreeValue(ctx, sp[-2]);
|
|
JS_FreeValue(ctx, sp[-1]);
|
|
sp -= 3;
|
|
*sp++ = ret_val;
|
|
}
|
|
BREAK;
|
|
CASE(OP_return):
|
|
ret_val = *--sp;
|
|
goto done;
|
|
CASE(OP_return_undef):
|
|
ret_val = JS_UNDEFINED;
|
|
goto done;
|
|
|
|
CASE(OP_check_ctor_return):
|
|
/* return TRUE if 'this' should be returned */
|
|
if (!JS_IsObject(sp[-1])) {
|
|
if (!JS_IsUndefined(sp[-1])) {
|
|
JS_ThrowTypeError(caller_ctx, "derived class constructor must return an object or undefined");
|
|
goto exception;
|
|
}
|
|
sp[0] = JS_TRUE;
|
|
} else {
|
|
sp[0] = JS_FALSE;
|
|
}
|
|
sp++;
|
|
BREAK;
|
|
CASE(OP_check_ctor):
|
|
if (JS_IsUndefined(new_target)) {
|
|
JS_ThrowTypeError(ctx, "class constructors must be invoked with 'new'");
|
|
goto exception;
|
|
}
|
|
BREAK;
|
|
CASE(OP_check_brand):
|
|
if (JS_CheckBrand(ctx, sp[-2], sp[-1]) < 0)
|
|
goto exception;
|
|
BREAK;
|
|
CASE(OP_add_brand):
|
|
if (JS_AddBrand(ctx, sp[-2], sp[-1]) < 0)
|
|
goto exception;
|
|
JS_FreeValue(ctx, sp[-2]);
|
|
JS_FreeValue(ctx, sp[-1]);
|
|
sp -= 2;
|
|
BREAK;
|
|
|
|
CASE(OP_throw):
|
|
JS_Throw(ctx, *--sp);
|
|
goto exception;
|
|
|
|
CASE(OP_throw_error):
|
|
#define JS_THROW_VAR_RO 0
|
|
#define JS_THROW_VAR_REDECL 1
|
|
#define JS_THROW_VAR_UNINITIALIZED 2
|
|
#define JS_THROW_ERROR_DELETE_SUPER 3
|
|
#define JS_THROW_ERROR_ITERATOR_THROW 4
|
|
{
|
|
JSAtom atom;
|
|
int type;
|
|
atom = get_u32(pc);
|
|
type = pc[4];
|
|
pc += 5;
|
|
if (type == JS_THROW_VAR_RO)
|
|
JS_ThrowTypeErrorReadOnly(ctx, JS_PROP_THROW, atom);
|
|
else
|
|
if (type == JS_THROW_VAR_REDECL)
|
|
JS_ThrowSyntaxErrorVarRedeclaration(ctx, atom);
|
|
else
|
|
if (type == JS_THROW_VAR_UNINITIALIZED)
|
|
JS_ThrowReferenceErrorUninitialized(ctx, atom);
|
|
else
|
|
if (type == JS_THROW_ERROR_DELETE_SUPER)
|
|
JS_ThrowReferenceError(ctx, "unsupported reference to 'super'");
|
|
else
|
|
if (type == JS_THROW_ERROR_ITERATOR_THROW)
|
|
JS_ThrowTypeError(ctx, "iterator does not have a throw method");
|
|
else
|
|
JS_ThrowInternalError(ctx, "invalid throw var type %d", type);
|
|
}
|
|
goto exception;
|
|
|
|
CASE(OP_eval):
|
|
{
|
|
JSValue obj;
|
|
int scope_idx;
|
|
call_argc = get_u16(pc);
|
|
scope_idx = get_u16(pc + 2) - 1;
|
|
pc += 4;
|
|
call_argv = sp - call_argc;
|
|
sf->cur_pc = pc;
|
|
if (js_same_value(ctx, call_argv[-1], ctx->eval_obj)) {
|
|
if (call_argc >= 1)
|
|
obj = call_argv[0];
|
|
else
|
|
obj = JS_UNDEFINED;
|
|
ret_val = JS_EvalObject(ctx, JS_UNDEFINED, obj,
|
|
JS_EVAL_TYPE_DIRECT, scope_idx);
|
|
} else {
|
|
ret_val = JS_CallInternal(ctx, call_argv[-1], JS_UNDEFINED,
|
|
JS_UNDEFINED, call_argc, call_argv, 0);
|
|
}
|
|
if (unlikely(JS_IsException(ret_val)))
|
|
goto exception;
|
|
for(i = -1; i < call_argc; i++)
|
|
JS_FreeValue(ctx, call_argv[i]);
|
|
sp -= call_argc + 1;
|
|
*sp++ = ret_val;
|
|
}
|
|
BREAK;
|
|
/* could merge with OP_apply */
|
|
CASE(OP_apply_eval):
|
|
{
|
|
int scope_idx;
|
|
uint32_t len;
|
|
JSValue *tab;
|
|
JSValue obj;
|
|
|
|
scope_idx = get_u16(pc) - 1;
|
|
pc += 2;
|
|
sf->cur_pc = pc;
|
|
tab = build_arg_list(ctx, &len, sp[-1]);
|
|
if (!tab)
|
|
goto exception;
|
|
if (js_same_value(ctx, sp[-2], ctx->eval_obj)) {
|
|
if (len >= 1)
|
|
obj = tab[0];
|
|
else
|
|
obj = JS_UNDEFINED;
|
|
ret_val = JS_EvalObject(ctx, JS_UNDEFINED, obj,
|
|
JS_EVAL_TYPE_DIRECT, scope_idx);
|
|
} else {
|
|
ret_val = JS_Call(ctx, sp[-2], JS_UNDEFINED, len,
|
|
tab);
|
|
}
|
|
free_arg_list(ctx, tab, len);
|
|
if (unlikely(JS_IsException(ret_val)))
|
|
goto exception;
|
|
JS_FreeValue(ctx, sp[-2]);
|
|
JS_FreeValue(ctx, sp[-1]);
|
|
sp -= 2;
|
|
*sp++ = ret_val;
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_regexp):
|
|
{
|
|
sp[-2] = js_regexp_constructor_internal(ctx, JS_UNDEFINED,
|
|
sp[-2], sp[-1]);
|
|
sp--;
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_get_super):
|
|
{
|
|
JSValue proto;
|
|
proto = JS_GetPrototype(ctx, sp[-1]);
|
|
if (JS_IsException(proto))
|
|
goto exception;
|
|
JS_FreeValue(ctx, sp[-1]);
|
|
sp[-1] = proto;
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_import):
|
|
{
|
|
JSValue val;
|
|
sf->cur_pc = pc;
|
|
val = js_dynamic_import(ctx, sp[-1]);
|
|
if (JS_IsException(val))
|
|
goto exception;
|
|
JS_FreeValue(ctx, sp[-1]);
|
|
sp[-1] = val;
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_check_var):
|
|
{
|
|
int ret;
|
|
JSAtom atom;
|
|
atom = get_u32(pc);
|
|
pc += 4;
|
|
|
|
ret = JS_CheckGlobalVar(ctx, atom);
|
|
if (ret < 0)
|
|
goto exception;
|
|
*sp++ = js_bool(ret);
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_get_var_undef):
|
|
CASE(OP_get_var):
|
|
{
|
|
JSValue val;
|
|
JSAtom atom;
|
|
atom = get_u32(pc);
|
|
pc += 4;
|
|
sf->cur_pc = pc;
|
|
|
|
val = JS_GetGlobalVar(ctx, atom, opcode - OP_get_var_undef);
|
|
if (unlikely(JS_IsException(val)))
|
|
goto exception;
|
|
*sp++ = val;
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_put_var):
|
|
CASE(OP_put_var_init):
|
|
{
|
|
int ret;
|
|
JSAtom atom;
|
|
atom = get_u32(pc);
|
|
pc += 4;
|
|
sf->cur_pc = pc;
|
|
|
|
ret = JS_SetGlobalVar(ctx, atom, sp[-1], opcode - OP_put_var);
|
|
sp--;
|
|
if (unlikely(ret < 0))
|
|
goto exception;
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_put_var_strict):
|
|
{
|
|
int ret;
|
|
JSAtom atom;
|
|
atom = get_u32(pc);
|
|
pc += 4;
|
|
sf->cur_pc = pc;
|
|
|
|
/* sp[-2] is JS_TRUE or JS_FALSE */
|
|
if (unlikely(!JS_VALUE_GET_INT(sp[-2]))) {
|
|
JS_ThrowReferenceErrorNotDefined(ctx, atom);
|
|
goto exception;
|
|
}
|
|
ret = JS_SetGlobalVar(ctx, atom, sp[-1], 2);
|
|
sp -= 2;
|
|
if (unlikely(ret < 0))
|
|
goto exception;
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_check_define_var):
|
|
{
|
|
JSAtom atom;
|
|
int flags;
|
|
atom = get_u32(pc);
|
|
flags = pc[4];
|
|
pc += 5;
|
|
if (JS_CheckDefineGlobalVar(ctx, atom, flags))
|
|
goto exception;
|
|
}
|
|
BREAK;
|
|
CASE(OP_define_var):
|
|
{
|
|
JSAtom atom;
|
|
int flags;
|
|
atom = get_u32(pc);
|
|
flags = pc[4];
|
|
pc += 5;
|
|
if (JS_DefineGlobalVar(ctx, atom, flags))
|
|
goto exception;
|
|
}
|
|
BREAK;
|
|
CASE(OP_define_func):
|
|
{
|
|
JSAtom atom;
|
|
int flags;
|
|
atom = get_u32(pc);
|
|
flags = pc[4];
|
|
pc += 5;
|
|
if (JS_DefineGlobalFunction(ctx, atom, sp[-1], flags))
|
|
goto exception;
|
|
JS_FreeValue(ctx, sp[-1]);
|
|
sp--;
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_get_loc):
|
|
{
|
|
int idx;
|
|
idx = get_u16(pc);
|
|
pc += 2;
|
|
sp[0] = js_dup(var_buf[idx]);
|
|
sp++;
|
|
}
|
|
BREAK;
|
|
CASE(OP_put_loc):
|
|
{
|
|
int idx;
|
|
idx = get_u16(pc);
|
|
pc += 2;
|
|
set_value(ctx, &var_buf[idx], sp[-1]);
|
|
sp--;
|
|
}
|
|
BREAK;
|
|
CASE(OP_set_loc):
|
|
{
|
|
int idx;
|
|
idx = get_u16(pc);
|
|
pc += 2;
|
|
set_value(ctx, &var_buf[idx], js_dup(sp[-1]));
|
|
}
|
|
BREAK;
|
|
CASE(OP_get_arg):
|
|
{
|
|
int idx;
|
|
idx = get_u16(pc);
|
|
pc += 2;
|
|
sp[0] = js_dup(arg_buf[idx]);
|
|
sp++;
|
|
}
|
|
BREAK;
|
|
CASE(OP_put_arg):
|
|
{
|
|
int idx;
|
|
idx = get_u16(pc);
|
|
pc += 2;
|
|
set_value(ctx, &arg_buf[idx], sp[-1]);
|
|
sp--;
|
|
}
|
|
BREAK;
|
|
CASE(OP_set_arg):
|
|
{
|
|
int idx;
|
|
idx = get_u16(pc);
|
|
pc += 2;
|
|
set_value(ctx, &arg_buf[idx], js_dup(sp[-1]));
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_get_loc8): *sp++ = js_dup(var_buf[*pc++]); BREAK;
|
|
CASE(OP_put_loc8): set_value(ctx, &var_buf[*pc++], *--sp); BREAK;
|
|
CASE(OP_set_loc8): set_value(ctx, &var_buf[*pc++], js_dup(sp[-1])); BREAK;
|
|
|
|
// Observation: get_loc0 and get_loc1 are individually very
|
|
// frequent opcodes _and_ they are very often paired together,
|
|
// making them ideal candidates for opcode fusion.
|
|
CASE(OP_get_loc0_loc1):
|
|
*sp++ = js_dup(var_buf[0]);
|
|
*sp++ = js_dup(var_buf[1]);
|
|
BREAK;
|
|
|
|
CASE(OP_get_loc0): *sp++ = js_dup(var_buf[0]); BREAK;
|
|
CASE(OP_get_loc1): *sp++ = js_dup(var_buf[1]); BREAK;
|
|
CASE(OP_get_loc2): *sp++ = js_dup(var_buf[2]); BREAK;
|
|
CASE(OP_get_loc3): *sp++ = js_dup(var_buf[3]); BREAK;
|
|
CASE(OP_put_loc0): set_value(ctx, &var_buf[0], *--sp); BREAK;
|
|
CASE(OP_put_loc1): set_value(ctx, &var_buf[1], *--sp); BREAK;
|
|
CASE(OP_put_loc2): set_value(ctx, &var_buf[2], *--sp); BREAK;
|
|
CASE(OP_put_loc3): set_value(ctx, &var_buf[3], *--sp); BREAK;
|
|
CASE(OP_set_loc0): set_value(ctx, &var_buf[0], js_dup(sp[-1])); BREAK;
|
|
CASE(OP_set_loc1): set_value(ctx, &var_buf[1], js_dup(sp[-1])); BREAK;
|
|
CASE(OP_set_loc2): set_value(ctx, &var_buf[2], js_dup(sp[-1])); BREAK;
|
|
CASE(OP_set_loc3): set_value(ctx, &var_buf[3], js_dup(sp[-1])); BREAK;
|
|
CASE(OP_get_arg0): *sp++ = js_dup(arg_buf[0]); BREAK;
|
|
CASE(OP_get_arg1): *sp++ = js_dup(arg_buf[1]); BREAK;
|
|
CASE(OP_get_arg2): *sp++ = js_dup(arg_buf[2]); BREAK;
|
|
CASE(OP_get_arg3): *sp++ = js_dup(arg_buf[3]); BREAK;
|
|
CASE(OP_put_arg0): set_value(ctx, &arg_buf[0], *--sp); BREAK;
|
|
CASE(OP_put_arg1): set_value(ctx, &arg_buf[1], *--sp); BREAK;
|
|
CASE(OP_put_arg2): set_value(ctx, &arg_buf[2], *--sp); BREAK;
|
|
CASE(OP_put_arg3): set_value(ctx, &arg_buf[3], *--sp); BREAK;
|
|
CASE(OP_set_arg0): set_value(ctx, &arg_buf[0], js_dup(sp[-1])); BREAK;
|
|
CASE(OP_set_arg1): set_value(ctx, &arg_buf[1], js_dup(sp[-1])); BREAK;
|
|
CASE(OP_set_arg2): set_value(ctx, &arg_buf[2], js_dup(sp[-1])); BREAK;
|
|
CASE(OP_set_arg3): set_value(ctx, &arg_buf[3], js_dup(sp[-1])); BREAK;
|
|
CASE(OP_get_var_ref0): *sp++ = js_dup(*var_refs[0]->pvalue); BREAK;
|
|
CASE(OP_get_var_ref1): *sp++ = js_dup(*var_refs[1]->pvalue); BREAK;
|
|
CASE(OP_get_var_ref2): *sp++ = js_dup(*var_refs[2]->pvalue); BREAK;
|
|
CASE(OP_get_var_ref3): *sp++ = js_dup(*var_refs[3]->pvalue); BREAK;
|
|
CASE(OP_put_var_ref0): set_value(ctx, var_refs[0]->pvalue, *--sp); BREAK;
|
|
CASE(OP_put_var_ref1): set_value(ctx, var_refs[1]->pvalue, *--sp); BREAK;
|
|
CASE(OP_put_var_ref2): set_value(ctx, var_refs[2]->pvalue, *--sp); BREAK;
|
|
CASE(OP_put_var_ref3): set_value(ctx, var_refs[3]->pvalue, *--sp); BREAK;
|
|
CASE(OP_set_var_ref0): set_value(ctx, var_refs[0]->pvalue, js_dup(sp[-1])); BREAK;
|
|
CASE(OP_set_var_ref1): set_value(ctx, var_refs[1]->pvalue, js_dup(sp[-1])); BREAK;
|
|
CASE(OP_set_var_ref2): set_value(ctx, var_refs[2]->pvalue, js_dup(sp[-1])); BREAK;
|
|
CASE(OP_set_var_ref3): set_value(ctx, var_refs[3]->pvalue, js_dup(sp[-1])); BREAK;
|
|
|
|
CASE(OP_get_var_ref):
|
|
{
|
|
int idx;
|
|
JSValue val;
|
|
idx = get_u16(pc);
|
|
pc += 2;
|
|
val = *var_refs[idx]->pvalue;
|
|
sp[0] = js_dup(val);
|
|
sp++;
|
|
}
|
|
BREAK;
|
|
CASE(OP_put_var_ref):
|
|
{
|
|
int idx;
|
|
idx = get_u16(pc);
|
|
pc += 2;
|
|
set_value(ctx, var_refs[idx]->pvalue, sp[-1]);
|
|
sp--;
|
|
}
|
|
BREAK;
|
|
CASE(OP_set_var_ref):
|
|
{
|
|
int idx;
|
|
idx = get_u16(pc);
|
|
pc += 2;
|
|
set_value(ctx, var_refs[idx]->pvalue, js_dup(sp[-1]));
|
|
}
|
|
BREAK;
|
|
CASE(OP_get_var_ref_check):
|
|
{
|
|
int idx;
|
|
JSValue val;
|
|
idx = get_u16(pc);
|
|
pc += 2;
|
|
val = *var_refs[idx]->pvalue;
|
|
if (unlikely(JS_IsUninitialized(val))) {
|
|
JS_ThrowReferenceErrorUninitialized2(ctx, b, idx, TRUE);
|
|
goto exception;
|
|
}
|
|
sp[0] = js_dup(val);
|
|
sp++;
|
|
}
|
|
BREAK;
|
|
CASE(OP_put_var_ref_check):
|
|
{
|
|
int idx;
|
|
idx = get_u16(pc);
|
|
pc += 2;
|
|
if (unlikely(JS_IsUninitialized(*var_refs[idx]->pvalue))) {
|
|
JS_ThrowReferenceErrorUninitialized2(ctx, b, idx, TRUE);
|
|
goto exception;
|
|
}
|
|
set_value(ctx, var_refs[idx]->pvalue, sp[-1]);
|
|
sp--;
|
|
}
|
|
BREAK;
|
|
CASE(OP_put_var_ref_check_init):
|
|
{
|
|
int idx;
|
|
idx = get_u16(pc);
|
|
pc += 2;
|
|
if (unlikely(!JS_IsUninitialized(*var_refs[idx]->pvalue))) {
|
|
JS_ThrowReferenceErrorUninitialized2(ctx, b, idx, TRUE);
|
|
goto exception;
|
|
}
|
|
set_value(ctx, var_refs[idx]->pvalue, sp[-1]);
|
|
sp--;
|
|
}
|
|
BREAK;
|
|
CASE(OP_set_loc_uninitialized):
|
|
{
|
|
int idx;
|
|
idx = get_u16(pc);
|
|
pc += 2;
|
|
set_value(ctx, &var_buf[idx], JS_UNINITIALIZED);
|
|
}
|
|
BREAK;
|
|
CASE(OP_get_loc_check):
|
|
{
|
|
int idx;
|
|
idx = get_u16(pc);
|
|
pc += 2;
|
|
if (unlikely(JS_IsUninitialized(var_buf[idx]))) {
|
|
JS_ThrowReferenceErrorUninitialized2(caller_ctx, b, idx,
|
|
FALSE);
|
|
goto exception;
|
|
}
|
|
sp[0] = js_dup(var_buf[idx]);
|
|
sp++;
|
|
}
|
|
BREAK;
|
|
CASE(OP_put_loc_check):
|
|
{
|
|
int idx;
|
|
idx = get_u16(pc);
|
|
pc += 2;
|
|
if (unlikely(JS_IsUninitialized(var_buf[idx]))) {
|
|
JS_ThrowReferenceErrorUninitialized2(caller_ctx, b, idx,
|
|
FALSE);
|
|
goto exception;
|
|
}
|
|
set_value(ctx, &var_buf[idx], sp[-1]);
|
|
sp--;
|
|
}
|
|
BREAK;
|
|
CASE(OP_put_loc_check_init):
|
|
{
|
|
int idx;
|
|
idx = get_u16(pc);
|
|
pc += 2;
|
|
if (unlikely(!JS_IsUninitialized(var_buf[idx]))) {
|
|
JS_ThrowReferenceError(caller_ctx,
|
|
"'this' can be initialized only once");
|
|
goto exception;
|
|
}
|
|
set_value(ctx, &var_buf[idx], sp[-1]);
|
|
sp--;
|
|
}
|
|
BREAK;
|
|
CASE(OP_close_loc):
|
|
{
|
|
int idx;
|
|
idx = get_u16(pc);
|
|
pc += 2;
|
|
close_lexical_var(ctx, sf, idx, FALSE);
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_make_loc_ref):
|
|
CASE(OP_make_arg_ref):
|
|
CASE(OP_make_var_ref_ref):
|
|
{
|
|
JSVarRef *var_ref;
|
|
JSProperty *pr;
|
|
JSAtom atom;
|
|
int idx;
|
|
atom = get_u32(pc);
|
|
idx = get_u16(pc + 4);
|
|
pc += 6;
|
|
*sp++ = JS_NewObjectProto(ctx, JS_NULL);
|
|
if (unlikely(JS_IsException(sp[-1])))
|
|
goto exception;
|
|
if (opcode == OP_make_var_ref_ref) {
|
|
var_ref = var_refs[idx];
|
|
var_ref->header.ref_count++;
|
|
} else {
|
|
var_ref = get_var_ref(ctx, sf, idx, opcode == OP_make_arg_ref);
|
|
if (!var_ref)
|
|
goto exception;
|
|
}
|
|
pr = add_property(ctx, JS_VALUE_GET_OBJ(sp[-1]), atom,
|
|
JS_PROP_WRITABLE | JS_PROP_VARREF);
|
|
if (!pr) {
|
|
free_var_ref(rt, var_ref);
|
|
goto exception;
|
|
}
|
|
pr->u.var_ref = var_ref;
|
|
*sp++ = JS_AtomToValue(ctx, atom);
|
|
}
|
|
BREAK;
|
|
CASE(OP_make_var_ref):
|
|
{
|
|
JSAtom atom;
|
|
atom = get_u32(pc);
|
|
pc += 4;
|
|
|
|
if (JS_GetGlobalVarRef(ctx, atom, sp))
|
|
goto exception;
|
|
sp += 2;
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_goto):
|
|
pc += (int32_t)get_u32(pc);
|
|
if (unlikely(js_poll_interrupts(ctx)))
|
|
goto exception;
|
|
BREAK;
|
|
CASE(OP_goto16):
|
|
pc += (int16_t)get_u16(pc);
|
|
if (unlikely(js_poll_interrupts(ctx)))
|
|
goto exception;
|
|
BREAK;
|
|
CASE(OP_goto8):
|
|
pc += (int8_t)pc[0];
|
|
if (unlikely(js_poll_interrupts(ctx)))
|
|
goto exception;
|
|
BREAK;
|
|
CASE(OP_if_true):
|
|
{
|
|
int res;
|
|
JSValue op1;
|
|
|
|
op1 = sp[-1];
|
|
pc += 4;
|
|
if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_UNDEFINED) {
|
|
res = JS_VALUE_GET_INT(op1);
|
|
} else {
|
|
res = JS_ToBoolFree(ctx, op1);
|
|
}
|
|
sp--;
|
|
if (res) {
|
|
pc += (int32_t)get_u32(pc - 4) - 4;
|
|
}
|
|
if (unlikely(js_poll_interrupts(ctx)))
|
|
goto exception;
|
|
}
|
|
BREAK;
|
|
CASE(OP_if_false):
|
|
{
|
|
int res;
|
|
JSValue op1;
|
|
|
|
op1 = sp[-1];
|
|
pc += 4;
|
|
if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_UNDEFINED) {
|
|
res = JS_VALUE_GET_INT(op1);
|
|
} else {
|
|
res = JS_ToBoolFree(ctx, op1);
|
|
}
|
|
sp--;
|
|
if (!res) {
|
|
pc += (int32_t)get_u32(pc - 4) - 4;
|
|
}
|
|
if (unlikely(js_poll_interrupts(ctx)))
|
|
goto exception;
|
|
}
|
|
BREAK;
|
|
CASE(OP_if_true8):
|
|
{
|
|
int res;
|
|
JSValue op1;
|
|
|
|
op1 = sp[-1];
|
|
pc += 1;
|
|
if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_UNDEFINED) {
|
|
res = JS_VALUE_GET_INT(op1);
|
|
} else {
|
|
res = JS_ToBoolFree(ctx, op1);
|
|
}
|
|
sp--;
|
|
if (res) {
|
|
pc += (int8_t)pc[-1] - 1;
|
|
}
|
|
if (unlikely(js_poll_interrupts(ctx)))
|
|
goto exception;
|
|
}
|
|
BREAK;
|
|
CASE(OP_if_false8):
|
|
{
|
|
int res;
|
|
JSValue op1;
|
|
|
|
op1 = sp[-1];
|
|
pc += 1;
|
|
if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_UNDEFINED) {
|
|
res = JS_VALUE_GET_INT(op1);
|
|
} else {
|
|
res = JS_ToBoolFree(ctx, op1);
|
|
}
|
|
sp--;
|
|
if (!res) {
|
|
pc += (int8_t)pc[-1] - 1;
|
|
}
|
|
if (unlikely(js_poll_interrupts(ctx)))
|
|
goto exception;
|
|
}
|
|
BREAK;
|
|
CASE(OP_catch):
|
|
{
|
|
int32_t diff;
|
|
diff = get_u32(pc);
|
|
sp[0] = JS_NewCatchOffset(ctx, pc + diff - b->byte_code_buf);
|
|
sp++;
|
|
pc += 4;
|
|
}
|
|
BREAK;
|
|
CASE(OP_gosub):
|
|
{
|
|
int32_t diff;
|
|
diff = get_u32(pc);
|
|
/* XXX: should have a different tag to avoid security flaw */
|
|
sp[0] = js_int32(pc + 4 - b->byte_code_buf);
|
|
sp++;
|
|
pc += diff;
|
|
}
|
|
BREAK;
|
|
CASE(OP_ret):
|
|
{
|
|
JSValue op1;
|
|
uint32_t pos;
|
|
op1 = sp[-1];
|
|
if (unlikely(JS_VALUE_GET_TAG(op1) != JS_TAG_INT))
|
|
goto ret_fail;
|
|
pos = JS_VALUE_GET_INT(op1);
|
|
if (unlikely(pos >= b->byte_code_len)) {
|
|
ret_fail:
|
|
JS_ThrowInternalError(ctx, "invalid ret value");
|
|
goto exception;
|
|
}
|
|
sp--;
|
|
pc = b->byte_code_buf + pos;
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_for_in_start):
|
|
sf->cur_pc = pc;
|
|
if (js_for_in_start(ctx, sp))
|
|
goto exception;
|
|
BREAK;
|
|
CASE(OP_for_in_next):
|
|
sf->cur_pc = pc;
|
|
if (js_for_in_next(ctx, sp))
|
|
goto exception;
|
|
sp += 2;
|
|
BREAK;
|
|
CASE(OP_for_of_start):
|
|
sf->cur_pc = pc;
|
|
if (js_for_of_start(ctx, sp, FALSE))
|
|
goto exception;
|
|
sp += 1;
|
|
*sp++ = JS_NewCatchOffset(ctx, 0);
|
|
BREAK;
|
|
CASE(OP_for_of_next):
|
|
{
|
|
int offset = -3 - pc[0];
|
|
pc += 1;
|
|
sf->cur_pc = pc;
|
|
if (js_for_of_next(ctx, sp, offset))
|
|
goto exception;
|
|
sp += 2;
|
|
}
|
|
BREAK;
|
|
CASE(OP_for_await_of_start):
|
|
sf->cur_pc = pc;
|
|
if (js_for_of_start(ctx, sp, TRUE))
|
|
goto exception;
|
|
sp += 1;
|
|
*sp++ = JS_NewCatchOffset(ctx, 0);
|
|
BREAK;
|
|
CASE(OP_iterator_get_value_done):
|
|
sf->cur_pc = pc;
|
|
if (js_iterator_get_value_done(ctx, sp))
|
|
goto exception;
|
|
sp += 1;
|
|
BREAK;
|
|
CASE(OP_iterator_check_object):
|
|
if (unlikely(!JS_IsObject(sp[-1]))) {
|
|
JS_ThrowTypeError(ctx, "iterator must return an object");
|
|
goto exception;
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_iterator_close):
|
|
/* iter_obj next catch_offset -> */
|
|
sp--; /* drop the catch offset to avoid getting caught by exception */
|
|
JS_FreeValue(ctx, sp[-1]); /* drop the next method */
|
|
sp--;
|
|
if (!JS_IsUndefined(sp[-1])) {
|
|
sf->cur_pc = pc;
|
|
if (JS_IteratorClose(ctx, sp[-1], FALSE))
|
|
goto exception;
|
|
JS_FreeValue(ctx, sp[-1]);
|
|
}
|
|
sp--;
|
|
BREAK;
|
|
CASE(OP_nip_catch):
|
|
{
|
|
JSValue ret_val;
|
|
/* catch_offset ... ret_val -> ret_eval */
|
|
ret_val = *--sp;
|
|
while (sp > stack_buf &&
|
|
JS_VALUE_GET_TAG(sp[-1]) != JS_TAG_CATCH_OFFSET) {
|
|
JS_FreeValue(ctx, *--sp);
|
|
}
|
|
if (unlikely(sp == stack_buf)) {
|
|
JS_ThrowInternalError(ctx, "nip_catch");
|
|
JS_FreeValue(ctx, ret_val);
|
|
goto exception;
|
|
}
|
|
sp[-1] = ret_val;
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_iterator_next):
|
|
/* stack: iter_obj next catch_offset val */
|
|
{
|
|
JSValue ret;
|
|
sf->cur_pc = pc;
|
|
ret = JS_Call(ctx, sp[-3], sp[-4], 1, (sp - 1));
|
|
if (JS_IsException(ret))
|
|
goto exception;
|
|
JS_FreeValue(ctx, sp[-1]);
|
|
sp[-1] = ret;
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_iterator_call):
|
|
/* stack: iter_obj next catch_offset val */
|
|
{
|
|
JSValue method, ret;
|
|
BOOL ret_flag;
|
|
int flags;
|
|
flags = *pc++;
|
|
sf->cur_pc = pc;
|
|
method = JS_GetProperty(ctx, sp[-4], (flags & 1) ?
|
|
JS_ATOM_throw : JS_ATOM_return);
|
|
if (JS_IsException(method))
|
|
goto exception;
|
|
if (JS_IsUndefined(method) || JS_IsNull(method)) {
|
|
ret_flag = TRUE;
|
|
} else {
|
|
if (flags & 2) {
|
|
/* no argument */
|
|
ret = JS_CallFree(ctx, method, sp[-4],
|
|
0, NULL);
|
|
} else {
|
|
ret = JS_CallFree(ctx, method, sp[-4],
|
|
1, (sp - 1));
|
|
}
|
|
if (JS_IsException(ret))
|
|
goto exception;
|
|
JS_FreeValue(ctx, sp[-1]);
|
|
sp[-1] = ret;
|
|
ret_flag = FALSE;
|
|
}
|
|
sp[0] = js_bool(ret_flag);
|
|
sp += 1;
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_lnot):
|
|
{
|
|
int res;
|
|
JSValue op1;
|
|
|
|
op1 = sp[-1];
|
|
if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_UNDEFINED) {
|
|
res = JS_VALUE_GET_INT(op1) != 0;
|
|
} else {
|
|
res = JS_ToBoolFree(ctx, op1);
|
|
}
|
|
sp[-1] = js_bool(!res);
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_get_field):
|
|
{
|
|
JSValue val;
|
|
JSAtom atom;
|
|
atom = get_u32(pc);
|
|
pc += 4;
|
|
sf->cur_pc = pc;
|
|
val = JS_GetPropertyInternal2(ctx, sp[-1], atom, sp[-1], ic, FALSE);
|
|
if (unlikely(JS_IsException(val)))
|
|
goto exception;
|
|
if (ic && ic->updated == TRUE) {
|
|
ic->updated = FALSE;
|
|
put_u8(pc - 5, OP_get_field_ic);
|
|
put_u32(pc - 4, ic->updated_offset);
|
|
JS_FreeAtom(ctx, atom);
|
|
}
|
|
JS_FreeValue(ctx, sp[-1]);
|
|
sp[-1] = val;
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_get_field_ic):
|
|
{
|
|
JSValue val;
|
|
JSAtom atom;
|
|
int32_t ic_offset;
|
|
ic_offset = get_u32(pc);
|
|
atom = get_ic_atom(ic, ic_offset);
|
|
pc += 4;
|
|
sf->cur_pc = pc;
|
|
val = JS_GetPropertyInternalWithIC(ctx, sp[-1], atom, sp[-1], ic, ic_offset, FALSE);
|
|
ic->updated = FALSE;
|
|
if (unlikely(JS_IsException(val)))
|
|
goto exception;
|
|
JS_FreeValue(ctx, sp[-1]);
|
|
sp[-1] = val;
|
|
}
|
|
BREAK;
|
|
CASE(OP_get_field2):
|
|
{
|
|
JSValue val;
|
|
JSAtom atom;
|
|
atom = get_u32(pc);
|
|
pc += 4;
|
|
sf->cur_pc = pc;
|
|
val = JS_GetPropertyInternal2(ctx, sp[-1], atom, sp[-1], NULL, FALSE);
|
|
if (unlikely(JS_IsException(val)))
|
|
goto exception;
|
|
if (ic != NULL && ic->updated == TRUE) {
|
|
ic->updated = FALSE;
|
|
put_u8(pc - 5, OP_get_field2_ic);
|
|
put_u32(pc - 4, ic->updated_offset);
|
|
JS_FreeAtom(ctx, atom);
|
|
}
|
|
*sp++ = val;
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_get_field2_ic):
|
|
{
|
|
JSValue val;
|
|
JSAtom atom;
|
|
int32_t ic_offset;
|
|
ic_offset = get_u32(pc);
|
|
atom = get_ic_atom(ic, ic_offset);
|
|
pc += 4;
|
|
sf->cur_pc = pc;
|
|
val = JS_GetPropertyInternalWithIC(ctx, sp[-1], atom, sp[-1], ic, ic_offset, FALSE);
|
|
ic->updated = FALSE;
|
|
if (unlikely(JS_IsException(val)))
|
|
goto exception;
|
|
*sp++ = val;
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_put_field):
|
|
{
|
|
int ret;
|
|
JSAtom atom;
|
|
atom = get_u32(pc);
|
|
pc += 4;
|
|
sf->cur_pc = pc;
|
|
ret = JS_SetPropertyInternal2(ctx,
|
|
sp[-2], atom,
|
|
sp[-1], sp[-2],
|
|
JS_PROP_THROW_STRICT, ic);
|
|
JS_FreeValue(ctx, sp[-2]);
|
|
sp -= 2;
|
|
if (unlikely(ret < 0))
|
|
goto exception;
|
|
if (ic != NULL && ic->updated == TRUE) {
|
|
ic->updated = FALSE;
|
|
put_u8(pc - 5, OP_put_field_ic);
|
|
put_u32(pc - 4, ic->updated_offset);
|
|
JS_FreeAtom(ctx, atom);
|
|
}
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_put_field_ic):
|
|
{
|
|
int ret;
|
|
JSAtom atom;
|
|
int32_t ic_offset;
|
|
ic_offset = get_u32(pc);
|
|
atom = get_ic_atom(ic, ic_offset);
|
|
pc += 4;
|
|
sf->cur_pc = pc;
|
|
ret = JS_SetPropertyInternalWithIC(ctx, sp[-2], atom, sp[-1], JS_PROP_THROW_STRICT, ic, ic_offset);
|
|
ic->updated = FALSE;
|
|
JS_FreeValue(ctx, sp[-2]);
|
|
sp -= 2;
|
|
if (unlikely(ret < 0))
|
|
goto exception;
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_private_symbol):
|
|
{
|
|
JSAtom atom;
|
|
JSValue val;
|
|
|
|
atom = get_u32(pc);
|
|
pc += 4;
|
|
val = JS_NewSymbolFromAtom(ctx, atom, JS_ATOM_TYPE_PRIVATE);
|
|
if (JS_IsException(val))
|
|
goto exception;
|
|
*sp++ = val;
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_get_private_field):
|
|
{
|
|
JSValue val;
|
|
sf->cur_pc = pc;
|
|
val = JS_GetPrivateField(ctx, sp[-2], sp[-1]);
|
|
JS_FreeValue(ctx, sp[-1]);
|
|
JS_FreeValue(ctx, sp[-2]);
|
|
sp[-2] = val;
|
|
sp--;
|
|
if (unlikely(JS_IsException(val)))
|
|
goto exception;
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_put_private_field):
|
|
{
|
|
int ret;
|
|
sf->cur_pc = pc;
|
|
ret = JS_SetPrivateField(ctx, sp[-3], sp[-1], sp[-2]);
|
|
JS_FreeValue(ctx, sp[-3]);
|
|
JS_FreeValue(ctx, sp[-1]);
|
|
sp -= 3;
|
|
if (unlikely(ret < 0))
|
|
goto exception;
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_define_private_field):
|
|
{
|
|
int ret;
|
|
ret = JS_DefinePrivateField(ctx, sp[-3], sp[-2], sp[-1]);
|
|
JS_FreeValue(ctx, sp[-2]);
|
|
sp -= 2;
|
|
if (unlikely(ret < 0))
|
|
goto exception;
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_define_field):
|
|
{
|
|
int ret;
|
|
JSAtom atom;
|
|
atom = get_u32(pc);
|
|
pc += 4;
|
|
|
|
ret = JS_DefinePropertyValue(ctx, sp[-2], atom, sp[-1],
|
|
JS_PROP_C_W_E | JS_PROP_THROW);
|
|
sp--;
|
|
if (unlikely(ret < 0))
|
|
goto exception;
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_set_name):
|
|
{
|
|
int ret;
|
|
JSAtom atom;
|
|
atom = get_u32(pc);
|
|
pc += 4;
|
|
|
|
ret = JS_DefineObjectName(ctx, sp[-1], atom, JS_PROP_CONFIGURABLE);
|
|
if (unlikely(ret < 0))
|
|
goto exception;
|
|
}
|
|
BREAK;
|
|
CASE(OP_set_name_computed):
|
|
{
|
|
int ret;
|
|
ret = JS_DefineObjectNameComputed(ctx, sp[-1], sp[-2], JS_PROP_CONFIGURABLE);
|
|
if (unlikely(ret < 0))
|
|
goto exception;
|
|
}
|
|
BREAK;
|
|
CASE(OP_set_proto):
|
|
{
|
|
JSValue proto;
|
|
proto = sp[-1];
|
|
if (JS_IsObject(proto) || JS_IsNull(proto)) {
|
|
if (JS_SetPrototypeInternal(ctx, sp[-2], proto, TRUE) < 0)
|
|
goto exception;
|
|
}
|
|
JS_FreeValue(ctx, proto);
|
|
sp--;
|
|
}
|
|
BREAK;
|
|
CASE(OP_set_home_object):
|
|
js_method_set_home_object(ctx, sp[-1], sp[-2]);
|
|
BREAK;
|
|
CASE(OP_define_method):
|
|
CASE(OP_define_method_computed):
|
|
{
|
|
JSValue getter, setter, value;
|
|
JSValue obj;
|
|
JSAtom atom;
|
|
int flags, ret, op_flags;
|
|
BOOL is_computed;
|
|
#define OP_DEFINE_METHOD_METHOD 0
|
|
#define OP_DEFINE_METHOD_GETTER 1
|
|
#define OP_DEFINE_METHOD_SETTER 2
|
|
#define OP_DEFINE_METHOD_ENUMERABLE 4
|
|
|
|
is_computed = (opcode == OP_define_method_computed);
|
|
if (is_computed) {
|
|
atom = JS_ValueToAtom(ctx, sp[-2]);
|
|
if (unlikely(atom == JS_ATOM_NULL))
|
|
goto exception;
|
|
opcode += OP_define_method - OP_define_method_computed;
|
|
} else {
|
|
atom = get_u32(pc);
|
|
pc += 4;
|
|
}
|
|
op_flags = *pc++;
|
|
|
|
obj = sp[-2 - is_computed];
|
|
flags = JS_PROP_HAS_CONFIGURABLE | JS_PROP_CONFIGURABLE |
|
|
JS_PROP_HAS_ENUMERABLE | JS_PROP_THROW;
|
|
if (op_flags & OP_DEFINE_METHOD_ENUMERABLE)
|
|
flags |= JS_PROP_ENUMERABLE;
|
|
op_flags &= 3;
|
|
value = JS_UNDEFINED;
|
|
getter = JS_UNDEFINED;
|
|
setter = JS_UNDEFINED;
|
|
if (op_flags == OP_DEFINE_METHOD_METHOD) {
|
|
value = sp[-1];
|
|
flags |= JS_PROP_HAS_VALUE | JS_PROP_HAS_WRITABLE | JS_PROP_WRITABLE;
|
|
} else if (op_flags == OP_DEFINE_METHOD_GETTER) {
|
|
getter = sp[-1];
|
|
flags |= JS_PROP_HAS_GET;
|
|
} else {
|
|
setter = sp[-1];
|
|
flags |= JS_PROP_HAS_SET;
|
|
}
|
|
ret = js_method_set_properties(ctx, sp[-1], atom, flags, obj);
|
|
if (ret >= 0) {
|
|
ret = JS_DefineProperty(ctx, obj, atom, value,
|
|
getter, setter, flags);
|
|
}
|
|
JS_FreeValue(ctx, sp[-1]);
|
|
if (is_computed) {
|
|
JS_FreeAtom(ctx, atom);
|
|
JS_FreeValue(ctx, sp[-2]);
|
|
}
|
|
sp -= 1 + is_computed;
|
|
if (unlikely(ret < 0))
|
|
goto exception;
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_define_class):
|
|
CASE(OP_define_class_computed):
|
|
{
|
|
int class_flags;
|
|
JSAtom atom;
|
|
|
|
atom = get_u32(pc);
|
|
class_flags = pc[4];
|
|
pc += 5;
|
|
if (js_op_define_class(ctx, sp, atom, class_flags,
|
|
var_refs, sf,
|
|
(opcode == OP_define_class_computed)) < 0)
|
|
goto exception;
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_get_array_el):
|
|
{
|
|
JSValue val;
|
|
|
|
sf->cur_pc = pc;
|
|
val = JS_GetPropertyValue(ctx, sp[-2], sp[-1]);
|
|
JS_FreeValue(ctx, sp[-2]);
|
|
sp[-2] = val;
|
|
sp--;
|
|
if (unlikely(JS_IsException(val)))
|
|
goto exception;
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_get_array_el2):
|
|
{
|
|
JSValue val;
|
|
|
|
sf->cur_pc = pc;
|
|
val = JS_GetPropertyValue(ctx, sp[-2], sp[-1]);
|
|
sp[-1] = val;
|
|
if (unlikely(JS_IsException(val)))
|
|
goto exception;
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_get_ref_value):
|
|
{
|
|
JSValue val;
|
|
sf->cur_pc = pc;
|
|
if (unlikely(JS_IsUndefined(sp[-2]))) {
|
|
JSAtom atom = JS_ValueToAtom(ctx, sp[-1]);
|
|
if (atom != JS_ATOM_NULL) {
|
|
JS_ThrowReferenceErrorNotDefined(ctx, atom);
|
|
JS_FreeAtom(ctx, atom);
|
|
}
|
|
goto exception;
|
|
}
|
|
val = JS_GetPropertyValue(ctx, sp[-2],
|
|
js_dup(sp[-1]));
|
|
if (unlikely(JS_IsException(val)))
|
|
goto exception;
|
|
sp[0] = val;
|
|
sp++;
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_get_super_value):
|
|
{
|
|
JSValue val;
|
|
JSAtom atom;
|
|
sf->cur_pc = pc;
|
|
atom = JS_ValueToAtom(ctx, sp[-1]);
|
|
if (unlikely(atom == JS_ATOM_NULL))
|
|
goto exception;
|
|
val = JS_GetPropertyInternal2(ctx, sp[-2], atom, sp[-3], NULL, FALSE);
|
|
JS_FreeAtom(ctx, atom);
|
|
if (unlikely(JS_IsException(val)))
|
|
goto exception;
|
|
JS_FreeValue(ctx, sp[-1]);
|
|
JS_FreeValue(ctx, sp[-2]);
|
|
JS_FreeValue(ctx, sp[-3]);
|
|
sp[-3] = val;
|
|
sp -= 2;
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_put_array_el):
|
|
{
|
|
int ret;
|
|
sf->cur_pc = pc;
|
|
ret = JS_SetPropertyValue(ctx, sp[-3], sp[-2], sp[-1], JS_PROP_THROW_STRICT);
|
|
JS_FreeValue(ctx, sp[-3]);
|
|
sp -= 3;
|
|
if (unlikely(ret < 0))
|
|
goto exception;
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_put_ref_value):
|
|
{
|
|
int ret, flags;
|
|
sf->cur_pc = pc;
|
|
flags = JS_PROP_THROW_STRICT;
|
|
if (unlikely(JS_IsUndefined(sp[-3]))) {
|
|
if (is_strict_mode(ctx)) {
|
|
JSAtom atom = JS_ValueToAtom(ctx, sp[-2]);
|
|
if (atom != JS_ATOM_NULL) {
|
|
JS_ThrowReferenceErrorNotDefined(ctx, atom);
|
|
JS_FreeAtom(ctx, atom);
|
|
}
|
|
goto exception;
|
|
} else {
|
|
sp[-3] = js_dup(ctx->global_obj);
|
|
}
|
|
} else {
|
|
if (is_strict_mode(ctx))
|
|
flags |= JS_PROP_NO_ADD;
|
|
}
|
|
ret = JS_SetPropertyValue(ctx, sp[-3], sp[-2], sp[-1], flags);
|
|
JS_FreeValue(ctx, sp[-3]);
|
|
sp -= 3;
|
|
if (unlikely(ret < 0))
|
|
goto exception;
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_put_super_value):
|
|
{
|
|
int ret;
|
|
JSAtom atom;
|
|
sf->cur_pc = pc;
|
|
if (JS_VALUE_GET_TAG(sp[-3]) != JS_TAG_OBJECT) {
|
|
JS_ThrowTypeErrorNotAnObject(ctx);
|
|
goto exception;
|
|
}
|
|
atom = JS_ValueToAtom(ctx, sp[-2]);
|
|
if (unlikely(atom == JS_ATOM_NULL))
|
|
goto exception;
|
|
ret = JS_SetPropertyInternal2(ctx,
|
|
sp[-3], atom,
|
|
sp[-1], sp[-4],
|
|
JS_PROP_THROW_STRICT, NULL);
|
|
JS_FreeAtom(ctx, atom);
|
|
JS_FreeValue(ctx, sp[-4]);
|
|
JS_FreeValue(ctx, sp[-3]);
|
|
JS_FreeValue(ctx, sp[-2]);
|
|
sp -= 4;
|
|
if (ret < 0)
|
|
goto exception;
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_define_array_el):
|
|
{
|
|
int ret;
|
|
ret = JS_DefinePropertyValueValue(ctx, sp[-3], js_dup(sp[-2]), sp[-1],
|
|
JS_PROP_C_W_E | JS_PROP_THROW);
|
|
sp -= 1;
|
|
if (unlikely(ret < 0))
|
|
goto exception;
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_append): /* array pos enumobj -- array pos */
|
|
{
|
|
sf->cur_pc = pc;
|
|
if (js_append_enumerate(ctx, sp))
|
|
goto exception;
|
|
JS_FreeValue(ctx, *--sp);
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_copy_data_properties): /* target source excludeList */
|
|
{
|
|
/* stack offsets (-1 based):
|
|
2 bits for target,
|
|
3 bits for source,
|
|
2 bits for exclusionList */
|
|
int mask;
|
|
|
|
mask = *pc++;
|
|
sf->cur_pc = pc;
|
|
if (JS_CopyDataProperties(ctx, sp[-1 - (mask & 3)],
|
|
sp[-1 - ((mask >> 2) & 7)],
|
|
sp[-1 - ((mask >> 5) & 7)], 0))
|
|
goto exception;
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_add):
|
|
{
|
|
JSValue op1, op2;
|
|
op1 = sp[-2];
|
|
op2 = sp[-1];
|
|
if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) {
|
|
int64_t r;
|
|
r = (int64_t)JS_VALUE_GET_INT(op1) + JS_VALUE_GET_INT(op2);
|
|
if (unlikely((int)r != r))
|
|
goto add_slow;
|
|
sp[-2] = js_int32(r);
|
|
sp--;
|
|
} else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2)) {
|
|
sp[-2] = js_float64(JS_VALUE_GET_FLOAT64(op1) +
|
|
JS_VALUE_GET_FLOAT64(op2));
|
|
sp--;
|
|
} else {
|
|
add_slow:
|
|
sf->cur_pc = pc;
|
|
if (js_add_slow(ctx, sp))
|
|
goto exception;
|
|
sp--;
|
|
}
|
|
}
|
|
BREAK;
|
|
CASE(OP_add_loc):
|
|
{
|
|
JSValue *pv;
|
|
int idx;
|
|
idx = *pc;
|
|
pc += 1;
|
|
|
|
pv = &var_buf[idx];
|
|
if (likely(JS_VALUE_IS_BOTH_INT(*pv, sp[-1]))) {
|
|
int64_t r;
|
|
r = (int64_t)JS_VALUE_GET_INT(*pv) +
|
|
JS_VALUE_GET_INT(sp[-1]);
|
|
if (unlikely((int)r != r))
|
|
goto add_loc_slow;
|
|
*pv = js_int32(r);
|
|
sp--;
|
|
} else if (JS_VALUE_GET_TAG(*pv) == JS_TAG_STRING) {
|
|
JSValue op1;
|
|
op1 = sp[-1];
|
|
sp--;
|
|
sf->cur_pc = pc;
|
|
op1 = JS_ToPrimitiveFree(ctx, op1, HINT_NONE);
|
|
if (JS_IsException(op1))
|
|
goto exception;
|
|
op1 = JS_ConcatString(ctx, js_dup(*pv), op1);
|
|
if (JS_IsException(op1))
|
|
goto exception;
|
|
set_value(ctx, pv, op1);
|
|
} else {
|
|
JSValue ops[2];
|
|
add_loc_slow:
|
|
/* In case of exception, js_add_slow frees ops[0]
|
|
and ops[1], so we must duplicate *pv */
|
|
sf->cur_pc = pc;
|
|
ops[0] = js_dup(*pv);
|
|
ops[1] = sp[-1];
|
|
sp--;
|
|
if (js_add_slow(ctx, ops + 2))
|
|
goto exception;
|
|
set_value(ctx, pv, ops[0]);
|
|
}
|
|
}
|
|
BREAK;
|
|
CASE(OP_sub):
|
|
{
|
|
JSValue op1, op2;
|
|
op1 = sp[-2];
|
|
op2 = sp[-1];
|
|
if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) {
|
|
int64_t r;
|
|
r = (int64_t)JS_VALUE_GET_INT(op1) - JS_VALUE_GET_INT(op2);
|
|
if (unlikely((int)r != r))
|
|
goto binary_arith_slow;
|
|
sp[-2] = js_int32(r);
|
|
sp--;
|
|
} else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2)) {
|
|
sp[-2] = js_float64(JS_VALUE_GET_FLOAT64(op1) -
|
|
JS_VALUE_GET_FLOAT64(op2));
|
|
sp--;
|
|
} else {
|
|
goto binary_arith_slow;
|
|
}
|
|
}
|
|
BREAK;
|
|
CASE(OP_mul):
|
|
{
|
|
JSValue op1, op2;
|
|
double d;
|
|
op1 = sp[-2];
|
|
op2 = sp[-1];
|
|
if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) {
|
|
int32_t v1, v2;
|
|
int64_t r;
|
|
v1 = JS_VALUE_GET_INT(op1);
|
|
v2 = JS_VALUE_GET_INT(op2);
|
|
r = (int64_t)v1 * v2;
|
|
if (unlikely((int)r != r)) {
|
|
d = (double)r;
|
|
goto mul_fp_res;
|
|
}
|
|
/* need to test zero case for -0 result */
|
|
if (unlikely(r == 0 && (v1 | v2) < 0)) {
|
|
d = -0.0;
|
|
goto mul_fp_res;
|
|
}
|
|
sp[-2] = js_int32(r);
|
|
sp--;
|
|
} else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2)) {
|
|
d = JS_VALUE_GET_FLOAT64(op1) * JS_VALUE_GET_FLOAT64(op2);
|
|
mul_fp_res:
|
|
sp[-2] = js_float64(d);
|
|
sp--;
|
|
} else {
|
|
goto binary_arith_slow;
|
|
}
|
|
}
|
|
BREAK;
|
|
CASE(OP_div):
|
|
{
|
|
JSValue op1, op2;
|
|
op1 = sp[-2];
|
|
op2 = sp[-1];
|
|
if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) {
|
|
int v1, v2;
|
|
v1 = JS_VALUE_GET_INT(op1);
|
|
v2 = JS_VALUE_GET_INT(op2);
|
|
sp[-2] = js_number((double)v1 / (double)v2);
|
|
sp--;
|
|
} else {
|
|
goto binary_arith_slow;
|
|
}
|
|
}
|
|
BREAK;
|
|
CASE(OP_mod):
|
|
{
|
|
JSValue op1, op2;
|
|
op1 = sp[-2];
|
|
op2 = sp[-1];
|
|
if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) {
|
|
int v1, v2, r;
|
|
v1 = JS_VALUE_GET_INT(op1);
|
|
v2 = JS_VALUE_GET_INT(op2);
|
|
/* We must avoid v2 = 0, v1 = INT32_MIN and v2 =
|
|
-1 and the cases where the result is -0. */
|
|
if (unlikely(v1 < 0 || v2 <= 0))
|
|
goto binary_arith_slow;
|
|
r = v1 % v2;
|
|
sp[-2] = js_int32(r);
|
|
sp--;
|
|
} else {
|
|
goto binary_arith_slow;
|
|
}
|
|
}
|
|
BREAK;
|
|
CASE(OP_pow):
|
|
binary_arith_slow:
|
|
sf->cur_pc = pc;
|
|
if (js_binary_arith_slow(ctx, sp, opcode))
|
|
goto exception;
|
|
sp--;
|
|
BREAK;
|
|
|
|
CASE(OP_plus):
|
|
{
|
|
JSValue op1;
|
|
uint32_t tag;
|
|
op1 = sp[-1];
|
|
tag = JS_VALUE_GET_TAG(op1);
|
|
if (tag == JS_TAG_INT || JS_TAG_IS_FLOAT64(tag)) {
|
|
} else {
|
|
sf->cur_pc = pc;
|
|
if (js_unary_arith_slow(ctx, sp, opcode))
|
|
goto exception;
|
|
}
|
|
}
|
|
BREAK;
|
|
CASE(OP_neg):
|
|
{
|
|
JSValue op1;
|
|
uint32_t tag;
|
|
int val;
|
|
double d;
|
|
op1 = sp[-1];
|
|
tag = JS_VALUE_GET_TAG(op1);
|
|
if (tag == JS_TAG_INT) {
|
|
val = JS_VALUE_GET_INT(op1);
|
|
/* Note: -0 cannot be expressed as integer */
|
|
if (unlikely(val == 0)) {
|
|
d = -0.0;
|
|
goto neg_fp_res;
|
|
}
|
|
if (unlikely(val == INT32_MIN)) {
|
|
d = -(double)val;
|
|
goto neg_fp_res;
|
|
}
|
|
sp[-1] = js_int32(-val);
|
|
} else if (JS_TAG_IS_FLOAT64(tag)) {
|
|
d = -JS_VALUE_GET_FLOAT64(op1);
|
|
neg_fp_res:
|
|
sp[-1] = js_float64(d);
|
|
} else {
|
|
sf->cur_pc = pc;
|
|
if (js_unary_arith_slow(ctx, sp, opcode))
|
|
goto exception;
|
|
}
|
|
}
|
|
BREAK;
|
|
CASE(OP_inc):
|
|
{
|
|
JSValue op1;
|
|
int val;
|
|
op1 = sp[-1];
|
|
if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) {
|
|
val = JS_VALUE_GET_INT(op1);
|
|
if (unlikely(val == INT32_MAX))
|
|
goto inc_slow;
|
|
sp[-1] = js_int32(val + 1);
|
|
} else {
|
|
inc_slow:
|
|
sf->cur_pc = pc;
|
|
if (js_unary_arith_slow(ctx, sp, opcode))
|
|
goto exception;
|
|
}
|
|
}
|
|
BREAK;
|
|
CASE(OP_dec):
|
|
{
|
|
JSValue op1;
|
|
int val;
|
|
op1 = sp[-1];
|
|
if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) {
|
|
val = JS_VALUE_GET_INT(op1);
|
|
if (unlikely(val == INT32_MIN))
|
|
goto dec_slow;
|
|
sp[-1] = js_int32(val - 1);
|
|
} else {
|
|
dec_slow:
|
|
sf->cur_pc = pc;
|
|
if (js_unary_arith_slow(ctx, sp, opcode))
|
|
goto exception;
|
|
}
|
|
}
|
|
BREAK;
|
|
CASE(OP_post_inc):
|
|
CASE(OP_post_dec):
|
|
sf->cur_pc = pc;
|
|
if (js_post_inc_slow(ctx, sp, opcode))
|
|
goto exception;
|
|
sp++;
|
|
BREAK;
|
|
CASE(OP_inc_loc):
|
|
{
|
|
JSValue op1;
|
|
int val;
|
|
int idx;
|
|
idx = *pc;
|
|
pc += 1;
|
|
|
|
op1 = var_buf[idx];
|
|
if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) {
|
|
val = JS_VALUE_GET_INT(op1);
|
|
if (unlikely(val == INT32_MAX))
|
|
goto inc_loc_slow;
|
|
var_buf[idx] = js_int32(val + 1);
|
|
} else {
|
|
inc_loc_slow:
|
|
sf->cur_pc = pc;
|
|
/* must duplicate otherwise the variable value may
|
|
be destroyed before JS code accesses it */
|
|
op1 = js_dup(op1);
|
|
if (js_unary_arith_slow(ctx, &op1 + 1, OP_inc))
|
|
goto exception;
|
|
set_value(ctx, &var_buf[idx], op1);
|
|
}
|
|
}
|
|
BREAK;
|
|
CASE(OP_dec_loc):
|
|
{
|
|
JSValue op1;
|
|
int val;
|
|
int idx;
|
|
idx = *pc;
|
|
pc += 1;
|
|
|
|
op1 = var_buf[idx];
|
|
if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) {
|
|
val = JS_VALUE_GET_INT(op1);
|
|
if (unlikely(val == INT32_MIN))
|
|
goto dec_loc_slow;
|
|
var_buf[idx] = js_int32(val - 1);
|
|
} else {
|
|
dec_loc_slow:
|
|
sf->cur_pc = pc;
|
|
/* must duplicate otherwise the variable value may
|
|
be destroyed before JS code accesses it */
|
|
op1 = js_dup(op1);
|
|
if (js_unary_arith_slow(ctx, &op1 + 1, OP_dec))
|
|
goto exception;
|
|
set_value(ctx, &var_buf[idx], op1);
|
|
}
|
|
}
|
|
BREAK;
|
|
CASE(OP_not):
|
|
{
|
|
JSValue op1;
|
|
op1 = sp[-1];
|
|
if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) {
|
|
sp[-1] = js_int32(~JS_VALUE_GET_INT(op1));
|
|
} else {
|
|
sf->cur_pc = pc;
|
|
if (js_not_slow(ctx, sp))
|
|
goto exception;
|
|
}
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_shl):
|
|
{
|
|
JSValue op1, op2;
|
|
op1 = sp[-2];
|
|
op2 = sp[-1];
|
|
if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) {
|
|
uint32_t v1, v2;
|
|
v1 = JS_VALUE_GET_INT(op1);
|
|
v2 = JS_VALUE_GET_INT(op2) & 0x1f;
|
|
sp[-2] = js_int32(v1 << v2);
|
|
sp--;
|
|
} else {
|
|
sf->cur_pc = pc;
|
|
if (js_binary_logic_slow(ctx, sp, opcode))
|
|
goto exception;
|
|
sp--;
|
|
}
|
|
}
|
|
BREAK;
|
|
CASE(OP_shr):
|
|
{
|
|
JSValue op1, op2;
|
|
op1 = sp[-2];
|
|
op2 = sp[-1];
|
|
if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) {
|
|
uint32_t v2;
|
|
v2 = JS_VALUE_GET_INT(op2);
|
|
v2 &= 0x1f;
|
|
sp[-2] = js_uint32((uint32_t)JS_VALUE_GET_INT(op1) >> v2);
|
|
sp--;
|
|
} else {
|
|
sf->cur_pc = pc;
|
|
if (js_shr_slow(ctx, sp))
|
|
goto exception;
|
|
sp--;
|
|
}
|
|
}
|
|
BREAK;
|
|
CASE(OP_sar):
|
|
{
|
|
JSValue op1, op2;
|
|
op1 = sp[-2];
|
|
op2 = sp[-1];
|
|
if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) {
|
|
uint32_t v2;
|
|
v2 = JS_VALUE_GET_INT(op2);
|
|
if (unlikely(v2 > 0x1f)) {
|
|
v2 &= 0x1f;
|
|
}
|
|
sp[-2] = js_int32((int)JS_VALUE_GET_INT(op1) >> v2);
|
|
sp--;
|
|
} else {
|
|
sf->cur_pc = pc;
|
|
if (js_binary_logic_slow(ctx, sp, opcode))
|
|
goto exception;
|
|
sp--;
|
|
}
|
|
}
|
|
BREAK;
|
|
CASE(OP_and):
|
|
{
|
|
JSValue op1, op2;
|
|
op1 = sp[-2];
|
|
op2 = sp[-1];
|
|
if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) {
|
|
sp[-2] = js_int32(JS_VALUE_GET_INT(op1) & JS_VALUE_GET_INT(op2));
|
|
sp--;
|
|
} else {
|
|
sf->cur_pc = pc;
|
|
if (js_binary_logic_slow(ctx, sp, opcode))
|
|
goto exception;
|
|
sp--;
|
|
}
|
|
}
|
|
BREAK;
|
|
CASE(OP_or):
|
|
{
|
|
JSValue op1, op2;
|
|
op1 = sp[-2];
|
|
op2 = sp[-1];
|
|
if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) {
|
|
sp[-2] = js_int32(JS_VALUE_GET_INT(op1) | JS_VALUE_GET_INT(op2));
|
|
sp--;
|
|
} else {
|
|
sf->cur_pc = pc;
|
|
if (js_binary_logic_slow(ctx, sp, opcode))
|
|
goto exception;
|
|
sp--;
|
|
}
|
|
}
|
|
BREAK;
|
|
CASE(OP_xor):
|
|
{
|
|
JSValue op1, op2;
|
|
op1 = sp[-2];
|
|
op2 = sp[-1];
|
|
if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) {
|
|
sp[-2] = js_int32(JS_VALUE_GET_INT(op1) ^ JS_VALUE_GET_INT(op2));
|
|
sp--;
|
|
} else {
|
|
sf->cur_pc = pc;
|
|
if (js_binary_logic_slow(ctx, sp, opcode))
|
|
goto exception;
|
|
sp--;
|
|
}
|
|
}
|
|
BREAK;
|
|
|
|
|
|
#define OP_CMP(opcode, binary_op, slow_call) \
|
|
CASE(opcode): \
|
|
{ \
|
|
JSValue op1, op2; \
|
|
op1 = sp[-2]; \
|
|
op2 = sp[-1]; \
|
|
if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { \
|
|
sp[-2] = js_bool(JS_VALUE_GET_INT(op1) binary_op JS_VALUE_GET_INT(op2)); \
|
|
sp--; \
|
|
} else { \
|
|
sf->cur_pc = pc; \
|
|
if (slow_call) \
|
|
goto exception; \
|
|
sp--; \
|
|
} \
|
|
} \
|
|
BREAK
|
|
|
|
OP_CMP(OP_lt, <, js_relational_slow(ctx, sp, opcode));
|
|
OP_CMP(OP_lte, <=, js_relational_slow(ctx, sp, opcode));
|
|
OP_CMP(OP_gt, >, js_relational_slow(ctx, sp, opcode));
|
|
OP_CMP(OP_gte, >=, js_relational_slow(ctx, sp, opcode));
|
|
OP_CMP(OP_eq, ==, js_eq_slow(ctx, sp, 0));
|
|
OP_CMP(OP_neq, !=, js_eq_slow(ctx, sp, 1));
|
|
OP_CMP(OP_strict_eq, ==, js_strict_eq_slow(ctx, sp, 0));
|
|
OP_CMP(OP_strict_neq, !=, js_strict_eq_slow(ctx, sp, 1));
|
|
|
|
CASE(OP_in):
|
|
sf->cur_pc = pc;
|
|
if (js_operator_in(ctx, sp))
|
|
goto exception;
|
|
sp--;
|
|
BREAK;
|
|
CASE(OP_instanceof):
|
|
sf->cur_pc = pc;
|
|
if (js_operator_instanceof(ctx, sp))
|
|
goto exception;
|
|
sp--;
|
|
BREAK;
|
|
CASE(OP_typeof):
|
|
{
|
|
JSValue op1;
|
|
JSAtom atom;
|
|
|
|
op1 = sp[-1];
|
|
atom = js_operator_typeof(ctx, op1);
|
|
JS_FreeValue(ctx, op1);
|
|
sp[-1] = JS_AtomToString(ctx, atom);
|
|
}
|
|
BREAK;
|
|
CASE(OP_delete):
|
|
if (js_operator_delete(ctx, sp))
|
|
goto exception;
|
|
sp--;
|
|
BREAK;
|
|
CASE(OP_delete_var):
|
|
{
|
|
JSAtom atom;
|
|
int ret;
|
|
|
|
atom = get_u32(pc);
|
|
pc += 4;
|
|
|
|
sf->cur_pc = pc;
|
|
ret = JS_DeleteProperty(ctx, ctx->global_obj, atom, 0);
|
|
if (unlikely(ret < 0))
|
|
goto exception;
|
|
*sp++ = js_bool(ret);
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_to_object):
|
|
if (JS_VALUE_GET_TAG(sp[-1]) != JS_TAG_OBJECT) {
|
|
sf->cur_pc = pc;
|
|
ret_val = JS_ToObject(ctx, sp[-1]);
|
|
if (JS_IsException(ret_val))
|
|
goto exception;
|
|
JS_FreeValue(ctx, sp[-1]);
|
|
sp[-1] = ret_val;
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_to_propkey):
|
|
switch (JS_VALUE_GET_TAG(sp[-1])) {
|
|
case JS_TAG_INT:
|
|
case JS_TAG_STRING:
|
|
case JS_TAG_SYMBOL:
|
|
break;
|
|
default:
|
|
sf->cur_pc = pc;
|
|
ret_val = JS_ToPropertyKey(ctx, sp[-1]);
|
|
if (JS_IsException(ret_val))
|
|
goto exception;
|
|
JS_FreeValue(ctx, sp[-1]);
|
|
sp[-1] = ret_val;
|
|
break;
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_to_propkey2):
|
|
/* must be tested first */
|
|
if (unlikely(JS_IsUndefined(sp[-2]) || JS_IsNull(sp[-2]))) {
|
|
JS_ThrowTypeError(ctx, "value has no property");
|
|
goto exception;
|
|
}
|
|
switch (JS_VALUE_GET_TAG(sp[-1])) {
|
|
case JS_TAG_INT:
|
|
case JS_TAG_STRING:
|
|
case JS_TAG_SYMBOL:
|
|
break;
|
|
default:
|
|
sf->cur_pc = pc;
|
|
ret_val = JS_ToPropertyKey(ctx, sp[-1]);
|
|
if (JS_IsException(ret_val))
|
|
goto exception;
|
|
JS_FreeValue(ctx, sp[-1]);
|
|
sp[-1] = ret_val;
|
|
break;
|
|
}
|
|
BREAK;
|
|
CASE(OP_with_get_var):
|
|
CASE(OP_with_put_var):
|
|
CASE(OP_with_delete_var):
|
|
CASE(OP_with_make_ref):
|
|
CASE(OP_with_get_ref):
|
|
CASE(OP_with_get_ref_undef):
|
|
{
|
|
JSAtom atom;
|
|
int32_t diff;
|
|
JSValue obj, val;
|
|
int ret, is_with;
|
|
atom = get_u32(pc);
|
|
diff = get_u32(pc + 4);
|
|
is_with = pc[8];
|
|
pc += 9;
|
|
sf->cur_pc = pc;
|
|
|
|
obj = sp[-1];
|
|
ret = JS_HasProperty(ctx, obj, atom);
|
|
if (unlikely(ret < 0))
|
|
goto exception;
|
|
if (ret) {
|
|
if (is_with) {
|
|
ret = js_has_unscopable(ctx, obj, atom);
|
|
if (unlikely(ret < 0))
|
|
goto exception;
|
|
if (ret)
|
|
goto no_with;
|
|
}
|
|
switch (opcode) {
|
|
case OP_with_get_var:
|
|
val = JS_GetProperty(ctx, obj, atom);
|
|
if (unlikely(JS_IsException(val)))
|
|
goto exception;
|
|
set_value(ctx, &sp[-1], val);
|
|
break;
|
|
case OP_with_put_var:
|
|
/* XXX: check if strict mode */
|
|
ret = JS_SetPropertyInternal(ctx, obj, atom, sp[-2],
|
|
JS_PROP_THROW_STRICT);
|
|
JS_FreeValue(ctx, sp[-1]);
|
|
sp -= 2;
|
|
if (unlikely(ret < 0))
|
|
goto exception;
|
|
break;
|
|
case OP_with_delete_var:
|
|
ret = JS_DeleteProperty(ctx, obj, atom, 0);
|
|
if (unlikely(ret < 0))
|
|
goto exception;
|
|
JS_FreeValue(ctx, sp[-1]);
|
|
sp[-1] = js_bool(ret);
|
|
break;
|
|
case OP_with_make_ref:
|
|
/* produce a pair object/propname on the stack */
|
|
*sp++ = JS_AtomToValue(ctx, atom);
|
|
break;
|
|
case OP_with_get_ref:
|
|
/* produce a pair object/method on the stack */
|
|
val = JS_GetProperty(ctx, obj, atom);
|
|
if (unlikely(JS_IsException(val)))
|
|
goto exception;
|
|
*sp++ = val;
|
|
break;
|
|
case OP_with_get_ref_undef:
|
|
/* produce a pair undefined/function on the stack */
|
|
val = JS_GetProperty(ctx, obj, atom);
|
|
if (unlikely(JS_IsException(val)))
|
|
goto exception;
|
|
JS_FreeValue(ctx, sp[-1]);
|
|
sp[-1] = JS_UNDEFINED;
|
|
*sp++ = val;
|
|
break;
|
|
}
|
|
pc += diff - 5;
|
|
} else {
|
|
no_with:
|
|
/* if not jumping, drop the object argument */
|
|
JS_FreeValue(ctx, sp[-1]);
|
|
sp--;
|
|
}
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_await):
|
|
ret_val = js_int32(FUNC_RET_AWAIT);
|
|
goto done_generator;
|
|
CASE(OP_yield):
|
|
ret_val = js_int32(FUNC_RET_YIELD);
|
|
goto done_generator;
|
|
CASE(OP_yield_star):
|
|
CASE(OP_async_yield_star):
|
|
ret_val = js_int32(FUNC_RET_YIELD_STAR);
|
|
goto done_generator;
|
|
CASE(OP_return_async):
|
|
CASE(OP_initial_yield):
|
|
ret_val = JS_UNDEFINED;
|
|
goto done_generator;
|
|
|
|
CASE(OP_nop):
|
|
BREAK;
|
|
CASE(OP_is_undefined_or_null):
|
|
if (JS_VALUE_GET_TAG(sp[-1]) == JS_TAG_UNDEFINED ||
|
|
JS_VALUE_GET_TAG(sp[-1]) == JS_TAG_NULL) {
|
|
goto set_true;
|
|
} else {
|
|
goto free_and_set_false;
|
|
}
|
|
CASE(OP_is_undefined):
|
|
if (JS_VALUE_GET_TAG(sp[-1]) == JS_TAG_UNDEFINED) {
|
|
goto set_true;
|
|
} else {
|
|
goto free_and_set_false;
|
|
}
|
|
CASE(OP_is_null):
|
|
if (JS_VALUE_GET_TAG(sp[-1]) == JS_TAG_NULL) {
|
|
goto set_true;
|
|
} else {
|
|
goto free_and_set_false;
|
|
}
|
|
/* XXX: could merge to a single opcode */
|
|
CASE(OP_typeof_is_undefined):
|
|
/* different from OP_is_undefined because of isHTMLDDA */
|
|
if (js_operator_typeof(ctx, sp[-1]) == JS_ATOM_undefined) {
|
|
goto free_and_set_true;
|
|
} else {
|
|
goto free_and_set_false;
|
|
}
|
|
CASE(OP_typeof_is_function):
|
|
if (js_operator_typeof(ctx, sp[-1]) == JS_ATOM_function) {
|
|
goto free_and_set_true;
|
|
} else {
|
|
goto free_and_set_false;
|
|
}
|
|
free_and_set_true:
|
|
JS_FreeValue(ctx, sp[-1]);
|
|
set_true:
|
|
sp[-1] = JS_TRUE;
|
|
BREAK;
|
|
free_and_set_false:
|
|
JS_FreeValue(ctx, sp[-1]);
|
|
sp[-1] = JS_FALSE;
|
|
BREAK;
|
|
CASE(OP_invalid):
|
|
DEFAULT:
|
|
JS_ThrowInternalError(ctx, "invalid opcode: pc=%u opcode=0x%02x",
|
|
(int)(pc - b->byte_code_buf - 1), opcode);
|
|
goto exception;
|
|
}
|
|
}
|
|
exception:
|
|
if (is_backtrace_needed(ctx, rt->current_exception)) {
|
|
/* add the backtrace information now (it is not done
|
|
before if the exception happens in a bytecode
|
|
operation */
|
|
sf->cur_pc = pc;
|
|
build_backtrace(ctx, rt->current_exception, NULL, 0, 0, 0);
|
|
}
|
|
if (!JS_IsUncatchableError(ctx, rt->current_exception)) {
|
|
while (sp > stack_buf) {
|
|
JSValue val = *--sp;
|
|
JS_FreeValue(ctx, val);
|
|
if (JS_VALUE_GET_TAG(val) == JS_TAG_CATCH_OFFSET) {
|
|
int pos = JS_VALUE_GET_INT(val);
|
|
if (pos == 0) {
|
|
/* enumerator: close it with a throw */
|
|
JS_FreeValue(ctx, sp[-1]); /* drop the next method */
|
|
sp--;
|
|
JS_IteratorClose(ctx, sp[-1], TRUE);
|
|
} else {
|
|
*sp++ = rt->current_exception;
|
|
rt->current_exception = JS_NULL;
|
|
pc = b->byte_code_buf + pos;
|
|
goto restart;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
ret_val = JS_EXCEPTION;
|
|
/* the local variables are freed by the caller in the generator
|
|
case. Hence the label 'done' should never be reached in a
|
|
generator function. */
|
|
if (b->func_kind != JS_FUNC_NORMAL) {
|
|
done_generator:
|
|
sf->cur_pc = pc;
|
|
sf->cur_sp = sp;
|
|
} else {
|
|
done:
|
|
if (unlikely(!list_empty(&sf->var_ref_list))) {
|
|
/* variable references reference the stack: must close them */
|
|
close_var_refs(rt, sf);
|
|
}
|
|
/* free the local variables and stack */
|
|
for(pval = local_buf; pval < sp; pval++) {
|
|
JS_FreeValue(ctx, *pval);
|
|
}
|
|
}
|
|
rt->current_stack_frame = sf->prev_frame;
|
|
return ret_val;
|
|
}
|
|
|
|
JSValue JS_Call(JSContext *ctx, JSValue func_obj, JSValue this_obj,
|
|
int argc, JSValue *argv)
|
|
{
|
|
return JS_CallInternal(ctx, func_obj, this_obj, JS_UNDEFINED,
|
|
argc, argv, JS_CALL_FLAG_COPY_ARGV);
|
|
}
|
|
|
|
static JSValue JS_CallFree(JSContext *ctx, JSValue func_obj, JSValue this_obj,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue res = JS_CallInternal(ctx, func_obj, this_obj, JS_UNDEFINED,
|
|
argc, argv, JS_CALL_FLAG_COPY_ARGV);
|
|
JS_FreeValue(ctx, func_obj);
|
|
return res;
|
|
}
|
|
|
|
/* warning: the refcount of the context is not incremented. Return
|
|
NULL in case of exception (case of revoked proxy only) */
|
|
static JSContext *JS_GetFunctionRealm(JSContext *ctx, JSValue func_obj)
|
|
{
|
|
JSObject *p;
|
|
JSContext *realm;
|
|
|
|
if (JS_VALUE_GET_TAG(func_obj) != JS_TAG_OBJECT)
|
|
return ctx;
|
|
p = JS_VALUE_GET_OBJ(func_obj);
|
|
switch(p->class_id) {
|
|
case JS_CLASS_C_FUNCTION:
|
|
realm = p->u.cfunc.realm;
|
|
break;
|
|
case JS_CLASS_BYTECODE_FUNCTION:
|
|
case JS_CLASS_GENERATOR_FUNCTION:
|
|
case JS_CLASS_ASYNC_FUNCTION:
|
|
case JS_CLASS_ASYNC_GENERATOR_FUNCTION:
|
|
{
|
|
JSFunctionBytecode *b;
|
|
b = p->u.func.function_bytecode;
|
|
realm = b->realm;
|
|
}
|
|
break;
|
|
case JS_CLASS_PROXY:
|
|
{
|
|
JSProxyData *s = p->u.opaque;
|
|
if (!s)
|
|
return ctx;
|
|
if (s->is_revoked) {
|
|
JS_ThrowTypeErrorRevokedProxy(ctx);
|
|
return NULL;
|
|
} else {
|
|
realm = JS_GetFunctionRealm(ctx, s->target);
|
|
}
|
|
}
|
|
break;
|
|
case JS_CLASS_BOUND_FUNCTION:
|
|
{
|
|
JSBoundFunction *bf = p->u.bound_function;
|
|
realm = JS_GetFunctionRealm(ctx, bf->func_obj);
|
|
}
|
|
break;
|
|
default:
|
|
realm = ctx;
|
|
break;
|
|
}
|
|
return realm;
|
|
}
|
|
|
|
static JSValue js_create_from_ctor(JSContext *ctx, JSValue ctor,
|
|
int class_id)
|
|
{
|
|
JSValue proto, obj;
|
|
JSContext *realm;
|
|
|
|
if (JS_IsUndefined(ctor)) {
|
|
proto = js_dup(ctx->class_proto[class_id]);
|
|
} else {
|
|
proto = JS_GetProperty(ctx, ctor, JS_ATOM_prototype);
|
|
if (JS_IsException(proto))
|
|
return proto;
|
|
if (!JS_IsObject(proto)) {
|
|
JS_FreeValue(ctx, proto);
|
|
realm = JS_GetFunctionRealm(ctx, ctor);
|
|
if (!realm)
|
|
return JS_EXCEPTION;
|
|
proto = js_dup(realm->class_proto[class_id]);
|
|
}
|
|
}
|
|
obj = JS_NewObjectProtoClass(ctx, proto, class_id);
|
|
JS_FreeValue(ctx, proto);
|
|
return obj;
|
|
}
|
|
|
|
/* argv[] is modified if (flags & JS_CALL_FLAG_COPY_ARGV) = 0. */
|
|
static JSValue JS_CallConstructorInternal(JSContext *ctx,
|
|
JSValue func_obj,
|
|
JSValue new_target,
|
|
int argc, JSValue *argv, int flags)
|
|
{
|
|
JSObject *p;
|
|
JSFunctionBytecode *b;
|
|
|
|
if (js_poll_interrupts(ctx))
|
|
return JS_EXCEPTION;
|
|
flags |= JS_CALL_FLAG_CONSTRUCTOR;
|
|
if (unlikely(JS_VALUE_GET_TAG(func_obj) != JS_TAG_OBJECT))
|
|
goto not_a_function;
|
|
p = JS_VALUE_GET_OBJ(func_obj);
|
|
if (unlikely(!p->is_constructor))
|
|
return JS_ThrowTypeError(ctx, "not a constructor");
|
|
if (unlikely(p->class_id != JS_CLASS_BYTECODE_FUNCTION)) {
|
|
JSClassCall *call_func;
|
|
call_func = ctx->rt->class_array[p->class_id].call;
|
|
if (!call_func) {
|
|
not_a_function:
|
|
return JS_ThrowTypeError(ctx, "not a function");
|
|
}
|
|
return call_func(ctx, func_obj, new_target, argc,
|
|
argv, flags);
|
|
}
|
|
|
|
b = p->u.func.function_bytecode;
|
|
if (b->is_derived_class_constructor) {
|
|
return JS_CallInternal(ctx, func_obj, JS_UNDEFINED, new_target, argc, argv, flags);
|
|
} else {
|
|
JSValue obj, ret;
|
|
/* legacy constructor behavior */
|
|
obj = js_create_from_ctor(ctx, new_target, JS_CLASS_OBJECT);
|
|
if (JS_IsException(obj))
|
|
return JS_EXCEPTION;
|
|
ret = JS_CallInternal(ctx, func_obj, obj, new_target, argc, argv, flags);
|
|
if (JS_VALUE_GET_TAG(ret) == JS_TAG_OBJECT ||
|
|
JS_IsException(ret)) {
|
|
JS_FreeValue(ctx, obj);
|
|
return ret;
|
|
} else {
|
|
JS_FreeValue(ctx, ret);
|
|
return obj;
|
|
}
|
|
}
|
|
}
|
|
|
|
JSValue JS_CallConstructor2(JSContext *ctx, JSValue func_obj,
|
|
JSValue new_target,
|
|
int argc, JSValue *argv)
|
|
{
|
|
return JS_CallConstructorInternal(ctx, func_obj, new_target,
|
|
argc, argv,
|
|
JS_CALL_FLAG_COPY_ARGV);
|
|
}
|
|
|
|
JSValue JS_CallConstructor(JSContext *ctx, JSValue func_obj,
|
|
int argc, JSValue *argv)
|
|
{
|
|
return JS_CallConstructorInternal(ctx, func_obj, func_obj,
|
|
argc, argv,
|
|
JS_CALL_FLAG_COPY_ARGV);
|
|
}
|
|
|
|
JSValue JS_Invoke(JSContext *ctx, JSValue this_val, JSAtom atom,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue func_obj;
|
|
func_obj = JS_GetProperty(ctx, this_val, atom);
|
|
if (JS_IsException(func_obj))
|
|
return func_obj;
|
|
return JS_CallFree(ctx, func_obj, this_val, argc, argv);
|
|
}
|
|
|
|
static JSValue JS_InvokeFree(JSContext *ctx, JSValue this_val, JSAtom atom,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue res = JS_Invoke(ctx, this_val, atom, argc, argv);
|
|
JS_FreeValue(ctx, this_val);
|
|
return res;
|
|
}
|
|
|
|
/* JSAsyncFunctionState (used by generator and async functions) */
|
|
static __exception int async_func_init(JSContext *ctx, JSAsyncFunctionState *s,
|
|
JSValue func_obj, JSValue this_obj,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSObject *p;
|
|
JSFunctionBytecode *b;
|
|
JSStackFrame *sf;
|
|
int local_count, i, arg_buf_len, n;
|
|
|
|
sf = &s->frame;
|
|
init_list_head(&sf->var_ref_list);
|
|
p = JS_VALUE_GET_OBJ(func_obj);
|
|
b = p->u.func.function_bytecode;
|
|
sf->js_mode = b->js_mode;
|
|
sf->cur_pc = b->byte_code_buf;
|
|
arg_buf_len = max_int(b->arg_count, argc);
|
|
local_count = arg_buf_len + b->var_count + b->stack_size;
|
|
sf->arg_buf = js_malloc(ctx, sizeof(JSValue) * max_int(local_count, 1));
|
|
if (!sf->arg_buf)
|
|
return -1;
|
|
sf->cur_func = js_dup(func_obj);
|
|
s->this_val = js_dup(this_obj);
|
|
s->argc = argc;
|
|
sf->arg_count = arg_buf_len;
|
|
sf->var_buf = sf->arg_buf + arg_buf_len;
|
|
sf->cur_sp = sf->var_buf + b->var_count;
|
|
for(i = 0; i < argc; i++)
|
|
sf->arg_buf[i] = js_dup(argv[i]);
|
|
n = arg_buf_len + b->var_count;
|
|
for(i = argc; i < n; i++)
|
|
sf->arg_buf[i] = JS_UNDEFINED;
|
|
return 0;
|
|
}
|
|
|
|
static void async_func_mark(JSRuntime *rt, JSAsyncFunctionState *s,
|
|
JS_MarkFunc *mark_func)
|
|
{
|
|
JSStackFrame *sf;
|
|
JSValue *sp;
|
|
|
|
sf = &s->frame;
|
|
JS_MarkValue(rt, sf->cur_func, mark_func);
|
|
JS_MarkValue(rt, s->this_val, mark_func);
|
|
if (sf->cur_sp) {
|
|
/* if the function is running, cur_sp is not known so we
|
|
cannot mark the stack. Marking the variables is not needed
|
|
because a running function cannot be part of a removable
|
|
cycle */
|
|
for(sp = sf->arg_buf; sp < sf->cur_sp; sp++)
|
|
JS_MarkValue(rt, *sp, mark_func);
|
|
}
|
|
}
|
|
|
|
static void async_func_free(JSRuntime *rt, JSAsyncFunctionState *s)
|
|
{
|
|
JSStackFrame *sf;
|
|
JSValue *sp;
|
|
|
|
sf = &s->frame;
|
|
|
|
/* close the closure variables. */
|
|
close_var_refs(rt, sf);
|
|
|
|
if (sf->arg_buf) {
|
|
/* cannot free the function if it is running */
|
|
assert(sf->cur_sp != NULL);
|
|
for(sp = sf->arg_buf; sp < sf->cur_sp; sp++) {
|
|
JS_FreeValueRT(rt, *sp);
|
|
}
|
|
js_free_rt(rt, sf->arg_buf);
|
|
}
|
|
JS_FreeValueRT(rt, sf->cur_func);
|
|
JS_FreeValueRT(rt, s->this_val);
|
|
}
|
|
|
|
static JSValue async_func_resume(JSContext *ctx, JSAsyncFunctionState *s)
|
|
{
|
|
JSValue func_obj;
|
|
|
|
if (js_check_stack_overflow(ctx->rt, 0))
|
|
return JS_ThrowStackOverflow(ctx);
|
|
|
|
/* the tag does not matter provided it is not an object */
|
|
func_obj = JS_MKPTR(JS_TAG_INT, s);
|
|
return JS_CallInternal(ctx, func_obj, s->this_val, JS_UNDEFINED,
|
|
s->argc, s->frame.arg_buf, JS_CALL_FLAG_GENERATOR);
|
|
}
|
|
|
|
|
|
/* Generators */
|
|
|
|
typedef enum JSGeneratorStateEnum {
|
|
JS_GENERATOR_STATE_SUSPENDED_START,
|
|
JS_GENERATOR_STATE_SUSPENDED_YIELD,
|
|
JS_GENERATOR_STATE_SUSPENDED_YIELD_STAR,
|
|
JS_GENERATOR_STATE_EXECUTING,
|
|
JS_GENERATOR_STATE_COMPLETED,
|
|
} JSGeneratorStateEnum;
|
|
|
|
typedef struct JSGeneratorData {
|
|
JSGeneratorStateEnum state;
|
|
JSAsyncFunctionState func_state;
|
|
} JSGeneratorData;
|
|
|
|
static void free_generator_stack_rt(JSRuntime *rt, JSGeneratorData *s)
|
|
{
|
|
if (s->state == JS_GENERATOR_STATE_COMPLETED)
|
|
return;
|
|
async_func_free(rt, &s->func_state);
|
|
s->state = JS_GENERATOR_STATE_COMPLETED;
|
|
}
|
|
|
|
static void js_generator_finalizer(JSRuntime *rt, JSValue obj)
|
|
{
|
|
JSGeneratorData *s = JS_GetOpaque(obj, JS_CLASS_GENERATOR);
|
|
|
|
if (s) {
|
|
free_generator_stack_rt(rt, s);
|
|
js_free_rt(rt, s);
|
|
}
|
|
}
|
|
|
|
static void free_generator_stack(JSContext *ctx, JSGeneratorData *s)
|
|
{
|
|
free_generator_stack_rt(ctx->rt, s);
|
|
}
|
|
|
|
static void js_generator_mark(JSRuntime *rt, JSValue val,
|
|
JS_MarkFunc *mark_func)
|
|
{
|
|
JSObject *p = JS_VALUE_GET_OBJ(val);
|
|
JSGeneratorData *s = p->u.generator_data;
|
|
|
|
if (!s || s->state == JS_GENERATOR_STATE_COMPLETED)
|
|
return;
|
|
async_func_mark(rt, &s->func_state, mark_func);
|
|
}
|
|
|
|
/* XXX: use enum */
|
|
#define GEN_MAGIC_NEXT 0
|
|
#define GEN_MAGIC_RETURN 1
|
|
#define GEN_MAGIC_THROW 2
|
|
|
|
static JSValue js_generator_next(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv,
|
|
BOOL *pdone, int magic)
|
|
{
|
|
JSGeneratorData *s = JS_GetOpaque(this_val, JS_CLASS_GENERATOR);
|
|
JSStackFrame *sf;
|
|
JSValue ret, func_ret;
|
|
|
|
*pdone = TRUE;
|
|
if (!s)
|
|
return JS_ThrowTypeError(ctx, "not a generator");
|
|
sf = &s->func_state.frame;
|
|
switch(s->state) {
|
|
default:
|
|
case JS_GENERATOR_STATE_SUSPENDED_START:
|
|
if (magic == GEN_MAGIC_NEXT) {
|
|
goto exec_no_arg;
|
|
} else {
|
|
free_generator_stack(ctx, s);
|
|
goto done;
|
|
}
|
|
break;
|
|
case JS_GENERATOR_STATE_SUSPENDED_YIELD_STAR:
|
|
case JS_GENERATOR_STATE_SUSPENDED_YIELD:
|
|
/* cur_sp[-1] was set to JS_UNDEFINED in the previous call */
|
|
ret = js_dup(argv[0]);
|
|
if (magic == GEN_MAGIC_THROW &&
|
|
s->state == JS_GENERATOR_STATE_SUSPENDED_YIELD) {
|
|
JS_Throw(ctx, ret);
|
|
s->func_state.throw_flag = TRUE;
|
|
} else {
|
|
sf->cur_sp[-1] = ret;
|
|
sf->cur_sp[0] = js_int32(magic);
|
|
sf->cur_sp++;
|
|
exec_no_arg:
|
|
s->func_state.throw_flag = FALSE;
|
|
}
|
|
s->state = JS_GENERATOR_STATE_EXECUTING;
|
|
func_ret = async_func_resume(ctx, &s->func_state);
|
|
s->state = JS_GENERATOR_STATE_SUSPENDED_YIELD;
|
|
if (JS_IsException(func_ret)) {
|
|
/* finalize the execution in case of exception */
|
|
free_generator_stack(ctx, s);
|
|
return func_ret;
|
|
}
|
|
if (JS_VALUE_GET_TAG(func_ret) == JS_TAG_INT) {
|
|
/* get the returned yield value at the top of the stack */
|
|
ret = sf->cur_sp[-1];
|
|
sf->cur_sp[-1] = JS_UNDEFINED;
|
|
if (JS_VALUE_GET_INT(func_ret) == FUNC_RET_YIELD_STAR) {
|
|
s->state = JS_GENERATOR_STATE_SUSPENDED_YIELD_STAR;
|
|
/* return (value, done) object */
|
|
*pdone = 2;
|
|
} else {
|
|
*pdone = FALSE;
|
|
}
|
|
} else {
|
|
/* end of iterator */
|
|
ret = sf->cur_sp[-1];
|
|
sf->cur_sp[-1] = JS_UNDEFINED;
|
|
JS_FreeValue(ctx, func_ret);
|
|
free_generator_stack(ctx, s);
|
|
}
|
|
break;
|
|
case JS_GENERATOR_STATE_COMPLETED:
|
|
done:
|
|
/* execution is finished */
|
|
switch(magic) {
|
|
default:
|
|
case GEN_MAGIC_NEXT:
|
|
ret = JS_UNDEFINED;
|
|
break;
|
|
case GEN_MAGIC_RETURN:
|
|
ret = js_dup(argv[0]);
|
|
break;
|
|
case GEN_MAGIC_THROW:
|
|
ret = JS_Throw(ctx, js_dup(argv[0]));
|
|
break;
|
|
}
|
|
break;
|
|
case JS_GENERATOR_STATE_EXECUTING:
|
|
ret = JS_ThrowTypeError(ctx, "cannot invoke a running generator");
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static JSValue js_generator_function_call(JSContext *ctx, JSValue func_obj,
|
|
JSValue this_obj,
|
|
int argc, JSValue *argv,
|
|
int flags)
|
|
{
|
|
JSValue obj, func_ret;
|
|
JSGeneratorData *s;
|
|
|
|
s = js_mallocz(ctx, sizeof(*s));
|
|
if (!s)
|
|
return JS_EXCEPTION;
|
|
s->state = JS_GENERATOR_STATE_SUSPENDED_START;
|
|
if (async_func_init(ctx, &s->func_state, func_obj, this_obj, argc, argv)) {
|
|
s->state = JS_GENERATOR_STATE_COMPLETED;
|
|
goto fail;
|
|
}
|
|
|
|
/* execute the function up to 'OP_initial_yield' */
|
|
func_ret = async_func_resume(ctx, &s->func_state);
|
|
if (JS_IsException(func_ret))
|
|
goto fail;
|
|
JS_FreeValue(ctx, func_ret);
|
|
|
|
obj = js_create_from_ctor(ctx, func_obj, JS_CLASS_GENERATOR);
|
|
if (JS_IsException(obj))
|
|
goto fail;
|
|
JS_SetOpaque(obj, s);
|
|
return obj;
|
|
fail:
|
|
free_generator_stack_rt(ctx->rt, s);
|
|
js_free(ctx, s);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
/* AsyncFunction */
|
|
|
|
static void js_async_function_terminate(JSRuntime *rt, JSAsyncFunctionData *s)
|
|
{
|
|
if (s->is_active) {
|
|
async_func_free(rt, &s->func_state);
|
|
s->is_active = FALSE;
|
|
}
|
|
}
|
|
|
|
static void js_async_function_free0(JSRuntime *rt, JSAsyncFunctionData *s)
|
|
{
|
|
js_async_function_terminate(rt, s);
|
|
JS_FreeValueRT(rt, s->resolving_funcs[0]);
|
|
JS_FreeValueRT(rt, s->resolving_funcs[1]);
|
|
remove_gc_object(&s->header);
|
|
js_free_rt(rt, s);
|
|
}
|
|
|
|
static void js_async_function_free(JSRuntime *rt, JSAsyncFunctionData *s)
|
|
{
|
|
if (--s->header.ref_count == 0) {
|
|
js_async_function_free0(rt, s);
|
|
}
|
|
}
|
|
|
|
static void js_async_function_resolve_finalizer(JSRuntime *rt, JSValue val)
|
|
{
|
|
JSObject *p = JS_VALUE_GET_OBJ(val);
|
|
JSAsyncFunctionData *s = p->u.async_function_data;
|
|
if (s) {
|
|
js_async_function_free(rt, s);
|
|
}
|
|
}
|
|
|
|
static void js_async_function_resolve_mark(JSRuntime *rt, JSValue val,
|
|
JS_MarkFunc *mark_func)
|
|
{
|
|
JSObject *p = JS_VALUE_GET_OBJ(val);
|
|
JSAsyncFunctionData *s = p->u.async_function_data;
|
|
if (s) {
|
|
mark_func(rt, &s->header);
|
|
}
|
|
}
|
|
|
|
static int js_async_function_resolve_create(JSContext *ctx,
|
|
JSAsyncFunctionData *s,
|
|
JSValue *resolving_funcs)
|
|
{
|
|
int i;
|
|
JSObject *p;
|
|
|
|
for(i = 0; i < 2; i++) {
|
|
resolving_funcs[i] =
|
|
JS_NewObjectProtoClass(ctx, ctx->function_proto,
|
|
JS_CLASS_ASYNC_FUNCTION_RESOLVE + i);
|
|
if (JS_IsException(resolving_funcs[i])) {
|
|
if (i == 1)
|
|
JS_FreeValue(ctx, resolving_funcs[0]);
|
|
return -1;
|
|
}
|
|
p = JS_VALUE_GET_OBJ(resolving_funcs[i]);
|
|
s->header.ref_count++;
|
|
p->u.async_function_data = s;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void js_async_function_resume(JSContext *ctx, JSAsyncFunctionData *s)
|
|
{
|
|
JSValue func_ret, ret2;
|
|
|
|
func_ret = async_func_resume(ctx, &s->func_state);
|
|
if (JS_IsException(func_ret)) {
|
|
JSValue error;
|
|
fail:
|
|
error = JS_GetException(ctx);
|
|
ret2 = JS_Call(ctx, s->resolving_funcs[1], JS_UNDEFINED,
|
|
1, &error);
|
|
JS_FreeValue(ctx, error);
|
|
js_async_function_terminate(ctx->rt, s);
|
|
JS_FreeValue(ctx, ret2); /* XXX: what to do if exception ? */
|
|
} else {
|
|
JSValue value;
|
|
value = s->func_state.frame.cur_sp[-1];
|
|
s->func_state.frame.cur_sp[-1] = JS_UNDEFINED;
|
|
if (JS_IsUndefined(func_ret)) {
|
|
/* function returned */
|
|
ret2 = JS_Call(ctx, s->resolving_funcs[0], JS_UNDEFINED,
|
|
1, &value);
|
|
JS_FreeValue(ctx, ret2); /* XXX: what to do if exception ? */
|
|
JS_FreeValue(ctx, value);
|
|
js_async_function_terminate(ctx->rt, s);
|
|
} else {
|
|
JSValue promise, resolving_funcs[2], resolving_funcs1[2];
|
|
int i, res;
|
|
|
|
/* await */
|
|
JS_FreeValue(ctx, func_ret); /* not used */
|
|
promise = js_promise_resolve(ctx, ctx->promise_ctor,
|
|
1, &value, 0);
|
|
JS_FreeValue(ctx, value);
|
|
if (JS_IsException(promise))
|
|
goto fail;
|
|
if (js_async_function_resolve_create(ctx, s, resolving_funcs)) {
|
|
JS_FreeValue(ctx, promise);
|
|
goto fail;
|
|
}
|
|
|
|
/* Note: no need to create 'thrownawayCapability' as in
|
|
the spec */
|
|
for(i = 0; i < 2; i++)
|
|
resolving_funcs1[i] = JS_UNDEFINED;
|
|
res = perform_promise_then(ctx, promise,
|
|
resolving_funcs,
|
|
resolving_funcs1);
|
|
JS_FreeValue(ctx, promise);
|
|
for(i = 0; i < 2; i++)
|
|
JS_FreeValue(ctx, resolving_funcs[i]);
|
|
if (res)
|
|
goto fail;
|
|
}
|
|
}
|
|
}
|
|
|
|
static JSValue js_async_function_resolve_call(JSContext *ctx,
|
|
JSValue func_obj,
|
|
JSValue this_obj,
|
|
int argc, JSValue *argv,
|
|
int flags)
|
|
{
|
|
JSObject *p = JS_VALUE_GET_OBJ(func_obj);
|
|
JSAsyncFunctionData *s = p->u.async_function_data;
|
|
BOOL is_reject = p->class_id - JS_CLASS_ASYNC_FUNCTION_RESOLVE;
|
|
JSValue arg;
|
|
|
|
if (argc > 0)
|
|
arg = argv[0];
|
|
else
|
|
arg = JS_UNDEFINED;
|
|
s->func_state.throw_flag = is_reject;
|
|
if (is_reject) {
|
|
JS_Throw(ctx, js_dup(arg));
|
|
} else {
|
|
/* return value of await */
|
|
s->func_state.frame.cur_sp[-1] = js_dup(arg);
|
|
}
|
|
js_async_function_resume(ctx, s);
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
static JSValue js_async_function_call(JSContext *ctx, JSValue func_obj,
|
|
JSValue this_obj,
|
|
int argc, JSValue *argv, int flags)
|
|
{
|
|
JSValue promise;
|
|
JSAsyncFunctionData *s;
|
|
|
|
s = js_mallocz(ctx, sizeof(*s));
|
|
if (!s)
|
|
return JS_EXCEPTION;
|
|
s->header.ref_count = 1;
|
|
add_gc_object(ctx->rt, &s->header, JS_GC_OBJ_TYPE_ASYNC_FUNCTION);
|
|
s->is_active = FALSE;
|
|
s->resolving_funcs[0] = JS_UNDEFINED;
|
|
s->resolving_funcs[1] = JS_UNDEFINED;
|
|
|
|
promise = JS_NewPromiseCapability(ctx, s->resolving_funcs);
|
|
if (JS_IsException(promise))
|
|
goto fail;
|
|
|
|
if (async_func_init(ctx, &s->func_state, func_obj, this_obj, argc, argv)) {
|
|
fail:
|
|
JS_FreeValue(ctx, promise);
|
|
js_async_function_free(ctx->rt, s);
|
|
return JS_EXCEPTION;
|
|
}
|
|
s->is_active = TRUE;
|
|
|
|
js_async_function_resume(ctx, s);
|
|
|
|
js_async_function_free(ctx->rt, s);
|
|
|
|
return promise;
|
|
}
|
|
|
|
/* AsyncGenerator */
|
|
|
|
typedef enum JSAsyncGeneratorStateEnum {
|
|
JS_ASYNC_GENERATOR_STATE_SUSPENDED_START,
|
|
JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD,
|
|
JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD_STAR,
|
|
JS_ASYNC_GENERATOR_STATE_EXECUTING,
|
|
JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN,
|
|
JS_ASYNC_GENERATOR_STATE_COMPLETED,
|
|
} JSAsyncGeneratorStateEnum;
|
|
|
|
typedef struct JSAsyncGeneratorRequest {
|
|
struct list_head link;
|
|
/* completion */
|
|
int completion_type; /* GEN_MAGIC_x */
|
|
JSValue result;
|
|
/* promise capability */
|
|
JSValue promise;
|
|
JSValue resolving_funcs[2];
|
|
} JSAsyncGeneratorRequest;
|
|
|
|
typedef struct JSAsyncGeneratorData {
|
|
JSObject *generator; /* back pointer to the object (const) */
|
|
JSAsyncGeneratorStateEnum state;
|
|
JSAsyncFunctionState func_state;
|
|
struct list_head queue; /* list of JSAsyncGeneratorRequest.link */
|
|
} JSAsyncGeneratorData;
|
|
|
|
static void js_async_generator_free(JSRuntime *rt,
|
|
JSAsyncGeneratorData *s)
|
|
{
|
|
struct list_head *el, *el1;
|
|
JSAsyncGeneratorRequest *req;
|
|
|
|
list_for_each_safe(el, el1, &s->queue) {
|
|
req = list_entry(el, JSAsyncGeneratorRequest, link);
|
|
JS_FreeValueRT(rt, req->result);
|
|
JS_FreeValueRT(rt, req->promise);
|
|
JS_FreeValueRT(rt, req->resolving_funcs[0]);
|
|
JS_FreeValueRT(rt, req->resolving_funcs[1]);
|
|
js_free_rt(rt, req);
|
|
}
|
|
if (s->state != JS_ASYNC_GENERATOR_STATE_COMPLETED &&
|
|
s->state != JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN) {
|
|
async_func_free(rt, &s->func_state);
|
|
}
|
|
js_free_rt(rt, s);
|
|
}
|
|
|
|
static void js_async_generator_finalizer(JSRuntime *rt, JSValue obj)
|
|
{
|
|
JSAsyncGeneratorData *s = JS_GetOpaque(obj, JS_CLASS_ASYNC_GENERATOR);
|
|
|
|
if (s) {
|
|
js_async_generator_free(rt, s);
|
|
}
|
|
}
|
|
|
|
static void js_async_generator_mark(JSRuntime *rt, JSValue val,
|
|
JS_MarkFunc *mark_func)
|
|
{
|
|
JSAsyncGeneratorData *s = JS_GetOpaque(val, JS_CLASS_ASYNC_GENERATOR);
|
|
struct list_head *el;
|
|
JSAsyncGeneratorRequest *req;
|
|
if (s) {
|
|
list_for_each(el, &s->queue) {
|
|
req = list_entry(el, JSAsyncGeneratorRequest, link);
|
|
JS_MarkValue(rt, req->result, mark_func);
|
|
JS_MarkValue(rt, req->promise, mark_func);
|
|
JS_MarkValue(rt, req->resolving_funcs[0], mark_func);
|
|
JS_MarkValue(rt, req->resolving_funcs[1], mark_func);
|
|
}
|
|
if (s->state != JS_ASYNC_GENERATOR_STATE_COMPLETED &&
|
|
s->state != JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN) {
|
|
async_func_mark(rt, &s->func_state, mark_func);
|
|
}
|
|
}
|
|
}
|
|
|
|
static JSValue js_async_generator_resolve_function(JSContext *ctx,
|
|
JSValue this_obj,
|
|
int argc, JSValue *argv,
|
|
int magic, JSValue *func_data);
|
|
|
|
static int js_async_generator_resolve_function_create(JSContext *ctx,
|
|
JSValue generator,
|
|
JSValue *resolving_funcs,
|
|
BOOL is_resume_next)
|
|
{
|
|
int i;
|
|
JSValue func;
|
|
|
|
for(i = 0; i < 2; i++) {
|
|
func = JS_NewCFunctionData(ctx, js_async_generator_resolve_function, 1,
|
|
i + is_resume_next * 2, 1, &generator);
|
|
if (JS_IsException(func)) {
|
|
if (i == 1)
|
|
JS_FreeValue(ctx, resolving_funcs[0]);
|
|
return -1;
|
|
}
|
|
resolving_funcs[i] = func;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int js_async_generator_await(JSContext *ctx,
|
|
JSAsyncGeneratorData *s,
|
|
JSValue value)
|
|
{
|
|
JSValue promise, resolving_funcs[2], resolving_funcs1[2];
|
|
int i, res;
|
|
|
|
promise = js_promise_resolve(ctx, ctx->promise_ctor,
|
|
1, &value, 0);
|
|
if (JS_IsException(promise))
|
|
goto fail;
|
|
|
|
if (js_async_generator_resolve_function_create(ctx, JS_MKPTR(JS_TAG_OBJECT, s->generator),
|
|
resolving_funcs, FALSE)) {
|
|
JS_FreeValue(ctx, promise);
|
|
goto fail;
|
|
}
|
|
|
|
/* Note: no need to create 'thrownawayCapability' as in
|
|
the spec */
|
|
for(i = 0; i < 2; i++)
|
|
resolving_funcs1[i] = JS_UNDEFINED;
|
|
res = perform_promise_then(ctx, promise,
|
|
resolving_funcs,
|
|
resolving_funcs1);
|
|
JS_FreeValue(ctx, promise);
|
|
for(i = 0; i < 2; i++)
|
|
JS_FreeValue(ctx, resolving_funcs[i]);
|
|
if (res)
|
|
goto fail;
|
|
return 0;
|
|
fail:
|
|
return -1;
|
|
}
|
|
|
|
static void js_async_generator_resolve_or_reject(JSContext *ctx,
|
|
JSAsyncGeneratorData *s,
|
|
JSValue result,
|
|
int is_reject)
|
|
{
|
|
JSAsyncGeneratorRequest *next;
|
|
JSValue ret;
|
|
|
|
next = list_entry(s->queue.next, JSAsyncGeneratorRequest, link);
|
|
list_del(&next->link);
|
|
ret = JS_Call(ctx, next->resolving_funcs[is_reject], JS_UNDEFINED, 1,
|
|
&result);
|
|
JS_FreeValue(ctx, ret);
|
|
JS_FreeValue(ctx, next->result);
|
|
JS_FreeValue(ctx, next->promise);
|
|
JS_FreeValue(ctx, next->resolving_funcs[0]);
|
|
JS_FreeValue(ctx, next->resolving_funcs[1]);
|
|
js_free(ctx, next);
|
|
}
|
|
|
|
static void js_async_generator_resolve(JSContext *ctx,
|
|
JSAsyncGeneratorData *s,
|
|
JSValue value,
|
|
BOOL done)
|
|
{
|
|
JSValue result;
|
|
result = js_create_iterator_result(ctx, js_dup(value), done);
|
|
/* XXX: better exception handling ? */
|
|
js_async_generator_resolve_or_reject(ctx, s, result, 0);
|
|
JS_FreeValue(ctx, result);
|
|
}
|
|
|
|
static void js_async_generator_reject(JSContext *ctx,
|
|
JSAsyncGeneratorData *s,
|
|
JSValue exception)
|
|
{
|
|
js_async_generator_resolve_or_reject(ctx, s, exception, 1);
|
|
}
|
|
|
|
static void js_async_generator_complete(JSContext *ctx,
|
|
JSAsyncGeneratorData *s)
|
|
{
|
|
if (s->state != JS_ASYNC_GENERATOR_STATE_COMPLETED) {
|
|
s->state = JS_ASYNC_GENERATOR_STATE_COMPLETED;
|
|
async_func_free(ctx->rt, &s->func_state);
|
|
}
|
|
}
|
|
|
|
static int js_async_generator_completed_return(JSContext *ctx,
|
|
JSAsyncGeneratorData *s,
|
|
JSValue value)
|
|
{
|
|
JSValue promise, resolving_funcs[2], resolving_funcs1[2];
|
|
int res;
|
|
|
|
// Can fail looking up JS_ATOM_constructor when is_reject==0.
|
|
promise = js_promise_resolve(ctx, ctx->promise_ctor, 1, &value,
|
|
/*is_reject*/0);
|
|
// A poisoned .constructor property is observable and the resulting
|
|
// exception should be delivered to the catch handler.
|
|
if (JS_IsException(promise)) {
|
|
JSValue err = JS_GetException(ctx);
|
|
promise = js_promise_resolve(ctx, ctx->promise_ctor, 1, &err,
|
|
/*is_reject*/1);
|
|
JS_FreeValue(ctx, err);
|
|
if (JS_IsException(promise))
|
|
return -1;
|
|
}
|
|
if (js_async_generator_resolve_function_create(ctx,
|
|
JS_MKPTR(JS_TAG_OBJECT, s->generator),
|
|
resolving_funcs1,
|
|
TRUE)) {
|
|
JS_FreeValue(ctx, promise);
|
|
return -1;
|
|
}
|
|
resolving_funcs[0] = JS_UNDEFINED;
|
|
resolving_funcs[1] = JS_UNDEFINED;
|
|
res = perform_promise_then(ctx, promise,
|
|
resolving_funcs1,
|
|
resolving_funcs);
|
|
JS_FreeValue(ctx, resolving_funcs1[0]);
|
|
JS_FreeValue(ctx, resolving_funcs1[1]);
|
|
JS_FreeValue(ctx, promise);
|
|
return res;
|
|
}
|
|
|
|
static void js_async_generator_resume_next(JSContext *ctx,
|
|
JSAsyncGeneratorData *s)
|
|
{
|
|
JSAsyncGeneratorRequest *next;
|
|
JSValue func_ret, value;
|
|
|
|
for(;;) {
|
|
if (list_empty(&s->queue))
|
|
break;
|
|
next = list_entry(s->queue.next, JSAsyncGeneratorRequest, link);
|
|
switch(s->state) {
|
|
case JS_ASYNC_GENERATOR_STATE_EXECUTING:
|
|
/* only happens when restarting execution after await() */
|
|
goto resume_exec;
|
|
case JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN:
|
|
goto done;
|
|
case JS_ASYNC_GENERATOR_STATE_SUSPENDED_START:
|
|
if (next->completion_type == GEN_MAGIC_NEXT) {
|
|
goto exec_no_arg;
|
|
} else {
|
|
js_async_generator_complete(ctx, s);
|
|
}
|
|
break;
|
|
case JS_ASYNC_GENERATOR_STATE_COMPLETED:
|
|
if (next->completion_type == GEN_MAGIC_NEXT) {
|
|
js_async_generator_resolve(ctx, s, JS_UNDEFINED, TRUE);
|
|
} else if (next->completion_type == GEN_MAGIC_RETURN) {
|
|
s->state = JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN;
|
|
js_async_generator_completed_return(ctx, s, next->result);
|
|
} else {
|
|
js_async_generator_reject(ctx, s, next->result);
|
|
}
|
|
goto done;
|
|
case JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD:
|
|
case JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD_STAR:
|
|
value = js_dup(next->result);
|
|
if (next->completion_type == GEN_MAGIC_THROW &&
|
|
s->state == JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD) {
|
|
JS_Throw(ctx, value);
|
|
s->func_state.throw_flag = TRUE;
|
|
} else {
|
|
/* 'yield' returns a value. 'yield *' also returns a value
|
|
in case the 'throw' method is called */
|
|
s->func_state.frame.cur_sp[-1] = value;
|
|
s->func_state.frame.cur_sp[0] =
|
|
js_int32(next->completion_type);
|
|
s->func_state.frame.cur_sp++;
|
|
exec_no_arg:
|
|
s->func_state.throw_flag = FALSE;
|
|
}
|
|
s->state = JS_ASYNC_GENERATOR_STATE_EXECUTING;
|
|
resume_exec:
|
|
func_ret = async_func_resume(ctx, &s->func_state);
|
|
if (JS_IsException(func_ret)) {
|
|
value = JS_GetException(ctx);
|
|
js_async_generator_complete(ctx, s);
|
|
js_async_generator_reject(ctx, s, value);
|
|
JS_FreeValue(ctx, value);
|
|
} else if (JS_VALUE_GET_TAG(func_ret) == JS_TAG_INT) {
|
|
int func_ret_code, ret;
|
|
value = s->func_state.frame.cur_sp[-1];
|
|
s->func_state.frame.cur_sp[-1] = JS_UNDEFINED;
|
|
func_ret_code = JS_VALUE_GET_INT(func_ret);
|
|
switch(func_ret_code) {
|
|
case FUNC_RET_YIELD:
|
|
case FUNC_RET_YIELD_STAR:
|
|
if (func_ret_code == FUNC_RET_YIELD_STAR)
|
|
s->state = JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD_STAR;
|
|
else
|
|
s->state = JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD;
|
|
js_async_generator_resolve(ctx, s, value, FALSE);
|
|
JS_FreeValue(ctx, value);
|
|
break;
|
|
case FUNC_RET_AWAIT:
|
|
ret = js_async_generator_await(ctx, s, value);
|
|
JS_FreeValue(ctx, value);
|
|
if (ret < 0) {
|
|
/* exception: throw it */
|
|
s->func_state.throw_flag = TRUE;
|
|
goto resume_exec;
|
|
}
|
|
goto done;
|
|
default:
|
|
abort();
|
|
}
|
|
} else {
|
|
assert(JS_IsUndefined(func_ret));
|
|
/* end of function */
|
|
value = s->func_state.frame.cur_sp[-1];
|
|
s->func_state.frame.cur_sp[-1] = JS_UNDEFINED;
|
|
js_async_generator_complete(ctx, s);
|
|
js_async_generator_resolve(ctx, s, value, TRUE);
|
|
JS_FreeValue(ctx, value);
|
|
}
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
}
|
|
done: ;
|
|
}
|
|
|
|
static JSValue js_async_generator_resolve_function(JSContext *ctx,
|
|
JSValue this_obj,
|
|
int argc, JSValue *argv,
|
|
int magic, JSValue *func_data)
|
|
{
|
|
BOOL is_reject = magic & 1;
|
|
JSAsyncGeneratorData *s = JS_GetOpaque(func_data[0], JS_CLASS_ASYNC_GENERATOR);
|
|
JSValue arg = argv[0];
|
|
|
|
/* XXX: what if s == NULL */
|
|
|
|
if (magic >= 2) {
|
|
/* resume next case in AWAITING_RETURN state */
|
|
assert(s->state == JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN ||
|
|
s->state == JS_ASYNC_GENERATOR_STATE_COMPLETED);
|
|
s->state = JS_ASYNC_GENERATOR_STATE_COMPLETED;
|
|
if (is_reject) {
|
|
js_async_generator_reject(ctx, s, arg);
|
|
} else {
|
|
js_async_generator_resolve(ctx, s, arg, TRUE);
|
|
}
|
|
} else {
|
|
/* restart function execution after await() */
|
|
assert(s->state == JS_ASYNC_GENERATOR_STATE_EXECUTING);
|
|
s->func_state.throw_flag = is_reject;
|
|
if (is_reject) {
|
|
JS_Throw(ctx, js_dup(arg));
|
|
} else {
|
|
/* return value of await */
|
|
s->func_state.frame.cur_sp[-1] = js_dup(arg);
|
|
}
|
|
js_async_generator_resume_next(ctx, s);
|
|
}
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
/* magic = GEN_MAGIC_x */
|
|
static JSValue js_async_generator_next(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv,
|
|
int magic)
|
|
{
|
|
JSAsyncGeneratorData *s = JS_GetOpaque(this_val, JS_CLASS_ASYNC_GENERATOR);
|
|
JSValue promise, resolving_funcs[2];
|
|
JSAsyncGeneratorRequest *req;
|
|
|
|
promise = JS_NewPromiseCapability(ctx, resolving_funcs);
|
|
if (JS_IsException(promise))
|
|
return JS_EXCEPTION;
|
|
if (!s) {
|
|
JSValue err, res2;
|
|
JS_ThrowTypeError(ctx, "not an AsyncGenerator object");
|
|
err = JS_GetException(ctx);
|
|
res2 = JS_Call(ctx, resolving_funcs[1], JS_UNDEFINED,
|
|
1, &err);
|
|
JS_FreeValue(ctx, err);
|
|
JS_FreeValue(ctx, res2);
|
|
JS_FreeValue(ctx, resolving_funcs[0]);
|
|
JS_FreeValue(ctx, resolving_funcs[1]);
|
|
return promise;
|
|
}
|
|
req = js_mallocz(ctx, sizeof(*req));
|
|
if (!req)
|
|
goto fail;
|
|
req->completion_type = magic;
|
|
req->result = js_dup(argv[0]);
|
|
req->promise = js_dup(promise);
|
|
req->resolving_funcs[0] = resolving_funcs[0];
|
|
req->resolving_funcs[1] = resolving_funcs[1];
|
|
list_add_tail(&req->link, &s->queue);
|
|
if (s->state != JS_ASYNC_GENERATOR_STATE_EXECUTING) {
|
|
js_async_generator_resume_next(ctx, s);
|
|
}
|
|
return promise;
|
|
fail:
|
|
JS_FreeValue(ctx, resolving_funcs[0]);
|
|
JS_FreeValue(ctx, resolving_funcs[1]);
|
|
JS_FreeValue(ctx, promise);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue js_async_generator_function_call(JSContext *ctx, JSValue func_obj,
|
|
JSValue this_obj,
|
|
int argc, JSValue *argv,
|
|
int flags)
|
|
{
|
|
JSValue obj, func_ret;
|
|
JSAsyncGeneratorData *s;
|
|
|
|
s = js_mallocz(ctx, sizeof(*s));
|
|
if (!s)
|
|
return JS_EXCEPTION;
|
|
s->state = JS_ASYNC_GENERATOR_STATE_SUSPENDED_START;
|
|
init_list_head(&s->queue);
|
|
if (async_func_init(ctx, &s->func_state, func_obj, this_obj, argc, argv)) {
|
|
s->state = JS_ASYNC_GENERATOR_STATE_COMPLETED;
|
|
goto fail;
|
|
}
|
|
|
|
/* execute the function up to 'OP_initial_yield' (no yield nor
|
|
await are possible) */
|
|
func_ret = async_func_resume(ctx, &s->func_state);
|
|
if (JS_IsException(func_ret))
|
|
goto fail;
|
|
JS_FreeValue(ctx, func_ret);
|
|
|
|
obj = js_create_from_ctor(ctx, func_obj, JS_CLASS_ASYNC_GENERATOR);
|
|
if (JS_IsException(obj))
|
|
goto fail;
|
|
s->generator = JS_VALUE_GET_OBJ(obj);
|
|
JS_SetOpaque(obj, s);
|
|
return obj;
|
|
fail:
|
|
js_async_generator_free(ctx->rt, s);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
/* JS parser */
|
|
|
|
enum {
|
|
TOK_NUMBER = -128,
|
|
TOK_STRING,
|
|
TOK_TEMPLATE,
|
|
TOK_IDENT,
|
|
TOK_REGEXP,
|
|
/* warning: order matters (see js_parse_assign_expr) */
|
|
TOK_MUL_ASSIGN,
|
|
TOK_DIV_ASSIGN,
|
|
TOK_MOD_ASSIGN,
|
|
TOK_PLUS_ASSIGN,
|
|
TOK_MINUS_ASSIGN,
|
|
TOK_SHL_ASSIGN,
|
|
TOK_SAR_ASSIGN,
|
|
TOK_SHR_ASSIGN,
|
|
TOK_AND_ASSIGN,
|
|
TOK_XOR_ASSIGN,
|
|
TOK_OR_ASSIGN,
|
|
TOK_POW_ASSIGN,
|
|
TOK_LAND_ASSIGN,
|
|
TOK_LOR_ASSIGN,
|
|
TOK_DOUBLE_QUESTION_MARK_ASSIGN,
|
|
TOK_DEC,
|
|
TOK_INC,
|
|
TOK_SHL,
|
|
TOK_SAR,
|
|
TOK_SHR,
|
|
TOK_LT,
|
|
TOK_LTE,
|
|
TOK_GT,
|
|
TOK_GTE,
|
|
TOK_EQ,
|
|
TOK_STRICT_EQ,
|
|
TOK_NEQ,
|
|
TOK_STRICT_NEQ,
|
|
TOK_LAND,
|
|
TOK_LOR,
|
|
TOK_POW,
|
|
TOK_ARROW,
|
|
TOK_ELLIPSIS,
|
|
TOK_DOUBLE_QUESTION_MARK,
|
|
TOK_QUESTION_MARK_DOT,
|
|
TOK_ERROR,
|
|
TOK_PRIVATE_NAME,
|
|
TOK_EOF,
|
|
/* keywords: WARNING: same order as atoms */
|
|
TOK_NULL, /* must be first */
|
|
TOK_FALSE,
|
|
TOK_TRUE,
|
|
TOK_IF,
|
|
TOK_ELSE,
|
|
TOK_RETURN,
|
|
TOK_VAR,
|
|
TOK_THIS,
|
|
TOK_DELETE,
|
|
TOK_VOID,
|
|
TOK_TYPEOF,
|
|
TOK_NEW,
|
|
TOK_IN,
|
|
TOK_INSTANCEOF,
|
|
TOK_DO,
|
|
TOK_WHILE,
|
|
TOK_FOR,
|
|
TOK_BREAK,
|
|
TOK_CONTINUE,
|
|
TOK_SWITCH,
|
|
TOK_CASE,
|
|
TOK_DEFAULT,
|
|
TOK_THROW,
|
|
TOK_TRY,
|
|
TOK_CATCH,
|
|
TOK_FINALLY,
|
|
TOK_FUNCTION,
|
|
TOK_DEBUGGER,
|
|
TOK_WITH,
|
|
/* FutureReservedWord */
|
|
TOK_CLASS,
|
|
TOK_CONST,
|
|
TOK_ENUM,
|
|
TOK_EXPORT,
|
|
TOK_EXTENDS,
|
|
TOK_IMPORT,
|
|
TOK_SUPER,
|
|
/* FutureReservedWords when parsing strict mode code */
|
|
TOK_IMPLEMENTS,
|
|
TOK_INTERFACE,
|
|
TOK_LET,
|
|
TOK_PACKAGE,
|
|
TOK_PRIVATE,
|
|
TOK_PROTECTED,
|
|
TOK_PUBLIC,
|
|
TOK_STATIC,
|
|
TOK_YIELD,
|
|
TOK_AWAIT, /* must be last */
|
|
TOK_OF, /* only used for js_parse_skip_parens_token() */
|
|
};
|
|
|
|
#define TOK_FIRST_KEYWORD TOK_NULL
|
|
#define TOK_LAST_KEYWORD TOK_AWAIT
|
|
|
|
/* unicode code points */
|
|
#define CP_NBSP 0x00a0
|
|
#define CP_BOM 0xfeff
|
|
|
|
#define CP_LS 0x2028
|
|
#define CP_PS 0x2029
|
|
|
|
typedef struct BlockEnv {
|
|
struct BlockEnv *prev;
|
|
JSAtom label_name; /* JS_ATOM_NULL if none */
|
|
int label_break; /* -1 if none */
|
|
int label_cont; /* -1 if none */
|
|
int drop_count; /* number of stack elements to drop */
|
|
int label_finally; /* -1 if none */
|
|
int scope_level;
|
|
int has_iterator;
|
|
} BlockEnv;
|
|
|
|
typedef struct JSGlobalVar {
|
|
int cpool_idx; /* if >= 0, index in the constant pool for hoisted
|
|
function defintion*/
|
|
uint8_t force_init : 1; /* force initialization to undefined */
|
|
uint8_t is_lexical : 1; /* global let/const definition */
|
|
uint8_t is_const : 1; /* const definition */
|
|
int scope_level; /* scope of definition */
|
|
JSAtom var_name; /* variable name */
|
|
} JSGlobalVar;
|
|
|
|
typedef struct RelocEntry {
|
|
struct RelocEntry *next;
|
|
uint32_t addr; /* address to patch */
|
|
int size; /* address size: 1, 2 or 4 bytes */
|
|
} RelocEntry;
|
|
|
|
typedef struct JumpSlot {
|
|
int op;
|
|
int size;
|
|
int pos;
|
|
int label;
|
|
} JumpSlot;
|
|
|
|
typedef struct LabelSlot {
|
|
int ref_count;
|
|
int pos; /* phase 1 address, -1 means not resolved yet */
|
|
int pos2; /* phase 2 address, -1 means not resolved yet */
|
|
int addr; /* phase 3 address, -1 means not resolved yet */
|
|
RelocEntry *first_reloc;
|
|
} LabelSlot;
|
|
|
|
typedef struct SourceLocSlot {
|
|
uint32_t pc;
|
|
int line_num;
|
|
int col_num;
|
|
} SourceLocSlot;
|
|
|
|
typedef enum JSParseFunctionEnum {
|
|
JS_PARSE_FUNC_STATEMENT,
|
|
JS_PARSE_FUNC_VAR,
|
|
JS_PARSE_FUNC_EXPR,
|
|
JS_PARSE_FUNC_ARROW,
|
|
JS_PARSE_FUNC_GETTER,
|
|
JS_PARSE_FUNC_SETTER,
|
|
JS_PARSE_FUNC_METHOD,
|
|
JS_PARSE_FUNC_CLASS_STATIC_INIT,
|
|
JS_PARSE_FUNC_CLASS_CONSTRUCTOR,
|
|
JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR,
|
|
} JSParseFunctionEnum;
|
|
|
|
typedef enum JSParseExportEnum {
|
|
JS_PARSE_EXPORT_NONE,
|
|
JS_PARSE_EXPORT_NAMED,
|
|
JS_PARSE_EXPORT_DEFAULT,
|
|
} JSParseExportEnum;
|
|
|
|
typedef struct JSFunctionDef {
|
|
JSContext *ctx;
|
|
struct JSFunctionDef *parent;
|
|
int parent_cpool_idx; /* index in the constant pool of the parent
|
|
or -1 if none */
|
|
int parent_scope_level; /* scope level in parent at point of definition */
|
|
struct list_head child_list; /* list of JSFunctionDef.link */
|
|
struct list_head link;
|
|
|
|
BOOL is_eval; /* TRUE if eval code */
|
|
int eval_type; /* only valid if is_eval = TRUE */
|
|
BOOL is_global_var; /* TRUE if variables are not defined locally:
|
|
eval global, eval module or non strict eval */
|
|
BOOL is_func_expr; /* TRUE if function expression */
|
|
BOOL has_home_object; /* TRUE if the home object is available */
|
|
BOOL has_prototype; /* true if a prototype field is necessary */
|
|
BOOL has_simple_parameter_list;
|
|
BOOL has_parameter_expressions; /* if true, an argument scope is created */
|
|
BOOL has_use_strict; /* to reject directive in special cases */
|
|
BOOL has_eval_call; /* true if the function contains a call to eval() */
|
|
BOOL has_arguments_binding; /* true if the 'arguments' binding is
|
|
available in the function */
|
|
BOOL has_this_binding; /* true if the 'this' and new.target binding are
|
|
available in the function */
|
|
BOOL new_target_allowed; /* true if the 'new.target' does not
|
|
throw a syntax error */
|
|
BOOL super_call_allowed; /* true if super() is allowed */
|
|
BOOL super_allowed; /* true if super. or super[] is allowed */
|
|
BOOL arguments_allowed; /* true if the 'arguments' identifier is allowed */
|
|
BOOL is_derived_class_constructor;
|
|
BOOL in_function_body;
|
|
BOOL backtrace_barrier;
|
|
JSFunctionKindEnum func_kind : 8;
|
|
JSParseFunctionEnum func_type : 8;
|
|
uint8_t js_mode; /* bitmap of JS_MODE_x */
|
|
JSAtom func_name; /* JS_ATOM_NULL if no name */
|
|
|
|
JSVarDef *vars;
|
|
int var_size; /* allocated size for vars[] */
|
|
int var_count;
|
|
JSVarDef *args;
|
|
int arg_size; /* allocated size for args[] */
|
|
int arg_count; /* number of arguments */
|
|
int defined_arg_count;
|
|
int var_object_idx; /* -1 if none */
|
|
int arg_var_object_idx; /* -1 if none (var object for the argument scope) */
|
|
int arguments_var_idx; /* -1 if none */
|
|
int arguments_arg_idx; /* argument variable definition in argument scope,
|
|
-1 if none */
|
|
int func_var_idx; /* variable containing the current function (-1
|
|
if none, only used if is_func_expr is true) */
|
|
int eval_ret_idx; /* variable containing the return value of the eval, -1 if none */
|
|
int this_var_idx; /* variable containg the 'this' value, -1 if none */
|
|
int new_target_var_idx; /* variable containg the 'new.target' value, -1 if none */
|
|
int this_active_func_var_idx; /* variable containg the 'this.active_func' value, -1 if none */
|
|
int home_object_var_idx;
|
|
BOOL need_home_object;
|
|
|
|
int scope_level; /* index into fd->scopes if the current lexical scope */
|
|
int scope_first; /* index into vd->vars of first lexically scoped variable */
|
|
int scope_size; /* allocated size of fd->scopes array */
|
|
int scope_count; /* number of entries used in the fd->scopes array */
|
|
JSVarScope *scopes;
|
|
JSVarScope def_scope_array[4];
|
|
int body_scope; /* scope of the body of the function or eval */
|
|
|
|
int global_var_count;
|
|
int global_var_size;
|
|
JSGlobalVar *global_vars;
|
|
|
|
DynBuf byte_code;
|
|
int last_opcode_pos; /* -1 if no last opcode */
|
|
int last_opcode_line_num;
|
|
int last_opcode_col_num;
|
|
BOOL use_short_opcodes; /* true if short opcodes are used in byte_code */
|
|
|
|
LabelSlot *label_slots;
|
|
int label_size; /* allocated size for label_slots[] */
|
|
int label_count;
|
|
BlockEnv *top_break; /* break/continue label stack */
|
|
|
|
/* constant pool (strings, functions, numbers) */
|
|
JSValue *cpool;
|
|
int cpool_count;
|
|
int cpool_size;
|
|
|
|
/* list of variables in the closure */
|
|
int closure_var_count;
|
|
int closure_var_size;
|
|
JSClosureVar *closure_var;
|
|
|
|
JumpSlot *jump_slots;
|
|
int jump_size;
|
|
int jump_count;
|
|
|
|
SourceLocSlot *source_loc_slots;
|
|
int source_loc_size;
|
|
int source_loc_count;
|
|
int line_number_last;
|
|
int line_number_last_pc;
|
|
int col_number_last;
|
|
|
|
/* pc2line table */
|
|
JSAtom filename;
|
|
int line_num;
|
|
int col_num;
|
|
DynBuf pc2line;
|
|
|
|
char *source; /* raw source, utf-8 encoded */
|
|
int source_len;
|
|
|
|
JSModuleDef *module; /* != NULL when parsing a module */
|
|
JSInlineCache *ic; /* inline cache for field op */
|
|
} JSFunctionDef;
|
|
|
|
typedef struct JSToken {
|
|
int val;
|
|
int line_num; /* line number of token start */
|
|
int col_num; /* column number of token start */
|
|
const uint8_t *ptr;
|
|
union {
|
|
struct {
|
|
JSValue str;
|
|
int sep;
|
|
} str;
|
|
struct {
|
|
JSValue val;
|
|
} num;
|
|
struct {
|
|
JSAtom atom;
|
|
BOOL has_escape;
|
|
BOOL is_reserved;
|
|
} ident;
|
|
struct {
|
|
JSValue body;
|
|
JSValue flags;
|
|
} regexp;
|
|
} u;
|
|
} JSToken;
|
|
|
|
typedef struct JSParseState {
|
|
JSContext *ctx;
|
|
int last_line_num; /* line number of last token */
|
|
int last_col_num; /* column number of last token */
|
|
int line_num; /* line number of current offset */
|
|
int col_num; /* column number of current offset */
|
|
const char *filename;
|
|
JSToken token;
|
|
BOOL got_lf; /* true if got line feed before the current token */
|
|
const uint8_t *last_ptr;
|
|
const uint8_t *buf_start;
|
|
const uint8_t *buf_ptr;
|
|
const uint8_t *buf_end;
|
|
const uint8_t *eol; // most recently seen end-of-line character
|
|
const uint8_t *mark; // first token character, invariant: eol < mark
|
|
|
|
/* current function code */
|
|
JSFunctionDef *cur_func;
|
|
BOOL is_module; /* parsing a module */
|
|
BOOL allow_html_comments;
|
|
} JSParseState;
|
|
|
|
typedef struct JSOpCode {
|
|
#ifdef DUMP_BYTECODE
|
|
const char *name;
|
|
#endif
|
|
uint8_t size; /* in bytes */
|
|
/* the opcodes remove n_pop items from the top of the stack, then
|
|
pushes n_push items */
|
|
uint8_t n_pop;
|
|
uint8_t n_push;
|
|
uint8_t fmt;
|
|
} JSOpCode;
|
|
|
|
static const JSOpCode opcode_info[OP_COUNT + (OP_TEMP_END - OP_TEMP_START)] = {
|
|
#define FMT(f)
|
|
#ifdef DUMP_BYTECODE
|
|
#define DEF(id, size, n_pop, n_push, f) { #id, size, n_pop, n_push, OP_FMT_ ## f },
|
|
#else
|
|
#define DEF(id, size, n_pop, n_push, f) { size, n_pop, n_push, OP_FMT_ ## f },
|
|
#endif
|
|
#include "quickjs-opcode.h"
|
|
#undef DEF
|
|
#undef FMT
|
|
};
|
|
|
|
/* After the final compilation pass, short opcodes are used. Their
|
|
opcodes overlap with the temporary opcodes which cannot appear in
|
|
the final bytecode. Their description is after the temporary
|
|
opcodes in opcode_info[]. */
|
|
#define short_opcode_info(op) \
|
|
opcode_info[(op) >= OP_TEMP_START ? \
|
|
(op) + (OP_TEMP_END - OP_TEMP_START) : (op)]
|
|
|
|
static __exception int next_token(JSParseState *s);
|
|
|
|
static void free_token(JSParseState *s, JSToken *token)
|
|
{
|
|
switch(token->val) {
|
|
case TOK_NUMBER:
|
|
JS_FreeValue(s->ctx, token->u.num.val);
|
|
break;
|
|
case TOK_STRING:
|
|
case TOK_TEMPLATE:
|
|
JS_FreeValue(s->ctx, token->u.str.str);
|
|
break;
|
|
case TOK_REGEXP:
|
|
JS_FreeValue(s->ctx, token->u.regexp.body);
|
|
JS_FreeValue(s->ctx, token->u.regexp.flags);
|
|
break;
|
|
case TOK_IDENT:
|
|
case TOK_PRIVATE_NAME:
|
|
JS_FreeAtom(s->ctx, token->u.ident.atom);
|
|
break;
|
|
default:
|
|
if (token->val >= TOK_FIRST_KEYWORD &&
|
|
token->val <= TOK_LAST_KEYWORD) {
|
|
JS_FreeAtom(s->ctx, token->u.ident.atom);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void __attribute((unused)) dump_token(JSParseState *s,
|
|
const JSToken *token)
|
|
{
|
|
printf("%d:%d ", token->line_num, token->col_num);
|
|
switch(token->val) {
|
|
case TOK_NUMBER:
|
|
{
|
|
double d;
|
|
JS_ToFloat64(s->ctx, &d, token->u.num.val); /* no exception possible */
|
|
printf("number: %.14g\n", d);
|
|
}
|
|
break;
|
|
case TOK_IDENT:
|
|
dump_atom:
|
|
{
|
|
char buf[ATOM_GET_STR_BUF_SIZE];
|
|
printf("ident: '%s'\n",
|
|
JS_AtomGetStr(s->ctx, buf, sizeof(buf), token->u.ident.atom));
|
|
}
|
|
break;
|
|
case TOK_STRING:
|
|
{
|
|
const char *str;
|
|
/* XXX: quote the string */
|
|
str = JS_ToCString(s->ctx, token->u.str.str);
|
|
printf("string: '%s'\n", str);
|
|
JS_FreeCString(s->ctx, str);
|
|
}
|
|
break;
|
|
case TOK_TEMPLATE:
|
|
{
|
|
const char *str;
|
|
str = JS_ToCString(s->ctx, token->u.str.str);
|
|
printf("template: `%s`\n", str);
|
|
JS_FreeCString(s->ctx, str);
|
|
}
|
|
break;
|
|
case TOK_REGEXP:
|
|
{
|
|
const char *str, *str2;
|
|
str = JS_ToCString(s->ctx, token->u.regexp.body);
|
|
str2 = JS_ToCString(s->ctx, token->u.regexp.flags);
|
|
printf("regexp: '%s' '%s'\n", str, str2);
|
|
JS_FreeCString(s->ctx, str);
|
|
JS_FreeCString(s->ctx, str2);
|
|
}
|
|
break;
|
|
case TOK_EOF:
|
|
printf("eof\n");
|
|
break;
|
|
default:
|
|
if (s->token.val >= TOK_NULL && s->token.val <= TOK_LAST_KEYWORD) {
|
|
goto dump_atom;
|
|
} else if (s->token.val >= 256) {
|
|
printf("token: %d\n", token->val);
|
|
} else {
|
|
printf("token: '%c'\n", token->val);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
int __attribute__((format(printf, 2, 3))) js_parse_error(JSParseState *s, const char *fmt, ...)
|
|
{
|
|
JSContext *ctx = s->ctx;
|
|
va_list ap;
|
|
int backtrace_flags;
|
|
|
|
va_start(ap, fmt);
|
|
JS_ThrowError2(ctx, JS_SYNTAX_ERROR, fmt, ap, FALSE);
|
|
va_end(ap);
|
|
backtrace_flags = 0;
|
|
if (s->cur_func && s->cur_func->backtrace_barrier)
|
|
backtrace_flags = JS_BACKTRACE_FLAG_SINGLE_LEVEL;
|
|
build_backtrace(ctx, ctx->rt->current_exception, s->filename,
|
|
s->line_num, s->col_num, backtrace_flags);
|
|
return -1;
|
|
}
|
|
|
|
static int js_parse_expect(JSParseState *s, int tok)
|
|
{
|
|
char buf[ATOM_GET_STR_BUF_SIZE];
|
|
|
|
if (s->token.val == tok)
|
|
return next_token(s);
|
|
|
|
switch(s->token.val) {
|
|
case TOK_EOF:
|
|
return js_parse_error(s, "Unexpected end of input");
|
|
case TOK_NUMBER:
|
|
return js_parse_error(s, "Unexpected number");
|
|
case TOK_STRING:
|
|
return js_parse_error(s, "Unexpected string");
|
|
case TOK_TEMPLATE:
|
|
return js_parse_error(s, "Unexpected string template");
|
|
case TOK_REGEXP:
|
|
return js_parse_error(s, "Unexpected regexp");
|
|
case TOK_IDENT:
|
|
return js_parse_error(s, "Unexpected identifier '%s'",
|
|
JS_AtomGetStr(s->ctx, buf, sizeof(buf),
|
|
s->token.u.ident.atom));
|
|
case TOK_ERROR:
|
|
return js_parse_error(s, "Invalid or unexpected token");
|
|
default:
|
|
return js_parse_error(s, "Unexpected token '%.*s'",
|
|
(int)(s->buf_ptr - s->token.ptr),
|
|
(const char *)s->token.ptr);
|
|
}
|
|
}
|
|
|
|
static int js_parse_expect_semi(JSParseState *s)
|
|
{
|
|
if (s->token.val != ';') {
|
|
/* automatic insertion of ';' */
|
|
if (s->token.val == TOK_EOF || s->token.val == '}' || s->got_lf) {
|
|
return 0;
|
|
}
|
|
return js_parse_error(s, "expecting '%c'", ';');
|
|
}
|
|
return next_token(s);
|
|
}
|
|
|
|
static int js_parse_error_reserved_identifier(JSParseState *s)
|
|
{
|
|
char buf1[ATOM_GET_STR_BUF_SIZE];
|
|
return js_parse_error(s, "'%s' is a reserved identifier",
|
|
JS_AtomGetStr(s->ctx, buf1, sizeof(buf1),
|
|
s->token.u.ident.atom));
|
|
}
|
|
|
|
static __exception int js_parse_template_part(JSParseState *s,
|
|
const uint8_t *p)
|
|
{
|
|
const uint8_t *p_next;
|
|
uint32_t c;
|
|
StringBuffer b_s, *b = &b_s;
|
|
|
|
/* p points to the first byte of the template part */
|
|
if (string_buffer_init(s->ctx, b, 32))
|
|
goto fail;
|
|
for(;;) {
|
|
if (p >= s->buf_end)
|
|
goto unexpected_eof;
|
|
c = *p++;
|
|
if (c == '`') {
|
|
/* template end part */
|
|
break;
|
|
}
|
|
if (c == '$' && *p == '{') {
|
|
/* template start or middle part */
|
|
p++;
|
|
break;
|
|
}
|
|
if (c == '\\') {
|
|
if (string_buffer_putc8(b, c))
|
|
goto fail;
|
|
if (p >= s->buf_end)
|
|
goto unexpected_eof;
|
|
c = *p++;
|
|
}
|
|
/* newline sequences are normalized as single '\n' bytes */
|
|
if (c == '\r') {
|
|
if (*p == '\n')
|
|
p++;
|
|
c = '\n';
|
|
}
|
|
if (c == '\n') {
|
|
s->line_num++;
|
|
s->eol = &p[-1];
|
|
s->mark = p;
|
|
} else if (c >= 0x80) {
|
|
c = utf8_decode(p - 1, &p_next);
|
|
if (p_next == p) {
|
|
js_parse_error(s, "invalid UTF-8 sequence");
|
|
goto fail;
|
|
}
|
|
p = p_next;
|
|
}
|
|
if (string_buffer_putc(b, c))
|
|
goto fail;
|
|
}
|
|
s->token.val = TOK_TEMPLATE;
|
|
s->token.u.str.sep = c;
|
|
s->token.u.str.str = string_buffer_end(b);
|
|
s->buf_ptr = p;
|
|
return 0;
|
|
|
|
unexpected_eof:
|
|
js_parse_error(s, "unexpected end of string");
|
|
fail:
|
|
string_buffer_free(b);
|
|
return -1;
|
|
}
|
|
|
|
static __exception int js_parse_string(JSParseState *s, int sep,
|
|
BOOL do_throw, const uint8_t *p,
|
|
JSToken *token, const uint8_t **pp)
|
|
{
|
|
const uint8_t *p_next;
|
|
int ret;
|
|
uint32_t c;
|
|
StringBuffer b_s, *b = &b_s;
|
|
|
|
/* string */
|
|
if (string_buffer_init(s->ctx, b, 32))
|
|
goto fail;
|
|
for(;;) {
|
|
if (p >= s->buf_end)
|
|
goto invalid_char;
|
|
c = *p;
|
|
if (c < 0x20) {
|
|
if (sep == '`') {
|
|
if (c == '\r') {
|
|
if (p[1] == '\n')
|
|
p++;
|
|
c = '\n';
|
|
}
|
|
/* do not update s->line_num */
|
|
} else if (c == '\n' || c == '\r')
|
|
goto invalid_char;
|
|
}
|
|
p++;
|
|
if (c == sep)
|
|
break;
|
|
if (c == '$' && *p == '{' && sep == '`') {
|
|
/* template start or middle part */
|
|
p++;
|
|
break;
|
|
}
|
|
if (c == '\\') {
|
|
const uint8_t *p0 = p;
|
|
c = *p;
|
|
switch(c) {
|
|
case '\0':
|
|
if (p >= s->buf_end) {
|
|
if (sep != '`')
|
|
goto invalid_char;
|
|
if (do_throw)
|
|
js_parse_error(s, "Unexpected end of input");
|
|
goto fail;
|
|
}
|
|
p++;
|
|
break;
|
|
case '\'':
|
|
case '\"':
|
|
case '\\':
|
|
p++;
|
|
break;
|
|
case '\r': /* accept DOS and MAC newline sequences */
|
|
if (p[1] == '\n') {
|
|
p++;
|
|
}
|
|
/* fall thru */
|
|
case '\n':
|
|
/* ignore escaped newline sequence */
|
|
p++;
|
|
if (sep != '`') {
|
|
s->line_num++;
|
|
s->eol = &p[-1];
|
|
s->mark = p;
|
|
}
|
|
continue;
|
|
default:
|
|
if (c == '0' && !(p[1] >= '0' && p[1] <= '9')) {
|
|
/* accept isolated \0 */
|
|
p++;
|
|
c = '\0';
|
|
} else
|
|
if ((c >= '0' && c <= '9')
|
|
&& ((s->cur_func->js_mode & JS_MODE_STRICT) || sep == '`')) {
|
|
if (do_throw) {
|
|
js_parse_error(s, "%s are not allowed in %s",
|
|
(c >= '8') ? "\\8 and \\9" : "Octal escape sequences",
|
|
(sep == '`') ? "template strings" : "strict mode");
|
|
}
|
|
goto fail;
|
|
} else if (c >= 0x80) {
|
|
c = utf8_decode(p, &p_next);
|
|
if (p_next == p + 1) {
|
|
goto invalid_utf8;
|
|
}
|
|
p = p_next;
|
|
/* LS or PS are skipped */
|
|
if (c == CP_LS || c == CP_PS)
|
|
continue;
|
|
} else {
|
|
ret = lre_parse_escape(&p, TRUE);
|
|
if (ret == -1) {
|
|
if (do_throw) {
|
|
js_parse_error(s, "Invalid %s escape sequence",
|
|
c == 'u' ? "Unicode" : "hexadecimal");
|
|
}
|
|
goto fail;
|
|
} else if (ret < 0) {
|
|
/* ignore the '\' (could output a warning) */
|
|
p++;
|
|
} else {
|
|
c = ret;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
} else if (c >= 0x80) {
|
|
c = utf8_decode(p - 1, &p_next);
|
|
if (p_next == p)
|
|
goto invalid_utf8;
|
|
p = p_next;
|
|
}
|
|
if (string_buffer_putc(b, c))
|
|
goto fail;
|
|
}
|
|
token->val = TOK_STRING;
|
|
token->u.str.sep = c;
|
|
token->u.str.str = string_buffer_end(b);
|
|
*pp = p;
|
|
return 0;
|
|
|
|
invalid_utf8:
|
|
if (do_throw)
|
|
js_parse_error(s, "invalid UTF-8 sequence");
|
|
goto fail;
|
|
invalid_char:
|
|
if (do_throw)
|
|
js_parse_error(s, "unexpected end of string");
|
|
fail:
|
|
string_buffer_free(b);
|
|
return -1;
|
|
}
|
|
|
|
static inline BOOL token_is_pseudo_keyword(JSParseState *s, JSAtom atom) {
|
|
return s->token.val == TOK_IDENT && s->token.u.ident.atom == atom &&
|
|
!s->token.u.ident.has_escape;
|
|
}
|
|
|
|
static __exception int js_parse_regexp(JSParseState *s)
|
|
{
|
|
const uint8_t *p, *p_next;
|
|
BOOL in_class;
|
|
StringBuffer b_s, *b = &b_s;
|
|
StringBuffer b2_s, *b2 = &b2_s;
|
|
uint32_t c;
|
|
|
|
p = s->buf_ptr;
|
|
p++;
|
|
in_class = FALSE;
|
|
if (string_buffer_init(s->ctx, b, 32))
|
|
return -1;
|
|
if (string_buffer_init(s->ctx, b2, 1))
|
|
goto fail;
|
|
for(;;) {
|
|
if (p >= s->buf_end) {
|
|
eof_error:
|
|
js_parse_error(s, "unexpected end of regexp");
|
|
goto fail;
|
|
}
|
|
c = *p++;
|
|
if (c == '\n' || c == '\r') {
|
|
goto eol_error;
|
|
} else if (c == '/') {
|
|
if (!in_class)
|
|
break;
|
|
} else if (c == '[') {
|
|
in_class = TRUE;
|
|
} else if (c == ']') {
|
|
/* XXX: incorrect as the first character in a class */
|
|
in_class = FALSE;
|
|
} else if (c == '\\') {
|
|
if (string_buffer_putc8(b, c))
|
|
goto fail;
|
|
c = *p++;
|
|
if (c == '\n' || c == '\r')
|
|
goto eol_error;
|
|
else if (c == '\0' && p >= s->buf_end)
|
|
goto eof_error;
|
|
else if (c >= 0x80) {
|
|
c = utf8_decode(p - 1, &p_next);
|
|
if (p_next == p) {
|
|
goto invalid_utf8;
|
|
}
|
|
p = p_next;
|
|
if (c == CP_LS || c == CP_PS)
|
|
goto eol_error;
|
|
}
|
|
} else if (c >= 0x80) {
|
|
c = utf8_decode(p - 1, &p_next);
|
|
if (p_next == p) {
|
|
invalid_utf8:
|
|
js_parse_error(s, "invalid UTF-8 sequence");
|
|
goto fail;
|
|
}
|
|
p = p_next;
|
|
/* LS or PS are considered as line terminator */
|
|
if (c == CP_LS || c == CP_PS) {
|
|
eol_error:
|
|
js_parse_error(s, "unexpected line terminator in regexp");
|
|
goto fail;
|
|
}
|
|
}
|
|
if (string_buffer_putc(b, c))
|
|
goto fail;
|
|
}
|
|
|
|
/* flags */
|
|
for(;;) {
|
|
c = utf8_decode(p, &p_next);
|
|
/* no need to test for invalid UTF-8, 0xFFFD is not ident_next */
|
|
if (!lre_js_is_ident_next(c))
|
|
break;
|
|
if (string_buffer_putc(b2, c))
|
|
goto fail;
|
|
p = p_next;
|
|
}
|
|
|
|
s->token.val = TOK_REGEXP;
|
|
s->token.u.regexp.body = string_buffer_end(b);
|
|
s->token.u.regexp.flags = string_buffer_end(b2);
|
|
s->buf_ptr = p;
|
|
return 0;
|
|
fail:
|
|
string_buffer_free(b);
|
|
string_buffer_free(b2);
|
|
return -1;
|
|
}
|
|
|
|
static __exception int ident_realloc(JSContext *ctx, char **pbuf, size_t *psize,
|
|
char *static_buf)
|
|
{
|
|
char *buf, *new_buf;
|
|
size_t size, new_size;
|
|
|
|
buf = *pbuf;
|
|
size = *psize;
|
|
if (size >= (SIZE_MAX / 3) * 2)
|
|
new_size = SIZE_MAX;
|
|
else
|
|
new_size = size + (size >> 1);
|
|
if (buf == static_buf) {
|
|
new_buf = js_malloc(ctx, new_size);
|
|
if (!new_buf)
|
|
return -1;
|
|
memcpy(new_buf, buf, size);
|
|
} else {
|
|
new_buf = js_realloc(ctx, buf, new_size);
|
|
if (!new_buf)
|
|
return -1;
|
|
}
|
|
*pbuf = new_buf;
|
|
*psize = new_size;
|
|
return 0;
|
|
}
|
|
|
|
/* 'c' is the first character. Return JS_ATOM_NULL in case of error */
|
|
static JSAtom parse_ident(JSParseState *s, const uint8_t **pp,
|
|
BOOL *pident_has_escape, int c, BOOL is_private)
|
|
{
|
|
const uint8_t *p, *p_next;
|
|
char ident_buf[128], *buf;
|
|
size_t ident_size, ident_pos;
|
|
JSAtom atom = JS_ATOM_NULL;
|
|
|
|
p = *pp;
|
|
buf = ident_buf;
|
|
ident_size = sizeof(ident_buf);
|
|
ident_pos = 0;
|
|
if (is_private)
|
|
buf[ident_pos++] = '#';
|
|
for(;;) {
|
|
if (c < 0x80) {
|
|
buf[ident_pos++] = c;
|
|
} else {
|
|
ident_pos += utf8_encode((uint8_t*)buf + ident_pos, c);
|
|
}
|
|
c = *p;
|
|
p_next = p + 1;
|
|
if (c == '\\' && *p_next == 'u') {
|
|
c = lre_parse_escape(&p_next, TRUE);
|
|
*pident_has_escape = TRUE;
|
|
} else if (c >= 0x80) {
|
|
c = utf8_decode(p, &p_next);
|
|
/* no need to test for invalid UTF-8, 0xFFFD is not ident_next */
|
|
}
|
|
if (!lre_js_is_ident_next(c))
|
|
break;
|
|
p = p_next;
|
|
if (unlikely(ident_pos >= ident_size - UTF8_CHAR_LEN_MAX)) {
|
|
if (ident_realloc(s->ctx, &buf, &ident_size, ident_buf))
|
|
goto done;
|
|
}
|
|
}
|
|
/* buf is pure ASCII or UTF-8 encoded */
|
|
atom = JS_NewAtomLen(s->ctx, buf, ident_pos);
|
|
done:
|
|
if (unlikely(buf != ident_buf))
|
|
js_free(s->ctx, buf);
|
|
*pp = p;
|
|
return atom;
|
|
}
|
|
|
|
|
|
static __exception int next_token(JSParseState *s)
|
|
{
|
|
const uint8_t *p, *p_next;
|
|
int c;
|
|
BOOL ident_has_escape;
|
|
JSAtom atom;
|
|
int flags, radix;
|
|
|
|
if (js_check_stack_overflow(s->ctx->rt, 1000)) {
|
|
JS_ThrowStackOverflow(s->ctx);
|
|
return -1;
|
|
}
|
|
|
|
free_token(s, &s->token);
|
|
|
|
p = s->last_ptr = s->buf_ptr;
|
|
s->got_lf = FALSE;
|
|
s->last_line_num = s->token.line_num;
|
|
s->last_col_num = s->token.col_num;
|
|
redo:
|
|
s->token.line_num = s->line_num;
|
|
s->token.col_num = s->col_num;
|
|
s->token.ptr = p;
|
|
c = *p;
|
|
switch(c) {
|
|
case 0:
|
|
if (p >= s->buf_end) {
|
|
s->token.val = TOK_EOF;
|
|
} else {
|
|
goto def_token;
|
|
}
|
|
break;
|
|
case '`':
|
|
if (js_parse_template_part(s, p + 1))
|
|
goto fail;
|
|
p = s->buf_ptr;
|
|
break;
|
|
case '\'':
|
|
case '\"':
|
|
if (js_parse_string(s, c, TRUE, p + 1, &s->token, &p))
|
|
goto fail;
|
|
break;
|
|
case '\r': /* accept DOS and MAC newline sequences */
|
|
if (p[1] == '\n') {
|
|
p++;
|
|
}
|
|
/* fall thru */
|
|
case '\n':
|
|
p++;
|
|
line_terminator:
|
|
s->eol = &p[-1];
|
|
s->mark = p;
|
|
s->got_lf = TRUE;
|
|
s->line_num++;
|
|
goto redo;
|
|
case '\f':
|
|
case '\v':
|
|
case ' ':
|
|
case '\t':
|
|
s->mark = ++p;
|
|
goto redo;
|
|
case '/':
|
|
if (p[1] == '*') {
|
|
/* comment */
|
|
p += 2;
|
|
for(;;) {
|
|
if (*p == '\0' && p >= s->buf_end) {
|
|
js_parse_error(s, "unexpected end of comment");
|
|
goto fail;
|
|
}
|
|
if (p[0] == '*' && p[1] == '/') {
|
|
p += 2;
|
|
break;
|
|
}
|
|
if (*p == '\n') {
|
|
s->line_num++;
|
|
s->got_lf = TRUE; /* considered as LF for ASI */
|
|
s->eol = p++;
|
|
s->mark = p;
|
|
} else if (*p == '\r') {
|
|
s->got_lf = TRUE; /* considered as LF for ASI */
|
|
p++;
|
|
} else if (*p >= 0x80) {
|
|
c = utf8_decode(p, &p);
|
|
/* ignore invalid UTF-8 in comments */
|
|
if (c == CP_LS || c == CP_PS) {
|
|
s->got_lf = TRUE; /* considered as LF for ASI */
|
|
}
|
|
} else {
|
|
p++;
|
|
}
|
|
}
|
|
s->mark = p;
|
|
goto redo;
|
|
} else if (p[1] == '/') {
|
|
/* line comment */
|
|
p += 2;
|
|
skip_line_comment:
|
|
for(;;) {
|
|
if (*p == '\0' && p >= s->buf_end)
|
|
break;
|
|
if (*p == '\r' || *p == '\n')
|
|
break;
|
|
if (*p >= 0x80) {
|
|
c = utf8_decode(p, &p);
|
|
/* ignore invalid UTF-8 in comments */
|
|
/* LS or PS are considered as line terminator */
|
|
if (c == CP_LS || c == CP_PS) {
|
|
break;
|
|
}
|
|
} else {
|
|
p++;
|
|
}
|
|
}
|
|
s->mark = p;
|
|
goto redo;
|
|
} else if (p[1] == '=') {
|
|
p += 2;
|
|
s->token.val = TOK_DIV_ASSIGN;
|
|
} else {
|
|
p++;
|
|
s->token.val = c;
|
|
}
|
|
break;
|
|
case '\\':
|
|
if (p[1] == 'u') {
|
|
const uint8_t *p1 = p + 1;
|
|
int c1 = lre_parse_escape(&p1, TRUE);
|
|
if (c1 >= 0 && lre_js_is_ident_first(c1)) {
|
|
c = c1;
|
|
p = p1;
|
|
ident_has_escape = TRUE;
|
|
goto has_ident;
|
|
} else {
|
|
/* XXX: syntax error? */
|
|
}
|
|
}
|
|
goto def_token;
|
|
case 'a': case 'b': case 'c': case 'd':
|
|
case 'e': case 'f': case 'g': case 'h':
|
|
case 'i': case 'j': case 'k': case 'l':
|
|
case 'm': case 'n': case 'o': case 'p':
|
|
case 'q': case 'r': case 's': case 't':
|
|
case 'u': case 'v': case 'w': case 'x':
|
|
case 'y': case 'z':
|
|
case 'A': case 'B': case 'C': case 'D':
|
|
case 'E': case 'F': case 'G': case 'H':
|
|
case 'I': case 'J': case 'K': case 'L':
|
|
case 'M': case 'N': case 'O': case 'P':
|
|
case 'Q': case 'R': case 'S': case 'T':
|
|
case 'U': case 'V': case 'W': case 'X':
|
|
case 'Y': case 'Z':
|
|
case '_':
|
|
case '$':
|
|
/* identifier */
|
|
p++;
|
|
ident_has_escape = FALSE;
|
|
has_ident:
|
|
atom = parse_ident(s, &p, &ident_has_escape, c, FALSE);
|
|
if (atom == JS_ATOM_NULL)
|
|
goto fail;
|
|
s->token.u.ident.atom = atom;
|
|
s->token.u.ident.has_escape = ident_has_escape;
|
|
s->token.u.ident.is_reserved = FALSE;
|
|
// TODO(bnoordhuis) accept await when used in a function expression
|
|
// inside the static initializer block
|
|
if (s->cur_func->func_type == JS_PARSE_FUNC_CLASS_STATIC_INIT &&
|
|
(atom == JS_ATOM_arguments || atom == JS_ATOM_await)) {
|
|
s->token.u.ident.is_reserved = TRUE;
|
|
s->token.val = TOK_IDENT;
|
|
} else if (s->token.u.ident.atom <= JS_ATOM_LAST_KEYWORD ||
|
|
(s->token.u.ident.atom <= JS_ATOM_LAST_STRICT_KEYWORD &&
|
|
(s->cur_func->js_mode & JS_MODE_STRICT)) ||
|
|
(s->token.u.ident.atom == JS_ATOM_yield &&
|
|
((s->cur_func->func_kind & JS_FUNC_GENERATOR) ||
|
|
(s->cur_func->func_type == JS_PARSE_FUNC_ARROW &&
|
|
!s->cur_func->in_function_body && s->cur_func->parent &&
|
|
(s->cur_func->parent->func_kind & JS_FUNC_GENERATOR)))) ||
|
|
(s->token.u.ident.atom == JS_ATOM_await &&
|
|
(s->is_module ||
|
|
(((s->cur_func->func_kind & JS_FUNC_ASYNC) ||
|
|
(s->cur_func->func_type == JS_PARSE_FUNC_ARROW &&
|
|
!s->cur_func->in_function_body && s->cur_func->parent &&
|
|
(s->cur_func->parent->func_kind & JS_FUNC_ASYNC))))))) {
|
|
if (ident_has_escape) {
|
|
s->token.u.ident.is_reserved = TRUE;
|
|
s->token.val = TOK_IDENT;
|
|
} else {
|
|
/* The keywords atoms are pre allocated */
|
|
s->token.val = s->token.u.ident.atom - 1 + TOK_FIRST_KEYWORD;
|
|
}
|
|
} else {
|
|
s->token.val = TOK_IDENT;
|
|
}
|
|
break;
|
|
case '#':
|
|
/* private name */
|
|
{
|
|
p++;
|
|
c = *p;
|
|
p_next = p + 1;
|
|
if (c == '\\' && *p_next == 'u') {
|
|
c = lre_parse_escape(&p_next, TRUE);
|
|
} else if (c >= 0x80) {
|
|
c = utf8_decode(p, &p_next);
|
|
if (p_next == p + 1)
|
|
goto invalid_utf8;
|
|
}
|
|
if (!lre_js_is_ident_first(c)) {
|
|
js_parse_error(s, "invalid first character of private name");
|
|
goto fail;
|
|
}
|
|
p = p_next;
|
|
ident_has_escape = FALSE; /* not used */
|
|
atom = parse_ident(s, &p, &ident_has_escape, c, TRUE);
|
|
if (atom == JS_ATOM_NULL)
|
|
goto fail;
|
|
s->token.u.ident.atom = atom;
|
|
s->token.val = TOK_PRIVATE_NAME;
|
|
}
|
|
break;
|
|
case '.':
|
|
if (p[1] == '.' && p[2] == '.') {
|
|
p += 3;
|
|
s->token.val = TOK_ELLIPSIS;
|
|
break;
|
|
}
|
|
if (p[1] >= '0' && p[1] <= '9') {
|
|
flags = ATOD_ACCEPT_UNDERSCORES | ATOD_ACCEPT_FLOAT;
|
|
radix = 10;
|
|
goto parse_number;
|
|
}
|
|
goto def_token;
|
|
case '0':
|
|
if (is_digit(p[1])) { /* handle legacy octal */
|
|
if (s->cur_func->js_mode & JS_MODE_STRICT) {
|
|
js_parse_error(s, "Octal literals are not allowed in strict mode");
|
|
goto fail;
|
|
}
|
|
/* Legacy octal: no separators, no suffix, no floats,
|
|
base 8 unless non octal digits are detected */
|
|
flags = 0;
|
|
radix = 8;
|
|
while (is_digit(*p)) {
|
|
if (*p >= '8' && *p <= '9')
|
|
radix = 10;
|
|
p++;
|
|
}
|
|
p = s->token.ptr;
|
|
goto parse_number;
|
|
}
|
|
if (p[1] == '_') {
|
|
js_parse_error(s, "Numeric separator can not be used after leading 0");
|
|
goto fail;
|
|
}
|
|
flags = ATOD_ACCEPT_HEX_PREFIX | ATOD_ACCEPT_BIN_OCT |
|
|
ATOD_ACCEPT_FLOAT | ATOD_ACCEPT_UNDERSCORES | ATOD_ACCEPT_SUFFIX;
|
|
radix = 10;
|
|
goto parse_number;
|
|
case '1': case '2': case '3': case '4':
|
|
case '5': case '6': case '7': case '8':
|
|
case '9':
|
|
/* number */
|
|
{
|
|
JSValue ret;
|
|
const uint8_t *p1;
|
|
|
|
flags = ATOD_ACCEPT_FLOAT | ATOD_ACCEPT_UNDERSCORES | ATOD_ACCEPT_SUFFIX;
|
|
radix = 10;
|
|
parse_number:
|
|
ret = js_atof(s->ctx, (const char *)p, (const char *)s->buf_end,
|
|
(const char **)&p, radix, flags);
|
|
if (JS_IsException(ret))
|
|
goto fail;
|
|
/* reject `10instanceof Number` */
|
|
if (JS_VALUE_IS_NAN(ret) ||
|
|
lre_js_is_ident_next(utf8_decode(p, &p_next))) {
|
|
JS_FreeValue(s->ctx, ret);
|
|
js_parse_error(s, "invalid number literal");
|
|
goto fail;
|
|
}
|
|
s->token.val = TOK_NUMBER;
|
|
s->token.u.num.val = ret;
|
|
}
|
|
break;
|
|
case '*':
|
|
if (p[1] == '=') {
|
|
p += 2;
|
|
s->token.val = TOK_MUL_ASSIGN;
|
|
} else if (p[1] == '*') {
|
|
if (p[2] == '=') {
|
|
p += 3;
|
|
s->token.val = TOK_POW_ASSIGN;
|
|
} else {
|
|
p += 2;
|
|
s->token.val = TOK_POW;
|
|
}
|
|
} else {
|
|
goto def_token;
|
|
}
|
|
break;
|
|
case '%':
|
|
if (p[1] == '=') {
|
|
p += 2;
|
|
s->token.val = TOK_MOD_ASSIGN;
|
|
} else {
|
|
goto def_token;
|
|
}
|
|
break;
|
|
case '+':
|
|
if (p[1] == '=') {
|
|
p += 2;
|
|
s->token.val = TOK_PLUS_ASSIGN;
|
|
} else if (p[1] == '+') {
|
|
p += 2;
|
|
s->token.val = TOK_INC;
|
|
} else {
|
|
goto def_token;
|
|
}
|
|
break;
|
|
case '-':
|
|
if (p[1] == '=') {
|
|
p += 2;
|
|
s->token.val = TOK_MINUS_ASSIGN;
|
|
} else if (p[1] == '-') {
|
|
if (s->allow_html_comments && p[2] == '>' &&
|
|
(s->got_lf || s->last_ptr == s->buf_start)) {
|
|
/* Annex B: `-->` at beginning of line is an html comment end.
|
|
It extends to the end of the line.
|
|
*/
|
|
goto skip_line_comment;
|
|
}
|
|
p += 2;
|
|
s->token.val = TOK_DEC;
|
|
} else {
|
|
goto def_token;
|
|
}
|
|
break;
|
|
case '<':
|
|
if (p[1] == '=') {
|
|
p += 2;
|
|
s->token.val = TOK_LTE;
|
|
} else if (p[1] == '<') {
|
|
if (p[2] == '=') {
|
|
p += 3;
|
|
s->token.val = TOK_SHL_ASSIGN;
|
|
} else {
|
|
p += 2;
|
|
s->token.val = TOK_SHL;
|
|
}
|
|
} else if (s->allow_html_comments &&
|
|
p[1] == '!' && p[2] == '-' && p[3] == '-') {
|
|
/* Annex B: handle `<!--` single line html comments */
|
|
goto skip_line_comment;
|
|
} else {
|
|
goto def_token;
|
|
}
|
|
break;
|
|
case '>':
|
|
if (p[1] == '=') {
|
|
p += 2;
|
|
s->token.val = TOK_GTE;
|
|
} else if (p[1] == '>') {
|
|
if (p[2] == '>') {
|
|
if (p[3] == '=') {
|
|
p += 4;
|
|
s->token.val = TOK_SHR_ASSIGN;
|
|
} else {
|
|
p += 3;
|
|
s->token.val = TOK_SHR;
|
|
}
|
|
} else if (p[2] == '=') {
|
|
p += 3;
|
|
s->token.val = TOK_SAR_ASSIGN;
|
|
} else {
|
|
p += 2;
|
|
s->token.val = TOK_SAR;
|
|
}
|
|
} else {
|
|
goto def_token;
|
|
}
|
|
break;
|
|
case '=':
|
|
if (p[1] == '=') {
|
|
if (p[2] == '=') {
|
|
p += 3;
|
|
s->token.val = TOK_STRICT_EQ;
|
|
} else {
|
|
p += 2;
|
|
s->token.val = TOK_EQ;
|
|
}
|
|
} else if (p[1] == '>') {
|
|
p += 2;
|
|
s->token.val = TOK_ARROW;
|
|
} else {
|
|
goto def_token;
|
|
}
|
|
break;
|
|
case '!':
|
|
if (p[1] == '=') {
|
|
if (p[2] == '=') {
|
|
p += 3;
|
|
s->token.val = TOK_STRICT_NEQ;
|
|
} else {
|
|
p += 2;
|
|
s->token.val = TOK_NEQ;
|
|
}
|
|
} else {
|
|
goto def_token;
|
|
}
|
|
break;
|
|
case '&':
|
|
if (p[1] == '=') {
|
|
p += 2;
|
|
s->token.val = TOK_AND_ASSIGN;
|
|
} else if (p[1] == '&') {
|
|
if (p[2] == '=') {
|
|
p += 3;
|
|
s->token.val = TOK_LAND_ASSIGN;
|
|
} else {
|
|
p += 2;
|
|
s->token.val = TOK_LAND;
|
|
}
|
|
} else {
|
|
goto def_token;
|
|
}
|
|
break;
|
|
case '^':
|
|
if (p[1] == '=') {
|
|
p += 2;
|
|
s->token.val = TOK_XOR_ASSIGN;
|
|
} else {
|
|
goto def_token;
|
|
}
|
|
break;
|
|
case '|':
|
|
if (p[1] == '=') {
|
|
p += 2;
|
|
s->token.val = TOK_OR_ASSIGN;
|
|
} else if (p[1] == '|') {
|
|
if (p[2] == '=') {
|
|
p += 3;
|
|
s->token.val = TOK_LOR_ASSIGN;
|
|
} else {
|
|
p += 2;
|
|
s->token.val = TOK_LOR;
|
|
}
|
|
} else {
|
|
goto def_token;
|
|
}
|
|
break;
|
|
case '?':
|
|
if (p[1] == '?') {
|
|
if (p[2] == '=') {
|
|
p += 3;
|
|
s->token.val = TOK_DOUBLE_QUESTION_MARK_ASSIGN;
|
|
} else {
|
|
p += 2;
|
|
s->token.val = TOK_DOUBLE_QUESTION_MARK;
|
|
}
|
|
} else if (p[1] == '.' && !(p[2] >= '0' && p[2] <= '9')) {
|
|
p += 2;
|
|
s->token.val = TOK_QUESTION_MARK_DOT;
|
|
} else {
|
|
goto def_token;
|
|
}
|
|
break;
|
|
default:
|
|
if (c >= 0x80) { /* non-ASCII code-point */
|
|
c = utf8_decode(p, &p_next);
|
|
if (p_next == p + 1)
|
|
goto invalid_utf8;
|
|
p = p_next;
|
|
switch(c) {
|
|
case CP_PS:
|
|
case CP_LS:
|
|
/* XXX: should avoid incrementing line_number, but
|
|
needed to handle HTML comments */
|
|
goto line_terminator;
|
|
default:
|
|
if (lre_is_space(c)) {
|
|
s->mark = p;
|
|
goto redo;
|
|
} else if (lre_js_is_ident_first(c)) {
|
|
ident_has_escape = FALSE;
|
|
goto has_ident;
|
|
} else {
|
|
js_parse_error(s, "unexpected character");
|
|
goto fail;
|
|
}
|
|
}
|
|
}
|
|
def_token:
|
|
s->token.val = c;
|
|
p++;
|
|
break;
|
|
}
|
|
s->token.col_num = s->mark - s->eol;
|
|
s->buf_ptr = p;
|
|
|
|
// dump_token(s, &s->token);
|
|
return 0;
|
|
|
|
invalid_utf8:
|
|
js_parse_error(s, "invalid UTF-8 sequence");
|
|
fail:
|
|
s->token.val = TOK_ERROR;
|
|
return -1;
|
|
}
|
|
|
|
static int json_parse_error(JSParseState *s, const uint8_t *curp, const char *msg)
|
|
{
|
|
const uint8_t *p, *line_start;
|
|
int position = curp - s->buf_start;
|
|
int line = 1;
|
|
for (line_start = p = s->buf_start; p < curp; p++) {
|
|
/* column count does not account for TABs nor wide characters */
|
|
if (*p == '\r' || *p == '\n') {
|
|
p += 1 + (p[0] == '\r' && p[1] == '\n');
|
|
line++;
|
|
line_start = p;
|
|
}
|
|
}
|
|
return js_parse_error(s, "%s in JSON at position %d (line %d column %d)",
|
|
msg, position, line, (int)(p - line_start) + 1);
|
|
}
|
|
|
|
static int json_parse_string(JSParseState *s, const uint8_t **pp)
|
|
{
|
|
const uint8_t *p, *p_next;
|
|
int i;
|
|
uint32_t c;
|
|
StringBuffer b_s, *b = &b_s;
|
|
|
|
if (string_buffer_init(s->ctx, b, 32))
|
|
goto fail;
|
|
|
|
p = *pp;
|
|
for(;;) {
|
|
if (p >= s->buf_end) {
|
|
goto end_of_input;
|
|
}
|
|
c = *p++;
|
|
if (c == '"')
|
|
break;
|
|
if (c < 0x20) {
|
|
json_parse_error(s, p - 1, "Bad control character in string literal");
|
|
goto fail;
|
|
}
|
|
if (c == '\\') {
|
|
c = *p++;
|
|
switch(c) {
|
|
case 'b': c = '\b'; break;
|
|
case 'f': c = '\f'; break;
|
|
case 'n': c = '\n'; break;
|
|
case 'r': c = '\r'; break;
|
|
case 't': c = '\t'; break;
|
|
case '\"': break;
|
|
case '\\': break;
|
|
case '/': break; /* for v8 compatibility */
|
|
case 'u':
|
|
c = 0;
|
|
for(i = 0; i < 4; i++) {
|
|
int h = from_hex(*p++);
|
|
if (h < 0) {
|
|
json_parse_error(s, p - 1, "Bad Unicode escape");
|
|
goto fail;
|
|
}
|
|
c = (c << 4) | h;
|
|
}
|
|
break;
|
|
default:
|
|
if (p > s->buf_end)
|
|
goto end_of_input;
|
|
json_parse_error(s, p - 1, "Bad escaped character");
|
|
goto fail;
|
|
}
|
|
} else
|
|
if (c >= 0x80) {
|
|
c = utf8_decode(p - 1, &p_next);
|
|
if (p_next == p) {
|
|
json_parse_error(s, p - 1, "Bad UTF-8 sequence");
|
|
goto fail;
|
|
}
|
|
p = p_next;
|
|
}
|
|
if (string_buffer_putc(b, c))
|
|
goto fail;
|
|
}
|
|
s->token.val = TOK_STRING;
|
|
s->token.u.str.sep = '"';
|
|
s->token.u.str.str = string_buffer_end(b);
|
|
*pp = p;
|
|
return 0;
|
|
|
|
end_of_input:
|
|
js_parse_error(s, "Unexpected end of JSON input");
|
|
fail:
|
|
string_buffer_free(b);
|
|
return -1;
|
|
}
|
|
|
|
static int json_parse_number(JSParseState *s, const uint8_t **pp)
|
|
{
|
|
const uint8_t *p = *pp;
|
|
const uint8_t *p_start = p;
|
|
|
|
if (*p == '+' || *p == '-')
|
|
p++;
|
|
|
|
if (!is_digit(*p))
|
|
return js_parse_error(s, "Unexpected token '%c'", *p_start);
|
|
|
|
if (p[0] == '0' && is_digit(p[1]))
|
|
return json_parse_error(s, p, "Unexpected number");
|
|
|
|
while (is_digit(*p))
|
|
p++;
|
|
|
|
if (*p == '.') {
|
|
p++;
|
|
if (!is_digit(*p))
|
|
return json_parse_error(s, p, "Unterminated fractional number");
|
|
while (is_digit(*p))
|
|
p++;
|
|
}
|
|
if (*p == 'e' || *p == 'E') {
|
|
p++;
|
|
if (*p == '+' || *p == '-')
|
|
p++;
|
|
if (!is_digit(*p))
|
|
return json_parse_error(s, p, "Exponent part is missing a number");
|
|
while (is_digit(*p))
|
|
p++;
|
|
}
|
|
s->token.val = TOK_NUMBER;
|
|
s->token.u.num.val = js_float64(strtod((const char *)p_start, NULL));
|
|
*pp = p;
|
|
return 0;
|
|
}
|
|
|
|
/* 'c' is the first character. Return JS_ATOM_NULL in case of error */
|
|
static JSAtom json_parse_ident(JSParseState *s, const uint8_t **pp, int c)
|
|
{
|
|
const uint8_t *p;
|
|
char ident_buf[128], *buf;
|
|
size_t ident_size, ident_pos;
|
|
JSAtom atom;
|
|
|
|
p = *pp;
|
|
buf = ident_buf;
|
|
ident_size = sizeof(ident_buf);
|
|
ident_pos = 0;
|
|
for(;;) {
|
|
buf[ident_pos++] = c;
|
|
c = *p;
|
|
if (c >= 128 ||
|
|
!((lre_id_continue_table_ascii[c >> 5] >> (c & 31)) & 1))
|
|
break;
|
|
p++;
|
|
if (unlikely(ident_pos >= ident_size - UTF8_CHAR_LEN_MAX)) {
|
|
if (ident_realloc(s->ctx, &buf, &ident_size, ident_buf)) {
|
|
atom = JS_ATOM_NULL;
|
|
goto done;
|
|
}
|
|
}
|
|
}
|
|
/* buf contains pure ASCII */
|
|
atom = JS_NewAtomLen(s->ctx, buf, ident_pos);
|
|
done:
|
|
if (unlikely(buf != ident_buf))
|
|
js_free(s->ctx, buf);
|
|
*pp = p;
|
|
return atom;
|
|
}
|
|
|
|
static __exception int json_next_token(JSParseState *s)
|
|
{
|
|
const uint8_t *p, *p_next;
|
|
int c;
|
|
JSAtom atom;
|
|
|
|
if (js_check_stack_overflow(s->ctx->rt, 1000)) {
|
|
JS_ThrowStackOverflow(s->ctx);
|
|
return -1;
|
|
}
|
|
|
|
free_token(s, &s->token);
|
|
|
|
p = s->last_ptr = s->buf_ptr;
|
|
s->last_line_num = s->token.line_num;
|
|
s->last_col_num = s->token.col_num;
|
|
redo:
|
|
s->token.line_num = s->line_num;
|
|
s->token.col_num = s->col_num;
|
|
s->token.ptr = p;
|
|
c = *p;
|
|
switch(c) {
|
|
case 0:
|
|
if (p >= s->buf_end) {
|
|
s->token.val = TOK_EOF;
|
|
} else {
|
|
goto def_token;
|
|
}
|
|
break;
|
|
case '\'':
|
|
/* JSON does not accept single quoted strings */
|
|
goto def_token;
|
|
case '\"':
|
|
p++;
|
|
if (json_parse_string(s, &p))
|
|
goto fail;
|
|
break;
|
|
case '\r': /* accept DOS and MAC newline sequences */
|
|
if (p[1] == '\n') {
|
|
p++;
|
|
}
|
|
/* fall thru */
|
|
case '\n':
|
|
s->line_num++;
|
|
s->eol = p++;
|
|
s->mark = p;
|
|
goto redo;
|
|
case '\f':
|
|
case '\v':
|
|
/* JSONWhitespace does not match <VT>, nor <FF> */
|
|
goto def_token;
|
|
case ' ':
|
|
case '\t':
|
|
p++;
|
|
s->mark = p;
|
|
goto redo;
|
|
case '/':
|
|
/* JSON does not accept comments */
|
|
goto def_token;
|
|
case 'a': case 'b': case 'c': case 'd':
|
|
case 'e': case 'f': case 'g': case 'h':
|
|
case 'i': case 'j': case 'k': case 'l':
|
|
case 'm': case 'n': case 'o': case 'p':
|
|
case 'q': case 'r': case 's': case 't':
|
|
case 'u': case 'v': case 'w': case 'x':
|
|
case 'y': case 'z':
|
|
case 'A': case 'B': case 'C': case 'D':
|
|
case 'E': case 'F': case 'G': case 'H':
|
|
case 'I': case 'J': case 'K': case 'L':
|
|
case 'M': case 'N': case 'O': case 'P':
|
|
case 'Q': case 'R': case 'S': case 'T':
|
|
case 'U': case 'V': case 'W': case 'X':
|
|
case 'Y': case 'Z':
|
|
case '_':
|
|
case '$':
|
|
/* identifier : only pure ascii characters are accepted */
|
|
p++;
|
|
atom = json_parse_ident(s, &p, c);
|
|
if (atom == JS_ATOM_NULL)
|
|
goto fail;
|
|
s->token.u.ident.atom = atom;
|
|
s->token.u.ident.has_escape = FALSE;
|
|
s->token.u.ident.is_reserved = FALSE;
|
|
s->token.val = TOK_IDENT;
|
|
break;
|
|
case '-':
|
|
if (!is_digit(p[1])) {
|
|
json_parse_error(s, p, "No number after minus sign");
|
|
goto fail;
|
|
}
|
|
goto parse_number;
|
|
case '0':
|
|
if (is_digit(p[1])) {
|
|
json_parse_error(s, p, "Unexpected number");
|
|
goto fail;
|
|
}
|
|
goto parse_number;
|
|
case '1': case '2': case '3': case '4':
|
|
case '5': case '6': case '7': case '8':
|
|
case '9':
|
|
/* number */
|
|
parse_number:
|
|
if (json_parse_number(s, &p))
|
|
goto fail;
|
|
break;
|
|
default:
|
|
if (c >= 0x80) {
|
|
c = utf8_decode(p, &p_next);
|
|
if (p_next == p + 1) {
|
|
js_parse_error(s, "Unexpected token '\\x%02x' in JSON", *p);
|
|
} else {
|
|
if (c > 0xFFFF) {
|
|
c = get_hi_surrogate(c);
|
|
}
|
|
js_parse_error(s, "Unexpected token '\\u%04x' in JSON", c);
|
|
}
|
|
goto fail;
|
|
}
|
|
def_token:
|
|
s->token.val = c;
|
|
p++;
|
|
break;
|
|
}
|
|
s->token.col_num = s->mark - s->eol;
|
|
s->buf_ptr = p;
|
|
|
|
// dump_token(s, &s->token);
|
|
return 0;
|
|
|
|
fail:
|
|
s->token.val = TOK_ERROR;
|
|
return -1;
|
|
}
|
|
|
|
/* only used for ':' and '=>', 'let' or 'function' look-ahead. *pp is
|
|
only set if TOK_IMPORT is returned */
|
|
/* XXX: handle all unicode cases */
|
|
static int simple_next_token(const uint8_t **pp, BOOL no_line_terminator)
|
|
{
|
|
const uint8_t *p;
|
|
uint32_t c;
|
|
|
|
/* skip spaces and comments */
|
|
p = *pp;
|
|
for (;;) {
|
|
switch(c = *p++) {
|
|
case '\r':
|
|
case '\n':
|
|
if (no_line_terminator)
|
|
return '\n';
|
|
continue;
|
|
case ' ':
|
|
case '\t':
|
|
case '\v':
|
|
case '\f':
|
|
continue;
|
|
case '/':
|
|
if (*p == '/') {
|
|
if (no_line_terminator)
|
|
return '\n';
|
|
while (*p && *p != '\r' && *p != '\n')
|
|
p++;
|
|
continue;
|
|
}
|
|
if (*p == '*') {
|
|
while (*++p) {
|
|
if ((*p == '\r' || *p == '\n') && no_line_terminator)
|
|
return '\n';
|
|
if (*p == '*' && p[1] == '/') {
|
|
p += 2;
|
|
break;
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
break;
|
|
case '=':
|
|
if (*p == '>')
|
|
return TOK_ARROW;
|
|
break;
|
|
default:
|
|
if (lre_js_is_ident_first(c)) {
|
|
if (c == 'i') {
|
|
if (p[0] == 'n' && !lre_js_is_ident_next(p[1])) {
|
|
return TOK_IN;
|
|
}
|
|
if (p[0] == 'm' && p[1] == 'p' && p[2] == 'o' &&
|
|
p[3] == 'r' && p[4] == 't' &&
|
|
!lre_js_is_ident_next(p[5])) {
|
|
*pp = p + 5;
|
|
return TOK_IMPORT;
|
|
}
|
|
} else if (c == 'o' && *p == 'f' && !lre_js_is_ident_next(p[1])) {
|
|
return TOK_OF;
|
|
} else if (c == 'e' &&
|
|
p[0] == 'x' && p[1] == 'p' && p[2] == 'o' &&
|
|
p[3] == 'r' && p[4] == 't' &&
|
|
!lre_js_is_ident_next(p[5])) {
|
|
*pp = p + 5;
|
|
return TOK_EXPORT;
|
|
} else if (c == 'f' && p[0] == 'u' && p[1] == 'n' &&
|
|
p[2] == 'c' && p[3] == 't' && p[4] == 'i' &&
|
|
p[5] == 'o' && p[6] == 'n' && !lre_js_is_ident_next(p[7])) {
|
|
return TOK_FUNCTION;
|
|
}
|
|
return TOK_IDENT;
|
|
}
|
|
break;
|
|
}
|
|
return c;
|
|
}
|
|
}
|
|
|
|
static int peek_token(JSParseState *s, BOOL no_line_terminator)
|
|
{
|
|
const uint8_t *p = s->buf_ptr;
|
|
return simple_next_token(&p, no_line_terminator);
|
|
}
|
|
|
|
static void skip_shebang(const uint8_t **pp, const uint8_t *buf_end)
|
|
{
|
|
const uint8_t *p = *pp;
|
|
int c;
|
|
|
|
if (p[0] == '#' && p[1] == '!') {
|
|
p += 2;
|
|
while (p < buf_end) {
|
|
if (*p == '\n' || *p == '\r') {
|
|
break;
|
|
} else if (*p >= 0x80) {
|
|
c = utf8_decode(p, &p);
|
|
/* purposely ignore UTF-8 encoding errors in this comment line */
|
|
if (c == CP_LS || c == CP_PS)
|
|
break;
|
|
} else {
|
|
p++;
|
|
}
|
|
}
|
|
*pp = p;
|
|
}
|
|
}
|
|
|
|
/* return true if 'input' contains the source of a module
|
|
(heuristic). 'input' must be a zero terminated.
|
|
|
|
Heuristic: skip comments and expect 'import' keyword not followed
|
|
by '(' or '.' or export keyword.
|
|
*/
|
|
/* input is pure ASCII or UTF-8 encoded source code */
|
|
BOOL JS_DetectModule(const char *input, size_t input_len)
|
|
{
|
|
const uint8_t *p = (const uint8_t *)input;
|
|
int tok;
|
|
|
|
skip_shebang(&p, p + input_len);
|
|
switch(simple_next_token(&p, FALSE)) {
|
|
case TOK_IMPORT:
|
|
tok = simple_next_token(&p, FALSE);
|
|
return (tok != '.' && tok != '(');
|
|
case TOK_EXPORT:
|
|
return TRUE;
|
|
default:
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
static inline int get_prev_opcode(JSFunctionDef *fd) {
|
|
if (fd->last_opcode_pos < 0)
|
|
return OP_invalid;
|
|
else
|
|
return fd->byte_code.buf[fd->last_opcode_pos];
|
|
}
|
|
|
|
static BOOL js_is_live_code(JSParseState *s) {
|
|
switch (get_prev_opcode(s->cur_func)) {
|
|
case OP_tail_call:
|
|
case OP_tail_call_method:
|
|
case OP_return:
|
|
case OP_return_undef:
|
|
case OP_return_async:
|
|
case OP_throw:
|
|
case OP_throw_error:
|
|
case OP_goto:
|
|
case OP_goto8:
|
|
case OP_goto16:
|
|
case OP_ret:
|
|
return FALSE;
|
|
default:
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
static void emit_u8(JSParseState *s, uint8_t val)
|
|
{
|
|
dbuf_putc(&s->cur_func->byte_code, val);
|
|
}
|
|
|
|
static void emit_u16(JSParseState *s, uint16_t val)
|
|
{
|
|
dbuf_put_u16(&s->cur_func->byte_code, val);
|
|
}
|
|
|
|
static void emit_u32(JSParseState *s, uint32_t val)
|
|
{
|
|
dbuf_put_u32(&s->cur_func->byte_code, val);
|
|
}
|
|
|
|
static void emit_op(JSParseState *s, uint8_t val)
|
|
{
|
|
JSFunctionDef *fd = s->cur_func;
|
|
DynBuf *bc = &fd->byte_code;
|
|
|
|
/* Use the line and column number of the last token used,
|
|
not the next token, nor the current offset in the source file.
|
|
*/
|
|
if (unlikely(fd->last_opcode_line_num != s->last_line_num ||
|
|
fd->last_opcode_col_num != s->last_col_num)) {
|
|
dbuf_putc(bc, OP_source_loc);
|
|
dbuf_put_u32(bc, s->last_line_num);
|
|
dbuf_put_u32(bc, s->last_col_num);
|
|
fd->last_opcode_line_num = s->last_line_num;
|
|
fd->last_opcode_col_num = s->last_col_num;
|
|
}
|
|
fd->last_opcode_pos = bc->size;
|
|
dbuf_putc(bc, val);
|
|
}
|
|
|
|
static void emit_atom(JSParseState *s, JSAtom name)
|
|
{
|
|
emit_u32(s, JS_DupAtom(s->ctx, name));
|
|
}
|
|
|
|
static force_inline uint32_t get_index_hash(JSAtom atom, int hash_bits)
|
|
{
|
|
return (atom * 0x9e370001) >> (32 - hash_bits);
|
|
}
|
|
|
|
static void emit_ic(JSParseState *s, JSAtom atom)
|
|
{
|
|
uint32_t h;
|
|
JSContext *ctx;
|
|
JSInlineCache *ic;
|
|
JSInlineCacheHashSlot *ch;
|
|
|
|
ic = s->cur_func->ic;
|
|
ctx = s->ctx;
|
|
if (ic->count + 1 >= ic->capacity && resize_ic_hash(ctx, ic))
|
|
return;
|
|
h = get_index_hash(atom, ic->hash_bits);
|
|
for (ch = ic->hash[h]; ch != NULL; ch = ch->next)
|
|
if (ch->atom == atom)
|
|
return;
|
|
ch = js_malloc(ctx, sizeof(*ch));
|
|
if (unlikely(!ch))
|
|
return;
|
|
ch->atom = JS_DupAtom(ctx, atom);
|
|
ch->index = 0;
|
|
ch->next = ic->hash[h];
|
|
ic->hash[h] = ch;
|
|
ic->count += 1;
|
|
}
|
|
|
|
static int update_label(JSFunctionDef *s, int label, int delta)
|
|
{
|
|
LabelSlot *ls;
|
|
|
|
assert(label >= 0 && label < s->label_count);
|
|
ls = &s->label_slots[label];
|
|
ls->ref_count += delta;
|
|
assert(ls->ref_count >= 0);
|
|
return ls->ref_count;
|
|
}
|
|
|
|
static int new_label_fd(JSFunctionDef *fd, int label)
|
|
{
|
|
LabelSlot *ls;
|
|
|
|
if (label < 0) {
|
|
if (js_resize_array(fd->ctx, (void *)&fd->label_slots,
|
|
sizeof(fd->label_slots[0]),
|
|
&fd->label_size, fd->label_count + 1))
|
|
return -1;
|
|
label = fd->label_count++;
|
|
ls = &fd->label_slots[label];
|
|
ls->ref_count = 0;
|
|
ls->pos = -1;
|
|
ls->pos2 = -1;
|
|
ls->addr = -1;
|
|
ls->first_reloc = NULL;
|
|
}
|
|
return label;
|
|
}
|
|
|
|
static int new_label(JSParseState *s)
|
|
{
|
|
return new_label_fd(s->cur_func, -1);
|
|
}
|
|
|
|
/* return the label ID offset */
|
|
static int emit_label(JSParseState *s, int label)
|
|
{
|
|
if (label >= 0) {
|
|
emit_op(s, OP_label);
|
|
emit_u32(s, label);
|
|
s->cur_func->label_slots[label].pos = s->cur_func->byte_code.size;
|
|
return s->cur_func->byte_code.size - 4;
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* return label or -1 if dead code */
|
|
static int emit_goto(JSParseState *s, int opcode, int label)
|
|
{
|
|
if (js_is_live_code(s)) {
|
|
if (label < 0)
|
|
label = new_label(s);
|
|
emit_op(s, opcode);
|
|
emit_u32(s, label);
|
|
s->cur_func->label_slots[label].ref_count++;
|
|
return label;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/* return the constant pool index. 'val' is not duplicated. */
|
|
static int cpool_add(JSParseState *s, JSValue val)
|
|
{
|
|
JSFunctionDef *fd = s->cur_func;
|
|
|
|
if (js_resize_array(s->ctx, (void *)&fd->cpool, sizeof(fd->cpool[0]),
|
|
&fd->cpool_size, fd->cpool_count + 1))
|
|
return -1;
|
|
fd->cpool[fd->cpool_count++] = val;
|
|
return fd->cpool_count - 1;
|
|
}
|
|
|
|
static __exception int emit_push_const(JSParseState *s, JSValue val,
|
|
BOOL as_atom)
|
|
{
|
|
int idx;
|
|
|
|
if (JS_VALUE_GET_TAG(val) == JS_TAG_STRING && as_atom) {
|
|
JSAtom atom;
|
|
/* warning: JS_NewAtomStr frees the string value */
|
|
JS_DupValue(s->ctx, val);
|
|
atom = JS_NewAtomStr(s->ctx, JS_VALUE_GET_STRING(val));
|
|
if (atom != JS_ATOM_NULL && !__JS_AtomIsTaggedInt(atom)) {
|
|
emit_op(s, OP_push_atom_value);
|
|
emit_u32(s, atom);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
idx = cpool_add(s, JS_DupValue(s->ctx, val));
|
|
if (idx < 0)
|
|
return -1;
|
|
emit_op(s, OP_push_const);
|
|
emit_u32(s, idx);
|
|
return 0;
|
|
}
|
|
|
|
/* return the variable index or -1 if not found,
|
|
add ARGUMENT_VAR_OFFSET for argument variables */
|
|
static int find_arg(JSContext *ctx, JSFunctionDef *fd, JSAtom name)
|
|
{
|
|
int i;
|
|
for(i = fd->arg_count; i-- > 0;) {
|
|
if (fd->args[i].var_name == name)
|
|
return i | ARGUMENT_VAR_OFFSET;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static int find_var(JSContext *ctx, JSFunctionDef *fd, JSAtom name)
|
|
{
|
|
int i;
|
|
for(i = fd->var_count; i-- > 0;) {
|
|
if (fd->vars[i].var_name == name && fd->vars[i].scope_level == 0)
|
|
return i;
|
|
}
|
|
return find_arg(ctx, fd, name);
|
|
}
|
|
|
|
/* find a variable declaration in a given scope */
|
|
static int find_var_in_scope(JSContext *ctx, JSFunctionDef *fd,
|
|
JSAtom name, int scope_level)
|
|
{
|
|
int scope_idx;
|
|
for(scope_idx = fd->scopes[scope_level].first; scope_idx >= 0;
|
|
scope_idx = fd->vars[scope_idx].scope_next) {
|
|
if (fd->vars[scope_idx].scope_level != scope_level)
|
|
break;
|
|
if (fd->vars[scope_idx].var_name == name)
|
|
return scope_idx;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/* return true if scope == parent_scope or if scope is a child of
|
|
parent_scope */
|
|
static BOOL is_child_scope(JSContext *ctx, JSFunctionDef *fd,
|
|
int scope, int parent_scope)
|
|
{
|
|
while (scope >= 0) {
|
|
if (scope == parent_scope)
|
|
return TRUE;
|
|
scope = fd->scopes[scope].parent;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/* find a 'var' declaration in the same scope or a child scope */
|
|
static int find_var_in_child_scope(JSContext *ctx, JSFunctionDef *fd,
|
|
JSAtom name, int scope_level)
|
|
{
|
|
int i;
|
|
for(i = 0; i < fd->var_count; i++) {
|
|
JSVarDef *vd = &fd->vars[i];
|
|
if (vd->var_name == name && vd->scope_level == 0) {
|
|
if (is_child_scope(ctx, fd, vd->scope_next,
|
|
scope_level))
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
|
|
static JSGlobalVar *find_global_var(JSFunctionDef *fd, JSAtom name)
|
|
{
|
|
int i;
|
|
for(i = 0; i < fd->global_var_count; i++) {
|
|
JSGlobalVar *hf = &fd->global_vars[i];
|
|
if (hf->var_name == name)
|
|
return hf;
|
|
}
|
|
return NULL;
|
|
|
|
}
|
|
|
|
static JSGlobalVar *find_lexical_global_var(JSFunctionDef *fd, JSAtom name)
|
|
{
|
|
JSGlobalVar *hf = find_global_var(fd, name);
|
|
if (hf && hf->is_lexical)
|
|
return hf;
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
static int find_lexical_decl(JSContext *ctx, JSFunctionDef *fd, JSAtom name,
|
|
int scope_idx, BOOL check_catch_var)
|
|
{
|
|
while (scope_idx >= 0) {
|
|
JSVarDef *vd = &fd->vars[scope_idx];
|
|
if (vd->var_name == name &&
|
|
(vd->is_lexical || (vd->var_kind == JS_VAR_CATCH &&
|
|
check_catch_var)))
|
|
return scope_idx;
|
|
scope_idx = vd->scope_next;
|
|
}
|
|
|
|
if (fd->is_eval && fd->eval_type == JS_EVAL_TYPE_GLOBAL) {
|
|
if (find_lexical_global_var(fd, name))
|
|
return GLOBAL_VAR_OFFSET;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static int push_scope(JSParseState *s) {
|
|
if (s->cur_func) {
|
|
JSFunctionDef *fd = s->cur_func;
|
|
int scope = fd->scope_count;
|
|
/* XXX: should check for scope overflow */
|
|
if ((fd->scope_count + 1) > fd->scope_size) {
|
|
int new_size;
|
|
size_t slack;
|
|
JSVarScope *new_buf;
|
|
/* XXX: potential arithmetic overflow */
|
|
new_size = max_int(fd->scope_count + 1, fd->scope_size * 3 / 2);
|
|
if (fd->scopes == fd->def_scope_array) {
|
|
new_buf = js_realloc2(s->ctx, NULL, new_size * sizeof(*fd->scopes), &slack);
|
|
if (!new_buf)
|
|
return -1;
|
|
memcpy(new_buf, fd->scopes, fd->scope_count * sizeof(*fd->scopes));
|
|
} else {
|
|
new_buf = js_realloc2(s->ctx, fd->scopes, new_size * sizeof(*fd->scopes), &slack);
|
|
if (!new_buf)
|
|
return -1;
|
|
}
|
|
new_size += slack / sizeof(*new_buf);
|
|
fd->scopes = new_buf;
|
|
fd->scope_size = new_size;
|
|
}
|
|
fd->scope_count++;
|
|
fd->scopes[scope].parent = fd->scope_level;
|
|
fd->scopes[scope].first = fd->scope_first;
|
|
emit_op(s, OP_enter_scope);
|
|
emit_u16(s, scope);
|
|
return fd->scope_level = scope;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int get_first_lexical_var(JSFunctionDef *fd, int scope)
|
|
{
|
|
while (scope >= 0) {
|
|
int scope_idx = fd->scopes[scope].first;
|
|
if (scope_idx >= 0)
|
|
return scope_idx;
|
|
scope = fd->scopes[scope].parent;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static void pop_scope(JSParseState *s) {
|
|
if (s->cur_func) {
|
|
/* disable scoped variables */
|
|
JSFunctionDef *fd = s->cur_func;
|
|
int scope = fd->scope_level;
|
|
emit_op(s, OP_leave_scope);
|
|
emit_u16(s, scope);
|
|
fd->scope_level = fd->scopes[scope].parent;
|
|
fd->scope_first = get_first_lexical_var(fd, fd->scope_level);
|
|
}
|
|
}
|
|
|
|
static void close_scopes(JSParseState *s, int scope, int scope_stop)
|
|
{
|
|
while (scope > scope_stop) {
|
|
emit_op(s, OP_leave_scope);
|
|
emit_u16(s, scope);
|
|
scope = s->cur_func->scopes[scope].parent;
|
|
}
|
|
}
|
|
|
|
/* return the variable index or -1 if error */
|
|
static int add_var(JSContext *ctx, JSFunctionDef *fd, JSAtom name)
|
|
{
|
|
JSVarDef *vd;
|
|
|
|
/* the local variable indexes are currently stored on 16 bits */
|
|
if (fd->var_count >= JS_MAX_LOCAL_VARS) {
|
|
// XXX: add_var() should take JSParseState *s and use js_parse_error
|
|
JS_ThrowSyntaxError(ctx, "too many variables declared (only %d allowed)",
|
|
JS_MAX_LOCAL_VARS - 1);
|
|
return -1;
|
|
}
|
|
if (js_resize_array(ctx, (void **)&fd->vars, sizeof(fd->vars[0]),
|
|
&fd->var_size, fd->var_count + 1))
|
|
return -1;
|
|
vd = &fd->vars[fd->var_count++];
|
|
memset(vd, 0, sizeof(*vd));
|
|
vd->var_name = JS_DupAtom(ctx, name);
|
|
vd->func_pool_idx = -1;
|
|
return fd->var_count - 1;
|
|
}
|
|
|
|
static int add_scope_var(JSContext *ctx, JSFunctionDef *fd, JSAtom name,
|
|
JSVarKindEnum var_kind)
|
|
{
|
|
int idx = add_var(ctx, fd, name);
|
|
if (idx >= 0) {
|
|
JSVarDef *vd = &fd->vars[idx];
|
|
vd->var_kind = var_kind;
|
|
vd->scope_level = fd->scope_level;
|
|
vd->scope_next = fd->scope_first;
|
|
fd->scopes[fd->scope_level].first = idx;
|
|
fd->scope_first = idx;
|
|
}
|
|
return idx;
|
|
}
|
|
|
|
static int add_func_var(JSContext *ctx, JSFunctionDef *fd, JSAtom name)
|
|
{
|
|
int idx = fd->func_var_idx;
|
|
if (idx < 0 && (idx = add_var(ctx, fd, name)) >= 0) {
|
|
fd->func_var_idx = idx;
|
|
fd->vars[idx].var_kind = JS_VAR_FUNCTION_NAME;
|
|
if (fd->js_mode & JS_MODE_STRICT)
|
|
fd->vars[idx].is_const = TRUE;
|
|
}
|
|
return idx;
|
|
}
|
|
|
|
static int add_arguments_var(JSContext *ctx, JSFunctionDef *fd)
|
|
{
|
|
int idx = fd->arguments_var_idx;
|
|
if (idx < 0 && (idx = add_var(ctx, fd, JS_ATOM_arguments)) >= 0) {
|
|
fd->arguments_var_idx = idx;
|
|
}
|
|
return idx;
|
|
}
|
|
|
|
/* add an argument definition in the argument scope. Only needed when
|
|
"eval()" may be called in the argument scope. Return 0 if OK. */
|
|
static int add_arguments_arg(JSContext *ctx, JSFunctionDef *fd)
|
|
{
|
|
int idx;
|
|
if (fd->arguments_arg_idx < 0) {
|
|
idx = find_var_in_scope(ctx, fd, JS_ATOM_arguments, ARG_SCOPE_INDEX);
|
|
if (idx < 0) {
|
|
/* XXX: the scope links are not fully updated. May be an
|
|
issue if there are child scopes of the argument
|
|
scope */
|
|
idx = add_var(ctx, fd, JS_ATOM_arguments);
|
|
if (idx < 0)
|
|
return -1;
|
|
fd->vars[idx].scope_next = fd->scopes[ARG_SCOPE_INDEX].first;
|
|
fd->scopes[ARG_SCOPE_INDEX].first = idx;
|
|
fd->vars[idx].scope_level = ARG_SCOPE_INDEX;
|
|
fd->vars[idx].is_lexical = TRUE;
|
|
|
|
fd->arguments_arg_idx = idx;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int add_arg(JSContext *ctx, JSFunctionDef *fd, JSAtom name)
|
|
{
|
|
JSVarDef *vd;
|
|
|
|
/* the local variable indexes are currently stored on 16 bits */
|
|
if (fd->arg_count >= JS_MAX_LOCAL_VARS) {
|
|
// XXX: add_arg() should take JSParseState *s and use js_parse_error
|
|
JS_ThrowSyntaxError(ctx, "too many parameters in function definition (only %d allowed)",
|
|
JS_MAX_LOCAL_VARS - 1);
|
|
return -1;
|
|
}
|
|
if (js_resize_array(ctx, (void **)&fd->args, sizeof(fd->args[0]),
|
|
&fd->arg_size, fd->arg_count + 1))
|
|
return -1;
|
|
vd = &fd->args[fd->arg_count++];
|
|
memset(vd, 0, sizeof(*vd));
|
|
vd->var_name = JS_DupAtom(ctx, name);
|
|
vd->func_pool_idx = -1;
|
|
return fd->arg_count - 1;
|
|
}
|
|
|
|
/* add a global variable definition */
|
|
static JSGlobalVar *add_global_var(JSContext *ctx, JSFunctionDef *s,
|
|
JSAtom name)
|
|
{
|
|
JSGlobalVar *hf;
|
|
|
|
if (js_resize_array(ctx, (void **)&s->global_vars,
|
|
sizeof(s->global_vars[0]),
|
|
&s->global_var_size, s->global_var_count + 1))
|
|
return NULL;
|
|
hf = &s->global_vars[s->global_var_count++];
|
|
hf->cpool_idx = -1;
|
|
hf->force_init = FALSE;
|
|
hf->is_lexical = FALSE;
|
|
hf->is_const = FALSE;
|
|
hf->scope_level = s->scope_level;
|
|
hf->var_name = JS_DupAtom(ctx, name);
|
|
return hf;
|
|
}
|
|
|
|
typedef enum {
|
|
JS_VAR_DEF_WITH,
|
|
JS_VAR_DEF_LET,
|
|
JS_VAR_DEF_CONST,
|
|
JS_VAR_DEF_FUNCTION_DECL, /* function declaration */
|
|
JS_VAR_DEF_NEW_FUNCTION_DECL, /* async/generator function declaration */
|
|
JS_VAR_DEF_CATCH,
|
|
JS_VAR_DEF_VAR,
|
|
} JSVarDefEnum;
|
|
|
|
static int define_var(JSParseState *s, JSFunctionDef *fd, JSAtom name,
|
|
JSVarDefEnum var_def_type)
|
|
{
|
|
JSContext *ctx = s->ctx;
|
|
JSVarDef *vd;
|
|
int idx;
|
|
|
|
switch (var_def_type) {
|
|
case JS_VAR_DEF_WITH:
|
|
idx = add_scope_var(ctx, fd, name, JS_VAR_NORMAL);
|
|
break;
|
|
|
|
case JS_VAR_DEF_LET:
|
|
case JS_VAR_DEF_CONST:
|
|
case JS_VAR_DEF_FUNCTION_DECL:
|
|
case JS_VAR_DEF_NEW_FUNCTION_DECL:
|
|
idx = find_lexical_decl(ctx, fd, name, fd->scope_first, TRUE);
|
|
if (idx >= 0) {
|
|
if (idx < GLOBAL_VAR_OFFSET) {
|
|
if (fd->vars[idx].scope_level == fd->scope_level) {
|
|
/* same scope: in non strict mode, functions
|
|
can be redefined (annex B.3.3.4). */
|
|
if (!(!(fd->js_mode & JS_MODE_STRICT) &&
|
|
var_def_type == JS_VAR_DEF_FUNCTION_DECL &&
|
|
fd->vars[idx].var_kind == JS_VAR_FUNCTION_DECL)) {
|
|
goto redef_lex_error;
|
|
}
|
|
} else if (fd->vars[idx].var_kind == JS_VAR_CATCH && (fd->vars[idx].scope_level + 2) == fd->scope_level) {
|
|
goto redef_lex_error;
|
|
}
|
|
} else {
|
|
if (fd->scope_level == fd->body_scope) {
|
|
redef_lex_error:
|
|
/* redefining a scoped var in the same scope: error */
|
|
return js_parse_error(s, "invalid redefinition of lexical identifier");
|
|
}
|
|
}
|
|
}
|
|
if (var_def_type != JS_VAR_DEF_FUNCTION_DECL &&
|
|
var_def_type != JS_VAR_DEF_NEW_FUNCTION_DECL &&
|
|
fd->scope_level == fd->body_scope &&
|
|
find_arg(ctx, fd, name) >= 0) {
|
|
/* lexical variable redefines a parameter name */
|
|
return js_parse_error(s, "invalid redefinition of parameter name");
|
|
}
|
|
|
|
if (find_var_in_child_scope(ctx, fd, name, fd->scope_level) >= 0) {
|
|
return js_parse_error(s, "invalid redefinition of a variable");
|
|
}
|
|
|
|
if (fd->is_global_var) {
|
|
JSGlobalVar *hf;
|
|
hf = find_global_var(fd, name);
|
|
if (hf && is_child_scope(ctx, fd, hf->scope_level,
|
|
fd->scope_level)) {
|
|
return js_parse_error(s, "invalid redefinition of global identifier");
|
|
}
|
|
}
|
|
|
|
if (fd->is_eval &&
|
|
(fd->eval_type == JS_EVAL_TYPE_GLOBAL ||
|
|
fd->eval_type == JS_EVAL_TYPE_MODULE) &&
|
|
fd->scope_level == fd->body_scope) {
|
|
JSGlobalVar *hf;
|
|
hf = add_global_var(s->ctx, fd, name);
|
|
if (!hf)
|
|
return -1;
|
|
hf->is_lexical = TRUE;
|
|
hf->is_const = (var_def_type == JS_VAR_DEF_CONST);
|
|
idx = GLOBAL_VAR_OFFSET;
|
|
} else {
|
|
JSVarKindEnum var_kind;
|
|
if (var_def_type == JS_VAR_DEF_FUNCTION_DECL)
|
|
var_kind = JS_VAR_FUNCTION_DECL;
|
|
else if (var_def_type == JS_VAR_DEF_NEW_FUNCTION_DECL)
|
|
var_kind = JS_VAR_NEW_FUNCTION_DECL;
|
|
else
|
|
var_kind = JS_VAR_NORMAL;
|
|
idx = add_scope_var(ctx, fd, name, var_kind);
|
|
if (idx >= 0) {
|
|
vd = &fd->vars[idx];
|
|
vd->is_lexical = 1;
|
|
vd->is_const = (var_def_type == JS_VAR_DEF_CONST);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case JS_VAR_DEF_CATCH:
|
|
idx = add_scope_var(ctx, fd, name, JS_VAR_CATCH);
|
|
break;
|
|
|
|
case JS_VAR_DEF_VAR:
|
|
if (find_lexical_decl(ctx, fd, name, fd->scope_first,
|
|
FALSE) >= 0) {
|
|
invalid_lexical_redefinition:
|
|
/* error to redefine a var that inside a lexical scope */
|
|
return js_parse_error(s, "invalid redefinition of lexical identifier");
|
|
}
|
|
if (fd->is_global_var) {
|
|
JSGlobalVar *hf;
|
|
hf = find_global_var(fd, name);
|
|
if (hf && hf->is_lexical && hf->scope_level == fd->scope_level &&
|
|
fd->eval_type == JS_EVAL_TYPE_MODULE) {
|
|
goto invalid_lexical_redefinition;
|
|
}
|
|
hf = add_global_var(s->ctx, fd, name);
|
|
if (!hf)
|
|
return -1;
|
|
idx = GLOBAL_VAR_OFFSET;
|
|
} else {
|
|
/* if the variable already exists, don't add it again */
|
|
idx = find_var(ctx, fd, name);
|
|
if (idx >= 0)
|
|
break;
|
|
idx = add_var(ctx, fd, name);
|
|
if (idx >= 0) {
|
|
if (name == JS_ATOM_arguments && fd->has_arguments_binding)
|
|
fd->arguments_var_idx = idx;
|
|
fd->vars[idx].scope_next = fd->scope_level;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
return idx;
|
|
}
|
|
|
|
/* add a private field variable in the current scope */
|
|
static int add_private_class_field(JSParseState *s, JSFunctionDef *fd,
|
|
JSAtom name, JSVarKindEnum var_kind, BOOL is_static)
|
|
{
|
|
JSContext *ctx = s->ctx;
|
|
JSVarDef *vd;
|
|
int idx;
|
|
|
|
idx = add_scope_var(ctx, fd, name, var_kind);
|
|
if (idx < 0)
|
|
return idx;
|
|
vd = &fd->vars[idx];
|
|
vd->is_lexical = 1;
|
|
vd->is_const = 1;
|
|
vd->is_static_private = is_static;
|
|
return idx;
|
|
}
|
|
|
|
static __exception int js_parse_expr(JSParseState *s);
|
|
static __exception int js_parse_function_decl(JSParseState *s,
|
|
JSParseFunctionEnum func_type,
|
|
JSFunctionKindEnum func_kind,
|
|
JSAtom func_name, const uint8_t *ptr,
|
|
int start_line, int start_col);
|
|
static JSFunctionDef *js_parse_function_class_fields_init(JSParseState *s);
|
|
static __exception int js_parse_function_decl2(JSParseState *s,
|
|
JSParseFunctionEnum func_type,
|
|
JSFunctionKindEnum func_kind,
|
|
JSAtom func_name,
|
|
const uint8_t *ptr,
|
|
int function_line_num,
|
|
int function_col_num,
|
|
JSParseExportEnum export_flag,
|
|
JSFunctionDef **pfd);
|
|
static __exception int js_parse_assign_expr2(JSParseState *s, int parse_flags);
|
|
static __exception int js_parse_assign_expr(JSParseState *s);
|
|
static __exception int js_parse_unary(JSParseState *s, int parse_flags);
|
|
static void push_break_entry(JSFunctionDef *fd, BlockEnv *be,
|
|
JSAtom label_name,
|
|
int label_break, int label_cont,
|
|
int drop_count);
|
|
static void pop_break_entry(JSFunctionDef *fd);
|
|
static JSExportEntry *add_export_entry(JSParseState *s, JSModuleDef *m,
|
|
JSAtom local_name, JSAtom export_name,
|
|
JSExportTypeEnum export_type);
|
|
|
|
/* Note: all the fields are already sealed except length */
|
|
static int seal_template_obj(JSContext *ctx, JSValue obj)
|
|
{
|
|
JSObject *p;
|
|
JSShapeProperty *prs;
|
|
|
|
p = JS_VALUE_GET_OBJ(obj);
|
|
prs = find_own_property1(p, JS_ATOM_length);
|
|
if (prs) {
|
|
if (js_update_property_flags(ctx, p, &prs,
|
|
prs->flags & ~(JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE)))
|
|
return -1;
|
|
}
|
|
p->extensible = FALSE;
|
|
return 0;
|
|
}
|
|
|
|
static __exception int js_parse_template(JSParseState *s, int call, int *argc)
|
|
{
|
|
JSContext *ctx = s->ctx;
|
|
JSValue raw_array, template_object;
|
|
JSToken cooked;
|
|
int depth, ret;
|
|
|
|
raw_array = JS_UNDEFINED; /* avoid warning */
|
|
template_object = JS_UNDEFINED; /* avoid warning */
|
|
if (call) {
|
|
/* Create a template object: an array of cooked strings */
|
|
/* Create an array of raw strings and store it to the raw property */
|
|
template_object = JS_NewArray(ctx);
|
|
if (JS_IsException(template_object))
|
|
return -1;
|
|
// pool_idx = s->cur_func->cpool_count;
|
|
ret = emit_push_const(s, template_object, 0);
|
|
JS_FreeValue(ctx, template_object);
|
|
if (ret)
|
|
return -1;
|
|
raw_array = JS_NewArray(ctx);
|
|
if (JS_IsException(raw_array))
|
|
return -1;
|
|
if (JS_DefinePropertyValue(ctx, template_object, JS_ATOM_raw,
|
|
raw_array, JS_PROP_THROW) < 0) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
depth = 0;
|
|
while (s->token.val == TOK_TEMPLATE) {
|
|
const uint8_t *p = s->token.ptr + 1;
|
|
cooked = s->token;
|
|
if (call) {
|
|
if (JS_DefinePropertyValueUint32(ctx, raw_array, depth,
|
|
js_dup(s->token.u.str.str),
|
|
JS_PROP_ENUMERABLE | JS_PROP_THROW) < 0) {
|
|
return -1;
|
|
}
|
|
/* re-parse the string with escape sequences but do not throw a
|
|
syntax error if it contains invalid sequences
|
|
*/
|
|
if (js_parse_string(s, '`', FALSE, p, &cooked, &p)) {
|
|
cooked.u.str.str = JS_UNDEFINED;
|
|
}
|
|
if (JS_DefinePropertyValueUint32(ctx, template_object, depth,
|
|
cooked.u.str.str,
|
|
JS_PROP_ENUMERABLE | JS_PROP_THROW) < 0) {
|
|
return -1;
|
|
}
|
|
} else {
|
|
JSString *str;
|
|
/* re-parse the string with escape sequences and throw a
|
|
syntax error if it contains invalid sequences
|
|
*/
|
|
JS_FreeValue(ctx, s->token.u.str.str);
|
|
s->token.u.str.str = JS_UNDEFINED;
|
|
if (js_parse_string(s, '`', TRUE, p, &cooked, &p))
|
|
return -1;
|
|
str = JS_VALUE_GET_STRING(cooked.u.str.str);
|
|
if (str->len != 0 || depth == 0) {
|
|
ret = emit_push_const(s, cooked.u.str.str, 1);
|
|
JS_FreeValue(s->ctx, cooked.u.str.str);
|
|
if (ret)
|
|
return -1;
|
|
if (depth == 0) {
|
|
if (s->token.u.str.sep == '`')
|
|
goto done1;
|
|
emit_op(s, OP_get_field2);
|
|
emit_atom(s, JS_ATOM_concat);
|
|
emit_ic(s, JS_ATOM_concat);
|
|
}
|
|
depth++;
|
|
} else {
|
|
JS_FreeValue(s->ctx, cooked.u.str.str);
|
|
}
|
|
}
|
|
if (s->token.u.str.sep == '`')
|
|
goto done;
|
|
if (next_token(s))
|
|
return -1;
|
|
if (js_parse_expr(s))
|
|
return -1;
|
|
depth++;
|
|
if (s->token.val != '}') {
|
|
return js_parse_error(s, "expected '}' after template expression");
|
|
}
|
|
/* XXX: should convert to string at this stage? */
|
|
free_token(s, &s->token);
|
|
/* Resume TOK_TEMPLATE parsing (s->token.line_num and
|
|
* s->token.ptr are OK) */
|
|
s->got_lf = FALSE;
|
|
s->last_line_num = s->token.line_num;
|
|
s->last_col_num = s->token.col_num;
|
|
if (js_parse_template_part(s, s->buf_ptr))
|
|
return -1;
|
|
}
|
|
return js_parse_expect(s, TOK_TEMPLATE);
|
|
|
|
done:
|
|
if (call) {
|
|
/* Seal the objects */
|
|
seal_template_obj(ctx, raw_array);
|
|
seal_template_obj(ctx, template_object);
|
|
*argc = depth + 1;
|
|
} else {
|
|
emit_op(s, OP_call_method);
|
|
emit_u16(s, depth - 1);
|
|
}
|
|
done1:
|
|
return next_token(s);
|
|
}
|
|
|
|
|
|
#define PROP_TYPE_IDENT 0
|
|
#define PROP_TYPE_VAR 1
|
|
#define PROP_TYPE_GET 2
|
|
#define PROP_TYPE_SET 3
|
|
#define PROP_TYPE_STAR 4
|
|
#define PROP_TYPE_ASYNC 5
|
|
#define PROP_TYPE_ASYNC_STAR 6
|
|
|
|
#define PROP_TYPE_PRIVATE (1 << 4)
|
|
|
|
static BOOL token_is_ident(int tok)
|
|
{
|
|
/* Accept keywords and reserved words as property names */
|
|
return (tok == TOK_IDENT ||
|
|
(tok >= TOK_FIRST_KEYWORD &&
|
|
tok <= TOK_LAST_KEYWORD));
|
|
}
|
|
|
|
/* if the property is an expression, name = JS_ATOM_NULL */
|
|
static int __exception js_parse_property_name(JSParseState *s,
|
|
JSAtom *pname,
|
|
BOOL allow_method, BOOL allow_var,
|
|
BOOL allow_private)
|
|
{
|
|
int is_private = 0;
|
|
BOOL is_non_reserved_ident;
|
|
JSAtom name;
|
|
int prop_type;
|
|
|
|
prop_type = PROP_TYPE_IDENT;
|
|
if (allow_method) {
|
|
if (token_is_pseudo_keyword(s, JS_ATOM_get)
|
|
|| token_is_pseudo_keyword(s, JS_ATOM_set)) {
|
|
/* get x(), set x() */
|
|
name = JS_DupAtom(s->ctx, s->token.u.ident.atom);
|
|
if (next_token(s))
|
|
goto fail1;
|
|
if (s->token.val == ':' || s->token.val == ',' ||
|
|
s->token.val == '}' || s->token.val == '(' ||
|
|
s->token.val == '=') {
|
|
is_non_reserved_ident = TRUE;
|
|
goto ident_found;
|
|
}
|
|
prop_type = PROP_TYPE_GET + (name == JS_ATOM_set);
|
|
JS_FreeAtom(s->ctx, name);
|
|
} else if (s->token.val == '*') {
|
|
if (next_token(s))
|
|
goto fail;
|
|
prop_type = PROP_TYPE_STAR;
|
|
} else if (token_is_pseudo_keyword(s, JS_ATOM_async) &&
|
|
peek_token(s, TRUE) != '\n') {
|
|
name = JS_DupAtom(s->ctx, s->token.u.ident.atom);
|
|
if (next_token(s))
|
|
goto fail1;
|
|
if (s->token.val == ':' || s->token.val == ',' ||
|
|
s->token.val == '}' || s->token.val == '(' ||
|
|
s->token.val == '=') {
|
|
is_non_reserved_ident = TRUE;
|
|
goto ident_found;
|
|
}
|
|
JS_FreeAtom(s->ctx, name);
|
|
if (s->token.val == '*') {
|
|
if (next_token(s))
|
|
goto fail;
|
|
prop_type = PROP_TYPE_ASYNC_STAR;
|
|
} else {
|
|
prop_type = PROP_TYPE_ASYNC;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (token_is_ident(s->token.val)) {
|
|
/* variable can only be a non-reserved identifier */
|
|
is_non_reserved_ident =
|
|
(s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved);
|
|
/* keywords and reserved words have a valid atom */
|
|
name = JS_DupAtom(s->ctx, s->token.u.ident.atom);
|
|
if (next_token(s))
|
|
goto fail1;
|
|
ident_found:
|
|
if (is_non_reserved_ident &&
|
|
prop_type == PROP_TYPE_IDENT && allow_var) {
|
|
if (!(s->token.val == ':' ||
|
|
(s->token.val == '(' && allow_method))) {
|
|
prop_type = PROP_TYPE_VAR;
|
|
}
|
|
}
|
|
} else if (s->token.val == TOK_STRING) {
|
|
name = JS_ValueToAtom(s->ctx, s->token.u.str.str);
|
|
if (name == JS_ATOM_NULL)
|
|
goto fail;
|
|
if (next_token(s))
|
|
goto fail1;
|
|
} else if (s->token.val == TOK_NUMBER) {
|
|
JSValue val;
|
|
val = s->token.u.num.val;
|
|
name = JS_ValueToAtom(s->ctx, val);
|
|
if (name == JS_ATOM_NULL)
|
|
goto fail;
|
|
if (next_token(s))
|
|
goto fail1;
|
|
} else if (s->token.val == '[') {
|
|
if (next_token(s))
|
|
goto fail;
|
|
if (js_parse_expr(s))
|
|
goto fail;
|
|
if (js_parse_expect(s, ']'))
|
|
goto fail;
|
|
name = JS_ATOM_NULL;
|
|
} else if (s->token.val == TOK_PRIVATE_NAME && allow_private) {
|
|
name = JS_DupAtom(s->ctx, s->token.u.ident.atom);
|
|
if (next_token(s))
|
|
goto fail1;
|
|
is_private = PROP_TYPE_PRIVATE;
|
|
} else {
|
|
goto invalid_prop;
|
|
}
|
|
if (prop_type != PROP_TYPE_IDENT && prop_type != PROP_TYPE_VAR &&
|
|
s->token.val != '(') {
|
|
JS_FreeAtom(s->ctx, name);
|
|
invalid_prop:
|
|
js_parse_error(s, "invalid property name");
|
|
goto fail;
|
|
}
|
|
*pname = name;
|
|
return prop_type | is_private;
|
|
fail1:
|
|
JS_FreeAtom(s->ctx, name);
|
|
fail:
|
|
*pname = JS_ATOM_NULL;
|
|
return -1;
|
|
}
|
|
|
|
typedef struct JSParsePos {
|
|
int last_line_num;
|
|
int last_col_num;
|
|
int line_num;
|
|
int col_num;
|
|
BOOL got_lf;
|
|
const uint8_t *ptr;
|
|
const uint8_t *eol;
|
|
const uint8_t *mark;
|
|
} JSParsePos;
|
|
|
|
static int js_parse_get_pos(JSParseState *s, JSParsePos *sp)
|
|
{
|
|
sp->last_line_num = s->last_line_num;
|
|
sp->last_col_num = s->last_col_num;
|
|
sp->line_num = s->token.line_num;
|
|
sp->col_num = s->token.col_num;
|
|
sp->ptr = s->token.ptr;
|
|
sp->eol = s->eol;
|
|
sp->mark = s->mark;
|
|
sp->got_lf = s->got_lf;
|
|
return 0;
|
|
}
|
|
|
|
static __exception int js_parse_seek_token(JSParseState *s, const JSParsePos *sp)
|
|
{
|
|
s->token.line_num = sp->last_line_num;
|
|
s->token.col_num = sp->last_col_num;
|
|
s->line_num = sp->line_num;
|
|
s->col_num = sp->col_num;
|
|
s->buf_ptr = sp->ptr;
|
|
s->eol = sp->eol;
|
|
s->mark = sp->mark;
|
|
s->got_lf = sp->got_lf;
|
|
return next_token(s);
|
|
}
|
|
|
|
/* return TRUE if a regexp literal is allowed after this token */
|
|
static BOOL is_regexp_allowed(int tok)
|
|
{
|
|
switch (tok) {
|
|
case TOK_NUMBER:
|
|
case TOK_STRING:
|
|
case TOK_REGEXP:
|
|
case TOK_DEC:
|
|
case TOK_INC:
|
|
case TOK_NULL:
|
|
case TOK_FALSE:
|
|
case TOK_TRUE:
|
|
case TOK_THIS:
|
|
case ')':
|
|
case ']':
|
|
case '}': /* XXX: regexp may occur after */
|
|
case TOK_IDENT:
|
|
return FALSE;
|
|
default:
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
#define SKIP_HAS_SEMI (1 << 0)
|
|
#define SKIP_HAS_ELLIPSIS (1 << 1)
|
|
#define SKIP_HAS_ASSIGNMENT (1 << 2)
|
|
|
|
/* XXX: improve speed with early bailout */
|
|
/* XXX: no longer works if regexps are present. Could use previous
|
|
regexp parsing heuristics to handle most cases */
|
|
static int js_parse_skip_parens_token(JSParseState *s, int *pbits, BOOL no_line_terminator)
|
|
{
|
|
char state[256];
|
|
size_t level = 0;
|
|
JSParsePos pos;
|
|
int last_tok, tok = TOK_EOF;
|
|
int c, tok_len, bits = 0;
|
|
|
|
/* protect from underflow */
|
|
state[level++] = 0;
|
|
|
|
js_parse_get_pos(s, &pos);
|
|
last_tok = 0;
|
|
for (;;) {
|
|
switch(s->token.val) {
|
|
case '(':
|
|
case '[':
|
|
case '{':
|
|
if (level >= sizeof(state))
|
|
goto done;
|
|
state[level++] = s->token.val;
|
|
break;
|
|
case ')':
|
|
if (state[--level] != '(')
|
|
goto done;
|
|
break;
|
|
case ']':
|
|
if (state[--level] != '[')
|
|
goto done;
|
|
break;
|
|
case '}':
|
|
c = state[--level];
|
|
if (c == '`') {
|
|
/* continue the parsing of the template */
|
|
free_token(s, &s->token);
|
|
/* Resume TOK_TEMPLATE parsing (s->token.line_num and
|
|
* s->token.ptr are OK) */
|
|
s->got_lf = FALSE;
|
|
s->last_line_num = s->token.line_num;
|
|
s->last_col_num = s->token.col_num;
|
|
if (js_parse_template_part(s, s->buf_ptr))
|
|
goto done;
|
|
goto handle_template;
|
|
} else if (c != '{') {
|
|
goto done;
|
|
}
|
|
break;
|
|
case TOK_TEMPLATE:
|
|
handle_template:
|
|
if (s->token.u.str.sep != '`') {
|
|
/* '${' inside the template : closing '}' and continue
|
|
parsing the template */
|
|
if (level >= sizeof(state))
|
|
goto done;
|
|
state[level++] = '`';
|
|
}
|
|
break;
|
|
case TOK_EOF:
|
|
goto done;
|
|
case ';':
|
|
if (level == 2) {
|
|
bits |= SKIP_HAS_SEMI;
|
|
}
|
|
break;
|
|
case TOK_ELLIPSIS:
|
|
if (level == 2) {
|
|
bits |= SKIP_HAS_ELLIPSIS;
|
|
}
|
|
break;
|
|
case '=':
|
|
bits |= SKIP_HAS_ASSIGNMENT;
|
|
break;
|
|
|
|
case TOK_DIV_ASSIGN:
|
|
tok_len = 2;
|
|
goto parse_regexp;
|
|
case '/':
|
|
tok_len = 1;
|
|
parse_regexp:
|
|
if (is_regexp_allowed(last_tok)) {
|
|
s->buf_ptr -= tok_len;
|
|
if (js_parse_regexp(s)) {
|
|
/* XXX: should clear the exception */
|
|
goto done;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
/* last_tok is only used to recognize regexps */
|
|
if (s->token.val == TOK_IDENT &&
|
|
(token_is_pseudo_keyword(s, JS_ATOM_of) ||
|
|
token_is_pseudo_keyword(s, JS_ATOM_yield))) {
|
|
last_tok = TOK_OF;
|
|
} else {
|
|
last_tok = s->token.val;
|
|
}
|
|
if (next_token(s)) {
|
|
/* XXX: should clear the exception generated by next_token() */
|
|
break;
|
|
}
|
|
if (level <= 1) {
|
|
tok = s->token.val;
|
|
if (token_is_pseudo_keyword(s, JS_ATOM_of))
|
|
tok = TOK_OF;
|
|
if (no_line_terminator && s->last_line_num != s->token.line_num)
|
|
tok = '\n';
|
|
break;
|
|
}
|
|
}
|
|
done:
|
|
if (pbits) {
|
|
*pbits = bits;
|
|
}
|
|
if (js_parse_seek_token(s, &pos))
|
|
return -1;
|
|
return tok;
|
|
}
|
|
|
|
static void set_object_name(JSParseState *s, JSAtom name)
|
|
{
|
|
JSFunctionDef *fd = s->cur_func;
|
|
int opcode;
|
|
|
|
opcode = get_prev_opcode(fd);
|
|
if (opcode == OP_set_name) {
|
|
/* XXX: should free atom after OP_set_name? */
|
|
fd->byte_code.size = fd->last_opcode_pos;
|
|
fd->last_opcode_pos = -1;
|
|
emit_op(s, OP_set_name);
|
|
emit_atom(s, name);
|
|
} else if (opcode == OP_set_class_name) {
|
|
int define_class_pos;
|
|
JSAtom atom;
|
|
define_class_pos = fd->last_opcode_pos + 1 -
|
|
get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1);
|
|
assert(fd->byte_code.buf[define_class_pos] == OP_define_class);
|
|
/* for consistency we free the previous atom which is
|
|
JS_ATOM_empty_string */
|
|
atom = get_u32(fd->byte_code.buf + define_class_pos + 1);
|
|
JS_FreeAtom(s->ctx, atom);
|
|
put_u32(fd->byte_code.buf + define_class_pos + 1,
|
|
JS_DupAtom(s->ctx, name));
|
|
fd->last_opcode_pos = -1;
|
|
}
|
|
}
|
|
|
|
static void set_object_name_computed(JSParseState *s)
|
|
{
|
|
JSFunctionDef *fd = s->cur_func;
|
|
int opcode;
|
|
|
|
opcode = get_prev_opcode(fd);
|
|
if (opcode == OP_set_name) {
|
|
/* XXX: should free atom after OP_set_name? */
|
|
fd->byte_code.size = fd->last_opcode_pos;
|
|
fd->last_opcode_pos = -1;
|
|
emit_op(s, OP_set_name_computed);
|
|
} else if (opcode == OP_set_class_name) {
|
|
int define_class_pos;
|
|
define_class_pos = fd->last_opcode_pos + 1 -
|
|
get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1);
|
|
assert(fd->byte_code.buf[define_class_pos] == OP_define_class);
|
|
fd->byte_code.buf[define_class_pos] = OP_define_class_computed;
|
|
fd->last_opcode_pos = -1;
|
|
}
|
|
}
|
|
|
|
static __exception int js_parse_object_literal(JSParseState *s)
|
|
{
|
|
JSAtom name = JS_ATOM_NULL;
|
|
const uint8_t *start_ptr;
|
|
int start_line, start_col, prop_type;
|
|
BOOL has_proto;
|
|
|
|
if (next_token(s))
|
|
goto fail;
|
|
/* XXX: add an initial length that will be patched back */
|
|
emit_op(s, OP_object);
|
|
has_proto = FALSE;
|
|
while (s->token.val != '}') {
|
|
/* specific case for getter/setter */
|
|
start_ptr = s->token.ptr;
|
|
start_line = s->token.line_num;
|
|
start_col = s->token.col_num;
|
|
|
|
if (s->token.val == TOK_ELLIPSIS) {
|
|
if (next_token(s))
|
|
return -1;
|
|
if (js_parse_assign_expr(s))
|
|
return -1;
|
|
emit_op(s, OP_null); /* dummy excludeList */
|
|
emit_op(s, OP_copy_data_properties);
|
|
emit_u8(s, 2 | (1 << 2) | (0 << 5));
|
|
emit_op(s, OP_drop); /* pop excludeList */
|
|
emit_op(s, OP_drop); /* pop src object */
|
|
goto next;
|
|
}
|
|
|
|
prop_type = js_parse_property_name(s, &name, TRUE, TRUE, FALSE);
|
|
if (prop_type < 0)
|
|
goto fail;
|
|
|
|
if (prop_type == PROP_TYPE_VAR) {
|
|
/* shortcut for x: x */
|
|
emit_op(s, OP_scope_get_var);
|
|
emit_atom(s, name);
|
|
emit_u16(s, s->cur_func->scope_level);
|
|
emit_op(s, OP_define_field);
|
|
emit_atom(s, name);
|
|
} else if (s->token.val == '(') {
|
|
BOOL is_getset = (prop_type == PROP_TYPE_GET ||
|
|
prop_type == PROP_TYPE_SET);
|
|
JSParseFunctionEnum func_type;
|
|
JSFunctionKindEnum func_kind;
|
|
int op_flags;
|
|
|
|
func_kind = JS_FUNC_NORMAL;
|
|
if (is_getset) {
|
|
func_type = JS_PARSE_FUNC_GETTER + prop_type - PROP_TYPE_GET;
|
|
} else {
|
|
func_type = JS_PARSE_FUNC_METHOD;
|
|
if (prop_type == PROP_TYPE_STAR)
|
|
func_kind = JS_FUNC_GENERATOR;
|
|
else if (prop_type == PROP_TYPE_ASYNC)
|
|
func_kind = JS_FUNC_ASYNC;
|
|
else if (prop_type == PROP_TYPE_ASYNC_STAR)
|
|
func_kind = JS_FUNC_ASYNC_GENERATOR;
|
|
}
|
|
if (js_parse_function_decl(s, func_type, func_kind, JS_ATOM_NULL,
|
|
start_ptr, start_line, start_col))
|
|
goto fail;
|
|
if (name == JS_ATOM_NULL) {
|
|
emit_op(s, OP_define_method_computed);
|
|
} else {
|
|
emit_op(s, OP_define_method);
|
|
emit_atom(s, name);
|
|
}
|
|
if (is_getset) {
|
|
op_flags = OP_DEFINE_METHOD_GETTER +
|
|
prop_type - PROP_TYPE_GET;
|
|
} else {
|
|
op_flags = OP_DEFINE_METHOD_METHOD;
|
|
}
|
|
emit_u8(s, op_flags | OP_DEFINE_METHOD_ENUMERABLE);
|
|
} else {
|
|
if (js_parse_expect(s, ':'))
|
|
goto fail;
|
|
if (js_parse_assign_expr(s))
|
|
goto fail;
|
|
if (name == JS_ATOM_NULL) {
|
|
set_object_name_computed(s);
|
|
emit_op(s, OP_define_array_el);
|
|
emit_op(s, OP_drop);
|
|
} else if (name == JS_ATOM___proto__) {
|
|
if (has_proto) {
|
|
js_parse_error(s, "duplicate __proto__ property name");
|
|
goto fail;
|
|
}
|
|
emit_op(s, OP_set_proto);
|
|
has_proto = TRUE;
|
|
} else {
|
|
set_object_name(s, name);
|
|
emit_op(s, OP_define_field);
|
|
emit_atom(s, name);
|
|
}
|
|
}
|
|
JS_FreeAtom(s->ctx, name);
|
|
next:
|
|
name = JS_ATOM_NULL;
|
|
if (s->token.val != ',')
|
|
break;
|
|
if (next_token(s))
|
|
goto fail;
|
|
}
|
|
if (js_parse_expect(s, '}'))
|
|
goto fail;
|
|
return 0;
|
|
fail:
|
|
JS_FreeAtom(s->ctx, name);
|
|
return -1;
|
|
}
|
|
|
|
/* allow the 'in' binary operator */
|
|
#define PF_IN_ACCEPTED (1 << 0)
|
|
/* allow function calls parsing in js_parse_postfix_expr() */
|
|
#define PF_POSTFIX_CALL (1 << 1)
|
|
/* allow the exponentiation operator in js_parse_unary() */
|
|
#define PF_POW_ALLOWED (1 << 2)
|
|
/* forbid the exponentiation operator in js_parse_unary() */
|
|
#define PF_POW_FORBIDDEN (1 << 3)
|
|
|
|
static __exception int js_parse_postfix_expr(JSParseState *s, int parse_flags);
|
|
|
|
static __exception int js_parse_left_hand_side_expr(JSParseState *s)
|
|
{
|
|
return js_parse_postfix_expr(s, PF_POSTFIX_CALL);
|
|
}
|
|
|
|
/* find field in the current scope */
|
|
static int find_private_class_field(JSContext *ctx, JSFunctionDef *fd,
|
|
JSAtom name, int scope_level)
|
|
{
|
|
int idx;
|
|
idx = fd->scopes[scope_level].first;
|
|
while (idx != -1) {
|
|
if (fd->vars[idx].scope_level != scope_level)
|
|
break;
|
|
if (fd->vars[idx].var_name == name)
|
|
return idx;
|
|
idx = fd->vars[idx].scope_next;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/* initialize the class fields, called by the constructor. Note:
|
|
super() can be called in an arrow function, so <this> and
|
|
<class_fields_init> can be variable references */
|
|
static void emit_class_field_init(JSParseState *s)
|
|
{
|
|
int label_next;
|
|
|
|
emit_op(s, OP_scope_get_var);
|
|
emit_atom(s, JS_ATOM_class_fields_init);
|
|
emit_u16(s, s->cur_func->scope_level);
|
|
|
|
/* no need to call the class field initializer if not defined */
|
|
emit_op(s, OP_dup);
|
|
label_next = emit_goto(s, OP_if_false, -1);
|
|
|
|
emit_op(s, OP_scope_get_var);
|
|
emit_atom(s, JS_ATOM_this);
|
|
emit_u16(s, 0);
|
|
|
|
emit_op(s, OP_swap);
|
|
|
|
emit_op(s, OP_call_method);
|
|
emit_u16(s, 0);
|
|
|
|
emit_label(s, label_next);
|
|
emit_op(s, OP_drop);
|
|
}
|
|
|
|
/* build a private setter function name from the private getter name */
|
|
static JSAtom get_private_setter_name(JSContext *ctx, JSAtom name)
|
|
{
|
|
return js_atom_concat_str(ctx, name, "<set>");
|
|
}
|
|
|
|
typedef struct {
|
|
JSFunctionDef *fields_init_fd;
|
|
int computed_fields_count;
|
|
BOOL has_brand;
|
|
int brand_push_pos;
|
|
} ClassFieldsDef;
|
|
|
|
static __exception int emit_class_init_start(JSParseState *s,
|
|
ClassFieldsDef *cf)
|
|
{
|
|
int label_add_brand;
|
|
|
|
cf->fields_init_fd = js_parse_function_class_fields_init(s);
|
|
if (!cf->fields_init_fd)
|
|
return -1;
|
|
|
|
s->cur_func = cf->fields_init_fd;
|
|
|
|
/* XXX: would be better to add the code only if needed, maybe in a
|
|
later pass */
|
|
emit_op(s, OP_push_false); /* will be patched later */
|
|
cf->brand_push_pos = cf->fields_init_fd->last_opcode_pos;
|
|
label_add_brand = emit_goto(s, OP_if_false, -1);
|
|
|
|
emit_op(s, OP_scope_get_var);
|
|
emit_atom(s, JS_ATOM_this);
|
|
emit_u16(s, 0);
|
|
|
|
emit_op(s, OP_scope_get_var);
|
|
emit_atom(s, JS_ATOM_home_object);
|
|
emit_u16(s, 0);
|
|
|
|
emit_op(s, OP_add_brand);
|
|
|
|
emit_label(s, label_add_brand);
|
|
|
|
s->cur_func = s->cur_func->parent;
|
|
return 0;
|
|
}
|
|
|
|
static __exception int add_brand(JSParseState *s, ClassFieldsDef *cf)
|
|
{
|
|
if (!cf->has_brand) {
|
|
/* define the brand field in 'this' of the initializer */
|
|
if (!cf->fields_init_fd) {
|
|
if (emit_class_init_start(s, cf))
|
|
return -1;
|
|
}
|
|
/* patch the start of the function to enable the OP_add_brand code */
|
|
cf->fields_init_fd->byte_code.buf[cf->brand_push_pos] = OP_push_true;
|
|
|
|
cf->has_brand = TRUE;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void emit_class_init_end(JSParseState *s, ClassFieldsDef *cf)
|
|
{
|
|
int cpool_idx;
|
|
|
|
s->cur_func = cf->fields_init_fd;
|
|
emit_op(s, OP_return_undef);
|
|
s->cur_func = s->cur_func->parent;
|
|
|
|
cpool_idx = cpool_add(s, JS_NULL);
|
|
cf->fields_init_fd->parent_cpool_idx = cpool_idx;
|
|
emit_op(s, OP_fclosure);
|
|
emit_u32(s, cpool_idx);
|
|
emit_op(s, OP_set_home_object);
|
|
}
|
|
|
|
static void emit_return(JSParseState *s, BOOL hasval);
|
|
|
|
static JSFunctionDef *js_new_function_def(JSContext *ctx,
|
|
JSFunctionDef *parent,
|
|
BOOL is_eval,
|
|
BOOL is_func_expr,
|
|
const char *filename,
|
|
int line_num,
|
|
int col_num);
|
|
|
|
static __exception int js_parse_class_default_ctor(JSParseState *s,
|
|
BOOL has_super,
|
|
JSFunctionDef **pfd)
|
|
{
|
|
JSParseFunctionEnum func_type;
|
|
JSFunctionDef *fd = s->cur_func;
|
|
|
|
fd = js_new_function_def(s->ctx, fd, FALSE, FALSE, s->filename,
|
|
s->token.line_num, s->token.col_num);
|
|
if (!fd)
|
|
return -1;
|
|
|
|
s->cur_func = fd;
|
|
fd->has_home_object = TRUE;
|
|
fd->super_allowed = TRUE;
|
|
fd->has_prototype = FALSE;
|
|
fd->has_this_binding = TRUE;
|
|
fd->new_target_allowed = TRUE;
|
|
|
|
/* error if not invoked as a constructor */
|
|
emit_op(s, OP_check_ctor);
|
|
|
|
push_scope(s); /* enter body scope */
|
|
fd->body_scope = fd->scope_level;
|
|
if (has_super) {
|
|
fd->is_derived_class_constructor = TRUE;
|
|
fd->super_call_allowed = TRUE;
|
|
fd->arguments_allowed = TRUE;
|
|
fd->has_arguments_binding = TRUE;
|
|
|
|
func_type = JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR;
|
|
/* super */
|
|
emit_op(s, OP_scope_get_var);
|
|
emit_atom(s, JS_ATOM_this_active_func);
|
|
emit_u16(s, 0);
|
|
|
|
emit_op(s, OP_get_super);
|
|
|
|
emit_op(s, OP_scope_get_var);
|
|
emit_atom(s, JS_ATOM_new_target);
|
|
emit_u16(s, 0);
|
|
|
|
emit_op(s, OP_array_from);
|
|
emit_u16(s, 0);
|
|
emit_op(s, OP_push_i32);
|
|
emit_u32(s, 0);
|
|
|
|
/* arguments */
|
|
emit_op(s, OP_scope_get_var);
|
|
emit_atom(s, JS_ATOM_arguments);
|
|
emit_u16(s, 0);
|
|
|
|
emit_op(s, OP_append);
|
|
/* drop the index */
|
|
emit_op(s, OP_drop);
|
|
|
|
emit_op(s, OP_apply);
|
|
emit_u16(s, 1);
|
|
/* set the 'this' value */
|
|
emit_op(s, OP_dup);
|
|
emit_op(s, OP_scope_put_var_init);
|
|
emit_atom(s, JS_ATOM_this);
|
|
emit_u16(s, 0);
|
|
emit_class_field_init(s);
|
|
} else {
|
|
func_type = JS_PARSE_FUNC_CLASS_CONSTRUCTOR;
|
|
emit_class_field_init(s);
|
|
}
|
|
|
|
fd->func_kind = JS_FUNC_NORMAL;
|
|
fd->func_type = func_type;
|
|
emit_return(s, FALSE);
|
|
|
|
s->cur_func = fd->parent;
|
|
if (pfd)
|
|
*pfd = fd;
|
|
|
|
int idx;
|
|
/* the real object will be set at the end of the compilation */
|
|
idx = cpool_add(s, JS_NULL);
|
|
fd->parent_cpool_idx = idx;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static __exception int js_parse_class(JSParseState *s, BOOL is_class_expr,
|
|
JSParseExportEnum export_flag)
|
|
{
|
|
JSContext *ctx = s->ctx;
|
|
JSFunctionDef *fd = s->cur_func;
|
|
JSAtom name = JS_ATOM_NULL, class_name = JS_ATOM_NULL, class_name1;
|
|
JSAtom class_var_name = JS_ATOM_NULL;
|
|
JSFunctionDef *method_fd, *ctor_fd;
|
|
int saved_js_mode, class_name_var_idx, prop_type, ctor_cpool_offset;
|
|
int class_flags = 0, i, define_class_offset;
|
|
BOOL is_static, is_private;
|
|
const uint8_t *class_start_ptr = s->token.ptr;
|
|
const uint8_t *start_ptr;
|
|
ClassFieldsDef class_fields[2];
|
|
|
|
/* classes are parsed and executed in strict mode */
|
|
saved_js_mode = fd->js_mode;
|
|
fd->js_mode |= JS_MODE_STRICT;
|
|
if (next_token(s))
|
|
goto fail;
|
|
if (s->token.val == TOK_IDENT) {
|
|
if (s->token.u.ident.is_reserved) {
|
|
js_parse_error_reserved_identifier(s);
|
|
goto fail;
|
|
}
|
|
class_name = JS_DupAtom(ctx, s->token.u.ident.atom);
|
|
if (next_token(s))
|
|
goto fail;
|
|
} else if (!is_class_expr && export_flag != JS_PARSE_EXPORT_DEFAULT) {
|
|
js_parse_error(s, "class statement requires a name");
|
|
goto fail;
|
|
}
|
|
if (!is_class_expr) {
|
|
if (class_name == JS_ATOM_NULL)
|
|
class_var_name = JS_ATOM__default_; /* export default */
|
|
else
|
|
class_var_name = class_name;
|
|
class_var_name = JS_DupAtom(ctx, class_var_name);
|
|
}
|
|
|
|
push_scope(s);
|
|
|
|
if (s->token.val == TOK_EXTENDS) {
|
|
class_flags = JS_DEFINE_CLASS_HAS_HERITAGE;
|
|
if (next_token(s))
|
|
goto fail;
|
|
if (js_parse_left_hand_side_expr(s))
|
|
goto fail;
|
|
} else {
|
|
emit_op(s, OP_undefined);
|
|
}
|
|
|
|
/* add a 'const' definition for the class name */
|
|
if (class_name != JS_ATOM_NULL) {
|
|
class_name_var_idx = define_var(s, fd, class_name, JS_VAR_DEF_CONST);
|
|
if (class_name_var_idx < 0)
|
|
goto fail;
|
|
}
|
|
|
|
if (js_parse_expect(s, '{'))
|
|
goto fail;
|
|
|
|
/* this scope contains the private fields */
|
|
push_scope(s);
|
|
|
|
emit_op(s, OP_push_const);
|
|
ctor_cpool_offset = fd->byte_code.size;
|
|
emit_u32(s, 0); /* will be patched at the end of the class parsing */
|
|
|
|
if (class_name == JS_ATOM_NULL) {
|
|
if (class_var_name != JS_ATOM_NULL)
|
|
class_name1 = JS_ATOM_default;
|
|
else
|
|
class_name1 = JS_ATOM_empty_string;
|
|
} else {
|
|
class_name1 = class_name;
|
|
}
|
|
|
|
emit_op(s, OP_define_class);
|
|
emit_atom(s, class_name1);
|
|
emit_u8(s, class_flags);
|
|
define_class_offset = fd->last_opcode_pos;
|
|
|
|
for(i = 0; i < 2; i++) {
|
|
ClassFieldsDef *cf = &class_fields[i];
|
|
cf->fields_init_fd = NULL;
|
|
cf->computed_fields_count = 0;
|
|
cf->has_brand = FALSE;
|
|
}
|
|
|
|
ctor_fd = NULL;
|
|
while (s->token.val != '}') {
|
|
if (s->token.val == ';') {
|
|
if (next_token(s))
|
|
goto fail;
|
|
continue;
|
|
}
|
|
is_static = FALSE;
|
|
if (s->token.val == TOK_STATIC) {
|
|
int next = peek_token(s, TRUE);
|
|
if (!(next == ';' || next == '}' || next == '(' || next == '='))
|
|
is_static = TRUE;
|
|
}
|
|
prop_type = -1;
|
|
if (is_static) {
|
|
if (next_token(s))
|
|
goto fail;
|
|
if (s->token.val == '{') {
|
|
ClassFieldsDef *cf = &class_fields[is_static];
|
|
if (!cf->fields_init_fd)
|
|
if (emit_class_init_start(s, cf))
|
|
goto fail;
|
|
s->cur_func = cf->fields_init_fd;
|
|
// stack is now: <empty>
|
|
JSFunctionDef *init;
|
|
if (js_parse_function_decl2(s, JS_PARSE_FUNC_CLASS_STATIC_INIT,
|
|
JS_FUNC_NORMAL, JS_ATOM_NULL,
|
|
s->token.ptr,
|
|
s->token.line_num,
|
|
s->token.col_num,
|
|
JS_PARSE_EXPORT_NONE, &init) < 0) {
|
|
goto fail;
|
|
}
|
|
// stack is now: fclosure
|
|
push_scope(s);
|
|
emit_op(s, OP_scope_get_var);
|
|
emit_atom(s, JS_ATOM_this);
|
|
emit_u16(s, 0);
|
|
// stack is now: fclosure this
|
|
if (class_name != JS_ATOM_NULL) {
|
|
// TODO(bnoordhuis) pass as argument to init method?
|
|
emit_op(s, OP_dup);
|
|
emit_op(s, OP_scope_put_var_init);
|
|
emit_atom(s, class_name);
|
|
emit_u16(s, s->cur_func->scope_level);
|
|
}
|
|
emit_op(s, OP_swap);
|
|
// stack is now: this fclosure
|
|
emit_op(s, OP_call_method);
|
|
emit_u16(s, 0);
|
|
// stack is now: returnvalue
|
|
emit_op(s, OP_drop);
|
|
// stack is now: <empty>
|
|
pop_scope(s);
|
|
s->cur_func = s->cur_func->parent;
|
|
continue;
|
|
}
|
|
/* allow "static" field name */
|
|
if (s->token.val == ';' || s->token.val == '=') {
|
|
is_static = FALSE;
|
|
name = JS_DupAtom(ctx, JS_ATOM_static);
|
|
prop_type = PROP_TYPE_IDENT;
|
|
}
|
|
}
|
|
if (is_static)
|
|
emit_op(s, OP_swap);
|
|
start_ptr = s->token.ptr;
|
|
if (prop_type < 0) {
|
|
prop_type = js_parse_property_name(s, &name, TRUE, FALSE, TRUE);
|
|
if (prop_type < 0)
|
|
goto fail;
|
|
}
|
|
is_private = prop_type & PROP_TYPE_PRIVATE;
|
|
prop_type &= ~PROP_TYPE_PRIVATE;
|
|
|
|
if ((name == JS_ATOM_constructor && !is_static &&
|
|
prop_type != PROP_TYPE_IDENT) ||
|
|
(name == JS_ATOM_prototype && is_static) ||
|
|
name == JS_ATOM_hash_constructor) {
|
|
js_parse_error(s, "invalid method name");
|
|
goto fail;
|
|
}
|
|
if (prop_type == PROP_TYPE_GET || prop_type == PROP_TYPE_SET) {
|
|
BOOL is_set = prop_type - PROP_TYPE_GET;
|
|
JSFunctionDef *method_fd;
|
|
|
|
if (is_private) {
|
|
int idx, var_kind, is_static1;
|
|
idx = find_private_class_field(ctx, fd, name, fd->scope_level);
|
|
if (idx >= 0) {
|
|
var_kind = fd->vars[idx].var_kind;
|
|
is_static1 = fd->vars[idx].is_static_private;
|
|
if (var_kind == JS_VAR_PRIVATE_FIELD ||
|
|
var_kind == JS_VAR_PRIVATE_METHOD ||
|
|
var_kind == JS_VAR_PRIVATE_GETTER_SETTER ||
|
|
var_kind == (JS_VAR_PRIVATE_GETTER + is_set) ||
|
|
(var_kind == (JS_VAR_PRIVATE_GETTER + 1 - is_set) &&
|
|
is_static != is_static1)) {
|
|
goto private_field_already_defined;
|
|
}
|
|
fd->vars[idx].var_kind = JS_VAR_PRIVATE_GETTER_SETTER;
|
|
} else {
|
|
if (add_private_class_field(s, fd, name,
|
|
JS_VAR_PRIVATE_GETTER + is_set, is_static) < 0)
|
|
goto fail;
|
|
}
|
|
if (add_brand(s, &class_fields[is_static]) < 0)
|
|
goto fail;
|
|
}
|
|
|
|
if (js_parse_function_decl2(s, JS_PARSE_FUNC_GETTER + is_set,
|
|
JS_FUNC_NORMAL, JS_ATOM_NULL,
|
|
start_ptr,
|
|
s->token.line_num,
|
|
s->token.col_num,
|
|
JS_PARSE_EXPORT_NONE, &method_fd))
|
|
goto fail;
|
|
if (is_private) {
|
|
method_fd->need_home_object = TRUE; /* needed for brand check */
|
|
emit_op(s, OP_set_home_object);
|
|
/* XXX: missing function name */
|
|
emit_op(s, OP_scope_put_var_init);
|
|
if (is_set) {
|
|
JSAtom setter_name;
|
|
int ret;
|
|
|
|
setter_name = get_private_setter_name(ctx, name);
|
|
if (setter_name == JS_ATOM_NULL)
|
|
goto fail;
|
|
emit_atom(s, setter_name);
|
|
ret = add_private_class_field(s, fd, setter_name,
|
|
JS_VAR_PRIVATE_SETTER, is_static);
|
|
JS_FreeAtom(ctx, setter_name);
|
|
if (ret < 0)
|
|
goto fail;
|
|
} else {
|
|
emit_atom(s, name);
|
|
}
|
|
emit_u16(s, s->cur_func->scope_level);
|
|
} else {
|
|
if (name == JS_ATOM_NULL) {
|
|
emit_op(s, OP_define_method_computed);
|
|
} else {
|
|
emit_op(s, OP_define_method);
|
|
emit_atom(s, name);
|
|
}
|
|
emit_u8(s, OP_DEFINE_METHOD_GETTER + is_set);
|
|
}
|
|
} else if (prop_type == PROP_TYPE_IDENT && s->token.val != '(') {
|
|
ClassFieldsDef *cf = &class_fields[is_static];
|
|
JSAtom field_var_name = JS_ATOM_NULL;
|
|
|
|
/* class field */
|
|
|
|
/* XXX: spec: not consistent with method name checks */
|
|
if (name == JS_ATOM_constructor || name == JS_ATOM_prototype) {
|
|
js_parse_error(s, "invalid field name");
|
|
goto fail;
|
|
}
|
|
|
|
if (is_private) {
|
|
if (find_private_class_field(ctx, fd, name,
|
|
fd->scope_level) >= 0) {
|
|
goto private_field_already_defined;
|
|
}
|
|
if (add_private_class_field(s, fd, name,
|
|
JS_VAR_PRIVATE_FIELD, is_static) < 0)
|
|
goto fail;
|
|
emit_op(s, OP_private_symbol);
|
|
emit_atom(s, name);
|
|
emit_op(s, OP_scope_put_var_init);
|
|
emit_atom(s, name);
|
|
emit_u16(s, s->cur_func->scope_level);
|
|
}
|
|
|
|
if (!cf->fields_init_fd) {
|
|
if (emit_class_init_start(s, cf))
|
|
goto fail;
|
|
}
|
|
if (name == JS_ATOM_NULL ) {
|
|
/* save the computed field name into a variable */
|
|
field_var_name = js_atom_concat_num(ctx, JS_ATOM_computed_field + is_static, cf->computed_fields_count);
|
|
if (field_var_name == JS_ATOM_NULL)
|
|
goto fail;
|
|
if (define_var(s, fd, field_var_name, JS_VAR_DEF_CONST) < 0) {
|
|
JS_FreeAtom(ctx, field_var_name);
|
|
goto fail;
|
|
}
|
|
emit_op(s, OP_to_propkey);
|
|
emit_op(s, OP_scope_put_var_init);
|
|
emit_atom(s, field_var_name);
|
|
emit_u16(s, s->cur_func->scope_level);
|
|
}
|
|
s->cur_func = cf->fields_init_fd;
|
|
emit_op(s, OP_scope_get_var);
|
|
emit_atom(s, JS_ATOM_this);
|
|
emit_u16(s, 0);
|
|
|
|
// expose class name to static initializers
|
|
if (is_static && class_name != JS_ATOM_NULL) {
|
|
emit_op(s, OP_dup);
|
|
emit_op(s, OP_scope_put_var_init);
|
|
emit_atom(s, class_name);
|
|
emit_u16(s, s->cur_func->scope_level);
|
|
}
|
|
|
|
if (name == JS_ATOM_NULL) {
|
|
emit_op(s, OP_scope_get_var);
|
|
emit_atom(s, field_var_name);
|
|
emit_u16(s, s->cur_func->scope_level);
|
|
cf->computed_fields_count++;
|
|
JS_FreeAtom(ctx, field_var_name);
|
|
} else if (is_private) {
|
|
emit_op(s, OP_scope_get_var);
|
|
emit_atom(s, name);
|
|
emit_u16(s, s->cur_func->scope_level);
|
|
}
|
|
|
|
if (s->token.val == '=') {
|
|
if (next_token(s))
|
|
goto fail;
|
|
if (js_parse_assign_expr(s))
|
|
goto fail;
|
|
} else {
|
|
emit_op(s, OP_undefined);
|
|
}
|
|
if (is_private) {
|
|
set_object_name_computed(s);
|
|
emit_op(s, OP_define_private_field);
|
|
} else if (name == JS_ATOM_NULL) {
|
|
set_object_name_computed(s);
|
|
emit_op(s, OP_define_array_el);
|
|
emit_op(s, OP_drop);
|
|
} else {
|
|
set_object_name(s, name);
|
|
emit_op(s, OP_define_field);
|
|
emit_atom(s, name);
|
|
}
|
|
s->cur_func = s->cur_func->parent;
|
|
if (js_parse_expect_semi(s))
|
|
goto fail;
|
|
} else {
|
|
JSParseFunctionEnum func_type;
|
|
JSFunctionKindEnum func_kind;
|
|
|
|
func_type = JS_PARSE_FUNC_METHOD;
|
|
func_kind = JS_FUNC_NORMAL;
|
|
if (prop_type == PROP_TYPE_STAR) {
|
|
func_kind = JS_FUNC_GENERATOR;
|
|
} else if (prop_type == PROP_TYPE_ASYNC) {
|
|
func_kind = JS_FUNC_ASYNC;
|
|
} else if (prop_type == PROP_TYPE_ASYNC_STAR) {
|
|
func_kind = JS_FUNC_ASYNC_GENERATOR;
|
|
} else if (name == JS_ATOM_constructor && !is_static) {
|
|
if (ctor_fd) {
|
|
js_parse_error(s, "property constructor appears more than once");
|
|
goto fail;
|
|
}
|
|
if (class_flags & JS_DEFINE_CLASS_HAS_HERITAGE)
|
|
func_type = JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR;
|
|
else
|
|
func_type = JS_PARSE_FUNC_CLASS_CONSTRUCTOR;
|
|
}
|
|
if (is_private) {
|
|
if (add_brand(s, &class_fields[is_static]) < 0)
|
|
goto fail;
|
|
}
|
|
if (js_parse_function_decl2(s, func_type, func_kind, JS_ATOM_NULL,
|
|
start_ptr,
|
|
s->token.line_num,
|
|
s->token.col_num,
|
|
JS_PARSE_EXPORT_NONE, &method_fd))
|
|
goto fail;
|
|
if (func_type == JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR ||
|
|
func_type == JS_PARSE_FUNC_CLASS_CONSTRUCTOR) {
|
|
ctor_fd = method_fd;
|
|
} else if (is_private) {
|
|
method_fd->need_home_object = TRUE; /* needed for brand check */
|
|
if (find_private_class_field(ctx, fd, name,
|
|
fd->scope_level) >= 0) {
|
|
private_field_already_defined:
|
|
js_parse_error(s, "private class field is already defined");
|
|
goto fail;
|
|
}
|
|
if (add_private_class_field(s, fd, name,
|
|
JS_VAR_PRIVATE_METHOD, is_static) < 0)
|
|
goto fail;
|
|
emit_op(s, OP_set_home_object);
|
|
emit_op(s, OP_set_name);
|
|
emit_atom(s, name);
|
|
emit_op(s, OP_scope_put_var_init);
|
|
emit_atom(s, name);
|
|
emit_u16(s, s->cur_func->scope_level);
|
|
} else {
|
|
if (name == JS_ATOM_NULL) {
|
|
emit_op(s, OP_define_method_computed);
|
|
} else {
|
|
emit_op(s, OP_define_method);
|
|
emit_atom(s, name);
|
|
}
|
|
emit_u8(s, OP_DEFINE_METHOD_METHOD);
|
|
}
|
|
}
|
|
if (is_static)
|
|
emit_op(s, OP_swap);
|
|
JS_FreeAtom(ctx, name);
|
|
name = JS_ATOM_NULL;
|
|
}
|
|
|
|
if (s->token.val != '}') {
|
|
js_parse_error(s, "expecting '%c'", '}');
|
|
goto fail;
|
|
}
|
|
|
|
if (!ctor_fd) {
|
|
if (js_parse_class_default_ctor(s, class_flags & JS_DEFINE_CLASS_HAS_HERITAGE, &ctor_fd))
|
|
goto fail;
|
|
}
|
|
/* patch the constant pool index for the constructor */
|
|
put_u32(fd->byte_code.buf + ctor_cpool_offset, ctor_fd->parent_cpool_idx);
|
|
|
|
/* store the class source code in the constructor. */
|
|
js_free(ctx, ctor_fd->source);
|
|
ctor_fd->source_len = s->buf_ptr - class_start_ptr;
|
|
ctor_fd->source = js_strndup(ctx, (const char *)class_start_ptr, ctor_fd->source_len);
|
|
if (!ctor_fd->source)
|
|
goto fail;
|
|
|
|
/* consume the '}' */
|
|
if (next_token(s))
|
|
goto fail;
|
|
|
|
/* store the function to initialize the fields to that it can be
|
|
referenced by the constructor */
|
|
{
|
|
ClassFieldsDef *cf = &class_fields[0];
|
|
int var_idx;
|
|
|
|
var_idx = define_var(s, fd, JS_ATOM_class_fields_init,
|
|
JS_VAR_DEF_CONST);
|
|
if (var_idx < 0)
|
|
goto fail;
|
|
if (cf->fields_init_fd) {
|
|
emit_class_init_end(s, cf);
|
|
} else {
|
|
emit_op(s, OP_undefined);
|
|
}
|
|
emit_op(s, OP_scope_put_var_init);
|
|
emit_atom(s, JS_ATOM_class_fields_init);
|
|
emit_u16(s, s->cur_func->scope_level);
|
|
}
|
|
|
|
/* drop the prototype */
|
|
emit_op(s, OP_drop);
|
|
|
|
/* initialize the static fields */
|
|
if (class_fields[1].fields_init_fd != NULL) {
|
|
ClassFieldsDef *cf = &class_fields[1];
|
|
emit_op(s, OP_dup);
|
|
emit_class_init_end(s, cf);
|
|
emit_op(s, OP_call_method);
|
|
emit_u16(s, 0);
|
|
emit_op(s, OP_drop);
|
|
}
|
|
|
|
if (class_name != JS_ATOM_NULL) {
|
|
/* store the class name in the scoped class name variable (it
|
|
is independent from the class statement variable
|
|
definition) */
|
|
emit_op(s, OP_dup);
|
|
emit_op(s, OP_scope_put_var_init);
|
|
emit_atom(s, class_name);
|
|
emit_u16(s, fd->scope_level);
|
|
}
|
|
pop_scope(s);
|
|
pop_scope(s);
|
|
|
|
/* the class statements have a block level scope */
|
|
if (class_var_name != JS_ATOM_NULL) {
|
|
if (define_var(s, fd, class_var_name, JS_VAR_DEF_LET) < 0)
|
|
goto fail;
|
|
emit_op(s, OP_scope_put_var_init);
|
|
emit_atom(s, class_var_name);
|
|
emit_u16(s, fd->scope_level);
|
|
} else {
|
|
if (class_name == JS_ATOM_NULL) {
|
|
/* cannot use OP_set_name because the name of the class
|
|
must be defined before the static initializers are
|
|
executed */
|
|
emit_op(s, OP_set_class_name);
|
|
emit_u32(s, fd->last_opcode_pos + 1 - define_class_offset);
|
|
}
|
|
}
|
|
|
|
if (export_flag != JS_PARSE_EXPORT_NONE) {
|
|
if (!add_export_entry(s, fd->module,
|
|
class_var_name,
|
|
export_flag == JS_PARSE_EXPORT_NAMED ? class_var_name : JS_ATOM_default,
|
|
JS_EXPORT_TYPE_LOCAL))
|
|
goto fail;
|
|
}
|
|
|
|
JS_FreeAtom(ctx, class_name);
|
|
JS_FreeAtom(ctx, class_var_name);
|
|
fd->js_mode = saved_js_mode;
|
|
return 0;
|
|
fail:
|
|
JS_FreeAtom(ctx, name);
|
|
JS_FreeAtom(ctx, class_name);
|
|
JS_FreeAtom(ctx, class_var_name);
|
|
fd->js_mode = saved_js_mode;
|
|
return -1;
|
|
}
|
|
|
|
static __exception int js_parse_array_literal(JSParseState *s)
|
|
{
|
|
uint32_t idx;
|
|
BOOL need_length;
|
|
|
|
if (next_token(s))
|
|
return -1;
|
|
/* small regular arrays are created on the stack */
|
|
idx = 0;
|
|
while (s->token.val != ']' && idx < 32) {
|
|
if (s->token.val == ',' || s->token.val == TOK_ELLIPSIS)
|
|
break;
|
|
if (js_parse_assign_expr(s))
|
|
return -1;
|
|
idx++;
|
|
/* accept trailing comma */
|
|
if (s->token.val == ',') {
|
|
if (next_token(s))
|
|
return -1;
|
|
} else
|
|
if (s->token.val != ']')
|
|
goto done;
|
|
}
|
|
emit_op(s, OP_array_from);
|
|
emit_u16(s, idx);
|
|
|
|
/* larger arrays and holes are handled with explicit indices */
|
|
need_length = FALSE;
|
|
while (s->token.val != ']' && idx < 0x7fffffff) {
|
|
if (s->token.val == TOK_ELLIPSIS)
|
|
break;
|
|
need_length = TRUE;
|
|
if (s->token.val != ',') {
|
|
if (js_parse_assign_expr(s))
|
|
return -1;
|
|
emit_op(s, OP_define_field);
|
|
emit_u32(s, __JS_AtomFromUInt32(idx));
|
|
need_length = FALSE;
|
|
}
|
|
idx++;
|
|
/* accept trailing comma */
|
|
if (s->token.val == ',') {
|
|
if (next_token(s))
|
|
return -1;
|
|
}
|
|
}
|
|
if (s->token.val == ']') {
|
|
if (need_length) {
|
|
/* Set the length: Cannot use OP_define_field because
|
|
length is not configurable */
|
|
emit_op(s, OP_dup);
|
|
emit_op(s, OP_push_i32);
|
|
emit_u32(s, idx);
|
|
emit_op(s, OP_put_field);
|
|
emit_atom(s, JS_ATOM_length);
|
|
emit_ic(s, JS_ATOM_length);
|
|
}
|
|
goto done;
|
|
}
|
|
|
|
/* huge arrays and spread elements require a dynamic index on the stack */
|
|
emit_op(s, OP_push_i32);
|
|
emit_u32(s, idx);
|
|
|
|
/* stack has array, index */
|
|
while (s->token.val != ']') {
|
|
if (s->token.val == TOK_ELLIPSIS) {
|
|
if (next_token(s))
|
|
return -1;
|
|
if (js_parse_assign_expr(s))
|
|
return -1;
|
|
emit_op(s, OP_append);
|
|
} else {
|
|
need_length = TRUE;
|
|
if (s->token.val != ',') {
|
|
if (js_parse_assign_expr(s))
|
|
return -1;
|
|
/* a idx val */
|
|
emit_op(s, OP_define_array_el);
|
|
need_length = FALSE;
|
|
}
|
|
emit_op(s, OP_inc);
|
|
}
|
|
if (s->token.val != ',')
|
|
break;
|
|
if (next_token(s))
|
|
return -1;
|
|
}
|
|
if (need_length) {
|
|
/* Set the length: cannot use OP_define_field because
|
|
length is not configurable */
|
|
emit_op(s, OP_dup1); /* array length - array array length */
|
|
emit_op(s, OP_put_field);
|
|
emit_atom(s, JS_ATOM_length);
|
|
emit_ic(s, JS_ATOM_length);
|
|
} else {
|
|
emit_op(s, OP_drop); /* array length - array */
|
|
}
|
|
done:
|
|
return js_parse_expect(s, ']');
|
|
}
|
|
|
|
/* XXX: remove */
|
|
static BOOL has_with_scope(JSFunctionDef *s, int scope_level)
|
|
{
|
|
/* check if scope chain contains a with statement */
|
|
while (s) {
|
|
int scope_idx = s->scopes[scope_level].first;
|
|
while (scope_idx >= 0) {
|
|
JSVarDef *vd = &s->vars[scope_idx];
|
|
|
|
if (vd->var_name == JS_ATOM__with_)
|
|
return TRUE;
|
|
scope_idx = vd->scope_next;
|
|
}
|
|
/* check parent scopes */
|
|
scope_level = s->parent_scope_level;
|
|
s = s->parent;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static __exception int get_lvalue(JSParseState *s, int *popcode, int *pscope,
|
|
JSAtom *pname, int *plabel, int *pdepth, BOOL keep,
|
|
int tok)
|
|
{
|
|
JSFunctionDef *fd;
|
|
int opcode, scope, label, depth;
|
|
JSAtom name;
|
|
|
|
/* we check the last opcode to get the lvalue type */
|
|
fd = s->cur_func;
|
|
scope = 0;
|
|
name = JS_ATOM_NULL;
|
|
label = -1;
|
|
depth = 0;
|
|
switch(opcode = get_prev_opcode(fd)) {
|
|
case OP_scope_get_var:
|
|
name = get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1);
|
|
scope = get_u16(fd->byte_code.buf + fd->last_opcode_pos + 5);
|
|
if ((name == JS_ATOM_arguments || name == JS_ATOM_eval) &&
|
|
(fd->js_mode & JS_MODE_STRICT)) {
|
|
return js_parse_error(s, "invalid lvalue in strict mode");
|
|
}
|
|
if (name == JS_ATOM_this || name == JS_ATOM_new_target)
|
|
goto invalid_lvalue;
|
|
depth = 2; /* will generate OP_get_ref_value */
|
|
break;
|
|
case OP_get_field:
|
|
name = get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1);
|
|
depth = 1;
|
|
break;
|
|
case OP_scope_get_private_field:
|
|
name = get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1);
|
|
scope = get_u16(fd->byte_code.buf + fd->last_opcode_pos + 5);
|
|
depth = 1;
|
|
break;
|
|
case OP_get_array_el:
|
|
depth = 2;
|
|
break;
|
|
case OP_get_super_value:
|
|
depth = 3;
|
|
break;
|
|
default:
|
|
invalid_lvalue:
|
|
if (tok == TOK_FOR) {
|
|
return js_parse_error(s, "invalid for in/of left hand-side");
|
|
} else if (tok == TOK_INC || tok == TOK_DEC) {
|
|
return js_parse_error(s, "invalid increment/decrement operand");
|
|
} else if (tok == '[' || tok == '{') {
|
|
return js_parse_error(s, "invalid destructuring target");
|
|
} else {
|
|
return js_parse_error(s, "invalid assignment left-hand side");
|
|
}
|
|
}
|
|
/* remove the last opcode */
|
|
fd->byte_code.size = fd->last_opcode_pos;
|
|
fd->last_opcode_pos = -1;
|
|
|
|
if (keep) {
|
|
/* get the value but keep the object/fields on the stack */
|
|
switch(opcode) {
|
|
case OP_scope_get_var:
|
|
label = new_label(s);
|
|
emit_op(s, OP_scope_make_ref);
|
|
emit_atom(s, name);
|
|
emit_u32(s, label);
|
|
emit_u16(s, scope);
|
|
update_label(fd, label, 1);
|
|
emit_op(s, OP_get_ref_value);
|
|
opcode = OP_get_ref_value;
|
|
break;
|
|
case OP_get_field:
|
|
emit_op(s, OP_get_field2);
|
|
emit_atom(s, name);
|
|
emit_ic(s, name);
|
|
break;
|
|
case OP_scope_get_private_field:
|
|
emit_op(s, OP_scope_get_private_field2);
|
|
emit_atom(s, name);
|
|
emit_u16(s, scope);
|
|
break;
|
|
case OP_get_array_el:
|
|
/* XXX: replace by a single opcode ? */
|
|
emit_op(s, OP_to_propkey2);
|
|
emit_op(s, OP_dup2);
|
|
emit_op(s, OP_get_array_el);
|
|
break;
|
|
case OP_get_super_value:
|
|
emit_op(s, OP_to_propkey);
|
|
emit_op(s, OP_dup3);
|
|
emit_op(s, OP_get_super_value);
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
} else {
|
|
switch(opcode) {
|
|
case OP_scope_get_var:
|
|
label = new_label(s);
|
|
emit_op(s, OP_scope_make_ref);
|
|
emit_atom(s, name);
|
|
emit_u32(s, label);
|
|
emit_u16(s, scope);
|
|
update_label(fd, label, 1);
|
|
opcode = OP_get_ref_value;
|
|
break;
|
|
case OP_get_array_el:
|
|
emit_op(s, OP_to_propkey2);
|
|
break;
|
|
case OP_get_super_value:
|
|
emit_op(s, OP_to_propkey);
|
|
break;
|
|
}
|
|
}
|
|
|
|
*popcode = opcode;
|
|
*pscope = scope;
|
|
/* name has refcount for OP_get_field and OP_get_ref_value,
|
|
and JS_ATOM_NULL for other opcodes */
|
|
*pname = name;
|
|
*plabel = label;
|
|
if (pdepth)
|
|
*pdepth = depth;
|
|
return 0;
|
|
}
|
|
|
|
typedef enum {
|
|
PUT_LVALUE_NOKEEP, /* [depth] v -> */
|
|
PUT_LVALUE_NOKEEP_DEPTH, /* [depth] v -> , keep depth (currently
|
|
just disable optimizations) */
|
|
PUT_LVALUE_KEEP_TOP, /* [depth] v -> v */
|
|
PUT_LVALUE_KEEP_SECOND, /* [depth] v0 v -> v0 */
|
|
PUT_LVALUE_NOKEEP_BOTTOM, /* v [depth] -> */
|
|
} PutLValueEnum;
|
|
|
|
/* name has a live reference. 'is_let' is only used with opcode =
|
|
OP_scope_get_var which is never generated by get_lvalue(). */
|
|
static void put_lvalue(JSParseState *s, int opcode, int scope,
|
|
JSAtom name, int label, PutLValueEnum special,
|
|
BOOL is_let)
|
|
{
|
|
switch(opcode) {
|
|
case OP_get_field:
|
|
case OP_scope_get_private_field:
|
|
/* depth = 1 */
|
|
switch(special) {
|
|
case PUT_LVALUE_NOKEEP:
|
|
case PUT_LVALUE_NOKEEP_DEPTH:
|
|
break;
|
|
case PUT_LVALUE_KEEP_TOP:
|
|
emit_op(s, OP_insert2); /* obj v -> v obj v */
|
|
break;
|
|
case PUT_LVALUE_KEEP_SECOND:
|
|
emit_op(s, OP_perm3); /* obj v0 v -> v0 obj v */
|
|
break;
|
|
case PUT_LVALUE_NOKEEP_BOTTOM:
|
|
emit_op(s, OP_swap);
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
break;
|
|
case OP_get_array_el:
|
|
case OP_get_ref_value:
|
|
/* depth = 2 */
|
|
if (opcode == OP_get_ref_value) {
|
|
JS_FreeAtom(s->ctx, name);
|
|
emit_label(s, label);
|
|
}
|
|
switch(special) {
|
|
case PUT_LVALUE_NOKEEP:
|
|
emit_op(s, OP_nop); /* will trigger optimization */
|
|
break;
|
|
case PUT_LVALUE_NOKEEP_DEPTH:
|
|
break;
|
|
case PUT_LVALUE_KEEP_TOP:
|
|
emit_op(s, OP_insert3); /* obj prop v -> v obj prop v */
|
|
break;
|
|
case PUT_LVALUE_KEEP_SECOND:
|
|
emit_op(s, OP_perm4); /* obj prop v0 v -> v0 obj prop v */
|
|
break;
|
|
case PUT_LVALUE_NOKEEP_BOTTOM:
|
|
emit_op(s, OP_rot3l);
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
break;
|
|
case OP_get_super_value:
|
|
/* depth = 3 */
|
|
switch(special) {
|
|
case PUT_LVALUE_NOKEEP:
|
|
case PUT_LVALUE_NOKEEP_DEPTH:
|
|
break;
|
|
case PUT_LVALUE_KEEP_TOP:
|
|
emit_op(s, OP_insert4); /* this obj prop v -> v this obj prop v */
|
|
break;
|
|
case PUT_LVALUE_KEEP_SECOND:
|
|
emit_op(s, OP_perm5); /* this obj prop v0 v -> v0 this obj prop v */
|
|
break;
|
|
case PUT_LVALUE_NOKEEP_BOTTOM:
|
|
emit_op(s, OP_rot4l);
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
switch(opcode) {
|
|
case OP_scope_get_var: /* val -- */
|
|
assert(special == PUT_LVALUE_NOKEEP ||
|
|
special == PUT_LVALUE_NOKEEP_DEPTH);
|
|
emit_op(s, is_let ? OP_scope_put_var_init : OP_scope_put_var);
|
|
emit_u32(s, name); /* has refcount */
|
|
emit_u16(s, scope);
|
|
break;
|
|
case OP_get_field:
|
|
emit_op(s, OP_put_field);
|
|
emit_u32(s, name); /* name has refcount */
|
|
emit_ic(s, name);
|
|
break;
|
|
case OP_scope_get_private_field:
|
|
emit_op(s, OP_scope_put_private_field);
|
|
emit_u32(s, name); /* name has refcount */
|
|
emit_u16(s, scope);
|
|
break;
|
|
case OP_get_array_el:
|
|
emit_op(s, OP_put_array_el);
|
|
break;
|
|
case OP_get_ref_value:
|
|
emit_op(s, OP_put_ref_value);
|
|
break;
|
|
case OP_get_super_value:
|
|
emit_op(s, OP_put_super_value);
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
}
|
|
|
|
static __exception int js_parse_expr_paren(JSParseState *s)
|
|
{
|
|
if (js_parse_expect(s, '('))
|
|
return -1;
|
|
if (js_parse_expr(s))
|
|
return -1;
|
|
if (js_parse_expect(s, ')'))
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
static int js_unsupported_keyword(JSParseState *s, JSAtom atom)
|
|
{
|
|
char buf[ATOM_GET_STR_BUF_SIZE];
|
|
return js_parse_error(s, "unsupported keyword: %s",
|
|
JS_AtomGetStr(s->ctx, buf, sizeof(buf), atom));
|
|
}
|
|
|
|
static __exception int js_define_var(JSParseState *s, JSAtom name, int tok)
|
|
{
|
|
JSFunctionDef *fd = s->cur_func;
|
|
JSVarDefEnum var_def_type;
|
|
|
|
if (name == JS_ATOM_yield && fd->func_kind == JS_FUNC_GENERATOR) {
|
|
return js_parse_error(s, "yield is a reserved identifier");
|
|
}
|
|
if ((name == JS_ATOM_arguments || name == JS_ATOM_eval)
|
|
&& (fd->js_mode & JS_MODE_STRICT)) {
|
|
return js_parse_error(s, "invalid variable name in strict mode");
|
|
}
|
|
if ((name == JS_ATOM_let || name == JS_ATOM_undefined)
|
|
&& (tok == TOK_LET || tok == TOK_CONST)) {
|
|
return js_parse_error(s, "invalid lexical variable name");
|
|
}
|
|
switch(tok) {
|
|
case TOK_LET:
|
|
var_def_type = JS_VAR_DEF_LET;
|
|
break;
|
|
case TOK_CONST:
|
|
var_def_type = JS_VAR_DEF_CONST;
|
|
break;
|
|
case TOK_VAR:
|
|
var_def_type = JS_VAR_DEF_VAR;
|
|
break;
|
|
case TOK_CATCH:
|
|
var_def_type = JS_VAR_DEF_CATCH;
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
if (define_var(s, fd, name, var_def_type) < 0)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
static void js_emit_spread_code(JSParseState *s, int depth)
|
|
{
|
|
int label_rest_next, label_rest_done;
|
|
|
|
/* XXX: could check if enum object is an actual array and optimize
|
|
slice extraction. enumeration record and target array are in a
|
|
different order from OP_append case. */
|
|
/* enum_rec xxx -- enum_rec xxx array 0 */
|
|
emit_op(s, OP_array_from);
|
|
emit_u16(s, 0);
|
|
emit_op(s, OP_push_i32);
|
|
emit_u32(s, 0);
|
|
emit_label(s, label_rest_next = new_label(s));
|
|
emit_op(s, OP_for_of_next);
|
|
emit_u8(s, 2 + depth);
|
|
label_rest_done = emit_goto(s, OP_if_true, -1);
|
|
/* array idx val -- array idx */
|
|
emit_op(s, OP_define_array_el);
|
|
emit_op(s, OP_inc);
|
|
emit_goto(s, OP_goto, label_rest_next);
|
|
emit_label(s, label_rest_done);
|
|
/* enum_rec xxx array idx undef -- enum_rec xxx array */
|
|
emit_op(s, OP_drop);
|
|
emit_op(s, OP_drop);
|
|
}
|
|
|
|
static int js_parse_check_duplicate_parameter(JSParseState *s, JSAtom name)
|
|
{
|
|
/* Check for duplicate parameter names */
|
|
JSFunctionDef *fd = s->cur_func;
|
|
int i;
|
|
for (i = 0; i < fd->arg_count; i++) {
|
|
if (fd->args[i].var_name == name)
|
|
goto duplicate;
|
|
}
|
|
for (i = 0; i < fd->var_count; i++) {
|
|
if (fd->vars[i].var_name == name)
|
|
goto duplicate;
|
|
}
|
|
return 0;
|
|
|
|
duplicate:
|
|
return js_parse_error(s, "Duplicate parameter name not allowed in this context");
|
|
}
|
|
|
|
static JSAtom js_parse_destructuring_var(JSParseState *s, int tok, int is_arg)
|
|
{
|
|
JSAtom name;
|
|
|
|
if (!(s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved)
|
|
|| ((s->cur_func->js_mode & JS_MODE_STRICT) &&
|
|
(s->token.u.ident.atom == JS_ATOM_eval || s->token.u.ident.atom == JS_ATOM_arguments))) {
|
|
js_parse_error(s, "invalid destructuring target");
|
|
return JS_ATOM_NULL;
|
|
}
|
|
name = JS_DupAtom(s->ctx, s->token.u.ident.atom);
|
|
if (is_arg && js_parse_check_duplicate_parameter(s, name))
|
|
goto fail;
|
|
if (next_token(s))
|
|
goto fail;
|
|
|
|
return name;
|
|
fail:
|
|
JS_FreeAtom(s->ctx, name);
|
|
return JS_ATOM_NULL;
|
|
}
|
|
|
|
/* Return -1 if error, 0 if no initializer, 1 if an initializer is
|
|
present at the top level. */
|
|
static int js_parse_destructuring_element(JSParseState *s, int tok, int is_arg,
|
|
int hasval, int has_ellipsis,
|
|
BOOL allow_initializer)
|
|
{
|
|
int label_parse, label_assign, label_done, label_lvalue, depth_lvalue;
|
|
int start_addr, assign_addr;
|
|
JSAtom prop_name, var_name;
|
|
int opcode, scope, tok1, skip_bits;
|
|
BOOL has_initializer;
|
|
|
|
label_lvalue = -1;
|
|
|
|
if (has_ellipsis < 0) {
|
|
/* pre-parse destructuration target for spread detection */
|
|
js_parse_skip_parens_token(s, &skip_bits, FALSE);
|
|
has_ellipsis = skip_bits & SKIP_HAS_ELLIPSIS;
|
|
}
|
|
|
|
label_parse = new_label(s);
|
|
label_assign = new_label(s);
|
|
|
|
start_addr = s->cur_func->byte_code.size;
|
|
if (hasval) {
|
|
/* consume value from the stack */
|
|
emit_op(s, OP_dup);
|
|
emit_op(s, OP_undefined);
|
|
emit_op(s, OP_strict_eq);
|
|
emit_goto(s, OP_if_true, label_parse);
|
|
emit_label(s, label_assign);
|
|
} else {
|
|
emit_goto(s, OP_goto, label_parse);
|
|
emit_label(s, label_assign);
|
|
/* leave value on the stack */
|
|
emit_op(s, OP_dup);
|
|
}
|
|
assign_addr = s->cur_func->byte_code.size;
|
|
if (s->token.val == '{') {
|
|
if (next_token(s))
|
|
return -1;
|
|
/* throw an exception if the value cannot be converted to an object */
|
|
emit_op(s, OP_to_object);
|
|
if (has_ellipsis) {
|
|
/* add excludeList on stack just below src object */
|
|
emit_op(s, OP_object);
|
|
emit_op(s, OP_swap);
|
|
}
|
|
while (s->token.val != '}') {
|
|
int prop_type;
|
|
if (s->token.val == TOK_ELLIPSIS) {
|
|
if (!has_ellipsis) {
|
|
JS_ThrowInternalError(s->ctx, "unexpected ellipsis token");
|
|
return -1;
|
|
}
|
|
if (next_token(s))
|
|
return -1;
|
|
if (tok) {
|
|
var_name = js_parse_destructuring_var(s, tok, is_arg);
|
|
if (var_name == JS_ATOM_NULL)
|
|
return -1;
|
|
opcode = OP_scope_get_var;
|
|
scope = s->cur_func->scope_level;
|
|
label_lvalue = -1;
|
|
depth_lvalue = 0;
|
|
} else {
|
|
if (js_parse_left_hand_side_expr(s))
|
|
return -1;
|
|
|
|
if (get_lvalue(s, &opcode, &scope, &var_name,
|
|
&label_lvalue, &depth_lvalue, FALSE, '{'))
|
|
return -1;
|
|
}
|
|
if (s->token.val != '}') {
|
|
js_parse_error(s, "assignment rest property must be last");
|
|
goto var_error;
|
|
}
|
|
emit_op(s, OP_object); /* target */
|
|
emit_op(s, OP_copy_data_properties);
|
|
emit_u8(s, 0 | ((depth_lvalue + 1) << 2) | ((depth_lvalue + 2) << 5));
|
|
goto set_val;
|
|
}
|
|
prop_type = js_parse_property_name(s, &prop_name, FALSE, TRUE, FALSE);
|
|
if (prop_type < 0)
|
|
return -1;
|
|
var_name = JS_ATOM_NULL;
|
|
opcode = OP_scope_get_var;
|
|
scope = s->cur_func->scope_level;
|
|
label_lvalue = -1;
|
|
depth_lvalue = 0;
|
|
if (prop_type == PROP_TYPE_IDENT) {
|
|
if (next_token(s))
|
|
goto prop_error;
|
|
if ((s->token.val == '[' || s->token.val == '{')
|
|
&& ((tok1 = js_parse_skip_parens_token(s, &skip_bits, FALSE)) == ',' ||
|
|
tok1 == '=' || tok1 == '}')) {
|
|
if (prop_name == JS_ATOM_NULL) {
|
|
/* computed property name on stack */
|
|
if (has_ellipsis) {
|
|
/* define the property in excludeList */
|
|
emit_op(s, OP_to_propkey); /* avoid calling ToString twice */
|
|
emit_op(s, OP_perm3); /* TOS: src excludeList prop */
|
|
emit_op(s, OP_null); /* TOS: src excludeList prop null */
|
|
emit_op(s, OP_define_array_el); /* TOS: src excludeList prop */
|
|
emit_op(s, OP_perm3); /* TOS: excludeList src prop */
|
|
}
|
|
/* get the computed property from the source object */
|
|
emit_op(s, OP_get_array_el2);
|
|
} else {
|
|
/* named property */
|
|
if (has_ellipsis) {
|
|
/* define the property in excludeList */
|
|
emit_op(s, OP_swap); /* TOS: src excludeList */
|
|
emit_op(s, OP_null); /* TOS: src excludeList null */
|
|
emit_op(s, OP_define_field); /* TOS: src excludeList */
|
|
emit_atom(s, prop_name);
|
|
emit_op(s, OP_swap); /* TOS: excludeList src */
|
|
}
|
|
/* get the named property from the source object */
|
|
emit_op(s, OP_get_field2);
|
|
emit_u32(s, prop_name);
|
|
emit_ic(s, prop_name);
|
|
}
|
|
if (js_parse_destructuring_element(s, tok, is_arg, TRUE, -1, TRUE) < 0)
|
|
return -1;
|
|
if (s->token.val == '}')
|
|
break;
|
|
/* accept a trailing comma before the '}' */
|
|
if (js_parse_expect(s, ','))
|
|
return -1;
|
|
continue;
|
|
}
|
|
if (prop_name == JS_ATOM_NULL) {
|
|
emit_op(s, OP_to_propkey2);
|
|
if (has_ellipsis) {
|
|
/* define the property in excludeList */
|
|
emit_op(s, OP_perm3);
|
|
emit_op(s, OP_null);
|
|
emit_op(s, OP_define_array_el);
|
|
emit_op(s, OP_perm3);
|
|
}
|
|
/* source prop -- source source prop */
|
|
emit_op(s, OP_dup1);
|
|
} else {
|
|
if (has_ellipsis) {
|
|
/* define the property in excludeList */
|
|
emit_op(s, OP_swap);
|
|
emit_op(s, OP_null);
|
|
emit_op(s, OP_define_field);
|
|
emit_atom(s, prop_name);
|
|
emit_op(s, OP_swap);
|
|
}
|
|
/* source -- source source */
|
|
emit_op(s, OP_dup);
|
|
}
|
|
if (tok) {
|
|
var_name = js_parse_destructuring_var(s, tok, is_arg);
|
|
if (var_name == JS_ATOM_NULL)
|
|
goto prop_error;
|
|
} else {
|
|
if (js_parse_left_hand_side_expr(s))
|
|
goto prop_error;
|
|
lvalue:
|
|
if (get_lvalue(s, &opcode, &scope, &var_name,
|
|
&label_lvalue, &depth_lvalue, FALSE, '{'))
|
|
goto prop_error;
|
|
/* swap ref and lvalue object if any */
|
|
if (prop_name == JS_ATOM_NULL) {
|
|
switch(depth_lvalue) {
|
|
case 1:
|
|
/* source prop x -> x source prop */
|
|
emit_op(s, OP_rot3r);
|
|
break;
|
|
case 2:
|
|
/* source prop x y -> x y source prop */
|
|
emit_op(s, OP_swap2); /* t p2 s p1 */
|
|
break;
|
|
case 3:
|
|
/* source prop x y z -> x y z source prop */
|
|
emit_op(s, OP_rot5l);
|
|
emit_op(s, OP_rot5l);
|
|
break;
|
|
}
|
|
} else {
|
|
switch(depth_lvalue) {
|
|
case 1:
|
|
/* source x -> x source */
|
|
emit_op(s, OP_swap);
|
|
break;
|
|
case 2:
|
|
/* source x y -> x y source */
|
|
emit_op(s, OP_rot3l);
|
|
break;
|
|
case 3:
|
|
/* source x y z -> x y z source */
|
|
emit_op(s, OP_rot4l);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (prop_name == JS_ATOM_NULL) {
|
|
/* computed property name on stack */
|
|
/* XXX: should have OP_get_array_el2x with depth */
|
|
/* source prop -- val */
|
|
emit_op(s, OP_get_array_el);
|
|
} else {
|
|
/* named property */
|
|
/* XXX: should have OP_get_field2x with depth */
|
|
/* source -- val */
|
|
emit_op(s, OP_get_field);
|
|
emit_u32(s, prop_name);
|
|
emit_ic(s, prop_name);
|
|
}
|
|
} else {
|
|
/* prop_type = PROP_TYPE_VAR, cannot be a computed property */
|
|
if (is_arg && js_parse_check_duplicate_parameter(s, prop_name))
|
|
goto prop_error;
|
|
if ((s->cur_func->js_mode & JS_MODE_STRICT) &&
|
|
(prop_name == JS_ATOM_eval || prop_name == JS_ATOM_arguments)) {
|
|
js_parse_error(s, "invalid destructuring target");
|
|
goto prop_error;
|
|
}
|
|
if (has_ellipsis) {
|
|
/* define the property in excludeList */
|
|
emit_op(s, OP_swap);
|
|
emit_op(s, OP_null);
|
|
emit_op(s, OP_define_field);
|
|
emit_atom(s, prop_name);
|
|
emit_op(s, OP_swap);
|
|
}
|
|
if (!tok || tok == TOK_VAR) {
|
|
/* generate reference */
|
|
/* source -- source source */
|
|
emit_op(s, OP_dup);
|
|
emit_op(s, OP_scope_get_var);
|
|
emit_atom(s, prop_name);
|
|
emit_u16(s, s->cur_func->scope_level);
|
|
goto lvalue;
|
|
}
|
|
var_name = JS_DupAtom(s->ctx, prop_name);
|
|
/* source -- source val */
|
|
emit_op(s, OP_get_field2);
|
|
emit_u32(s, prop_name);
|
|
emit_ic(s, prop_name);
|
|
}
|
|
set_val:
|
|
if (tok) {
|
|
if (js_define_var(s, var_name, tok))
|
|
goto var_error;
|
|
scope = s->cur_func->scope_level;
|
|
}
|
|
if (s->token.val == '=') { /* handle optional default value */
|
|
int label_hasval;
|
|
emit_op(s, OP_dup);
|
|
emit_op(s, OP_undefined);
|
|
emit_op(s, OP_strict_eq);
|
|
label_hasval = emit_goto(s, OP_if_false, -1);
|
|
if (next_token(s))
|
|
goto var_error;
|
|
emit_op(s, OP_drop);
|
|
if (js_parse_assign_expr(s))
|
|
goto var_error;
|
|
if (opcode == OP_scope_get_var || opcode == OP_get_ref_value)
|
|
set_object_name(s, var_name);
|
|
emit_label(s, label_hasval);
|
|
}
|
|
/* store value into lvalue object */
|
|
put_lvalue(s, opcode, scope, var_name, label_lvalue,
|
|
PUT_LVALUE_NOKEEP_DEPTH,
|
|
(tok == TOK_CONST || tok == TOK_LET));
|
|
if (s->token.val == '}')
|
|
break;
|
|
/* accept a trailing comma before the '}' */
|
|
if (js_parse_expect(s, ','))
|
|
return -1;
|
|
}
|
|
/* drop the source object */
|
|
emit_op(s, OP_drop);
|
|
if (has_ellipsis) {
|
|
emit_op(s, OP_drop); /* pop excludeList */
|
|
}
|
|
if (next_token(s))
|
|
return -1;
|
|
} else if (s->token.val == '[') {
|
|
BOOL has_spread;
|
|
int enum_depth;
|
|
BlockEnv block_env;
|
|
|
|
if (next_token(s))
|
|
return -1;
|
|
/* the block environment is only needed in generators in case
|
|
'yield' triggers a 'return' */
|
|
push_break_entry(s->cur_func, &block_env,
|
|
JS_ATOM_NULL, -1, -1, 2);
|
|
block_env.has_iterator = TRUE;
|
|
emit_op(s, OP_for_of_start);
|
|
has_spread = FALSE;
|
|
while (s->token.val != ']') {
|
|
/* get the next value */
|
|
if (s->token.val == TOK_ELLIPSIS) {
|
|
if (next_token(s))
|
|
return -1;
|
|
if (s->token.val == ',' || s->token.val == ']')
|
|
return js_parse_error(s, "missing binding pattern...");
|
|
has_spread = TRUE;
|
|
}
|
|
if (s->token.val == ',') {
|
|
/* do nothing, skip the value, has_spread is false */
|
|
emit_op(s, OP_for_of_next);
|
|
emit_u8(s, 0);
|
|
emit_op(s, OP_drop);
|
|
emit_op(s, OP_drop);
|
|
} else if ((s->token.val == '[' || s->token.val == '{')
|
|
&& ((tok1 = js_parse_skip_parens_token(s, &skip_bits, FALSE)) == ',' ||
|
|
tok1 == '=' || tok1 == ']')) {
|
|
if (has_spread) {
|
|
if (tok1 == '=')
|
|
return js_parse_error(s, "rest element cannot have a default value");
|
|
js_emit_spread_code(s, 0);
|
|
} else {
|
|
emit_op(s, OP_for_of_next);
|
|
emit_u8(s, 0);
|
|
emit_op(s, OP_drop);
|
|
}
|
|
if (js_parse_destructuring_element(s, tok, is_arg, TRUE, skip_bits & SKIP_HAS_ELLIPSIS, TRUE) < 0)
|
|
return -1;
|
|
} else {
|
|
var_name = JS_ATOM_NULL;
|
|
enum_depth = 0;
|
|
if (tok) {
|
|
var_name = js_parse_destructuring_var(s, tok, is_arg);
|
|
if (var_name == JS_ATOM_NULL)
|
|
goto var_error;
|
|
if (js_define_var(s, var_name, tok))
|
|
goto var_error;
|
|
opcode = OP_scope_get_var;
|
|
scope = s->cur_func->scope_level;
|
|
} else {
|
|
if (js_parse_left_hand_side_expr(s))
|
|
return -1;
|
|
if (get_lvalue(s, &opcode, &scope, &var_name,
|
|
&label_lvalue, &enum_depth, FALSE, '[')) {
|
|
return -1;
|
|
}
|
|
}
|
|
if (has_spread) {
|
|
js_emit_spread_code(s, enum_depth);
|
|
} else {
|
|
emit_op(s, OP_for_of_next);
|
|
emit_u8(s, enum_depth);
|
|
emit_op(s, OP_drop);
|
|
}
|
|
if (s->token.val == '=' && !has_spread) {
|
|
/* handle optional default value */
|
|
int label_hasval;
|
|
emit_op(s, OP_dup);
|
|
emit_op(s, OP_undefined);
|
|
emit_op(s, OP_strict_eq);
|
|
label_hasval = emit_goto(s, OP_if_false, -1);
|
|
if (next_token(s))
|
|
goto var_error;
|
|
emit_op(s, OP_drop);
|
|
if (js_parse_assign_expr(s))
|
|
goto var_error;
|
|
if (opcode == OP_scope_get_var || opcode == OP_get_ref_value)
|
|
set_object_name(s, var_name);
|
|
emit_label(s, label_hasval);
|
|
}
|
|
/* store value into lvalue object */
|
|
put_lvalue(s, opcode, scope, var_name,
|
|
label_lvalue, PUT_LVALUE_NOKEEP_DEPTH,
|
|
(tok == TOK_CONST || tok == TOK_LET));
|
|
}
|
|
if (s->token.val == ']')
|
|
break;
|
|
if (has_spread)
|
|
return js_parse_error(s, "rest element must be the last one");
|
|
/* accept a trailing comma before the ']' */
|
|
if (js_parse_expect(s, ','))
|
|
return -1;
|
|
}
|
|
/* close iterator object:
|
|
if completed, enum_obj has been replaced by undefined */
|
|
emit_op(s, OP_iterator_close);
|
|
pop_break_entry(s->cur_func);
|
|
if (next_token(s))
|
|
return -1;
|
|
} else {
|
|
return js_parse_error(s, "invalid assignment syntax");
|
|
}
|
|
if (s->token.val == '=' && allow_initializer) {
|
|
label_done = emit_goto(s, OP_goto, -1);
|
|
if (next_token(s))
|
|
return -1;
|
|
emit_label(s, label_parse);
|
|
if (hasval)
|
|
emit_op(s, OP_drop);
|
|
if (js_parse_assign_expr(s))
|
|
return -1;
|
|
emit_goto(s, OP_goto, label_assign);
|
|
emit_label(s, label_done);
|
|
has_initializer = TRUE;
|
|
} else {
|
|
/* normally hasval is true except if
|
|
js_parse_skip_parens_token() was wrong in the parsing */
|
|
// assert(hasval);
|
|
if (!hasval) {
|
|
js_parse_error(s, "too complicated destructuring expression");
|
|
return -1;
|
|
}
|
|
/* remove test and decrement label ref count */
|
|
memset(s->cur_func->byte_code.buf + start_addr, OP_nop,
|
|
assign_addr - start_addr);
|
|
s->cur_func->label_slots[label_parse].ref_count--;
|
|
has_initializer = FALSE;
|
|
}
|
|
return has_initializer;
|
|
|
|
prop_error:
|
|
JS_FreeAtom(s->ctx, prop_name);
|
|
var_error:
|
|
JS_FreeAtom(s->ctx, var_name);
|
|
return -1;
|
|
}
|
|
|
|
typedef enum FuncCallType {
|
|
FUNC_CALL_NORMAL,
|
|
FUNC_CALL_NEW,
|
|
FUNC_CALL_SUPER_CTOR,
|
|
FUNC_CALL_TEMPLATE,
|
|
} FuncCallType;
|
|
|
|
static void optional_chain_test(JSParseState *s, int *poptional_chaining_label,
|
|
int drop_count)
|
|
{
|
|
int label_next, i;
|
|
if (*poptional_chaining_label < 0)
|
|
*poptional_chaining_label = new_label(s);
|
|
/* XXX: could be more efficient with a specific opcode */
|
|
emit_op(s, OP_dup);
|
|
emit_op(s, OP_is_undefined_or_null);
|
|
label_next = emit_goto(s, OP_if_false, -1);
|
|
for(i = 0; i < drop_count; i++)
|
|
emit_op(s, OP_drop);
|
|
emit_op(s, OP_undefined);
|
|
emit_goto(s, OP_goto, *poptional_chaining_label);
|
|
emit_label(s, label_next);
|
|
}
|
|
|
|
/* allowed parse_flags: PF_POSTFIX_CALL */
|
|
static __exception int js_parse_postfix_expr(JSParseState *s, int parse_flags)
|
|
{
|
|
FuncCallType call_type;
|
|
int optional_chaining_label;
|
|
BOOL accept_lparen = (parse_flags & PF_POSTFIX_CALL) != 0;
|
|
|
|
call_type = FUNC_CALL_NORMAL;
|
|
switch(s->token.val) {
|
|
case TOK_NUMBER:
|
|
{
|
|
JSValue val;
|
|
val = s->token.u.num.val;
|
|
|
|
if (JS_VALUE_GET_TAG(val) == JS_TAG_INT) {
|
|
emit_op(s, OP_push_i32);
|
|
emit_u32(s, JS_VALUE_GET_INT(val));
|
|
} else {
|
|
if (emit_push_const(s, val, 0) < 0)
|
|
return -1;
|
|
}
|
|
}
|
|
if (next_token(s))
|
|
return -1;
|
|
break;
|
|
case TOK_TEMPLATE:
|
|
if (js_parse_template(s, 0, NULL))
|
|
return -1;
|
|
break;
|
|
case TOK_STRING:
|
|
if (emit_push_const(s, s->token.u.str.str, 1))
|
|
return -1;
|
|
if (next_token(s))
|
|
return -1;
|
|
break;
|
|
|
|
case TOK_DIV_ASSIGN:
|
|
s->buf_ptr -= 2;
|
|
goto parse_regexp;
|
|
case '/':
|
|
s->buf_ptr--;
|
|
parse_regexp:
|
|
{
|
|
JSValue str;
|
|
int ret, backtrace_flags;
|
|
if (!s->ctx->compile_regexp)
|
|
return js_parse_error(s, "RegExp are not supported");
|
|
/* the previous token is '/' or '/=', so no need to free */
|
|
if (js_parse_regexp(s))
|
|
return -1;
|
|
ret = emit_push_const(s, s->token.u.regexp.body, 0);
|
|
str = s->ctx->compile_regexp(s->ctx, s->token.u.regexp.body,
|
|
s->token.u.regexp.flags);
|
|
if (JS_IsException(str)) {
|
|
/* add the line number info */
|
|
backtrace_flags = 0;
|
|
if (s->cur_func && s->cur_func->backtrace_barrier)
|
|
backtrace_flags = JS_BACKTRACE_FLAG_SINGLE_LEVEL;
|
|
build_backtrace(s->ctx, s->ctx->rt->current_exception,
|
|
s->filename,
|
|
s->token.line_num,
|
|
s->token.col_num,
|
|
backtrace_flags);
|
|
return -1;
|
|
}
|
|
ret = emit_push_const(s, str, 0);
|
|
JS_FreeValue(s->ctx, str);
|
|
if (ret)
|
|
return -1;
|
|
/* we use a specific opcode to be sure the correct
|
|
function is called (otherwise the bytecode would have
|
|
to be verified by the RegExp constructor) */
|
|
emit_op(s, OP_regexp);
|
|
if (next_token(s))
|
|
return -1;
|
|
}
|
|
break;
|
|
case '(':
|
|
if (js_parse_expr_paren(s))
|
|
return -1;
|
|
break;
|
|
case TOK_FUNCTION:
|
|
if (js_parse_function_decl(s, JS_PARSE_FUNC_EXPR,
|
|
JS_FUNC_NORMAL, JS_ATOM_NULL,
|
|
s->token.ptr,
|
|
s->token.line_num,
|
|
s->token.col_num))
|
|
return -1;
|
|
break;
|
|
case TOK_CLASS:
|
|
if (js_parse_class(s, TRUE, JS_PARSE_EXPORT_NONE))
|
|
return -1;
|
|
break;
|
|
case TOK_NULL:
|
|
if (next_token(s))
|
|
return -1;
|
|
emit_op(s, OP_null);
|
|
break;
|
|
case TOK_THIS:
|
|
if (next_token(s))
|
|
return -1;
|
|
emit_op(s, OP_scope_get_var);
|
|
emit_atom(s, JS_ATOM_this);
|
|
emit_u16(s, 0);
|
|
break;
|
|
case TOK_FALSE:
|
|
if (next_token(s))
|
|
return -1;
|
|
emit_op(s, OP_push_false);
|
|
break;
|
|
case TOK_TRUE:
|
|
if (next_token(s))
|
|
return -1;
|
|
emit_op(s, OP_push_true);
|
|
break;
|
|
case TOK_IDENT:
|
|
{
|
|
JSAtom name;
|
|
if (s->token.u.ident.is_reserved) {
|
|
return js_parse_error_reserved_identifier(s);
|
|
}
|
|
if (token_is_pseudo_keyword(s, JS_ATOM_async) &&
|
|
peek_token(s, TRUE) != '\n') {
|
|
const uint8_t *source_ptr;
|
|
int source_line_num;
|
|
int source_col_num;
|
|
|
|
source_ptr = s->token.ptr;
|
|
source_line_num = s->token.line_num;
|
|
source_col_num = s->token.col_num;
|
|
if (next_token(s))
|
|
return -1;
|
|
if (s->token.val == TOK_FUNCTION) {
|
|
if (js_parse_function_decl(s, JS_PARSE_FUNC_EXPR,
|
|
JS_FUNC_ASYNC, JS_ATOM_NULL,
|
|
source_ptr,
|
|
source_line_num,
|
|
source_col_num))
|
|
return -1;
|
|
} else {
|
|
name = JS_DupAtom(s->ctx, JS_ATOM_async);
|
|
goto do_get_var;
|
|
}
|
|
} else {
|
|
if (s->token.u.ident.atom == JS_ATOM_arguments &&
|
|
!s->cur_func->arguments_allowed) {
|
|
js_parse_error(s, "'arguments' identifier is not allowed in class field initializer");
|
|
return -1;
|
|
}
|
|
name = JS_DupAtom(s->ctx, s->token.u.ident.atom);
|
|
if (next_token(s)) { /* update line number before emitting code */
|
|
JS_FreeAtom(s->ctx, name);
|
|
return -1;
|
|
}
|
|
do_get_var:
|
|
emit_op(s, OP_scope_get_var);
|
|
emit_u32(s, name);
|
|
emit_u16(s, s->cur_func->scope_level);
|
|
}
|
|
}
|
|
break;
|
|
case '{':
|
|
case '[':
|
|
{
|
|
int skip_bits;
|
|
if (js_parse_skip_parens_token(s, &skip_bits, FALSE) == '=') {
|
|
if (js_parse_destructuring_element(s, 0, 0, FALSE, skip_bits & SKIP_HAS_ELLIPSIS, TRUE) < 0)
|
|
return -1;
|
|
} else {
|
|
if (s->token.val == '{') {
|
|
if (js_parse_object_literal(s))
|
|
return -1;
|
|
} else {
|
|
if (js_parse_array_literal(s))
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case TOK_NEW:
|
|
if (next_token(s))
|
|
return -1;
|
|
if (s->token.val == '.') {
|
|
if (next_token(s))
|
|
return -1;
|
|
if (!token_is_pseudo_keyword(s, JS_ATOM_target))
|
|
return js_parse_error(s, "expecting target");
|
|
if (!s->cur_func->new_target_allowed)
|
|
return js_parse_error(s, "new.target only allowed within functions");
|
|
if (next_token(s))
|
|
return -1;
|
|
emit_op(s, OP_scope_get_var);
|
|
emit_atom(s, JS_ATOM_new_target);
|
|
emit_u16(s, 0);
|
|
} else {
|
|
if (js_parse_postfix_expr(s, 0))
|
|
return -1;
|
|
accept_lparen = TRUE;
|
|
if (s->token.val != '(') {
|
|
/* new operator on an object */
|
|
emit_op(s, OP_dup);
|
|
emit_op(s, OP_call_constructor);
|
|
emit_u16(s, 0);
|
|
} else {
|
|
call_type = FUNC_CALL_NEW;
|
|
}
|
|
}
|
|
break;
|
|
case TOK_SUPER:
|
|
if (next_token(s))
|
|
return -1;
|
|
if (s->token.val == '(') {
|
|
if (!s->cur_func->super_call_allowed)
|
|
return js_parse_error(s, "super() is only valid in a derived class constructor");
|
|
call_type = FUNC_CALL_SUPER_CTOR;
|
|
} else if (s->token.val == '.' || s->token.val == '[') {
|
|
if (!s->cur_func->super_allowed)
|
|
return js_parse_error(s, "'super' is only valid in a method");
|
|
emit_op(s, OP_scope_get_var);
|
|
emit_atom(s, JS_ATOM_this);
|
|
emit_u16(s, 0);
|
|
emit_op(s, OP_scope_get_var);
|
|
emit_atom(s, JS_ATOM_home_object);
|
|
emit_u16(s, 0);
|
|
emit_op(s, OP_get_super);
|
|
} else {
|
|
return js_parse_error(s, "invalid use of 'super'");
|
|
}
|
|
break;
|
|
case TOK_IMPORT:
|
|
if (next_token(s))
|
|
return -1;
|
|
if (s->token.val == '.') {
|
|
if (next_token(s))
|
|
return -1;
|
|
if (!token_is_pseudo_keyword(s, JS_ATOM_meta))
|
|
return js_parse_error(s, "meta expected");
|
|
if (!s->is_module)
|
|
return js_parse_error(s, "import.meta only valid in module code");
|
|
if (next_token(s))
|
|
return -1;
|
|
emit_op(s, OP_special_object);
|
|
emit_u8(s, OP_SPECIAL_OBJECT_IMPORT_META);
|
|
} else {
|
|
if (js_parse_expect(s, '('))
|
|
return -1;
|
|
if (!accept_lparen)
|
|
return js_parse_error(s, "invalid use of 'import()'");
|
|
if (js_parse_assign_expr(s))
|
|
return -1;
|
|
if (js_parse_expect(s, ')'))
|
|
return -1;
|
|
emit_op(s, OP_import);
|
|
}
|
|
break;
|
|
default:
|
|
return js_parse_error(s, "unexpected token in expression: '%.*s'",
|
|
(int)(s->buf_ptr - s->token.ptr), s->token.ptr);
|
|
}
|
|
|
|
optional_chaining_label = -1;
|
|
for(;;) {
|
|
JSFunctionDef *fd = s->cur_func;
|
|
BOOL has_optional_chain = FALSE;
|
|
|
|
if (s->token.val == TOK_QUESTION_MARK_DOT) {
|
|
/* optional chaining */
|
|
if (next_token(s))
|
|
return -1;
|
|
has_optional_chain = TRUE;
|
|
if (s->token.val == '(' && accept_lparen) {
|
|
goto parse_func_call;
|
|
} else if (s->token.val == '[') {
|
|
goto parse_array_access;
|
|
} else {
|
|
goto parse_property;
|
|
}
|
|
} else if (s->token.val == TOK_TEMPLATE &&
|
|
call_type == FUNC_CALL_NORMAL) {
|
|
if (optional_chaining_label >= 0) {
|
|
return js_parse_error(s, "template literal cannot appear in an optional chain");
|
|
}
|
|
call_type = FUNC_CALL_TEMPLATE;
|
|
goto parse_func_call2;
|
|
} else if (s->token.val == '(' && accept_lparen) {
|
|
int opcode, arg_count, drop_count;
|
|
|
|
/* function call */
|
|
parse_func_call:
|
|
if (next_token(s))
|
|
return -1;
|
|
|
|
if (call_type == FUNC_CALL_NORMAL) {
|
|
parse_func_call2:
|
|
switch(opcode = get_prev_opcode(fd)) {
|
|
case OP_get_field:
|
|
/* keep the object on the stack */
|
|
fd->byte_code.buf[fd->last_opcode_pos] = OP_get_field2;
|
|
drop_count = 2;
|
|
break;
|
|
case OP_scope_get_private_field:
|
|
/* keep the object on the stack */
|
|
fd->byte_code.buf[fd->last_opcode_pos] = OP_scope_get_private_field2;
|
|
drop_count = 2;
|
|
break;
|
|
case OP_get_array_el:
|
|
/* keep the object on the stack */
|
|
fd->byte_code.buf[fd->last_opcode_pos] = OP_get_array_el2;
|
|
drop_count = 2;
|
|
break;
|
|
case OP_scope_get_var:
|
|
{
|
|
JSAtom name;
|
|
int scope;
|
|
name = get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1);
|
|
scope = get_u16(fd->byte_code.buf + fd->last_opcode_pos + 5);
|
|
if (name == JS_ATOM_eval && call_type == FUNC_CALL_NORMAL && !has_optional_chain) {
|
|
/* direct 'eval' */
|
|
opcode = OP_eval;
|
|
} else {
|
|
/* verify if function name resolves to a simple
|
|
get_loc/get_arg: a function call inside a `with`
|
|
statement can resolve to a method call of the
|
|
`with` context object
|
|
*/
|
|
/* XXX: always generate the OP_scope_get_ref
|
|
and remove it in variable resolution
|
|
pass ? */
|
|
if (has_with_scope(fd, scope)) {
|
|
opcode = OP_scope_get_ref;
|
|
fd->byte_code.buf[fd->last_opcode_pos] = opcode;
|
|
}
|
|
}
|
|
drop_count = 1;
|
|
}
|
|
break;
|
|
case OP_get_super_value:
|
|
fd->byte_code.buf[fd->last_opcode_pos] = OP_get_array_el;
|
|
/* on stack: this func_obj */
|
|
opcode = OP_get_array_el;
|
|
drop_count = 2;
|
|
break;
|
|
default:
|
|
opcode = OP_invalid;
|
|
drop_count = 1;
|
|
break;
|
|
}
|
|
if (has_optional_chain) {
|
|
optional_chain_test(s, &optional_chaining_label,
|
|
drop_count);
|
|
}
|
|
} else {
|
|
opcode = OP_invalid;
|
|
}
|
|
|
|
if (call_type == FUNC_CALL_TEMPLATE) {
|
|
if (js_parse_template(s, 1, &arg_count))
|
|
return -1;
|
|
goto emit_func_call;
|
|
} else if (call_type == FUNC_CALL_SUPER_CTOR) {
|
|
emit_op(s, OP_scope_get_var);
|
|
emit_atom(s, JS_ATOM_this_active_func);
|
|
emit_u16(s, 0);
|
|
|
|
emit_op(s, OP_get_super);
|
|
|
|
emit_op(s, OP_scope_get_var);
|
|
emit_atom(s, JS_ATOM_new_target);
|
|
emit_u16(s, 0);
|
|
} else if (call_type == FUNC_CALL_NEW) {
|
|
emit_op(s, OP_dup); /* new.target = function */
|
|
}
|
|
|
|
/* parse arguments */
|
|
arg_count = 0;
|
|
while (s->token.val != ')') {
|
|
if (arg_count >= 65535) {
|
|
return js_parse_error(s, "Too many arguments in function call (only %d allowed)",
|
|
65535 - 1);
|
|
}
|
|
if (s->token.val == TOK_ELLIPSIS)
|
|
break;
|
|
if (js_parse_assign_expr(s))
|
|
return -1;
|
|
arg_count++;
|
|
if (s->token.val == ')')
|
|
break;
|
|
/* accept a trailing comma before the ')' */
|
|
if (js_parse_expect(s, ','))
|
|
return -1;
|
|
}
|
|
if (s->token.val == TOK_ELLIPSIS) {
|
|
emit_op(s, OP_array_from);
|
|
emit_u16(s, arg_count);
|
|
emit_op(s, OP_push_i32);
|
|
emit_u32(s, arg_count);
|
|
|
|
/* on stack: array idx */
|
|
while (s->token.val != ')') {
|
|
if (s->token.val == TOK_ELLIPSIS) {
|
|
if (next_token(s))
|
|
return -1;
|
|
if (js_parse_assign_expr(s))
|
|
return -1;
|
|
/* XXX: could pass is_last indicator? */
|
|
emit_op(s, OP_append);
|
|
} else {
|
|
if (js_parse_assign_expr(s))
|
|
return -1;
|
|
/* array idx val */
|
|
emit_op(s, OP_define_array_el);
|
|
emit_op(s, OP_inc);
|
|
}
|
|
if (s->token.val == ')')
|
|
break;
|
|
/* accept a trailing comma before the ')' */
|
|
if (js_parse_expect(s, ','))
|
|
return -1;
|
|
}
|
|
if (next_token(s))
|
|
return -1;
|
|
/* drop the index */
|
|
emit_op(s, OP_drop);
|
|
|
|
/* apply function call */
|
|
switch(opcode) {
|
|
case OP_get_field:
|
|
case OP_scope_get_private_field:
|
|
case OP_get_array_el:
|
|
case OP_scope_get_ref:
|
|
/* obj func array -> func obj array */
|
|
emit_op(s, OP_perm3);
|
|
emit_op(s, OP_apply);
|
|
emit_u16(s, call_type == FUNC_CALL_NEW);
|
|
break;
|
|
case OP_eval:
|
|
emit_op(s, OP_apply_eval);
|
|
emit_u16(s, fd->scope_level);
|
|
fd->has_eval_call = TRUE;
|
|
break;
|
|
default:
|
|
if (call_type == FUNC_CALL_SUPER_CTOR) {
|
|
emit_op(s, OP_apply);
|
|
emit_u16(s, 1);
|
|
/* set the 'this' value */
|
|
emit_op(s, OP_dup);
|
|
emit_op(s, OP_scope_put_var_init);
|
|
emit_atom(s, JS_ATOM_this);
|
|
emit_u16(s, 0);
|
|
|
|
emit_class_field_init(s);
|
|
} else if (call_type == FUNC_CALL_NEW) {
|
|
/* obj func array -> func obj array */
|
|
emit_op(s, OP_perm3);
|
|
emit_op(s, OP_apply);
|
|
emit_u16(s, 1);
|
|
} else {
|
|
/* func array -> func undef array */
|
|
emit_op(s, OP_undefined);
|
|
emit_op(s, OP_swap);
|
|
emit_op(s, OP_apply);
|
|
emit_u16(s, 0);
|
|
}
|
|
break;
|
|
}
|
|
} else {
|
|
if (next_token(s))
|
|
return -1;
|
|
emit_func_call:
|
|
switch(opcode) {
|
|
case OP_get_field:
|
|
case OP_scope_get_private_field:
|
|
case OP_get_array_el:
|
|
case OP_scope_get_ref:
|
|
emit_op(s, OP_call_method);
|
|
emit_u16(s, arg_count);
|
|
break;
|
|
case OP_eval:
|
|
emit_op(s, OP_eval);
|
|
emit_u16(s, arg_count);
|
|
emit_u16(s, fd->scope_level);
|
|
fd->has_eval_call = TRUE;
|
|
break;
|
|
default:
|
|
if (call_type == FUNC_CALL_SUPER_CTOR) {
|
|
emit_op(s, OP_call_constructor);
|
|
emit_u16(s, arg_count);
|
|
|
|
/* set the 'this' value */
|
|
emit_op(s, OP_dup);
|
|
emit_op(s, OP_scope_put_var_init);
|
|
emit_atom(s, JS_ATOM_this);
|
|
emit_u16(s, 0);
|
|
|
|
emit_class_field_init(s);
|
|
} else if (call_type == FUNC_CALL_NEW) {
|
|
emit_op(s, OP_call_constructor);
|
|
emit_u16(s, arg_count);
|
|
} else {
|
|
emit_op(s, OP_call);
|
|
emit_u16(s, arg_count);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
call_type = FUNC_CALL_NORMAL;
|
|
} else if (s->token.val == '.') {
|
|
if (next_token(s))
|
|
return -1;
|
|
parse_property:
|
|
if (s->token.val == TOK_PRIVATE_NAME) {
|
|
/* private class field */
|
|
if (get_prev_opcode(fd) == OP_get_super) {
|
|
return js_parse_error(s, "private class field forbidden after super");
|
|
}
|
|
if (has_optional_chain) {
|
|
optional_chain_test(s, &optional_chaining_label, 1);
|
|
}
|
|
emit_op(s, OP_scope_get_private_field);
|
|
emit_atom(s, s->token.u.ident.atom);
|
|
emit_u16(s, s->cur_func->scope_level);
|
|
} else {
|
|
if (!token_is_ident(s->token.val)) {
|
|
return js_parse_error(s, "expecting field name");
|
|
}
|
|
if (get_prev_opcode(fd) == OP_get_super) {
|
|
JSValue val;
|
|
int ret;
|
|
val = JS_AtomToValue(s->ctx, s->token.u.ident.atom);
|
|
ret = emit_push_const(s, val, 1);
|
|
JS_FreeValue(s->ctx, val);
|
|
if (ret)
|
|
return -1;
|
|
emit_op(s, OP_get_super_value);
|
|
} else {
|
|
if (has_optional_chain) {
|
|
optional_chain_test(s, &optional_chaining_label, 1);
|
|
}
|
|
emit_op(s, OP_get_field);
|
|
emit_atom(s, s->token.u.ident.atom);
|
|
emit_ic(s, s->token.u.ident.atom);
|
|
}
|
|
}
|
|
if (next_token(s))
|
|
return -1;
|
|
} else if (s->token.val == '[') {
|
|
int prev_op;
|
|
|
|
parse_array_access:
|
|
prev_op = get_prev_opcode(fd);
|
|
if (has_optional_chain) {
|
|
optional_chain_test(s, &optional_chaining_label, 1);
|
|
}
|
|
if (next_token(s))
|
|
return -1;
|
|
if (js_parse_expr(s))
|
|
return -1;
|
|
if (js_parse_expect(s, ']'))
|
|
return -1;
|
|
if (prev_op == OP_get_super) {
|
|
emit_op(s, OP_get_super_value);
|
|
} else {
|
|
emit_op(s, OP_get_array_el);
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
if (optional_chaining_label >= 0)
|
|
emit_label(s, optional_chaining_label);
|
|
return 0;
|
|
}
|
|
|
|
static __exception int js_parse_delete(JSParseState *s)
|
|
{
|
|
JSFunctionDef *fd = s->cur_func;
|
|
JSAtom name;
|
|
int opcode;
|
|
|
|
if (next_token(s))
|
|
return -1;
|
|
if (js_parse_unary(s, PF_POW_FORBIDDEN))
|
|
return -1;
|
|
switch(opcode = get_prev_opcode(fd)) {
|
|
case OP_get_field:
|
|
{
|
|
JSValue val;
|
|
int ret;
|
|
|
|
name = get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1);
|
|
fd->byte_code.size = fd->last_opcode_pos;
|
|
fd->last_opcode_pos = -1;
|
|
val = JS_AtomToValue(s->ctx, name);
|
|
ret = emit_push_const(s, val, 1);
|
|
JS_FreeValue(s->ctx, val);
|
|
JS_FreeAtom(s->ctx, name);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
goto do_delete;
|
|
case OP_get_array_el:
|
|
fd->byte_code.size = fd->last_opcode_pos;
|
|
fd->last_opcode_pos = -1;
|
|
do_delete:
|
|
emit_op(s, OP_delete);
|
|
break;
|
|
case OP_scope_get_var:
|
|
/* 'delete this': this is not a reference */
|
|
name = get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1);
|
|
if (name == JS_ATOM_this || name == JS_ATOM_new_target)
|
|
goto ret_true;
|
|
if (fd->js_mode & JS_MODE_STRICT) {
|
|
return js_parse_error(s, "cannot delete a direct reference in strict mode");
|
|
} else {
|
|
fd->byte_code.buf[fd->last_opcode_pos] = OP_scope_delete_var;
|
|
}
|
|
break;
|
|
case OP_scope_get_private_field:
|
|
return js_parse_error(s, "cannot delete a private class field");
|
|
case OP_get_super_value:
|
|
fd->byte_code.size = fd->last_opcode_pos;
|
|
fd->last_opcode_pos = -1;
|
|
emit_op(s, OP_throw_error);
|
|
emit_atom(s, JS_ATOM_NULL);
|
|
emit_u8(s, JS_THROW_ERROR_DELETE_SUPER);
|
|
break;
|
|
default:
|
|
ret_true:
|
|
emit_op(s, OP_drop);
|
|
emit_op(s, OP_push_true);
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* allowed parse_flags: PF_POW_ALLOWED, PF_POW_FORBIDDEN */
|
|
static __exception int js_parse_unary(JSParseState *s, int parse_flags)
|
|
{
|
|
int op;
|
|
|
|
switch(s->token.val) {
|
|
case '+':
|
|
case '-':
|
|
case '!':
|
|
case '~':
|
|
case TOK_VOID:
|
|
op = s->token.val;
|
|
if (next_token(s))
|
|
return -1;
|
|
if (js_parse_unary(s, PF_POW_FORBIDDEN))
|
|
return -1;
|
|
switch(op) {
|
|
case '-':
|
|
emit_op(s, OP_neg);
|
|
break;
|
|
case '+':
|
|
emit_op(s, OP_plus);
|
|
break;
|
|
case '!':
|
|
emit_op(s, OP_lnot);
|
|
break;
|
|
case '~':
|
|
emit_op(s, OP_not);
|
|
break;
|
|
case TOK_VOID:
|
|
emit_op(s, OP_drop);
|
|
emit_op(s, OP_undefined);
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
parse_flags = 0;
|
|
break;
|
|
case TOK_DEC:
|
|
case TOK_INC:
|
|
{
|
|
int opcode, op, scope, label;
|
|
JSAtom name;
|
|
op = s->token.val;
|
|
if (next_token(s))
|
|
return -1;
|
|
if (js_parse_unary(s, 0))
|
|
return -1;
|
|
if (get_lvalue(s, &opcode, &scope, &name, &label, NULL, TRUE, op))
|
|
return -1;
|
|
emit_op(s, OP_dec + op - TOK_DEC);
|
|
put_lvalue(s, opcode, scope, name, label, PUT_LVALUE_KEEP_TOP,
|
|
FALSE);
|
|
}
|
|
break;
|
|
case TOK_TYPEOF:
|
|
{
|
|
JSFunctionDef *fd;
|
|
if (next_token(s))
|
|
return -1;
|
|
if (js_parse_unary(s, PF_POW_FORBIDDEN))
|
|
return -1;
|
|
/* reference access should not return an exception, so we
|
|
patch the get_var */
|
|
fd = s->cur_func;
|
|
if (get_prev_opcode(fd) == OP_scope_get_var) {
|
|
fd->byte_code.buf[fd->last_opcode_pos] = OP_scope_get_var_undef;
|
|
}
|
|
emit_op(s, OP_typeof);
|
|
parse_flags = 0;
|
|
}
|
|
break;
|
|
case TOK_DELETE:
|
|
if (js_parse_delete(s))
|
|
return -1;
|
|
parse_flags = 0;
|
|
break;
|
|
case TOK_AWAIT:
|
|
if (!(s->cur_func->func_kind & JS_FUNC_ASYNC))
|
|
return js_parse_error(s, "unexpected 'await' keyword");
|
|
if (!s->cur_func->in_function_body)
|
|
return js_parse_error(s, "await in default expression");
|
|
if (next_token(s))
|
|
return -1;
|
|
if (js_parse_unary(s, PF_POW_FORBIDDEN))
|
|
return -1;
|
|
emit_op(s, OP_await);
|
|
parse_flags = 0;
|
|
break;
|
|
default:
|
|
if (js_parse_postfix_expr(s, PF_POSTFIX_CALL))
|
|
return -1;
|
|
if (!s->got_lf &&
|
|
(s->token.val == TOK_DEC || s->token.val == TOK_INC)) {
|
|
int opcode, op, scope, label;
|
|
JSAtom name;
|
|
op = s->token.val;
|
|
if (get_lvalue(s, &opcode, &scope, &name, &label, NULL, TRUE, op))
|
|
return -1;
|
|
emit_op(s, OP_post_dec + op - TOK_DEC);
|
|
put_lvalue(s, opcode, scope, name, label, PUT_LVALUE_KEEP_SECOND,
|
|
FALSE);
|
|
if (next_token(s))
|
|
return -1;
|
|
}
|
|
break;
|
|
}
|
|
if (parse_flags & (PF_POW_ALLOWED | PF_POW_FORBIDDEN)) {
|
|
if (s->token.val == TOK_POW) {
|
|
/* Strict ES7 exponentiation syntax rules: To solve
|
|
conficting semantics between different implementations
|
|
regarding the precedence of prefix operators and the
|
|
postifx exponential, ES7 specifies that -2**2 is a
|
|
syntax error. */
|
|
if (parse_flags & PF_POW_FORBIDDEN)
|
|
return js_parse_error(s, "unparenthesized unary expression can't appear on the left-hand side of '**'");
|
|
if (next_token(s))
|
|
return -1;
|
|
if (js_parse_unary(s, PF_POW_ALLOWED))
|
|
return -1;
|
|
emit_op(s, OP_pow);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* allowed parse_flags: PF_IN_ACCEPTED */
|
|
static __exception int js_parse_expr_binary(JSParseState *s, int level,
|
|
int parse_flags)
|
|
{
|
|
int op, opcode;
|
|
|
|
if (level == 0) {
|
|
return js_parse_unary(s, PF_POW_ALLOWED);
|
|
}
|
|
if (js_parse_expr_binary(s, level - 1, parse_flags))
|
|
return -1;
|
|
for(;;) {
|
|
op = s->token.val;
|
|
switch(level) {
|
|
case 1:
|
|
switch(op) {
|
|
case '*':
|
|
opcode = OP_mul;
|
|
break;
|
|
case '/':
|
|
opcode = OP_div;
|
|
break;
|
|
case '%':
|
|
opcode = OP_mod;
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
break;
|
|
case 2:
|
|
switch(op) {
|
|
case '+':
|
|
opcode = OP_add;
|
|
break;
|
|
case '-':
|
|
opcode = OP_sub;
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
break;
|
|
case 3:
|
|
switch(op) {
|
|
case TOK_SHL:
|
|
opcode = OP_shl;
|
|
break;
|
|
case TOK_SAR:
|
|
opcode = OP_sar;
|
|
break;
|
|
case TOK_SHR:
|
|
opcode = OP_shr;
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
break;
|
|
case 4:
|
|
switch(op) {
|
|
case '<':
|
|
opcode = OP_lt;
|
|
break;
|
|
case '>':
|
|
opcode = OP_gt;
|
|
break;
|
|
case TOK_LTE:
|
|
opcode = OP_lte;
|
|
break;
|
|
case TOK_GTE:
|
|
opcode = OP_gte;
|
|
break;
|
|
case TOK_INSTANCEOF:
|
|
opcode = OP_instanceof;
|
|
break;
|
|
case TOK_IN:
|
|
if (parse_flags & PF_IN_ACCEPTED) {
|
|
opcode = OP_in;
|
|
} else {
|
|
return 0;
|
|
}
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
break;
|
|
case 5:
|
|
switch(op) {
|
|
case TOK_EQ:
|
|
opcode = OP_eq;
|
|
break;
|
|
case TOK_NEQ:
|
|
opcode = OP_neq;
|
|
break;
|
|
case TOK_STRICT_EQ:
|
|
opcode = OP_strict_eq;
|
|
break;
|
|
case TOK_STRICT_NEQ:
|
|
opcode = OP_strict_neq;
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
break;
|
|
case 6:
|
|
switch(op) {
|
|
case '&':
|
|
opcode = OP_and;
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
break;
|
|
case 7:
|
|
switch(op) {
|
|
case '^':
|
|
opcode = OP_xor;
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
break;
|
|
case 8:
|
|
switch(op) {
|
|
case '|':
|
|
opcode = OP_or;
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
if (next_token(s))
|
|
return -1;
|
|
if (js_parse_expr_binary(s, level - 1, parse_flags))
|
|
return -1;
|
|
emit_op(s, opcode);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* allowed parse_flags: PF_IN_ACCEPTED */
|
|
static __exception int js_parse_logical_and_or(JSParseState *s, int op,
|
|
int parse_flags)
|
|
{
|
|
int label1;
|
|
|
|
if (op == TOK_LAND) {
|
|
if (js_parse_expr_binary(s, 8, parse_flags))
|
|
return -1;
|
|
} else {
|
|
if (js_parse_logical_and_or(s, TOK_LAND, parse_flags))
|
|
return -1;
|
|
}
|
|
if (s->token.val == op) {
|
|
label1 = new_label(s);
|
|
|
|
for(;;) {
|
|
if (next_token(s))
|
|
return -1;
|
|
emit_op(s, OP_dup);
|
|
emit_goto(s, op == TOK_LAND ? OP_if_false : OP_if_true, label1);
|
|
emit_op(s, OP_drop);
|
|
|
|
if (op == TOK_LAND) {
|
|
if (js_parse_expr_binary(s, 8, parse_flags))
|
|
return -1;
|
|
} else {
|
|
if (js_parse_logical_and_or(s, TOK_LAND, parse_flags))
|
|
return -1;
|
|
}
|
|
if (s->token.val != op) {
|
|
if (s->token.val == TOK_DOUBLE_QUESTION_MARK)
|
|
return js_parse_error(s, "cannot mix ?? with && or ||");
|
|
break;
|
|
}
|
|
}
|
|
|
|
emit_label(s, label1);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static __exception int js_parse_coalesce_expr(JSParseState *s, int parse_flags)
|
|
{
|
|
int label1;
|
|
|
|
if (js_parse_logical_and_or(s, TOK_LOR, parse_flags))
|
|
return -1;
|
|
if (s->token.val == TOK_DOUBLE_QUESTION_MARK) {
|
|
label1 = new_label(s);
|
|
for(;;) {
|
|
if (next_token(s))
|
|
return -1;
|
|
|
|
emit_op(s, OP_dup);
|
|
emit_op(s, OP_is_undefined_or_null);
|
|
emit_goto(s, OP_if_false, label1);
|
|
emit_op(s, OP_drop);
|
|
|
|
if (js_parse_expr_binary(s, 8, parse_flags))
|
|
return -1;
|
|
if (s->token.val != TOK_DOUBLE_QUESTION_MARK)
|
|
break;
|
|
}
|
|
emit_label(s, label1);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* allowed parse_flags: PF_IN_ACCEPTED */
|
|
static __exception int js_parse_cond_expr(JSParseState *s, int parse_flags)
|
|
{
|
|
int label1, label2;
|
|
|
|
if (js_parse_coalesce_expr(s, parse_flags))
|
|
return -1;
|
|
if (s->token.val == '?') {
|
|
if (next_token(s))
|
|
return -1;
|
|
label1 = emit_goto(s, OP_if_false, -1);
|
|
|
|
if (js_parse_assign_expr(s))
|
|
return -1;
|
|
if (js_parse_expect(s, ':'))
|
|
return -1;
|
|
|
|
label2 = emit_goto(s, OP_goto, -1);
|
|
|
|
emit_label(s, label1);
|
|
|
|
if (js_parse_assign_expr2(s, parse_flags & PF_IN_ACCEPTED))
|
|
return -1;
|
|
|
|
emit_label(s, label2);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* allowed parse_flags: PF_IN_ACCEPTED */
|
|
static __exception int js_parse_assign_expr2(JSParseState *s, int parse_flags)
|
|
{
|
|
int opcode, op, scope;
|
|
JSAtom name0 = JS_ATOM_NULL;
|
|
JSAtom name;
|
|
|
|
if (s->token.val == TOK_YIELD) {
|
|
BOOL is_star = FALSE, is_async;
|
|
|
|
if (!(s->cur_func->func_kind & JS_FUNC_GENERATOR))
|
|
return js_parse_error(s, "unexpected 'yield' keyword");
|
|
if (!s->cur_func->in_function_body)
|
|
return js_parse_error(s, "yield in default expression");
|
|
if (next_token(s))
|
|
return -1;
|
|
/* XXX: is there a better method to detect 'yield' without
|
|
parameters ? */
|
|
if (s->token.val != ';' && s->token.val != ')' &&
|
|
s->token.val != ']' && s->token.val != '}' &&
|
|
s->token.val != ',' && s->token.val != ':' && !s->got_lf) {
|
|
if (s->token.val == '*') {
|
|
is_star = TRUE;
|
|
if (next_token(s))
|
|
return -1;
|
|
}
|
|
if (js_parse_assign_expr2(s, parse_flags))
|
|
return -1;
|
|
} else {
|
|
emit_op(s, OP_undefined);
|
|
}
|
|
is_async = (s->cur_func->func_kind == JS_FUNC_ASYNC_GENERATOR);
|
|
|
|
if (is_star) {
|
|
int label_loop, label_return, label_next;
|
|
int label_return1, label_yield, label_throw, label_throw1;
|
|
int label_throw2;
|
|
|
|
label_loop = new_label(s);
|
|
label_yield = new_label(s);
|
|
|
|
emit_op(s, is_async ? OP_for_await_of_start : OP_for_of_start);
|
|
|
|
/* remove the catch offset (XXX: could avoid pushing back
|
|
undefined) */
|
|
emit_op(s, OP_drop);
|
|
emit_op(s, OP_undefined);
|
|
|
|
emit_op(s, OP_undefined); /* initial value */
|
|
|
|
emit_label(s, label_loop);
|
|
emit_op(s, OP_iterator_next);
|
|
if (is_async)
|
|
emit_op(s, OP_await);
|
|
emit_op(s, OP_iterator_check_object);
|
|
emit_op(s, OP_get_field2);
|
|
emit_atom(s, JS_ATOM_done);
|
|
emit_ic(s, JS_ATOM_done);
|
|
label_next = emit_goto(s, OP_if_true, -1); /* end of loop */
|
|
emit_label(s, label_yield);
|
|
if (is_async) {
|
|
/* OP_async_yield_star takes the value as parameter */
|
|
emit_op(s, OP_get_field);
|
|
emit_atom(s, JS_ATOM_value);
|
|
emit_ic(s, JS_ATOM_value);
|
|
emit_op(s, OP_async_yield_star);
|
|
} else {
|
|
/* OP_yield_star takes (value, done) as parameter */
|
|
emit_op(s, OP_yield_star);
|
|
}
|
|
emit_op(s, OP_dup);
|
|
label_return = emit_goto(s, OP_if_true, -1);
|
|
emit_op(s, OP_drop);
|
|
emit_goto(s, OP_goto, label_loop);
|
|
|
|
emit_label(s, label_return);
|
|
emit_op(s, OP_push_i32);
|
|
emit_u32(s, 2);
|
|
emit_op(s, OP_strict_eq);
|
|
label_throw = emit_goto(s, OP_if_true, -1);
|
|
|
|
/* return handling */
|
|
if (is_async)
|
|
emit_op(s, OP_await);
|
|
emit_op(s, OP_iterator_call);
|
|
emit_u8(s, 0);
|
|
label_return1 = emit_goto(s, OP_if_true, -1);
|
|
if (is_async)
|
|
emit_op(s, OP_await);
|
|
emit_op(s, OP_iterator_check_object);
|
|
emit_op(s, OP_get_field2);
|
|
emit_atom(s, JS_ATOM_done);
|
|
emit_ic(s, JS_ATOM_done);
|
|
emit_goto(s, OP_if_false, label_yield);
|
|
|
|
emit_op(s, OP_get_field);
|
|
emit_atom(s, JS_ATOM_value);
|
|
emit_ic(s, JS_ATOM_value);
|
|
|
|
emit_label(s, label_return1);
|
|
emit_op(s, OP_nip);
|
|
emit_op(s, OP_nip);
|
|
emit_op(s, OP_nip);
|
|
emit_return(s, TRUE);
|
|
|
|
/* throw handling */
|
|
emit_label(s, label_throw);
|
|
emit_op(s, OP_iterator_call);
|
|
emit_u8(s, 1);
|
|
label_throw1 = emit_goto(s, OP_if_true, -1);
|
|
if (is_async)
|
|
emit_op(s, OP_await);
|
|
emit_op(s, OP_iterator_check_object);
|
|
emit_op(s, OP_get_field2);
|
|
emit_atom(s, JS_ATOM_done);
|
|
emit_ic(s, JS_ATOM_done);
|
|
emit_goto(s, OP_if_false, label_yield);
|
|
emit_goto(s, OP_goto, label_next);
|
|
/* close the iterator and throw a type error exception */
|
|
emit_label(s, label_throw1);
|
|
emit_op(s, OP_iterator_call);
|
|
emit_u8(s, 2);
|
|
label_throw2 = emit_goto(s, OP_if_true, -1);
|
|
if (is_async)
|
|
emit_op(s, OP_await);
|
|
emit_label(s, label_throw2);
|
|
|
|
emit_op(s, OP_throw_error);
|
|
emit_atom(s, JS_ATOM_NULL);
|
|
emit_u8(s, JS_THROW_ERROR_ITERATOR_THROW);
|
|
|
|
emit_label(s, label_next);
|
|
emit_op(s, OP_get_field);
|
|
emit_atom(s, JS_ATOM_value);
|
|
emit_ic(s, JS_ATOM_value);
|
|
emit_op(s, OP_nip); /* keep the value associated with
|
|
done = true */
|
|
emit_op(s, OP_nip);
|
|
emit_op(s, OP_nip);
|
|
} else {
|
|
int label_next;
|
|
|
|
if (is_async)
|
|
emit_op(s, OP_await);
|
|
emit_op(s, OP_yield);
|
|
label_next = emit_goto(s, OP_if_false, -1);
|
|
emit_return(s, TRUE);
|
|
emit_label(s, label_next);
|
|
}
|
|
return 0;
|
|
} else if (s->token.val == '(' &&
|
|
js_parse_skip_parens_token(s, NULL, TRUE) == TOK_ARROW) {
|
|
return js_parse_function_decl(s, JS_PARSE_FUNC_ARROW,
|
|
JS_FUNC_NORMAL, JS_ATOM_NULL,
|
|
s->token.ptr, s->token.line_num,
|
|
s->token.col_num);
|
|
} else if (token_is_pseudo_keyword(s, JS_ATOM_async)) {
|
|
const uint8_t *source_ptr;
|
|
int tok, source_line_num, source_col_num;
|
|
JSParsePos pos;
|
|
|
|
/* fast test */
|
|
tok = peek_token(s, TRUE);
|
|
if (tok == TOK_FUNCTION || tok == '\n')
|
|
goto next;
|
|
|
|
source_ptr = s->token.ptr;
|
|
source_line_num = s->token.line_num;
|
|
source_col_num = s->token.col_num;
|
|
js_parse_get_pos(s, &pos);
|
|
if (next_token(s))
|
|
return -1;
|
|
if ((s->token.val == '(' &&
|
|
js_parse_skip_parens_token(s, NULL, TRUE) == TOK_ARROW) ||
|
|
(s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved &&
|
|
peek_token(s, TRUE) == TOK_ARROW)) {
|
|
return js_parse_function_decl(s, JS_PARSE_FUNC_ARROW,
|
|
JS_FUNC_ASYNC, JS_ATOM_NULL,
|
|
source_ptr, source_line_num,
|
|
source_col_num);
|
|
} else {
|
|
/* undo the token parsing */
|
|
if (js_parse_seek_token(s, &pos))
|
|
return -1;
|
|
}
|
|
} else if (s->token.val == TOK_IDENT &&
|
|
peek_token(s, TRUE) == TOK_ARROW) {
|
|
return js_parse_function_decl(s, JS_PARSE_FUNC_ARROW,
|
|
JS_FUNC_NORMAL, JS_ATOM_NULL,
|
|
s->token.ptr, s->token.line_num,
|
|
s->token.col_num);
|
|
}
|
|
next:
|
|
if (s->token.val == TOK_IDENT) {
|
|
/* name0 is used to check for OP_set_name pattern, not duplicated */
|
|
name0 = s->token.u.ident.atom;
|
|
}
|
|
if (js_parse_cond_expr(s, parse_flags))
|
|
return -1;
|
|
|
|
op = s->token.val;
|
|
if (op == '=' || (op >= TOK_MUL_ASSIGN && op <= TOK_POW_ASSIGN)) {
|
|
int label;
|
|
if (next_token(s))
|
|
return -1;
|
|
if (get_lvalue(s, &opcode, &scope, &name, &label, NULL, (op != '='), op) < 0)
|
|
return -1;
|
|
|
|
// comply with rather obtuse evaluation order of computed properties:
|
|
// obj[key]=val evaluates val->obj->key when obj is null/undefined
|
|
// but key->obj->val when an object
|
|
// FIXME(bnoordhuis) less stack shuffling; don't to_propkey twice in
|
|
// happy path; replace `dup is_undefined_or_null if_true` with new
|
|
// opcode if_undefined_or_null? replace `swap dup` with over?
|
|
if (op == '=' && opcode == OP_get_array_el) {
|
|
int label_next = -1;
|
|
JSFunctionDef *fd = s->cur_func;
|
|
assert(OP_to_propkey2 == fd->byte_code.buf[fd->last_opcode_pos]);
|
|
fd->byte_code.size = fd->last_opcode_pos;
|
|
fd->last_opcode_pos = -1;
|
|
emit_op(s, OP_swap); // obj key -> key obj
|
|
emit_op(s, OP_dup);
|
|
emit_op(s, OP_is_undefined_or_null);
|
|
label_next = emit_goto(s, OP_if_true, -1);
|
|
emit_op(s, OP_swap);
|
|
emit_op(s, OP_to_propkey);
|
|
emit_op(s, OP_swap);
|
|
emit_label(s, label_next);
|
|
emit_op(s, OP_swap);
|
|
}
|
|
|
|
if (js_parse_assign_expr2(s, parse_flags)) {
|
|
JS_FreeAtom(s->ctx, name);
|
|
return -1;
|
|
}
|
|
|
|
if (op == '=' && opcode == OP_get_array_el) {
|
|
emit_op(s, OP_swap); // obj key val -> obj val key
|
|
emit_op(s, OP_to_propkey);
|
|
emit_op(s, OP_swap);
|
|
}
|
|
|
|
if (op == '=') {
|
|
if (opcode == OP_get_ref_value && name == name0) {
|
|
set_object_name(s, name);
|
|
}
|
|
} else {
|
|
emit_op(s, op - TOK_MUL_ASSIGN + OP_mul);
|
|
}
|
|
put_lvalue(s, opcode, scope, name, label, PUT_LVALUE_KEEP_TOP, FALSE);
|
|
} else if (op >= TOK_LAND_ASSIGN && op <= TOK_DOUBLE_QUESTION_MARK_ASSIGN) {
|
|
int label, label1, depth_lvalue, label2;
|
|
|
|
if (next_token(s))
|
|
return -1;
|
|
if (get_lvalue(s, &opcode, &scope, &name, &label,
|
|
&depth_lvalue, TRUE, op) < 0)
|
|
return -1;
|
|
|
|
emit_op(s, OP_dup);
|
|
if (op == TOK_DOUBLE_QUESTION_MARK_ASSIGN)
|
|
emit_op(s, OP_is_undefined_or_null);
|
|
label1 = emit_goto(s, op == TOK_LOR_ASSIGN ? OP_if_true : OP_if_false,
|
|
-1);
|
|
emit_op(s, OP_drop);
|
|
|
|
if (js_parse_assign_expr2(s, parse_flags)) {
|
|
JS_FreeAtom(s->ctx, name);
|
|
return -1;
|
|
}
|
|
|
|
if (opcode == OP_get_ref_value && name == name0) {
|
|
set_object_name(s, name);
|
|
}
|
|
|
|
switch(depth_lvalue) {
|
|
case 1:
|
|
emit_op(s, OP_insert2);
|
|
break;
|
|
case 2:
|
|
emit_op(s, OP_insert3);
|
|
break;
|
|
case 3:
|
|
emit_op(s, OP_insert4);
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
|
|
/* XXX: we disable the OP_put_ref_value optimization by not
|
|
using put_lvalue() otherwise depth_lvalue is not correct */
|
|
put_lvalue(s, opcode, scope, name, label, PUT_LVALUE_NOKEEP_DEPTH,
|
|
FALSE);
|
|
label2 = emit_goto(s, OP_goto, -1);
|
|
|
|
emit_label(s, label1);
|
|
|
|
/* remove the lvalue stack entries */
|
|
while (depth_lvalue != 0) {
|
|
emit_op(s, OP_nip);
|
|
depth_lvalue--;
|
|
}
|
|
|
|
emit_label(s, label2);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static __exception int js_parse_assign_expr(JSParseState *s)
|
|
{
|
|
return js_parse_assign_expr2(s, PF_IN_ACCEPTED);
|
|
}
|
|
|
|
/* allowed parse_flags: PF_IN_ACCEPTED */
|
|
static __exception int js_parse_expr2(JSParseState *s, int parse_flags)
|
|
{
|
|
BOOL comma = FALSE;
|
|
for(;;) {
|
|
if (js_parse_assign_expr2(s, parse_flags))
|
|
return -1;
|
|
if (comma) {
|
|
/* prevent get_lvalue from using the last expression
|
|
as an lvalue. This also prevents the conversion of
|
|
of get_var to get_ref for method lookup in function
|
|
call inside `with` statement.
|
|
*/
|
|
s->cur_func->last_opcode_pos = -1;
|
|
}
|
|
if (s->token.val != ',')
|
|
break;
|
|
comma = TRUE;
|
|
if (next_token(s))
|
|
return -1;
|
|
emit_op(s, OP_drop);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static __exception int js_parse_expr(JSParseState *s)
|
|
{
|
|
return js_parse_expr2(s, PF_IN_ACCEPTED);
|
|
}
|
|
|
|
static void push_break_entry(JSFunctionDef *fd, BlockEnv *be,
|
|
JSAtom label_name,
|
|
int label_break, int label_cont,
|
|
int drop_count)
|
|
{
|
|
be->prev = fd->top_break;
|
|
fd->top_break = be;
|
|
be->label_name = label_name;
|
|
be->label_break = label_break;
|
|
be->label_cont = label_cont;
|
|
be->drop_count = drop_count;
|
|
be->label_finally = -1;
|
|
be->scope_level = fd->scope_level;
|
|
be->has_iterator = FALSE;
|
|
}
|
|
|
|
static void pop_break_entry(JSFunctionDef *fd)
|
|
{
|
|
BlockEnv *be;
|
|
be = fd->top_break;
|
|
fd->top_break = be->prev;
|
|
}
|
|
|
|
static __exception int emit_break(JSParseState *s, JSAtom name, int is_cont)
|
|
{
|
|
BlockEnv *top;
|
|
int i, scope_level;
|
|
|
|
scope_level = s->cur_func->scope_level;
|
|
top = s->cur_func->top_break;
|
|
while (top != NULL) {
|
|
close_scopes(s, scope_level, top->scope_level);
|
|
scope_level = top->scope_level;
|
|
if (is_cont &&
|
|
top->label_cont != -1 &&
|
|
(name == JS_ATOM_NULL || top->label_name == name)) {
|
|
/* continue stays inside the same block */
|
|
emit_goto(s, OP_goto, top->label_cont);
|
|
return 0;
|
|
}
|
|
if (!is_cont &&
|
|
top->label_break != -1 &&
|
|
(name == JS_ATOM_NULL || top->label_name == name)) {
|
|
emit_goto(s, OP_goto, top->label_break);
|
|
return 0;
|
|
}
|
|
i = 0;
|
|
if (top->has_iterator) {
|
|
emit_op(s, OP_iterator_close);
|
|
i += 3;
|
|
}
|
|
for(; i < top->drop_count; i++)
|
|
emit_op(s, OP_drop);
|
|
if (top->label_finally != -1) {
|
|
/* must push dummy value to keep same stack depth */
|
|
emit_op(s, OP_undefined);
|
|
emit_goto(s, OP_gosub, top->label_finally);
|
|
emit_op(s, OP_drop);
|
|
}
|
|
top = top->prev;
|
|
}
|
|
if (name == JS_ATOM_NULL) {
|
|
if (is_cont)
|
|
return js_parse_error(s, "continue must be inside loop");
|
|
else
|
|
return js_parse_error(s, "break must be inside loop or switch");
|
|
} else {
|
|
return js_parse_error(s, "break/continue label not found");
|
|
}
|
|
}
|
|
|
|
/* execute the finally blocks before return */
|
|
static void emit_return(JSParseState *s, BOOL hasval)
|
|
{
|
|
BlockEnv *top;
|
|
|
|
if (s->cur_func->func_kind != JS_FUNC_NORMAL) {
|
|
if (!hasval) {
|
|
/* no value: direct return in case of async generator */
|
|
emit_op(s, OP_undefined);
|
|
hasval = TRUE;
|
|
} else if (s->cur_func->func_kind == JS_FUNC_ASYNC_GENERATOR) {
|
|
/* the await must be done before handling the "finally" in
|
|
case it raises an exception */
|
|
emit_op(s, OP_await);
|
|
}
|
|
}
|
|
|
|
top = s->cur_func->top_break;
|
|
while (top != NULL) {
|
|
if (top->has_iterator || top->label_finally != -1) {
|
|
if (!hasval) {
|
|
emit_op(s, OP_undefined);
|
|
hasval = TRUE;
|
|
}
|
|
/* Remove the stack elements up to and including the catch
|
|
offset. When 'yield' is used in an expression we have
|
|
no easy way to count them, so we use this specific
|
|
instruction instead. */
|
|
emit_op(s, OP_nip_catch);
|
|
/* stack: iter_obj next ret_val */
|
|
if (top->has_iterator) {
|
|
if (s->cur_func->func_kind == JS_FUNC_ASYNC_GENERATOR) {
|
|
int label_next, label_next2;
|
|
emit_op(s, OP_nip); /* next */
|
|
emit_op(s, OP_swap);
|
|
emit_op(s, OP_get_field2);
|
|
emit_atom(s, JS_ATOM_return);
|
|
emit_ic(s, JS_ATOM_return);
|
|
/* stack: iter_obj return_func */
|
|
emit_op(s, OP_dup);
|
|
emit_op(s, OP_is_undefined_or_null);
|
|
label_next = emit_goto(s, OP_if_true, -1);
|
|
emit_op(s, OP_call_method);
|
|
emit_u16(s, 0);
|
|
emit_op(s, OP_iterator_check_object);
|
|
emit_op(s, OP_await);
|
|
label_next2 = emit_goto(s, OP_goto, -1);
|
|
emit_label(s, label_next);
|
|
emit_op(s, OP_drop);
|
|
emit_label(s, label_next2);
|
|
emit_op(s, OP_drop);
|
|
} else {
|
|
emit_op(s, OP_rot3r);
|
|
emit_op(s, OP_undefined); /* dummy catch offset */
|
|
emit_op(s, OP_iterator_close);
|
|
}
|
|
} else {
|
|
/* execute the "finally" block */
|
|
emit_goto(s, OP_gosub, top->label_finally);
|
|
}
|
|
}
|
|
top = top->prev;
|
|
}
|
|
if (s->cur_func->is_derived_class_constructor) {
|
|
int label_return;
|
|
|
|
/* 'this' can be uninitialized, so it may be accessed only if
|
|
the derived class constructor does not return an object */
|
|
if (hasval) {
|
|
emit_op(s, OP_check_ctor_return);
|
|
label_return = emit_goto(s, OP_if_false, -1);
|
|
emit_op(s, OP_drop);
|
|
} else {
|
|
label_return = -1;
|
|
}
|
|
|
|
/* XXX: if this is not initialized, should throw the
|
|
ReferenceError in the caller realm */
|
|
emit_op(s, OP_scope_get_var);
|
|
emit_atom(s, JS_ATOM_this);
|
|
emit_u16(s, 0);
|
|
|
|
emit_label(s, label_return);
|
|
emit_op(s, OP_return);
|
|
} else if (s->cur_func->func_kind != JS_FUNC_NORMAL) {
|
|
emit_op(s, OP_return_async);
|
|
} else {
|
|
emit_op(s, hasval ? OP_return : OP_return_undef);
|
|
}
|
|
}
|
|
|
|
#define DECL_MASK_FUNC (1 << 0) /* allow normal function declaration */
|
|
/* ored with DECL_MASK_FUNC if function declarations are allowed with a label */
|
|
#define DECL_MASK_FUNC_WITH_LABEL (1 << 1)
|
|
#define DECL_MASK_OTHER (1 << 2) /* all other declarations */
|
|
#define DECL_MASK_ALL (DECL_MASK_FUNC | DECL_MASK_FUNC_WITH_LABEL | DECL_MASK_OTHER)
|
|
|
|
static __exception int js_parse_statement_or_decl(JSParseState *s,
|
|
int decl_mask);
|
|
|
|
static __exception int js_parse_statement(JSParseState *s)
|
|
{
|
|
return js_parse_statement_or_decl(s, 0);
|
|
}
|
|
|
|
static __exception int js_parse_block(JSParseState *s)
|
|
{
|
|
if (js_parse_expect(s, '{'))
|
|
return -1;
|
|
if (s->token.val != '}') {
|
|
push_scope(s);
|
|
for(;;) {
|
|
if (js_parse_statement_or_decl(s, DECL_MASK_ALL))
|
|
return -1;
|
|
if (s->token.val == '}')
|
|
break;
|
|
}
|
|
pop_scope(s);
|
|
}
|
|
if (next_token(s))
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
/* allowed parse_flags: PF_IN_ACCEPTED */
|
|
static __exception int js_parse_var(JSParseState *s, int parse_flags, int tok,
|
|
BOOL export_flag)
|
|
{
|
|
JSContext *ctx = s->ctx;
|
|
JSFunctionDef *fd = s->cur_func;
|
|
JSAtom name = JS_ATOM_NULL;
|
|
|
|
for (;;) {
|
|
if (s->token.val == TOK_IDENT) {
|
|
if (s->token.u.ident.is_reserved) {
|
|
return js_parse_error_reserved_identifier(s);
|
|
}
|
|
name = JS_DupAtom(ctx, s->token.u.ident.atom);
|
|
if (name == JS_ATOM_let && (tok == TOK_LET || tok == TOK_CONST)) {
|
|
js_parse_error(s, "'let' is not a valid lexical identifier");
|
|
goto var_error;
|
|
}
|
|
if (next_token(s))
|
|
goto var_error;
|
|
if (js_define_var(s, name, tok))
|
|
goto var_error;
|
|
if (export_flag) {
|
|
if (!add_export_entry(s, s->cur_func->module, name, name,
|
|
JS_EXPORT_TYPE_LOCAL))
|
|
goto var_error;
|
|
}
|
|
|
|
if (s->token.val == '=') {
|
|
if (next_token(s))
|
|
goto var_error;
|
|
if (tok == TOK_VAR) {
|
|
/* Must make a reference for proper `with` semantics */
|
|
int opcode, scope, label;
|
|
JSAtom name1;
|
|
|
|
emit_op(s, OP_scope_get_var);
|
|
emit_atom(s, name);
|
|
emit_u16(s, fd->scope_level);
|
|
if (get_lvalue(s, &opcode, &scope, &name1, &label, NULL, FALSE, '=') < 0)
|
|
goto var_error;
|
|
if (js_parse_assign_expr2(s, parse_flags)) {
|
|
JS_FreeAtom(ctx, name1);
|
|
goto var_error;
|
|
}
|
|
set_object_name(s, name);
|
|
put_lvalue(s, opcode, scope, name1, label,
|
|
PUT_LVALUE_NOKEEP, FALSE);
|
|
} else {
|
|
if (js_parse_assign_expr2(s, parse_flags))
|
|
goto var_error;
|
|
set_object_name(s, name);
|
|
emit_op(s, (tok == TOK_CONST || tok == TOK_LET) ?
|
|
OP_scope_put_var_init : OP_scope_put_var);
|
|
emit_atom(s, name);
|
|
emit_u16(s, fd->scope_level);
|
|
}
|
|
} else {
|
|
if (tok == TOK_CONST) {
|
|
js_parse_error(s, "missing initializer for const variable");
|
|
goto var_error;
|
|
}
|
|
if (tok == TOK_LET) {
|
|
/* initialize lexical variable upon entering its scope */
|
|
emit_op(s, OP_undefined);
|
|
emit_op(s, OP_scope_put_var_init);
|
|
emit_atom(s, name);
|
|
emit_u16(s, fd->scope_level);
|
|
}
|
|
}
|
|
JS_FreeAtom(ctx, name);
|
|
} else {
|
|
int skip_bits;
|
|
if ((s->token.val == '[' || s->token.val == '{')
|
|
&& js_parse_skip_parens_token(s, &skip_bits, FALSE) == '=') {
|
|
emit_op(s, OP_undefined);
|
|
if (js_parse_destructuring_element(s, tok, 0, TRUE, skip_bits & SKIP_HAS_ELLIPSIS, TRUE) < 0)
|
|
return -1;
|
|
} else {
|
|
return js_parse_error(s, "variable name expected");
|
|
}
|
|
}
|
|
if (s->token.val != ',')
|
|
break;
|
|
if (next_token(s))
|
|
return -1;
|
|
}
|
|
return 0;
|
|
|
|
var_error:
|
|
JS_FreeAtom(ctx, name);
|
|
return -1;
|
|
}
|
|
|
|
/* test if the current token is a label. Use simplistic look-ahead scanner */
|
|
static BOOL is_label(JSParseState *s)
|
|
{
|
|
return (s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved &&
|
|
peek_token(s, FALSE) == ':');
|
|
}
|
|
|
|
/* test if the current token is a let keyword. Use simplistic look-ahead scanner */
|
|
static int is_let(JSParseState *s, int decl_mask)
|
|
{
|
|
int res = FALSE;
|
|
|
|
if (token_is_pseudo_keyword(s, JS_ATOM_let)) {
|
|
JSParsePos pos;
|
|
js_parse_get_pos(s, &pos);
|
|
for (;;) {
|
|
if (next_token(s)) {
|
|
res = -1;
|
|
break;
|
|
}
|
|
if (s->token.val == '[') {
|
|
/* let [ is a syntax restriction:
|
|
it never introduces an ExpressionStatement */
|
|
res = TRUE;
|
|
break;
|
|
}
|
|
if (s->token.val == '{' ||
|
|
(s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved) ||
|
|
s->token.val == TOK_LET ||
|
|
s->token.val == TOK_YIELD ||
|
|
s->token.val == TOK_AWAIT) {
|
|
/* Check for possible ASI if not scanning for Declaration */
|
|
/* XXX: should also check that `{` introduces a BindingPattern,
|
|
but Firefox does not and rejects eval("let=1;let\n{if(1)2;}") */
|
|
if (s->last_line_num == s->token.line_num || (decl_mask & DECL_MASK_OTHER)) {
|
|
res = TRUE;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
if (js_parse_seek_token(s, &pos)) {
|
|
res = -1;
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
/* XXX: handle IteratorClose when exiting the loop before the
|
|
enumeration is done */
|
|
static __exception int js_parse_for_in_of(JSParseState *s, int label_name,
|
|
BOOL is_async)
|
|
{
|
|
JSContext *ctx = s->ctx;
|
|
JSFunctionDef *fd = s->cur_func;
|
|
JSAtom var_name;
|
|
BOOL has_initializer, is_for_of, has_destructuring;
|
|
int tok, tok1, opcode, scope, block_scope_level;
|
|
int label_next, label_expr, label_cont, label_body, label_break;
|
|
int pos_next, pos_expr;
|
|
BlockEnv break_entry;
|
|
|
|
has_initializer = FALSE;
|
|
has_destructuring = FALSE;
|
|
is_for_of = FALSE;
|
|
block_scope_level = fd->scope_level;
|
|
label_cont = new_label(s);
|
|
label_body = new_label(s);
|
|
label_break = new_label(s);
|
|
label_next = new_label(s);
|
|
|
|
/* create scope for the lexical variables declared in the enumeration
|
|
expressions. XXX: Not completely correct because of weird capturing
|
|
semantics in `for (i of o) a.push(function(){return i})` */
|
|
push_scope(s);
|
|
|
|
/* local for_in scope starts here so individual elements
|
|
can be closed in statement. */
|
|
push_break_entry(s->cur_func, &break_entry,
|
|
label_name, label_break, label_cont, 1);
|
|
break_entry.scope_level = block_scope_level;
|
|
|
|
label_expr = emit_goto(s, OP_goto, -1);
|
|
|
|
pos_next = s->cur_func->byte_code.size;
|
|
emit_label(s, label_next);
|
|
|
|
tok = s->token.val;
|
|
switch (is_let(s, DECL_MASK_OTHER)) {
|
|
case TRUE:
|
|
tok = TOK_LET;
|
|
break;
|
|
case FALSE:
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
if (tok == TOK_VAR || tok == TOK_LET || tok == TOK_CONST) {
|
|
if (next_token(s))
|
|
return -1;
|
|
|
|
if (!(s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved)) {
|
|
if (s->token.val == '[' || s->token.val == '{') {
|
|
if (js_parse_destructuring_element(s, tok, 0, TRUE, -1, FALSE) < 0)
|
|
return -1;
|
|
has_destructuring = TRUE;
|
|
} else {
|
|
return js_parse_error(s, "variable name expected");
|
|
}
|
|
var_name = JS_ATOM_NULL;
|
|
} else {
|
|
var_name = JS_DupAtom(ctx, s->token.u.ident.atom);
|
|
if (next_token(s)) {
|
|
JS_FreeAtom(s->ctx, var_name);
|
|
return -1;
|
|
}
|
|
if (js_define_var(s, var_name, tok)) {
|
|
JS_FreeAtom(s->ctx, var_name);
|
|
return -1;
|
|
}
|
|
emit_op(s, (tok == TOK_CONST || tok == TOK_LET) ?
|
|
OP_scope_put_var_init : OP_scope_put_var);
|
|
emit_atom(s, var_name);
|
|
emit_u16(s, fd->scope_level);
|
|
}
|
|
} else if (!is_async && token_is_pseudo_keyword(s, JS_ATOM_async) && peek_token(s, FALSE) == TOK_OF) {
|
|
return js_parse_error(s, "'for of' expression cannot start with 'async'");
|
|
} else {
|
|
int skip_bits;
|
|
if ((s->token.val == '[' || s->token.val == '{')
|
|
&& ((tok1 = js_parse_skip_parens_token(s, &skip_bits, FALSE)) == TOK_IN || tok1 == TOK_OF)) {
|
|
if (js_parse_destructuring_element(s, 0, 0, TRUE, skip_bits & SKIP_HAS_ELLIPSIS, TRUE) < 0)
|
|
return -1;
|
|
} else {
|
|
int lvalue_label;
|
|
if (js_parse_left_hand_side_expr(s))
|
|
return -1;
|
|
if (get_lvalue(s, &opcode, &scope, &var_name, &lvalue_label,
|
|
NULL, FALSE, TOK_FOR))
|
|
return -1;
|
|
put_lvalue(s, opcode, scope, var_name, lvalue_label,
|
|
PUT_LVALUE_NOKEEP_BOTTOM, FALSE);
|
|
}
|
|
var_name = JS_ATOM_NULL;
|
|
}
|
|
emit_goto(s, OP_goto, label_body);
|
|
|
|
pos_expr = s->cur_func->byte_code.size;
|
|
emit_label(s, label_expr);
|
|
if (s->token.val == '=') {
|
|
/* XXX: potential scoping issue if inside `with` statement */
|
|
has_initializer = TRUE;
|
|
/* parse and evaluate initializer prior to evaluating the
|
|
object (only used with "for in" with a non lexical variable
|
|
in non strict mode */
|
|
if (next_token(s) || js_parse_assign_expr2(s, 0)) {
|
|
JS_FreeAtom(ctx, var_name);
|
|
return -1;
|
|
}
|
|
if (var_name != JS_ATOM_NULL) {
|
|
emit_op(s, OP_scope_put_var);
|
|
emit_atom(s, var_name);
|
|
emit_u16(s, fd->scope_level);
|
|
}
|
|
}
|
|
JS_FreeAtom(ctx, var_name);
|
|
|
|
if (token_is_pseudo_keyword(s, JS_ATOM_of)) {
|
|
break_entry.has_iterator = is_for_of = TRUE;
|
|
break_entry.drop_count += 2;
|
|
if (has_initializer)
|
|
goto initializer_error;
|
|
} else if (s->token.val == TOK_IN) {
|
|
if (is_async)
|
|
return js_parse_error(s, "'for await' loop should be used with 'of'");
|
|
if (has_initializer &&
|
|
(tok != TOK_VAR || (fd->js_mode & JS_MODE_STRICT) ||
|
|
has_destructuring)) {
|
|
initializer_error:
|
|
return js_parse_error(s, "a declaration in the head of a for-%s loop can't have an initializer",
|
|
is_for_of ? "of" : "in");
|
|
}
|
|
} else {
|
|
return js_parse_error(s, "expected 'of' or 'in' in for control expression");
|
|
}
|
|
if (next_token(s))
|
|
return -1;
|
|
if (is_for_of) {
|
|
if (js_parse_assign_expr(s))
|
|
return -1;
|
|
} else {
|
|
if (js_parse_expr(s))
|
|
return -1;
|
|
}
|
|
/* close the scope after having evaluated the expression so that
|
|
the TDZ values are in the closures */
|
|
close_scopes(s, s->cur_func->scope_level, block_scope_level);
|
|
if (is_for_of) {
|
|
if (is_async)
|
|
emit_op(s, OP_for_await_of_start);
|
|
else
|
|
emit_op(s, OP_for_of_start);
|
|
/* on stack: enum_rec */
|
|
} else {
|
|
emit_op(s, OP_for_in_start);
|
|
/* on stack: enum_obj */
|
|
}
|
|
emit_goto(s, OP_goto, label_cont);
|
|
|
|
if (js_parse_expect(s, ')'))
|
|
return -1;
|
|
|
|
{
|
|
/* move the `next` code here */
|
|
DynBuf *bc = &s->cur_func->byte_code;
|
|
int chunk_size = pos_expr - pos_next;
|
|
int offset = bc->size - pos_next;
|
|
int i;
|
|
dbuf_realloc(bc, bc->size + chunk_size);
|
|
dbuf_put(bc, bc->buf + pos_next, chunk_size);
|
|
memset(bc->buf + pos_next, OP_nop, chunk_size);
|
|
/* `next` part ends with a goto */
|
|
s->cur_func->last_opcode_pos = bc->size - 5;
|
|
/* relocate labels */
|
|
for (i = label_cont; i < s->cur_func->label_count; i++) {
|
|
LabelSlot *ls = &s->cur_func->label_slots[i];
|
|
if (ls->pos >= pos_next && ls->pos < pos_expr)
|
|
ls->pos += offset;
|
|
}
|
|
}
|
|
|
|
emit_label(s, label_body);
|
|
if (js_parse_statement(s))
|
|
return -1;
|
|
|
|
close_scopes(s, s->cur_func->scope_level, block_scope_level);
|
|
|
|
emit_label(s, label_cont);
|
|
if (is_for_of) {
|
|
if (is_async) {
|
|
/* call the next method */
|
|
/* stack: iter_obj next catch_offset */
|
|
emit_op(s, OP_dup3);
|
|
emit_op(s, OP_drop);
|
|
emit_op(s, OP_call_method);
|
|
emit_u16(s, 0);
|
|
/* get the result of the promise */
|
|
emit_op(s, OP_await);
|
|
/* unwrap the value and done values */
|
|
emit_op(s, OP_iterator_get_value_done);
|
|
} else {
|
|
emit_op(s, OP_for_of_next);
|
|
emit_u8(s, 0);
|
|
}
|
|
} else {
|
|
emit_op(s, OP_for_in_next);
|
|
}
|
|
/* on stack: enum_rec / enum_obj value bool */
|
|
emit_goto(s, OP_if_false, label_next);
|
|
/* drop the undefined value from for_xx_next */
|
|
emit_op(s, OP_drop);
|
|
|
|
emit_label(s, label_break);
|
|
if (is_for_of) {
|
|
/* close and drop enum_rec */
|
|
emit_op(s, OP_iterator_close);
|
|
} else {
|
|
emit_op(s, OP_drop);
|
|
}
|
|
pop_break_entry(s->cur_func);
|
|
pop_scope(s);
|
|
return 0;
|
|
}
|
|
|
|
static void set_eval_ret_undefined(JSParseState *s)
|
|
{
|
|
if (s->cur_func->eval_ret_idx >= 0) {
|
|
emit_op(s, OP_undefined);
|
|
emit_op(s, OP_put_loc);
|
|
emit_u16(s, s->cur_func->eval_ret_idx);
|
|
}
|
|
}
|
|
|
|
static __exception int js_parse_statement_or_decl(JSParseState *s,
|
|
int decl_mask)
|
|
{
|
|
JSContext *ctx = s->ctx;
|
|
JSAtom label_name;
|
|
int tok;
|
|
|
|
/* specific label handling */
|
|
/* XXX: support multiple labels on loop statements */
|
|
label_name = JS_ATOM_NULL;
|
|
if (is_label(s)) {
|
|
BlockEnv *be;
|
|
|
|
label_name = JS_DupAtom(ctx, s->token.u.ident.atom);
|
|
|
|
for (be = s->cur_func->top_break; be; be = be->prev) {
|
|
if (be->label_name == label_name) {
|
|
js_parse_error(s, "duplicate label name");
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
if (next_token(s))
|
|
goto fail;
|
|
if (js_parse_expect(s, ':'))
|
|
goto fail;
|
|
if (s->token.val != TOK_FOR
|
|
&& s->token.val != TOK_DO
|
|
&& s->token.val != TOK_WHILE) {
|
|
/* labelled regular statement */
|
|
int label_break, mask;
|
|
BlockEnv break_entry;
|
|
|
|
label_break = new_label(s);
|
|
push_break_entry(s->cur_func, &break_entry,
|
|
label_name, label_break, -1, 0);
|
|
if (!(s->cur_func->js_mode & JS_MODE_STRICT) &&
|
|
(decl_mask & DECL_MASK_FUNC_WITH_LABEL)) {
|
|
mask = DECL_MASK_FUNC | DECL_MASK_FUNC_WITH_LABEL;
|
|
} else {
|
|
mask = 0;
|
|
}
|
|
if (js_parse_statement_or_decl(s, mask))
|
|
goto fail;
|
|
emit_label(s, label_break);
|
|
pop_break_entry(s->cur_func);
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
switch(tok = s->token.val) {
|
|
case '{':
|
|
if (js_parse_block(s))
|
|
goto fail;
|
|
break;
|
|
case TOK_RETURN:
|
|
if (s->cur_func->is_eval) {
|
|
js_parse_error(s, "return not in a function");
|
|
goto fail;
|
|
}
|
|
if (s->cur_func->func_type == JS_PARSE_FUNC_CLASS_STATIC_INIT) {
|
|
js_parse_error(s, "return in a static initializer block");
|
|
goto fail;
|
|
}
|
|
if (next_token(s))
|
|
goto fail;
|
|
if (s->token.val != ';' && s->token.val != '}' && !s->got_lf) {
|
|
if (js_parse_expr(s))
|
|
goto fail;
|
|
emit_return(s, TRUE);
|
|
} else {
|
|
emit_return(s, FALSE);
|
|
}
|
|
if (js_parse_expect_semi(s))
|
|
goto fail;
|
|
break;
|
|
case TOK_THROW:
|
|
if (next_token(s))
|
|
goto fail;
|
|
if (s->got_lf) {
|
|
js_parse_error(s, "line terminator not allowed after throw");
|
|
goto fail;
|
|
}
|
|
if (js_parse_expr(s))
|
|
goto fail;
|
|
emit_op(s, OP_throw);
|
|
if (js_parse_expect_semi(s))
|
|
goto fail;
|
|
break;
|
|
case TOK_LET:
|
|
case TOK_CONST:
|
|
haslet:
|
|
if (!(decl_mask & DECL_MASK_OTHER)) {
|
|
js_parse_error(s, "lexical declarations can't appear in single-statement context");
|
|
goto fail;
|
|
}
|
|
/* fall thru */
|
|
case TOK_VAR:
|
|
if (next_token(s))
|
|
goto fail;
|
|
if (js_parse_var(s, TRUE, tok, FALSE))
|
|
goto fail;
|
|
if (js_parse_expect_semi(s))
|
|
goto fail;
|
|
break;
|
|
case TOK_IF:
|
|
{
|
|
int label1, label2, mask;
|
|
if (next_token(s))
|
|
goto fail;
|
|
/* create a new scope for `let f;if(1) function f(){}` */
|
|
push_scope(s);
|
|
set_eval_ret_undefined(s);
|
|
if (js_parse_expr_paren(s))
|
|
goto fail;
|
|
label1 = emit_goto(s, OP_if_false, -1);
|
|
if (s->cur_func->js_mode & JS_MODE_STRICT)
|
|
mask = 0;
|
|
else
|
|
mask = DECL_MASK_FUNC; /* Annex B.3.4 */
|
|
|
|
if (js_parse_statement_or_decl(s, mask))
|
|
goto fail;
|
|
|
|
if (s->token.val == TOK_ELSE) {
|
|
label2 = emit_goto(s, OP_goto, -1);
|
|
if (next_token(s))
|
|
goto fail;
|
|
|
|
emit_label(s, label1);
|
|
if (js_parse_statement_or_decl(s, mask))
|
|
goto fail;
|
|
|
|
label1 = label2;
|
|
}
|
|
emit_label(s, label1);
|
|
pop_scope(s);
|
|
}
|
|
break;
|
|
case TOK_WHILE:
|
|
{
|
|
int label_cont, label_break;
|
|
BlockEnv break_entry;
|
|
|
|
label_cont = new_label(s);
|
|
label_break = new_label(s);
|
|
|
|
push_break_entry(s->cur_func, &break_entry,
|
|
label_name, label_break, label_cont, 0);
|
|
|
|
if (next_token(s))
|
|
goto fail;
|
|
|
|
set_eval_ret_undefined(s);
|
|
|
|
emit_label(s, label_cont);
|
|
if (js_parse_expr_paren(s))
|
|
goto fail;
|
|
emit_goto(s, OP_if_false, label_break);
|
|
|
|
if (js_parse_statement(s))
|
|
goto fail;
|
|
emit_goto(s, OP_goto, label_cont);
|
|
|
|
emit_label(s, label_break);
|
|
|
|
pop_break_entry(s->cur_func);
|
|
}
|
|
break;
|
|
case TOK_DO:
|
|
{
|
|
int label_cont, label_break, label1;
|
|
BlockEnv break_entry;
|
|
|
|
label_cont = new_label(s);
|
|
label_break = new_label(s);
|
|
label1 = new_label(s);
|
|
|
|
push_break_entry(s->cur_func, &break_entry,
|
|
label_name, label_break, label_cont, 0);
|
|
|
|
if (next_token(s))
|
|
goto fail;
|
|
|
|
emit_label(s, label1);
|
|
|
|
set_eval_ret_undefined(s);
|
|
|
|
if (js_parse_statement(s))
|
|
goto fail;
|
|
|
|
emit_label(s, label_cont);
|
|
if (js_parse_expect(s, TOK_WHILE))
|
|
goto fail;
|
|
if (js_parse_expr_paren(s))
|
|
goto fail;
|
|
/* Insert semicolon if missing */
|
|
if (s->token.val == ';') {
|
|
if (next_token(s))
|
|
goto fail;
|
|
}
|
|
emit_goto(s, OP_if_true, label1);
|
|
|
|
emit_label(s, label_break);
|
|
|
|
pop_break_entry(s->cur_func);
|
|
}
|
|
break;
|
|
case TOK_FOR:
|
|
{
|
|
int label_cont, label_break, label_body, label_test;
|
|
int pos_cont, pos_body, block_scope_level;
|
|
BlockEnv break_entry;
|
|
int tok, bits;
|
|
BOOL is_async;
|
|
|
|
if (next_token(s))
|
|
goto fail;
|
|
|
|
set_eval_ret_undefined(s);
|
|
bits = 0;
|
|
is_async = FALSE;
|
|
if (s->token.val == '(') {
|
|
js_parse_skip_parens_token(s, &bits, FALSE);
|
|
} else if (s->token.val == TOK_AWAIT) {
|
|
if (!(s->cur_func->func_kind & JS_FUNC_ASYNC)) {
|
|
js_parse_error(s, "for await is only valid in asynchronous functions");
|
|
goto fail;
|
|
}
|
|
is_async = TRUE;
|
|
if (next_token(s))
|
|
goto fail;
|
|
}
|
|
if (js_parse_expect(s, '('))
|
|
goto fail;
|
|
|
|
if (!(bits & SKIP_HAS_SEMI)) {
|
|
/* parse for/in or for/of */
|
|
if (js_parse_for_in_of(s, label_name, is_async))
|
|
goto fail;
|
|
break;
|
|
}
|
|
block_scope_level = s->cur_func->scope_level;
|
|
|
|
/* create scope for the lexical variables declared in the initial,
|
|
test and increment expressions */
|
|
push_scope(s);
|
|
/* initial expression */
|
|
tok = s->token.val;
|
|
if (tok != ';') {
|
|
switch (is_let(s, DECL_MASK_OTHER)) {
|
|
case TRUE:
|
|
tok = TOK_LET;
|
|
break;
|
|
case FALSE:
|
|
break;
|
|
default:
|
|
goto fail;
|
|
}
|
|
if (tok == TOK_VAR || tok == TOK_LET || tok == TOK_CONST) {
|
|
if (next_token(s))
|
|
goto fail;
|
|
if (js_parse_var(s, FALSE, tok, FALSE))
|
|
goto fail;
|
|
} else {
|
|
if (js_parse_expr2(s, FALSE))
|
|
goto fail;
|
|
emit_op(s, OP_drop);
|
|
}
|
|
|
|
/* close the closures before the first iteration */
|
|
close_scopes(s, s->cur_func->scope_level, block_scope_level);
|
|
}
|
|
if (js_parse_expect(s, ';'))
|
|
goto fail;
|
|
|
|
label_test = new_label(s);
|
|
label_cont = new_label(s);
|
|
label_body = new_label(s);
|
|
label_break = new_label(s);
|
|
|
|
push_break_entry(s->cur_func, &break_entry,
|
|
label_name, label_break, label_cont, 0);
|
|
|
|
/* test expression */
|
|
if (s->token.val == ';') {
|
|
/* no test expression */
|
|
label_test = label_body;
|
|
} else {
|
|
emit_label(s, label_test);
|
|
if (js_parse_expr(s))
|
|
goto fail;
|
|
emit_goto(s, OP_if_false, label_break);
|
|
}
|
|
if (js_parse_expect(s, ';'))
|
|
goto fail;
|
|
|
|
if (s->token.val == ')') {
|
|
/* no end expression */
|
|
break_entry.label_cont = label_cont = label_test;
|
|
pos_cont = 0; /* avoid warning */
|
|
} else {
|
|
/* skip the end expression */
|
|
emit_goto(s, OP_goto, label_body);
|
|
|
|
pos_cont = s->cur_func->byte_code.size;
|
|
emit_label(s, label_cont);
|
|
if (js_parse_expr(s))
|
|
goto fail;
|
|
emit_op(s, OP_drop);
|
|
if (label_test != label_body)
|
|
emit_goto(s, OP_goto, label_test);
|
|
}
|
|
if (js_parse_expect(s, ')'))
|
|
goto fail;
|
|
|
|
pos_body = s->cur_func->byte_code.size;
|
|
emit_label(s, label_body);
|
|
if (js_parse_statement(s))
|
|
goto fail;
|
|
|
|
/* close the closures before the next iteration */
|
|
/* XXX: check continue case */
|
|
close_scopes(s, s->cur_func->scope_level, block_scope_level);
|
|
|
|
if (label_test != label_body && label_cont != label_test) {
|
|
/* move the increment code here */
|
|
DynBuf *bc = &s->cur_func->byte_code;
|
|
int chunk_size = pos_body - pos_cont;
|
|
int offset = bc->size - pos_cont;
|
|
int i;
|
|
dbuf_realloc(bc, bc->size + chunk_size);
|
|
dbuf_put(bc, bc->buf + pos_cont, chunk_size);
|
|
memset(bc->buf + pos_cont, OP_nop, chunk_size);
|
|
/* increment part ends with a goto */
|
|
s->cur_func->last_opcode_pos = bc->size - 5;
|
|
/* relocate labels */
|
|
for (i = label_cont; i < s->cur_func->label_count; i++) {
|
|
LabelSlot *ls = &s->cur_func->label_slots[i];
|
|
if (ls->pos >= pos_cont && ls->pos < pos_body)
|
|
ls->pos += offset;
|
|
}
|
|
} else {
|
|
emit_goto(s, OP_goto, label_cont);
|
|
}
|
|
|
|
emit_label(s, label_break);
|
|
|
|
pop_break_entry(s->cur_func);
|
|
pop_scope(s);
|
|
}
|
|
break;
|
|
case TOK_BREAK:
|
|
case TOK_CONTINUE:
|
|
{
|
|
int is_cont = s->token.val - TOK_BREAK;
|
|
int label;
|
|
|
|
if (next_token(s))
|
|
goto fail;
|
|
if (!s->got_lf && s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved)
|
|
label = s->token.u.ident.atom;
|
|
else
|
|
label = JS_ATOM_NULL;
|
|
if (emit_break(s, label, is_cont))
|
|
goto fail;
|
|
if (label != JS_ATOM_NULL) {
|
|
if (next_token(s))
|
|
goto fail;
|
|
}
|
|
if (js_parse_expect_semi(s))
|
|
goto fail;
|
|
}
|
|
break;
|
|
case TOK_SWITCH:
|
|
{
|
|
int label_case, label_break, label1;
|
|
int default_label_pos;
|
|
BlockEnv break_entry;
|
|
|
|
if (next_token(s))
|
|
goto fail;
|
|
|
|
set_eval_ret_undefined(s);
|
|
if (js_parse_expr_paren(s))
|
|
goto fail;
|
|
|
|
push_scope(s);
|
|
label_break = new_label(s);
|
|
push_break_entry(s->cur_func, &break_entry,
|
|
label_name, label_break, -1, 1);
|
|
|
|
if (js_parse_expect(s, '{'))
|
|
goto fail;
|
|
|
|
default_label_pos = -1;
|
|
label_case = -1;
|
|
while (s->token.val != '}') {
|
|
if (s->token.val == TOK_CASE) {
|
|
label1 = -1;
|
|
if (label_case >= 0) {
|
|
/* skip the case if needed */
|
|
label1 = emit_goto(s, OP_goto, -1);
|
|
}
|
|
emit_label(s, label_case);
|
|
label_case = -1;
|
|
for (;;) {
|
|
/* parse a sequence of case clauses */
|
|
if (next_token(s))
|
|
goto fail;
|
|
emit_op(s, OP_dup);
|
|
if (js_parse_expr(s))
|
|
goto fail;
|
|
if (js_parse_expect(s, ':'))
|
|
goto fail;
|
|
emit_op(s, OP_strict_eq);
|
|
if (s->token.val == TOK_CASE) {
|
|
label1 = emit_goto(s, OP_if_true, label1);
|
|
} else {
|
|
label_case = emit_goto(s, OP_if_false, -1);
|
|
emit_label(s, label1);
|
|
break;
|
|
}
|
|
}
|
|
} else if (s->token.val == TOK_DEFAULT) {
|
|
if (next_token(s))
|
|
goto fail;
|
|
if (js_parse_expect(s, ':'))
|
|
goto fail;
|
|
if (default_label_pos >= 0) {
|
|
js_parse_error(s, "duplicate default");
|
|
goto fail;
|
|
}
|
|
if (label_case < 0) {
|
|
/* falling thru direct from switch expression */
|
|
label_case = emit_goto(s, OP_goto, -1);
|
|
}
|
|
/* Emit a dummy label opcode. Label will be patched after
|
|
the end of the switch body. Do not use emit_label(s, 0)
|
|
because it would clobber label 0 address, preventing
|
|
proper optimizer operation.
|
|
*/
|
|
emit_op(s, OP_label);
|
|
emit_u32(s, 0);
|
|
default_label_pos = s->cur_func->byte_code.size - 4;
|
|
} else {
|
|
if (label_case < 0) {
|
|
/* falling thru direct from switch expression */
|
|
js_parse_error(s, "invalid switch statement");
|
|
goto fail;
|
|
}
|
|
if (js_parse_statement_or_decl(s, DECL_MASK_ALL))
|
|
goto fail;
|
|
}
|
|
}
|
|
if (js_parse_expect(s, '}'))
|
|
goto fail;
|
|
if (default_label_pos >= 0) {
|
|
/* Ugly patch for the the `default` label, shameful and risky */
|
|
put_u32(s->cur_func->byte_code.buf + default_label_pos,
|
|
label_case);
|
|
s->cur_func->label_slots[label_case].pos = default_label_pos + 4;
|
|
} else {
|
|
emit_label(s, label_case);
|
|
}
|
|
emit_label(s, label_break);
|
|
emit_op(s, OP_drop); /* drop the switch expression */
|
|
|
|
pop_break_entry(s->cur_func);
|
|
pop_scope(s);
|
|
}
|
|
break;
|
|
case TOK_TRY:
|
|
{
|
|
int label_catch, label_catch2, label_finally, label_end;
|
|
JSAtom name;
|
|
BlockEnv block_env;
|
|
|
|
set_eval_ret_undefined(s);
|
|
if (next_token(s))
|
|
goto fail;
|
|
label_catch = new_label(s);
|
|
label_catch2 = new_label(s);
|
|
label_finally = new_label(s);
|
|
label_end = new_label(s);
|
|
|
|
emit_goto(s, OP_catch, label_catch);
|
|
|
|
push_break_entry(s->cur_func, &block_env,
|
|
JS_ATOM_NULL, -1, -1, 1);
|
|
block_env.label_finally = label_finally;
|
|
|
|
if (js_parse_block(s))
|
|
goto fail;
|
|
|
|
pop_break_entry(s->cur_func);
|
|
|
|
if (js_is_live_code(s)) {
|
|
/* drop the catch offset */
|
|
emit_op(s, OP_drop);
|
|
/* must push dummy value to keep same stack size */
|
|
emit_op(s, OP_undefined);
|
|
emit_goto(s, OP_gosub, label_finally);
|
|
emit_op(s, OP_drop);
|
|
|
|
emit_goto(s, OP_goto, label_end);
|
|
}
|
|
|
|
if (s->token.val == TOK_CATCH) {
|
|
if (next_token(s))
|
|
goto fail;
|
|
|
|
push_scope(s); /* catch variable */
|
|
emit_label(s, label_catch);
|
|
|
|
if (s->token.val == '{') {
|
|
/* support optional-catch-binding feature */
|
|
emit_op(s, OP_drop); /* pop the exception object */
|
|
} else {
|
|
if (js_parse_expect(s, '('))
|
|
goto fail;
|
|
if (!(s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved)) {
|
|
if (s->token.val == '[' || s->token.val == '{') {
|
|
/* XXX: TOK_LET is not completely correct */
|
|
if (js_parse_destructuring_element(s, TOK_LET, 0, TRUE, -1, TRUE) < 0)
|
|
goto fail;
|
|
} else {
|
|
js_parse_error(s, "identifier expected");
|
|
goto fail;
|
|
}
|
|
} else {
|
|
name = JS_DupAtom(ctx, s->token.u.ident.atom);
|
|
if (next_token(s)
|
|
|| js_define_var(s, name, TOK_CATCH) < 0) {
|
|
JS_FreeAtom(ctx, name);
|
|
goto fail;
|
|
}
|
|
/* store the exception value in the catch variable */
|
|
emit_op(s, OP_scope_put_var);
|
|
emit_u32(s, name);
|
|
emit_u16(s, s->cur_func->scope_level);
|
|
}
|
|
if (js_parse_expect(s, ')'))
|
|
goto fail;
|
|
}
|
|
/* XXX: should keep the address to nop it out if there is no finally block */
|
|
emit_goto(s, OP_catch, label_catch2);
|
|
|
|
push_scope(s); /* catch block */
|
|
push_break_entry(s->cur_func, &block_env, JS_ATOM_NULL,
|
|
-1, -1, 1);
|
|
block_env.label_finally = label_finally;
|
|
|
|
if (js_parse_block(s))
|
|
goto fail;
|
|
|
|
pop_break_entry(s->cur_func);
|
|
pop_scope(s); /* catch block */
|
|
pop_scope(s); /* catch variable */
|
|
|
|
if (js_is_live_code(s)) {
|
|
/* drop the catch2 offset */
|
|
emit_op(s, OP_drop);
|
|
/* XXX: should keep the address to nop it out if there is no finally block */
|
|
/* must push dummy value to keep same stack size */
|
|
emit_op(s, OP_undefined);
|
|
emit_goto(s, OP_gosub, label_finally);
|
|
emit_op(s, OP_drop);
|
|
emit_goto(s, OP_goto, label_end);
|
|
}
|
|
/* catch exceptions thrown in the catch block to execute the
|
|
* finally clause and rethrow the exception */
|
|
emit_label(s, label_catch2);
|
|
/* catch value is at TOS, no need to push undefined */
|
|
emit_goto(s, OP_gosub, label_finally);
|
|
emit_op(s, OP_throw);
|
|
|
|
} else if (s->token.val == TOK_FINALLY) {
|
|
/* finally without catch : execute the finally clause
|
|
* and rethrow the exception */
|
|
emit_label(s, label_catch);
|
|
/* catch value is at TOS, no need to push undefined */
|
|
emit_goto(s, OP_gosub, label_finally);
|
|
emit_op(s, OP_throw);
|
|
} else {
|
|
js_parse_error(s, "expecting catch or finally");
|
|
goto fail;
|
|
}
|
|
emit_label(s, label_finally);
|
|
if (s->token.val == TOK_FINALLY) {
|
|
int saved_eval_ret_idx = 0; /* avoid warning */
|
|
|
|
if (next_token(s))
|
|
goto fail;
|
|
/* on the stack: ret_value gosub_ret_value */
|
|
push_break_entry(s->cur_func, &block_env, JS_ATOM_NULL,
|
|
-1, -1, 2);
|
|
|
|
if (s->cur_func->eval_ret_idx >= 0) {
|
|
/* 'finally' updates eval_ret only if not a normal
|
|
termination */
|
|
saved_eval_ret_idx =
|
|
add_var(s->ctx, s->cur_func, JS_ATOM__ret_);
|
|
if (saved_eval_ret_idx < 0)
|
|
goto fail;
|
|
emit_op(s, OP_get_loc);
|
|
emit_u16(s, s->cur_func->eval_ret_idx);
|
|
emit_op(s, OP_put_loc);
|
|
emit_u16(s, saved_eval_ret_idx);
|
|
set_eval_ret_undefined(s);
|
|
}
|
|
|
|
if (js_parse_block(s))
|
|
goto fail;
|
|
|
|
if (s->cur_func->eval_ret_idx >= 0) {
|
|
emit_op(s, OP_get_loc);
|
|
emit_u16(s, saved_eval_ret_idx);
|
|
emit_op(s, OP_put_loc);
|
|
emit_u16(s, s->cur_func->eval_ret_idx);
|
|
}
|
|
pop_break_entry(s->cur_func);
|
|
}
|
|
emit_op(s, OP_ret);
|
|
emit_label(s, label_end);
|
|
}
|
|
break;
|
|
case ';':
|
|
/* empty statement */
|
|
if (next_token(s))
|
|
goto fail;
|
|
break;
|
|
case TOK_WITH:
|
|
if (s->cur_func->js_mode & JS_MODE_STRICT) {
|
|
js_parse_error(s, "invalid keyword: with");
|
|
goto fail;
|
|
} else {
|
|
int with_idx;
|
|
|
|
if (next_token(s))
|
|
goto fail;
|
|
|
|
if (js_parse_expr_paren(s))
|
|
goto fail;
|
|
|
|
push_scope(s);
|
|
with_idx = define_var(s, s->cur_func, JS_ATOM__with_,
|
|
JS_VAR_DEF_WITH);
|
|
if (with_idx < 0)
|
|
goto fail;
|
|
emit_op(s, OP_to_object);
|
|
emit_op(s, OP_put_loc);
|
|
emit_u16(s, with_idx);
|
|
|
|
set_eval_ret_undefined(s);
|
|
if (js_parse_statement(s))
|
|
goto fail;
|
|
|
|
/* Popping scope drops lexical context for the with object variable */
|
|
pop_scope(s);
|
|
}
|
|
break;
|
|
case TOK_FUNCTION:
|
|
/* ES6 Annex B.3.2 and B.3.3 semantics */
|
|
if (!(decl_mask & DECL_MASK_FUNC))
|
|
goto func_decl_error;
|
|
if (!(decl_mask & DECL_MASK_OTHER) && peek_token(s, FALSE) == '*')
|
|
goto func_decl_error;
|
|
goto parse_func_var;
|
|
case TOK_IDENT:
|
|
if (s->token.u.ident.is_reserved) {
|
|
js_parse_error_reserved_identifier(s);
|
|
goto fail;
|
|
}
|
|
/* Determine if `let` introduces a Declaration or an ExpressionStatement */
|
|
switch (is_let(s, decl_mask)) {
|
|
case TRUE:
|
|
tok = TOK_LET;
|
|
goto haslet;
|
|
case FALSE:
|
|
break;
|
|
default:
|
|
goto fail;
|
|
}
|
|
if (token_is_pseudo_keyword(s, JS_ATOM_async) &&
|
|
peek_token(s, TRUE) == TOK_FUNCTION) {
|
|
if (!(decl_mask & DECL_MASK_OTHER)) {
|
|
func_decl_error:
|
|
js_parse_error(s, "function declarations can't appear in single-statement context");
|
|
goto fail;
|
|
}
|
|
parse_func_var:
|
|
if (js_parse_function_decl(s, JS_PARSE_FUNC_VAR,
|
|
JS_FUNC_NORMAL, JS_ATOM_NULL,
|
|
s->token.ptr,
|
|
s->token.line_num,
|
|
s->token.col_num))
|
|
goto fail;
|
|
break;
|
|
}
|
|
goto hasexpr;
|
|
|
|
case TOK_CLASS:
|
|
if (!(decl_mask & DECL_MASK_OTHER)) {
|
|
js_parse_error(s, "class declarations can't appear in single-statement context");
|
|
goto fail;
|
|
}
|
|
if (js_parse_class(s, FALSE, JS_PARSE_EXPORT_NONE))
|
|
return -1;
|
|
break;
|
|
|
|
case TOK_DEBUGGER:
|
|
/* currently no debugger, so just skip the keyword */
|
|
if (next_token(s))
|
|
goto fail;
|
|
if (js_parse_expect_semi(s))
|
|
goto fail;
|
|
break;
|
|
|
|
case TOK_ENUM:
|
|
case TOK_EXPORT:
|
|
case TOK_EXTENDS:
|
|
js_unsupported_keyword(s, s->token.u.ident.atom);
|
|
goto fail;
|
|
|
|
default:
|
|
hasexpr:
|
|
if (js_parse_expr(s))
|
|
goto fail;
|
|
if (s->cur_func->eval_ret_idx >= 0) {
|
|
/* store the expression value so that it can be returned
|
|
by eval() */
|
|
emit_op(s, OP_put_loc);
|
|
emit_u16(s, s->cur_func->eval_ret_idx);
|
|
} else {
|
|
emit_op(s, OP_drop); /* drop the result */
|
|
}
|
|
if (js_parse_expect_semi(s))
|
|
goto fail;
|
|
break;
|
|
}
|
|
done:
|
|
JS_FreeAtom(ctx, label_name);
|
|
return 0;
|
|
fail:
|
|
JS_FreeAtom(ctx, label_name);
|
|
return -1;
|
|
}
|
|
|
|
/* 'name' is freed */
|
|
static JSModuleDef *js_new_module_def(JSContext *ctx, JSAtom name)
|
|
{
|
|
JSModuleDef *m;
|
|
m = js_mallocz(ctx, sizeof(*m));
|
|
if (!m) {
|
|
JS_FreeAtom(ctx, name);
|
|
return NULL;
|
|
}
|
|
m->header.ref_count = 1;
|
|
m->module_name = name;
|
|
m->promise = JS_UNDEFINED;
|
|
m->module_ns = JS_UNDEFINED;
|
|
m->func_obj = JS_UNDEFINED;
|
|
m->eval_exception = JS_UNDEFINED;
|
|
m->meta_obj = JS_UNDEFINED;
|
|
list_add_tail(&m->link, &ctx->loaded_modules);
|
|
return m;
|
|
}
|
|
|
|
static void js_mark_module_def(JSRuntime *rt, JSModuleDef *m,
|
|
JS_MarkFunc *mark_func)
|
|
{
|
|
int i;
|
|
|
|
for(i = 0; i < m->export_entries_count; i++) {
|
|
JSExportEntry *me = &m->export_entries[i];
|
|
if (me->export_type == JS_EXPORT_TYPE_LOCAL &&
|
|
me->u.local.var_ref) {
|
|
mark_func(rt, &me->u.local.var_ref->header);
|
|
}
|
|
}
|
|
|
|
JS_MarkValue(rt, m->promise, mark_func);
|
|
JS_MarkValue(rt, m->module_ns, mark_func);
|
|
JS_MarkValue(rt, m->func_obj, mark_func);
|
|
JS_MarkValue(rt, m->eval_exception, mark_func);
|
|
JS_MarkValue(rt, m->meta_obj, mark_func);
|
|
}
|
|
|
|
static void js_free_module_def(JSContext *ctx, JSModuleDef *m)
|
|
{
|
|
int i;
|
|
|
|
JS_FreeAtom(ctx, m->module_name);
|
|
|
|
for(i = 0; i < m->req_module_entries_count; i++) {
|
|
JSReqModuleEntry *rme = &m->req_module_entries[i];
|
|
JS_FreeAtom(ctx, rme->module_name);
|
|
}
|
|
js_free(ctx, m->req_module_entries);
|
|
|
|
for(i = 0; i < m->export_entries_count; i++) {
|
|
JSExportEntry *me = &m->export_entries[i];
|
|
if (me->export_type == JS_EXPORT_TYPE_LOCAL)
|
|
free_var_ref(ctx->rt, me->u.local.var_ref);
|
|
JS_FreeAtom(ctx, me->export_name);
|
|
JS_FreeAtom(ctx, me->local_name);
|
|
}
|
|
js_free(ctx, m->export_entries);
|
|
|
|
js_free(ctx, m->star_export_entries);
|
|
|
|
for(i = 0; i < m->import_entries_count; i++) {
|
|
JSImportEntry *mi = &m->import_entries[i];
|
|
JS_FreeAtom(ctx, mi->import_name);
|
|
}
|
|
js_free(ctx, m->import_entries);
|
|
|
|
JS_FreeValue(ctx, m->promise);
|
|
JS_FreeValue(ctx, m->module_ns);
|
|
JS_FreeValue(ctx, m->func_obj);
|
|
JS_FreeValue(ctx, m->eval_exception);
|
|
JS_FreeValue(ctx, m->meta_obj);
|
|
list_del(&m->link);
|
|
js_free(ctx, m);
|
|
}
|
|
|
|
static int add_req_module_entry(JSContext *ctx, JSModuleDef *m,
|
|
JSAtom module_name)
|
|
{
|
|
JSReqModuleEntry *rme;
|
|
int i;
|
|
|
|
/* no need to add the module request if it is already present */
|
|
for(i = 0; i < m->req_module_entries_count; i++) {
|
|
rme = &m->req_module_entries[i];
|
|
if (rme->module_name == module_name)
|
|
return i;
|
|
}
|
|
|
|
if (js_resize_array(ctx, (void **)&m->req_module_entries,
|
|
sizeof(JSReqModuleEntry),
|
|
&m->req_module_entries_size,
|
|
m->req_module_entries_count + 1))
|
|
return -1;
|
|
rme = &m->req_module_entries[m->req_module_entries_count++];
|
|
rme->module_name = JS_DupAtom(ctx, module_name);
|
|
rme->module = NULL;
|
|
return i;
|
|
}
|
|
|
|
static JSExportEntry *find_export_entry(JSContext *ctx, const JSModuleDef *m,
|
|
JSAtom export_name)
|
|
{
|
|
JSExportEntry *me;
|
|
int i;
|
|
for(i = 0; i < m->export_entries_count; i++) {
|
|
me = &m->export_entries[i];
|
|
if (me->export_name == export_name)
|
|
return me;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static JSExportEntry *add_export_entry2(JSContext *ctx,
|
|
JSParseState *s, JSModuleDef *m,
|
|
JSAtom local_name, JSAtom export_name,
|
|
JSExportTypeEnum export_type)
|
|
{
|
|
JSExportEntry *me;
|
|
|
|
if (find_export_entry(ctx, m, export_name)) {
|
|
char buf1[ATOM_GET_STR_BUF_SIZE];
|
|
if (s) {
|
|
js_parse_error(s, "duplicate exported name '%s'",
|
|
JS_AtomGetStr(ctx, buf1, sizeof(buf1), export_name));
|
|
} else {
|
|
JS_ThrowSyntaxErrorAtom(ctx, "duplicate exported name '%s'", export_name);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
if (js_resize_array(ctx, (void **)&m->export_entries,
|
|
sizeof(JSExportEntry),
|
|
&m->export_entries_size,
|
|
m->export_entries_count + 1))
|
|
return NULL;
|
|
me = &m->export_entries[m->export_entries_count++];
|
|
memset(me, 0, sizeof(*me));
|
|
me->local_name = JS_DupAtom(ctx, local_name);
|
|
me->export_name = JS_DupAtom(ctx, export_name);
|
|
me->export_type = export_type;
|
|
return me;
|
|
}
|
|
|
|
static JSExportEntry *add_export_entry(JSParseState *s, JSModuleDef *m,
|
|
JSAtom local_name, JSAtom export_name,
|
|
JSExportTypeEnum export_type)
|
|
{
|
|
return add_export_entry2(s->ctx, s, m, local_name, export_name,
|
|
export_type);
|
|
}
|
|
|
|
static int add_star_export_entry(JSContext *ctx, JSModuleDef *m,
|
|
int req_module_idx)
|
|
{
|
|
JSStarExportEntry *se;
|
|
|
|
if (js_resize_array(ctx, (void **)&m->star_export_entries,
|
|
sizeof(JSStarExportEntry),
|
|
&m->star_export_entries_size,
|
|
m->star_export_entries_count + 1))
|
|
return -1;
|
|
se = &m->star_export_entries[m->star_export_entries_count++];
|
|
se->req_module_idx = req_module_idx;
|
|
return 0;
|
|
}
|
|
|
|
/* create a C module */
|
|
/* `name_str` may be pure ASCII or UTF-8 encoded */
|
|
JSModuleDef *JS_NewCModule(JSContext *ctx, const char *name_str,
|
|
JSModuleInitFunc *func)
|
|
{
|
|
JSModuleDef *m;
|
|
JSAtom name;
|
|
name = JS_NewAtom(ctx, name_str);
|
|
if (name == JS_ATOM_NULL)
|
|
return NULL;
|
|
m = js_new_module_def(ctx, name);
|
|
m->init_func = func;
|
|
return m;
|
|
}
|
|
|
|
/* `export_name` may be pure ASCII or UTF-8 encoded */
|
|
int JS_AddModuleExport(JSContext *ctx, JSModuleDef *m, const char *export_name)
|
|
{
|
|
JSExportEntry *me;
|
|
JSAtom name;
|
|
name = JS_NewAtom(ctx, export_name);
|
|
if (name == JS_ATOM_NULL)
|
|
return -1;
|
|
me = add_export_entry2(ctx, NULL, m, JS_ATOM_NULL, name,
|
|
JS_EXPORT_TYPE_LOCAL);
|
|
JS_FreeAtom(ctx, name);
|
|
if (!me)
|
|
return -1;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
/* `export_name` may be pure ASCII or UTF-8 encoded */
|
|
int JS_SetModuleExport(JSContext *ctx, JSModuleDef *m, const char *export_name,
|
|
JSValue val)
|
|
{
|
|
JSExportEntry *me;
|
|
JSAtom name;
|
|
name = JS_NewAtom(ctx, export_name);
|
|
if (name == JS_ATOM_NULL)
|
|
goto fail;
|
|
me = find_export_entry(ctx, m, name);
|
|
JS_FreeAtom(ctx, name);
|
|
if (!me)
|
|
goto fail;
|
|
set_value(ctx, me->u.local.var_ref->pvalue, val);
|
|
return 0;
|
|
fail:
|
|
JS_FreeValue(ctx, val);
|
|
return -1;
|
|
}
|
|
|
|
void JS_SetModuleLoaderFunc(JSRuntime *rt,
|
|
JSModuleNormalizeFunc *module_normalize,
|
|
JSModuleLoaderFunc *module_loader, void *opaque)
|
|
{
|
|
rt->module_normalize_func = module_normalize;
|
|
rt->module_loader_func = module_loader;
|
|
rt->module_loader_opaque = opaque;
|
|
}
|
|
|
|
/* default module filename normalizer */
|
|
static char *js_default_module_normalize_name(JSContext *ctx,
|
|
const char *base_name,
|
|
const char *name)
|
|
{
|
|
char *filename, *p;
|
|
const char *r;
|
|
int cap;
|
|
int len;
|
|
|
|
if (name[0] != '.') {
|
|
/* if no initial dot, the module name is not modified */
|
|
return js_strdup(ctx, name);
|
|
}
|
|
|
|
p = strrchr(base_name, '/');
|
|
if (p)
|
|
len = p - base_name;
|
|
else
|
|
len = 0;
|
|
|
|
cap = len + strlen(name) + 1 + 1;
|
|
filename = js_malloc(ctx, cap);
|
|
if (!filename)
|
|
return NULL;
|
|
memcpy(filename, base_name, len);
|
|
filename[len] = '\0';
|
|
|
|
/* we only normalize the leading '..' or '.' */
|
|
r = name;
|
|
for(;;) {
|
|
if (r[0] == '.' && r[1] == '/') {
|
|
r += 2;
|
|
} else if (r[0] == '.' && r[1] == '.' && r[2] == '/') {
|
|
/* remove the last path element of filename, except if "."
|
|
or ".." */
|
|
if (filename[0] == '\0')
|
|
break;
|
|
p = strrchr(filename, '/');
|
|
if (!p)
|
|
p = filename;
|
|
else
|
|
p++;
|
|
if (!strcmp(p, ".") || !strcmp(p, ".."))
|
|
break;
|
|
if (p > filename)
|
|
p--;
|
|
*p = '\0';
|
|
r += 3;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
if (filename[0] != '\0')
|
|
pstrcat(filename, cap, "/");
|
|
pstrcat(filename, cap, r);
|
|
// printf("normalize: %s %s -> %s\n", base_name, name, filename);
|
|
return filename;
|
|
}
|
|
|
|
static JSModuleDef *js_find_loaded_module(JSContext *ctx, JSAtom name)
|
|
{
|
|
struct list_head *el;
|
|
JSModuleDef *m;
|
|
|
|
/* first look at the loaded modules */
|
|
list_for_each(el, &ctx->loaded_modules) {
|
|
m = list_entry(el, JSModuleDef, link);
|
|
if (m->module_name == name)
|
|
return m;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* return NULL in case of exception (e.g. module could not be loaded) */
|
|
/* `base_cname` and `cname1` may be pure ASCII or UTF-8 encoded */
|
|
static JSModuleDef *js_host_resolve_imported_module(JSContext *ctx,
|
|
const char *base_cname,
|
|
const char *cname1)
|
|
{
|
|
JSRuntime *rt = ctx->rt;
|
|
JSModuleDef *m;
|
|
char *cname;
|
|
JSAtom module_name;
|
|
|
|
if (!rt->module_normalize_func) {
|
|
cname = js_default_module_normalize_name(ctx, base_cname, cname1);
|
|
} else {
|
|
cname = rt->module_normalize_func(ctx, base_cname, cname1,
|
|
rt->module_loader_opaque);
|
|
}
|
|
if (!cname)
|
|
return NULL;
|
|
|
|
module_name = JS_NewAtom(ctx, cname);
|
|
if (module_name == JS_ATOM_NULL) {
|
|
js_free(ctx, cname);
|
|
return NULL;
|
|
}
|
|
|
|
/* first look at the loaded modules */
|
|
m = js_find_loaded_module(ctx, module_name);
|
|
if (m) {
|
|
js_free(ctx, cname);
|
|
JS_FreeAtom(ctx, module_name);
|
|
return m;
|
|
}
|
|
|
|
JS_FreeAtom(ctx, module_name);
|
|
|
|
/* load the module */
|
|
if (!rt->module_loader_func) {
|
|
/* XXX: use a syntax error ? */
|
|
JS_ThrowReferenceError(ctx, "could not load module '%s'",
|
|
cname);
|
|
js_free(ctx, cname);
|
|
return NULL;
|
|
}
|
|
|
|
m = rt->module_loader_func(ctx, cname, rt->module_loader_opaque);
|
|
js_free(ctx, cname);
|
|
return m;
|
|
}
|
|
|
|
static JSModuleDef *js_host_resolve_imported_module_atom(JSContext *ctx,
|
|
JSAtom base_module_name,
|
|
JSAtom module_name1)
|
|
{
|
|
const char *base_cname, *cname;
|
|
JSModuleDef *m;
|
|
|
|
base_cname = JS_AtomToCString(ctx, base_module_name);
|
|
if (!base_cname)
|
|
return NULL;
|
|
cname = JS_AtomToCString(ctx, module_name1);
|
|
if (!cname) {
|
|
JS_FreeCString(ctx, base_cname);
|
|
return NULL;
|
|
}
|
|
m = js_host_resolve_imported_module(ctx, base_cname, cname);
|
|
JS_FreeCString(ctx, base_cname);
|
|
JS_FreeCString(ctx, cname);
|
|
return m;
|
|
}
|
|
|
|
typedef struct JSResolveEntry {
|
|
JSModuleDef *module;
|
|
JSAtom name;
|
|
} JSResolveEntry;
|
|
|
|
typedef struct JSResolveState {
|
|
JSResolveEntry *array;
|
|
int size;
|
|
int count;
|
|
} JSResolveState;
|
|
|
|
static int find_resolve_entry(JSResolveState *s,
|
|
JSModuleDef *m, JSAtom name)
|
|
{
|
|
int i;
|
|
for(i = 0; i < s->count; i++) {
|
|
JSResolveEntry *re = &s->array[i];
|
|
if (re->module == m && re->name == name)
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static int add_resolve_entry(JSContext *ctx, JSResolveState *s,
|
|
JSModuleDef *m, JSAtom name)
|
|
{
|
|
JSResolveEntry *re;
|
|
|
|
if (js_resize_array(ctx, (void **)&s->array,
|
|
sizeof(JSResolveEntry),
|
|
&s->size, s->count + 1))
|
|
return -1;
|
|
re = &s->array[s->count++];
|
|
re->module = m;
|
|
re->name = JS_DupAtom(ctx, name);
|
|
return 0;
|
|
}
|
|
|
|
typedef enum JSResolveResultEnum {
|
|
JS_RESOLVE_RES_EXCEPTION = -1, /* memory alloc error */
|
|
JS_RESOLVE_RES_FOUND = 0,
|
|
JS_RESOLVE_RES_NOT_FOUND,
|
|
JS_RESOLVE_RES_CIRCULAR,
|
|
JS_RESOLVE_RES_AMBIGUOUS,
|
|
} JSResolveResultEnum;
|
|
|
|
static JSResolveResultEnum js_resolve_export1(JSContext *ctx,
|
|
JSModuleDef **pmodule,
|
|
JSExportEntry **pme,
|
|
JSModuleDef *m,
|
|
JSAtom export_name,
|
|
JSResolveState *s)
|
|
{
|
|
JSExportEntry *me;
|
|
|
|
*pmodule = NULL;
|
|
*pme = NULL;
|
|
if (find_resolve_entry(s, m, export_name) >= 0)
|
|
return JS_RESOLVE_RES_CIRCULAR;
|
|
if (add_resolve_entry(ctx, s, m, export_name) < 0)
|
|
return JS_RESOLVE_RES_EXCEPTION;
|
|
me = find_export_entry(ctx, m, export_name);
|
|
if (me) {
|
|
if (me->export_type == JS_EXPORT_TYPE_LOCAL) {
|
|
/* local export */
|
|
*pmodule = m;
|
|
*pme = me;
|
|
return JS_RESOLVE_RES_FOUND;
|
|
} else {
|
|
/* indirect export */
|
|
JSModuleDef *m1;
|
|
m1 = m->req_module_entries[me->u.req_module_idx].module;
|
|
if (me->local_name == JS_ATOM__star_) {
|
|
/* export ns from */
|
|
*pmodule = m;
|
|
*pme = me;
|
|
return JS_RESOLVE_RES_FOUND;
|
|
} else {
|
|
return js_resolve_export1(ctx, pmodule, pme, m1,
|
|
me->local_name, s);
|
|
}
|
|
}
|
|
} else {
|
|
if (export_name != JS_ATOM_default) {
|
|
/* not found in direct or indirect exports: try star exports */
|
|
int i;
|
|
|
|
for(i = 0; i < m->star_export_entries_count; i++) {
|
|
JSStarExportEntry *se = &m->star_export_entries[i];
|
|
JSModuleDef *m1, *res_m;
|
|
JSExportEntry *res_me;
|
|
JSResolveResultEnum ret;
|
|
|
|
m1 = m->req_module_entries[se->req_module_idx].module;
|
|
ret = js_resolve_export1(ctx, &res_m, &res_me, m1,
|
|
export_name, s);
|
|
if (ret == JS_RESOLVE_RES_AMBIGUOUS ||
|
|
ret == JS_RESOLVE_RES_EXCEPTION) {
|
|
return ret;
|
|
} else if (ret == JS_RESOLVE_RES_FOUND) {
|
|
if (*pme != NULL) {
|
|
if (*pmodule != res_m ||
|
|
res_me->local_name != (*pme)->local_name) {
|
|
*pmodule = NULL;
|
|
*pme = NULL;
|
|
return JS_RESOLVE_RES_AMBIGUOUS;
|
|
}
|
|
} else {
|
|
*pmodule = res_m;
|
|
*pme = res_me;
|
|
}
|
|
}
|
|
}
|
|
if (*pme != NULL)
|
|
return JS_RESOLVE_RES_FOUND;
|
|
}
|
|
return JS_RESOLVE_RES_NOT_FOUND;
|
|
}
|
|
}
|
|
|
|
/* If the return value is JS_RESOLVE_RES_FOUND, return the module
|
|
(*pmodule) and the corresponding local export entry
|
|
(*pme). Otherwise return (NULL, NULL) */
|
|
static JSResolveResultEnum js_resolve_export(JSContext *ctx,
|
|
JSModuleDef **pmodule,
|
|
JSExportEntry **pme,
|
|
JSModuleDef *m,
|
|
JSAtom export_name)
|
|
{
|
|
JSResolveState ss, *s = &ss;
|
|
int i;
|
|
JSResolveResultEnum ret;
|
|
|
|
s->array = NULL;
|
|
s->size = 0;
|
|
s->count = 0;
|
|
|
|
ret = js_resolve_export1(ctx, pmodule, pme, m, export_name, s);
|
|
|
|
for(i = 0; i < s->count; i++)
|
|
JS_FreeAtom(ctx, s->array[i].name);
|
|
js_free(ctx, s->array);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void js_resolve_export_throw_error(JSContext *ctx,
|
|
JSResolveResultEnum res,
|
|
JSModuleDef *m, JSAtom export_name)
|
|
{
|
|
char buf1[ATOM_GET_STR_BUF_SIZE];
|
|
char buf2[ATOM_GET_STR_BUF_SIZE];
|
|
switch(res) {
|
|
case JS_RESOLVE_RES_EXCEPTION:
|
|
break;
|
|
default:
|
|
case JS_RESOLVE_RES_NOT_FOUND:
|
|
JS_ThrowSyntaxError(ctx, "Could not find export '%s' in module '%s'",
|
|
JS_AtomGetStr(ctx, buf1, sizeof(buf1), export_name),
|
|
JS_AtomGetStr(ctx, buf2, sizeof(buf2), m->module_name));
|
|
break;
|
|
case JS_RESOLVE_RES_CIRCULAR:
|
|
JS_ThrowSyntaxError(ctx, "circular reference when looking for export '%s' in module '%s'",
|
|
JS_AtomGetStr(ctx, buf1, sizeof(buf1), export_name),
|
|
JS_AtomGetStr(ctx, buf2, sizeof(buf2), m->module_name));
|
|
break;
|
|
case JS_RESOLVE_RES_AMBIGUOUS:
|
|
JS_ThrowSyntaxError(ctx, "export '%s' in module '%s' is ambiguous",
|
|
JS_AtomGetStr(ctx, buf1, sizeof(buf1), export_name),
|
|
JS_AtomGetStr(ctx, buf2, sizeof(buf2), m->module_name));
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
typedef enum {
|
|
EXPORTED_NAME_AMBIGUOUS,
|
|
EXPORTED_NAME_NORMAL,
|
|
EXPORTED_NAME_NS,
|
|
} ExportedNameEntryEnum;
|
|
|
|
typedef struct ExportedNameEntry {
|
|
JSAtom export_name;
|
|
ExportedNameEntryEnum export_type;
|
|
union {
|
|
JSExportEntry *me; /* using when the list is built */
|
|
JSVarRef *var_ref; /* EXPORTED_NAME_NORMAL */
|
|
JSModuleDef *module; /* for EXPORTED_NAME_NS */
|
|
} u;
|
|
} ExportedNameEntry;
|
|
|
|
typedef struct GetExportNamesState {
|
|
JSModuleDef **modules;
|
|
int modules_size;
|
|
int modules_count;
|
|
|
|
ExportedNameEntry *exported_names;
|
|
int exported_names_size;
|
|
int exported_names_count;
|
|
} GetExportNamesState;
|
|
|
|
static int find_exported_name(GetExportNamesState *s, JSAtom name)
|
|
{
|
|
int i;
|
|
for(i = 0; i < s->exported_names_count; i++) {
|
|
if (s->exported_names[i].export_name == name)
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static __exception int get_exported_names(JSContext *ctx,
|
|
GetExportNamesState *s,
|
|
JSModuleDef *m, BOOL from_star)
|
|
{
|
|
ExportedNameEntry *en;
|
|
int i, j;
|
|
|
|
/* check circular reference */
|
|
for(i = 0; i < s->modules_count; i++) {
|
|
if (s->modules[i] == m)
|
|
return 0;
|
|
}
|
|
if (js_resize_array(ctx, (void **)&s->modules, sizeof(s->modules[0]),
|
|
&s->modules_size, s->modules_count + 1))
|
|
return -1;
|
|
s->modules[s->modules_count++] = m;
|
|
|
|
for(i = 0; i < m->export_entries_count; i++) {
|
|
JSExportEntry *me = &m->export_entries[i];
|
|
if (from_star && me->export_name == JS_ATOM_default)
|
|
continue;
|
|
j = find_exported_name(s, me->export_name);
|
|
if (j < 0) {
|
|
if (js_resize_array(ctx, (void **)&s->exported_names, sizeof(s->exported_names[0]),
|
|
&s->exported_names_size,
|
|
s->exported_names_count + 1))
|
|
return -1;
|
|
en = &s->exported_names[s->exported_names_count++];
|
|
en->export_name = me->export_name;
|
|
/* avoid a second lookup for simple module exports */
|
|
if (from_star || me->export_type != JS_EXPORT_TYPE_LOCAL)
|
|
en->u.me = NULL;
|
|
else
|
|
en->u.me = me;
|
|
} else {
|
|
en = &s->exported_names[j];
|
|
en->u.me = NULL;
|
|
}
|
|
}
|
|
for(i = 0; i < m->star_export_entries_count; i++) {
|
|
JSStarExportEntry *se = &m->star_export_entries[i];
|
|
JSModuleDef *m1;
|
|
m1 = m->req_module_entries[se->req_module_idx].module;
|
|
if (get_exported_names(ctx, s, m1, TRUE))
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Unfortunately, the spec gives a different behavior from GetOwnProperty ! */
|
|
static int js_module_ns_has(JSContext *ctx, JSValue obj, JSAtom atom)
|
|
{
|
|
return (find_own_property1(JS_VALUE_GET_OBJ(obj), atom) != NULL);
|
|
}
|
|
|
|
static const JSClassExoticMethods js_module_ns_exotic_methods = {
|
|
.has_property = js_module_ns_has,
|
|
};
|
|
|
|
static int exported_names_cmp(const void *p1, const void *p2, void *opaque)
|
|
{
|
|
JSContext *ctx = opaque;
|
|
const ExportedNameEntry *me1 = p1;
|
|
const ExportedNameEntry *me2 = p2;
|
|
JSValue str1, str2;
|
|
int ret;
|
|
|
|
/* XXX: should avoid allocation memory in atom comparison */
|
|
str1 = JS_AtomToString(ctx, me1->export_name);
|
|
str2 = JS_AtomToString(ctx, me2->export_name);
|
|
if (JS_IsException(str1) || JS_IsException(str2)) {
|
|
/* XXX: raise an error ? */
|
|
ret = 0;
|
|
} else {
|
|
ret = js_string_compare(ctx, JS_VALUE_GET_STRING(str1),
|
|
JS_VALUE_GET_STRING(str2));
|
|
}
|
|
JS_FreeValue(ctx, str1);
|
|
JS_FreeValue(ctx, str2);
|
|
return ret;
|
|
}
|
|
|
|
static JSValue js_module_ns_autoinit(JSContext *ctx, JSObject *p, JSAtom atom,
|
|
void *opaque)
|
|
{
|
|
JSModuleDef *m = opaque;
|
|
return JS_GetModuleNamespace(ctx, m);
|
|
}
|
|
|
|
static JSValue js_build_module_ns(JSContext *ctx, JSModuleDef *m)
|
|
{
|
|
JSValue obj;
|
|
JSObject *p;
|
|
GetExportNamesState s_s, *s = &s_s;
|
|
int i, ret;
|
|
JSProperty *pr;
|
|
|
|
obj = JS_NewObjectClass(ctx, JS_CLASS_MODULE_NS);
|
|
if (JS_IsException(obj))
|
|
return obj;
|
|
p = JS_VALUE_GET_OBJ(obj);
|
|
|
|
memset(s, 0, sizeof(*s));
|
|
ret = get_exported_names(ctx, s, m, FALSE);
|
|
js_free(ctx, s->modules);
|
|
if (ret)
|
|
goto fail;
|
|
|
|
/* Resolve the exported names. The ambiguous exports are removed */
|
|
for(i = 0; i < s->exported_names_count; i++) {
|
|
ExportedNameEntry *en = &s->exported_names[i];
|
|
JSResolveResultEnum res;
|
|
JSExportEntry *res_me;
|
|
JSModuleDef *res_m;
|
|
|
|
if (en->u.me) {
|
|
res_me = en->u.me; /* fast case: no resolution needed */
|
|
res_m = m;
|
|
res = JS_RESOLVE_RES_FOUND;
|
|
} else {
|
|
res = js_resolve_export(ctx, &res_m, &res_me, m,
|
|
en->export_name);
|
|
}
|
|
if (res != JS_RESOLVE_RES_FOUND) {
|
|
if (res != JS_RESOLVE_RES_AMBIGUOUS) {
|
|
js_resolve_export_throw_error(ctx, res, m, en->export_name);
|
|
goto fail;
|
|
}
|
|
en->export_type = EXPORTED_NAME_AMBIGUOUS;
|
|
} else {
|
|
if (res_me->local_name == JS_ATOM__star_) {
|
|
en->export_type = EXPORTED_NAME_NS;
|
|
en->u.module = res_m->req_module_entries[res_me->u.req_module_idx].module;
|
|
} else {
|
|
en->export_type = EXPORTED_NAME_NORMAL;
|
|
if (res_me->u.local.var_ref) {
|
|
en->u.var_ref = res_me->u.local.var_ref;
|
|
} else {
|
|
JSObject *p1 = JS_VALUE_GET_OBJ(res_m->func_obj);
|
|
p1 = JS_VALUE_GET_OBJ(res_m->func_obj);
|
|
en->u.var_ref = p1->u.func.var_refs[res_me->u.local.var_idx];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* sort the exported names */
|
|
rqsort(s->exported_names, s->exported_names_count,
|
|
sizeof(s->exported_names[0]), exported_names_cmp, ctx);
|
|
|
|
for(i = 0; i < s->exported_names_count; i++) {
|
|
ExportedNameEntry *en = &s->exported_names[i];
|
|
switch(en->export_type) {
|
|
case EXPORTED_NAME_NORMAL:
|
|
{
|
|
JSVarRef *var_ref = en->u.var_ref;
|
|
pr = add_property(ctx, p, en->export_name,
|
|
JS_PROP_ENUMERABLE | JS_PROP_WRITABLE |
|
|
JS_PROP_VARREF);
|
|
if (!pr)
|
|
goto fail;
|
|
var_ref->header.ref_count++;
|
|
pr->u.var_ref = var_ref;
|
|
}
|
|
break;
|
|
case EXPORTED_NAME_NS:
|
|
/* the exported namespace must be created on demand */
|
|
if (JS_DefineAutoInitProperty(ctx, obj,
|
|
en->export_name,
|
|
JS_AUTOINIT_ID_MODULE_NS,
|
|
en->u.module, JS_PROP_ENUMERABLE | JS_PROP_WRITABLE) < 0)
|
|
goto fail;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
js_free(ctx, s->exported_names);
|
|
|
|
JS_DefinePropertyValue(ctx, obj, JS_ATOM_Symbol_toStringTag,
|
|
JS_AtomToString(ctx, JS_ATOM_Module),
|
|
0);
|
|
|
|
p->extensible = FALSE;
|
|
return obj;
|
|
fail:
|
|
js_free(ctx, s->exported_names);
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
JSValue JS_GetModuleNamespace(JSContext *ctx, JSModuleDef *m)
|
|
{
|
|
if (JS_IsUndefined(m->module_ns)) {
|
|
JSValue val;
|
|
val = js_build_module_ns(ctx, m);
|
|
if (JS_IsException(val))
|
|
return JS_EXCEPTION;
|
|
m->module_ns = val;
|
|
}
|
|
return js_dup(m->module_ns);
|
|
}
|
|
|
|
#ifdef DUMP_MODULE_RESOLVE
|
|
#define module_trace(ctx, ...) \
|
|
do if (check_dump_flag(ctx->rt, DUMP_MODULE_RESOLVE)) \
|
|
printf(__VA_ARGS__); while (0)
|
|
#else
|
|
#define module_trace(...)
|
|
#endif
|
|
|
|
/* Load all the required modules for module 'm' */
|
|
static int js_resolve_module(JSContext *ctx, JSModuleDef *m)
|
|
{
|
|
int i;
|
|
JSModuleDef *m1;
|
|
|
|
if (m->resolved)
|
|
return 0;
|
|
#ifdef DUMP_MODULE_RESOLVE
|
|
if (check_dump_flag(ctx->rt, DUMP_MODULE_RESOLVE)) {
|
|
char buf1[ATOM_GET_STR_BUF_SIZE];
|
|
printf("resolving module '%s':\n", JS_AtomGetStr(ctx, buf1, sizeof(buf1), m->module_name));
|
|
}
|
|
#endif
|
|
m->resolved = TRUE;
|
|
/* resolve each requested module */
|
|
for(i = 0; i < m->req_module_entries_count; i++) {
|
|
JSReqModuleEntry *rme = &m->req_module_entries[i];
|
|
m1 = js_host_resolve_imported_module_atom(ctx, m->module_name,
|
|
rme->module_name);
|
|
if (!m1)
|
|
return -1;
|
|
rme->module = m1;
|
|
/* already done in js_host_resolve_imported_module() except if
|
|
the module was loaded with JS_EvalBinary() */
|
|
if (js_resolve_module(ctx, m1) < 0)
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static JSVarRef *js_create_module_var(JSContext *ctx, BOOL is_lexical)
|
|
{
|
|
JSVarRef *var_ref;
|
|
var_ref = js_malloc(ctx, sizeof(JSVarRef));
|
|
if (!var_ref)
|
|
return NULL;
|
|
var_ref->header.ref_count = 1;
|
|
if (is_lexical)
|
|
var_ref->value = JS_UNINITIALIZED;
|
|
else
|
|
var_ref->value = JS_UNDEFINED;
|
|
var_ref->pvalue = &var_ref->value;
|
|
var_ref->is_detached = TRUE;
|
|
add_gc_object(ctx->rt, &var_ref->header, JS_GC_OBJ_TYPE_VAR_REF);
|
|
return var_ref;
|
|
}
|
|
|
|
/* Create the <eval> function associated with the module */
|
|
static int js_create_module_bytecode_function(JSContext *ctx, JSModuleDef *m)
|
|
{
|
|
JSFunctionBytecode *b;
|
|
int i;
|
|
JSVarRef **var_refs;
|
|
JSValue func_obj, bfunc;
|
|
JSObject *p;
|
|
|
|
bfunc = m->func_obj;
|
|
func_obj = JS_NewObjectProtoClass(ctx, ctx->function_proto,
|
|
JS_CLASS_BYTECODE_FUNCTION);
|
|
|
|
if (JS_IsException(func_obj))
|
|
return -1;
|
|
b = JS_VALUE_GET_PTR(bfunc);
|
|
|
|
p = JS_VALUE_GET_OBJ(func_obj);
|
|
p->u.func.function_bytecode = b;
|
|
b->header.ref_count++;
|
|
p->u.func.home_object = NULL;
|
|
p->u.func.var_refs = NULL;
|
|
if (b->closure_var_count) {
|
|
var_refs = js_mallocz(ctx, sizeof(var_refs[0]) * b->closure_var_count);
|
|
if (!var_refs)
|
|
goto fail;
|
|
p->u.func.var_refs = var_refs;
|
|
|
|
/* create the global variables. The other variables are
|
|
imported from other modules */
|
|
for(i = 0; i < b->closure_var_count; i++) {
|
|
JSClosureVar *cv = &b->closure_var[i];
|
|
JSVarRef *var_ref;
|
|
if (cv->is_local) {
|
|
var_ref = js_create_module_var(ctx, cv->is_lexical);
|
|
if (!var_ref)
|
|
goto fail;
|
|
|
|
module_trace(ctx, "local %d: %p\n", i, var_ref);
|
|
|
|
var_refs[i] = var_ref;
|
|
}
|
|
}
|
|
}
|
|
m->func_obj = func_obj;
|
|
JS_FreeValue(ctx, bfunc);
|
|
return 0;
|
|
fail:
|
|
JS_FreeValue(ctx, func_obj);
|
|
return -1;
|
|
}
|
|
|
|
/* must be done before js_link_module() because of cyclic references */
|
|
static int js_create_module_function(JSContext *ctx, JSModuleDef *m)
|
|
{
|
|
BOOL is_c_module;
|
|
int i;
|
|
JSVarRef *var_ref;
|
|
|
|
if (m->func_created)
|
|
return 0;
|
|
|
|
is_c_module = (m->init_func != NULL);
|
|
|
|
if (is_c_module) {
|
|
/* initialize the exported variables */
|
|
for(i = 0; i < m->export_entries_count; i++) {
|
|
JSExportEntry *me = &m->export_entries[i];
|
|
if (me->export_type == JS_EXPORT_TYPE_LOCAL) {
|
|
var_ref = js_create_module_var(ctx, FALSE);
|
|
if (!var_ref)
|
|
return -1;
|
|
me->u.local.var_ref = var_ref;
|
|
}
|
|
}
|
|
} else {
|
|
if (js_create_module_bytecode_function(ctx, m))
|
|
return -1;
|
|
}
|
|
m->func_created = TRUE;
|
|
|
|
/* do it on the dependencies */
|
|
|
|
for(i = 0; i < m->req_module_entries_count; i++) {
|
|
JSReqModuleEntry *rme = &m->req_module_entries[i];
|
|
if (js_create_module_function(ctx, rme->module) < 0)
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* Prepare a module to be executed by resolving all the imported
|
|
variables. */
|
|
static int js_link_module(JSContext *ctx, JSModuleDef *m)
|
|
{
|
|
int i;
|
|
JSImportEntry *mi;
|
|
JSModuleDef *m1;
|
|
JSVarRef **var_refs, *var_ref;
|
|
JSObject *p;
|
|
BOOL is_c_module;
|
|
JSValue ret_val;
|
|
|
|
if (m->instantiated)
|
|
return 0;
|
|
m->instantiated = TRUE;
|
|
|
|
#ifdef DUMP_MODULE_RESOLVE
|
|
if (check_dump_flag(ctx->rt, DUMP_MODULE_RESOLVE)) {
|
|
char buf1[ATOM_GET_STR_BUF_SIZE];
|
|
printf("start instantiating module '%s':\n", JS_AtomGetStr(ctx, buf1, sizeof(buf1), m->module_name));
|
|
}
|
|
#endif
|
|
|
|
for(i = 0; i < m->req_module_entries_count; i++) {
|
|
JSReqModuleEntry *rme = &m->req_module_entries[i];
|
|
if (js_link_module(ctx, rme->module) < 0)
|
|
goto fail;
|
|
}
|
|
|
|
#ifdef DUMP_MODULE_RESOLVE
|
|
if (check_dump_flag(ctx->rt, DUMP_MODULE_RESOLVE)) {
|
|
char buf1[ATOM_GET_STR_BUF_SIZE];
|
|
printf("instantiating module '%s':\n", JS_AtomGetStr(ctx, buf1, sizeof(buf1), m->module_name));
|
|
}
|
|
#endif
|
|
/* check the indirect exports */
|
|
for(i = 0; i < m->export_entries_count; i++) {
|
|
JSExportEntry *me = &m->export_entries[i];
|
|
if (me->export_type == JS_EXPORT_TYPE_INDIRECT &&
|
|
me->local_name != JS_ATOM__star_) {
|
|
JSResolveResultEnum ret;
|
|
JSExportEntry *res_me;
|
|
JSModuleDef *res_m, *m1;
|
|
m1 = m->req_module_entries[me->u.req_module_idx].module;
|
|
ret = js_resolve_export(ctx, &res_m, &res_me, m1, me->local_name);
|
|
if (ret != JS_RESOLVE_RES_FOUND) {
|
|
js_resolve_export_throw_error(ctx, ret, m, me->export_name);
|
|
goto fail;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef DUMP_MODULE_RESOLVE
|
|
if (check_dump_flag(ctx->rt, DUMP_MODULE_RESOLVE)) {
|
|
printf("exported bindings:\n");
|
|
for(i = 0; i < m->export_entries_count; i++) {
|
|
JSExportEntry *me = &m->export_entries[i];
|
|
printf(" name="); print_atom(ctx, me->export_name);
|
|
printf(" local="); print_atom(ctx, me->local_name);
|
|
printf(" type=%d idx=%d\n", me->export_type, me->u.local.var_idx);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
is_c_module = (m->init_func != NULL);
|
|
|
|
if (!is_c_module) {
|
|
p = JS_VALUE_GET_OBJ(m->func_obj);
|
|
var_refs = p->u.func.var_refs;
|
|
|
|
for(i = 0; i < m->import_entries_count; i++) {
|
|
mi = &m->import_entries[i];
|
|
#ifdef DUMP_MODULE_RESOLVE
|
|
if (check_dump_flag(ctx->rt, DUMP_MODULE_RESOLVE)) {
|
|
printf("import var_idx=%d name=", mi->var_idx);
|
|
print_atom(ctx, mi->import_name);
|
|
printf(": ");
|
|
}
|
|
#endif
|
|
m1 = m->req_module_entries[mi->req_module_idx].module;
|
|
if (mi->import_name == JS_ATOM__star_) {
|
|
JSValue val;
|
|
/* name space import */
|
|
val = JS_GetModuleNamespace(ctx, m1);
|
|
if (JS_IsException(val))
|
|
goto fail;
|
|
set_value(ctx, &var_refs[mi->var_idx]->value, val);
|
|
|
|
module_trace(ctx, "namespace\n");
|
|
|
|
} else {
|
|
JSResolveResultEnum ret;
|
|
JSExportEntry *res_me;
|
|
JSModuleDef *res_m;
|
|
JSObject *p1;
|
|
|
|
ret = js_resolve_export(ctx, &res_m,
|
|
&res_me, m1, mi->import_name);
|
|
if (ret != JS_RESOLVE_RES_FOUND) {
|
|
js_resolve_export_throw_error(ctx, ret, m1, mi->import_name);
|
|
goto fail;
|
|
}
|
|
if (res_me->local_name == JS_ATOM__star_) {
|
|
JSValue val;
|
|
JSModuleDef *m2;
|
|
/* name space import from */
|
|
m2 = res_m->req_module_entries[res_me->u.req_module_idx].module;
|
|
val = JS_GetModuleNamespace(ctx, m2);
|
|
if (JS_IsException(val))
|
|
goto fail;
|
|
var_ref = js_create_module_var(ctx, TRUE);
|
|
if (!var_ref) {
|
|
JS_FreeValue(ctx, val);
|
|
goto fail;
|
|
}
|
|
set_value(ctx, &var_ref->value, val);
|
|
var_refs[mi->var_idx] = var_ref;
|
|
|
|
module_trace(ctx, "namespace from\n");
|
|
|
|
} else {
|
|
var_ref = res_me->u.local.var_ref;
|
|
if (!var_ref) {
|
|
p1 = JS_VALUE_GET_OBJ(res_m->func_obj);
|
|
var_ref = p1->u.func.var_refs[res_me->u.local.var_idx];
|
|
}
|
|
var_ref->header.ref_count++;
|
|
var_refs[mi->var_idx] = var_ref;
|
|
|
|
module_trace(ctx, "local export (var_ref=%p)\n", var_ref);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* keep the exported variables in the module export entries (they
|
|
are used when the eval function is deleted and cannot be
|
|
initialized before in case imports are exported) */
|
|
for(i = 0; i < m->export_entries_count; i++) {
|
|
JSExportEntry *me = &m->export_entries[i];
|
|
if (me->export_type == JS_EXPORT_TYPE_LOCAL) {
|
|
var_ref = var_refs[me->u.local.var_idx];
|
|
var_ref->header.ref_count++;
|
|
me->u.local.var_ref = var_ref;
|
|
}
|
|
}
|
|
|
|
/* initialize the global variables */
|
|
ret_val = JS_Call(ctx, m->func_obj, JS_TRUE, 0, NULL);
|
|
if (JS_IsException(ret_val))
|
|
goto fail;
|
|
JS_FreeValue(ctx, ret_val);
|
|
}
|
|
|
|
module_trace(ctx, "done instantiate\n");
|
|
|
|
return 0;
|
|
fail:
|
|
return -1;
|
|
}
|
|
|
|
/* return JS_ATOM_NULL if the name cannot be found. Only works with
|
|
not striped bytecode functions. */
|
|
JSAtom JS_GetScriptOrModuleName(JSContext *ctx, int n_stack_levels)
|
|
{
|
|
JSStackFrame *sf;
|
|
JSFunctionBytecode *b;
|
|
JSObject *p;
|
|
/* XXX: currently we just use the filename of the englobing
|
|
function. It does not work for eval(). Need to add a
|
|
ScriptOrModule info in JSFunctionBytecode */
|
|
sf = ctx->rt->current_stack_frame;
|
|
if (!sf)
|
|
return JS_ATOM_NULL;
|
|
while (n_stack_levels-- > 0) {
|
|
sf = sf->prev_frame;
|
|
if (!sf)
|
|
return JS_ATOM_NULL;
|
|
}
|
|
if (JS_VALUE_GET_TAG(sf->cur_func) != JS_TAG_OBJECT)
|
|
return JS_ATOM_NULL;
|
|
p = JS_VALUE_GET_OBJ(sf->cur_func);
|
|
if (!js_class_has_bytecode(p->class_id))
|
|
return JS_ATOM_NULL;
|
|
b = p->u.func.function_bytecode;
|
|
return JS_DupAtom(ctx, b->filename);
|
|
}
|
|
|
|
JSAtom JS_GetModuleName(JSContext *ctx, JSModuleDef *m)
|
|
{
|
|
return JS_DupAtom(ctx, m->module_name);
|
|
}
|
|
|
|
JSValue JS_GetImportMeta(JSContext *ctx, JSModuleDef *m)
|
|
{
|
|
JSValue obj;
|
|
/* allocate meta_obj only if requested to save memory */
|
|
obj = m->meta_obj;
|
|
if (JS_IsUndefined(obj)) {
|
|
obj = JS_NewObjectProto(ctx, JS_NULL);
|
|
if (JS_IsException(obj))
|
|
return JS_EXCEPTION;
|
|
m->meta_obj = obj;
|
|
}
|
|
return js_dup(obj);
|
|
}
|
|
|
|
static JSValue js_import_meta(JSContext *ctx)
|
|
{
|
|
JSAtom filename;
|
|
JSModuleDef *m;
|
|
|
|
filename = JS_GetScriptOrModuleName(ctx, 0);
|
|
if (filename == JS_ATOM_NULL)
|
|
goto fail;
|
|
|
|
/* XXX: inefficient, need to add a module or script pointer in
|
|
JSFunctionBytecode */
|
|
m = js_find_loaded_module(ctx, filename);
|
|
JS_FreeAtom(ctx, filename);
|
|
if (!m) {
|
|
fail:
|
|
JS_ThrowTypeError(ctx, "import.meta not supported in this context");
|
|
return JS_EXCEPTION;
|
|
}
|
|
return JS_GetImportMeta(ctx, m);
|
|
}
|
|
|
|
/* used by os.Worker() and import() */
|
|
JSModuleDef *JS_RunModule(JSContext *ctx, const char *basename,
|
|
const char *filename)
|
|
{
|
|
JSModuleDef *m;
|
|
JSValue ret, func_obj;
|
|
|
|
m = js_host_resolve_imported_module(ctx, basename, filename);
|
|
if (!m)
|
|
return NULL;
|
|
|
|
if (js_resolve_module(ctx, m) < 0) {
|
|
js_free_modules(ctx, JS_FREE_MODULE_NOT_RESOLVED);
|
|
return NULL;
|
|
}
|
|
|
|
/* Evaluate the module code */
|
|
func_obj = js_dup(JS_MKPTR(JS_TAG_MODULE, m));
|
|
ret = JS_EvalFunction(ctx, func_obj);
|
|
if (JS_IsException(ret))
|
|
return NULL;
|
|
JS_FreeValue(ctx, ret);
|
|
return m;
|
|
}
|
|
|
|
static JSValue js_dynamic_import_resolve(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv, int magic, JSValue *func_data)
|
|
{
|
|
return JS_Call(ctx, func_data[0], JS_UNDEFINED, 1, &func_data[2]);
|
|
}
|
|
|
|
static JSValue js_dynamic_import_reject(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv, int magic, JSValue *func_data)
|
|
{
|
|
return JS_Call(ctx, func_data[1], JS_UNDEFINED, 1, &argv[0]);
|
|
}
|
|
|
|
static JSValue js_dynamic_import_job(JSContext *ctx,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue *resolving_funcs = argv;
|
|
JSValue basename_val = argv[2];
|
|
JSValue specifier = argv[3];
|
|
JSModuleDef *m;
|
|
const char *basename = NULL, *filename;
|
|
JSValue ret, err, ns;
|
|
|
|
if (!JS_IsString(basename_val)) {
|
|
JS_ThrowTypeError(ctx, "no function filename for import()");
|
|
goto exception;
|
|
}
|
|
basename = JS_ToCString(ctx, basename_val);
|
|
if (!basename)
|
|
goto exception;
|
|
|
|
filename = JS_ToCString(ctx, specifier);
|
|
if (!filename)
|
|
goto exception;
|
|
|
|
m = JS_RunModule(ctx, basename, filename);
|
|
JS_FreeCString(ctx, filename);
|
|
if (!m)
|
|
goto exception;
|
|
|
|
/* return the module namespace */
|
|
ns = JS_GetModuleNamespace(ctx, m);
|
|
if (JS_IsException(ns))
|
|
goto exception;
|
|
|
|
if (!JS_IsUndefined(m->promise)) {
|
|
JSValue args[] = {argv[0], argv[1], ns};
|
|
JSValue funcs[2];
|
|
funcs[0] = JS_NewCFunctionData(ctx, js_dynamic_import_resolve, 0, 0, 3, args);
|
|
funcs[1] = JS_NewCFunctionData(ctx, js_dynamic_import_reject, 0, 0, 3, args);
|
|
JS_FreeValue(ctx, js_promise_then(ctx, m->promise, 2, funcs));
|
|
|
|
JS_FreeValue(ctx, funcs[0]);
|
|
JS_FreeValue(ctx, funcs[1]);
|
|
JS_FreeValue(ctx, ns);
|
|
JS_FreeCString(ctx, basename);
|
|
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
ret = JS_Call(ctx, resolving_funcs[0], JS_UNDEFINED,
|
|
1, &ns);
|
|
JS_FreeValue(ctx, ret); /* XXX: what to do if exception ? */
|
|
JS_FreeValue(ctx, ns);
|
|
JS_FreeCString(ctx, basename);
|
|
return JS_UNDEFINED;
|
|
exception:
|
|
|
|
err = JS_GetException(ctx);
|
|
ret = JS_Call(ctx, resolving_funcs[1], JS_UNDEFINED,
|
|
1, &err);
|
|
JS_FreeValue(ctx, ret); /* XXX: what to do if exception ? */
|
|
JS_FreeValue(ctx, err);
|
|
JS_FreeCString(ctx, basename);
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
static JSValue js_dynamic_import(JSContext *ctx, JSValue specifier)
|
|
{
|
|
JSAtom basename;
|
|
JSValue promise, resolving_funcs[2], basename_val;
|
|
JSValue args[4];
|
|
|
|
basename = JS_GetScriptOrModuleName(ctx, 0);
|
|
if (basename == JS_ATOM_NULL)
|
|
basename_val = JS_NULL;
|
|
else
|
|
basename_val = JS_AtomToValue(ctx, basename);
|
|
JS_FreeAtom(ctx, basename);
|
|
if (JS_IsException(basename_val))
|
|
return basename_val;
|
|
|
|
promise = JS_NewPromiseCapability(ctx, resolving_funcs);
|
|
if (JS_IsException(promise)) {
|
|
JS_FreeValue(ctx, basename_val);
|
|
return promise;
|
|
}
|
|
|
|
args[0] = resolving_funcs[0];
|
|
args[1] = resolving_funcs[1];
|
|
args[2] = basename_val;
|
|
args[3] = specifier;
|
|
|
|
JS_EnqueueJob(ctx, js_dynamic_import_job, 4, args);
|
|
|
|
JS_FreeValue(ctx, basename_val);
|
|
JS_FreeValue(ctx, resolving_funcs[0]);
|
|
JS_FreeValue(ctx, resolving_funcs[1]);
|
|
return promise;
|
|
}
|
|
|
|
static JSValue js_async_function_call2(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv, int magic, JSValue *func_data)
|
|
{
|
|
return js_async_function_call(ctx, func_data[0], this_val, argc, argv, magic);
|
|
}
|
|
|
|
/* Run the <eval> function of the module and of all its requested
|
|
modules. */
|
|
static JSValue js_evaluate_module(JSContext *ctx, JSModuleDef *m)
|
|
{
|
|
JSModuleDef *m1;
|
|
int i;
|
|
JSValue ret_val;
|
|
|
|
if (m->eval_mark)
|
|
return JS_UNDEFINED; /* avoid cycles */
|
|
|
|
if (m->evaluated) {
|
|
/* if the module was already evaluated, rethrow the exception
|
|
it raised */
|
|
if (m->eval_has_exception) {
|
|
return JS_Throw(ctx, js_dup(m->eval_exception));
|
|
} else {
|
|
return js_dup(m->promise);
|
|
}
|
|
}
|
|
|
|
m->eval_mark = TRUE;
|
|
|
|
JSValue promises = JS_NewArray(ctx);
|
|
if (JS_IsException(promises))
|
|
return JS_EXCEPTION;
|
|
|
|
BOOL async = FALSE;
|
|
JSValue promise = JS_UNDEFINED;
|
|
for(i = 0; i < m->req_module_entries_count; i++) {
|
|
JSReqModuleEntry *rme = &m->req_module_entries[i];
|
|
m1 = rme->module;
|
|
if (!m1->eval_mark) {
|
|
ret_val = js_evaluate_module(ctx, m1);
|
|
if (JS_IsException(ret_val)) {
|
|
m->eval_mark = FALSE;
|
|
js_free_modules(ctx, JS_FREE_MODULE_NOT_EVALUATED);
|
|
goto clean;
|
|
}
|
|
if (!JS_IsUndefined(ret_val)) {
|
|
js_array_push(ctx, promises, 1, &ret_val, 0);
|
|
JS_FreeValue(ctx, ret_val);
|
|
async = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
promise = js_promise_all(ctx, ctx->promise_ctor, 1, &promises, 0);
|
|
if (JS_IsException(promise)) {
|
|
JS_FreeValue(ctx, promises);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
if (m->init_func) {
|
|
/* C module init */
|
|
if (m->init_func(ctx, m) < 0)
|
|
ret_val = JS_EXCEPTION;
|
|
else
|
|
ret_val = JS_UNDEFINED;
|
|
} else if (!async) {
|
|
ret_val = js_async_function_call(ctx, m->func_obj, JS_UNDEFINED, 0, NULL, 0);
|
|
JS_FreeValue(ctx, m->func_obj);
|
|
m->func_obj = JS_UNDEFINED;
|
|
JSPromiseData *s = JS_GetOpaque(ret_val, JS_CLASS_PROMISE);
|
|
if (s->promise_state != JS_PROMISE_PENDING) {
|
|
JSValue ret_val2 = ret_val;
|
|
if (s->promise_state == JS_PROMISE_REJECTED)
|
|
ret_val = JS_Throw(ctx, js_dup(s->promise_result));
|
|
else
|
|
ret_val = js_dup(s->promise_result);
|
|
JS_FreeValue(ctx, ret_val2);
|
|
}
|
|
} else {
|
|
JSValue funcs[2];
|
|
funcs[0] = JS_NewCFunctionData(ctx, js_async_function_call2, 0, 0, 1, &m->func_obj);
|
|
funcs[1] = JS_UNDEFINED;
|
|
ret_val = js_promise_then(ctx, promise, 2, funcs);
|
|
JS_FreeValue(ctx, funcs[0]);
|
|
JS_FreeValue(ctx, m->func_obj);
|
|
m->func_obj = JS_UNDEFINED;
|
|
}
|
|
if (JS_IsException(ret_val)) {
|
|
/* save the thrown exception value */
|
|
m->eval_has_exception = TRUE;
|
|
m->eval_exception = js_dup(ctx->rt->current_exception);
|
|
} else if (!JS_IsUndefined(ret_val)) {
|
|
m->promise = js_dup(ret_val);
|
|
}
|
|
m->eval_mark = FALSE;
|
|
m->evaluated = TRUE;
|
|
clean:
|
|
JS_FreeValue(ctx, promises);
|
|
JS_FreeValue(ctx, promise);
|
|
return ret_val;
|
|
}
|
|
|
|
static __exception JSAtom js_parse_from_clause(JSParseState *s)
|
|
{
|
|
JSAtom module_name;
|
|
if (!token_is_pseudo_keyword(s, JS_ATOM_from)) {
|
|
js_parse_error(s, "from clause expected");
|
|
return JS_ATOM_NULL;
|
|
}
|
|
if (next_token(s))
|
|
return JS_ATOM_NULL;
|
|
if (s->token.val != TOK_STRING) {
|
|
js_parse_error(s, "string expected");
|
|
return JS_ATOM_NULL;
|
|
}
|
|
module_name = JS_ValueToAtom(s->ctx, s->token.u.str.str);
|
|
if (module_name == JS_ATOM_NULL)
|
|
return JS_ATOM_NULL;
|
|
if (next_token(s)) {
|
|
JS_FreeAtom(s->ctx, module_name);
|
|
return JS_ATOM_NULL;
|
|
}
|
|
return module_name;
|
|
}
|
|
|
|
static __exception int js_parse_export(JSParseState *s)
|
|
{
|
|
JSContext *ctx = s->ctx;
|
|
JSModuleDef *m = s->cur_func->module;
|
|
JSAtom local_name, export_name;
|
|
int first_export, idx, i, tok;
|
|
JSAtom module_name;
|
|
JSExportEntry *me;
|
|
|
|
if (next_token(s))
|
|
return -1;
|
|
|
|
tok = s->token.val;
|
|
if (tok == TOK_CLASS) {
|
|
return js_parse_class(s, FALSE, JS_PARSE_EXPORT_NAMED);
|
|
} else if (tok == TOK_FUNCTION ||
|
|
(token_is_pseudo_keyword(s, JS_ATOM_async) &&
|
|
peek_token(s, TRUE) == TOK_FUNCTION)) {
|
|
return js_parse_function_decl2(s, JS_PARSE_FUNC_STATEMENT,
|
|
JS_FUNC_NORMAL, JS_ATOM_NULL,
|
|
s->token.ptr,
|
|
s->token.line_num,
|
|
s->token.col_num,
|
|
JS_PARSE_EXPORT_NAMED, NULL);
|
|
}
|
|
|
|
if (next_token(s))
|
|
return -1;
|
|
|
|
switch(tok) {
|
|
case '{':
|
|
first_export = m->export_entries_count;
|
|
while (s->token.val != '}') {
|
|
if (!token_is_ident(s->token.val)) {
|
|
js_parse_error(s, "identifier expected");
|
|
return -1;
|
|
}
|
|
local_name = JS_DupAtom(ctx, s->token.u.ident.atom);
|
|
export_name = JS_ATOM_NULL;
|
|
if (next_token(s))
|
|
goto fail;
|
|
if (token_is_pseudo_keyword(s, JS_ATOM_as)) {
|
|
if (next_token(s))
|
|
goto fail;
|
|
if (!token_is_ident(s->token.val)) {
|
|
js_parse_error(s, "identifier expected");
|
|
goto fail;
|
|
}
|
|
export_name = JS_DupAtom(ctx, s->token.u.ident.atom);
|
|
if (next_token(s)) {
|
|
fail:
|
|
JS_FreeAtom(ctx, local_name);
|
|
fail1:
|
|
JS_FreeAtom(ctx, export_name);
|
|
return -1;
|
|
}
|
|
} else {
|
|
export_name = JS_DupAtom(ctx, local_name);
|
|
}
|
|
me = add_export_entry(s, m, local_name, export_name,
|
|
JS_EXPORT_TYPE_LOCAL);
|
|
JS_FreeAtom(ctx, local_name);
|
|
JS_FreeAtom(ctx, export_name);
|
|
if (!me)
|
|
return -1;
|
|
if (s->token.val != ',')
|
|
break;
|
|
if (next_token(s))
|
|
return -1;
|
|
}
|
|
if (js_parse_expect(s, '}'))
|
|
return -1;
|
|
if (token_is_pseudo_keyword(s, JS_ATOM_from)) {
|
|
module_name = js_parse_from_clause(s);
|
|
if (module_name == JS_ATOM_NULL)
|
|
return -1;
|
|
idx = add_req_module_entry(ctx, m, module_name);
|
|
JS_FreeAtom(ctx, module_name);
|
|
if (idx < 0)
|
|
return -1;
|
|
for(i = first_export; i < m->export_entries_count; i++) {
|
|
me = &m->export_entries[i];
|
|
me->export_type = JS_EXPORT_TYPE_INDIRECT;
|
|
me->u.req_module_idx = idx;
|
|
}
|
|
}
|
|
break;
|
|
case '*':
|
|
if (token_is_pseudo_keyword(s, JS_ATOM_as)) {
|
|
/* export ns from */
|
|
if (next_token(s))
|
|
return -1;
|
|
if (!token_is_ident(s->token.val)) {
|
|
js_parse_error(s, "identifier expected");
|
|
return -1;
|
|
}
|
|
export_name = JS_DupAtom(ctx, s->token.u.ident.atom);
|
|
if (next_token(s))
|
|
goto fail1;
|
|
module_name = js_parse_from_clause(s);
|
|
if (module_name == JS_ATOM_NULL)
|
|
goto fail1;
|
|
idx = add_req_module_entry(ctx, m, module_name);
|
|
JS_FreeAtom(ctx, module_name);
|
|
if (idx < 0)
|
|
goto fail1;
|
|
me = add_export_entry(s, m, JS_ATOM__star_, export_name,
|
|
JS_EXPORT_TYPE_INDIRECT);
|
|
JS_FreeAtom(ctx, export_name);
|
|
if (!me)
|
|
return -1;
|
|
me->u.req_module_idx = idx;
|
|
} else {
|
|
module_name = js_parse_from_clause(s);
|
|
if (module_name == JS_ATOM_NULL)
|
|
return -1;
|
|
idx = add_req_module_entry(ctx, m, module_name);
|
|
JS_FreeAtom(ctx, module_name);
|
|
if (idx < 0)
|
|
return -1;
|
|
if (add_star_export_entry(ctx, m, idx) < 0)
|
|
return -1;
|
|
}
|
|
break;
|
|
case TOK_DEFAULT:
|
|
if (s->token.val == TOK_CLASS) {
|
|
return js_parse_class(s, FALSE, JS_PARSE_EXPORT_DEFAULT);
|
|
} else if (s->token.val == TOK_FUNCTION ||
|
|
(token_is_pseudo_keyword(s, JS_ATOM_async) &&
|
|
peek_token(s, TRUE) == TOK_FUNCTION)) {
|
|
return js_parse_function_decl2(s, JS_PARSE_FUNC_STATEMENT,
|
|
JS_FUNC_NORMAL, JS_ATOM_NULL,
|
|
s->token.ptr,
|
|
s->token.line_num,
|
|
s->token.col_num,
|
|
JS_PARSE_EXPORT_DEFAULT, NULL);
|
|
} else {
|
|
if (js_parse_assign_expr(s))
|
|
return -1;
|
|
}
|
|
/* set the name of anonymous functions */
|
|
set_object_name(s, JS_ATOM_default);
|
|
|
|
/* store the value in the _default_ global variable and export
|
|
it */
|
|
local_name = JS_ATOM__default_;
|
|
if (define_var(s, s->cur_func, local_name, JS_VAR_DEF_LET) < 0)
|
|
return -1;
|
|
emit_op(s, OP_scope_put_var_init);
|
|
emit_atom(s, local_name);
|
|
emit_u16(s, 0);
|
|
|
|
if (!add_export_entry(s, m, local_name, JS_ATOM_default,
|
|
JS_EXPORT_TYPE_LOCAL))
|
|
return -1;
|
|
break;
|
|
case TOK_VAR:
|
|
case TOK_LET:
|
|
case TOK_CONST:
|
|
return js_parse_var(s, TRUE, tok, TRUE);
|
|
default:
|
|
return js_parse_error(s, "invalid export syntax");
|
|
}
|
|
return js_parse_expect_semi(s);
|
|
}
|
|
|
|
static int add_closure_var(JSContext *ctx, JSFunctionDef *s,
|
|
BOOL is_local, BOOL is_arg,
|
|
int var_idx, JSAtom var_name,
|
|
BOOL is_const, BOOL is_lexical,
|
|
JSVarKindEnum var_kind);
|
|
|
|
static int add_import(JSParseState *s, JSModuleDef *m,
|
|
JSAtom local_name, JSAtom import_name)
|
|
{
|
|
JSContext *ctx = s->ctx;
|
|
int i, var_idx;
|
|
JSImportEntry *mi;
|
|
BOOL is_local;
|
|
|
|
if (local_name == JS_ATOM_arguments || local_name == JS_ATOM_eval)
|
|
return js_parse_error(s, "invalid import binding");
|
|
|
|
if (local_name != JS_ATOM_default) {
|
|
for (i = 0; i < s->cur_func->closure_var_count; i++) {
|
|
if (s->cur_func->closure_var[i].var_name == local_name)
|
|
return js_parse_error(s, "duplicate import binding");
|
|
}
|
|
}
|
|
|
|
is_local = (import_name == JS_ATOM__star_);
|
|
var_idx = add_closure_var(ctx, s->cur_func, is_local, FALSE,
|
|
m->import_entries_count,
|
|
local_name, TRUE, TRUE, JS_VAR_NORMAL);
|
|
if (var_idx < 0)
|
|
return -1;
|
|
if (js_resize_array(ctx, (void **)&m->import_entries,
|
|
sizeof(JSImportEntry),
|
|
&m->import_entries_size,
|
|
m->import_entries_count + 1))
|
|
return -1;
|
|
mi = &m->import_entries[m->import_entries_count++];
|
|
mi->import_name = JS_DupAtom(ctx, import_name);
|
|
mi->var_idx = var_idx;
|
|
return 0;
|
|
}
|
|
|
|
static __exception int js_parse_import(JSParseState *s)
|
|
{
|
|
JSContext *ctx = s->ctx;
|
|
JSModuleDef *m = s->cur_func->module;
|
|
JSAtom local_name, import_name, module_name;
|
|
int first_import, i, idx;
|
|
|
|
if (next_token(s))
|
|
return -1;
|
|
|
|
first_import = m->import_entries_count;
|
|
if (s->token.val == TOK_STRING) {
|
|
module_name = JS_ValueToAtom(ctx, s->token.u.str.str);
|
|
if (module_name == JS_ATOM_NULL)
|
|
return -1;
|
|
if (next_token(s)) {
|
|
JS_FreeAtom(ctx, module_name);
|
|
return -1;
|
|
}
|
|
} else {
|
|
if (s->token.val == TOK_IDENT) {
|
|
if (s->token.u.ident.is_reserved) {
|
|
return js_parse_error_reserved_identifier(s);
|
|
}
|
|
/* "default" import */
|
|
local_name = JS_DupAtom(ctx, s->token.u.ident.atom);
|
|
import_name = JS_ATOM_default;
|
|
if (next_token(s))
|
|
goto fail;
|
|
if (add_import(s, m, local_name, import_name))
|
|
goto fail;
|
|
JS_FreeAtom(ctx, local_name);
|
|
|
|
if (s->token.val != ',')
|
|
goto end_import_clause;
|
|
if (next_token(s))
|
|
return -1;
|
|
}
|
|
|
|
if (s->token.val == '*') {
|
|
/* name space import */
|
|
if (next_token(s))
|
|
return -1;
|
|
if (!token_is_pseudo_keyword(s, JS_ATOM_as))
|
|
return js_parse_error(s, "expecting 'as'");
|
|
if (next_token(s))
|
|
return -1;
|
|
if (!token_is_ident(s->token.val)) {
|
|
js_parse_error(s, "identifier expected");
|
|
return -1;
|
|
}
|
|
local_name = JS_DupAtom(ctx, s->token.u.ident.atom);
|
|
import_name = JS_ATOM__star_;
|
|
if (next_token(s))
|
|
goto fail;
|
|
if (add_import(s, m, local_name, import_name))
|
|
goto fail;
|
|
JS_FreeAtom(ctx, local_name);
|
|
} else if (s->token.val == '{') {
|
|
if (next_token(s))
|
|
return -1;
|
|
|
|
while (s->token.val != '}') {
|
|
if (!token_is_ident(s->token.val)) {
|
|
js_parse_error(s, "identifier expected");
|
|
return -1;
|
|
}
|
|
import_name = JS_DupAtom(ctx, s->token.u.ident.atom);
|
|
local_name = JS_ATOM_NULL;
|
|
if (next_token(s))
|
|
goto fail;
|
|
if (token_is_pseudo_keyword(s, JS_ATOM_as)) {
|
|
if (next_token(s))
|
|
goto fail;
|
|
if (!token_is_ident(s->token.val)) {
|
|
js_parse_error(s, "identifier expected");
|
|
goto fail;
|
|
}
|
|
local_name = JS_DupAtom(ctx, s->token.u.ident.atom);
|
|
if (next_token(s)) {
|
|
fail:
|
|
JS_FreeAtom(ctx, local_name);
|
|
JS_FreeAtom(ctx, import_name);
|
|
return -1;
|
|
}
|
|
} else {
|
|
local_name = JS_DupAtom(ctx, import_name);
|
|
}
|
|
if (add_import(s, m, local_name, import_name))
|
|
goto fail;
|
|
JS_FreeAtom(ctx, local_name);
|
|
JS_FreeAtom(ctx, import_name);
|
|
if (s->token.val != ',')
|
|
break;
|
|
if (next_token(s))
|
|
return -1;
|
|
}
|
|
if (js_parse_expect(s, '}'))
|
|
return -1;
|
|
}
|
|
end_import_clause:
|
|
module_name = js_parse_from_clause(s);
|
|
if (module_name == JS_ATOM_NULL)
|
|
return -1;
|
|
}
|
|
idx = add_req_module_entry(ctx, m, module_name);
|
|
JS_FreeAtom(ctx, module_name);
|
|
if (idx < 0)
|
|
return -1;
|
|
for(i = first_import; i < m->import_entries_count; i++)
|
|
m->import_entries[i].req_module_idx = idx;
|
|
|
|
return js_parse_expect_semi(s);
|
|
}
|
|
|
|
static __exception int js_parse_source_element(JSParseState *s)
|
|
{
|
|
JSFunctionDef *fd = s->cur_func;
|
|
int tok;
|
|
|
|
if (s->token.val == TOK_FUNCTION ||
|
|
(token_is_pseudo_keyword(s, JS_ATOM_async) &&
|
|
peek_token(s, TRUE) == TOK_FUNCTION)) {
|
|
if (js_parse_function_decl(s, JS_PARSE_FUNC_STATEMENT,
|
|
JS_FUNC_NORMAL, JS_ATOM_NULL,
|
|
s->token.ptr,
|
|
s->token.line_num,
|
|
s->token.col_num))
|
|
return -1;
|
|
} else if (s->token.val == TOK_EXPORT && fd->module) {
|
|
if (js_parse_export(s))
|
|
return -1;
|
|
} else if (s->token.val == TOK_IMPORT && fd->module &&
|
|
((tok = peek_token(s, FALSE)) != '(' && tok != '.')) {
|
|
/* the peek_token is needed to avoid confusion with ImportCall
|
|
(dynamic import) or import.meta */
|
|
if (js_parse_import(s))
|
|
return -1;
|
|
} else {
|
|
if (js_parse_statement_or_decl(s, DECL_MASK_ALL))
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* `filename` may be pure ASCII or UTF-8 encoded */
|
|
static JSFunctionDef *js_new_function_def(JSContext *ctx,
|
|
JSFunctionDef *parent,
|
|
BOOL is_eval,
|
|
BOOL is_func_expr,
|
|
const char *filename,
|
|
int line_num,
|
|
int col_num)
|
|
{
|
|
JSFunctionDef *fd;
|
|
|
|
fd = js_mallocz(ctx, sizeof(*fd));
|
|
if (!fd)
|
|
return NULL;
|
|
|
|
fd->ctx = ctx;
|
|
init_list_head(&fd->child_list);
|
|
|
|
/* insert in parent list */
|
|
fd->parent = parent;
|
|
fd->parent_cpool_idx = -1;
|
|
if (parent) {
|
|
list_add_tail(&fd->link, &parent->child_list);
|
|
fd->js_mode = parent->js_mode;
|
|
fd->parent_scope_level = parent->scope_level;
|
|
}
|
|
|
|
fd->is_eval = is_eval;
|
|
fd->is_func_expr = is_func_expr;
|
|
js_dbuf_init(ctx, &fd->byte_code);
|
|
fd->last_opcode_pos = -1;
|
|
fd->func_name = JS_ATOM_NULL;
|
|
fd->var_object_idx = -1;
|
|
fd->arg_var_object_idx = -1;
|
|
fd->arguments_var_idx = -1;
|
|
fd->arguments_arg_idx = -1;
|
|
fd->func_var_idx = -1;
|
|
fd->eval_ret_idx = -1;
|
|
fd->this_var_idx = -1;
|
|
fd->new_target_var_idx = -1;
|
|
fd->this_active_func_var_idx = -1;
|
|
fd->home_object_var_idx = -1;
|
|
|
|
/* XXX: should distinguish arg, var and var object and body scopes */
|
|
fd->scopes = fd->def_scope_array;
|
|
fd->scope_size = countof(fd->def_scope_array);
|
|
fd->scope_count = 1;
|
|
fd->scopes[0].first = -1;
|
|
fd->scopes[0].parent = -1;
|
|
fd->scope_level = 0; /* 0: var/arg scope */
|
|
fd->scope_first = -1;
|
|
fd->body_scope = -1;
|
|
|
|
fd->filename = JS_NewAtom(ctx, filename);
|
|
fd->line_num = line_num;
|
|
fd->col_num = col_num;
|
|
|
|
js_dbuf_init(ctx, &fd->pc2line);
|
|
//fd->pc2line_last_line_num = line_num;
|
|
//fd->pc2line_last_pc = 0;
|
|
fd->last_opcode_line_num = line_num;
|
|
|
|
fd->ic = init_ic(ctx);
|
|
return fd;
|
|
}
|
|
|
|
static void free_bytecode_atoms(JSRuntime *rt,
|
|
const uint8_t *bc_buf, int bc_len,
|
|
BOOL use_short_opcodes)
|
|
{
|
|
int pos, len, op;
|
|
JSAtom atom;
|
|
const JSOpCode *oi;
|
|
|
|
pos = 0;
|
|
while (pos < bc_len) {
|
|
op = bc_buf[pos];
|
|
if (use_short_opcodes)
|
|
oi = &short_opcode_info(op);
|
|
else
|
|
oi = &opcode_info[op];
|
|
|
|
len = oi->size;
|
|
switch(oi->fmt) {
|
|
case OP_FMT_atom:
|
|
case OP_FMT_atom_u8:
|
|
case OP_FMT_atom_u16:
|
|
case OP_FMT_atom_label_u8:
|
|
case OP_FMT_atom_label_u16:
|
|
atom = get_u32(bc_buf + pos + 1);
|
|
JS_FreeAtomRT(rt, atom);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
pos += len;
|
|
}
|
|
}
|
|
|
|
static void js_free_function_def(JSContext *ctx, JSFunctionDef *fd)
|
|
{
|
|
int i;
|
|
struct list_head *el, *el1;
|
|
|
|
/* free the child functions */
|
|
list_for_each_safe(el, el1, &fd->child_list) {
|
|
JSFunctionDef *fd1;
|
|
fd1 = list_entry(el, JSFunctionDef, link);
|
|
js_free_function_def(ctx, fd1);
|
|
}
|
|
|
|
free_bytecode_atoms(ctx->rt, fd->byte_code.buf, fd->byte_code.size,
|
|
fd->use_short_opcodes);
|
|
dbuf_free(&fd->byte_code);
|
|
js_free(ctx, fd->jump_slots);
|
|
js_free(ctx, fd->label_slots);
|
|
js_free(ctx, fd->source_loc_slots);
|
|
|
|
/* free ic */
|
|
if (fd->ic)
|
|
free_ic(ctx->rt, fd->ic);
|
|
|
|
for(i = 0; i < fd->cpool_count; i++) {
|
|
JS_FreeValue(ctx, fd->cpool[i]);
|
|
}
|
|
js_free(ctx, fd->cpool);
|
|
|
|
JS_FreeAtom(ctx, fd->func_name);
|
|
|
|
for(i = 0; i < fd->var_count; i++) {
|
|
JS_FreeAtom(ctx, fd->vars[i].var_name);
|
|
}
|
|
js_free(ctx, fd->vars);
|
|
for(i = 0; i < fd->arg_count; i++) {
|
|
JS_FreeAtom(ctx, fd->args[i].var_name);
|
|
}
|
|
js_free(ctx, fd->args);
|
|
|
|
for(i = 0; i < fd->global_var_count; i++) {
|
|
JS_FreeAtom(ctx, fd->global_vars[i].var_name);
|
|
}
|
|
js_free(ctx, fd->global_vars);
|
|
|
|
for(i = 0; i < fd->closure_var_count; i++) {
|
|
JSClosureVar *cv = &fd->closure_var[i];
|
|
JS_FreeAtom(ctx, cv->var_name);
|
|
}
|
|
js_free(ctx, fd->closure_var);
|
|
|
|
if (fd->scopes != fd->def_scope_array)
|
|
js_free(ctx, fd->scopes);
|
|
|
|
JS_FreeAtom(ctx, fd->filename);
|
|
dbuf_free(&fd->pc2line);
|
|
|
|
js_free(ctx, fd->source);
|
|
|
|
if (fd->parent) {
|
|
/* remove in parent list */
|
|
list_del(&fd->link);
|
|
}
|
|
js_free(ctx, fd);
|
|
}
|
|
|
|
#ifdef DUMP_BYTECODE
|
|
static const char *skip_lines(const char *p, int n) {
|
|
while (p && n-- > 0 && *p) {
|
|
while (*p && *p++ != '\n')
|
|
continue;
|
|
}
|
|
return p;
|
|
}
|
|
|
|
static void print_lines(const char *source, int line, int line1) {
|
|
const char *s = source;
|
|
const char *p = skip_lines(s, line);
|
|
if (p && *p) {
|
|
while (line++ < line1) {
|
|
p = skip_lines(s = p, 1);
|
|
printf(";; %.*s", (int)(p - s), s);
|
|
if (!*p) {
|
|
if (p[-1] != '\n')
|
|
printf("\n");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void dump_byte_code(JSContext *ctx, int pass,
|
|
const uint8_t *tab, int len,
|
|
const JSVarDef *args, int arg_count,
|
|
const JSVarDef *vars, int var_count,
|
|
const JSClosureVar *closure_var, int closure_var_count,
|
|
const JSValue *cpool, uint32_t cpool_count,
|
|
const char *source, int line_num,
|
|
const LabelSlot *label_slots, JSFunctionBytecode *b,
|
|
int start_pos)
|
|
{
|
|
const JSOpCode *oi;
|
|
int pos, pos_next, op, size, idx, addr, line, line1, in_source;
|
|
uint8_t *bits = js_mallocz(ctx, len * sizeof(*bits));
|
|
BOOL use_short_opcodes = (b != NULL);
|
|
|
|
if (start_pos != 0 || bits == NULL)
|
|
goto no_labels;
|
|
|
|
/* scan for jump targets */
|
|
for (pos = 0; pos < len; pos = pos_next) {
|
|
op = tab[pos];
|
|
if (use_short_opcodes)
|
|
oi = &short_opcode_info(op);
|
|
else
|
|
oi = &opcode_info[op];
|
|
pos_next = pos + oi->size;
|
|
if (op < OP_COUNT) {
|
|
switch (oi->fmt) {
|
|
case OP_FMT_label8:
|
|
pos++;
|
|
addr = (int8_t)tab[pos];
|
|
goto has_addr;
|
|
case OP_FMT_label16:
|
|
pos++;
|
|
addr = (int16_t)get_u16(tab + pos);
|
|
goto has_addr;
|
|
case OP_FMT_atom_label_u8:
|
|
case OP_FMT_atom_label_u16:
|
|
pos += 4;
|
|
/* fall thru */
|
|
case OP_FMT_label:
|
|
case OP_FMT_label_u16:
|
|
pos++;
|
|
addr = get_u32(tab + pos);
|
|
goto has_addr;
|
|
has_addr:
|
|
if (pass == 1)
|
|
addr = label_slots[addr].pos;
|
|
if (pass == 2)
|
|
addr = label_slots[addr].pos2;
|
|
if (pass == 3)
|
|
addr += pos;
|
|
if (addr >= 0 && addr < len)
|
|
bits[addr] |= 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
no_labels:
|
|
in_source = 0;
|
|
if (source) {
|
|
/* Always print first line: needed if single line */
|
|
print_lines(source, 0, 1);
|
|
in_source = 1;
|
|
}
|
|
line1 = line = 1;
|
|
pos = 0;
|
|
while (pos < len) {
|
|
op = tab[pos];
|
|
if (source) {
|
|
if (b) {
|
|
int col1;
|
|
line1 = find_line_num(ctx, b, pos, &col1) - line_num + 1;
|
|
} else if (op == OP_source_loc) {
|
|
line1 = get_u32(tab + pos + 1) - line_num + 1;
|
|
}
|
|
if (line1 > line) {
|
|
if (!in_source)
|
|
printf("\n");
|
|
in_source = 1;
|
|
print_lines(source, line, line1);
|
|
line = line1;
|
|
//bits[pos] |= 2;
|
|
}
|
|
}
|
|
if (in_source)
|
|
printf("\n");
|
|
in_source = 0;
|
|
if (op >= OP_COUNT) {
|
|
printf("invalid opcode (0x%02x)\n", op);
|
|
pos++;
|
|
continue;
|
|
}
|
|
if (use_short_opcodes)
|
|
oi = &short_opcode_info(op);
|
|
else
|
|
oi = &opcode_info[op];
|
|
size = oi->size;
|
|
if (pos + size > len) {
|
|
printf("truncated opcode (0x%02x)\n", op);
|
|
break;
|
|
}
|
|
#ifdef DUMP_BYTECODE_HEX
|
|
if (check_dump_flag(ctx->rt, DUMP_BYTECODE_HEX)) {
|
|
int i, x, x0;
|
|
x = x0 = printf("%5d ", pos);
|
|
for (i = 0; i < size; i++) {
|
|
if (i == 6) {
|
|
printf("\n%*s", x = x0, "");
|
|
}
|
|
x += printf(" %02X", tab[pos + i]);
|
|
}
|
|
printf("%*s", x0 + 20 - x, "");
|
|
}
|
|
#endif
|
|
if (bits && bits[pos]) {
|
|
printf("%5d: ", pos);
|
|
} else {
|
|
printf(" ");
|
|
}
|
|
printf("%-15s", oi->name); /* align opcode arguments */
|
|
pos++;
|
|
switch(oi->fmt) {
|
|
case OP_FMT_none_int:
|
|
printf(" %d", op - OP_push_0);
|
|
break;
|
|
case OP_FMT_npopx:
|
|
printf(" %d", op - OP_call0);
|
|
break;
|
|
case OP_FMT_u8:
|
|
printf(" %u", get_u8(tab + pos));
|
|
break;
|
|
case OP_FMT_i8:
|
|
printf(" %d", get_i8(tab + pos));
|
|
break;
|
|
case OP_FMT_u16:
|
|
case OP_FMT_npop:
|
|
printf(" %u", get_u16(tab + pos));
|
|
break;
|
|
case OP_FMT_npop_u16:
|
|
printf(" %u,%u", get_u16(tab + pos), get_u16(tab + pos + 2));
|
|
break;
|
|
case OP_FMT_i16:
|
|
printf(" %d", get_i16(tab + pos));
|
|
break;
|
|
case OP_FMT_i32:
|
|
printf(" %d", get_i32(tab + pos));
|
|
break;
|
|
case OP_FMT_u32:
|
|
printf(" %u", get_u32(tab + pos));
|
|
break;
|
|
case OP_FMT_u32x2:
|
|
printf(" %u:%u", get_u32(tab + pos), get_u32(tab + pos + 4));
|
|
break;
|
|
case OP_FMT_label8:
|
|
addr = get_i8(tab + pos);
|
|
goto has_addr1;
|
|
case OP_FMT_label16:
|
|
addr = get_i16(tab + pos);
|
|
goto has_addr1;
|
|
case OP_FMT_label:
|
|
case OP_FMT_label_u16:
|
|
addr = get_u32(tab + pos);
|
|
has_addr1:
|
|
if (pass == 1)
|
|
printf(" %d:%u", addr, label_slots[addr].pos);
|
|
if (pass == 2)
|
|
printf(" %d:%u", addr, label_slots[addr].pos2);
|
|
if (pass == 3) {
|
|
if (start_pos)
|
|
printf(" %04x", addr + pos + start_pos);
|
|
else
|
|
printf(" %d", addr + pos);
|
|
}
|
|
if (oi->fmt == OP_FMT_label_u16)
|
|
printf(",%u", get_u16(tab + pos + 4));
|
|
break;
|
|
case OP_FMT_const8:
|
|
idx = get_u8(tab + pos);
|
|
goto has_pool_idx;
|
|
case OP_FMT_const:
|
|
idx = get_u32(tab + pos);
|
|
goto has_pool_idx;
|
|
has_pool_idx:
|
|
printf(" %-4u ; ", idx);
|
|
if (idx < cpool_count) {
|
|
JS_DumpValue(ctx->rt, cpool[idx]);
|
|
}
|
|
break;
|
|
case OP_FMT_atom:
|
|
printf(" ");
|
|
print_atom(ctx, get_u32(tab + pos));
|
|
break;
|
|
case OP_FMT_atom_u8:
|
|
printf(" ");
|
|
print_atom(ctx, get_u32(tab + pos));
|
|
printf(",%d", get_u8(tab + pos + 4));
|
|
break;
|
|
case OP_FMT_atom_u16:
|
|
printf(" ");
|
|
print_atom(ctx, get_u32(tab + pos));
|
|
printf(",%d", get_u16(tab + pos + 4));
|
|
break;
|
|
case OP_FMT_atom_label_u8:
|
|
case OP_FMT_atom_label_u16:
|
|
printf(" ");
|
|
print_atom(ctx, get_u32(tab + pos));
|
|
addr = get_u32(tab + pos + 4);
|
|
if (pass == 1)
|
|
printf(",%u:%u", addr, label_slots[addr].pos);
|
|
if (pass == 2)
|
|
printf(",%u:%u", addr, label_slots[addr].pos2);
|
|
if (pass == 3)
|
|
printf(",%u", addr + pos + 4);
|
|
if (oi->fmt == OP_FMT_atom_label_u8)
|
|
printf(",%u", get_u8(tab + pos + 8));
|
|
else
|
|
printf(",%u", get_u16(tab + pos + 8));
|
|
break;
|
|
case OP_FMT_none_loc:
|
|
idx = (op - OP_get_loc0_loc1) % 4;
|
|
goto has_loc;
|
|
case OP_FMT_loc8:
|
|
idx = get_u8(tab + pos);
|
|
goto has_loc;
|
|
case OP_FMT_loc:
|
|
idx = get_u16(tab + pos);
|
|
has_loc:
|
|
printf(" %-4d ; ", idx);
|
|
if (idx < var_count) {
|
|
print_atom(ctx, vars[idx].var_name);
|
|
}
|
|
break;
|
|
case OP_FMT_none_arg:
|
|
idx = (op - OP_get_arg0) % 4;
|
|
goto has_arg;
|
|
case OP_FMT_arg:
|
|
idx = get_u16(tab + pos);
|
|
has_arg:
|
|
printf(" %-4d ; ", idx);
|
|
if (idx < arg_count) {
|
|
print_atom(ctx, args[idx].var_name);
|
|
}
|
|
break;
|
|
case OP_FMT_none_var_ref:
|
|
idx = (op - OP_get_var_ref0) % 4;
|
|
goto has_var_ref;
|
|
case OP_FMT_var_ref:
|
|
idx = get_u16(tab + pos);
|
|
has_var_ref:
|
|
printf(" %-4d ; ", idx);
|
|
if (idx < closure_var_count) {
|
|
print_atom(ctx, closure_var[idx].var_name);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
printf("\n");
|
|
pos += oi->size - 1;
|
|
}
|
|
if (source) {
|
|
if (!in_source)
|
|
printf("\n");
|
|
print_lines(source, line, INT32_MAX);
|
|
}
|
|
js_free(ctx, bits);
|
|
}
|
|
|
|
// caveat emptor: intended to be called during execution of bytecode
|
|
// and only works for pass3 bytecode
|
|
static __maybe_unused void dump_single_byte_code(JSContext *ctx,
|
|
const uint8_t *pc,
|
|
JSFunctionBytecode *b,
|
|
int start_pos)
|
|
{
|
|
JSVarDef *args, *vars;
|
|
|
|
args = vars = b->vardefs;
|
|
if (vars)
|
|
vars = &vars[b->arg_count];
|
|
|
|
dump_byte_code(ctx, /*pass*/3, pc, short_opcode_info(*pc).size,
|
|
args, b->arg_count, vars, b->var_count,
|
|
b->closure_var, b->closure_var_count,
|
|
b->cpool, b->cpool_count,
|
|
NULL, b->line_num,
|
|
NULL, b, start_pos);
|
|
}
|
|
|
|
static __maybe_unused void print_func_name(JSFunctionBytecode *b)
|
|
{
|
|
print_lines(b->source, 0, 1);
|
|
}
|
|
|
|
static __maybe_unused void dump_pc2line(JSContext *ctx,
|
|
const uint8_t *buf, int len,
|
|
int line_num, int col_num)
|
|
{
|
|
const uint8_t *p_end, *p_next, *p;
|
|
int pc, v;
|
|
unsigned int op;
|
|
|
|
if (len <= 0)
|
|
return;
|
|
|
|
printf("%5s %5s %5s\n", "PC", "LINE", "COLUMN");
|
|
|
|
p = buf;
|
|
p_end = buf + len;
|
|
pc = 0;
|
|
while (p < p_end) {
|
|
op = *p++;
|
|
if (op == 0) {
|
|
v = unicode_from_utf8(p, p_end - p, &p_next);
|
|
if (v < 0)
|
|
goto fail;
|
|
pc += v;
|
|
p = p_next;
|
|
v = unicode_from_utf8(p, p_end - p, &p_next);
|
|
if (v < 0)
|
|
goto fail;
|
|
if (v & 1) {
|
|
v = -(v >> 1) - 1;
|
|
} else {
|
|
v = v >> 1;
|
|
}
|
|
line_num += v;
|
|
p = p_next;
|
|
} else {
|
|
op -= PC2LINE_OP_FIRST;
|
|
pc += (op / PC2LINE_RANGE);
|
|
line_num += (op % PC2LINE_RANGE) + PC2LINE_BASE;
|
|
}
|
|
v = unicode_from_utf8(p, p_end - p, &p_next);
|
|
if (v < 0)
|
|
goto fail;
|
|
if (v & 1) {
|
|
v = -(v >> 1) - 1;
|
|
} else {
|
|
v = v >> 1;
|
|
}
|
|
col_num += v;
|
|
p = p_next;
|
|
printf("%5d %5d %5d\n", pc, line_num, col_num);
|
|
}
|
|
return;
|
|
fail:
|
|
printf("invalid pc2line encode pos=%d\n", (int)(p - buf));
|
|
}
|
|
|
|
static __maybe_unused void js_dump_function_bytecode(JSContext *ctx, JSFunctionBytecode *b)
|
|
{
|
|
int i;
|
|
char atom_buf[ATOM_GET_STR_BUF_SIZE];
|
|
const char *str;
|
|
|
|
if (b->filename != JS_ATOM_NULL) {
|
|
str = JS_AtomGetStr(ctx, atom_buf, sizeof(atom_buf), b->filename);
|
|
printf("%s:%d:%d: ", str, b->line_num, b->col_num);
|
|
}
|
|
|
|
str = JS_AtomGetStr(ctx, atom_buf, sizeof(atom_buf), b->func_name);
|
|
printf("function: %s%s\n", &"*"[b->func_kind != JS_FUNC_GENERATOR], str);
|
|
if (b->js_mode) {
|
|
printf(" mode:");
|
|
if (b->js_mode & JS_MODE_STRICT)
|
|
printf(" strict");
|
|
|
|
printf("\n");
|
|
}
|
|
if (b->arg_count && b->vardefs) {
|
|
printf(" args:");
|
|
for(i = 0; i < b->arg_count; i++) {
|
|
printf(" %s", JS_AtomGetStr(ctx, atom_buf, sizeof(atom_buf),
|
|
b->vardefs[i].var_name));
|
|
}
|
|
printf("\n");
|
|
}
|
|
if (b->var_count && b->vardefs) {
|
|
printf(" locals:\n");
|
|
for(i = 0; i < b->var_count; i++) {
|
|
JSVarDef *vd = &b->vardefs[b->arg_count + i];
|
|
printf("%5d: %s %s", i,
|
|
vd->var_kind == JS_VAR_CATCH ? "catch" :
|
|
(vd->var_kind == JS_VAR_FUNCTION_DECL ||
|
|
vd->var_kind == JS_VAR_NEW_FUNCTION_DECL) ? "function" :
|
|
vd->is_const ? "const" :
|
|
vd->is_lexical ? "let" : "var",
|
|
JS_AtomGetStr(ctx, atom_buf, sizeof(atom_buf), vd->var_name));
|
|
if (vd->scope_level)
|
|
printf(" [level:%d next:%d]", vd->scope_level, vd->scope_next);
|
|
printf("\n");
|
|
}
|
|
}
|
|
if (b->closure_var_count) {
|
|
printf(" closure vars:\n");
|
|
for(i = 0; i < b->closure_var_count; i++) {
|
|
JSClosureVar *cv = &b->closure_var[i];
|
|
printf("%5d: %s %s:%s%d %s\n", i,
|
|
JS_AtomGetStr(ctx, atom_buf, sizeof(atom_buf), cv->var_name),
|
|
cv->is_local ? "local" : "parent",
|
|
cv->is_arg ? "arg" : "loc", cv->var_idx,
|
|
cv->is_const ? "const" :
|
|
cv->is_lexical ? "let" : "var");
|
|
}
|
|
}
|
|
printf(" stack_size: %d\n", b->stack_size);
|
|
printf(" opcodes:\n");
|
|
dump_byte_code(ctx, 3, b->byte_code_buf, b->byte_code_len,
|
|
b->vardefs, b->arg_count,
|
|
b->vardefs ? b->vardefs + b->arg_count : NULL, b->var_count,
|
|
b->closure_var, b->closure_var_count,
|
|
b->cpool, b->cpool_count,
|
|
b->source, b->line_num, NULL, b, 0);
|
|
#ifdef DUMP_BYTECODE_PC2LINE
|
|
if (check_dump_flag(ctx->rt, DUMP_BYTECODE_PC2LINE))
|
|
dump_pc2line(ctx, b->pc2line_buf, b->pc2line_len, b->line_num, b->col_num);
|
|
#endif
|
|
printf("\n");
|
|
}
|
|
#endif
|
|
|
|
static int add_closure_var(JSContext *ctx, JSFunctionDef *s,
|
|
BOOL is_local, BOOL is_arg,
|
|
int var_idx, JSAtom var_name,
|
|
BOOL is_const, BOOL is_lexical,
|
|
JSVarKindEnum var_kind)
|
|
{
|
|
JSClosureVar *cv;
|
|
|
|
/* the closure variable indexes are currently stored on 16 bits */
|
|
if (s->closure_var_count >= JS_MAX_LOCAL_VARS) {
|
|
// XXX: add_closure_var() should take JSParseState *s and use js_parse_error
|
|
JS_ThrowSyntaxError(ctx, "too many closure variables used (only %d allowed)",
|
|
JS_MAX_LOCAL_VARS - 1);
|
|
return -1;
|
|
}
|
|
|
|
if (js_resize_array(ctx, (void **)&s->closure_var,
|
|
sizeof(s->closure_var[0]),
|
|
&s->closure_var_size, s->closure_var_count + 1))
|
|
return -1;
|
|
cv = &s->closure_var[s->closure_var_count++];
|
|
cv->is_local = is_local;
|
|
cv->is_arg = is_arg;
|
|
cv->is_const = is_const;
|
|
cv->is_lexical = is_lexical;
|
|
cv->var_kind = var_kind;
|
|
cv->var_idx = var_idx;
|
|
cv->var_name = JS_DupAtom(ctx, var_name);
|
|
return s->closure_var_count - 1;
|
|
}
|
|
|
|
static int find_closure_var(JSContext *ctx, JSFunctionDef *s,
|
|
JSAtom var_name)
|
|
{
|
|
int i;
|
|
for(i = 0; i < s->closure_var_count; i++) {
|
|
JSClosureVar *cv = &s->closure_var[i];
|
|
if (cv->var_name == var_name)
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/* 'fd' must be a parent of 's'. Create in 's' a closure referencing a
|
|
local variable (is_local = TRUE) or a closure (is_local = FALSE) in
|
|
'fd' */
|
|
static int get_closure_var2(JSContext *ctx, JSFunctionDef *s,
|
|
JSFunctionDef *fd, BOOL is_local,
|
|
BOOL is_arg, int var_idx, JSAtom var_name,
|
|
BOOL is_const, BOOL is_lexical,
|
|
JSVarKindEnum var_kind)
|
|
{
|
|
int i;
|
|
|
|
if (fd != s->parent) {
|
|
var_idx = get_closure_var2(ctx, s->parent, fd, is_local,
|
|
is_arg, var_idx, var_name,
|
|
is_const, is_lexical, var_kind);
|
|
if (var_idx < 0)
|
|
return -1;
|
|
is_local = FALSE;
|
|
}
|
|
for(i = 0; i < s->closure_var_count; i++) {
|
|
JSClosureVar *cv = &s->closure_var[i];
|
|
if (cv->var_idx == var_idx && cv->is_arg == is_arg &&
|
|
cv->is_local == is_local)
|
|
return i;
|
|
}
|
|
return add_closure_var(ctx, s, is_local, is_arg, var_idx, var_name,
|
|
is_const, is_lexical, var_kind);
|
|
}
|
|
|
|
static int get_closure_var(JSContext *ctx, JSFunctionDef *s,
|
|
JSFunctionDef *fd, BOOL is_arg,
|
|
int var_idx, JSAtom var_name,
|
|
BOOL is_const, BOOL is_lexical,
|
|
JSVarKindEnum var_kind)
|
|
{
|
|
return get_closure_var2(ctx, s, fd, TRUE, is_arg,
|
|
var_idx, var_name, is_const, is_lexical,
|
|
var_kind);
|
|
}
|
|
|
|
static int get_with_scope_opcode(int op)
|
|
{
|
|
if (op == OP_scope_get_var_undef)
|
|
return OP_with_get_var;
|
|
else
|
|
return OP_with_get_var + (op - OP_scope_get_var);
|
|
}
|
|
|
|
static BOOL can_opt_put_ref_value(const uint8_t *bc_buf, int pos)
|
|
{
|
|
int opcode = bc_buf[pos];
|
|
return (bc_buf[pos + 1] == OP_put_ref_value &&
|
|
(opcode == OP_insert3 ||
|
|
opcode == OP_perm4 ||
|
|
opcode == OP_nop ||
|
|
opcode == OP_rot3l));
|
|
}
|
|
|
|
static BOOL can_opt_put_global_ref_value(const uint8_t *bc_buf, int pos)
|
|
{
|
|
int opcode = bc_buf[pos];
|
|
return (bc_buf[pos + 1] == OP_put_ref_value &&
|
|
(opcode == OP_insert3 ||
|
|
opcode == OP_perm4 ||
|
|
opcode == OP_nop ||
|
|
opcode == OP_rot3l));
|
|
}
|
|
|
|
static int optimize_scope_make_ref(JSContext *ctx, JSFunctionDef *s,
|
|
DynBuf *bc, uint8_t *bc_buf,
|
|
LabelSlot *ls, int pos_next,
|
|
int get_op, int var_idx)
|
|
{
|
|
int label_pos, end_pos, pos;
|
|
|
|
/* XXX: should optimize `loc(a) += expr` as `expr add_loc(a)`
|
|
but only if expr does not modify `a`.
|
|
should scan the code between pos_next and label_pos
|
|
for operations that can potentially change `a`:
|
|
OP_scope_make_ref(a), function calls, jumps and gosub.
|
|
*/
|
|
/* replace the reference get/put with normal variable
|
|
accesses */
|
|
if (bc_buf[pos_next] == OP_get_ref_value) {
|
|
dbuf_putc(bc, get_op);
|
|
dbuf_put_u16(bc, var_idx);
|
|
pos_next++;
|
|
}
|
|
/* remove the OP_label to make room for replacement */
|
|
/* label should have a refcount of 0 anyway */
|
|
/* XXX: should avoid this patch by inserting nops in phase 1 */
|
|
label_pos = ls->pos;
|
|
pos = label_pos - 5;
|
|
assert(bc_buf[pos] == OP_label);
|
|
/* label points to an instruction pair:
|
|
- insert3 / put_ref_value
|
|
- perm4 / put_ref_value
|
|
- rot3l / put_ref_value
|
|
- nop / put_ref_value
|
|
*/
|
|
end_pos = label_pos + 2;
|
|
if (bc_buf[label_pos] == OP_insert3)
|
|
bc_buf[pos++] = OP_dup;
|
|
bc_buf[pos] = get_op + 1;
|
|
put_u16(bc_buf + pos + 1, var_idx);
|
|
pos += 3;
|
|
/* pad with OP_nop */
|
|
while (pos < end_pos)
|
|
bc_buf[pos++] = OP_nop;
|
|
return pos_next;
|
|
}
|
|
|
|
static int optimize_scope_make_global_ref(JSContext *ctx, JSFunctionDef *s,
|
|
DynBuf *bc, uint8_t *bc_buf,
|
|
LabelSlot *ls, int pos_next,
|
|
JSAtom var_name)
|
|
{
|
|
int label_pos, end_pos, pos, op;
|
|
BOOL is_strict;
|
|
is_strict = ((s->js_mode & JS_MODE_STRICT) != 0);
|
|
|
|
/* replace the reference get/put with normal variable
|
|
accesses */
|
|
if (is_strict) {
|
|
/* need to check if the variable exists before evaluating the right
|
|
expression */
|
|
/* XXX: need an extra OP_true if destructuring an array */
|
|
dbuf_putc(bc, OP_check_var);
|
|
dbuf_put_u32(bc, JS_DupAtom(ctx, var_name));
|
|
} else {
|
|
/* XXX: need 2 extra OP_true if destructuring an array */
|
|
}
|
|
if (bc_buf[pos_next] == OP_get_ref_value) {
|
|
dbuf_putc(bc, OP_get_var);
|
|
dbuf_put_u32(bc, JS_DupAtom(ctx, var_name));
|
|
pos_next++;
|
|
}
|
|
/* remove the OP_label to make room for replacement */
|
|
/* label should have a refcount of 0 anyway */
|
|
/* XXX: should have emitted several OP_nop to avoid this kludge */
|
|
label_pos = ls->pos;
|
|
pos = label_pos - 5;
|
|
assert(bc_buf[pos] == OP_label);
|
|
end_pos = label_pos + 2;
|
|
op = bc_buf[label_pos];
|
|
if (is_strict) {
|
|
if (op != OP_nop) {
|
|
switch(op) {
|
|
case OP_insert3:
|
|
op = OP_insert2;
|
|
break;
|
|
case OP_perm4:
|
|
op = OP_perm3;
|
|
break;
|
|
case OP_rot3l:
|
|
op = OP_swap;
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
bc_buf[pos++] = op;
|
|
}
|
|
} else {
|
|
if (op == OP_insert3)
|
|
bc_buf[pos++] = OP_dup;
|
|
}
|
|
if (is_strict) {
|
|
bc_buf[pos] = OP_put_var_strict;
|
|
/* XXX: need 1 extra OP_drop if destructuring an array */
|
|
} else {
|
|
bc_buf[pos] = OP_put_var;
|
|
/* XXX: need 2 extra OP_drop if destructuring an array */
|
|
}
|
|
put_u32(bc_buf + pos + 1, JS_DupAtom(ctx, var_name));
|
|
pos += 5;
|
|
/* pad with OP_nop */
|
|
while (pos < end_pos)
|
|
bc_buf[pos++] = OP_nop;
|
|
return pos_next;
|
|
}
|
|
|
|
static int add_var_this(JSContext *ctx, JSFunctionDef *fd)
|
|
{
|
|
int idx;
|
|
idx = add_var(ctx, fd, JS_ATOM_this);
|
|
if (idx >= 0 && fd->is_derived_class_constructor) {
|
|
JSVarDef *vd = &fd->vars[idx];
|
|
/* XXX: should have is_this flag or var type */
|
|
vd->is_lexical = 1; /* used to trigger 'uninitialized' checks
|
|
in a derived class constructor */
|
|
}
|
|
return idx;
|
|
}
|
|
|
|
static int resolve_pseudo_var(JSContext *ctx, JSFunctionDef *s,
|
|
JSAtom var_name)
|
|
{
|
|
int var_idx;
|
|
|
|
if (!s->has_this_binding)
|
|
return -1;
|
|
switch(var_name) {
|
|
case JS_ATOM_home_object:
|
|
/* 'home_object' pseudo variable */
|
|
if (s->home_object_var_idx < 0)
|
|
s->home_object_var_idx = add_var(ctx, s, var_name);
|
|
var_idx = s->home_object_var_idx;
|
|
break;
|
|
case JS_ATOM_this_active_func:
|
|
/* 'this.active_func' pseudo variable */
|
|
if (s->this_active_func_var_idx < 0)
|
|
s->this_active_func_var_idx = add_var(ctx, s, var_name);
|
|
var_idx = s->this_active_func_var_idx;
|
|
break;
|
|
case JS_ATOM_new_target:
|
|
/* 'new.target' pseudo variable */
|
|
if (s->new_target_var_idx < 0)
|
|
s->new_target_var_idx = add_var(ctx, s, var_name);
|
|
var_idx = s->new_target_var_idx;
|
|
break;
|
|
case JS_ATOM_this:
|
|
/* 'this' pseudo variable */
|
|
if (s->this_var_idx < 0)
|
|
s->this_var_idx = add_var_this(ctx, s);
|
|
var_idx = s->this_var_idx;
|
|
break;
|
|
default:
|
|
var_idx = -1;
|
|
break;
|
|
}
|
|
return var_idx;
|
|
}
|
|
|
|
/* test if 'var_name' is in the variable object on the stack. If is it
|
|
the case, handle it and jump to 'label_done' */
|
|
static void var_object_test(JSContext *ctx, JSFunctionDef *s,
|
|
JSAtom var_name, int op, DynBuf *bc,
|
|
int *plabel_done, BOOL is_with)
|
|
{
|
|
dbuf_putc(bc, get_with_scope_opcode(op));
|
|
dbuf_put_u32(bc, JS_DupAtom(ctx, var_name));
|
|
*plabel_done = new_label_fd(s, *plabel_done);
|
|
dbuf_put_u32(bc, *plabel_done);
|
|
dbuf_putc(bc, is_with);
|
|
update_label(s, *plabel_done, 1);
|
|
s->jump_size++;
|
|
}
|
|
|
|
/* return the position of the next opcode */
|
|
static int resolve_scope_var(JSContext *ctx, JSFunctionDef *s,
|
|
JSAtom var_name, int scope_level, int op,
|
|
DynBuf *bc, uint8_t *bc_buf,
|
|
LabelSlot *ls, int pos_next)
|
|
{
|
|
int idx, var_idx, is_put;
|
|
int label_done;
|
|
JSFunctionDef *fd;
|
|
JSVarDef *vd;
|
|
BOOL is_pseudo_var, is_arg_scope;
|
|
|
|
label_done = -1;
|
|
|
|
/* XXX: could be simpler to use a specific function to
|
|
resolve the pseudo variables */
|
|
is_pseudo_var = (var_name == JS_ATOM_home_object ||
|
|
var_name == JS_ATOM_this_active_func ||
|
|
var_name == JS_ATOM_new_target ||
|
|
var_name == JS_ATOM_this);
|
|
|
|
/* resolve local scoped variables */
|
|
var_idx = -1;
|
|
for (idx = s->scopes[scope_level].first; idx >= 0;) {
|
|
vd = &s->vars[idx];
|
|
if (vd->var_name == var_name) {
|
|
if (op == OP_scope_put_var || op == OP_scope_make_ref) {
|
|
if (vd->is_const) {
|
|
dbuf_putc(bc, OP_throw_error);
|
|
dbuf_put_u32(bc, JS_DupAtom(ctx, var_name));
|
|
dbuf_putc(bc, JS_THROW_VAR_RO);
|
|
goto done;
|
|
}
|
|
}
|
|
var_idx = idx;
|
|
break;
|
|
} else
|
|
if (vd->var_name == JS_ATOM__with_ && !is_pseudo_var) {
|
|
dbuf_putc(bc, OP_get_loc);
|
|
dbuf_put_u16(bc, idx);
|
|
var_object_test(ctx, s, var_name, op, bc, &label_done, 1);
|
|
}
|
|
idx = vd->scope_next;
|
|
}
|
|
is_arg_scope = (idx == ARG_SCOPE_END);
|
|
if (var_idx < 0) {
|
|
/* argument scope: variables are not visible but pseudo
|
|
variables are visible */
|
|
if (!is_arg_scope) {
|
|
var_idx = find_var(ctx, s, var_name);
|
|
}
|
|
|
|
if (var_idx < 0 && is_pseudo_var)
|
|
var_idx = resolve_pseudo_var(ctx, s, var_name);
|
|
|
|
if (var_idx < 0 && var_name == JS_ATOM_arguments &&
|
|
s->has_arguments_binding) {
|
|
/* 'arguments' pseudo variable */
|
|
var_idx = add_arguments_var(ctx, s);
|
|
}
|
|
if (var_idx < 0 && s->is_func_expr && var_name == s->func_name) {
|
|
/* add a new variable with the function name */
|
|
var_idx = add_func_var(ctx, s, var_name);
|
|
}
|
|
}
|
|
if (var_idx >= 0) {
|
|
if ((op == OP_scope_put_var || op == OP_scope_make_ref) &&
|
|
!(var_idx & ARGUMENT_VAR_OFFSET) &&
|
|
s->vars[var_idx].is_const) {
|
|
/* only happens when assigning a function expression name
|
|
in strict mode */
|
|
dbuf_putc(bc, OP_throw_error);
|
|
dbuf_put_u32(bc, JS_DupAtom(ctx, var_name));
|
|
dbuf_putc(bc, JS_THROW_VAR_RO);
|
|
goto done;
|
|
}
|
|
/* OP_scope_put_var_init is only used to initialize a
|
|
lexical variable, so it is never used in a with or var object. It
|
|
can be used with a closure (module global variable case). */
|
|
switch (op) {
|
|
case OP_scope_make_ref:
|
|
if (!(var_idx & ARGUMENT_VAR_OFFSET) &&
|
|
s->vars[var_idx].var_kind == JS_VAR_FUNCTION_NAME) {
|
|
/* Create a dummy object reference for the func_var */
|
|
dbuf_putc(bc, OP_object);
|
|
dbuf_putc(bc, OP_get_loc);
|
|
dbuf_put_u16(bc, var_idx);
|
|
dbuf_putc(bc, OP_define_field);
|
|
dbuf_put_u32(bc, JS_DupAtom(ctx, var_name));
|
|
dbuf_putc(bc, OP_push_atom_value);
|
|
dbuf_put_u32(bc, JS_DupAtom(ctx, var_name));
|
|
} else
|
|
if (label_done == -1 && can_opt_put_ref_value(bc_buf, ls->pos)) {
|
|
int get_op;
|
|
if (var_idx & ARGUMENT_VAR_OFFSET) {
|
|
get_op = OP_get_arg;
|
|
var_idx -= ARGUMENT_VAR_OFFSET;
|
|
} else {
|
|
if (s->vars[var_idx].is_lexical)
|
|
get_op = OP_get_loc_check;
|
|
else
|
|
get_op = OP_get_loc;
|
|
}
|
|
pos_next = optimize_scope_make_ref(ctx, s, bc, bc_buf, ls,
|
|
pos_next, get_op, var_idx);
|
|
} else {
|
|
/* Create a dummy object with a named slot that is
|
|
a reference to the local variable */
|
|
if (var_idx & ARGUMENT_VAR_OFFSET) {
|
|
dbuf_putc(bc, OP_make_arg_ref);
|
|
dbuf_put_u32(bc, JS_DupAtom(ctx, var_name));
|
|
dbuf_put_u16(bc, var_idx - ARGUMENT_VAR_OFFSET);
|
|
} else {
|
|
dbuf_putc(bc, OP_make_loc_ref);
|
|
dbuf_put_u32(bc, JS_DupAtom(ctx, var_name));
|
|
dbuf_put_u16(bc, var_idx);
|
|
}
|
|
}
|
|
break;
|
|
case OP_scope_get_ref:
|
|
dbuf_putc(bc, OP_undefined);
|
|
/* fall thru */
|
|
case OP_scope_get_var_undef:
|
|
case OP_scope_get_var:
|
|
case OP_scope_put_var:
|
|
case OP_scope_put_var_init:
|
|
is_put = (op == OP_scope_put_var || op == OP_scope_put_var_init);
|
|
if (var_idx & ARGUMENT_VAR_OFFSET) {
|
|
dbuf_putc(bc, OP_get_arg + is_put);
|
|
dbuf_put_u16(bc, var_idx - ARGUMENT_VAR_OFFSET);
|
|
} else {
|
|
if (is_put) {
|
|
if (s->vars[var_idx].is_lexical) {
|
|
if (op == OP_scope_put_var_init) {
|
|
/* 'this' can only be initialized once */
|
|
if (var_name == JS_ATOM_this)
|
|
dbuf_putc(bc, OP_put_loc_check_init);
|
|
else
|
|
dbuf_putc(bc, OP_put_loc);
|
|
} else {
|
|
dbuf_putc(bc, OP_put_loc_check);
|
|
}
|
|
} else {
|
|
dbuf_putc(bc, OP_put_loc);
|
|
}
|
|
} else {
|
|
if (s->vars[var_idx].is_lexical) {
|
|
dbuf_putc(bc, OP_get_loc_check);
|
|
} else {
|
|
dbuf_putc(bc, OP_get_loc);
|
|
}
|
|
}
|
|
dbuf_put_u16(bc, var_idx);
|
|
}
|
|
break;
|
|
case OP_scope_delete_var:
|
|
dbuf_putc(bc, OP_push_false);
|
|
break;
|
|
}
|
|
goto done;
|
|
}
|
|
/* check eval object */
|
|
if (!is_arg_scope && s->var_object_idx >= 0 && !is_pseudo_var) {
|
|
dbuf_putc(bc, OP_get_loc);
|
|
dbuf_put_u16(bc, s->var_object_idx);
|
|
var_object_test(ctx, s, var_name, op, bc, &label_done, 0);
|
|
}
|
|
/* check eval object in argument scope */
|
|
if (s->arg_var_object_idx >= 0 && !is_pseudo_var) {
|
|
dbuf_putc(bc, OP_get_loc);
|
|
dbuf_put_u16(bc, s->arg_var_object_idx);
|
|
var_object_test(ctx, s, var_name, op, bc, &label_done, 0);
|
|
}
|
|
|
|
/* check parent scopes */
|
|
for (fd = s; fd->parent;) {
|
|
scope_level = fd->parent_scope_level;
|
|
fd = fd->parent;
|
|
for (idx = fd->scopes[scope_level].first; idx >= 0;) {
|
|
vd = &fd->vars[idx];
|
|
if (vd->var_name == var_name) {
|
|
if (op == OP_scope_put_var || op == OP_scope_make_ref) {
|
|
if (vd->is_const) {
|
|
dbuf_putc(bc, OP_throw_error);
|
|
dbuf_put_u32(bc, JS_DupAtom(ctx, var_name));
|
|
dbuf_putc(bc, JS_THROW_VAR_RO);
|
|
goto done;
|
|
}
|
|
}
|
|
var_idx = idx;
|
|
break;
|
|
} else if (vd->var_name == JS_ATOM__with_ && !is_pseudo_var) {
|
|
vd->is_captured = 1;
|
|
idx = get_closure_var(ctx, s, fd, FALSE, idx, vd->var_name, FALSE, FALSE, JS_VAR_NORMAL);
|
|
if (idx >= 0) {
|
|
dbuf_putc(bc, OP_get_var_ref);
|
|
dbuf_put_u16(bc, idx);
|
|
var_object_test(ctx, s, var_name, op, bc, &label_done, 1);
|
|
}
|
|
}
|
|
idx = vd->scope_next;
|
|
}
|
|
is_arg_scope = (idx == ARG_SCOPE_END);
|
|
if (var_idx >= 0)
|
|
break;
|
|
|
|
if (!is_arg_scope) {
|
|
var_idx = find_var(ctx, fd, var_name);
|
|
if (var_idx >= 0)
|
|
break;
|
|
}
|
|
if (is_pseudo_var) {
|
|
var_idx = resolve_pseudo_var(ctx, fd, var_name);
|
|
if (var_idx >= 0)
|
|
break;
|
|
}
|
|
if (var_name == JS_ATOM_arguments && fd->has_arguments_binding) {
|
|
var_idx = add_arguments_var(ctx, fd);
|
|
break;
|
|
}
|
|
if (fd->is_func_expr && fd->func_name == var_name) {
|
|
/* add a new variable with the function name */
|
|
var_idx = add_func_var(ctx, fd, var_name);
|
|
break;
|
|
}
|
|
|
|
/* check eval object */
|
|
if (!is_arg_scope && fd->var_object_idx >= 0 && !is_pseudo_var) {
|
|
vd = &fd->vars[fd->var_object_idx];
|
|
vd->is_captured = 1;
|
|
idx = get_closure_var(ctx, s, fd, FALSE,
|
|
fd->var_object_idx, vd->var_name,
|
|
FALSE, FALSE, JS_VAR_NORMAL);
|
|
dbuf_putc(bc, OP_get_var_ref);
|
|
dbuf_put_u16(bc, idx);
|
|
var_object_test(ctx, s, var_name, op, bc, &label_done, 0);
|
|
}
|
|
|
|
/* check eval object in argument scope */
|
|
if (fd->arg_var_object_idx >= 0 && !is_pseudo_var) {
|
|
vd = &fd->vars[fd->arg_var_object_idx];
|
|
vd->is_captured = 1;
|
|
idx = get_closure_var(ctx, s, fd, FALSE,
|
|
fd->arg_var_object_idx, vd->var_name,
|
|
FALSE, FALSE, JS_VAR_NORMAL);
|
|
dbuf_putc(bc, OP_get_var_ref);
|
|
dbuf_put_u16(bc, idx);
|
|
var_object_test(ctx, s, var_name, op, bc, &label_done, 0);
|
|
}
|
|
|
|
if (fd->is_eval)
|
|
break; /* it it necessarily the top level function */
|
|
}
|
|
|
|
/* check direct eval scope (in the closure of the eval function
|
|
which is necessarily at the top level) */
|
|
if (!fd)
|
|
fd = s;
|
|
if (var_idx < 0 && fd->is_eval) {
|
|
int idx1;
|
|
for (idx1 = 0; idx1 < fd->closure_var_count; idx1++) {
|
|
JSClosureVar *cv = &fd->closure_var[idx1];
|
|
if (var_name == cv->var_name) {
|
|
if (fd != s) {
|
|
idx = get_closure_var2(ctx, s, fd,
|
|
FALSE,
|
|
cv->is_arg, idx1,
|
|
cv->var_name, cv->is_const,
|
|
cv->is_lexical, cv->var_kind);
|
|
} else {
|
|
idx = idx1;
|
|
}
|
|
goto has_idx;
|
|
} else if ((cv->var_name == JS_ATOM__var_ ||
|
|
cv->var_name == JS_ATOM__arg_var_ ||
|
|
cv->var_name == JS_ATOM__with_) && !is_pseudo_var) {
|
|
int is_with = (cv->var_name == JS_ATOM__with_);
|
|
if (fd != s) {
|
|
idx = get_closure_var2(ctx, s, fd,
|
|
FALSE,
|
|
cv->is_arg, idx1,
|
|
cv->var_name, FALSE, FALSE,
|
|
JS_VAR_NORMAL);
|
|
} else {
|
|
idx = idx1;
|
|
}
|
|
dbuf_putc(bc, OP_get_var_ref);
|
|
dbuf_put_u16(bc, idx);
|
|
var_object_test(ctx, s, var_name, op, bc, &label_done, is_with);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (var_idx >= 0) {
|
|
/* find the corresponding closure variable */
|
|
if (var_idx & ARGUMENT_VAR_OFFSET) {
|
|
fd->args[var_idx - ARGUMENT_VAR_OFFSET].is_captured = 1;
|
|
idx = get_closure_var(ctx, s, fd,
|
|
TRUE, var_idx - ARGUMENT_VAR_OFFSET,
|
|
var_name, FALSE, FALSE, JS_VAR_NORMAL);
|
|
} else {
|
|
fd->vars[var_idx].is_captured = 1;
|
|
idx = get_closure_var(ctx, s, fd,
|
|
FALSE, var_idx,
|
|
var_name,
|
|
fd->vars[var_idx].is_const,
|
|
fd->vars[var_idx].is_lexical,
|
|
fd->vars[var_idx].var_kind);
|
|
}
|
|
if (idx >= 0) {
|
|
has_idx:
|
|
if ((op == OP_scope_put_var || op == OP_scope_make_ref) &&
|
|
s->closure_var[idx].is_const) {
|
|
dbuf_putc(bc, OP_throw_error);
|
|
dbuf_put_u32(bc, JS_DupAtom(ctx, var_name));
|
|
dbuf_putc(bc, JS_THROW_VAR_RO);
|
|
goto done;
|
|
}
|
|
switch (op) {
|
|
case OP_scope_make_ref:
|
|
if (s->closure_var[idx].var_kind == JS_VAR_FUNCTION_NAME) {
|
|
/* Create a dummy object reference for the func_var */
|
|
dbuf_putc(bc, OP_object);
|
|
dbuf_putc(bc, OP_get_var_ref);
|
|
dbuf_put_u16(bc, idx);
|
|
dbuf_putc(bc, OP_define_field);
|
|
dbuf_put_u32(bc, JS_DupAtom(ctx, var_name));
|
|
dbuf_putc(bc, OP_push_atom_value);
|
|
dbuf_put_u32(bc, JS_DupAtom(ctx, var_name));
|
|
} else
|
|
if (label_done == -1 &&
|
|
can_opt_put_ref_value(bc_buf, ls->pos)) {
|
|
int get_op;
|
|
if (s->closure_var[idx].is_lexical)
|
|
get_op = OP_get_var_ref_check;
|
|
else
|
|
get_op = OP_get_var_ref;
|
|
pos_next = optimize_scope_make_ref(ctx, s, bc, bc_buf, ls,
|
|
pos_next,
|
|
get_op, idx);
|
|
} else {
|
|
/* Create a dummy object with a named slot that is
|
|
a reference to the closure variable */
|
|
dbuf_putc(bc, OP_make_var_ref_ref);
|
|
dbuf_put_u32(bc, JS_DupAtom(ctx, var_name));
|
|
dbuf_put_u16(bc, idx);
|
|
}
|
|
break;
|
|
case OP_scope_get_ref:
|
|
/* XXX: should create a dummy object with a named slot that is
|
|
a reference to the closure variable */
|
|
dbuf_putc(bc, OP_undefined);
|
|
/* fall thru */
|
|
case OP_scope_get_var_undef:
|
|
case OP_scope_get_var:
|
|
case OP_scope_put_var:
|
|
case OP_scope_put_var_init:
|
|
is_put = (op == OP_scope_put_var ||
|
|
op == OP_scope_put_var_init);
|
|
if (is_put) {
|
|
if (s->closure_var[idx].is_lexical) {
|
|
if (op == OP_scope_put_var_init) {
|
|
/* 'this' can only be initialized once */
|
|
if (var_name == JS_ATOM_this)
|
|
dbuf_putc(bc, OP_put_var_ref_check_init);
|
|
else
|
|
dbuf_putc(bc, OP_put_var_ref);
|
|
} else {
|
|
dbuf_putc(bc, OP_put_var_ref_check);
|
|
}
|
|
} else {
|
|
dbuf_putc(bc, OP_put_var_ref);
|
|
}
|
|
} else {
|
|
if (s->closure_var[idx].is_lexical) {
|
|
dbuf_putc(bc, OP_get_var_ref_check);
|
|
} else {
|
|
dbuf_putc(bc, OP_get_var_ref);
|
|
}
|
|
}
|
|
dbuf_put_u16(bc, idx);
|
|
break;
|
|
case OP_scope_delete_var:
|
|
dbuf_putc(bc, OP_push_false);
|
|
break;
|
|
}
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
/* global variable access */
|
|
|
|
switch (op) {
|
|
case OP_scope_make_ref:
|
|
if (label_done == -1 && can_opt_put_global_ref_value(bc_buf, ls->pos)) {
|
|
pos_next = optimize_scope_make_global_ref(ctx, s, bc, bc_buf, ls,
|
|
pos_next, var_name);
|
|
} else {
|
|
dbuf_putc(bc, OP_make_var_ref);
|
|
dbuf_put_u32(bc, JS_DupAtom(ctx, var_name));
|
|
}
|
|
break;
|
|
case OP_scope_get_ref:
|
|
/* XXX: should create a dummy object with a named slot that is
|
|
a reference to the global variable */
|
|
dbuf_putc(bc, OP_undefined);
|
|
dbuf_putc(bc, OP_get_var);
|
|
dbuf_put_u32(bc, JS_DupAtom(ctx, var_name));
|
|
break;
|
|
case OP_scope_get_var_undef:
|
|
case OP_scope_get_var:
|
|
case OP_scope_put_var:
|
|
dbuf_putc(bc, OP_get_var_undef + (op - OP_scope_get_var_undef));
|
|
dbuf_put_u32(bc, JS_DupAtom(ctx, var_name));
|
|
break;
|
|
case OP_scope_put_var_init:
|
|
dbuf_putc(bc, OP_put_var_init);
|
|
dbuf_put_u32(bc, JS_DupAtom(ctx, var_name));
|
|
break;
|
|
case OP_scope_delete_var:
|
|
dbuf_putc(bc, OP_delete_var);
|
|
dbuf_put_u32(bc, JS_DupAtom(ctx, var_name));
|
|
break;
|
|
}
|
|
done:
|
|
if (label_done >= 0) {
|
|
dbuf_putc(bc, OP_label);
|
|
dbuf_put_u32(bc, label_done);
|
|
s->label_slots[label_done].pos2 = bc->size;
|
|
}
|
|
return pos_next;
|
|
}
|
|
|
|
/* search in all scopes */
|
|
static int find_private_class_field_all(JSContext *ctx, JSFunctionDef *fd,
|
|
JSAtom name, int scope_level)
|
|
{
|
|
int idx;
|
|
|
|
idx = fd->scopes[scope_level].first;
|
|
while (idx >= 0) {
|
|
if (fd->vars[idx].var_name == name)
|
|
return idx;
|
|
idx = fd->vars[idx].scope_next;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static void get_loc_or_ref(DynBuf *bc, BOOL is_ref, int idx)
|
|
{
|
|
/* if the field is not initialized, the error is catched when
|
|
accessing it */
|
|
if (is_ref)
|
|
dbuf_putc(bc, OP_get_var_ref);
|
|
else
|
|
dbuf_putc(bc, OP_get_loc);
|
|
dbuf_put_u16(bc, idx);
|
|
}
|
|
|
|
static int resolve_scope_private_field1(JSContext *ctx,
|
|
BOOL *pis_ref, int *pvar_kind,
|
|
JSFunctionDef *s,
|
|
JSAtom var_name, int scope_level)
|
|
{
|
|
int idx, var_kind;
|
|
JSFunctionDef *fd;
|
|
BOOL is_ref;
|
|
|
|
fd = s;
|
|
is_ref = FALSE;
|
|
for(;;) {
|
|
idx = find_private_class_field_all(ctx, fd, var_name, scope_level);
|
|
if (idx >= 0) {
|
|
var_kind = fd->vars[idx].var_kind;
|
|
if (is_ref) {
|
|
idx = get_closure_var(ctx, s, fd, FALSE, idx, var_name,
|
|
TRUE, TRUE, JS_VAR_NORMAL);
|
|
if (idx < 0)
|
|
return -1;
|
|
}
|
|
break;
|
|
}
|
|
scope_level = fd->parent_scope_level;
|
|
if (!fd->parent) {
|
|
if (fd->is_eval) {
|
|
/* closure of the eval function (top level) */
|
|
for (idx = 0; idx < fd->closure_var_count; idx++) {
|
|
JSClosureVar *cv = &fd->closure_var[idx];
|
|
if (cv->var_name == var_name) {
|
|
var_kind = cv->var_kind;
|
|
is_ref = TRUE;
|
|
if (fd != s) {
|
|
idx = get_closure_var2(ctx, s, fd,
|
|
FALSE,
|
|
cv->is_arg, idx,
|
|
cv->var_name, cv->is_const,
|
|
cv->is_lexical,
|
|
cv->var_kind);
|
|
if (idx < 0)
|
|
return -1;
|
|
}
|
|
goto done;
|
|
}
|
|
}
|
|
}
|
|
/* XXX: no line number info */
|
|
// XXX: resolve_scope_private_field1() should take JSParseState *s and use js_parse_error_atom
|
|
JS_ThrowSyntaxErrorAtom(ctx, "undefined private field '%s'",
|
|
var_name);
|
|
return -1;
|
|
} else {
|
|
fd = fd->parent;
|
|
}
|
|
is_ref = TRUE;
|
|
}
|
|
done:
|
|
*pis_ref = is_ref;
|
|
*pvar_kind = var_kind;
|
|
return idx;
|
|
}
|
|
|
|
/* return 0 if OK or -1 if the private field could not be resolved */
|
|
static int resolve_scope_private_field(JSContext *ctx, JSFunctionDef *s,
|
|
JSAtom var_name, int scope_level, int op,
|
|
DynBuf *bc)
|
|
{
|
|
int idx, var_kind;
|
|
BOOL is_ref;
|
|
|
|
idx = resolve_scope_private_field1(ctx, &is_ref, &var_kind, s,
|
|
var_name, scope_level);
|
|
if (idx < 0)
|
|
return -1;
|
|
assert(var_kind != JS_VAR_NORMAL);
|
|
switch (op) {
|
|
case OP_scope_get_private_field:
|
|
case OP_scope_get_private_field2:
|
|
switch(var_kind) {
|
|
case JS_VAR_PRIVATE_FIELD:
|
|
if (op == OP_scope_get_private_field2)
|
|
dbuf_putc(bc, OP_dup);
|
|
get_loc_or_ref(bc, is_ref, idx);
|
|
dbuf_putc(bc, OP_get_private_field);
|
|
break;
|
|
case JS_VAR_PRIVATE_METHOD:
|
|
get_loc_or_ref(bc, is_ref, idx);
|
|
dbuf_putc(bc, OP_check_brand);
|
|
if (op != OP_scope_get_private_field2)
|
|
dbuf_putc(bc, OP_nip);
|
|
break;
|
|
case JS_VAR_PRIVATE_GETTER:
|
|
case JS_VAR_PRIVATE_GETTER_SETTER:
|
|
if (op == OP_scope_get_private_field2)
|
|
dbuf_putc(bc, OP_dup);
|
|
get_loc_or_ref(bc, is_ref, idx);
|
|
dbuf_putc(bc, OP_check_brand);
|
|
dbuf_putc(bc, OP_call_method);
|
|
dbuf_put_u16(bc, 0);
|
|
break;
|
|
case JS_VAR_PRIVATE_SETTER:
|
|
/* XXX: add clearer error message */
|
|
dbuf_putc(bc, OP_throw_error);
|
|
dbuf_put_u32(bc, JS_DupAtom(ctx, var_name));
|
|
dbuf_putc(bc, JS_THROW_VAR_RO);
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
break;
|
|
case OP_scope_put_private_field:
|
|
switch(var_kind) {
|
|
case JS_VAR_PRIVATE_FIELD:
|
|
get_loc_or_ref(bc, is_ref, idx);
|
|
dbuf_putc(bc, OP_put_private_field);
|
|
break;
|
|
case JS_VAR_PRIVATE_METHOD:
|
|
case JS_VAR_PRIVATE_GETTER:
|
|
/* XXX: add clearer error message */
|
|
dbuf_putc(bc, OP_throw_error);
|
|
dbuf_put_u32(bc, JS_DupAtom(ctx, var_name));
|
|
dbuf_putc(bc, JS_THROW_VAR_RO);
|
|
break;
|
|
case JS_VAR_PRIVATE_SETTER:
|
|
case JS_VAR_PRIVATE_GETTER_SETTER:
|
|
{
|
|
JSAtom setter_name = get_private_setter_name(ctx, var_name);
|
|
if (setter_name == JS_ATOM_NULL)
|
|
return -1;
|
|
idx = resolve_scope_private_field1(ctx, &is_ref,
|
|
&var_kind, s,
|
|
setter_name, scope_level);
|
|
JS_FreeAtom(ctx, setter_name);
|
|
if (idx < 0)
|
|
return -1;
|
|
assert(var_kind == JS_VAR_PRIVATE_SETTER);
|
|
get_loc_or_ref(bc, is_ref, idx);
|
|
dbuf_putc(bc, OP_swap);
|
|
/* obj func value */
|
|
dbuf_putc(bc, OP_rot3r);
|
|
/* value obj func */
|
|
dbuf_putc(bc, OP_check_brand);
|
|
dbuf_putc(bc, OP_rot3l);
|
|
/* obj func value */
|
|
dbuf_putc(bc, OP_call_method);
|
|
dbuf_put_u16(bc, 1);
|
|
dbuf_putc(bc, OP_drop);
|
|
}
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void mark_eval_captured_variables(JSContext *ctx, JSFunctionDef *s,
|
|
int scope_level)
|
|
{
|
|
int idx;
|
|
JSVarDef *vd;
|
|
|
|
for (idx = s->scopes[scope_level].first; idx >= 0;) {
|
|
vd = &s->vars[idx];
|
|
vd->is_captured = 1;
|
|
idx = vd->scope_next;
|
|
}
|
|
}
|
|
|
|
/* XXX: should handle the argument scope generically */
|
|
static BOOL is_var_in_arg_scope(const JSVarDef *vd)
|
|
{
|
|
return (vd->var_name == JS_ATOM_home_object ||
|
|
vd->var_name == JS_ATOM_this_active_func ||
|
|
vd->var_name == JS_ATOM_new_target ||
|
|
vd->var_name == JS_ATOM_this ||
|
|
vd->var_name == JS_ATOM__arg_var_ ||
|
|
vd->var_kind == JS_VAR_FUNCTION_NAME);
|
|
}
|
|
|
|
static void add_eval_variables(JSContext *ctx, JSFunctionDef *s)
|
|
{
|
|
JSFunctionDef *fd;
|
|
JSVarDef *vd;
|
|
int i, scope_level, scope_idx;
|
|
BOOL has_arguments_binding, has_this_binding, is_arg_scope;
|
|
|
|
/* in non strict mode, variables are created in the caller's
|
|
environment object */
|
|
if (!s->is_eval && !(s->js_mode & JS_MODE_STRICT)) {
|
|
s->var_object_idx = add_var(ctx, s, JS_ATOM__var_);
|
|
if (s->has_parameter_expressions) {
|
|
/* an additional variable object is needed for the
|
|
argument scope */
|
|
s->arg_var_object_idx = add_var(ctx, s, JS_ATOM__arg_var_);
|
|
}
|
|
}
|
|
|
|
/* eval can potentially use 'arguments' so we must define it */
|
|
has_this_binding = s->has_this_binding;
|
|
if (has_this_binding) {
|
|
if (s->this_var_idx < 0)
|
|
s->this_var_idx = add_var_this(ctx, s);
|
|
if (s->new_target_var_idx < 0)
|
|
s->new_target_var_idx = add_var(ctx, s, JS_ATOM_new_target);
|
|
if (s->is_derived_class_constructor && s->this_active_func_var_idx < 0)
|
|
s->this_active_func_var_idx = add_var(ctx, s, JS_ATOM_this_active_func);
|
|
if (s->has_home_object && s->home_object_var_idx < 0)
|
|
s->home_object_var_idx = add_var(ctx, s, JS_ATOM_home_object);
|
|
}
|
|
has_arguments_binding = s->has_arguments_binding;
|
|
if (has_arguments_binding) {
|
|
add_arguments_var(ctx, s);
|
|
/* also add an arguments binding in the argument scope to
|
|
raise an error if a direct eval in the argument scope tries
|
|
to redefine it */
|
|
if (s->has_parameter_expressions && !(s->js_mode & JS_MODE_STRICT))
|
|
add_arguments_arg(ctx, s);
|
|
}
|
|
if (s->is_func_expr && s->func_name != JS_ATOM_NULL)
|
|
add_func_var(ctx, s, s->func_name);
|
|
|
|
/* eval can use all the variables of the enclosing functions, so
|
|
they must be all put in the closure. The closure variables are
|
|
ordered by scope. It works only because no closure are created
|
|
before. */
|
|
assert(s->is_eval || s->closure_var_count == 0);
|
|
|
|
/* XXX: inefficient, but eval performance is less critical */
|
|
fd = s;
|
|
for(;;) {
|
|
scope_level = fd->parent_scope_level;
|
|
fd = fd->parent;
|
|
if (!fd)
|
|
break;
|
|
/* add 'this' if it was not previously added */
|
|
if (!has_this_binding && fd->has_this_binding) {
|
|
if (fd->this_var_idx < 0)
|
|
fd->this_var_idx = add_var_this(ctx, fd);
|
|
if (fd->new_target_var_idx < 0)
|
|
fd->new_target_var_idx = add_var(ctx, fd, JS_ATOM_new_target);
|
|
if (fd->is_derived_class_constructor && fd->this_active_func_var_idx < 0)
|
|
fd->this_active_func_var_idx = add_var(ctx, fd, JS_ATOM_this_active_func);
|
|
if (fd->has_home_object && fd->home_object_var_idx < 0)
|
|
fd->home_object_var_idx = add_var(ctx, fd, JS_ATOM_home_object);
|
|
has_this_binding = TRUE;
|
|
}
|
|
/* add 'arguments' if it was not previously added */
|
|
if (!has_arguments_binding && fd->has_arguments_binding) {
|
|
add_arguments_var(ctx, fd);
|
|
has_arguments_binding = TRUE;
|
|
}
|
|
/* add function name */
|
|
if (fd->is_func_expr && fd->func_name != JS_ATOM_NULL)
|
|
add_func_var(ctx, fd, fd->func_name);
|
|
|
|
/* add lexical variables */
|
|
scope_idx = fd->scopes[scope_level].first;
|
|
while (scope_idx >= 0) {
|
|
vd = &fd->vars[scope_idx];
|
|
vd->is_captured = 1;
|
|
get_closure_var(ctx, s, fd, FALSE, scope_idx,
|
|
vd->var_name, vd->is_const, vd->is_lexical, vd->var_kind);
|
|
scope_idx = vd->scope_next;
|
|
}
|
|
is_arg_scope = (scope_idx == ARG_SCOPE_END);
|
|
if (!is_arg_scope) {
|
|
/* add unscoped variables */
|
|
/* XXX: propagate is_const and var_kind too ? */
|
|
for(i = 0; i < fd->arg_count; i++) {
|
|
vd = &fd->args[i];
|
|
if (vd->var_name != JS_ATOM_NULL) {
|
|
get_closure_var(ctx, s, fd,
|
|
TRUE, i, vd->var_name, FALSE,
|
|
vd->is_lexical, JS_VAR_NORMAL);
|
|
}
|
|
}
|
|
for(i = 0; i < fd->var_count; i++) {
|
|
vd = &fd->vars[i];
|
|
/* do not close top level last result */
|
|
if (vd->scope_level == 0 &&
|
|
vd->var_name != JS_ATOM__ret_ &&
|
|
vd->var_name != JS_ATOM_NULL) {
|
|
get_closure_var(ctx, s, fd,
|
|
FALSE, i, vd->var_name, FALSE,
|
|
vd->is_lexical, JS_VAR_NORMAL);
|
|
}
|
|
}
|
|
} else {
|
|
for(i = 0; i < fd->var_count; i++) {
|
|
vd = &fd->vars[i];
|
|
/* do not close top level last result */
|
|
if (vd->scope_level == 0 && is_var_in_arg_scope(vd)) {
|
|
get_closure_var(ctx, s, fd,
|
|
FALSE, i, vd->var_name, FALSE,
|
|
vd->is_lexical, JS_VAR_NORMAL);
|
|
}
|
|
}
|
|
}
|
|
if (fd->is_eval) {
|
|
int idx;
|
|
/* add direct eval variables (we are necessarily at the
|
|
top level) */
|
|
for (idx = 0; idx < fd->closure_var_count; idx++) {
|
|
JSClosureVar *cv = &fd->closure_var[idx];
|
|
get_closure_var2(ctx, s, fd,
|
|
FALSE, cv->is_arg,
|
|
idx, cv->var_name, cv->is_const,
|
|
cv->is_lexical, cv->var_kind);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void set_closure_from_var(JSContext *ctx, JSClosureVar *cv,
|
|
JSVarDef *vd, int var_idx)
|
|
{
|
|
cv->is_local = TRUE;
|
|
cv->is_arg = FALSE;
|
|
cv->is_const = vd->is_const;
|
|
cv->is_lexical = vd->is_lexical;
|
|
cv->var_kind = vd->var_kind;
|
|
cv->var_idx = var_idx;
|
|
cv->var_name = JS_DupAtom(ctx, vd->var_name);
|
|
}
|
|
|
|
/* for direct eval compilation: add references to the variables of the
|
|
calling function */
|
|
static __exception int add_closure_variables(JSContext *ctx, JSFunctionDef *s,
|
|
JSFunctionBytecode *b, int scope_idx)
|
|
{
|
|
int i, count;
|
|
JSVarDef *vd;
|
|
BOOL is_arg_scope;
|
|
|
|
count = b->arg_count + b->var_count + b->closure_var_count;
|
|
s->closure_var = NULL;
|
|
s->closure_var_count = 0;
|
|
s->closure_var_size = count;
|
|
if (count == 0)
|
|
return 0;
|
|
s->closure_var = js_malloc(ctx, sizeof(s->closure_var[0]) * count);
|
|
if (!s->closure_var)
|
|
return -1;
|
|
/* Add lexical variables in scope at the point of evaluation */
|
|
for (i = scope_idx; i >= 0;) {
|
|
vd = &b->vardefs[b->arg_count + i];
|
|
if (vd->scope_level > 0) {
|
|
JSClosureVar *cv = &s->closure_var[s->closure_var_count++];
|
|
set_closure_from_var(ctx, cv, vd, i);
|
|
}
|
|
i = vd->scope_next;
|
|
}
|
|
is_arg_scope = (i == ARG_SCOPE_END);
|
|
if (!is_arg_scope) {
|
|
/* Add argument variables */
|
|
for(i = 0; i < b->arg_count; i++) {
|
|
JSClosureVar *cv = &s->closure_var[s->closure_var_count++];
|
|
vd = &b->vardefs[i];
|
|
cv->is_local = TRUE;
|
|
cv->is_arg = TRUE;
|
|
cv->is_const = FALSE;
|
|
cv->is_lexical = FALSE;
|
|
cv->var_kind = JS_VAR_NORMAL;
|
|
cv->var_idx = i;
|
|
cv->var_name = JS_DupAtom(ctx, vd->var_name);
|
|
}
|
|
/* Add local non lexical variables */
|
|
for(i = 0; i < b->var_count; i++) {
|
|
vd = &b->vardefs[b->arg_count + i];
|
|
if (vd->scope_level == 0 && vd->var_name != JS_ATOM__ret_) {
|
|
JSClosureVar *cv = &s->closure_var[s->closure_var_count++];
|
|
set_closure_from_var(ctx, cv, vd, i);
|
|
}
|
|
}
|
|
} else {
|
|
/* only add pseudo variables */
|
|
for(i = 0; i < b->var_count; i++) {
|
|
vd = &b->vardefs[b->arg_count + i];
|
|
if (vd->scope_level == 0 && is_var_in_arg_scope(vd)) {
|
|
JSClosureVar *cv = &s->closure_var[s->closure_var_count++];
|
|
set_closure_from_var(ctx, cv, vd, i);
|
|
}
|
|
}
|
|
}
|
|
for(i = 0; i < b->closure_var_count; i++) {
|
|
JSClosureVar *cv0 = &b->closure_var[i];
|
|
JSClosureVar *cv = &s->closure_var[s->closure_var_count++];
|
|
cv->is_local = FALSE;
|
|
cv->is_arg = cv0->is_arg;
|
|
cv->is_const = cv0->is_const;
|
|
cv->is_lexical = cv0->is_lexical;
|
|
cv->var_kind = cv0->var_kind;
|
|
cv->var_idx = i;
|
|
cv->var_name = JS_DupAtom(ctx, cv0->var_name);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
typedef struct CodeContext {
|
|
const uint8_t *bc_buf; /* code buffer */
|
|
int bc_len; /* length of the code buffer */
|
|
int pos; /* position past the matched code pattern */
|
|
int line_num; /* last visited OP_source_loc parameter or -1 */
|
|
int col_num; /* last visited OP_source_loc parameter or -1 */
|
|
int op;
|
|
int idx;
|
|
int label;
|
|
int val;
|
|
JSAtom atom;
|
|
} CodeContext;
|
|
|
|
#define M2(op1, op2) ((uint32_t)(op1) | ((uint32_t)(op2) << 8))
|
|
#define M3(op1, op2, op3) ((uint32_t)(op1) | ((uint32_t)(op2) << 8) | ((uint32_t)(op3) << 16))
|
|
#define M4(op1, op2, op3, op4) ((uint32_t)(op1) | ((uint32_t)(op2) << 8) | ((uint32_t)(op3) << 16) | ((uint32_t)(op4) << 24))
|
|
|
|
static BOOL code_match(CodeContext *s, int pos, ...)
|
|
{
|
|
const uint8_t *tab = s->bc_buf;
|
|
int op, len, op1, line_num, col_num, pos_next;
|
|
va_list ap;
|
|
BOOL ret = FALSE;
|
|
|
|
line_num = -1;
|
|
col_num = -1;
|
|
va_start(ap, pos);
|
|
|
|
for(;;) {
|
|
op1 = va_arg(ap, int);
|
|
if (op1 == -1) {
|
|
s->pos = pos;
|
|
s->line_num = line_num;
|
|
s->col_num = col_num;
|
|
ret = TRUE;
|
|
break;
|
|
}
|
|
for (;;) {
|
|
if (pos >= s->bc_len)
|
|
goto done;
|
|
op = tab[pos];
|
|
len = opcode_info[op].size;
|
|
pos_next = pos + len;
|
|
if (pos_next > s->bc_len)
|
|
goto done;
|
|
if (op == OP_source_loc) {
|
|
line_num = get_u32(tab + pos + 1);
|
|
col_num = get_u32(tab + pos + 5);
|
|
pos = pos_next;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
if (op != op1) {
|
|
if (op1 == (uint8_t)op1 || !op)
|
|
break;
|
|
if (op != (uint8_t)op1
|
|
&& op != (uint8_t)(op1 >> 8)
|
|
&& op != (uint8_t)(op1 >> 16)
|
|
&& op != (uint8_t)(op1 >> 24)) {
|
|
break;
|
|
}
|
|
s->op = op;
|
|
}
|
|
|
|
pos++;
|
|
switch(opcode_info[op].fmt) {
|
|
case OP_FMT_loc8:
|
|
case OP_FMT_u8:
|
|
{
|
|
int idx = tab[pos];
|
|
int arg = va_arg(ap, int);
|
|
if (arg == -1) {
|
|
s->idx = idx;
|
|
} else {
|
|
if (arg != idx)
|
|
goto done;
|
|
}
|
|
break;
|
|
}
|
|
case OP_FMT_u16:
|
|
case OP_FMT_npop:
|
|
case OP_FMT_loc:
|
|
case OP_FMT_arg:
|
|
case OP_FMT_var_ref:
|
|
{
|
|
int idx = get_u16(tab + pos);
|
|
int arg = va_arg(ap, int);
|
|
if (arg == -1) {
|
|
s->idx = idx;
|
|
} else {
|
|
if (arg != idx)
|
|
goto done;
|
|
}
|
|
break;
|
|
}
|
|
case OP_FMT_i32:
|
|
case OP_FMT_u32:
|
|
case OP_FMT_label:
|
|
case OP_FMT_const:
|
|
{
|
|
s->label = get_u32(tab + pos);
|
|
break;
|
|
}
|
|
case OP_FMT_label_u16:
|
|
{
|
|
s->label = get_u32(tab + pos);
|
|
s->val = get_u16(tab + pos + 4);
|
|
break;
|
|
}
|
|
case OP_FMT_atom:
|
|
{
|
|
s->atom = get_u32(tab + pos);
|
|
break;
|
|
}
|
|
case OP_FMT_atom_u8:
|
|
{
|
|
s->atom = get_u32(tab + pos);
|
|
s->val = get_u8(tab + pos + 4);
|
|
break;
|
|
}
|
|
case OP_FMT_atom_u16:
|
|
{
|
|
s->atom = get_u32(tab + pos);
|
|
s->val = get_u16(tab + pos + 4);
|
|
break;
|
|
}
|
|
case OP_FMT_atom_label_u8:
|
|
{
|
|
s->atom = get_u32(tab + pos);
|
|
s->label = get_u32(tab + pos + 4);
|
|
s->val = get_u8(tab + pos + 8);
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
pos = pos_next;
|
|
}
|
|
done:
|
|
va_end(ap);
|
|
return ret;
|
|
}
|
|
|
|
static void instantiate_hoisted_definitions(JSContext *ctx, JSFunctionDef *s, DynBuf *bc)
|
|
{
|
|
int i, idx, label_next = -1;
|
|
|
|
/* add the hoisted functions in arguments and local variables */
|
|
for(i = 0; i < s->arg_count; i++) {
|
|
JSVarDef *vd = &s->args[i];
|
|
if (vd->func_pool_idx >= 0) {
|
|
dbuf_putc(bc, OP_fclosure);
|
|
dbuf_put_u32(bc, vd->func_pool_idx);
|
|
dbuf_putc(bc, OP_put_arg);
|
|
dbuf_put_u16(bc, i);
|
|
}
|
|
}
|
|
for(i = 0; i < s->var_count; i++) {
|
|
JSVarDef *vd = &s->vars[i];
|
|
if (vd->scope_level == 0 && vd->func_pool_idx >= 0) {
|
|
dbuf_putc(bc, OP_fclosure);
|
|
dbuf_put_u32(bc, vd->func_pool_idx);
|
|
dbuf_putc(bc, OP_put_loc);
|
|
dbuf_put_u16(bc, i);
|
|
}
|
|
}
|
|
|
|
/* the module global variables must be initialized before
|
|
evaluating the module so that the exported functions are
|
|
visible if there are cyclic module references */
|
|
if (s->module) {
|
|
label_next = new_label_fd(s, -1);
|
|
|
|
/* if 'this' is true, initialize the global variables and return */
|
|
dbuf_putc(bc, OP_push_this);
|
|
dbuf_putc(bc, OP_if_false);
|
|
dbuf_put_u32(bc, label_next);
|
|
update_label(s, label_next, 1);
|
|
s->jump_size++;
|
|
}
|
|
|
|
/* add the global variables (only happens if s->is_global_var is
|
|
true) */
|
|
for(i = 0; i < s->global_var_count; i++) {
|
|
JSGlobalVar *hf = &s->global_vars[i];
|
|
int has_closure = 0;
|
|
BOOL force_init = hf->force_init;
|
|
/* we are in an eval, so the closure contains all the
|
|
enclosing variables */
|
|
/* If the outer function has a variable environment,
|
|
create a property for the variable there */
|
|
for(idx = 0; idx < s->closure_var_count; idx++) {
|
|
JSClosureVar *cv = &s->closure_var[idx];
|
|
if (cv->var_name == hf->var_name) {
|
|
has_closure = 2;
|
|
force_init = FALSE;
|
|
break;
|
|
}
|
|
if (cv->var_name == JS_ATOM__var_ ||
|
|
cv->var_name == JS_ATOM__arg_var_) {
|
|
dbuf_putc(bc, OP_get_var_ref);
|
|
dbuf_put_u16(bc, idx);
|
|
has_closure = 1;
|
|
force_init = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
if (!has_closure) {
|
|
int flags;
|
|
|
|
flags = 0;
|
|
if (s->eval_type != JS_EVAL_TYPE_GLOBAL)
|
|
flags |= JS_PROP_CONFIGURABLE;
|
|
if (hf->cpool_idx >= 0 && !hf->is_lexical) {
|
|
/* global function definitions need a specific handling */
|
|
dbuf_putc(bc, OP_fclosure);
|
|
dbuf_put_u32(bc, hf->cpool_idx);
|
|
|
|
dbuf_putc(bc, OP_define_func);
|
|
dbuf_put_u32(bc, JS_DupAtom(ctx, hf->var_name));
|
|
dbuf_putc(bc, flags);
|
|
|
|
goto done_global_var;
|
|
} else {
|
|
if (hf->is_lexical) {
|
|
flags |= DEFINE_GLOBAL_LEX_VAR;
|
|
if (!hf->is_const)
|
|
flags |= JS_PROP_WRITABLE;
|
|
}
|
|
dbuf_putc(bc, OP_define_var);
|
|
dbuf_put_u32(bc, JS_DupAtom(ctx, hf->var_name));
|
|
dbuf_putc(bc, flags);
|
|
}
|
|
}
|
|
if (hf->cpool_idx >= 0 || force_init) {
|
|
if (hf->cpool_idx >= 0) {
|
|
dbuf_putc(bc, OP_fclosure);
|
|
dbuf_put_u32(bc, hf->cpool_idx);
|
|
if (hf->var_name == JS_ATOM__default_) {
|
|
/* set default export function name */
|
|
dbuf_putc(bc, OP_set_name);
|
|
dbuf_put_u32(bc, JS_DupAtom(ctx, JS_ATOM_default));
|
|
}
|
|
} else {
|
|
dbuf_putc(bc, OP_undefined);
|
|
}
|
|
if (has_closure == 2) {
|
|
dbuf_putc(bc, OP_put_var_ref);
|
|
dbuf_put_u16(bc, idx);
|
|
} else if (has_closure == 1) {
|
|
dbuf_putc(bc, OP_define_field);
|
|
dbuf_put_u32(bc, JS_DupAtom(ctx, hf->var_name));
|
|
dbuf_putc(bc, OP_drop);
|
|
} else {
|
|
/* XXX: Check if variable is writable and enumerable */
|
|
dbuf_putc(bc, OP_put_var);
|
|
dbuf_put_u32(bc, JS_DupAtom(ctx, hf->var_name));
|
|
}
|
|
}
|
|
done_global_var:
|
|
JS_FreeAtom(ctx, hf->var_name);
|
|
}
|
|
|
|
if (s->module) {
|
|
dbuf_putc(bc, OP_return_undef);
|
|
|
|
dbuf_putc(bc, OP_label);
|
|
dbuf_put_u32(bc, label_next);
|
|
s->label_slots[label_next].pos2 = bc->size;
|
|
}
|
|
|
|
js_free(ctx, s->global_vars);
|
|
s->global_vars = NULL;
|
|
s->global_var_count = 0;
|
|
s->global_var_size = 0;
|
|
}
|
|
|
|
static int skip_dead_code(JSFunctionDef *s, const uint8_t *bc_buf, int bc_len,
|
|
int pos, int *linep, int *colp)
|
|
{
|
|
int op, len, label;
|
|
|
|
for (; pos < bc_len; pos += len) {
|
|
op = bc_buf[pos];
|
|
len = opcode_info[op].size;
|
|
if (op == OP_source_loc) {
|
|
*linep = get_u32(bc_buf + pos + 1);
|
|
*colp = get_u32(bc_buf + pos + 5);
|
|
} else if (op == OP_label) {
|
|
label = get_u32(bc_buf + pos + 1);
|
|
if (update_label(s, label, 0) > 0)
|
|
break;
|
|
assert(s->label_slots[label].first_reloc == NULL);
|
|
} else {
|
|
/* XXX: output a warning for unreachable code? */
|
|
JSAtom atom;
|
|
switch(opcode_info[op].fmt) {
|
|
case OP_FMT_label:
|
|
case OP_FMT_label_u16:
|
|
label = get_u32(bc_buf + pos + 1);
|
|
update_label(s, label, -1);
|
|
break;
|
|
case OP_FMT_atom_label_u8:
|
|
case OP_FMT_atom_label_u16:
|
|
label = get_u32(bc_buf + pos + 5);
|
|
update_label(s, label, -1);
|
|
/* fall thru */
|
|
case OP_FMT_atom:
|
|
case OP_FMT_atom_u8:
|
|
case OP_FMT_atom_u16:
|
|
atom = get_u32(bc_buf + pos + 1);
|
|
JS_FreeAtom(s->ctx, atom);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return pos;
|
|
}
|
|
|
|
static int get_label_pos(JSFunctionDef *s, int label)
|
|
{
|
|
int i, pos;
|
|
for (i = 0; i < 20; i++) {
|
|
pos = s->label_slots[label].pos;
|
|
for (;;) {
|
|
switch (s->byte_code.buf[pos]) {
|
|
case OP_source_loc:
|
|
pos += 9;
|
|
continue;
|
|
case OP_label:
|
|
pos += 5;
|
|
continue;
|
|
case OP_goto:
|
|
label = get_u32(s->byte_code.buf + pos + 1);
|
|
break;
|
|
default:
|
|
return pos;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return pos;
|
|
}
|
|
|
|
/* convert global variable accesses to local variables or closure
|
|
variables when necessary */
|
|
static __exception int resolve_variables(JSContext *ctx, JSFunctionDef *s)
|
|
{
|
|
int pos, pos_next, bc_len, op, len, i, idx, line_num, col_num;
|
|
uint8_t *bc_buf;
|
|
JSAtom var_name;
|
|
DynBuf bc_out;
|
|
CodeContext cc;
|
|
int scope;
|
|
|
|
cc.bc_buf = bc_buf = s->byte_code.buf;
|
|
cc.bc_len = bc_len = s->byte_code.size;
|
|
js_dbuf_init(ctx, &bc_out);
|
|
|
|
/* first pass for runtime checks (must be done before the
|
|
variables are created) */
|
|
for(i = 0; i < s->global_var_count; i++) {
|
|
JSGlobalVar *hf = &s->global_vars[i];
|
|
int flags;
|
|
|
|
/* check if global variable (XXX: simplify) */
|
|
for(idx = 0; idx < s->closure_var_count; idx++) {
|
|
JSClosureVar *cv = &s->closure_var[idx];
|
|
if (cv->var_name == hf->var_name) {
|
|
if (s->eval_type == JS_EVAL_TYPE_DIRECT &&
|
|
cv->is_lexical) {
|
|
/* Check if a lexical variable is
|
|
redefined as 'var'. XXX: Could abort
|
|
compilation here, but for consistency
|
|
with the other checks, we delay the
|
|
error generation. */
|
|
dbuf_putc(&bc_out, OP_throw_error);
|
|
dbuf_put_u32(&bc_out, JS_DupAtom(ctx, hf->var_name));
|
|
dbuf_putc(&bc_out, JS_THROW_VAR_REDECL);
|
|
}
|
|
goto next;
|
|
}
|
|
if (cv->var_name == JS_ATOM__var_ ||
|
|
cv->var_name == JS_ATOM__arg_var_)
|
|
goto next;
|
|
}
|
|
|
|
dbuf_putc(&bc_out, OP_check_define_var);
|
|
dbuf_put_u32(&bc_out, JS_DupAtom(ctx, hf->var_name));
|
|
flags = 0;
|
|
if (hf->is_lexical)
|
|
flags |= DEFINE_GLOBAL_LEX_VAR;
|
|
if (hf->cpool_idx >= 0)
|
|
flags |= DEFINE_GLOBAL_FUNC_VAR;
|
|
dbuf_putc(&bc_out, flags);
|
|
next: ;
|
|
}
|
|
|
|
line_num = 0; /* avoid warning */
|
|
col_num = 0;
|
|
for (pos = 0; pos < bc_len; pos = pos_next) {
|
|
op = bc_buf[pos];
|
|
len = opcode_info[op].size;
|
|
pos_next = pos + len;
|
|
switch(op) {
|
|
case OP_source_loc:
|
|
line_num = get_u32(bc_buf + pos + 1);
|
|
col_num = get_u32(bc_buf + pos + 5);
|
|
s->source_loc_size++;
|
|
goto no_change;
|
|
|
|
case OP_eval: /* convert scope index to adjusted variable index */
|
|
{
|
|
int call_argc = get_u16(bc_buf + pos + 1);
|
|
scope = get_u16(bc_buf + pos + 1 + 2);
|
|
mark_eval_captured_variables(ctx, s, scope);
|
|
dbuf_putc(&bc_out, op);
|
|
dbuf_put_u16(&bc_out, call_argc);
|
|
dbuf_put_u16(&bc_out, s->scopes[scope].first + 1);
|
|
}
|
|
break;
|
|
case OP_apply_eval: /* convert scope index to adjusted variable index */
|
|
scope = get_u16(bc_buf + pos + 1);
|
|
mark_eval_captured_variables(ctx, s, scope);
|
|
dbuf_putc(&bc_out, op);
|
|
dbuf_put_u16(&bc_out, s->scopes[scope].first + 1);
|
|
break;
|
|
case OP_scope_get_var_undef:
|
|
case OP_scope_get_var:
|
|
case OP_scope_put_var:
|
|
case OP_scope_delete_var:
|
|
case OP_scope_get_ref:
|
|
case OP_scope_put_var_init:
|
|
var_name = get_u32(bc_buf + pos + 1);
|
|
scope = get_u16(bc_buf + pos + 5);
|
|
pos_next = resolve_scope_var(ctx, s, var_name, scope, op, &bc_out,
|
|
NULL, NULL, pos_next);
|
|
JS_FreeAtom(ctx, var_name);
|
|
break;
|
|
case OP_scope_make_ref:
|
|
{
|
|
int label;
|
|
LabelSlot *ls;
|
|
var_name = get_u32(bc_buf + pos + 1);
|
|
label = get_u32(bc_buf + pos + 5);
|
|
scope = get_u16(bc_buf + pos + 9);
|
|
ls = &s->label_slots[label];
|
|
ls->ref_count--; /* always remove label reference */
|
|
pos_next = resolve_scope_var(ctx, s, var_name, scope, op, &bc_out,
|
|
bc_buf, ls, pos_next);
|
|
JS_FreeAtom(ctx, var_name);
|
|
}
|
|
break;
|
|
case OP_scope_get_private_field:
|
|
case OP_scope_get_private_field2:
|
|
case OP_scope_put_private_field:
|
|
{
|
|
int ret;
|
|
var_name = get_u32(bc_buf + pos + 1);
|
|
scope = get_u16(bc_buf + pos + 5);
|
|
ret = resolve_scope_private_field(ctx, s, var_name, scope, op, &bc_out);
|
|
if (ret < 0)
|
|
goto fail;
|
|
JS_FreeAtom(ctx, var_name);
|
|
}
|
|
break;
|
|
case OP_gosub:
|
|
s->jump_size++;
|
|
{
|
|
/* remove calls to empty finalizers */
|
|
int label;
|
|
LabelSlot *ls;
|
|
|
|
label = get_u32(bc_buf + pos + 1);
|
|
assert(label >= 0 && label < s->label_count);
|
|
ls = &s->label_slots[label];
|
|
if (code_match(&cc, ls->pos, OP_ret, -1)) {
|
|
ls->ref_count--;
|
|
break;
|
|
}
|
|
}
|
|
goto no_change;
|
|
case OP_insert3:
|
|
/* Transformation: insert3 put_array_el|put_ref_value drop -> put_array_el|put_ref_value */
|
|
if (code_match(&cc, pos_next, M2(OP_put_array_el, OP_put_ref_value), OP_drop, -1)) {
|
|
dbuf_putc(&bc_out, cc.op);
|
|
pos_next = cc.pos;
|
|
if (cc.line_num == -1)
|
|
break;
|
|
if (cc.line_num != line_num || cc.col_num != col_num) {
|
|
line_num = cc.line_num;
|
|
col_num = cc.col_num;
|
|
s->source_loc_size++;
|
|
dbuf_putc(&bc_out, OP_source_loc);
|
|
dbuf_put_u32(&bc_out, line_num);
|
|
dbuf_put_u32(&bc_out, col_num);
|
|
}
|
|
break;
|
|
}
|
|
goto no_change;
|
|
|
|
case OP_goto:
|
|
s->jump_size++;
|
|
/* fall thru */
|
|
case OP_tail_call:
|
|
case OP_tail_call_method:
|
|
case OP_return:
|
|
case OP_return_undef:
|
|
case OP_throw:
|
|
case OP_throw_error:
|
|
case OP_ret:
|
|
{
|
|
/* remove dead code */
|
|
int line = -1;
|
|
int col = -1;
|
|
dbuf_put(&bc_out, bc_buf + pos, len);
|
|
pos = skip_dead_code(s, bc_buf, bc_len, pos + len,
|
|
&line, &col);
|
|
pos_next = pos;
|
|
if (line < 0 || pos >= bc_len)
|
|
break;
|
|
if (line_num != line || col_num != col) {
|
|
line_num = line;
|
|
col_num = col;
|
|
s->source_loc_size++;
|
|
dbuf_putc(&bc_out, OP_source_loc);
|
|
dbuf_put_u32(&bc_out, line_num);
|
|
dbuf_put_u32(&bc_out, col_num);
|
|
}
|
|
break;
|
|
}
|
|
goto no_change;
|
|
|
|
case OP_label:
|
|
{
|
|
int label;
|
|
LabelSlot *ls;
|
|
|
|
label = get_u32(bc_buf + pos + 1);
|
|
assert(label >= 0 && label < s->label_count);
|
|
ls = &s->label_slots[label];
|
|
ls->pos2 = bc_out.size + opcode_info[op].size;
|
|
}
|
|
goto no_change;
|
|
|
|
case OP_enter_scope:
|
|
{
|
|
int scope_idx, scope = get_u16(bc_buf + pos + 1);
|
|
|
|
if (scope == s->body_scope) {
|
|
instantiate_hoisted_definitions(ctx, s, &bc_out);
|
|
}
|
|
|
|
for(scope_idx = s->scopes[scope].first; scope_idx >= 0;) {
|
|
JSVarDef *vd = &s->vars[scope_idx];
|
|
if (vd->scope_level == scope) {
|
|
if (scope_idx != s->arguments_arg_idx) {
|
|
if (vd->var_kind == JS_VAR_FUNCTION_DECL ||
|
|
vd->var_kind == JS_VAR_NEW_FUNCTION_DECL) {
|
|
/* Initialize lexical variable upon entering scope */
|
|
dbuf_putc(&bc_out, OP_fclosure);
|
|
dbuf_put_u32(&bc_out, vd->func_pool_idx);
|
|
dbuf_putc(&bc_out, OP_put_loc);
|
|
dbuf_put_u16(&bc_out, scope_idx);
|
|
} else {
|
|
/* XXX: should check if variable can be used
|
|
before initialization */
|
|
dbuf_putc(&bc_out, OP_set_loc_uninitialized);
|
|
dbuf_put_u16(&bc_out, scope_idx);
|
|
}
|
|
}
|
|
scope_idx = vd->scope_next;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case OP_leave_scope:
|
|
{
|
|
int scope_idx, scope = get_u16(bc_buf + pos + 1);
|
|
|
|
for(scope_idx = s->scopes[scope].first; scope_idx >= 0;) {
|
|
JSVarDef *vd = &s->vars[scope_idx];
|
|
if (vd->scope_level == scope) {
|
|
if (vd->is_captured) {
|
|
dbuf_putc(&bc_out, OP_close_loc);
|
|
dbuf_put_u16(&bc_out, scope_idx);
|
|
}
|
|
scope_idx = vd->scope_next;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case OP_set_name:
|
|
{
|
|
/* remove dummy set_name opcodes */
|
|
JSAtom name = get_u32(bc_buf + pos + 1);
|
|
if (name == JS_ATOM_NULL)
|
|
break;
|
|
}
|
|
goto no_change;
|
|
|
|
case OP_if_false:
|
|
case OP_if_true:
|
|
case OP_catch:
|
|
s->jump_size++;
|
|
goto no_change;
|
|
|
|
case OP_dup:
|
|
/* Transformation: dup if_false(l1) drop, l1: if_false(l2) -> if_false(l2) */
|
|
/* Transformation: dup if_true(l1) drop, l1: if_true(l2) -> if_true(l2) */
|
|
if (code_match(&cc, pos_next, M2(OP_if_false, OP_if_true), OP_drop, -1)) {
|
|
int lab0, lab1, op1, pos1, line1, col1, pos2;
|
|
lab0 = lab1 = cc.label;
|
|
assert(lab1 >= 0 && lab1 < s->label_count);
|
|
op1 = cc.op;
|
|
pos1 = cc.pos;
|
|
line1 = cc.line_num;
|
|
col1 = cc.col_num;
|
|
while (code_match(&cc, (pos2 = get_label_pos(s, lab1)), OP_dup, op1, OP_drop, -1)) {
|
|
lab1 = cc.label;
|
|
}
|
|
if (code_match(&cc, pos2, op1, -1)) {
|
|
s->jump_size++;
|
|
update_label(s, lab0, -1);
|
|
update_label(s, cc.label, +1);
|
|
dbuf_putc(&bc_out, op1);
|
|
dbuf_put_u32(&bc_out, cc.label);
|
|
pos_next = pos1;
|
|
if (line1 == -1)
|
|
break;
|
|
if (line1 != line_num || col1 != col_num) {
|
|
line_num = line1;
|
|
col_num = col1;
|
|
s->source_loc_size++;
|
|
dbuf_putc(&bc_out, OP_source_loc);
|
|
dbuf_put_u32(&bc_out, line_num);
|
|
dbuf_put_u32(&bc_out, col_num);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
goto no_change;
|
|
|
|
case OP_nop:
|
|
/* remove erased code */
|
|
break;
|
|
case OP_set_class_name:
|
|
/* only used during parsing */
|
|
break;
|
|
|
|
default:
|
|
no_change:
|
|
dbuf_put(&bc_out, bc_buf + pos, len);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* set the new byte code */
|
|
dbuf_free(&s->byte_code);
|
|
s->byte_code = bc_out;
|
|
if (dbuf_error(&s->byte_code)) {
|
|
JS_ThrowOutOfMemory(ctx);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
fail:
|
|
/* continue the copy to keep the atom refcounts consistent */
|
|
/* XXX: find a better solution ? */
|
|
for (; pos < bc_len; pos = pos_next) {
|
|
op = bc_buf[pos];
|
|
len = opcode_info[op].size;
|
|
pos_next = pos + len;
|
|
dbuf_put(&bc_out, bc_buf + pos, len);
|
|
}
|
|
dbuf_free(&s->byte_code);
|
|
s->byte_code = bc_out;
|
|
return -1;
|
|
}
|
|
|
|
/* the pc2line table gives a line number for each PC value */
|
|
static void add_pc2line_info(JSFunctionDef *s, uint32_t pc,
|
|
int line_num, int col_num)
|
|
{
|
|
if (s->source_loc_slots == NULL)
|
|
return;
|
|
if (s->source_loc_count >= s->source_loc_size)
|
|
return;
|
|
if (pc < s->line_number_last_pc)
|
|
return;
|
|
if (line_num == s->line_number_last)
|
|
if (col_num == s->col_number_last)
|
|
return;
|
|
s->source_loc_slots[s->source_loc_count].pc = pc;
|
|
s->source_loc_slots[s->source_loc_count].line_num = line_num;
|
|
s->source_loc_slots[s->source_loc_count].col_num = col_num;
|
|
s->source_loc_count++;
|
|
s->line_number_last_pc = pc;
|
|
s->line_number_last = line_num;
|
|
s->col_number_last = col_num;
|
|
}
|
|
|
|
static void compute_pc2line_info(JSFunctionDef *s)
|
|
{
|
|
if (s->source_loc_slots) {
|
|
int last_line_num = s->line_num;
|
|
int last_col_num = s->col_num;
|
|
uint32_t last_pc = 0;
|
|
int i;
|
|
|
|
js_dbuf_init(s->ctx, &s->pc2line);
|
|
for (i = 0; i < s->source_loc_count; i++) {
|
|
uint32_t pc = s->source_loc_slots[i].pc;
|
|
int line_num = s->source_loc_slots[i].line_num;
|
|
int col_num = s->source_loc_slots[i].col_num;
|
|
int diff_pc, diff_line, diff_col;
|
|
|
|
if (line_num < 0)
|
|
continue;
|
|
|
|
diff_pc = pc - last_pc;
|
|
if (diff_pc < 0)
|
|
continue;
|
|
|
|
diff_line = line_num - last_line_num;
|
|
diff_col = col_num - last_col_num;
|
|
if (diff_line == 0 && diff_col == 0)
|
|
continue;
|
|
|
|
if (diff_line >= PC2LINE_BASE &&
|
|
diff_line < PC2LINE_BASE + PC2LINE_RANGE &&
|
|
diff_pc <= PC2LINE_DIFF_PC_MAX) {
|
|
dbuf_putc(&s->pc2line, (diff_line - PC2LINE_BASE) +
|
|
diff_pc * PC2LINE_RANGE + PC2LINE_OP_FIRST);
|
|
} else {
|
|
/* longer encoding */
|
|
dbuf_putc(&s->pc2line, 0);
|
|
dbuf_put_leb128(&s->pc2line, diff_pc);
|
|
dbuf_put_sleb128(&s->pc2line, diff_line);
|
|
}
|
|
dbuf_put_sleb128(&s->pc2line, diff_col);
|
|
|
|
last_pc = pc;
|
|
last_line_num = line_num;
|
|
last_col_num = col_num;
|
|
}
|
|
}
|
|
}
|
|
|
|
static RelocEntry *add_reloc(JSContext *ctx, LabelSlot *ls, uint32_t addr, int size)
|
|
{
|
|
RelocEntry *re;
|
|
re = js_malloc(ctx, sizeof(*re));
|
|
if (!re)
|
|
return NULL;
|
|
re->addr = addr;
|
|
re->size = size;
|
|
re->next = ls->first_reloc;
|
|
ls->first_reloc = re;
|
|
return re;
|
|
}
|
|
|
|
static BOOL code_has_label(CodeContext *s, int pos, int label)
|
|
{
|
|
while (pos < s->bc_len) {
|
|
int op = s->bc_buf[pos];
|
|
if (op == OP_source_loc) {
|
|
pos += 9;
|
|
continue;
|
|
}
|
|
if (op == OP_label) {
|
|
int lab = get_u32(s->bc_buf + pos + 1);
|
|
if (lab == label)
|
|
return TRUE;
|
|
pos += 5;
|
|
continue;
|
|
}
|
|
if (op == OP_goto) {
|
|
int lab = get_u32(s->bc_buf + pos + 1);
|
|
if (lab == label)
|
|
return TRUE;
|
|
}
|
|
break;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/* return the target label, following the OP_goto jumps
|
|
the first opcode at destination is stored in *pop
|
|
*/
|
|
static int find_jump_target(JSFunctionDef *s, int label, int *pop)
|
|
{
|
|
int i, pos, op;
|
|
|
|
update_label(s, label, -1);
|
|
for (i = 0; i < 10; i++) {
|
|
assert(label >= 0 && label < s->label_count);
|
|
pos = s->label_slots[label].pos2;
|
|
for (;;) {
|
|
switch(op = s->byte_code.buf[pos]) {
|
|
case OP_source_loc:
|
|
case OP_label:
|
|
pos += opcode_info[op].size;
|
|
continue;
|
|
case OP_goto:
|
|
label = get_u32(s->byte_code.buf + pos + 1);
|
|
break;
|
|
case OP_drop:
|
|
/* ignore drop opcodes if followed by OP_return_undef */
|
|
while (s->byte_code.buf[++pos] == OP_drop)
|
|
continue;
|
|
if (s->byte_code.buf[pos] == OP_return_undef)
|
|
op = OP_return_undef;
|
|
/* fall thru */
|
|
default:
|
|
goto done;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
/* cycle detected, could issue a warning */
|
|
done:
|
|
*pop = op;
|
|
update_label(s, label, +1);
|
|
return label;
|
|
}
|
|
|
|
static void push_short_int(DynBuf *bc_out, int val)
|
|
{
|
|
if (val >= -1 && val <= 7) {
|
|
dbuf_putc(bc_out, OP_push_0 + val);
|
|
return;
|
|
}
|
|
if (val == (int8_t)val) {
|
|
dbuf_putc(bc_out, OP_push_i8);
|
|
dbuf_putc(bc_out, val);
|
|
return;
|
|
}
|
|
if (val == (int16_t)val) {
|
|
dbuf_putc(bc_out, OP_push_i16);
|
|
dbuf_put_u16(bc_out, val);
|
|
return;
|
|
}
|
|
dbuf_putc(bc_out, OP_push_i32);
|
|
dbuf_put_u32(bc_out, val);
|
|
}
|
|
|
|
static void put_short_code(DynBuf *bc_out, int op, int idx)
|
|
{
|
|
if (idx < 4) {
|
|
switch (op) {
|
|
case OP_get_loc:
|
|
dbuf_putc(bc_out, OP_get_loc0 + idx);
|
|
return;
|
|
case OP_put_loc:
|
|
dbuf_putc(bc_out, OP_put_loc0 + idx);
|
|
return;
|
|
case OP_set_loc:
|
|
dbuf_putc(bc_out, OP_set_loc0 + idx);
|
|
return;
|
|
case OP_get_arg:
|
|
dbuf_putc(bc_out, OP_get_arg0 + idx);
|
|
return;
|
|
case OP_put_arg:
|
|
dbuf_putc(bc_out, OP_put_arg0 + idx);
|
|
return;
|
|
case OP_set_arg:
|
|
dbuf_putc(bc_out, OP_set_arg0 + idx);
|
|
return;
|
|
case OP_get_var_ref:
|
|
dbuf_putc(bc_out, OP_get_var_ref0 + idx);
|
|
return;
|
|
case OP_put_var_ref:
|
|
dbuf_putc(bc_out, OP_put_var_ref0 + idx);
|
|
return;
|
|
case OP_set_var_ref:
|
|
dbuf_putc(bc_out, OP_set_var_ref0 + idx);
|
|
return;
|
|
case OP_call:
|
|
dbuf_putc(bc_out, OP_call0 + idx);
|
|
return;
|
|
}
|
|
}
|
|
if (idx < 256) {
|
|
switch (op) {
|
|
case OP_get_loc:
|
|
dbuf_putc(bc_out, OP_get_loc8);
|
|
dbuf_putc(bc_out, idx);
|
|
return;
|
|
case OP_put_loc:
|
|
dbuf_putc(bc_out, OP_put_loc8);
|
|
dbuf_putc(bc_out, idx);
|
|
return;
|
|
case OP_set_loc:
|
|
dbuf_putc(bc_out, OP_set_loc8);
|
|
dbuf_putc(bc_out, idx);
|
|
return;
|
|
}
|
|
}
|
|
dbuf_putc(bc_out, op);
|
|
dbuf_put_u16(bc_out, idx);
|
|
}
|
|
|
|
/* peephole optimizations and resolve goto/labels */
|
|
static __exception int resolve_labels(JSContext *ctx, JSFunctionDef *s)
|
|
{
|
|
int pos, pos_next, bc_len, op, op1, len, i, line_num, col_num, patch_offsets;
|
|
const uint8_t *bc_buf;
|
|
DynBuf bc_out;
|
|
LabelSlot *label_slots, *ls;
|
|
RelocEntry *re, *re_next;
|
|
CodeContext cc;
|
|
int label;
|
|
JumpSlot *jp;
|
|
|
|
label_slots = s->label_slots;
|
|
|
|
line_num = s->line_num;
|
|
col_num = s->col_num;
|
|
|
|
cc.bc_buf = bc_buf = s->byte_code.buf;
|
|
cc.bc_len = bc_len = s->byte_code.size;
|
|
js_dbuf_init(ctx, &bc_out);
|
|
|
|
if (s->jump_size) {
|
|
s->jump_slots = js_mallocz(s->ctx, sizeof(*s->jump_slots) * s->jump_size);
|
|
if (s->jump_slots == NULL)
|
|
return -1;
|
|
}
|
|
|
|
if (s->source_loc_size) {
|
|
s->source_loc_slots = js_mallocz(s->ctx, sizeof(*s->source_loc_slots) * s->source_loc_size);
|
|
if (s->source_loc_slots == NULL)
|
|
return -1;
|
|
s->line_number_last = s->line_num;
|
|
s->col_number_last = s->col_num;
|
|
s->line_number_last_pc = 0;
|
|
}
|
|
|
|
/* initialize the 'home_object' variable if needed */
|
|
if (s->home_object_var_idx >= 0) {
|
|
dbuf_putc(&bc_out, OP_special_object);
|
|
dbuf_putc(&bc_out, OP_SPECIAL_OBJECT_HOME_OBJECT);
|
|
put_short_code(&bc_out, OP_put_loc, s->home_object_var_idx);
|
|
}
|
|
/* initialize the 'this.active_func' variable if needed */
|
|
if (s->this_active_func_var_idx >= 0) {
|
|
dbuf_putc(&bc_out, OP_special_object);
|
|
dbuf_putc(&bc_out, OP_SPECIAL_OBJECT_THIS_FUNC);
|
|
put_short_code(&bc_out, OP_put_loc, s->this_active_func_var_idx);
|
|
}
|
|
/* initialize the 'new.target' variable if needed */
|
|
if (s->new_target_var_idx >= 0) {
|
|
dbuf_putc(&bc_out, OP_special_object);
|
|
dbuf_putc(&bc_out, OP_SPECIAL_OBJECT_NEW_TARGET);
|
|
put_short_code(&bc_out, OP_put_loc, s->new_target_var_idx);
|
|
}
|
|
/* initialize the 'this' variable if needed. In a derived class
|
|
constructor, this is initially uninitialized. */
|
|
if (s->this_var_idx >= 0) {
|
|
if (s->is_derived_class_constructor) {
|
|
dbuf_putc(&bc_out, OP_set_loc_uninitialized);
|
|
dbuf_put_u16(&bc_out, s->this_var_idx);
|
|
} else {
|
|
dbuf_putc(&bc_out, OP_push_this);
|
|
put_short_code(&bc_out, OP_put_loc, s->this_var_idx);
|
|
}
|
|
}
|
|
/* initialize the 'arguments' variable if needed */
|
|
if (s->arguments_var_idx >= 0) {
|
|
if ((s->js_mode & JS_MODE_STRICT) || !s->has_simple_parameter_list) {
|
|
dbuf_putc(&bc_out, OP_special_object);
|
|
dbuf_putc(&bc_out, OP_SPECIAL_OBJECT_ARGUMENTS);
|
|
} else {
|
|
dbuf_putc(&bc_out, OP_special_object);
|
|
dbuf_putc(&bc_out, OP_SPECIAL_OBJECT_MAPPED_ARGUMENTS);
|
|
}
|
|
if (s->arguments_arg_idx >= 0)
|
|
put_short_code(&bc_out, OP_set_loc, s->arguments_arg_idx);
|
|
put_short_code(&bc_out, OP_put_loc, s->arguments_var_idx);
|
|
}
|
|
/* initialize a reference to the current function if needed */
|
|
if (s->func_var_idx >= 0) {
|
|
dbuf_putc(&bc_out, OP_special_object);
|
|
dbuf_putc(&bc_out, OP_SPECIAL_OBJECT_THIS_FUNC);
|
|
put_short_code(&bc_out, OP_put_loc, s->func_var_idx);
|
|
}
|
|
/* initialize the variable environment object if needed */
|
|
if (s->var_object_idx >= 0) {
|
|
dbuf_putc(&bc_out, OP_special_object);
|
|
dbuf_putc(&bc_out, OP_SPECIAL_OBJECT_VAR_OBJECT);
|
|
put_short_code(&bc_out, OP_put_loc, s->var_object_idx);
|
|
}
|
|
if (s->arg_var_object_idx >= 0) {
|
|
dbuf_putc(&bc_out, OP_special_object);
|
|
dbuf_putc(&bc_out, OP_SPECIAL_OBJECT_VAR_OBJECT);
|
|
put_short_code(&bc_out, OP_put_loc, s->arg_var_object_idx);
|
|
}
|
|
|
|
for (pos = 0; pos < bc_len; pos = pos_next) {
|
|
int val;
|
|
op = bc_buf[pos];
|
|
len = opcode_info[op].size;
|
|
pos_next = pos + len;
|
|
switch(op) {
|
|
case OP_source_loc:
|
|
/* line number info (for debug). We put it in a separate
|
|
compressed table to reduce memory usage and get better
|
|
performance */
|
|
line_num = get_u32(bc_buf + pos + 1);
|
|
col_num = get_u32(bc_buf + pos + 5);
|
|
break;
|
|
|
|
case OP_label:
|
|
{
|
|
label = get_u32(bc_buf + pos + 1);
|
|
assert(label >= 0 && label < s->label_count);
|
|
ls = &label_slots[label];
|
|
assert(ls->addr == -1);
|
|
ls->addr = bc_out.size;
|
|
/* resolve the relocation entries */
|
|
for(re = ls->first_reloc; re != NULL; re = re_next) {
|
|
int diff = ls->addr - re->addr;
|
|
re_next = re->next;
|
|
switch (re->size) {
|
|
case 4:
|
|
put_u32(bc_out.buf + re->addr, diff);
|
|
break;
|
|
case 2:
|
|
assert(diff == (int16_t)diff);
|
|
put_u16(bc_out.buf + re->addr, diff);
|
|
break;
|
|
case 1:
|
|
assert(diff == (int8_t)diff);
|
|
put_u8(bc_out.buf + re->addr, diff);
|
|
break;
|
|
}
|
|
js_free(ctx, re);
|
|
}
|
|
ls->first_reloc = NULL;
|
|
}
|
|
break;
|
|
|
|
case OP_call:
|
|
case OP_call_method:
|
|
{
|
|
/* detect and transform tail calls */
|
|
int argc;
|
|
argc = get_u16(bc_buf + pos + 1);
|
|
if (code_match(&cc, pos_next, OP_return, -1)) {
|
|
if (cc.line_num >= 0) line_num = cc.line_num;
|
|
if (cc.col_num >= 0) col_num = cc.col_num;
|
|
add_pc2line_info(s, bc_out.size, line_num, col_num);
|
|
put_short_code(&bc_out, op + 1, argc);
|
|
pos_next = skip_dead_code(s, bc_buf, bc_len, cc.pos,
|
|
&line_num, &col_num);
|
|
break;
|
|
}
|
|
add_pc2line_info(s, bc_out.size, line_num, col_num);
|
|
put_short_code(&bc_out, op, argc);
|
|
break;
|
|
}
|
|
goto no_change;
|
|
|
|
case OP_return:
|
|
case OP_return_undef:
|
|
case OP_return_async:
|
|
case OP_throw:
|
|
case OP_throw_error:
|
|
pos_next = skip_dead_code(s, bc_buf, bc_len, pos_next,
|
|
&line_num, &col_num);
|
|
goto no_change;
|
|
|
|
case OP_goto:
|
|
label = get_u32(bc_buf + pos + 1);
|
|
has_goto:
|
|
{
|
|
/* Use custom matcher because multiple labels can follow */
|
|
label = find_jump_target(s, label, &op1);
|
|
if (code_has_label(&cc, pos_next, label)) {
|
|
/* jump to next instruction: remove jump */
|
|
update_label(s, label, -1);
|
|
break;
|
|
}
|
|
if (op1 == OP_return || op1 == OP_return_undef || op1 == OP_throw) {
|
|
/* jump to return/throw: remove jump, append return/throw */
|
|
/* updating the line number obfuscates assembly listing */
|
|
update_label(s, label, -1);
|
|
add_pc2line_info(s, bc_out.size, line_num, col_num);
|
|
dbuf_putc(&bc_out, op1);
|
|
pos_next = skip_dead_code(s, bc_buf, bc_len, pos_next,
|
|
&line_num, &col_num);
|
|
break;
|
|
}
|
|
/* XXX: should duplicate single instructions followed by goto or return */
|
|
/* For example, can match one of these followed by return:
|
|
push_i32 / push_const / push_atom_value / get_var /
|
|
undefined / null / push_false / push_true / get_ref_value /
|
|
get_loc / get_arg / get_var_ref
|
|
*/
|
|
}
|
|
goto has_label;
|
|
|
|
case OP_gosub:
|
|
label = get_u32(bc_buf + pos + 1);
|
|
goto has_label;
|
|
|
|
case OP_catch:
|
|
label = get_u32(bc_buf + pos + 1);
|
|
goto has_label;
|
|
|
|
case OP_if_true:
|
|
case OP_if_false:
|
|
label = get_u32(bc_buf + pos + 1);
|
|
label = find_jump_target(s, label, &op1);
|
|
/* transform if_false/if_true(l1) label(l1) -> drop label(l1) */
|
|
if (code_has_label(&cc, pos_next, label)) {
|
|
update_label(s, label, -1);
|
|
dbuf_putc(&bc_out, OP_drop);
|
|
break;
|
|
}
|
|
/* transform if_false(l1) goto(l2) label(l1) -> if_false(l2) label(l1) */
|
|
if (code_match(&cc, pos_next, OP_goto, -1)) {
|
|
int pos1 = cc.pos;
|
|
int line1 = cc.line_num;
|
|
int col1 = cc.col_num;
|
|
if (code_has_label(&cc, pos1, label)) {
|
|
if (line1 >= 0) line_num = line1;
|
|
if (col1 >= 0) col_num = col1;
|
|
pos_next = pos1;
|
|
update_label(s, label, -1);
|
|
label = cc.label;
|
|
op ^= OP_if_true ^ OP_if_false;
|
|
}
|
|
}
|
|
has_label:
|
|
add_pc2line_info(s, bc_out.size, line_num, col_num);
|
|
if (op == OP_goto) {
|
|
pos_next = skip_dead_code(s, bc_buf, bc_len, pos_next,
|
|
&line_num, &col_num);
|
|
}
|
|
assert(label >= 0 && label < s->label_count);
|
|
ls = &label_slots[label];
|
|
jp = &s->jump_slots[s->jump_count++];
|
|
jp->op = op;
|
|
jp->size = 4;
|
|
jp->pos = bc_out.size + 1;
|
|
jp->label = label;
|
|
|
|
if (ls->addr == -1) {
|
|
int diff = ls->pos2 - pos - 1;
|
|
if (diff < 128 && (op == OP_if_false || op == OP_if_true || op == OP_goto)) {
|
|
jp->size = 1;
|
|
jp->op = OP_if_false8 + (op - OP_if_false);
|
|
dbuf_putc(&bc_out, OP_if_false8 + (op - OP_if_false));
|
|
dbuf_putc(&bc_out, 0);
|
|
if (!add_reloc(ctx, ls, bc_out.size - 1, 1))
|
|
goto fail;
|
|
break;
|
|
}
|
|
if (diff < 32768 && op == OP_goto) {
|
|
jp->size = 2;
|
|
jp->op = OP_goto16;
|
|
dbuf_putc(&bc_out, OP_goto16);
|
|
dbuf_put_u16(&bc_out, 0);
|
|
if (!add_reloc(ctx, ls, bc_out.size - 2, 2))
|
|
goto fail;
|
|
break;
|
|
}
|
|
} else {
|
|
int diff = ls->addr - bc_out.size - 1;
|
|
if (diff == (int8_t)diff && (op == OP_if_false || op == OP_if_true || op == OP_goto)) {
|
|
jp->size = 1;
|
|
jp->op = OP_if_false8 + (op - OP_if_false);
|
|
dbuf_putc(&bc_out, OP_if_false8 + (op - OP_if_false));
|
|
dbuf_putc(&bc_out, diff);
|
|
break;
|
|
}
|
|
if (diff == (int16_t)diff && op == OP_goto) {
|
|
jp->size = 2;
|
|
jp->op = OP_goto16;
|
|
dbuf_putc(&bc_out, OP_goto16);
|
|
dbuf_put_u16(&bc_out, diff);
|
|
break;
|
|
}
|
|
}
|
|
dbuf_putc(&bc_out, op);
|
|
dbuf_put_u32(&bc_out, ls->addr - bc_out.size);
|
|
if (ls->addr == -1) {
|
|
/* unresolved yet: create a new relocation entry */
|
|
if (!add_reloc(ctx, ls, bc_out.size - 4, 4))
|
|
goto fail;
|
|
}
|
|
break;
|
|
case OP_with_get_var:
|
|
case OP_with_put_var:
|
|
case OP_with_delete_var:
|
|
case OP_with_make_ref:
|
|
case OP_with_get_ref:
|
|
case OP_with_get_ref_undef:
|
|
{
|
|
JSAtom atom;
|
|
int is_with;
|
|
|
|
atom = get_u32(bc_buf + pos + 1);
|
|
label = get_u32(bc_buf + pos + 5);
|
|
is_with = bc_buf[pos + 9];
|
|
label = find_jump_target(s, label, &op1);
|
|
assert(label >= 0 && label < s->label_count);
|
|
ls = &label_slots[label];
|
|
add_pc2line_info(s, bc_out.size, line_num, col_num);
|
|
jp = &s->jump_slots[s->jump_count++];
|
|
jp->op = op;
|
|
jp->size = 4;
|
|
jp->pos = bc_out.size + 5;
|
|
jp->label = label;
|
|
dbuf_putc(&bc_out, op);
|
|
dbuf_put_u32(&bc_out, atom);
|
|
dbuf_put_u32(&bc_out, ls->addr - bc_out.size);
|
|
if (ls->addr == -1) {
|
|
/* unresolved yet: create a new relocation entry */
|
|
if (!add_reloc(ctx, ls, bc_out.size - 4, 4))
|
|
goto fail;
|
|
}
|
|
dbuf_putc(&bc_out, is_with);
|
|
}
|
|
break;
|
|
|
|
case OP_drop:
|
|
/* remove useless drops before return */
|
|
if (code_match(&cc, pos_next, OP_return_undef, -1)) {
|
|
if (cc.line_num >= 0) line_num = cc.line_num;
|
|
if (cc.col_num >= 0) col_num = cc.col_num;
|
|
break;
|
|
}
|
|
goto no_change;
|
|
|
|
case OP_null:
|
|
/* transform null strict_eq into is_null */
|
|
if (code_match(&cc, pos_next, OP_strict_eq, -1)) {
|
|
if (cc.line_num >= 0) line_num = cc.line_num;
|
|
if (cc.col_num >= 0) col_num = cc.col_num;
|
|
add_pc2line_info(s, bc_out.size, line_num, col_num);
|
|
dbuf_putc(&bc_out, OP_is_null);
|
|
pos_next = cc.pos;
|
|
break;
|
|
}
|
|
/* transform null strict_neq if_false/if_true -> is_null if_true/if_false */
|
|
if (code_match(&cc, pos_next, OP_strict_neq, M2(OP_if_false, OP_if_true), -1)) {
|
|
if (cc.line_num >= 0) line_num = cc.line_num;
|
|
if (cc.col_num >= 0) col_num = cc.col_num;
|
|
add_pc2line_info(s, bc_out.size, line_num, col_num);
|
|
dbuf_putc(&bc_out, OP_is_null);
|
|
pos_next = cc.pos;
|
|
label = cc.label;
|
|
op = cc.op ^ OP_if_false ^ OP_if_true;
|
|
goto has_label;
|
|
}
|
|
/* fall thru */
|
|
case OP_push_false:
|
|
case OP_push_true:
|
|
val = (op == OP_push_true);
|
|
if (code_match(&cc, pos_next, M2(OP_if_false, OP_if_true), -1)) {
|
|
has_constant_test:
|
|
if (cc.line_num >= 0) line_num = cc.line_num;
|
|
if (cc.col_num >= 0) col_num = cc.col_num;
|
|
if (val == cc.op - OP_if_false) {
|
|
/* transform null if_false(l1) -> goto l1 */
|
|
/* transform false if_false(l1) -> goto l1 */
|
|
/* transform true if_true(l1) -> goto l1 */
|
|
pos_next = cc.pos;
|
|
op = OP_goto;
|
|
label = cc.label;
|
|
goto has_goto;
|
|
} else {
|
|
/* transform null if_true(l1) -> nop */
|
|
/* transform false if_true(l1) -> nop */
|
|
/* transform true if_false(l1) -> nop */
|
|
pos_next = cc.pos;
|
|
update_label(s, cc.label, -1);
|
|
break;
|
|
}
|
|
}
|
|
goto no_change;
|
|
|
|
case OP_push_i32:
|
|
/* transform i32(val) neg -> i32(-val) */
|
|
val = get_i32(bc_buf + pos + 1);
|
|
if ((val != INT32_MIN && val != 0)
|
|
&& code_match(&cc, pos_next, OP_neg, -1)) {
|
|
if (cc.line_num >= 0) line_num = cc.line_num;
|
|
if (cc.col_num >= 0) col_num = cc.col_num;
|
|
if (code_match(&cc, cc.pos, OP_drop, -1)) {
|
|
if (cc.line_num >= 0) line_num = cc.line_num;
|
|
if (cc.col_num >= 0) col_num = cc.col_num;
|
|
} else {
|
|
add_pc2line_info(s, bc_out.size, line_num, col_num);
|
|
push_short_int(&bc_out, -val);
|
|
}
|
|
pos_next = cc.pos;
|
|
break;
|
|
}
|
|
/* remove push/drop pairs generated by the parser */
|
|
if (code_match(&cc, pos_next, OP_drop, -1)) {
|
|
if (cc.line_num >= 0) line_num = cc.line_num;
|
|
if (cc.col_num >= 0) col_num = cc.col_num;
|
|
pos_next = cc.pos;
|
|
break;
|
|
}
|
|
/* Optimize constant tests: `if (0)`, `if (1)`, `if (!0)`... */
|
|
if (code_match(&cc, pos_next, M2(OP_if_false, OP_if_true), -1)) {
|
|
val = (val != 0);
|
|
goto has_constant_test;
|
|
}
|
|
add_pc2line_info(s, bc_out.size, line_num, col_num);
|
|
push_short_int(&bc_out, val);
|
|
break;
|
|
|
|
case OP_push_const:
|
|
case OP_fclosure:
|
|
{
|
|
int idx = get_u32(bc_buf + pos + 1);
|
|
if (idx < 256) {
|
|
add_pc2line_info(s, bc_out.size, line_num, col_num);
|
|
dbuf_putc(&bc_out, OP_push_const8 + op - OP_push_const);
|
|
dbuf_putc(&bc_out, idx);
|
|
break;
|
|
}
|
|
}
|
|
goto no_change;
|
|
|
|
case OP_get_field:
|
|
{
|
|
JSAtom atom = get_u32(bc_buf + pos + 1);
|
|
if (atom == JS_ATOM_length) {
|
|
JS_FreeAtom(ctx, atom);
|
|
add_pc2line_info(s, bc_out.size, line_num, col_num);
|
|
dbuf_putc(&bc_out, OP_get_length);
|
|
break;
|
|
}
|
|
}
|
|
goto no_change;
|
|
|
|
case OP_push_atom_value:
|
|
{
|
|
JSAtom atom = get_u32(bc_buf + pos + 1);
|
|
/* remove push/drop pairs generated by the parser */
|
|
if (code_match(&cc, pos_next, OP_drop, -1)) {
|
|
JS_FreeAtom(ctx, atom);
|
|
if (cc.line_num >= 0) line_num = cc.line_num;
|
|
if (cc.col_num >= 0) col_num = cc.col_num;
|
|
pos_next = cc.pos;
|
|
break;
|
|
}
|
|
if (atom == JS_ATOM_empty_string) {
|
|
JS_FreeAtom(ctx, atom);
|
|
add_pc2line_info(s, bc_out.size, line_num, col_num);
|
|
dbuf_putc(&bc_out, OP_push_empty_string);
|
|
break;
|
|
}
|
|
}
|
|
goto no_change;
|
|
|
|
case OP_to_propkey:
|
|
case OP_to_propkey2:
|
|
/* remove redundant to_propkey/to_propkey2 opcodes when storing simple data */
|
|
if (code_match(&cc, pos_next, M3(OP_get_loc, OP_get_arg, OP_get_var_ref), -1, OP_put_array_el, -1)
|
|
|| code_match(&cc, pos_next, M3(OP_push_i32, OP_push_const, OP_push_atom_value), OP_put_array_el, -1)
|
|
|| code_match(&cc, pos_next, M4(OP_undefined, OP_null, OP_push_true, OP_push_false), OP_put_array_el, -1)) {
|
|
break;
|
|
}
|
|
goto no_change;
|
|
|
|
case OP_undefined:
|
|
/* remove push/drop pairs generated by the parser */
|
|
if (code_match(&cc, pos_next, OP_drop, -1)) {
|
|
if (cc.line_num >= 0) line_num = cc.line_num;
|
|
if (cc.col_num >= 0) col_num = cc.col_num;
|
|
pos_next = cc.pos;
|
|
break;
|
|
}
|
|
/* transform undefined return -> return_undefined */
|
|
if (code_match(&cc, pos_next, OP_return, -1)) {
|
|
if (cc.line_num >= 0) line_num = cc.line_num;
|
|
if (cc.col_num >= 0) col_num = cc.col_num;
|
|
add_pc2line_info(s, bc_out.size, line_num, col_num);
|
|
dbuf_putc(&bc_out, OP_return_undef);
|
|
pos_next = cc.pos;
|
|
break;
|
|
}
|
|
/* transform undefined if_true(l1)/if_false(l1) -> nop/goto(l1) */
|
|
if (code_match(&cc, pos_next, M2(OP_if_false, OP_if_true), -1)) {
|
|
val = 0;
|
|
goto has_constant_test;
|
|
}
|
|
/* transform undefined strict_eq -> is_undefined */
|
|
if (code_match(&cc, pos_next, OP_strict_eq, -1)) {
|
|
if (cc.line_num >= 0) line_num = cc.line_num;
|
|
if (cc.col_num >= 0) col_num = cc.col_num;
|
|
add_pc2line_info(s, bc_out.size, line_num, col_num);
|
|
dbuf_putc(&bc_out, OP_is_undefined);
|
|
pos_next = cc.pos;
|
|
break;
|
|
}
|
|
/* transform undefined strict_neq if_false/if_true -> is_undefined if_true/if_false */
|
|
if (code_match(&cc, pos_next, OP_strict_neq, M2(OP_if_false, OP_if_true), -1)) {
|
|
if (cc.line_num >= 0) line_num = cc.line_num;
|
|
if (cc.col_num >= 0) col_num = cc.col_num;
|
|
add_pc2line_info(s, bc_out.size, line_num, col_num);
|
|
dbuf_putc(&bc_out, OP_is_undefined);
|
|
pos_next = cc.pos;
|
|
label = cc.label;
|
|
op = cc.op ^ OP_if_false ^ OP_if_true;
|
|
goto has_label;
|
|
}
|
|
goto no_change;
|
|
|
|
case OP_insert2:
|
|
/* Transformation:
|
|
insert2 put_field(a) drop -> put_field(a)
|
|
insert2 put_var_strict(a) drop -> put_var_strict(a)
|
|
*/
|
|
if (code_match(&cc, pos_next, M2(OP_put_field, OP_put_var_strict), OP_drop, -1)) {
|
|
if (cc.line_num >= 0) line_num = cc.line_num;
|
|
if (cc.col_num >= 0) col_num = cc.col_num;
|
|
add_pc2line_info(s, bc_out.size, line_num, col_num);
|
|
dbuf_putc(&bc_out, cc.op);
|
|
dbuf_put_u32(&bc_out, cc.atom);
|
|
pos_next = cc.pos;
|
|
break;
|
|
}
|
|
goto no_change;
|
|
|
|
case OP_dup:
|
|
{
|
|
/* Transformation: dup put_x(n) drop -> put_x(n) */
|
|
int op1, line2 = -1;
|
|
/* Transformation: dup put_x(n) -> set_x(n) */
|
|
if (code_match(&cc, pos_next, M3(OP_put_loc, OP_put_arg, OP_put_var_ref), -1, -1)) {
|
|
if (cc.line_num >= 0) line_num = cc.line_num;
|
|
if (cc.col_num >= 0) col_num = cc.col_num;
|
|
op1 = cc.op + 1; /* put_x -> set_x */
|
|
pos_next = cc.pos;
|
|
if (code_match(&cc, cc.pos, OP_drop, -1)) {
|
|
if (cc.line_num >= 0) line_num = cc.line_num;
|
|
if (cc.col_num >= 0) col_num = cc.col_num;
|
|
op1 -= 1; /* set_x drop -> put_x */
|
|
pos_next = cc.pos;
|
|
if (code_match(&cc, cc.pos, op1 - 1, cc.idx, -1)) {
|
|
line2 = cc.line_num; /* delay line number update */
|
|
op1 += 1; /* put_x(n) get_x(n) -> set_x(n) */
|
|
pos_next = cc.pos;
|
|
}
|
|
}
|
|
add_pc2line_info(s, bc_out.size, line_num, col_num);
|
|
put_short_code(&bc_out, op1, cc.idx);
|
|
if (line2 >= 0) line_num = line2;
|
|
break;
|
|
}
|
|
}
|
|
goto no_change;
|
|
|
|
case OP_swap:
|
|
// transformation: swap swap -> nothing!
|
|
if (code_match(&cc, pos_next, OP_swap, -1, -1)) {
|
|
if (cc.line_num >= 0) line_num = cc.line_num;
|
|
if (cc.col_num >= 0) col_num = cc.col_num;
|
|
pos_next = cc.pos;
|
|
break;
|
|
}
|
|
goto no_change;
|
|
|
|
case OP_get_loc:
|
|
{
|
|
/* transformation:
|
|
get_loc(n) post_dec put_loc(n) drop -> dec_loc(n)
|
|
get_loc(n) post_inc put_loc(n) drop -> inc_loc(n)
|
|
get_loc(n) dec dup put_loc(n) drop -> dec_loc(n)
|
|
get_loc(n) inc dup put_loc(n) drop -> inc_loc(n)
|
|
*/
|
|
int idx;
|
|
idx = get_u16(bc_buf + pos + 1);
|
|
if (idx >= 256)
|
|
goto no_change;
|
|
if (code_match(&cc, pos_next, M2(OP_post_dec, OP_post_inc), OP_put_loc, idx, OP_drop, -1) ||
|
|
code_match(&cc, pos_next, M2(OP_dec, OP_inc), OP_dup, OP_put_loc, idx, OP_drop, -1)) {
|
|
if (cc.line_num >= 0) line_num = cc.line_num;
|
|
if (cc.col_num >= 0) col_num = cc.col_num;
|
|
add_pc2line_info(s, bc_out.size, line_num, col_num);
|
|
dbuf_putc(&bc_out, (cc.op == OP_inc || cc.op == OP_post_inc) ? OP_inc_loc : OP_dec_loc);
|
|
dbuf_putc(&bc_out, idx);
|
|
pos_next = cc.pos;
|
|
break;
|
|
}
|
|
/* transformation:
|
|
get_loc(n) push_atom_value(x) add dup put_loc(n) drop -> push_atom_value(x) add_loc(n)
|
|
*/
|
|
if (code_match(&cc, pos_next, OP_push_atom_value, OP_add, OP_dup, OP_put_loc, idx, OP_drop, -1)) {
|
|
if (cc.line_num >= 0) line_num = cc.line_num;
|
|
if (cc.col_num >= 0) col_num = cc.col_num;
|
|
add_pc2line_info(s, bc_out.size, line_num, col_num);
|
|
if (cc.atom == JS_ATOM_empty_string) {
|
|
JS_FreeAtom(ctx, cc.atom);
|
|
dbuf_putc(&bc_out, OP_push_empty_string);
|
|
} else {
|
|
dbuf_putc(&bc_out, OP_push_atom_value);
|
|
dbuf_put_u32(&bc_out, cc.atom);
|
|
}
|
|
dbuf_putc(&bc_out, OP_add_loc);
|
|
dbuf_putc(&bc_out, idx);
|
|
pos_next = cc.pos;
|
|
break;
|
|
}
|
|
/* transformation:
|
|
get_loc(n) push_i32(x) add dup put_loc(n) drop -> push_i32(x) add_loc(n)
|
|
*/
|
|
if (code_match(&cc, pos_next, OP_push_i32, OP_add, OP_dup, OP_put_loc, idx, OP_drop, -1)) {
|
|
if (cc.line_num >= 0) line_num = cc.line_num;
|
|
if (cc.col_num >= 0) col_num = cc.col_num;
|
|
add_pc2line_info(s, bc_out.size, line_num, col_num);
|
|
push_short_int(&bc_out, cc.label);
|
|
dbuf_putc(&bc_out, OP_add_loc);
|
|
dbuf_putc(&bc_out, idx);
|
|
pos_next = cc.pos;
|
|
break;
|
|
}
|
|
/* transformation: XXX: also do these:
|
|
get_loc(n) get_loc(x) add dup put_loc(n) drop -> get_loc(x) add_loc(n)
|
|
get_loc(n) get_arg(x) add dup put_loc(n) drop -> get_arg(x) add_loc(n)
|
|
get_loc(n) get_var_ref(x) add dup put_loc(n) drop -> get_var_ref(x) add_loc(n)
|
|
*/
|
|
if (code_match(&cc, pos_next, M3(OP_get_loc, OP_get_arg, OP_get_var_ref), -1, OP_add, OP_dup, OP_put_loc, idx, OP_drop, -1)) {
|
|
if (cc.line_num >= 0) line_num = cc.line_num;
|
|
if (cc.col_num >= 0) col_num = cc.col_num;
|
|
add_pc2line_info(s, bc_out.size, line_num, col_num);
|
|
put_short_code(&bc_out, cc.op, cc.idx);
|
|
dbuf_putc(&bc_out, OP_add_loc);
|
|
dbuf_putc(&bc_out, idx);
|
|
pos_next = cc.pos;
|
|
break;
|
|
}
|
|
/* transformation: get_loc(0) get_loc(1) -> get_loc0_loc1 */
|
|
if (idx == 0 && code_match(&cc, pos_next, OP_get_loc, 1, -1)) {
|
|
if (cc.line_num >= 0) line_num = cc.line_num;
|
|
if (cc.col_num >= 0) col_num = cc.col_num;
|
|
add_pc2line_info(s, bc_out.size, line_num, col_num);
|
|
dbuf_putc(&bc_out, OP_get_loc0_loc1);
|
|
pos_next = cc.pos;
|
|
break;
|
|
}
|
|
add_pc2line_info(s, bc_out.size, line_num, col_num);
|
|
put_short_code(&bc_out, op, idx);
|
|
}
|
|
break;
|
|
case OP_get_arg:
|
|
case OP_get_var_ref:
|
|
{
|
|
int idx;
|
|
idx = get_u16(bc_buf + pos + 1);
|
|
add_pc2line_info(s, bc_out.size, line_num, col_num);
|
|
put_short_code(&bc_out, op, idx);
|
|
}
|
|
break;
|
|
case OP_put_loc:
|
|
case OP_put_arg:
|
|
case OP_put_var_ref:
|
|
{
|
|
/* transformation: put_x(n) get_x(n) -> set_x(n) */
|
|
int idx;
|
|
idx = get_u16(bc_buf + pos + 1);
|
|
if (code_match(&cc, pos_next, op - 1, idx, -1)) {
|
|
if (cc.line_num >= 0) line_num = cc.line_num;
|
|
if (cc.col_num >= 0) col_num = cc.col_num;
|
|
add_pc2line_info(s, bc_out.size, line_num, col_num);
|
|
put_short_code(&bc_out, op + 1, idx);
|
|
pos_next = cc.pos;
|
|
break;
|
|
}
|
|
add_pc2line_info(s, bc_out.size, line_num, col_num);
|
|
put_short_code(&bc_out, op, idx);
|
|
}
|
|
break;
|
|
|
|
case OP_post_inc:
|
|
case OP_post_dec:
|
|
{
|
|
/* transformation:
|
|
post_inc put_x drop -> inc put_x
|
|
post_inc perm3 put_field drop -> inc put_field
|
|
post_inc perm3 put_var_strict drop -> inc put_var_strict
|
|
post_inc perm4 put_array_el drop -> inc put_array_el
|
|
*/
|
|
int op1, idx;
|
|
if (code_match(&cc, pos_next, M3(OP_put_loc, OP_put_arg, OP_put_var_ref), -1, OP_drop, -1)) {
|
|
if (cc.line_num >= 0) line_num = cc.line_num;
|
|
if (cc.col_num >= 0) col_num = cc.col_num;
|
|
op1 = cc.op;
|
|
idx = cc.idx;
|
|
pos_next = cc.pos;
|
|
if (code_match(&cc, cc.pos, op1 - 1, idx, -1)) {
|
|
if (cc.line_num >= 0) line_num = cc.line_num;
|
|
if (cc.col_num >= 0) col_num = cc.col_num;
|
|
op1 += 1; /* put_x(n) get_x(n) -> set_x(n) */
|
|
pos_next = cc.pos;
|
|
}
|
|
add_pc2line_info(s, bc_out.size, line_num, col_num);
|
|
dbuf_putc(&bc_out, OP_dec + (op - OP_post_dec));
|
|
put_short_code(&bc_out, op1, idx);
|
|
break;
|
|
}
|
|
if (code_match(&cc, pos_next, OP_perm3, M2(OP_put_field, OP_put_var_strict), OP_drop, -1)) {
|
|
if (cc.line_num >= 0) line_num = cc.line_num;
|
|
if (cc.col_num >= 0) col_num = cc.col_num;
|
|
add_pc2line_info(s, bc_out.size, line_num, col_num);
|
|
dbuf_putc(&bc_out, OP_dec + (op - OP_post_dec));
|
|
dbuf_putc(&bc_out, cc.op);
|
|
dbuf_put_u32(&bc_out, cc.atom);
|
|
pos_next = cc.pos;
|
|
break;
|
|
}
|
|
if (code_match(&cc, pos_next, OP_perm4, OP_put_array_el, OP_drop, -1)) {
|
|
if (cc.line_num >= 0) line_num = cc.line_num;
|
|
if (cc.col_num >= 0) col_num = cc.col_num;
|
|
add_pc2line_info(s, bc_out.size, line_num, col_num);
|
|
dbuf_putc(&bc_out, OP_dec + (op - OP_post_dec));
|
|
dbuf_putc(&bc_out, OP_put_array_el);
|
|
pos_next = cc.pos;
|
|
break;
|
|
}
|
|
}
|
|
goto no_change;
|
|
|
|
case OP_typeof:
|
|
/* simplify typeof tests */
|
|
if (code_match(&cc, pos_next, OP_push_atom_value, M4(OP_strict_eq, OP_strict_neq, OP_eq, OP_neq), -1)) {
|
|
if (cc.line_num >= 0) line_num = cc.line_num;
|
|
if (cc.col_num >= 0) col_num = cc.col_num;
|
|
int op1 = (cc.op == OP_strict_eq || cc.op == OP_eq) ? OP_strict_eq : OP_strict_neq;
|
|
int op2 = -1;
|
|
switch (cc.atom) {
|
|
case JS_ATOM_undefined:
|
|
op2 = OP_typeof_is_undefined;
|
|
break;
|
|
case JS_ATOM_function:
|
|
op2 = OP_typeof_is_function;
|
|
break;
|
|
}
|
|
if (op2 >= 0) {
|
|
/* transform typeof(s) == "<type>" into is_<type> */
|
|
if (op1 == OP_strict_eq) {
|
|
add_pc2line_info(s, bc_out.size, line_num, col_num);
|
|
dbuf_putc(&bc_out, op2);
|
|
JS_FreeAtom(ctx, cc.atom);
|
|
pos_next = cc.pos;
|
|
break;
|
|
}
|
|
if (op1 == OP_strict_neq && code_match(&cc, cc.pos, OP_if_false, -1)) {
|
|
/* transform typeof(s) != "<type>" if_false into is_<type> if_true */
|
|
if (cc.line_num >= 0) line_num = cc.line_num;
|
|
if (cc.col_num >= 0) col_num = cc.col_num;
|
|
add_pc2line_info(s, bc_out.size, line_num, col_num);
|
|
dbuf_putc(&bc_out, op2);
|
|
JS_FreeAtom(ctx, cc.atom);
|
|
pos_next = cc.pos;
|
|
label = cc.label;
|
|
op = OP_if_true;
|
|
goto has_label;
|
|
}
|
|
}
|
|
}
|
|
goto no_change;
|
|
|
|
default:
|
|
no_change:
|
|
add_pc2line_info(s, bc_out.size, line_num, col_num);
|
|
dbuf_put(&bc_out, bc_buf + pos, len);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* check that there were no missing labels */
|
|
for(i = 0; i < s->label_count; i++) {
|
|
assert(label_slots[i].first_reloc == NULL);
|
|
}
|
|
|
|
/* more jump optimizations */
|
|
patch_offsets = 0;
|
|
for (i = 0, jp = s->jump_slots; i < s->jump_count; i++, jp++) {
|
|
LabelSlot *ls;
|
|
JumpSlot *jp1;
|
|
int j, pos, diff, delta;
|
|
|
|
delta = 3;
|
|
switch (op = jp->op) {
|
|
case OP_goto16:
|
|
delta = 1;
|
|
/* fall thru */
|
|
case OP_if_false:
|
|
case OP_if_true:
|
|
case OP_goto:
|
|
pos = jp->pos;
|
|
diff = s->label_slots[jp->label].addr - pos;
|
|
if (diff >= -128 && diff <= 127 + delta) {
|
|
//put_u8(bc_out.buf + pos, diff);
|
|
jp->size = 1;
|
|
if (op == OP_goto16) {
|
|
bc_out.buf[pos - 1] = jp->op = OP_goto8;
|
|
} else {
|
|
bc_out.buf[pos - 1] = jp->op = OP_if_false8 + (op - OP_if_false);
|
|
}
|
|
goto shrink;
|
|
} else
|
|
if (diff == (int16_t)diff && op == OP_goto) {
|
|
//put_u16(bc_out.buf + pos, diff);
|
|
jp->size = 2;
|
|
delta = 2;
|
|
bc_out.buf[pos - 1] = jp->op = OP_goto16;
|
|
shrink:
|
|
/* XXX: should reduce complexity, using 2 finger copy scheme */
|
|
memmove(bc_out.buf + pos + jp->size, bc_out.buf + pos + jp->size + delta,
|
|
bc_out.size - pos - jp->size - delta);
|
|
bc_out.size -= delta;
|
|
patch_offsets++;
|
|
for (j = 0, ls = s->label_slots; j < s->label_count; j++, ls++) {
|
|
if (ls->addr > pos)
|
|
ls->addr -= delta;
|
|
}
|
|
for (j = i + 1, jp1 = jp + 1; j < s->jump_count; j++, jp1++) {
|
|
if (jp1->pos > pos)
|
|
jp1->pos -= delta;
|
|
}
|
|
for (j = 0; j < s->source_loc_count; j++) {
|
|
if (s->source_loc_slots[j].pc > pos)
|
|
s->source_loc_slots[j].pc -= delta;
|
|
}
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (patch_offsets) {
|
|
JumpSlot *jp1;
|
|
int j;
|
|
for (j = 0, jp1 = s->jump_slots; j < s->jump_count; j++, jp1++) {
|
|
int diff1 = s->label_slots[jp1->label].addr - jp1->pos;
|
|
switch (jp1->size) {
|
|
case 1:
|
|
put_u8(bc_out.buf + jp1->pos, diff1);
|
|
break;
|
|
case 2:
|
|
put_u16(bc_out.buf + jp1->pos, diff1);
|
|
break;
|
|
case 4:
|
|
put_u32(bc_out.buf + jp1->pos, diff1);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
js_free(ctx, s->jump_slots);
|
|
s->jump_slots = NULL;
|
|
js_free(ctx, s->label_slots);
|
|
s->label_slots = NULL;
|
|
/* XXX: should delay until copying to runtime bytecode function */
|
|
compute_pc2line_info(s);
|
|
js_free(ctx, s->source_loc_slots);
|
|
s->source_loc_slots = NULL;
|
|
/* set the new byte code */
|
|
dbuf_free(&s->byte_code);
|
|
s->byte_code = bc_out;
|
|
s->use_short_opcodes = TRUE;
|
|
if (dbuf_error(&s->byte_code)) {
|
|
JS_ThrowOutOfMemory(ctx);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
fail:
|
|
/* XXX: not safe */
|
|
dbuf_free(&bc_out);
|
|
return -1;
|
|
}
|
|
|
|
/* compute the maximum stack size needed by the function */
|
|
|
|
typedef struct StackSizeState {
|
|
int bc_len;
|
|
int stack_len_max;
|
|
uint16_t *stack_level_tab;
|
|
int32_t *catch_pos_tab;
|
|
int *pc_stack;
|
|
int pc_stack_len;
|
|
int pc_stack_size;
|
|
} StackSizeState;
|
|
|
|
/* 'op' is only used for error indication */
|
|
static __exception int ss_check(JSContext *ctx, StackSizeState *s,
|
|
int pos, int op, int stack_len, int catch_pos)
|
|
{
|
|
if ((unsigned)pos >= s->bc_len) {
|
|
JS_ThrowInternalError(ctx, "bytecode buffer overflow (op=%d, pc=%d)", op, pos);
|
|
return -1;
|
|
}
|
|
if (stack_len > s->stack_len_max) {
|
|
s->stack_len_max = stack_len;
|
|
if (s->stack_len_max > JS_STACK_SIZE_MAX) {
|
|
JS_ThrowInternalError(ctx, "stack overflow (op=%d, pc=%d)", op, pos);
|
|
return -1;
|
|
}
|
|
}
|
|
if (s->stack_level_tab[pos] != 0xffff) {
|
|
/* already explored: check that the stack size is consistent */
|
|
if (s->stack_level_tab[pos] != stack_len) {
|
|
JS_ThrowInternalError(ctx, "inconsistent stack size: %d %d (pc=%d)",
|
|
s->stack_level_tab[pos], stack_len, pos);
|
|
return -1;
|
|
} else if (s->catch_pos_tab[pos] != catch_pos) {
|
|
JS_ThrowInternalError(ctx, "inconsistent catch position: %d %d (pc=%d)",
|
|
s->catch_pos_tab[pos], catch_pos, pos);
|
|
return -1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* mark as explored and store the stack size */
|
|
s->stack_level_tab[pos] = stack_len;
|
|
s->catch_pos_tab[pos] = catch_pos;
|
|
|
|
/* queue the new PC to explore */
|
|
if (js_resize_array(ctx, (void **)&s->pc_stack, sizeof(s->pc_stack[0]),
|
|
&s->pc_stack_size, s->pc_stack_len + 1))
|
|
return -1;
|
|
s->pc_stack[s->pc_stack_len++] = pos;
|
|
return 0;
|
|
}
|
|
|
|
static __exception int compute_stack_size(JSContext *ctx,
|
|
JSFunctionDef *fd,
|
|
int *pstack_size)
|
|
{
|
|
StackSizeState s_s, *s = &s_s;
|
|
int i, diff, n_pop, pos_next, stack_len, pos, op, catch_pos, catch_level;
|
|
const JSOpCode *oi;
|
|
const uint8_t *bc_buf;
|
|
|
|
bc_buf = fd->byte_code.buf;
|
|
s->bc_len = fd->byte_code.size;
|
|
/* bc_len > 0 */
|
|
s->stack_level_tab = js_malloc(ctx, sizeof(s->stack_level_tab[0]) *
|
|
s->bc_len);
|
|
if (!s->stack_level_tab)
|
|
return -1;
|
|
for(i = 0; i < s->bc_len; i++)
|
|
s->stack_level_tab[i] = 0xffff;
|
|
s->pc_stack = NULL;
|
|
s->catch_pos_tab = js_malloc(ctx, sizeof(s->catch_pos_tab[0]) * s->bc_len);
|
|
if (!s->catch_pos_tab)
|
|
goto fail;
|
|
|
|
s->stack_len_max = 0;
|
|
s->pc_stack_len = 0;
|
|
s->pc_stack_size = 0;
|
|
|
|
/* breadth-first graph exploration */
|
|
if (ss_check(ctx, s, 0, OP_invalid, 0, -1))
|
|
goto fail;
|
|
|
|
while (s->pc_stack_len > 0) {
|
|
pos = s->pc_stack[--s->pc_stack_len];
|
|
stack_len = s->stack_level_tab[pos];
|
|
catch_pos = s->catch_pos_tab[pos];
|
|
op = bc_buf[pos];
|
|
if (op == 0 || op >= OP_COUNT) {
|
|
JS_ThrowInternalError(ctx, "invalid opcode (op=%d, pc=%d)", op, pos);
|
|
goto fail;
|
|
}
|
|
oi = &short_opcode_info(op);
|
|
#ifdef DUMP_BYTECODE_STACK
|
|
if (check_dump_flag(ctx->rt, DUMP_BYTECODE_STACK))
|
|
printf("%5d: %10s %5d %5d\n", pos, oi->name, stack_len, catch_pos);
|
|
#endif
|
|
pos_next = pos + oi->size;
|
|
if (pos_next > s->bc_len) {
|
|
JS_ThrowInternalError(ctx, "bytecode buffer overflow (op=%d, pc=%d)", op, pos);
|
|
goto fail;
|
|
}
|
|
n_pop = oi->n_pop;
|
|
/* call pops a variable number of arguments */
|
|
if (oi->fmt == OP_FMT_npop || oi->fmt == OP_FMT_npop_u16) {
|
|
n_pop += get_u16(bc_buf + pos + 1);
|
|
} else if (oi->fmt == OP_FMT_npopx) {
|
|
n_pop += op - OP_call0;
|
|
}
|
|
|
|
if (stack_len < n_pop) {
|
|
JS_ThrowInternalError(ctx, "stack underflow (op=%d, pc=%d)", op, pos);
|
|
goto fail;
|
|
}
|
|
stack_len += oi->n_push - n_pop;
|
|
if (stack_len > s->stack_len_max) {
|
|
s->stack_len_max = stack_len;
|
|
if (s->stack_len_max > JS_STACK_SIZE_MAX) {
|
|
JS_ThrowInternalError(ctx, "stack overflow (op=%d, pc=%d)", op, pos);
|
|
goto fail;
|
|
}
|
|
}
|
|
switch(op) {
|
|
case OP_tail_call:
|
|
case OP_tail_call_method:
|
|
case OP_return:
|
|
case OP_return_undef:
|
|
case OP_return_async:
|
|
case OP_throw:
|
|
case OP_throw_error:
|
|
case OP_ret:
|
|
goto done_insn;
|
|
case OP_goto:
|
|
diff = get_u32(bc_buf + pos + 1);
|
|
pos_next = pos + 1 + diff;
|
|
break;
|
|
case OP_goto16:
|
|
diff = (int16_t)get_u16(bc_buf + pos + 1);
|
|
pos_next = pos + 1 + diff;
|
|
break;
|
|
case OP_goto8:
|
|
diff = (int8_t)bc_buf[pos + 1];
|
|
pos_next = pos + 1 + diff;
|
|
break;
|
|
case OP_if_true8:
|
|
case OP_if_false8:
|
|
diff = (int8_t)bc_buf[pos + 1];
|
|
if (ss_check(ctx, s, pos + 1 + diff, op, stack_len, catch_pos))
|
|
goto fail;
|
|
break;
|
|
case OP_if_true:
|
|
case OP_if_false:
|
|
diff = get_u32(bc_buf + pos + 1);
|
|
if (ss_check(ctx, s, pos + 1 + diff, op, stack_len, catch_pos))
|
|
goto fail;
|
|
break;
|
|
case OP_gosub:
|
|
diff = get_u32(bc_buf + pos + 1);
|
|
if (ss_check(ctx, s, pos + 1 + diff, op, stack_len + 1, catch_pos))
|
|
goto fail;
|
|
break;
|
|
case OP_with_get_var:
|
|
case OP_with_delete_var:
|
|
diff = get_u32(bc_buf + pos + 5);
|
|
if (ss_check(ctx, s, pos + 5 + diff, op, stack_len + 1, catch_pos))
|
|
goto fail;
|
|
break;
|
|
case OP_with_make_ref:
|
|
case OP_with_get_ref:
|
|
case OP_with_get_ref_undef:
|
|
diff = get_u32(bc_buf + pos + 5);
|
|
if (ss_check(ctx, s, pos + 5 + diff, op, stack_len + 2, catch_pos))
|
|
goto fail;
|
|
break;
|
|
case OP_with_put_var:
|
|
diff = get_u32(bc_buf + pos + 5);
|
|
if (ss_check(ctx, s, pos + 5 + diff, op, stack_len - 1, catch_pos))
|
|
goto fail;
|
|
break;
|
|
case OP_catch:
|
|
diff = get_u32(bc_buf + pos + 1);
|
|
if (ss_check(ctx, s, pos + 1 + diff, op, stack_len, catch_pos))
|
|
goto fail;
|
|
catch_pos = pos;
|
|
break;
|
|
case OP_for_of_start:
|
|
case OP_for_await_of_start:
|
|
catch_pos = pos;
|
|
break;
|
|
/* we assume the catch offset entry is only removed with
|
|
some op codes */
|
|
case OP_drop:
|
|
catch_level = stack_len;
|
|
goto check_catch;
|
|
case OP_nip:
|
|
catch_level = stack_len - 1;
|
|
goto check_catch;
|
|
case OP_nip1:
|
|
catch_level = stack_len - 1;
|
|
goto check_catch;
|
|
case OP_iterator_close:
|
|
catch_level = stack_len + 2;
|
|
check_catch:
|
|
/* Note: for for_of_start/for_await_of_start we consider
|
|
the catch offset is on the first stack entry instead of
|
|
the thirst */
|
|
if (catch_pos >= 0) {
|
|
int level;
|
|
level = s->stack_level_tab[catch_pos];
|
|
if (bc_buf[catch_pos] != OP_catch)
|
|
level++; /* for_of_start, for_wait_of_start */
|
|
/* catch_level = stack_level before op_catch is executed ? */
|
|
if (catch_level == level) {
|
|
catch_pos = s->catch_pos_tab[catch_pos];
|
|
}
|
|
}
|
|
break;
|
|
case OP_nip_catch:
|
|
if (catch_pos < 0) {
|
|
JS_ThrowInternalError(ctx, "nip_catch: no catch op (pc=%d)", pos);
|
|
goto fail;
|
|
}
|
|
stack_len = s->stack_level_tab[catch_pos];
|
|
if (bc_buf[catch_pos] != OP_catch)
|
|
stack_len++; /* for_of_start, for_wait_of_start */
|
|
stack_len++; /* no stack overflow is possible by construction */
|
|
catch_pos = s->catch_pos_tab[catch_pos];
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (ss_check(ctx, s, pos_next, op, stack_len, catch_pos))
|
|
goto fail;
|
|
done_insn: ;
|
|
}
|
|
js_free(ctx, s->pc_stack);
|
|
js_free(ctx, s->catch_pos_tab);
|
|
js_free(ctx, s->stack_level_tab);
|
|
*pstack_size = s->stack_len_max;
|
|
return 0;
|
|
fail:
|
|
js_free(ctx, s->pc_stack);
|
|
js_free(ctx, s->catch_pos_tab);
|
|
js_free(ctx, s->stack_level_tab);
|
|
*pstack_size = 0;
|
|
return -1;
|
|
}
|
|
|
|
static int add_module_variables(JSContext *ctx, JSFunctionDef *fd)
|
|
{
|
|
int i, idx;
|
|
JSModuleDef *m = fd->module;
|
|
JSExportEntry *me;
|
|
JSGlobalVar *hf;
|
|
|
|
/* The imported global variables were added as closure variables
|
|
in js_parse_import(). We add here the module global
|
|
variables. */
|
|
|
|
for(i = 0; i < fd->global_var_count; i++) {
|
|
hf = &fd->global_vars[i];
|
|
if (add_closure_var(ctx, fd, TRUE, FALSE, i, hf->var_name, hf->is_const,
|
|
hf->is_lexical, JS_VAR_NORMAL) < 0)
|
|
return -1;
|
|
}
|
|
|
|
/* resolve the variable names of the local exports */
|
|
for(i = 0; i < m->export_entries_count; i++) {
|
|
me = &m->export_entries[i];
|
|
if (me->export_type == JS_EXPORT_TYPE_LOCAL) {
|
|
idx = find_closure_var(ctx, fd, me->local_name);
|
|
if (idx < 0) {
|
|
// XXX: add_module_variables() should take JSParseState *s and use js_parse_error_atom
|
|
JS_ThrowSyntaxErrorAtom(ctx, "exported variable '%s' does not exist",
|
|
me->local_name);
|
|
return -1;
|
|
}
|
|
me->u.local.var_idx = idx;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* create a function object from a function definition. The function
|
|
definition is freed. All the child functions are also created. It
|
|
must be done this way to resolve all the variables. */
|
|
static JSValue js_create_function(JSContext *ctx, JSFunctionDef *fd)
|
|
{
|
|
JSValue func_obj;
|
|
JSFunctionBytecode *b;
|
|
struct list_head *el, *el1;
|
|
int stack_size, scope, idx;
|
|
int function_size, byte_code_offset, cpool_offset;
|
|
int closure_var_offset, vardefs_offset;
|
|
|
|
/* recompute scope linkage */
|
|
for (scope = 0; scope < fd->scope_count; scope++) {
|
|
fd->scopes[scope].first = -1;
|
|
}
|
|
if (fd->has_parameter_expressions) {
|
|
/* special end of variable list marker for the argument scope */
|
|
fd->scopes[ARG_SCOPE_INDEX].first = ARG_SCOPE_END;
|
|
}
|
|
for (idx = 0; idx < fd->var_count; idx++) {
|
|
JSVarDef *vd = &fd->vars[idx];
|
|
vd->scope_next = fd->scopes[vd->scope_level].first;
|
|
fd->scopes[vd->scope_level].first = idx;
|
|
}
|
|
for (scope = 2; scope < fd->scope_count; scope++) {
|
|
JSVarScope *sd = &fd->scopes[scope];
|
|
if (sd->first < 0)
|
|
sd->first = fd->scopes[sd->parent].first;
|
|
}
|
|
for (idx = 0; idx < fd->var_count; idx++) {
|
|
JSVarDef *vd = &fd->vars[idx];
|
|
if (vd->scope_next < 0 && vd->scope_level > 1) {
|
|
scope = fd->scopes[vd->scope_level].parent;
|
|
vd->scope_next = fd->scopes[scope].first;
|
|
}
|
|
}
|
|
|
|
/* if the function contains an eval call, the closure variables
|
|
are used to compile the eval and they must be ordered by scope,
|
|
so it is necessary to create the closure variables before any
|
|
other variable lookup is done. */
|
|
if (fd->has_eval_call)
|
|
add_eval_variables(ctx, fd);
|
|
|
|
/* add the module global variables in the closure */
|
|
if (fd->module) {
|
|
if (add_module_variables(ctx, fd))
|
|
goto fail;
|
|
}
|
|
|
|
/* first create all the child functions */
|
|
list_for_each_safe(el, el1, &fd->child_list) {
|
|
JSFunctionDef *fd1;
|
|
int cpool_idx;
|
|
|
|
fd1 = list_entry(el, JSFunctionDef, link);
|
|
cpool_idx = fd1->parent_cpool_idx;
|
|
func_obj = js_create_function(ctx, fd1);
|
|
if (JS_IsException(func_obj))
|
|
goto fail;
|
|
/* save it in the constant pool */
|
|
assert(cpool_idx >= 0);
|
|
fd->cpool[cpool_idx] = func_obj;
|
|
}
|
|
|
|
#ifdef DUMP_BYTECODE_PASS1
|
|
if (check_dump_flag(ctx->rt, DUMP_BYTECODE_PASS1)) {
|
|
printf("pass 1\n");
|
|
dump_byte_code(ctx, 1, fd->byte_code.buf, fd->byte_code.size,
|
|
fd->args, fd->arg_count, fd->vars, fd->var_count,
|
|
fd->closure_var, fd->closure_var_count,
|
|
fd->cpool, fd->cpool_count, fd->source, fd->line_num,
|
|
fd->label_slots, NULL, 0);
|
|
printf("\n");
|
|
}
|
|
#endif
|
|
|
|
if (resolve_variables(ctx, fd))
|
|
goto fail;
|
|
|
|
#ifdef DUMP_BYTECODE_PASS2
|
|
if (check_dump_flag(ctx->rt, DUMP_BYTECODE_PASS2)) {
|
|
printf("pass 2\n");
|
|
dump_byte_code(ctx, 2, fd->byte_code.buf, fd->byte_code.size,
|
|
fd->args, fd->arg_count, fd->vars, fd->var_count,
|
|
fd->closure_var, fd->closure_var_count,
|
|
fd->cpool, fd->cpool_count, fd->source, fd->line_num,
|
|
fd->label_slots, NULL, 0);
|
|
printf("\n");
|
|
}
|
|
#endif
|
|
|
|
if (resolve_labels(ctx, fd))
|
|
goto fail;
|
|
|
|
if (compute_stack_size(ctx, fd, &stack_size) < 0)
|
|
goto fail;
|
|
|
|
function_size = sizeof(*b);
|
|
cpool_offset = function_size;
|
|
function_size += fd->cpool_count * sizeof(*fd->cpool);
|
|
vardefs_offset = function_size;
|
|
function_size += (fd->arg_count + fd->var_count) * sizeof(*b->vardefs);
|
|
closure_var_offset = function_size;
|
|
function_size += fd->closure_var_count * sizeof(*fd->closure_var);
|
|
byte_code_offset = function_size;
|
|
function_size += fd->byte_code.size;
|
|
|
|
b = js_mallocz(ctx, function_size);
|
|
if (!b)
|
|
goto fail;
|
|
b->header.ref_count = 1;
|
|
|
|
b->byte_code_buf = (void *)((uint8_t*)b + byte_code_offset);
|
|
b->byte_code_len = fd->byte_code.size;
|
|
memcpy(b->byte_code_buf, fd->byte_code.buf, fd->byte_code.size);
|
|
js_free(ctx, fd->byte_code.buf);
|
|
fd->byte_code.buf = NULL;
|
|
|
|
b->func_name = fd->func_name;
|
|
if (fd->arg_count + fd->var_count > 0) {
|
|
b->vardefs = (void *)((uint8_t*)b + vardefs_offset);
|
|
if (fd->arg_count > 0)
|
|
memcpy(b->vardefs, fd->args, fd->arg_count * sizeof(fd->args[0]));
|
|
if (fd->var_count > 0)
|
|
memcpy(b->vardefs + fd->arg_count, fd->vars, fd->var_count * sizeof(fd->vars[0]));
|
|
b->var_count = fd->var_count;
|
|
b->arg_count = fd->arg_count;
|
|
b->defined_arg_count = fd->defined_arg_count;
|
|
js_free(ctx, fd->args);
|
|
js_free(ctx, fd->vars);
|
|
}
|
|
b->cpool_count = fd->cpool_count;
|
|
if (b->cpool_count) {
|
|
b->cpool = (void *)((uint8_t*)b + cpool_offset);
|
|
memcpy(b->cpool, fd->cpool, b->cpool_count * sizeof(*b->cpool));
|
|
}
|
|
js_free(ctx, fd->cpool);
|
|
fd->cpool = NULL;
|
|
|
|
b->stack_size = stack_size;
|
|
|
|
/* XXX: source and pc2line info should be packed at the end of the
|
|
JSFunctionBytecode structure, avoiding allocation overhead
|
|
*/
|
|
b->filename = fd->filename;
|
|
b->line_num = fd->line_num;
|
|
b->col_num = fd->col_num;
|
|
|
|
b->pc2line_buf = js_realloc(ctx, fd->pc2line.buf, fd->pc2line.size);
|
|
if (!b->pc2line_buf)
|
|
b->pc2line_buf = fd->pc2line.buf;
|
|
b->pc2line_len = fd->pc2line.size;
|
|
b->source = fd->source;
|
|
b->source_len = fd->source_len;
|
|
|
|
if (fd->scopes != fd->def_scope_array)
|
|
js_free(ctx, fd->scopes);
|
|
|
|
b->closure_var_count = fd->closure_var_count;
|
|
if (b->closure_var_count) {
|
|
b->closure_var = (void *)((uint8_t*)b + closure_var_offset);
|
|
memcpy(b->closure_var, fd->closure_var, b->closure_var_count * sizeof(*b->closure_var));
|
|
}
|
|
js_free(ctx, fd->closure_var);
|
|
fd->closure_var = NULL;
|
|
|
|
b->has_prototype = fd->has_prototype;
|
|
b->has_simple_parameter_list = fd->has_simple_parameter_list;
|
|
b->js_mode = fd->js_mode;
|
|
b->is_derived_class_constructor = fd->is_derived_class_constructor;
|
|
b->func_kind = fd->func_kind;
|
|
b->need_home_object = (fd->home_object_var_idx >= 0 ||
|
|
fd->need_home_object);
|
|
b->new_target_allowed = fd->new_target_allowed;
|
|
b->super_call_allowed = fd->super_call_allowed;
|
|
b->super_allowed = fd->super_allowed;
|
|
b->arguments_allowed = fd->arguments_allowed;
|
|
b->backtrace_barrier = fd->backtrace_barrier;
|
|
b->realm = JS_DupContext(ctx);
|
|
b->ic = fd->ic;
|
|
fd->ic = NULL;
|
|
rebuild_ic(ctx, b->ic);
|
|
if (b->ic->count == 0) {
|
|
free_ic(ctx->rt, b->ic);
|
|
b->ic = NULL;
|
|
}
|
|
|
|
add_gc_object(ctx->rt, &b->header, JS_GC_OBJ_TYPE_FUNCTION_BYTECODE);
|
|
|
|
#ifdef DUMP_BYTECODE_FINAL
|
|
if (check_dump_flag(ctx->rt, DUMP_BYTECODE_FINAL))
|
|
js_dump_function_bytecode(ctx, b);
|
|
#endif
|
|
|
|
if (fd->parent) {
|
|
/* remove from parent list */
|
|
list_del(&fd->link);
|
|
}
|
|
|
|
js_free(ctx, fd);
|
|
return JS_MKPTR(JS_TAG_FUNCTION_BYTECODE, b);
|
|
fail:
|
|
js_free_function_def(ctx, fd);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static void free_function_bytecode(JSRuntime *rt, JSFunctionBytecode *b)
|
|
{
|
|
int i;
|
|
|
|
free_bytecode_atoms(rt, b->byte_code_buf, b->byte_code_len, TRUE);
|
|
|
|
if (b->ic)
|
|
free_ic(rt, b->ic);
|
|
|
|
if (b->vardefs) {
|
|
for(i = 0; i < b->arg_count + b->var_count; i++) {
|
|
JS_FreeAtomRT(rt, b->vardefs[i].var_name);
|
|
}
|
|
}
|
|
for(i = 0; i < b->cpool_count; i++)
|
|
JS_FreeValueRT(rt, b->cpool[i]);
|
|
|
|
for(i = 0; i < b->closure_var_count; i++) {
|
|
JSClosureVar *cv = &b->closure_var[i];
|
|
JS_FreeAtomRT(rt, cv->var_name);
|
|
}
|
|
if (b->realm)
|
|
JS_FreeContext(b->realm);
|
|
|
|
JS_FreeAtomRT(rt, b->func_name);
|
|
JS_FreeAtomRT(rt, b->filename);
|
|
js_free_rt(rt, b->pc2line_buf);
|
|
js_free_rt(rt, b->source);
|
|
|
|
remove_gc_object(&b->header);
|
|
if (rt->gc_phase == JS_GC_PHASE_REMOVE_CYCLES && b->header.ref_count != 0) {
|
|
list_add_tail(&b->header.link, &rt->gc_zero_ref_count_list);
|
|
} else {
|
|
js_free_rt(rt, b);
|
|
}
|
|
}
|
|
|
|
static __exception int js_parse_directives(JSParseState *s)
|
|
{
|
|
char str[20];
|
|
JSParsePos pos;
|
|
BOOL has_semi;
|
|
|
|
if (s->token.val != TOK_STRING)
|
|
return 0;
|
|
|
|
js_parse_get_pos(s, &pos);
|
|
|
|
while(s->token.val == TOK_STRING) {
|
|
/* Copy actual source string representation */
|
|
snprintf(str, sizeof str, "%.*s",
|
|
(int)(s->buf_ptr - s->token.ptr - 2), s->token.ptr + 1);
|
|
|
|
if (next_token(s))
|
|
return -1;
|
|
|
|
has_semi = FALSE;
|
|
switch (s->token.val) {
|
|
case ';':
|
|
if (next_token(s))
|
|
return -1;
|
|
has_semi = TRUE;
|
|
break;
|
|
case '}':
|
|
case TOK_EOF:
|
|
has_semi = TRUE;
|
|
break;
|
|
case TOK_NUMBER:
|
|
case TOK_STRING:
|
|
case TOK_TEMPLATE:
|
|
case TOK_IDENT:
|
|
case TOK_REGEXP:
|
|
case TOK_DEC:
|
|
case TOK_INC:
|
|
case TOK_NULL:
|
|
case TOK_FALSE:
|
|
case TOK_TRUE:
|
|
case TOK_IF:
|
|
case TOK_RETURN:
|
|
case TOK_VAR:
|
|
case TOK_THIS:
|
|
case TOK_DELETE:
|
|
case TOK_TYPEOF:
|
|
case TOK_NEW:
|
|
case TOK_DO:
|
|
case TOK_WHILE:
|
|
case TOK_FOR:
|
|
case TOK_SWITCH:
|
|
case TOK_THROW:
|
|
case TOK_TRY:
|
|
case TOK_FUNCTION:
|
|
case TOK_DEBUGGER:
|
|
case TOK_WITH:
|
|
case TOK_CLASS:
|
|
case TOK_CONST:
|
|
case TOK_ENUM:
|
|
case TOK_EXPORT:
|
|
case TOK_IMPORT:
|
|
case TOK_SUPER:
|
|
case TOK_INTERFACE:
|
|
case TOK_LET:
|
|
case TOK_PACKAGE:
|
|
case TOK_PRIVATE:
|
|
case TOK_PROTECTED:
|
|
case TOK_PUBLIC:
|
|
case TOK_STATIC:
|
|
/* automatic insertion of ';' */
|
|
if (s->got_lf)
|
|
has_semi = TRUE;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (!has_semi)
|
|
break;
|
|
if (!strcmp(str, "use strict")) {
|
|
s->cur_func->has_use_strict = TRUE;
|
|
s->cur_func->js_mode |= JS_MODE_STRICT;
|
|
}
|
|
}
|
|
return js_parse_seek_token(s, &pos);
|
|
}
|
|
|
|
static BOOL js_invalid_strict_name(JSAtom name) {
|
|
switch (name) {
|
|
case JS_ATOM_eval:
|
|
case JS_ATOM_arguments:
|
|
case JS_ATOM_implements: // future strict reserved words
|
|
case JS_ATOM_interface:
|
|
case JS_ATOM_let:
|
|
case JS_ATOM_package:
|
|
case JS_ATOM_private:
|
|
case JS_ATOM_protected:
|
|
case JS_ATOM_public:
|
|
case JS_ATOM_static:
|
|
case JS_ATOM_yield:
|
|
return TRUE;
|
|
default:
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
static int js_parse_function_check_names(JSParseState *s, JSFunctionDef *fd,
|
|
JSAtom func_name)
|
|
{
|
|
JSAtom name;
|
|
int i, idx;
|
|
|
|
if (fd->js_mode & JS_MODE_STRICT) {
|
|
if (!fd->has_simple_parameter_list && fd->has_use_strict) {
|
|
return js_parse_error(s, "\"use strict\" not allowed in function with default or destructuring parameter");
|
|
}
|
|
if (js_invalid_strict_name(func_name)) {
|
|
return js_parse_error(s, "invalid function name in strict code");
|
|
}
|
|
for (idx = 0; idx < fd->arg_count; idx++) {
|
|
name = fd->args[idx].var_name;
|
|
if (js_invalid_strict_name(name)) {
|
|
return js_parse_error(s, "invalid argument name in strict code");
|
|
}
|
|
}
|
|
}
|
|
/* check async_generator case */
|
|
if ((fd->js_mode & JS_MODE_STRICT)
|
|
|| !fd->has_simple_parameter_list
|
|
|| (fd->func_type == JS_PARSE_FUNC_METHOD && fd->func_kind == JS_FUNC_ASYNC)
|
|
|| fd->func_type == JS_PARSE_FUNC_ARROW
|
|
|| fd->func_type == JS_PARSE_FUNC_METHOD) {
|
|
for (idx = 0; idx < fd->arg_count; idx++) {
|
|
name = fd->args[idx].var_name;
|
|
if (name != JS_ATOM_NULL) {
|
|
for (i = 0; i < idx; i++) {
|
|
if (fd->args[i].var_name == name)
|
|
goto duplicate;
|
|
}
|
|
/* Check if argument name duplicates a destructuring parameter */
|
|
/* XXX: should have a flag for such variables */
|
|
for (i = 0; i < fd->var_count; i++) {
|
|
if (fd->vars[i].var_name == name &&
|
|
fd->vars[i].scope_level == 0)
|
|
goto duplicate;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
|
|
duplicate:
|
|
return js_parse_error(s, "Duplicate parameter name not allowed in this context");
|
|
}
|
|
|
|
/* create a function to initialize class fields */
|
|
static JSFunctionDef *js_parse_function_class_fields_init(JSParseState *s)
|
|
{
|
|
JSFunctionDef *fd;
|
|
|
|
fd = js_new_function_def(s->ctx, s->cur_func, FALSE, FALSE,
|
|
s->filename, 0, 0);
|
|
if (!fd)
|
|
return NULL;
|
|
fd->func_name = JS_ATOM_NULL;
|
|
fd->has_prototype = FALSE;
|
|
fd->has_home_object = TRUE;
|
|
|
|
fd->has_arguments_binding = FALSE;
|
|
fd->has_this_binding = TRUE;
|
|
fd->is_derived_class_constructor = FALSE;
|
|
fd->new_target_allowed = TRUE;
|
|
fd->super_call_allowed = FALSE;
|
|
fd->super_allowed = fd->has_home_object;
|
|
fd->arguments_allowed = FALSE;
|
|
|
|
fd->func_kind = JS_FUNC_NORMAL;
|
|
fd->func_type = JS_PARSE_FUNC_METHOD;
|
|
return fd;
|
|
}
|
|
|
|
/* func_name must be JS_ATOM_NULL for JS_PARSE_FUNC_STATEMENT and
|
|
JS_PARSE_FUNC_EXPR, JS_PARSE_FUNC_ARROW and JS_PARSE_FUNC_VAR */
|
|
static __exception int js_parse_function_decl2(JSParseState *s,
|
|
JSParseFunctionEnum func_type,
|
|
JSFunctionKindEnum func_kind,
|
|
JSAtom func_name,
|
|
const uint8_t *ptr,
|
|
int function_line_num,
|
|
int function_col_num,
|
|
JSParseExportEnum export_flag,
|
|
JSFunctionDef **pfd)
|
|
{
|
|
JSContext *ctx = s->ctx;
|
|
JSFunctionDef *fd = s->cur_func;
|
|
BOOL is_expr;
|
|
int func_idx, lexical_func_idx = -1;
|
|
BOOL has_opt_arg;
|
|
BOOL create_func_var = FALSE;
|
|
|
|
is_expr = (func_type != JS_PARSE_FUNC_STATEMENT &&
|
|
func_type != JS_PARSE_FUNC_VAR);
|
|
|
|
if (func_type == JS_PARSE_FUNC_STATEMENT ||
|
|
func_type == JS_PARSE_FUNC_VAR ||
|
|
func_type == JS_PARSE_FUNC_EXPR) {
|
|
if (func_kind == JS_FUNC_NORMAL &&
|
|
token_is_pseudo_keyword(s, JS_ATOM_async) &&
|
|
peek_token(s, TRUE) != '\n') {
|
|
if (next_token(s))
|
|
return -1;
|
|
func_kind = JS_FUNC_ASYNC;
|
|
}
|
|
if (next_token(s))
|
|
return -1;
|
|
if (s->token.val == '*') {
|
|
if (next_token(s))
|
|
return -1;
|
|
func_kind |= JS_FUNC_GENERATOR;
|
|
}
|
|
|
|
if (s->token.val == TOK_IDENT) {
|
|
if (s->token.u.ident.is_reserved ||
|
|
(s->token.u.ident.atom == JS_ATOM_yield &&
|
|
func_type == JS_PARSE_FUNC_EXPR &&
|
|
(func_kind & JS_FUNC_GENERATOR)) ||
|
|
(s->token.u.ident.atom == JS_ATOM_await &&
|
|
func_type == JS_PARSE_FUNC_EXPR &&
|
|
(func_kind & JS_FUNC_ASYNC)) ||
|
|
(s->token.u.ident.atom == JS_ATOM_await &&
|
|
func_type == JS_PARSE_FUNC_CLASS_STATIC_INIT)) {
|
|
return js_parse_error_reserved_identifier(s);
|
|
}
|
|
}
|
|
if (s->token.val == TOK_IDENT ||
|
|
(((s->token.val == TOK_YIELD && !(fd->js_mode & JS_MODE_STRICT)) ||
|
|
(s->token.val == TOK_AWAIT && !s->is_module)) &&
|
|
func_type == JS_PARSE_FUNC_EXPR)) {
|
|
func_name = JS_DupAtom(ctx, s->token.u.ident.atom);
|
|
if (next_token(s)) {
|
|
JS_FreeAtom(ctx, func_name);
|
|
return -1;
|
|
}
|
|
} else {
|
|
if (func_type != JS_PARSE_FUNC_EXPR &&
|
|
export_flag != JS_PARSE_EXPORT_DEFAULT) {
|
|
return js_parse_error(s, "function name expected");
|
|
}
|
|
}
|
|
} else if (func_type != JS_PARSE_FUNC_ARROW) {
|
|
func_name = JS_DupAtom(ctx, func_name);
|
|
}
|
|
|
|
if (fd->is_eval && fd->eval_type == JS_EVAL_TYPE_MODULE &&
|
|
(func_type == JS_PARSE_FUNC_STATEMENT || func_type == JS_PARSE_FUNC_VAR)) {
|
|
JSGlobalVar *hf;
|
|
hf = find_global_var(fd, func_name);
|
|
/* XXX: should check scope chain */
|
|
if (hf && hf->scope_level == fd->scope_level) {
|
|
js_parse_error(s, "invalid redefinition of global identifier in module code");
|
|
JS_FreeAtom(ctx, func_name);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (func_type == JS_PARSE_FUNC_VAR) {
|
|
if (!(fd->js_mode & JS_MODE_STRICT)
|
|
&& func_kind == JS_FUNC_NORMAL
|
|
&& find_lexical_decl(ctx, fd, func_name, fd->scope_first, FALSE) < 0
|
|
&& !((func_idx = find_var(ctx, fd, func_name)) >= 0 && (func_idx & ARGUMENT_VAR_OFFSET))
|
|
&& !(func_name == JS_ATOM_arguments && fd->has_arguments_binding)) {
|
|
create_func_var = TRUE;
|
|
}
|
|
/* Create the lexical name here so that the function closure
|
|
contains it */
|
|
if (fd->is_eval &&
|
|
(fd->eval_type == JS_EVAL_TYPE_GLOBAL ||
|
|
fd->eval_type == JS_EVAL_TYPE_MODULE) &&
|
|
fd->scope_level == fd->body_scope) {
|
|
/* avoid creating a lexical variable in the global
|
|
scope. XXX: check annex B */
|
|
JSGlobalVar *hf;
|
|
hf = find_global_var(fd, func_name);
|
|
/* XXX: should check scope chain */
|
|
if (hf && hf->scope_level == fd->scope_level) {
|
|
js_parse_error(s, "invalid redefinition of global identifier");
|
|
JS_FreeAtom(ctx, func_name);
|
|
return -1;
|
|
}
|
|
} else {
|
|
/* Always create a lexical name, fail if at the same scope as
|
|
existing name */
|
|
/* Lexical variable will be initialized upon entering scope */
|
|
lexical_func_idx = define_var(s, fd, func_name,
|
|
func_kind != JS_FUNC_NORMAL ?
|
|
JS_VAR_DEF_NEW_FUNCTION_DECL :
|
|
JS_VAR_DEF_FUNCTION_DECL);
|
|
if (lexical_func_idx < 0) {
|
|
JS_FreeAtom(ctx, func_name);
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
fd = js_new_function_def(ctx, fd, FALSE, is_expr, s->filename,
|
|
function_line_num, function_col_num);
|
|
if (!fd) {
|
|
JS_FreeAtom(ctx, func_name);
|
|
return -1;
|
|
}
|
|
if (pfd)
|
|
*pfd = fd;
|
|
s->cur_func = fd;
|
|
fd->func_name = func_name;
|
|
/* XXX: test !fd->is_generator is always false */
|
|
fd->has_prototype = (func_type == JS_PARSE_FUNC_STATEMENT ||
|
|
func_type == JS_PARSE_FUNC_VAR ||
|
|
func_type == JS_PARSE_FUNC_EXPR) &&
|
|
func_kind == JS_FUNC_NORMAL;
|
|
fd->has_home_object = (func_type == JS_PARSE_FUNC_METHOD ||
|
|
func_type == JS_PARSE_FUNC_GETTER ||
|
|
func_type == JS_PARSE_FUNC_SETTER ||
|
|
func_type == JS_PARSE_FUNC_CLASS_CONSTRUCTOR ||
|
|
func_type == JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR);
|
|
fd->has_arguments_binding = (func_type != JS_PARSE_FUNC_ARROW &&
|
|
func_type != JS_PARSE_FUNC_CLASS_STATIC_INIT);
|
|
fd->has_this_binding = fd->has_arguments_binding;
|
|
fd->is_derived_class_constructor = (func_type == JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR);
|
|
if (func_type == JS_PARSE_FUNC_ARROW) {
|
|
fd->new_target_allowed = fd->parent->new_target_allowed;
|
|
fd->super_call_allowed = fd->parent->super_call_allowed;
|
|
fd->super_allowed = fd->parent->super_allowed;
|
|
fd->arguments_allowed = fd->parent->arguments_allowed;
|
|
} else if (func_type == JS_PARSE_FUNC_CLASS_STATIC_INIT) {
|
|
fd->new_target_allowed = TRUE; // although new.target === undefined
|
|
fd->super_call_allowed = FALSE;
|
|
fd->super_allowed = TRUE;
|
|
fd->arguments_allowed = FALSE;
|
|
} else {
|
|
fd->new_target_allowed = TRUE;
|
|
fd->super_call_allowed = fd->is_derived_class_constructor;
|
|
fd->super_allowed = fd->has_home_object;
|
|
fd->arguments_allowed = TRUE;
|
|
}
|
|
|
|
/* fd->in_function_body == FALSE prevents yield/await during the parsing
|
|
of the arguments in generator/async functions. They are parsed as
|
|
regular identifiers for other function kinds. */
|
|
fd->func_kind = func_kind;
|
|
fd->func_type = func_type;
|
|
|
|
if (func_type == JS_PARSE_FUNC_CLASS_CONSTRUCTOR ||
|
|
func_type == JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR) {
|
|
/* error if not invoked as a constructor */
|
|
emit_op(s, OP_check_ctor);
|
|
}
|
|
|
|
if (func_type == JS_PARSE_FUNC_CLASS_CONSTRUCTOR) {
|
|
emit_class_field_init(s);
|
|
}
|
|
|
|
/* parse arguments */
|
|
fd->has_simple_parameter_list = TRUE;
|
|
fd->has_parameter_expressions = FALSE;
|
|
has_opt_arg = FALSE;
|
|
if (func_type == JS_PARSE_FUNC_ARROW && s->token.val == TOK_IDENT) {
|
|
JSAtom name;
|
|
if (s->token.u.ident.is_reserved) {
|
|
js_parse_error_reserved_identifier(s);
|
|
goto fail;
|
|
}
|
|
name = s->token.u.ident.atom;
|
|
if (add_arg(ctx, fd, name) < 0)
|
|
goto fail;
|
|
fd->defined_arg_count = 1;
|
|
} else if (func_type != JS_PARSE_FUNC_CLASS_STATIC_INIT) {
|
|
if (s->token.val == '(') {
|
|
int skip_bits;
|
|
/* if there is an '=' inside the parameter list, we
|
|
consider there is a parameter expression inside */
|
|
js_parse_skip_parens_token(s, &skip_bits, FALSE);
|
|
if (skip_bits & SKIP_HAS_ASSIGNMENT)
|
|
fd->has_parameter_expressions = TRUE;
|
|
if (next_token(s))
|
|
goto fail;
|
|
} else {
|
|
if (js_parse_expect(s, '('))
|
|
goto fail;
|
|
}
|
|
|
|
if (fd->has_parameter_expressions) {
|
|
fd->scope_level = -1; /* force no parent scope */
|
|
if (push_scope(s) < 0)
|
|
return -1;
|
|
}
|
|
|
|
while (s->token.val != ')') {
|
|
JSAtom name;
|
|
BOOL rest = FALSE;
|
|
int idx, has_initializer;
|
|
|
|
if (s->token.val == TOK_ELLIPSIS) {
|
|
fd->has_simple_parameter_list = FALSE;
|
|
rest = TRUE;
|
|
if (next_token(s))
|
|
goto fail;
|
|
}
|
|
if (s->token.val == '[' || s->token.val == '{') {
|
|
fd->has_simple_parameter_list = FALSE;
|
|
if (rest) {
|
|
emit_op(s, OP_rest);
|
|
emit_u16(s, fd->arg_count);
|
|
} else {
|
|
/* unnamed arg for destructuring */
|
|
idx = add_arg(ctx, fd, JS_ATOM_NULL);
|
|
emit_op(s, OP_get_arg);
|
|
emit_u16(s, idx);
|
|
}
|
|
has_initializer = js_parse_destructuring_element(s, fd->has_parameter_expressions ? TOK_LET : TOK_VAR, 1, TRUE, -1, TRUE);
|
|
if (has_initializer < 0)
|
|
goto fail;
|
|
if (has_initializer)
|
|
has_opt_arg = TRUE;
|
|
if (!has_opt_arg)
|
|
fd->defined_arg_count++;
|
|
} else if (s->token.val == TOK_IDENT) {
|
|
if (s->token.u.ident.is_reserved) {
|
|
js_parse_error_reserved_identifier(s);
|
|
goto fail;
|
|
}
|
|
name = s->token.u.ident.atom;
|
|
if (name == JS_ATOM_yield && fd->func_kind == JS_FUNC_GENERATOR) {
|
|
js_parse_error_reserved_identifier(s);
|
|
goto fail;
|
|
}
|
|
if (fd->has_parameter_expressions) {
|
|
if (js_parse_check_duplicate_parameter(s, name))
|
|
goto fail;
|
|
if (define_var(s, fd, name, JS_VAR_DEF_LET) < 0)
|
|
goto fail;
|
|
}
|
|
/* XXX: could avoid allocating an argument if rest is true */
|
|
idx = add_arg(ctx, fd, name);
|
|
if (idx < 0)
|
|
goto fail;
|
|
if (next_token(s))
|
|
goto fail;
|
|
if (rest) {
|
|
emit_op(s, OP_rest);
|
|
emit_u16(s, idx);
|
|
if (fd->has_parameter_expressions) {
|
|
emit_op(s, OP_dup);
|
|
emit_op(s, OP_scope_put_var_init);
|
|
emit_atom(s, name);
|
|
emit_u16(s, fd->scope_level);
|
|
}
|
|
emit_op(s, OP_put_arg);
|
|
emit_u16(s, idx);
|
|
fd->has_simple_parameter_list = FALSE;
|
|
has_opt_arg = TRUE;
|
|
} else if (s->token.val == '=') {
|
|
int label;
|
|
|
|
fd->has_simple_parameter_list = FALSE;
|
|
has_opt_arg = TRUE;
|
|
|
|
if (next_token(s))
|
|
goto fail;
|
|
|
|
label = new_label(s);
|
|
emit_op(s, OP_get_arg);
|
|
emit_u16(s, idx);
|
|
emit_op(s, OP_dup);
|
|
emit_op(s, OP_undefined);
|
|
emit_op(s, OP_strict_eq);
|
|
emit_goto(s, OP_if_false, label);
|
|
emit_op(s, OP_drop);
|
|
if (js_parse_assign_expr(s))
|
|
goto fail;
|
|
set_object_name(s, name);
|
|
emit_op(s, OP_dup);
|
|
emit_op(s, OP_put_arg);
|
|
emit_u16(s, idx);
|
|
emit_label(s, label);
|
|
emit_op(s, OP_scope_put_var_init);
|
|
emit_atom(s, name);
|
|
emit_u16(s, fd->scope_level);
|
|
} else {
|
|
if (!has_opt_arg) {
|
|
fd->defined_arg_count++;
|
|
}
|
|
if (fd->has_parameter_expressions) {
|
|
/* copy the argument to the argument scope */
|
|
emit_op(s, OP_get_arg);
|
|
emit_u16(s, idx);
|
|
emit_op(s, OP_scope_put_var_init);
|
|
emit_atom(s, name);
|
|
emit_u16(s, fd->scope_level);
|
|
}
|
|
}
|
|
} else {
|
|
js_parse_error(s, "missing formal parameter");
|
|
goto fail;
|
|
}
|
|
if (rest && s->token.val != ')') {
|
|
js_parse_expect(s, ')');
|
|
goto fail;
|
|
}
|
|
if (s->token.val == ')')
|
|
break;
|
|
if (js_parse_expect(s, ','))
|
|
goto fail;
|
|
}
|
|
if ((func_type == JS_PARSE_FUNC_GETTER && fd->arg_count != 0) ||
|
|
(func_type == JS_PARSE_FUNC_SETTER && fd->arg_count != 1)) {
|
|
js_parse_error(s, "invalid number of arguments for getter or setter");
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
if (fd->has_parameter_expressions) {
|
|
int idx;
|
|
|
|
/* Copy the variables in the argument scope to the variable
|
|
scope (see FunctionDeclarationInstantiation() in spec). The
|
|
normal arguments are already present, so no need to copy
|
|
them. */
|
|
idx = fd->scopes[fd->scope_level].first;
|
|
while (idx >= 0) {
|
|
JSVarDef *vd = &fd->vars[idx];
|
|
if (vd->scope_level != fd->scope_level)
|
|
break;
|
|
if (find_var(ctx, fd, vd->var_name) < 0) {
|
|
if (add_var(ctx, fd, vd->var_name) < 0)
|
|
goto fail;
|
|
vd = &fd->vars[idx]; /* fd->vars may have been reallocated */
|
|
emit_op(s, OP_scope_get_var);
|
|
emit_atom(s, vd->var_name);
|
|
emit_u16(s, fd->scope_level);
|
|
emit_op(s, OP_scope_put_var);
|
|
emit_atom(s, vd->var_name);
|
|
emit_u16(s, 0);
|
|
}
|
|
idx = vd->scope_next;
|
|
}
|
|
|
|
/* the argument scope has no parent, hence we don't use pop_scope(s) */
|
|
emit_op(s, OP_leave_scope);
|
|
emit_u16(s, fd->scope_level);
|
|
|
|
/* set the variable scope as the current scope */
|
|
fd->scope_level = 0;
|
|
fd->scope_first = fd->scopes[fd->scope_level].first;
|
|
}
|
|
|
|
if (next_token(s))
|
|
goto fail;
|
|
|
|
/* generator function: yield after the parameters are evaluated */
|
|
if (func_kind == JS_FUNC_GENERATOR ||
|
|
func_kind == JS_FUNC_ASYNC_GENERATOR)
|
|
emit_op(s, OP_initial_yield);
|
|
|
|
/* in generators, yield expression is forbidden during the parsing
|
|
of the arguments */
|
|
fd->in_function_body = TRUE;
|
|
push_scope(s); /* enter body scope */
|
|
fd->body_scope = fd->scope_level;
|
|
|
|
if (s->token.val == TOK_ARROW) {
|
|
if (next_token(s))
|
|
goto fail;
|
|
|
|
if (s->token.val != '{') {
|
|
if (js_parse_function_check_names(s, fd, func_name))
|
|
goto fail;
|
|
|
|
if (js_parse_assign_expr(s))
|
|
goto fail;
|
|
|
|
if (func_kind != JS_FUNC_NORMAL)
|
|
emit_op(s, OP_return_async);
|
|
else
|
|
emit_op(s, OP_return);
|
|
|
|
/* save the function source code */
|
|
/* the end of the function source code is after the last
|
|
token of the function source stored into s->last_ptr */
|
|
fd->source_len = s->last_ptr - ptr;
|
|
fd->source = js_strndup(ctx, (const char *)ptr, fd->source_len);
|
|
if (!fd->source)
|
|
goto fail;
|
|
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
// js_parse_class() already consumed the '{'
|
|
if (func_type != JS_PARSE_FUNC_CLASS_STATIC_INIT)
|
|
if (js_parse_expect(s, '{'))
|
|
goto fail;
|
|
|
|
if (js_parse_directives(s))
|
|
goto fail;
|
|
|
|
/* in strict_mode, check function and argument names */
|
|
if (js_parse_function_check_names(s, fd, func_name))
|
|
goto fail;
|
|
|
|
while (s->token.val != '}') {
|
|
if (js_parse_source_element(s))
|
|
goto fail;
|
|
}
|
|
|
|
/* save the function source code */
|
|
fd->source_len = s->buf_ptr - ptr;
|
|
fd->source = js_strndup(ctx, (const char *)ptr, fd->source_len);
|
|
if (!fd->source)
|
|
goto fail;
|
|
|
|
if (next_token(s)) {
|
|
/* consume the '}' */
|
|
goto fail;
|
|
}
|
|
|
|
/* in case there is no return, add one */
|
|
if (js_is_live_code(s)) {
|
|
emit_return(s, FALSE);
|
|
}
|
|
done:
|
|
s->cur_func = fd->parent;
|
|
|
|
/* create the function object */
|
|
{
|
|
int idx;
|
|
JSAtom func_name = fd->func_name;
|
|
|
|
/* the real object will be set at the end of the compilation */
|
|
idx = cpool_add(s, JS_NULL);
|
|
fd->parent_cpool_idx = idx;
|
|
|
|
if (is_expr) {
|
|
/* for constructors, no code needs to be generated here */
|
|
if (func_type != JS_PARSE_FUNC_CLASS_CONSTRUCTOR &&
|
|
func_type != JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR) {
|
|
/* OP_fclosure creates the function object from the bytecode
|
|
and adds the scope information */
|
|
emit_op(s, OP_fclosure);
|
|
emit_u32(s, idx);
|
|
if (func_name == JS_ATOM_NULL) {
|
|
emit_op(s, OP_set_name);
|
|
emit_u32(s, JS_ATOM_NULL);
|
|
}
|
|
}
|
|
} else if (func_type == JS_PARSE_FUNC_VAR) {
|
|
emit_op(s, OP_fclosure);
|
|
emit_u32(s, idx);
|
|
if (create_func_var) {
|
|
if (s->cur_func->is_global_var) {
|
|
JSGlobalVar *hf;
|
|
/* the global variable must be defined at the start of the
|
|
function */
|
|
hf = add_global_var(ctx, s->cur_func, func_name);
|
|
if (!hf)
|
|
goto fail;
|
|
/* it is considered as defined at the top level
|
|
(needed for annex B.3.3.4 and B.3.3.5
|
|
checks) */
|
|
hf->scope_level = 0;
|
|
hf->force_init = ((s->cur_func->js_mode & JS_MODE_STRICT) != 0);
|
|
/* store directly into global var, bypass lexical scope */
|
|
emit_op(s, OP_dup);
|
|
emit_op(s, OP_scope_put_var);
|
|
emit_atom(s, func_name);
|
|
emit_u16(s, 0);
|
|
} else {
|
|
/* do not call define_var to bypass lexical scope check */
|
|
func_idx = find_var(ctx, s->cur_func, func_name);
|
|
if (func_idx < 0) {
|
|
func_idx = add_var(ctx, s->cur_func, func_name);
|
|
if (func_idx < 0)
|
|
goto fail;
|
|
}
|
|
/* store directly into local var, bypass lexical catch scope */
|
|
emit_op(s, OP_dup);
|
|
emit_op(s, OP_scope_put_var);
|
|
emit_atom(s, func_name);
|
|
emit_u16(s, 0);
|
|
}
|
|
}
|
|
if (lexical_func_idx >= 0) {
|
|
/* lexical variable will be initialized upon entering scope */
|
|
s->cur_func->vars[lexical_func_idx].func_pool_idx = idx;
|
|
emit_op(s, OP_drop);
|
|
} else {
|
|
/* store function object into its lexical name */
|
|
/* XXX: could use OP_put_loc directly */
|
|
emit_op(s, OP_scope_put_var_init);
|
|
emit_atom(s, func_name);
|
|
emit_u16(s, s->cur_func->scope_level);
|
|
}
|
|
} else {
|
|
if (!s->cur_func->is_global_var) {
|
|
int var_idx = define_var(s, s->cur_func, func_name, JS_VAR_DEF_VAR);
|
|
|
|
if (var_idx < 0)
|
|
goto fail;
|
|
/* the variable will be assigned at the top of the function */
|
|
if (var_idx & ARGUMENT_VAR_OFFSET) {
|
|
s->cur_func->args[var_idx - ARGUMENT_VAR_OFFSET].func_pool_idx = idx;
|
|
} else {
|
|
s->cur_func->vars[var_idx].func_pool_idx = idx;
|
|
}
|
|
} else {
|
|
JSAtom func_var_name;
|
|
JSGlobalVar *hf;
|
|
if (func_name == JS_ATOM_NULL)
|
|
func_var_name = JS_ATOM__default_; /* export default */
|
|
else
|
|
func_var_name = func_name;
|
|
/* the variable will be assigned at the top of the function */
|
|
hf = add_global_var(ctx, s->cur_func, func_var_name);
|
|
if (!hf)
|
|
goto fail;
|
|
hf->cpool_idx = idx;
|
|
if (export_flag != JS_PARSE_EXPORT_NONE) {
|
|
if (!add_export_entry(s, s->cur_func->module, func_var_name,
|
|
export_flag == JS_PARSE_EXPORT_NAMED ? func_var_name : JS_ATOM_default, JS_EXPORT_TYPE_LOCAL))
|
|
goto fail;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
fail:
|
|
s->cur_func = fd->parent;
|
|
js_free_function_def(ctx, fd);
|
|
if (pfd)
|
|
*pfd = NULL;
|
|
return -1;
|
|
}
|
|
|
|
static __exception int js_parse_function_decl(JSParseState *s,
|
|
JSParseFunctionEnum func_type,
|
|
JSFunctionKindEnum func_kind,
|
|
JSAtom func_name,
|
|
const uint8_t *ptr,
|
|
int start_line,
|
|
int start_col)
|
|
{
|
|
return js_parse_function_decl2(s, func_type, func_kind, func_name, ptr,
|
|
start_line, start_col,
|
|
JS_PARSE_EXPORT_NONE, NULL);
|
|
}
|
|
|
|
static __exception int js_parse_program(JSParseState *s)
|
|
{
|
|
JSFunctionDef *fd = s->cur_func;
|
|
int idx;
|
|
|
|
if (next_token(s))
|
|
return -1;
|
|
|
|
if (js_parse_directives(s))
|
|
return -1;
|
|
|
|
fd->is_global_var = (fd->eval_type == JS_EVAL_TYPE_GLOBAL) ||
|
|
(fd->eval_type == JS_EVAL_TYPE_MODULE) ||
|
|
!(fd->js_mode & JS_MODE_STRICT);
|
|
|
|
if (!s->is_module) {
|
|
/* hidden variable for the return value */
|
|
fd->eval_ret_idx = idx = add_var(s->ctx, fd, JS_ATOM__ret_);
|
|
if (idx < 0)
|
|
return -1;
|
|
}
|
|
|
|
while (s->token.val != TOK_EOF) {
|
|
if (js_parse_source_element(s))
|
|
return -1;
|
|
}
|
|
|
|
if (!s->is_module) {
|
|
/* return the value of the hidden variable eval_ret_idx */
|
|
emit_op(s, OP_get_loc);
|
|
emit_u16(s, fd->eval_ret_idx);
|
|
|
|
emit_op(s, OP_return);
|
|
} else {
|
|
emit_return(s, FALSE);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void js_parse_init(JSContext *ctx, JSParseState *s,
|
|
const char *input, size_t input_len,
|
|
const char *filename)
|
|
{
|
|
memset(s, 0, sizeof(*s));
|
|
s->ctx = ctx;
|
|
s->filename = filename;
|
|
s->line_num = 1;
|
|
s->col_num = 1;
|
|
s->buf_start = s->buf_ptr = (const uint8_t *)input;
|
|
s->buf_end = s->buf_ptr + input_len;
|
|
s->mark = s->buf_ptr + min_int(1, input_len);
|
|
s->eol = s->buf_ptr;
|
|
s->token.val = ' ';
|
|
s->token.line_num = 1;
|
|
s->token.col_num = 1;
|
|
}
|
|
|
|
static JSValue JS_EvalFunctionInternal(JSContext *ctx, JSValue fun_obj,
|
|
JSValue this_obj,
|
|
JSVarRef **var_refs, JSStackFrame *sf)
|
|
{
|
|
JSValue ret_val;
|
|
uint32_t tag;
|
|
|
|
tag = JS_VALUE_GET_TAG(fun_obj);
|
|
if (tag == JS_TAG_FUNCTION_BYTECODE) {
|
|
fun_obj = js_closure(ctx, fun_obj, var_refs, sf);
|
|
ret_val = JS_CallFree(ctx, fun_obj, this_obj, 0, NULL);
|
|
} else if (tag == JS_TAG_MODULE) {
|
|
JSModuleDef *m;
|
|
m = JS_VALUE_GET_PTR(fun_obj);
|
|
/* the module refcount should be >= 2 */
|
|
JS_FreeValue(ctx, fun_obj);
|
|
if (js_create_module_function(ctx, m) < 0)
|
|
goto fail;
|
|
if (js_link_module(ctx, m) < 0)
|
|
goto fail;
|
|
ret_val = js_evaluate_module(ctx, m);
|
|
if (JS_IsException(ret_val)) {
|
|
fail:
|
|
js_free_modules(ctx, JS_FREE_MODULE_NOT_EVALUATED);
|
|
return JS_EXCEPTION;
|
|
}
|
|
} else {
|
|
JS_FreeValue(ctx, fun_obj);
|
|
ret_val = JS_ThrowTypeError(ctx, "bytecode function expected");
|
|
}
|
|
return ret_val;
|
|
}
|
|
|
|
JSValue JS_EvalFunction(JSContext *ctx, JSValue fun_obj)
|
|
{
|
|
return JS_EvalFunctionInternal(ctx, fun_obj, ctx->global_obj, NULL, NULL);
|
|
}
|
|
|
|
/* 'input' must be zero terminated i.e. input[input_len] = '\0'. */
|
|
/* `export_name` and `input` may be pure ASCII or UTF-8 encoded */
|
|
static JSValue __JS_EvalInternal(JSContext *ctx, JSValue this_obj,
|
|
const char *input, size_t input_len,
|
|
const char *filename, int flags, int scope_idx)
|
|
{
|
|
JSParseState s1, *s = &s1;
|
|
int err, js_mode, eval_type;
|
|
JSValue fun_obj, ret_val;
|
|
JSStackFrame *sf;
|
|
JSVarRef **var_refs;
|
|
JSFunctionBytecode *b;
|
|
JSFunctionDef *fd;
|
|
JSModuleDef *m;
|
|
|
|
js_parse_init(ctx, s, input, input_len, filename);
|
|
skip_shebang(&s->buf_ptr, s->buf_end);
|
|
|
|
eval_type = flags & JS_EVAL_TYPE_MASK;
|
|
m = NULL;
|
|
if (eval_type == JS_EVAL_TYPE_DIRECT) {
|
|
JSObject *p;
|
|
sf = ctx->rt->current_stack_frame;
|
|
assert(sf != NULL);
|
|
assert(JS_VALUE_GET_TAG(sf->cur_func) == JS_TAG_OBJECT);
|
|
p = JS_VALUE_GET_OBJ(sf->cur_func);
|
|
assert(js_class_has_bytecode(p->class_id));
|
|
b = p->u.func.function_bytecode;
|
|
var_refs = p->u.func.var_refs;
|
|
js_mode = b->js_mode;
|
|
} else {
|
|
sf = NULL;
|
|
b = NULL;
|
|
var_refs = NULL;
|
|
js_mode = 0;
|
|
if (flags & JS_EVAL_FLAG_STRICT)
|
|
js_mode |= JS_MODE_STRICT;
|
|
if (eval_type == JS_EVAL_TYPE_MODULE) {
|
|
JSAtom module_name = JS_NewAtom(ctx, filename);
|
|
if (module_name == JS_ATOM_NULL)
|
|
return JS_EXCEPTION;
|
|
m = js_new_module_def(ctx, module_name);
|
|
if (!m)
|
|
return JS_EXCEPTION;
|
|
js_mode |= JS_MODE_STRICT;
|
|
}
|
|
}
|
|
fd = js_new_function_def(ctx, NULL, TRUE, FALSE, filename, 1, 1);
|
|
if (!fd)
|
|
goto fail1;
|
|
if (m != NULL) {
|
|
fd->in_function_body = TRUE;
|
|
fd->func_kind = JS_FUNC_ASYNC;
|
|
}
|
|
s->cur_func = fd;
|
|
fd->eval_type = eval_type;
|
|
fd->has_this_binding = (eval_type != JS_EVAL_TYPE_DIRECT);
|
|
fd->backtrace_barrier = ((flags & JS_EVAL_FLAG_BACKTRACE_BARRIER) != 0);
|
|
if (eval_type == JS_EVAL_TYPE_DIRECT) {
|
|
fd->new_target_allowed = b->new_target_allowed;
|
|
fd->super_call_allowed = b->super_call_allowed;
|
|
fd->super_allowed = b->super_allowed;
|
|
fd->arguments_allowed = b->arguments_allowed;
|
|
} else {
|
|
fd->new_target_allowed = FALSE;
|
|
fd->super_call_allowed = FALSE;
|
|
fd->super_allowed = FALSE;
|
|
fd->arguments_allowed = TRUE;
|
|
}
|
|
fd->js_mode = js_mode;
|
|
fd->func_name = JS_DupAtom(ctx, JS_ATOM__eval_);
|
|
if (b) {
|
|
if (add_closure_variables(ctx, fd, b, scope_idx))
|
|
goto fail;
|
|
}
|
|
fd->module = m;
|
|
s->is_module = (m != NULL);
|
|
s->allow_html_comments = !s->is_module;
|
|
|
|
push_scope(s); /* body scope */
|
|
fd->body_scope = fd->scope_level;
|
|
|
|
err = js_parse_program(s);
|
|
if (err) {
|
|
fail:
|
|
free_token(s, &s->token);
|
|
js_free_function_def(ctx, fd);
|
|
goto fail1;
|
|
}
|
|
|
|
/* create the function object and all the enclosed functions */
|
|
fun_obj = js_create_function(ctx, fd);
|
|
if (JS_IsException(fun_obj))
|
|
goto fail1;
|
|
/* Could add a flag to avoid resolution if necessary */
|
|
if (m) {
|
|
m->func_obj = fun_obj;
|
|
if (js_resolve_module(ctx, m) < 0)
|
|
goto fail1;
|
|
fun_obj = js_dup(JS_MKPTR(JS_TAG_MODULE, m));
|
|
}
|
|
if (flags & JS_EVAL_FLAG_COMPILE_ONLY) {
|
|
ret_val = fun_obj;
|
|
} else {
|
|
ret_val = JS_EvalFunctionInternal(ctx, fun_obj, this_obj, var_refs, sf);
|
|
}
|
|
return ret_val;
|
|
fail1:
|
|
/* XXX: should free all the unresolved dependencies */
|
|
if (m)
|
|
js_free_module_def(ctx, m);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
/* the indirection is needed to make 'eval' optional */
|
|
static JSValue JS_EvalInternal(JSContext *ctx, JSValue this_obj,
|
|
const char *input, size_t input_len,
|
|
const char *filename, int flags, int scope_idx)
|
|
{
|
|
if (unlikely(!ctx->eval_internal)) {
|
|
return JS_ThrowTypeError(ctx, "eval is not supported");
|
|
}
|
|
return ctx->eval_internal(ctx, this_obj, input, input_len, filename,
|
|
flags, scope_idx);
|
|
}
|
|
|
|
static JSValue JS_EvalObject(JSContext *ctx, JSValue this_obj,
|
|
JSValue val, int flags, int scope_idx)
|
|
{
|
|
JSValue ret;
|
|
const char *str;
|
|
size_t len;
|
|
|
|
if (!JS_IsString(val))
|
|
return js_dup(val);
|
|
str = JS_ToCStringLen(ctx, &len, val);
|
|
if (!str)
|
|
return JS_EXCEPTION;
|
|
ret = JS_EvalInternal(ctx, this_obj, str, len, "<input>", flags, scope_idx);
|
|
JS_FreeCString(ctx, str);
|
|
return ret;
|
|
|
|
}
|
|
|
|
JSValue JS_EvalThis(JSContext *ctx, JSValue this_obj,
|
|
const char *input, size_t input_len,
|
|
const char *filename, int eval_flags)
|
|
{
|
|
int eval_type = eval_flags & JS_EVAL_TYPE_MASK;
|
|
JSValue ret;
|
|
|
|
assert(eval_type == JS_EVAL_TYPE_GLOBAL ||
|
|
eval_type == JS_EVAL_TYPE_MODULE);
|
|
ret = JS_EvalInternal(ctx, this_obj, input, input_len, filename,
|
|
eval_flags, -1);
|
|
return ret;
|
|
}
|
|
|
|
JSValue JS_Eval(JSContext *ctx, const char *input, size_t input_len,
|
|
const char *filename, int eval_flags)
|
|
{
|
|
return JS_EvalThis(ctx, ctx->global_obj, input, input_len, filename,
|
|
eval_flags);
|
|
}
|
|
|
|
int JS_ResolveModule(JSContext *ctx, JSValue obj)
|
|
{
|
|
if (JS_VALUE_GET_TAG(obj) == JS_TAG_MODULE) {
|
|
JSModuleDef *m = JS_VALUE_GET_PTR(obj);
|
|
if (js_resolve_module(ctx, m) < 0) {
|
|
js_free_modules(ctx, JS_FREE_MODULE_NOT_RESOLVED);
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*******************************************************************/
|
|
/* object list */
|
|
|
|
typedef struct {
|
|
JSObject *obj;
|
|
uint32_t hash_next; /* -1 if no next entry */
|
|
} JSObjectListEntry;
|
|
|
|
/* XXX: reuse it to optimize weak references */
|
|
typedef struct {
|
|
JSObjectListEntry *object_tab;
|
|
int object_count;
|
|
int object_size;
|
|
uint32_t *hash_table;
|
|
uint32_t hash_size;
|
|
} JSObjectList;
|
|
|
|
static void js_object_list_init(JSObjectList *s)
|
|
{
|
|
memset(s, 0, sizeof(*s));
|
|
}
|
|
|
|
static uint32_t js_object_list_get_hash(JSObject *p, uint32_t hash_size)
|
|
{
|
|
return ((uintptr_t)p * 3163) & (hash_size - 1);
|
|
}
|
|
|
|
static int js_object_list_resize_hash(JSContext *ctx, JSObjectList *s,
|
|
uint32_t new_hash_size)
|
|
{
|
|
JSObjectListEntry *e;
|
|
uint32_t i, h, *new_hash_table;
|
|
|
|
new_hash_table = js_malloc(ctx, sizeof(new_hash_table[0]) * new_hash_size);
|
|
if (!new_hash_table)
|
|
return -1;
|
|
js_free(ctx, s->hash_table);
|
|
s->hash_table = new_hash_table;
|
|
s->hash_size = new_hash_size;
|
|
|
|
for(i = 0; i < s->hash_size; i++) {
|
|
s->hash_table[i] = -1;
|
|
}
|
|
for(i = 0; i < s->object_count; i++) {
|
|
e = &s->object_tab[i];
|
|
h = js_object_list_get_hash(e->obj, s->hash_size);
|
|
e->hash_next = s->hash_table[h];
|
|
s->hash_table[h] = i;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* the reference count of 'obj' is not modified. Return 0 if OK, -1 if
|
|
memory error */
|
|
static int js_object_list_add(JSContext *ctx, JSObjectList *s, JSObject *obj)
|
|
{
|
|
JSObjectListEntry *e;
|
|
uint32_t h, new_hash_size;
|
|
|
|
if (js_resize_array(ctx, (void *)&s->object_tab,
|
|
sizeof(s->object_tab[0]),
|
|
&s->object_size, s->object_count + 1))
|
|
return -1;
|
|
if (unlikely((s->object_count + 1) >= s->hash_size)) {
|
|
new_hash_size = max_uint32(s->hash_size, 4);
|
|
while (new_hash_size <= s->object_count)
|
|
new_hash_size *= 2;
|
|
if (js_object_list_resize_hash(ctx, s, new_hash_size))
|
|
return -1;
|
|
}
|
|
e = &s->object_tab[s->object_count++];
|
|
h = js_object_list_get_hash(obj, s->hash_size);
|
|
e->obj = obj;
|
|
e->hash_next = s->hash_table[h];
|
|
s->hash_table[h] = s->object_count - 1;
|
|
return 0;
|
|
}
|
|
|
|
/* return -1 if not present or the object index */
|
|
static int js_object_list_find(JSContext *ctx, JSObjectList *s, JSObject *obj)
|
|
{
|
|
JSObjectListEntry *e;
|
|
uint32_t h, p;
|
|
|
|
/* must test empty size because there is no hash table */
|
|
if (s->object_count == 0)
|
|
return -1;
|
|
h = js_object_list_get_hash(obj, s->hash_size);
|
|
p = s->hash_table[h];
|
|
while (p != -1) {
|
|
e = &s->object_tab[p];
|
|
if (e->obj == obj)
|
|
return p;
|
|
p = e->hash_next;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static void js_object_list_end(JSContext *ctx, JSObjectList *s)
|
|
{
|
|
js_free(ctx, s->object_tab);
|
|
js_free(ctx, s->hash_table);
|
|
}
|
|
|
|
/*******************************************************************/
|
|
/* binary object writer & reader */
|
|
|
|
typedef enum BCTagEnum {
|
|
BC_TAG_NULL = 1,
|
|
BC_TAG_UNDEFINED,
|
|
BC_TAG_BOOL_FALSE,
|
|
BC_TAG_BOOL_TRUE,
|
|
BC_TAG_INT32,
|
|
BC_TAG_FLOAT64,
|
|
BC_TAG_STRING,
|
|
BC_TAG_OBJECT,
|
|
BC_TAG_ARRAY,
|
|
BC_TAG_BIG_INT,
|
|
BC_TAG_TEMPLATE_OBJECT,
|
|
BC_TAG_FUNCTION_BYTECODE,
|
|
BC_TAG_MODULE,
|
|
BC_TAG_TYPED_ARRAY,
|
|
BC_TAG_ARRAY_BUFFER,
|
|
BC_TAG_SHARED_ARRAY_BUFFER,
|
|
BC_TAG_REGEXP,
|
|
BC_TAG_DATE,
|
|
BC_TAG_OBJECT_VALUE,
|
|
BC_TAG_OBJECT_REFERENCE,
|
|
} BCTagEnum;
|
|
|
|
#define BC_VERSION 12
|
|
|
|
typedef struct BCWriterState {
|
|
JSContext *ctx;
|
|
DynBuf dbuf;
|
|
BOOL allow_bytecode : 8;
|
|
BOOL allow_sab : 8;
|
|
BOOL allow_reference : 8;
|
|
BOOL allow_source : 1;
|
|
BOOL allow_debug : 1;
|
|
uint32_t first_atom;
|
|
uint32_t *atom_to_idx;
|
|
int atom_to_idx_size;
|
|
JSAtom *idx_to_atom;
|
|
int idx_to_atom_count;
|
|
int idx_to_atom_size;
|
|
uint8_t **sab_tab;
|
|
int sab_tab_len;
|
|
int sab_tab_size;
|
|
/* list of referenced objects (used if allow_reference = TRUE) */
|
|
JSObjectList object_list;
|
|
} BCWriterState;
|
|
|
|
#ifdef DUMP_READ_OBJECT
|
|
static const char * const bc_tag_str[] = {
|
|
"invalid",
|
|
"null",
|
|
"undefined",
|
|
"false",
|
|
"true",
|
|
"int32",
|
|
"float64",
|
|
"string",
|
|
"object",
|
|
"array",
|
|
"BigInt",
|
|
"template",
|
|
"function",
|
|
"module",
|
|
"TypedArray",
|
|
"ArrayBuffer",
|
|
"SharedArrayBuffer",
|
|
"RegExp",
|
|
"Date",
|
|
"ObjectValue",
|
|
"ObjectReference",
|
|
};
|
|
#endif
|
|
|
|
static void bc_put_u8(BCWriterState *s, uint8_t v)
|
|
{
|
|
dbuf_putc(&s->dbuf, v);
|
|
}
|
|
|
|
static void bc_put_u16(BCWriterState *s, uint16_t v)
|
|
{
|
|
if (is_be())
|
|
v = bswap16(v);
|
|
dbuf_put_u16(&s->dbuf, v);
|
|
}
|
|
|
|
static __maybe_unused void bc_put_u32(BCWriterState *s, uint32_t v)
|
|
{
|
|
if (is_be())
|
|
v = bswap32(v);
|
|
dbuf_put_u32(&s->dbuf, v);
|
|
}
|
|
|
|
static void bc_put_u64(BCWriterState *s, uint64_t v)
|
|
{
|
|
if (is_be())
|
|
v = bswap64(v);
|
|
dbuf_put(&s->dbuf, (uint8_t *)&v, sizeof(v));
|
|
}
|
|
|
|
static void bc_put_leb128(BCWriterState *s, uint32_t v)
|
|
{
|
|
dbuf_put_leb128(&s->dbuf, v);
|
|
}
|
|
|
|
static void bc_put_sleb128(BCWriterState *s, int32_t v)
|
|
{
|
|
dbuf_put_sleb128(&s->dbuf, v);
|
|
}
|
|
|
|
static void bc_set_flags(uint32_t *pflags, int *pidx, uint32_t val, int n)
|
|
{
|
|
*pflags = *pflags | (val << *pidx);
|
|
*pidx += n;
|
|
}
|
|
|
|
static int bc_atom_to_idx(BCWriterState *s, uint32_t *pres, JSAtom atom)
|
|
{
|
|
uint32_t v;
|
|
|
|
if (atom < s->first_atom || __JS_AtomIsTaggedInt(atom)) {
|
|
*pres = atom;
|
|
return 0;
|
|
}
|
|
atom -= s->first_atom;
|
|
if (atom < s->atom_to_idx_size && s->atom_to_idx[atom] != 0) {
|
|
*pres = s->atom_to_idx[atom];
|
|
return 0;
|
|
}
|
|
if (atom >= s->atom_to_idx_size) {
|
|
int old_size, i;
|
|
old_size = s->atom_to_idx_size;
|
|
if (js_resize_array(s->ctx, (void **)&s->atom_to_idx,
|
|
sizeof(s->atom_to_idx[0]), &s->atom_to_idx_size,
|
|
atom + 1))
|
|
return -1;
|
|
/* XXX: could add a specific js_resize_array() function to do it */
|
|
for(i = old_size; i < s->atom_to_idx_size; i++)
|
|
s->atom_to_idx[i] = 0;
|
|
}
|
|
if (js_resize_array(s->ctx, (void **)&s->idx_to_atom,
|
|
sizeof(s->idx_to_atom[0]),
|
|
&s->idx_to_atom_size, s->idx_to_atom_count + 1))
|
|
goto fail;
|
|
|
|
v = s->idx_to_atom_count++;
|
|
s->idx_to_atom[v] = atom + s->first_atom;
|
|
v += s->first_atom;
|
|
s->atom_to_idx[atom] = v;
|
|
*pres = v;
|
|
return 0;
|
|
fail:
|
|
*pres = 0;
|
|
return -1;
|
|
}
|
|
|
|
static int bc_put_atom(BCWriterState *s, JSAtom atom)
|
|
{
|
|
uint32_t v;
|
|
|
|
if (__JS_AtomIsTaggedInt(atom)) {
|
|
v = (__JS_AtomToUInt32(atom) << 1) | 1;
|
|
} else {
|
|
if (bc_atom_to_idx(s, &v, atom))
|
|
return -1;
|
|
v <<= 1;
|
|
}
|
|
bc_put_leb128(s, v);
|
|
return 0;
|
|
}
|
|
|
|
static void bc_byte_swap(uint8_t *bc_buf, int bc_len)
|
|
{
|
|
int pos, len, op, fmt;
|
|
|
|
pos = 0;
|
|
while (pos < bc_len) {
|
|
op = bc_buf[pos];
|
|
len = short_opcode_info(op).size;
|
|
fmt = short_opcode_info(op).fmt;
|
|
switch(fmt) {
|
|
case OP_FMT_u16:
|
|
case OP_FMT_i16:
|
|
case OP_FMT_label16:
|
|
case OP_FMT_npop:
|
|
case OP_FMT_loc:
|
|
case OP_FMT_arg:
|
|
case OP_FMT_var_ref:
|
|
put_u16(bc_buf + pos + 1,
|
|
bswap16(get_u16(bc_buf + pos + 1)));
|
|
break;
|
|
case OP_FMT_i32:
|
|
case OP_FMT_u32:
|
|
case OP_FMT_const:
|
|
case OP_FMT_label:
|
|
case OP_FMT_atom:
|
|
case OP_FMT_atom_u8:
|
|
put_u32(bc_buf + pos + 1,
|
|
bswap32(get_u32(bc_buf + pos + 1)));
|
|
break;
|
|
case OP_FMT_atom_u16:
|
|
case OP_FMT_label_u16:
|
|
put_u32(bc_buf + pos + 1,
|
|
bswap32(get_u32(bc_buf + pos + 1)));
|
|
put_u16(bc_buf + pos + 1 + 4,
|
|
bswap16(get_u16(bc_buf + pos + 1 + 4)));
|
|
break;
|
|
case OP_FMT_atom_label_u8:
|
|
case OP_FMT_atom_label_u16:
|
|
put_u32(bc_buf + pos + 1,
|
|
bswap32(get_u32(bc_buf + pos + 1)));
|
|
put_u32(bc_buf + pos + 1 + 4,
|
|
bswap32(get_u32(bc_buf + pos + 1 + 4)));
|
|
if (fmt == OP_FMT_atom_label_u16) {
|
|
put_u16(bc_buf + pos + 1 + 4 + 4,
|
|
bswap16(get_u16(bc_buf + pos + 1 + 4 + 4)));
|
|
}
|
|
break;
|
|
case OP_FMT_npop_u16:
|
|
put_u16(bc_buf + pos + 1,
|
|
bswap16(get_u16(bc_buf + pos + 1)));
|
|
put_u16(bc_buf + pos + 1 + 2,
|
|
bswap16(get_u16(bc_buf + pos + 1 + 2)));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
pos += len;
|
|
}
|
|
}
|
|
|
|
static BOOL is_ic_op(uint8_t op)
|
|
{
|
|
return op >= OP_get_field_ic && op <= OP_put_field_ic;
|
|
}
|
|
|
|
static int JS_WriteFunctionBytecode(BCWriterState *s,
|
|
const JSFunctionBytecode *b)
|
|
{
|
|
int pos, len, bc_len, op;
|
|
JSAtom atom;
|
|
uint8_t *bc_buf;
|
|
uint32_t val;
|
|
|
|
bc_len = b->byte_code_len;
|
|
bc_buf = js_malloc(s->ctx, bc_len);
|
|
if (!bc_buf)
|
|
return -1;
|
|
memcpy(bc_buf, b->byte_code_buf, bc_len);
|
|
|
|
pos = 0;
|
|
while (pos < bc_len) {
|
|
op = bc_buf[pos];
|
|
len = short_opcode_info(op).size;
|
|
switch(short_opcode_info(op).fmt) {
|
|
case OP_FMT_atom:
|
|
case OP_FMT_atom_u8:
|
|
case OP_FMT_atom_u16:
|
|
case OP_FMT_atom_label_u8:
|
|
case OP_FMT_atom_label_u16:
|
|
atom = get_u32(bc_buf + pos + 1);
|
|
if (bc_atom_to_idx(s, &val, atom))
|
|
goto fail;
|
|
put_u32(bc_buf + pos + 1, val);
|
|
break;
|
|
default:
|
|
// IC (inline cache) opcodes should not end up in the serialized
|
|
// bytecode; translate them to their non-IC counterparts here
|
|
if (is_ic_op(op)) {
|
|
val = get_u32(bc_buf + pos + 1);
|
|
atom = get_ic_atom(b->ic, val);
|
|
if (bc_atom_to_idx(s, &val, atom))
|
|
goto fail;
|
|
put_u32(bc_buf + pos + 1, val);
|
|
bc_buf[pos] -= (OP_get_field_ic - OP_get_field);
|
|
}
|
|
break;
|
|
}
|
|
pos += len;
|
|
}
|
|
|
|
if (is_be())
|
|
bc_byte_swap(bc_buf, bc_len);
|
|
|
|
dbuf_put(&s->dbuf, bc_buf, bc_len);
|
|
|
|
js_free(s->ctx, bc_buf);
|
|
return 0;
|
|
fail:
|
|
js_free(s->ctx, bc_buf);
|
|
return -1;
|
|
}
|
|
|
|
static void JS_WriteString(BCWriterState *s, JSString *p)
|
|
{
|
|
int i;
|
|
bc_put_leb128(s, ((uint32_t)p->len << 1) | p->is_wide_char);
|
|
if (p->is_wide_char) {
|
|
for(i = 0; i < p->len; i++)
|
|
bc_put_u16(s, p->u.str16[i]);
|
|
} else {
|
|
dbuf_put(&s->dbuf, p->u.str8, p->len);
|
|
}
|
|
}
|
|
|
|
static int JS_WriteBigInt(BCWriterState *s, JSValue obj)
|
|
{
|
|
uint32_t tag, tag1;
|
|
int64_t e;
|
|
JSBigInt *bf = JS_VALUE_GET_PTR(obj);
|
|
bf_t *a = &bf->num;
|
|
size_t len, i, n1, j;
|
|
limb_t v;
|
|
|
|
tag = JS_VALUE_GET_TAG(obj);
|
|
switch(tag) {
|
|
case JS_TAG_BIG_INT:
|
|
tag1 = BC_TAG_BIG_INT;
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
bc_put_u8(s, tag1);
|
|
|
|
/* sign + exponent */
|
|
if (a->expn == BF_EXP_ZERO)
|
|
e = 0;
|
|
else if (a->expn == BF_EXP_INF)
|
|
e = 1;
|
|
else if (a->expn == BF_EXP_NAN)
|
|
e = 2;
|
|
else if (a->expn >= 0)
|
|
e = a->expn + 3;
|
|
else
|
|
e = a->expn;
|
|
e = (e * 2) | a->sign;
|
|
if (e < INT32_MIN || e > INT32_MAX) {
|
|
JS_ThrowRangeError(s->ctx, "maximum BigInt size exceeded");
|
|
return -1;
|
|
}
|
|
bc_put_sleb128(s, e);
|
|
|
|
/* mantissa */
|
|
if (a->len != 0) {
|
|
i = 0;
|
|
while (i < a->len && a->tab[i] == 0)
|
|
i++;
|
|
assert(i < a->len);
|
|
v = a->tab[i];
|
|
n1 = sizeof(limb_t);
|
|
while ((v & 0xff) == 0) {
|
|
n1--;
|
|
v >>= 8;
|
|
}
|
|
i++;
|
|
len = (a->len - i) * sizeof(limb_t) + n1;
|
|
if (len > INT32_MAX) {
|
|
JS_ThrowRangeError(s->ctx, "maximum BigInt size exceeded");
|
|
return -1;
|
|
}
|
|
bc_put_leb128(s, len);
|
|
/* always saved in byte based little endian representation */
|
|
for(j = 0; j < n1; j++) {
|
|
bc_put_u8(s, v >> (j * 8));
|
|
}
|
|
for(; i < a->len; i++) {
|
|
limb_t v = a->tab[i];
|
|
#if LIMB_BITS == 32
|
|
bc_put_u32(s, v);
|
|
#else
|
|
bc_put_u64(s, v);
|
|
#endif
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int JS_WriteObjectRec(BCWriterState *s, JSValue obj);
|
|
|
|
static int JS_WriteFunctionTag(BCWriterState *s, JSValue obj)
|
|
{
|
|
JSFunctionBytecode *b = JS_VALUE_GET_PTR(obj);
|
|
uint32_t flags;
|
|
int idx, i;
|
|
|
|
bc_put_u8(s, BC_TAG_FUNCTION_BYTECODE);
|
|
flags = idx = 0;
|
|
bc_set_flags(&flags, &idx, b->has_prototype, 1);
|
|
bc_set_flags(&flags, &idx, b->has_simple_parameter_list, 1);
|
|
bc_set_flags(&flags, &idx, b->is_derived_class_constructor, 1);
|
|
bc_set_flags(&flags, &idx, b->need_home_object, 1);
|
|
bc_set_flags(&flags, &idx, b->func_kind, 2);
|
|
bc_set_flags(&flags, &idx, b->new_target_allowed, 1);
|
|
bc_set_flags(&flags, &idx, b->super_call_allowed, 1);
|
|
bc_set_flags(&flags, &idx, b->super_allowed, 1);
|
|
bc_set_flags(&flags, &idx, b->arguments_allowed, 1);
|
|
bc_set_flags(&flags, &idx, b->backtrace_barrier, 1);
|
|
bc_set_flags(&flags, &idx, s->allow_debug, 1);
|
|
assert(idx <= 16);
|
|
bc_put_u16(s, flags);
|
|
bc_put_u8(s, b->js_mode);
|
|
bc_put_atom(s, b->func_name);
|
|
|
|
bc_put_leb128(s, b->arg_count);
|
|
bc_put_leb128(s, b->var_count);
|
|
bc_put_leb128(s, b->defined_arg_count);
|
|
bc_put_leb128(s, b->stack_size);
|
|
bc_put_leb128(s, b->closure_var_count);
|
|
bc_put_leb128(s, b->cpool_count);
|
|
bc_put_leb128(s, b->byte_code_len);
|
|
if (b->vardefs) {
|
|
/* XXX: this field is redundant */
|
|
bc_put_leb128(s, b->arg_count + b->var_count);
|
|
for(i = 0; i < b->arg_count + b->var_count; i++) {
|
|
JSVarDef *vd = &b->vardefs[i];
|
|
bc_put_atom(s, vd->var_name);
|
|
bc_put_leb128(s, vd->scope_level);
|
|
bc_put_leb128(s, vd->scope_next + 1);
|
|
flags = idx = 0;
|
|
bc_set_flags(&flags, &idx, vd->var_kind, 4);
|
|
bc_set_flags(&flags, &idx, vd->is_const, 1);
|
|
bc_set_flags(&flags, &idx, vd->is_lexical, 1);
|
|
bc_set_flags(&flags, &idx, vd->is_captured, 1);
|
|
assert(idx <= 8);
|
|
bc_put_u8(s, flags);
|
|
}
|
|
} else {
|
|
bc_put_leb128(s, 0);
|
|
}
|
|
|
|
for(i = 0; i < b->closure_var_count; i++) {
|
|
JSClosureVar *cv = &b->closure_var[i];
|
|
bc_put_atom(s, cv->var_name);
|
|
bc_put_leb128(s, cv->var_idx);
|
|
flags = idx = 0;
|
|
bc_set_flags(&flags, &idx, cv->is_local, 1);
|
|
bc_set_flags(&flags, &idx, cv->is_arg, 1);
|
|
bc_set_flags(&flags, &idx, cv->is_const, 1);
|
|
bc_set_flags(&flags, &idx, cv->is_lexical, 1);
|
|
bc_set_flags(&flags, &idx, cv->var_kind, 4);
|
|
assert(idx <= 8);
|
|
bc_put_u8(s, flags);
|
|
}
|
|
|
|
// write constant pool before code so code can be disassembled
|
|
// on the fly at read time
|
|
for(i = 0; i < b->cpool_count; i++) {
|
|
if (JS_WriteObjectRec(s, b->cpool[i]))
|
|
goto fail;
|
|
}
|
|
|
|
if (JS_WriteFunctionBytecode(s, b))
|
|
goto fail;
|
|
|
|
if (s->allow_debug) {
|
|
bc_put_atom(s, b->filename);
|
|
bc_put_leb128(s, b->line_num);
|
|
bc_put_leb128(s, b->col_num);
|
|
bc_put_leb128(s, b->pc2line_len);
|
|
dbuf_put(&s->dbuf, b->pc2line_buf, b->pc2line_len);
|
|
if (s->allow_source && b->source) {
|
|
bc_put_leb128(s, b->source_len);
|
|
dbuf_put(&s->dbuf, b->source, b->source_len);
|
|
} else {
|
|
bc_put_leb128(s, 0);
|
|
}
|
|
}
|
|
return 0;
|
|
fail:
|
|
return -1;
|
|
}
|
|
|
|
static int JS_WriteModule(BCWriterState *s, JSValue obj)
|
|
{
|
|
JSModuleDef *m = JS_VALUE_GET_PTR(obj);
|
|
int i;
|
|
|
|
bc_put_u8(s, BC_TAG_MODULE);
|
|
bc_put_atom(s, m->module_name);
|
|
|
|
bc_put_leb128(s, m->req_module_entries_count);
|
|
for(i = 0; i < m->req_module_entries_count; i++) {
|
|
JSReqModuleEntry *rme = &m->req_module_entries[i];
|
|
bc_put_atom(s, rme->module_name);
|
|
}
|
|
|
|
bc_put_leb128(s, m->export_entries_count);
|
|
for(i = 0; i < m->export_entries_count; i++) {
|
|
JSExportEntry *me = &m->export_entries[i];
|
|
bc_put_u8(s, me->export_type);
|
|
if (me->export_type == JS_EXPORT_TYPE_LOCAL) {
|
|
bc_put_leb128(s, me->u.local.var_idx);
|
|
} else {
|
|
bc_put_leb128(s, me->u.req_module_idx);
|
|
bc_put_atom(s, me->local_name);
|
|
}
|
|
bc_put_atom(s, me->export_name);
|
|
}
|
|
|
|
bc_put_leb128(s, m->star_export_entries_count);
|
|
for(i = 0; i < m->star_export_entries_count; i++) {
|
|
JSStarExportEntry *se = &m->star_export_entries[i];
|
|
bc_put_leb128(s, se->req_module_idx);
|
|
}
|
|
|
|
bc_put_leb128(s, m->import_entries_count);
|
|
for(i = 0; i < m->import_entries_count; i++) {
|
|
JSImportEntry *mi = &m->import_entries[i];
|
|
bc_put_leb128(s, mi->var_idx);
|
|
bc_put_atom(s, mi->import_name);
|
|
bc_put_leb128(s, mi->req_module_idx);
|
|
}
|
|
|
|
if (JS_WriteObjectRec(s, m->func_obj))
|
|
goto fail;
|
|
return 0;
|
|
fail:
|
|
return -1;
|
|
}
|
|
|
|
static int JS_WriteArray(BCWriterState *s, JSValue obj)
|
|
{
|
|
JSObject *p = JS_VALUE_GET_OBJ(obj);
|
|
uint32_t i, len;
|
|
JSValue val;
|
|
int ret;
|
|
BOOL is_template;
|
|
|
|
if (s->allow_bytecode && !p->extensible) {
|
|
/* not extensible array: we consider it is a
|
|
template when we are saving bytecode */
|
|
bc_put_u8(s, BC_TAG_TEMPLATE_OBJECT);
|
|
is_template = TRUE;
|
|
} else {
|
|
bc_put_u8(s, BC_TAG_ARRAY);
|
|
is_template = FALSE;
|
|
}
|
|
if (js_get_length32(s->ctx, &len, obj))
|
|
goto fail1;
|
|
bc_put_leb128(s, len);
|
|
for(i = 0; i < len; i++) {
|
|
val = JS_GetPropertyUint32(s->ctx, obj, i);
|
|
if (JS_IsException(val))
|
|
goto fail1;
|
|
ret = JS_WriteObjectRec(s, val);
|
|
JS_FreeValue(s->ctx, val);
|
|
if (ret)
|
|
goto fail1;
|
|
}
|
|
if (is_template) {
|
|
val = JS_GetProperty(s->ctx, obj, JS_ATOM_raw);
|
|
if (JS_IsException(val))
|
|
goto fail1;
|
|
ret = JS_WriteObjectRec(s, val);
|
|
JS_FreeValue(s->ctx, val);
|
|
if (ret)
|
|
goto fail1;
|
|
}
|
|
return 0;
|
|
fail1:
|
|
return -1;
|
|
}
|
|
|
|
static int JS_WriteObjectTag(BCWriterState *s, JSValue obj)
|
|
{
|
|
JSObject *p = JS_VALUE_GET_OBJ(obj);
|
|
uint32_t i, prop_count;
|
|
JSShape *sh;
|
|
JSShapeProperty *pr;
|
|
int pass;
|
|
JSAtom atom;
|
|
|
|
bc_put_u8(s, BC_TAG_OBJECT);
|
|
prop_count = 0;
|
|
sh = p->shape;
|
|
for(pass = 0; pass < 2; pass++) {
|
|
if (pass == 1)
|
|
bc_put_leb128(s, prop_count);
|
|
for(i = 0, pr = get_shape_prop(sh); i < sh->prop_count; i++, pr++) {
|
|
atom = pr->atom;
|
|
if (atom != JS_ATOM_NULL &&
|
|
JS_AtomIsString(s->ctx, atom) &&
|
|
(pr->flags & JS_PROP_ENUMERABLE)) {
|
|
if (pr->flags & JS_PROP_TMASK) {
|
|
JS_ThrowTypeError(s->ctx, "only value properties are supported");
|
|
goto fail;
|
|
}
|
|
if (pass == 0) {
|
|
prop_count++;
|
|
} else {
|
|
bc_put_atom(s, atom);
|
|
if (JS_WriteObjectRec(s, p->prop[i].u.value))
|
|
goto fail;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
fail:
|
|
return -1;
|
|
}
|
|
|
|
static int JS_WriteTypedArray(BCWriterState *s, JSValue obj)
|
|
{
|
|
JSObject *p = JS_VALUE_GET_OBJ(obj);
|
|
JSTypedArray *ta = p->u.typed_array;
|
|
|
|
bc_put_u8(s, BC_TAG_TYPED_ARRAY);
|
|
bc_put_u8(s, p->class_id - JS_CLASS_UINT8C_ARRAY);
|
|
bc_put_leb128(s, p->u.array.count);
|
|
bc_put_leb128(s, ta->offset);
|
|
if (JS_WriteObjectRec(s, JS_MKPTR(JS_TAG_OBJECT, ta->buffer)))
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
static int JS_WriteArrayBuffer(BCWriterState *s, JSValue obj)
|
|
{
|
|
JSObject *p = JS_VALUE_GET_OBJ(obj);
|
|
JSArrayBuffer *abuf = p->u.array_buffer;
|
|
if (abuf->detached) {
|
|
JS_ThrowTypeErrorDetachedArrayBuffer(s->ctx);
|
|
return -1;
|
|
}
|
|
bc_put_u8(s, BC_TAG_ARRAY_BUFFER);
|
|
bc_put_leb128(s, abuf->byte_length);
|
|
dbuf_put(&s->dbuf, abuf->data, abuf->byte_length);
|
|
return 0;
|
|
}
|
|
|
|
static int JS_WriteSharedArrayBuffer(BCWriterState *s, JSValue obj)
|
|
{
|
|
JSObject *p = JS_VALUE_GET_OBJ(obj);
|
|
JSArrayBuffer *abuf = p->u.array_buffer;
|
|
assert(!abuf->detached); /* SharedArrayBuffer are never detached */
|
|
bc_put_u8(s, BC_TAG_SHARED_ARRAY_BUFFER);
|
|
bc_put_leb128(s, abuf->byte_length);
|
|
bc_put_u64(s, (uintptr_t)abuf->data);
|
|
if (js_resize_array(s->ctx, (void **)&s->sab_tab, sizeof(s->sab_tab[0]),
|
|
&s->sab_tab_size, s->sab_tab_len + 1))
|
|
return -1;
|
|
/* keep the SAB pointer so that the user can clone it or free it */
|
|
s->sab_tab[s->sab_tab_len++] = abuf->data;
|
|
return 0;
|
|
}
|
|
|
|
static int JS_WriteRegExp(BCWriterState *s, JSRegExp regexp)
|
|
{
|
|
JSString *bc = regexp.bytecode;
|
|
assert(!bc->is_wide_char);
|
|
|
|
JS_WriteString(s, regexp.pattern);
|
|
|
|
if (is_be())
|
|
lre_byte_swap(bc->u.str8, bc->len, /*is_byte_swapped*/FALSE);
|
|
|
|
JS_WriteString(s, bc);
|
|
|
|
if (is_be())
|
|
lre_byte_swap(bc->u.str8, bc->len, /*is_byte_swapped*/TRUE);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int JS_WriteObjectRec(BCWriterState *s, JSValue obj)
|
|
{
|
|
uint32_t tag;
|
|
|
|
if (js_check_stack_overflow(s->ctx->rt, 0)) {
|
|
JS_ThrowStackOverflow(s->ctx);
|
|
return -1;
|
|
}
|
|
|
|
tag = JS_VALUE_GET_NORM_TAG(obj);
|
|
switch(tag) {
|
|
case JS_TAG_NULL:
|
|
bc_put_u8(s, BC_TAG_NULL);
|
|
break;
|
|
case JS_TAG_UNDEFINED:
|
|
bc_put_u8(s, BC_TAG_UNDEFINED);
|
|
break;
|
|
case JS_TAG_BOOL:
|
|
bc_put_u8(s, BC_TAG_BOOL_FALSE + JS_VALUE_GET_INT(obj));
|
|
break;
|
|
case JS_TAG_INT:
|
|
bc_put_u8(s, BC_TAG_INT32);
|
|
bc_put_sleb128(s, JS_VALUE_GET_INT(obj));
|
|
break;
|
|
case JS_TAG_FLOAT64:
|
|
{
|
|
JSFloat64Union u;
|
|
bc_put_u8(s, BC_TAG_FLOAT64);
|
|
u.d = JS_VALUE_GET_FLOAT64(obj);
|
|
bc_put_u64(s, u.u64);
|
|
}
|
|
break;
|
|
case JS_TAG_STRING:
|
|
{
|
|
JSString *p = JS_VALUE_GET_STRING(obj);
|
|
bc_put_u8(s, BC_TAG_STRING);
|
|
JS_WriteString(s, p);
|
|
}
|
|
break;
|
|
case JS_TAG_FUNCTION_BYTECODE:
|
|
if (!s->allow_bytecode)
|
|
goto invalid_tag;
|
|
if (JS_WriteFunctionTag(s, obj))
|
|
goto fail;
|
|
break;
|
|
case JS_TAG_MODULE:
|
|
if (!s->allow_bytecode)
|
|
goto invalid_tag;
|
|
if (JS_WriteModule(s, obj))
|
|
goto fail;
|
|
break;
|
|
case JS_TAG_OBJECT:
|
|
{
|
|
JSObject *p = JS_VALUE_GET_OBJ(obj);
|
|
int ret, idx;
|
|
|
|
if (s->allow_reference) {
|
|
idx = js_object_list_find(s->ctx, &s->object_list, p);
|
|
if (idx >= 0) {
|
|
bc_put_u8(s, BC_TAG_OBJECT_REFERENCE);
|
|
bc_put_leb128(s, idx);
|
|
break;
|
|
} else {
|
|
if (js_object_list_add(s->ctx, &s->object_list, p))
|
|
goto fail;
|
|
}
|
|
} else {
|
|
if (p->tmp_mark) {
|
|
JS_ThrowTypeError(s->ctx, "circular reference");
|
|
goto fail;
|
|
}
|
|
p->tmp_mark = 1;
|
|
}
|
|
switch(p->class_id) {
|
|
case JS_CLASS_ARRAY:
|
|
ret = JS_WriteArray(s, obj);
|
|
break;
|
|
case JS_CLASS_OBJECT:
|
|
ret = JS_WriteObjectTag(s, obj);
|
|
break;
|
|
case JS_CLASS_ARRAY_BUFFER:
|
|
ret = JS_WriteArrayBuffer(s, obj);
|
|
break;
|
|
case JS_CLASS_SHARED_ARRAY_BUFFER:
|
|
if (!s->allow_sab)
|
|
goto invalid_tag;
|
|
ret = JS_WriteSharedArrayBuffer(s, obj);
|
|
break;
|
|
case JS_CLASS_REGEXP:
|
|
bc_put_u8(s, BC_TAG_REGEXP);
|
|
ret = JS_WriteRegExp(s, p->u.regexp);
|
|
break;
|
|
case JS_CLASS_DATE:
|
|
bc_put_u8(s, BC_TAG_DATE);
|
|
ret = JS_WriteObjectRec(s, p->u.object_data);
|
|
break;
|
|
case JS_CLASS_NUMBER:
|
|
case JS_CLASS_STRING:
|
|
case JS_CLASS_BOOLEAN:
|
|
case JS_CLASS_BIG_INT:
|
|
bc_put_u8(s, BC_TAG_OBJECT_VALUE);
|
|
ret = JS_WriteObjectRec(s, p->u.object_data);
|
|
break;
|
|
default:
|
|
if (p->class_id >= JS_CLASS_UINT8C_ARRAY &&
|
|
p->class_id <= JS_CLASS_FLOAT64_ARRAY) {
|
|
ret = JS_WriteTypedArray(s, obj);
|
|
} else {
|
|
JS_ThrowTypeError(s->ctx, "unsupported object class");
|
|
ret = -1;
|
|
}
|
|
break;
|
|
}
|
|
p->tmp_mark = 0;
|
|
if (ret)
|
|
goto fail;
|
|
}
|
|
break;
|
|
case JS_TAG_BIG_INT:
|
|
if (JS_WriteBigInt(s, obj))
|
|
goto fail;
|
|
break;
|
|
default:
|
|
invalid_tag:
|
|
JS_ThrowInternalError(s->ctx, "unsupported tag (%d)", tag);
|
|
goto fail;
|
|
}
|
|
return 0;
|
|
|
|
fail:
|
|
return -1;
|
|
}
|
|
|
|
/* create the atom table */
|
|
static int JS_WriteObjectAtoms(BCWriterState *s)
|
|
{
|
|
JSRuntime *rt = s->ctx->rt;
|
|
DynBuf dbuf1;
|
|
int i, atoms_size;
|
|
|
|
dbuf1 = s->dbuf;
|
|
js_dbuf_init(s->ctx, &s->dbuf);
|
|
bc_put_u8(s, BC_VERSION);
|
|
|
|
bc_put_leb128(s, s->idx_to_atom_count);
|
|
for(i = 0; i < s->idx_to_atom_count; i++) {
|
|
JSAtomStruct *p = rt->atom_array[s->idx_to_atom[i]];
|
|
JS_WriteString(s, p);
|
|
}
|
|
/* XXX: should check for OOM in above phase */
|
|
|
|
/* move the atoms at the start */
|
|
/* XXX: could just append dbuf1 data, but it uses more memory if
|
|
dbuf1 is larger than dbuf */
|
|
atoms_size = s->dbuf.size;
|
|
if (dbuf_realloc(&dbuf1, dbuf1.size + atoms_size))
|
|
goto fail;
|
|
memmove(dbuf1.buf + atoms_size, dbuf1.buf, dbuf1.size);
|
|
memcpy(dbuf1.buf, s->dbuf.buf, atoms_size);
|
|
dbuf1.size += atoms_size;
|
|
dbuf_free(&s->dbuf);
|
|
s->dbuf = dbuf1;
|
|
return 0;
|
|
fail:
|
|
dbuf_free(&dbuf1);
|
|
return -1;
|
|
}
|
|
|
|
uint8_t *JS_WriteObject2(JSContext *ctx, size_t *psize, JSValue obj,
|
|
int flags, uint8_t ***psab_tab, size_t *psab_tab_len)
|
|
{
|
|
BCWriterState ss, *s = &ss;
|
|
|
|
memset(s, 0, sizeof(*s));
|
|
s->ctx = ctx;
|
|
s->allow_bytecode = ((flags & JS_WRITE_OBJ_BYTECODE) != 0);
|
|
s->allow_sab = ((flags & JS_WRITE_OBJ_SAB) != 0);
|
|
s->allow_reference = ((flags & JS_WRITE_OBJ_REFERENCE) != 0);
|
|
s->allow_source = ((flags & JS_WRITE_OBJ_STRIP_SOURCE) == 0);
|
|
s->allow_debug = ((flags & JS_WRITE_OBJ_STRIP_DEBUG) == 0);
|
|
/* XXX: could use a different version when bytecode is included */
|
|
if (s->allow_bytecode)
|
|
s->first_atom = JS_ATOM_END;
|
|
else
|
|
s->first_atom = 1;
|
|
js_dbuf_init(ctx, &s->dbuf);
|
|
js_object_list_init(&s->object_list);
|
|
|
|
if (JS_WriteObjectRec(s, obj))
|
|
goto fail;
|
|
if (JS_WriteObjectAtoms(s))
|
|
goto fail;
|
|
js_object_list_end(ctx, &s->object_list);
|
|
js_free(ctx, s->atom_to_idx);
|
|
js_free(ctx, s->idx_to_atom);
|
|
*psize = s->dbuf.size;
|
|
if (psab_tab)
|
|
*psab_tab = s->sab_tab;
|
|
if (psab_tab_len)
|
|
*psab_tab_len = s->sab_tab_len;
|
|
return s->dbuf.buf;
|
|
fail:
|
|
js_object_list_end(ctx, &s->object_list);
|
|
js_free(ctx, s->atom_to_idx);
|
|
js_free(ctx, s->idx_to_atom);
|
|
dbuf_free(&s->dbuf);
|
|
*psize = 0;
|
|
if (psab_tab)
|
|
*psab_tab = NULL;
|
|
if (psab_tab_len)
|
|
*psab_tab_len = 0;
|
|
return NULL;
|
|
}
|
|
|
|
uint8_t *JS_WriteObject(JSContext *ctx, size_t *psize, JSValue obj,
|
|
int flags)
|
|
{
|
|
return JS_WriteObject2(ctx, psize, obj, flags, NULL, NULL);
|
|
}
|
|
|
|
typedef struct BCReaderState {
|
|
JSContext *ctx;
|
|
const uint8_t *buf_start, *ptr, *buf_end;
|
|
uint32_t first_atom;
|
|
uint32_t idx_to_atom_count;
|
|
JSAtom *idx_to_atom;
|
|
int error_state;
|
|
BOOL allow_sab : 8;
|
|
BOOL allow_bytecode : 8;
|
|
BOOL allow_reference : 8;
|
|
/* object references */
|
|
JSObject **objects;
|
|
int objects_count;
|
|
int objects_size;
|
|
|
|
/* used for DUMP_READ_OBJECT */
|
|
const uint8_t *ptr_last;
|
|
int level;
|
|
} BCReaderState;
|
|
|
|
#ifdef DUMP_READ_OBJECT
|
|
static void __attribute__((format(printf, 2, 3))) bc_read_trace(BCReaderState *s, const char *fmt, ...) {
|
|
va_list ap;
|
|
int i, n, n0;
|
|
|
|
if (!check_dump_flag(s->ctx->rt, DUMP_READ_OBJECT))
|
|
return;
|
|
|
|
if (!s->ptr_last)
|
|
s->ptr_last = s->buf_start;
|
|
|
|
n = n0 = 0;
|
|
if (s->ptr > s->ptr_last || s->ptr == s->buf_start) {
|
|
n0 = printf("%04x: ", (int)(s->ptr_last - s->buf_start));
|
|
n += n0;
|
|
}
|
|
for (i = 0; s->ptr_last < s->ptr; i++) {
|
|
if ((i & 7) == 0 && i > 0) {
|
|
printf("\n%*s", n0, "");
|
|
n = n0;
|
|
}
|
|
n += printf(" %02x", *s->ptr_last++);
|
|
}
|
|
if (*fmt == '}')
|
|
s->level--;
|
|
if (n < 32 + s->level * 2) {
|
|
printf("%*s", 32 + s->level * 2 - n, "");
|
|
}
|
|
va_start(ap, fmt);
|
|
vfprintf(stdout, fmt, ap);
|
|
va_end(ap);
|
|
if (strchr(fmt, '{'))
|
|
s->level++;
|
|
}
|
|
#else
|
|
#define bc_read_trace(...)
|
|
#endif
|
|
|
|
static int bc_read_error_end(BCReaderState *s)
|
|
{
|
|
if (!s->error_state) {
|
|
JS_ThrowSyntaxError(s->ctx, "read after the end of the buffer");
|
|
}
|
|
return s->error_state = -1;
|
|
}
|
|
|
|
static int bc_get_u8(BCReaderState *s, uint8_t *pval)
|
|
{
|
|
if (unlikely(s->buf_end - s->ptr < 1)) {
|
|
*pval = 0; /* avoid warning */
|
|
return bc_read_error_end(s);
|
|
}
|
|
*pval = *s->ptr++;
|
|
return 0;
|
|
}
|
|
|
|
static int bc_get_u16(BCReaderState *s, uint16_t *pval)
|
|
{
|
|
uint16_t v;
|
|
if (unlikely(s->buf_end - s->ptr < 2)) {
|
|
*pval = 0; /* avoid warning */
|
|
return bc_read_error_end(s);
|
|
}
|
|
v = get_u16(s->ptr);
|
|
if (is_be())
|
|
v = bswap16(v);
|
|
*pval = v;
|
|
s->ptr += 2;
|
|
return 0;
|
|
}
|
|
|
|
static __maybe_unused int bc_get_u32(BCReaderState *s, uint32_t *pval)
|
|
{
|
|
uint32_t v;
|
|
if (unlikely(s->buf_end - s->ptr < 4)) {
|
|
*pval = 0; /* avoid warning */
|
|
return bc_read_error_end(s);
|
|
}
|
|
v = get_u32(s->ptr);
|
|
if (is_be())
|
|
v = bswap32(v);
|
|
*pval = v;
|
|
s->ptr += 4;
|
|
return 0;
|
|
}
|
|
|
|
static int bc_get_u64(BCReaderState *s, uint64_t *pval)
|
|
{
|
|
uint64_t v;
|
|
if (unlikely(s->buf_end - s->ptr < 8)) {
|
|
*pval = 0; /* avoid warning */
|
|
return bc_read_error_end(s);
|
|
}
|
|
v = get_u64(s->ptr);
|
|
if (is_be())
|
|
v = bswap64(v);
|
|
*pval = v;
|
|
s->ptr += 8;
|
|
return 0;
|
|
}
|
|
|
|
static int bc_get_leb128(BCReaderState *s, uint32_t *pval)
|
|
{
|
|
int ret;
|
|
ret = get_leb128(pval, s->ptr, s->buf_end);
|
|
if (unlikely(ret < 0))
|
|
return bc_read_error_end(s);
|
|
s->ptr += ret;
|
|
return 0;
|
|
}
|
|
|
|
static int bc_get_sleb128(BCReaderState *s, int32_t *pval)
|
|
{
|
|
int ret;
|
|
ret = get_sleb128(pval, s->ptr, s->buf_end);
|
|
if (unlikely(ret < 0))
|
|
return bc_read_error_end(s);
|
|
s->ptr += ret;
|
|
return 0;
|
|
}
|
|
|
|
/* XXX: used to read an `int` with a positive value */
|
|
static int bc_get_leb128_int(BCReaderState *s, int *pval)
|
|
{
|
|
return bc_get_leb128(s, (uint32_t *)pval);
|
|
}
|
|
|
|
static int bc_get_leb128_u16(BCReaderState *s, uint16_t *pval)
|
|
{
|
|
uint32_t val;
|
|
if (bc_get_leb128(s, &val)) {
|
|
*pval = 0;
|
|
return -1;
|
|
}
|
|
*pval = val;
|
|
return 0;
|
|
}
|
|
|
|
static int bc_get_buf(BCReaderState *s, void *buf, uint32_t buf_len)
|
|
{
|
|
if (buf_len != 0) {
|
|
if (unlikely(!buf || s->buf_end - s->ptr < buf_len))
|
|
return bc_read_error_end(s);
|
|
memcpy(buf, s->ptr, buf_len);
|
|
s->ptr += buf_len;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int bc_idx_to_atom(BCReaderState *s, JSAtom *patom, uint32_t idx)
|
|
{
|
|
JSAtom atom;
|
|
|
|
if (__JS_AtomIsTaggedInt(idx)) {
|
|
atom = idx;
|
|
} else if (idx < s->first_atom) {
|
|
atom = JS_DupAtom(s->ctx, idx);
|
|
} else {
|
|
idx -= s->first_atom;
|
|
if (idx >= s->idx_to_atom_count) {
|
|
JS_ThrowSyntaxError(s->ctx, "invalid atom index (pos=%u)",
|
|
(unsigned int)(s->ptr - s->buf_start));
|
|
*patom = JS_ATOM_NULL;
|
|
return s->error_state = -1;
|
|
}
|
|
atom = JS_DupAtom(s->ctx, s->idx_to_atom[idx]);
|
|
}
|
|
*patom = atom;
|
|
return 0;
|
|
}
|
|
|
|
static int bc_get_atom(BCReaderState *s, JSAtom *patom)
|
|
{
|
|
uint32_t v;
|
|
if (bc_get_leb128(s, &v))
|
|
return -1;
|
|
if (v & 1) {
|
|
*patom = __JS_AtomFromUInt32(v >> 1);
|
|
return 0;
|
|
} else {
|
|
return bc_idx_to_atom(s, patom, v >> 1);
|
|
}
|
|
}
|
|
|
|
static JSString *JS_ReadString(BCReaderState *s)
|
|
{
|
|
uint32_t len;
|
|
size_t size;
|
|
BOOL is_wide_char;
|
|
JSString *p;
|
|
|
|
if (bc_get_leb128(s, &len))
|
|
return NULL;
|
|
is_wide_char = len & 1;
|
|
len >>= 1;
|
|
p = js_alloc_string(s->ctx, len, is_wide_char);
|
|
if (!p) {
|
|
s->error_state = -1;
|
|
return NULL;
|
|
}
|
|
size = (size_t)len << is_wide_char;
|
|
if ((s->buf_end - s->ptr) < size) {
|
|
bc_read_error_end(s);
|
|
js_free_string(s->ctx->rt, p);
|
|
return NULL;
|
|
}
|
|
memcpy(p->u.str8, s->ptr, size);
|
|
s->ptr += size;
|
|
if (is_wide_char) {
|
|
if (is_be()) {
|
|
uint32_t i;
|
|
for (i = 0; i < len; i++)
|
|
p->u.str16[i] = bswap16(p->u.str16[i]);
|
|
}
|
|
} else {
|
|
p->u.str8[size] = '\0'; /* add the trailing zero for 8 bit strings */
|
|
}
|
|
#ifdef DUMP_READ_OBJECT
|
|
if (check_dump_flag(s->ctx->rt, DUMP_READ_OBJECT)) {
|
|
bc_read_trace(s, ""); // hex dump and indentation
|
|
JS_DumpString(s->ctx->rt, p);
|
|
printf("\n");
|
|
}
|
|
#endif
|
|
return p;
|
|
}
|
|
|
|
static uint32_t bc_get_flags(uint32_t flags, int *pidx, int n)
|
|
{
|
|
uint32_t val;
|
|
/* XXX: this does not work for n == 32 */
|
|
val = (flags >> *pidx) & ((1U << n) - 1);
|
|
*pidx += n;
|
|
return val;
|
|
}
|
|
|
|
static int JS_ReadFunctionBytecode(BCReaderState *s, JSFunctionBytecode *b,
|
|
int byte_code_offset, uint32_t bc_len)
|
|
{
|
|
uint8_t *bc_buf;
|
|
int pos, len, op;
|
|
JSAtom atom;
|
|
uint32_t idx;
|
|
|
|
bc_buf = (uint8_t*)b + byte_code_offset;
|
|
if (bc_get_buf(s, bc_buf, bc_len))
|
|
return -1;
|
|
b->byte_code_buf = bc_buf;
|
|
|
|
if (is_be())
|
|
bc_byte_swap(bc_buf, bc_len);
|
|
|
|
pos = 0;
|
|
while (pos < bc_len) {
|
|
op = bc_buf[pos];
|
|
len = short_opcode_info(op).size;
|
|
switch(short_opcode_info(op).fmt) {
|
|
case OP_FMT_atom:
|
|
case OP_FMT_atom_u8:
|
|
case OP_FMT_atom_u16:
|
|
case OP_FMT_atom_label_u8:
|
|
case OP_FMT_atom_label_u16:
|
|
idx = get_u32(bc_buf + pos + 1);
|
|
if (bc_idx_to_atom(s, &atom, idx)) {
|
|
/* Note: the atoms will be freed up to this position */
|
|
b->byte_code_len = pos;
|
|
return -1;
|
|
}
|
|
put_u32(bc_buf + pos + 1, atom);
|
|
break;
|
|
default:
|
|
assert(!is_ic_op(op)); // should not end up in serialized bytecode
|
|
break;
|
|
}
|
|
#ifdef DUMP_READ_OBJECT
|
|
if (check_dump_flag(s->ctx->rt, DUMP_READ_OBJECT)) {
|
|
const uint8_t *save_ptr = s->ptr;
|
|
s->ptr = s->ptr_last + len;
|
|
s->level -= 4;
|
|
bc_read_trace(s, ""); // hex dump + indent
|
|
dump_single_byte_code(s->ctx, bc_buf + pos, b,
|
|
s->ptr - s->buf_start - len);
|
|
s->level += 4;
|
|
s->ptr = save_ptr;
|
|
}
|
|
#endif
|
|
pos += len;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static JSValue JS_ReadBigInt(BCReaderState *s)
|
|
{
|
|
JSValue obj;
|
|
uint8_t v8;
|
|
int32_t e;
|
|
uint32_t len;
|
|
limb_t l, i, n;
|
|
limb_t v;
|
|
bf_t *a;
|
|
|
|
obj = JS_NewBigInt(s->ctx);
|
|
if (JS_IsException(obj))
|
|
goto fail;
|
|
|
|
/* sign + exponent */
|
|
if (bc_get_sleb128(s, &e))
|
|
goto fail;
|
|
|
|
a = JS_GetBigInt(obj);
|
|
a->sign = e & 1;
|
|
e >>= 1;
|
|
if (e == 0)
|
|
a->expn = BF_EXP_ZERO;
|
|
else if (e == 1)
|
|
a->expn = BF_EXP_INF;
|
|
else if (e == 2)
|
|
a->expn = BF_EXP_NAN;
|
|
else if (e >= 3)
|
|
a->expn = e - 3;
|
|
else
|
|
a->expn = e;
|
|
|
|
/* mantissa */
|
|
if (a->expn != BF_EXP_ZERO &&
|
|
a->expn != BF_EXP_INF &&
|
|
a->expn != BF_EXP_NAN) {
|
|
if (bc_get_leb128(s, &len))
|
|
goto fail;
|
|
bc_read_trace(s, "len=%" PRId64 "\n", (int64_t)len);
|
|
if (len == 0) {
|
|
JS_ThrowRangeError(s->ctx, "maximum BigInt size exceeded");
|
|
goto fail;
|
|
}
|
|
l = (len + sizeof(limb_t) - 1) / sizeof(limb_t);
|
|
if (bf_resize(a, l)) {
|
|
JS_ThrowOutOfMemory(s->ctx);
|
|
goto fail;
|
|
}
|
|
n = len & (sizeof(limb_t) - 1);
|
|
if (n != 0) {
|
|
v = 0;
|
|
for(i = 0; i < n; i++) {
|
|
if (bc_get_u8(s, &v8))
|
|
goto fail;
|
|
v |= (limb_t)v8 << ((sizeof(limb_t) - n + i) * 8);
|
|
}
|
|
a->tab[0] = v;
|
|
i = 1;
|
|
} else {
|
|
i = 0;
|
|
}
|
|
for(; i < l; i++) {
|
|
#if LIMB_BITS == 32
|
|
if (bc_get_u32(s, &v))
|
|
goto fail;
|
|
#else
|
|
if (bc_get_u64(s, &v))
|
|
goto fail;
|
|
#endif
|
|
a->tab[i] = v;
|
|
}
|
|
}
|
|
return obj;
|
|
fail:
|
|
JS_FreeValue(s->ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue JS_ReadObjectRec(BCReaderState *s);
|
|
|
|
static int BC_add_object_ref1(BCReaderState *s, JSObject *p)
|
|
{
|
|
if (s->allow_reference) {
|
|
if (js_resize_array(s->ctx, (void *)&s->objects,
|
|
sizeof(s->objects[0]),
|
|
&s->objects_size, s->objects_count + 1))
|
|
return -1;
|
|
s->objects[s->objects_count++] = p;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int BC_add_object_ref(BCReaderState *s, JSValue obj)
|
|
{
|
|
return BC_add_object_ref1(s, JS_VALUE_GET_OBJ(obj));
|
|
}
|
|
|
|
static JSValue JS_ReadFunctionTag(BCReaderState *s)
|
|
{
|
|
JSContext *ctx = s->ctx;
|
|
JSFunctionBytecode bc, *b;
|
|
JSValue obj = JS_UNDEFINED;
|
|
uint16_t v16;
|
|
uint8_t v8;
|
|
int idx, i, local_count, has_debug_info;
|
|
int function_size, cpool_offset, byte_code_offset;
|
|
int closure_var_offset, vardefs_offset;
|
|
uint32_t ic_len;
|
|
JSAtom atom;
|
|
|
|
memset(&bc, 0, sizeof(bc));
|
|
bc.header.ref_count = 1;
|
|
//bc.gc_header.mark = 0;
|
|
|
|
if (bc_get_u16(s, &v16))
|
|
goto fail;
|
|
idx = 0;
|
|
bc.has_prototype = bc_get_flags(v16, &idx, 1);
|
|
bc.has_simple_parameter_list = bc_get_flags(v16, &idx, 1);
|
|
bc.is_derived_class_constructor = bc_get_flags(v16, &idx, 1);
|
|
bc.need_home_object = bc_get_flags(v16, &idx, 1);
|
|
bc.func_kind = bc_get_flags(v16, &idx, 2);
|
|
bc.new_target_allowed = bc_get_flags(v16, &idx, 1);
|
|
bc.super_call_allowed = bc_get_flags(v16, &idx, 1);
|
|
bc.super_allowed = bc_get_flags(v16, &idx, 1);
|
|
bc.arguments_allowed = bc_get_flags(v16, &idx, 1);
|
|
bc.backtrace_barrier = bc_get_flags(v16, &idx, 1);
|
|
has_debug_info = bc_get_flags(v16, &idx, 1);
|
|
if (bc_get_u8(s, &v8))
|
|
goto fail;
|
|
bc.js_mode = v8;
|
|
if (bc_get_atom(s, &bc.func_name))
|
|
goto fail;
|
|
if (bc_get_leb128_u16(s, &bc.arg_count))
|
|
goto fail;
|
|
if (bc_get_leb128_u16(s, &bc.var_count))
|
|
goto fail;
|
|
if (bc_get_leb128_u16(s, &bc.defined_arg_count))
|
|
goto fail;
|
|
if (bc_get_leb128_u16(s, &bc.stack_size))
|
|
goto fail;
|
|
if (bc_get_leb128_int(s, &bc.closure_var_count))
|
|
goto fail;
|
|
if (bc_get_leb128_int(s, &bc.cpool_count))
|
|
goto fail;
|
|
if (bc_get_leb128_int(s, &bc.byte_code_len))
|
|
goto fail;
|
|
if (bc_get_leb128_int(s, &local_count))
|
|
goto fail;
|
|
|
|
function_size = sizeof(*b);
|
|
cpool_offset = function_size;
|
|
function_size += bc.cpool_count * sizeof(*bc.cpool);
|
|
vardefs_offset = function_size;
|
|
function_size += local_count * sizeof(*bc.vardefs);
|
|
closure_var_offset = function_size;
|
|
function_size += bc.closure_var_count * sizeof(*bc.closure_var);
|
|
byte_code_offset = function_size;
|
|
function_size += bc.byte_code_len;
|
|
|
|
b = js_mallocz(ctx, function_size);
|
|
if (!b)
|
|
goto fail;
|
|
|
|
memcpy(b, &bc, sizeof(*b));
|
|
bc.func_name = JS_ATOM_NULL;
|
|
b->header.ref_count = 1;
|
|
if (local_count != 0) {
|
|
b->vardefs = (void *)((uint8_t*)b + vardefs_offset);
|
|
}
|
|
if (b->closure_var_count != 0) {
|
|
b->closure_var = (void *)((uint8_t*)b + closure_var_offset);
|
|
}
|
|
if (b->cpool_count != 0) {
|
|
b->cpool = (void *)((uint8_t*)b + cpool_offset);
|
|
}
|
|
|
|
add_gc_object(ctx->rt, &b->header, JS_GC_OBJ_TYPE_FUNCTION_BYTECODE);
|
|
|
|
obj = JS_MKPTR(JS_TAG_FUNCTION_BYTECODE, b);
|
|
|
|
#ifdef DUMP_READ_OBJECT
|
|
if (check_dump_flag(s->ctx->rt, DUMP_READ_OBJECT)) {
|
|
if (b->func_name) {
|
|
bc_read_trace(s, "name: ");
|
|
print_atom(s->ctx, b->func_name);
|
|
printf("\n");
|
|
}
|
|
}
|
|
#endif
|
|
bc_read_trace(s, "args=%d vars=%d defargs=%d closures=%d cpool=%d\n",
|
|
b->arg_count, b->var_count, b->defined_arg_count,
|
|
b->closure_var_count, b->cpool_count);
|
|
bc_read_trace(s, "stack=%d bclen=%d locals=%d\n",
|
|
b->stack_size, b->byte_code_len, local_count);
|
|
|
|
if (local_count != 0) {
|
|
bc_read_trace(s, "vars {\n");
|
|
bc_read_trace(s, "off flags scope name\n");
|
|
for(i = 0; i < local_count; i++) {
|
|
JSVarDef *vd = &b->vardefs[i];
|
|
if (bc_get_atom(s, &vd->var_name))
|
|
goto fail;
|
|
if (bc_get_leb128_int(s, &vd->scope_level))
|
|
goto fail;
|
|
if (bc_get_leb128_int(s, &vd->scope_next))
|
|
goto fail;
|
|
vd->scope_next--;
|
|
if (bc_get_u8(s, &v8))
|
|
goto fail;
|
|
idx = 0;
|
|
vd->var_kind = bc_get_flags(v8, &idx, 4);
|
|
vd->is_const = bc_get_flags(v8, &idx, 1);
|
|
vd->is_lexical = bc_get_flags(v8, &idx, 1);
|
|
vd->is_captured = bc_get_flags(v8, &idx, 1);
|
|
#ifdef DUMP_READ_OBJECT
|
|
if (check_dump_flag(s->ctx->rt, DUMP_READ_OBJECT)) {
|
|
bc_read_trace(s, "%3d %d%c%c%c %4d ",
|
|
i, vd->var_kind,
|
|
vd->is_const ? 'C' : '.',
|
|
vd->is_lexical ? 'L' : '.',
|
|
vd->is_captured ? 'X' : '.',
|
|
vd->scope_level);
|
|
print_atom(s->ctx, vd->var_name);
|
|
printf("\n");
|
|
}
|
|
#endif
|
|
}
|
|
bc_read_trace(s, "}\n");
|
|
}
|
|
if (b->closure_var_count != 0) {
|
|
bc_read_trace(s, "closure vars {\n");
|
|
bc_read_trace(s, "off flags idx name\n");
|
|
for(i = 0; i < b->closure_var_count; i++) {
|
|
JSClosureVar *cv = &b->closure_var[i];
|
|
int var_idx;
|
|
if (bc_get_atom(s, &cv->var_name))
|
|
goto fail;
|
|
if (bc_get_leb128_int(s, &var_idx))
|
|
goto fail;
|
|
cv->var_idx = var_idx;
|
|
if (bc_get_u8(s, &v8))
|
|
goto fail;
|
|
idx = 0;
|
|
cv->is_local = bc_get_flags(v8, &idx, 1);
|
|
cv->is_arg = bc_get_flags(v8, &idx, 1);
|
|
cv->is_const = bc_get_flags(v8, &idx, 1);
|
|
cv->is_lexical = bc_get_flags(v8, &idx, 1);
|
|
cv->var_kind = bc_get_flags(v8, &idx, 4);
|
|
#ifdef DUMP_READ_OBJECT
|
|
if (check_dump_flag(s->ctx->rt, DUMP_READ_OBJECT)) {
|
|
bc_read_trace(s, "%3d %d%c%c%c%c %3d ",
|
|
i, cv->var_kind,
|
|
cv->is_local ? 'L' : '.',
|
|
cv->is_arg ? 'A' : '.',
|
|
cv->is_const ? 'C' : '.',
|
|
cv->is_lexical ? 'X' : '.',
|
|
cv->var_idx);
|
|
print_atom(s->ctx, cv->var_name);
|
|
printf("\n");
|
|
}
|
|
#endif
|
|
}
|
|
bc_read_trace(s, "}\n");
|
|
}
|
|
if (b->cpool_count != 0) {
|
|
bc_read_trace(s, "cpool {\n");
|
|
for(i = 0; i < b->cpool_count; i++) {
|
|
JSValue val;
|
|
val = JS_ReadObjectRec(s);
|
|
if (JS_IsException(val))
|
|
goto fail;
|
|
b->cpool[i] = val;
|
|
}
|
|
bc_read_trace(s, "}\n");
|
|
}
|
|
{
|
|
bc_read_trace(s, "bytecode {\n");
|
|
if (JS_ReadFunctionBytecode(s, b, byte_code_offset, b->byte_code_len))
|
|
goto fail;
|
|
bc_read_trace(s, "}\n");
|
|
}
|
|
if (!has_debug_info)
|
|
goto nodebug;
|
|
|
|
/* read optional debug information */
|
|
bc_read_trace(s, "debug {\n");
|
|
if (bc_get_atom(s, &b->filename))
|
|
goto fail;
|
|
if (bc_get_leb128_int(s, &b->line_num))
|
|
goto fail;
|
|
if (bc_get_leb128_int(s, &b->col_num))
|
|
goto fail;
|
|
#ifdef DUMP_READ_OBJECT
|
|
if (check_dump_flag(s->ctx->rt, DUMP_READ_OBJECT)) {
|
|
bc_read_trace(s, "filename: ");
|
|
print_atom(s->ctx, b->filename);
|
|
printf(", line: %d, column: %d\n", b->line_num, b->col_num);
|
|
}
|
|
#endif
|
|
if (bc_get_leb128_int(s, &b->pc2line_len))
|
|
goto fail;
|
|
if (b->pc2line_len) {
|
|
bc_read_trace(s, "positions: %d bytes\n", b->pc2line_len);
|
|
b->pc2line_buf = js_mallocz(ctx, b->pc2line_len);
|
|
if (!b->pc2line_buf)
|
|
goto fail;
|
|
if (bc_get_buf(s, b->pc2line_buf, b->pc2line_len))
|
|
goto fail;
|
|
}
|
|
if (bc_get_leb128_int(s, &b->source_len))
|
|
goto fail;
|
|
if (b->source_len) {
|
|
bc_read_trace(s, "source: %d bytes\n", b->source_len);
|
|
s->ptr_last += b->source_len; // omit source code hex dump
|
|
/* b->source is a UTF-8 encoded null terminated C string */
|
|
b->source = js_mallocz(ctx, b->source_len + 1);
|
|
if (!b->source)
|
|
goto fail;
|
|
if (bc_get_buf(s, b->source, b->source_len))
|
|
goto fail;
|
|
}
|
|
bc_read_trace(s, "}\n");
|
|
|
|
nodebug:
|
|
b->realm = JS_DupContext(ctx);
|
|
return obj;
|
|
|
|
fail:
|
|
JS_FreeAtom(ctx, bc.func_name);
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue JS_ReadModule(BCReaderState *s)
|
|
{
|
|
JSContext *ctx = s->ctx;
|
|
JSValue obj;
|
|
JSModuleDef *m = NULL;
|
|
JSAtom module_name;
|
|
int i;
|
|
uint8_t v8;
|
|
|
|
if (bc_get_atom(s, &module_name))
|
|
goto fail;
|
|
#ifdef DUMP_READ_OBJECT
|
|
if (check_dump_flag(s->ctx->rt, DUMP_READ_OBJECT)) {
|
|
bc_read_trace(s, "name: ");
|
|
print_atom(s->ctx, module_name);
|
|
printf("\n");
|
|
}
|
|
#endif
|
|
m = js_new_module_def(ctx, module_name);
|
|
if (!m)
|
|
goto fail;
|
|
obj = js_dup(JS_MKPTR(JS_TAG_MODULE, m));
|
|
if (bc_get_leb128_int(s, &m->req_module_entries_count))
|
|
goto fail;
|
|
if (m->req_module_entries_count != 0) {
|
|
m->req_module_entries_size = m->req_module_entries_count;
|
|
m->req_module_entries = js_mallocz(ctx, sizeof(m->req_module_entries[0]) * m->req_module_entries_size);
|
|
if (!m->req_module_entries)
|
|
goto fail;
|
|
for(i = 0; i < m->req_module_entries_count; i++) {
|
|
JSReqModuleEntry *rme = &m->req_module_entries[i];
|
|
if (bc_get_atom(s, &rme->module_name))
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
if (bc_get_leb128_int(s, &m->export_entries_count))
|
|
goto fail;
|
|
if (m->export_entries_count != 0) {
|
|
m->export_entries_size = m->export_entries_count;
|
|
m->export_entries = js_mallocz(ctx, sizeof(m->export_entries[0]) * m->export_entries_size);
|
|
if (!m->export_entries)
|
|
goto fail;
|
|
for(i = 0; i < m->export_entries_count; i++) {
|
|
JSExportEntry *me = &m->export_entries[i];
|
|
if (bc_get_u8(s, &v8))
|
|
goto fail;
|
|
me->export_type = v8;
|
|
if (me->export_type == JS_EXPORT_TYPE_LOCAL) {
|
|
if (bc_get_leb128_int(s, &me->u.local.var_idx))
|
|
goto fail;
|
|
} else {
|
|
if (bc_get_leb128_int(s, &me->u.req_module_idx))
|
|
goto fail;
|
|
if (bc_get_atom(s, &me->local_name))
|
|
goto fail;
|
|
}
|
|
if (bc_get_atom(s, &me->export_name))
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
if (bc_get_leb128_int(s, &m->star_export_entries_count))
|
|
goto fail;
|
|
if (m->star_export_entries_count != 0) {
|
|
m->star_export_entries_size = m->star_export_entries_count;
|
|
m->star_export_entries = js_mallocz(ctx, sizeof(m->star_export_entries[0]) * m->star_export_entries_size);
|
|
if (!m->star_export_entries)
|
|
goto fail;
|
|
for(i = 0; i < m->star_export_entries_count; i++) {
|
|
JSStarExportEntry *se = &m->star_export_entries[i];
|
|
if (bc_get_leb128_int(s, &se->req_module_idx))
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
if (bc_get_leb128_int(s, &m->import_entries_count))
|
|
goto fail;
|
|
if (m->import_entries_count != 0) {
|
|
m->import_entries_size = m->import_entries_count;
|
|
m->import_entries = js_mallocz(ctx, sizeof(m->import_entries[0]) * m->import_entries_size);
|
|
if (!m->import_entries)
|
|
goto fail;
|
|
for(i = 0; i < m->import_entries_count; i++) {
|
|
JSImportEntry *mi = &m->import_entries[i];
|
|
if (bc_get_leb128_int(s, &mi->var_idx))
|
|
goto fail;
|
|
if (bc_get_atom(s, &mi->import_name))
|
|
goto fail;
|
|
if (bc_get_leb128_int(s, &mi->req_module_idx))
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
m->func_obj = JS_ReadObjectRec(s);
|
|
if (JS_IsException(m->func_obj))
|
|
goto fail;
|
|
return obj;
|
|
fail:
|
|
if (m) {
|
|
js_free_module_def(ctx, m);
|
|
}
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue JS_ReadObjectTag(BCReaderState *s)
|
|
{
|
|
JSContext *ctx = s->ctx;
|
|
JSValue obj;
|
|
uint32_t prop_count, i;
|
|
JSAtom atom;
|
|
JSValue val;
|
|
int ret;
|
|
|
|
obj = JS_NewObject(ctx);
|
|
if (BC_add_object_ref(s, obj))
|
|
goto fail;
|
|
if (bc_get_leb128(s, &prop_count))
|
|
goto fail;
|
|
for(i = 0; i < prop_count; i++) {
|
|
if (bc_get_atom(s, &atom))
|
|
goto fail;
|
|
#ifdef DUMP_READ_OBJECT
|
|
if (check_dump_flag(s->ctx->rt, DUMP_READ_OBJECT)) {
|
|
bc_read_trace(s, "propname: ");
|
|
print_atom(s->ctx, atom);
|
|
printf("\n");
|
|
}
|
|
#endif
|
|
val = JS_ReadObjectRec(s);
|
|
if (JS_IsException(val)) {
|
|
JS_FreeAtom(ctx, atom);
|
|
goto fail;
|
|
}
|
|
ret = JS_DefinePropertyValue(ctx, obj, atom, val, JS_PROP_C_W_E);
|
|
JS_FreeAtom(ctx, atom);
|
|
if (ret < 0)
|
|
goto fail;
|
|
}
|
|
return obj;
|
|
fail:
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue JS_ReadArray(BCReaderState *s, int tag)
|
|
{
|
|
JSContext *ctx = s->ctx;
|
|
JSValue obj;
|
|
uint32_t len, i;
|
|
JSValue val;
|
|
int ret, prop_flags;
|
|
BOOL is_template;
|
|
|
|
obj = JS_NewArray(ctx);
|
|
if (BC_add_object_ref(s, obj))
|
|
goto fail;
|
|
is_template = (tag == BC_TAG_TEMPLATE_OBJECT);
|
|
if (bc_get_leb128(s, &len))
|
|
goto fail;
|
|
for(i = 0; i < len; i++) {
|
|
val = JS_ReadObjectRec(s);
|
|
if (JS_IsException(val))
|
|
goto fail;
|
|
if (is_template)
|
|
prop_flags = JS_PROP_ENUMERABLE;
|
|
else
|
|
prop_flags = JS_PROP_C_W_E;
|
|
ret = JS_DefinePropertyValueUint32(ctx, obj, i, val,
|
|
prop_flags);
|
|
if (ret < 0)
|
|
goto fail;
|
|
}
|
|
if (is_template) {
|
|
val = JS_ReadObjectRec(s);
|
|
if (JS_IsException(val))
|
|
goto fail;
|
|
if (!JS_IsUndefined(val)) {
|
|
ret = JS_DefinePropertyValue(ctx, obj, JS_ATOM_raw, val, 0);
|
|
if (ret < 0)
|
|
goto fail;
|
|
}
|
|
JS_PreventExtensions(ctx, obj);
|
|
}
|
|
return obj;
|
|
fail:
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue JS_ReadTypedArray(BCReaderState *s)
|
|
{
|
|
JSContext *ctx = s->ctx;
|
|
JSValue obj = JS_UNDEFINED, array_buffer = JS_UNDEFINED;
|
|
uint8_t array_tag;
|
|
JSValue args[3];
|
|
uint32_t offset, len, idx;
|
|
|
|
if (bc_get_u8(s, &array_tag))
|
|
return JS_EXCEPTION;
|
|
if (array_tag >= JS_TYPED_ARRAY_COUNT)
|
|
return JS_ThrowTypeError(ctx, "invalid typed array");
|
|
if (bc_get_leb128(s, &len))
|
|
return JS_EXCEPTION;
|
|
if (bc_get_leb128(s, &offset))
|
|
return JS_EXCEPTION;
|
|
/* XXX: this hack could be avoided if the typed array could be
|
|
created before the array buffer */
|
|
idx = s->objects_count;
|
|
if (BC_add_object_ref1(s, NULL))
|
|
goto fail;
|
|
array_buffer = JS_ReadObjectRec(s);
|
|
if (JS_IsException(array_buffer))
|
|
return JS_EXCEPTION;
|
|
if (!js_get_array_buffer(ctx, array_buffer)) {
|
|
JS_FreeValue(ctx, array_buffer);
|
|
return JS_EXCEPTION;
|
|
}
|
|
args[0] = array_buffer;
|
|
args[1] = JS_NewInt64(ctx, offset);
|
|
args[2] = JS_NewInt64(ctx, len);
|
|
obj = js_typed_array_constructor(ctx, JS_UNDEFINED,
|
|
3, args,
|
|
JS_CLASS_UINT8C_ARRAY + array_tag);
|
|
if (JS_IsException(obj))
|
|
goto fail;
|
|
if (s->allow_reference) {
|
|
s->objects[idx] = JS_VALUE_GET_OBJ(obj);
|
|
}
|
|
JS_FreeValue(ctx, array_buffer);
|
|
return obj;
|
|
fail:
|
|
JS_FreeValue(ctx, array_buffer);
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue JS_ReadArrayBuffer(BCReaderState *s)
|
|
{
|
|
JSContext *ctx = s->ctx;
|
|
uint32_t byte_length;
|
|
JSValue obj;
|
|
|
|
if (bc_get_leb128(s, &byte_length))
|
|
return JS_EXCEPTION;
|
|
if (unlikely(s->buf_end - s->ptr < byte_length)) {
|
|
bc_read_error_end(s);
|
|
return JS_EXCEPTION;
|
|
}
|
|
obj = JS_NewArrayBufferCopy(ctx, s->ptr, byte_length);
|
|
if (JS_IsException(obj))
|
|
goto fail;
|
|
if (BC_add_object_ref(s, obj))
|
|
goto fail;
|
|
s->ptr += byte_length;
|
|
return obj;
|
|
fail:
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue JS_ReadSharedArrayBuffer(BCReaderState *s)
|
|
{
|
|
JSContext *ctx = s->ctx;
|
|
uint32_t byte_length;
|
|
uint8_t *data_ptr;
|
|
JSValue obj;
|
|
uint64_t u64;
|
|
|
|
if (bc_get_leb128(s, &byte_length))
|
|
return JS_EXCEPTION;
|
|
if (bc_get_u64(s, &u64))
|
|
return JS_EXCEPTION;
|
|
data_ptr = (uint8_t *)(uintptr_t)u64;
|
|
/* the SharedArrayBuffer is cloned */
|
|
obj = js_array_buffer_constructor3(ctx, JS_UNDEFINED, byte_length,
|
|
JS_CLASS_SHARED_ARRAY_BUFFER,
|
|
data_ptr,
|
|
NULL, NULL, FALSE);
|
|
if (JS_IsException(obj))
|
|
goto fail;
|
|
if (BC_add_object_ref(s, obj))
|
|
goto fail;
|
|
return obj;
|
|
fail:
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue JS_ReadRegExp(BCReaderState *s)
|
|
{
|
|
JSContext *ctx = s->ctx;
|
|
JSString *pattern;
|
|
JSString *bc;
|
|
|
|
pattern = JS_ReadString(s);
|
|
if (!pattern)
|
|
return JS_EXCEPTION;
|
|
|
|
bc = JS_ReadString(s);
|
|
if (!bc) {
|
|
js_free_string(ctx->rt, pattern);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
assert(!bc->is_wide_char);
|
|
if (is_be())
|
|
lre_byte_swap(bc->u.str8, bc->len, /*is_byte_swapped*/TRUE);
|
|
|
|
return js_regexp_constructor_internal(ctx, JS_UNDEFINED,
|
|
JS_MKPTR(JS_TAG_STRING, pattern),
|
|
JS_MKPTR(JS_TAG_STRING, bc));
|
|
}
|
|
|
|
static JSValue JS_ReadDate(BCReaderState *s)
|
|
{
|
|
JSContext *ctx = s->ctx;
|
|
JSValue val, obj = JS_UNDEFINED;
|
|
|
|
val = JS_ReadObjectRec(s);
|
|
if (JS_IsException(val))
|
|
goto fail;
|
|
if (!JS_IsNumber(val)) {
|
|
JS_ThrowTypeError(ctx, "Number tag expected for date");
|
|
goto fail;
|
|
}
|
|
obj = JS_NewObjectProtoClass(ctx, ctx->class_proto[JS_CLASS_DATE],
|
|
JS_CLASS_DATE);
|
|
if (JS_IsException(obj))
|
|
goto fail;
|
|
if (BC_add_object_ref(s, obj))
|
|
goto fail;
|
|
JS_SetObjectData(ctx, obj, val);
|
|
return obj;
|
|
fail:
|
|
JS_FreeValue(ctx, val);
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue JS_ReadObjectValue(BCReaderState *s)
|
|
{
|
|
JSContext *ctx = s->ctx;
|
|
JSValue val, obj = JS_UNDEFINED;
|
|
|
|
val = JS_ReadObjectRec(s);
|
|
if (JS_IsException(val))
|
|
goto fail;
|
|
obj = JS_ToObject(ctx, val);
|
|
if (JS_IsException(obj))
|
|
goto fail;
|
|
if (BC_add_object_ref(s, obj))
|
|
goto fail;
|
|
JS_FreeValue(ctx, val);
|
|
return obj;
|
|
fail:
|
|
JS_FreeValue(ctx, val);
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue JS_ReadObjectRec(BCReaderState *s)
|
|
{
|
|
JSContext *ctx = s->ctx;
|
|
uint8_t tag;
|
|
JSValue obj = JS_UNDEFINED;
|
|
|
|
if (js_check_stack_overflow(ctx->rt, 0))
|
|
return JS_ThrowStackOverflow(ctx);
|
|
|
|
if (bc_get_u8(s, &tag))
|
|
return JS_EXCEPTION;
|
|
|
|
bc_read_trace(s, "%s {\n", bc_tag_str[tag]);
|
|
|
|
switch(tag) {
|
|
case BC_TAG_NULL:
|
|
obj = JS_NULL;
|
|
break;
|
|
case BC_TAG_UNDEFINED:
|
|
obj = JS_UNDEFINED;
|
|
break;
|
|
case BC_TAG_BOOL_FALSE:
|
|
case BC_TAG_BOOL_TRUE:
|
|
obj = js_bool(tag - BC_TAG_BOOL_FALSE);
|
|
break;
|
|
case BC_TAG_INT32:
|
|
{
|
|
int32_t val;
|
|
if (bc_get_sleb128(s, &val))
|
|
return JS_EXCEPTION;
|
|
bc_read_trace(s, "%d\n", val);
|
|
obj = js_int32(val);
|
|
}
|
|
break;
|
|
case BC_TAG_FLOAT64:
|
|
{
|
|
JSFloat64Union u;
|
|
if (bc_get_u64(s, &u.u64))
|
|
return JS_EXCEPTION;
|
|
bc_read_trace(s, "%g\n", u.d);
|
|
obj = js_float64(u.d);
|
|
}
|
|
break;
|
|
case BC_TAG_STRING:
|
|
{
|
|
JSString *p;
|
|
p = JS_ReadString(s);
|
|
if (!p)
|
|
return JS_EXCEPTION;
|
|
obj = JS_MKPTR(JS_TAG_STRING, p);
|
|
}
|
|
break;
|
|
case BC_TAG_FUNCTION_BYTECODE:
|
|
if (!s->allow_bytecode)
|
|
goto invalid_tag;
|
|
obj = JS_ReadFunctionTag(s);
|
|
break;
|
|
case BC_TAG_MODULE:
|
|
if (!s->allow_bytecode)
|
|
goto invalid_tag;
|
|
obj = JS_ReadModule(s);
|
|
break;
|
|
case BC_TAG_OBJECT:
|
|
obj = JS_ReadObjectTag(s);
|
|
break;
|
|
case BC_TAG_ARRAY:
|
|
case BC_TAG_TEMPLATE_OBJECT:
|
|
obj = JS_ReadArray(s, tag);
|
|
break;
|
|
case BC_TAG_TYPED_ARRAY:
|
|
obj = JS_ReadTypedArray(s);
|
|
break;
|
|
case BC_TAG_ARRAY_BUFFER:
|
|
obj = JS_ReadArrayBuffer(s);
|
|
break;
|
|
case BC_TAG_SHARED_ARRAY_BUFFER:
|
|
if (!s->allow_sab || !ctx->rt->sab_funcs.sab_dup)
|
|
goto invalid_tag;
|
|
obj = JS_ReadSharedArrayBuffer(s);
|
|
break;
|
|
case BC_TAG_REGEXP:
|
|
obj = JS_ReadRegExp(s);
|
|
break;
|
|
case BC_TAG_DATE:
|
|
obj = JS_ReadDate(s);
|
|
break;
|
|
case BC_TAG_OBJECT_VALUE:
|
|
obj = JS_ReadObjectValue(s);
|
|
break;
|
|
case BC_TAG_BIG_INT:
|
|
obj = JS_ReadBigInt(s);
|
|
break;
|
|
case BC_TAG_OBJECT_REFERENCE:
|
|
{
|
|
uint32_t val;
|
|
if (!s->allow_reference)
|
|
return JS_ThrowSyntaxError(ctx, "object references are not allowed");
|
|
if (bc_get_leb128(s, &val))
|
|
return JS_EXCEPTION;
|
|
bc_read_trace(s, "%u\n", val);
|
|
if (val >= s->objects_count) {
|
|
return JS_ThrowSyntaxError(ctx, "invalid object reference (%u >= %u)",
|
|
val, s->objects_count);
|
|
}
|
|
obj = js_dup(JS_MKPTR(JS_TAG_OBJECT, s->objects[val]));
|
|
}
|
|
break;
|
|
default:
|
|
invalid_tag:
|
|
return JS_ThrowSyntaxError(ctx, "invalid tag (tag=%d pos=%u)",
|
|
tag, (unsigned int)(s->ptr - s->buf_start));
|
|
}
|
|
bc_read_trace(s, "}\n");
|
|
return obj;
|
|
}
|
|
|
|
static int JS_ReadObjectAtoms(BCReaderState *s)
|
|
{
|
|
uint8_t v8;
|
|
JSString *p;
|
|
int i;
|
|
JSAtom atom;
|
|
|
|
if (bc_get_u8(s, &v8))
|
|
return -1;
|
|
if (v8 != BC_VERSION) {
|
|
JS_ThrowSyntaxError(s->ctx, "invalid version (%d expected=%d)",
|
|
v8, BC_VERSION);
|
|
return -1;
|
|
}
|
|
if (bc_get_leb128(s, &s->idx_to_atom_count))
|
|
return -1;
|
|
|
|
bc_read_trace(s, "%d atom indexes {\n", s->idx_to_atom_count);
|
|
|
|
if (s->idx_to_atom_count != 0) {
|
|
s->idx_to_atom = js_mallocz(s->ctx, s->idx_to_atom_count *
|
|
sizeof(s->idx_to_atom[0]));
|
|
if (!s->idx_to_atom)
|
|
return s->error_state = -1;
|
|
}
|
|
for(i = 0; i < s->idx_to_atom_count; i++) {
|
|
p = JS_ReadString(s);
|
|
if (!p)
|
|
return -1;
|
|
atom = JS_NewAtomStr(s->ctx, p);
|
|
if (atom == JS_ATOM_NULL)
|
|
return s->error_state = -1;
|
|
s->idx_to_atom[i] = atom;
|
|
}
|
|
bc_read_trace(s, "}\n");
|
|
return 0;
|
|
}
|
|
|
|
static void bc_reader_free(BCReaderState *s)
|
|
{
|
|
int i;
|
|
if (s->idx_to_atom) {
|
|
for(i = 0; i < s->idx_to_atom_count; i++) {
|
|
JS_FreeAtom(s->ctx, s->idx_to_atom[i]);
|
|
}
|
|
js_free(s->ctx, s->idx_to_atom);
|
|
}
|
|
js_free(s->ctx, s->objects);
|
|
}
|
|
|
|
JSValue JS_ReadObject(JSContext *ctx, const uint8_t *buf, size_t buf_len,
|
|
int flags)
|
|
{
|
|
BCReaderState ss, *s = &ss;
|
|
JSValue obj;
|
|
|
|
ctx->binary_object_count += 1;
|
|
ctx->binary_object_size += buf_len;
|
|
|
|
memset(s, 0, sizeof(*s));
|
|
s->ctx = ctx;
|
|
s->buf_start = buf;
|
|
s->buf_end = buf + buf_len;
|
|
s->ptr = buf;
|
|
s->allow_bytecode = ((flags & JS_READ_OBJ_BYTECODE) != 0);
|
|
s->allow_sab = ((flags & JS_READ_OBJ_SAB) != 0);
|
|
s->allow_reference = ((flags & JS_READ_OBJ_REFERENCE) != 0);
|
|
if (s->allow_bytecode)
|
|
s->first_atom = JS_ATOM_END;
|
|
else
|
|
s->first_atom = 1;
|
|
if (JS_ReadObjectAtoms(s)) {
|
|
obj = JS_EXCEPTION;
|
|
} else {
|
|
obj = JS_ReadObjectRec(s);
|
|
}
|
|
bc_reader_free(s);
|
|
return obj;
|
|
}
|
|
|
|
/*******************************************************************/
|
|
/* runtime functions & objects */
|
|
|
|
static JSValue js_string_constructor(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv);
|
|
static JSValue js_boolean_constructor(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv);
|
|
static JSValue js_number_constructor(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv);
|
|
|
|
static int check_function(JSContext *ctx, JSValue obj)
|
|
{
|
|
if (likely(JS_IsFunction(ctx, obj)))
|
|
return 0;
|
|
JS_ThrowTypeError(ctx, "not a function");
|
|
return -1;
|
|
}
|
|
|
|
static int check_exception_free(JSContext *ctx, JSValue obj)
|
|
{
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_IsException(obj);
|
|
}
|
|
|
|
/* `export_name` may be pure ASCII or UTF-8 encoded */
|
|
static JSAtom find_atom(JSContext *ctx, const char *name)
|
|
{
|
|
JSAtom atom;
|
|
int len;
|
|
|
|
if (*name == '[') {
|
|
name++;
|
|
len = strlen(name) - 1;
|
|
/* We assume 8 bit non null strings, which is the case for these
|
|
symbols */
|
|
for(atom = JS_ATOM_Symbol_toPrimitive; atom < JS_ATOM_END; atom++) {
|
|
JSAtomStruct *p = ctx->rt->atom_array[atom];
|
|
JSString *str = p;
|
|
if (str->len == len && !memcmp(str->u.str8, name, len))
|
|
return JS_DupAtom(ctx, atom);
|
|
}
|
|
abort();
|
|
} else {
|
|
atom = JS_NewAtom(ctx, name);
|
|
}
|
|
return atom;
|
|
}
|
|
|
|
static JSValue JS_InstantiateFunctionListItem2(JSContext *ctx, JSObject *p,
|
|
JSAtom atom, void *opaque)
|
|
{
|
|
const JSCFunctionListEntry *e = opaque;
|
|
JSValue val;
|
|
|
|
switch(e->def_type) {
|
|
case JS_DEF_CFUNC:
|
|
val = JS_NewCFunction2(ctx, e->u.func.cfunc.generic,
|
|
e->name, e->u.func.length, e->u.func.cproto, e->magic);
|
|
break;
|
|
case JS_DEF_PROP_STRING:
|
|
val = JS_NewAtomString(ctx, e->u.str);
|
|
break;
|
|
case JS_DEF_OBJECT:
|
|
val = JS_NewObject(ctx);
|
|
JS_SetPropertyFunctionList(ctx, val, e->u.prop_list.tab, e->u.prop_list.len);
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
return val;
|
|
}
|
|
|
|
static int JS_InstantiateFunctionListItem(JSContext *ctx, JSValue obj,
|
|
JSAtom atom,
|
|
const JSCFunctionListEntry *e)
|
|
{
|
|
JSValue val;
|
|
int prop_flags = e->prop_flags;
|
|
|
|
switch(e->def_type) {
|
|
case JS_DEF_ALIAS: /* using autoinit for aliases is not safe */
|
|
{
|
|
JSAtom atom1 = find_atom(ctx, e->u.alias.name);
|
|
switch (e->u.alias.base) {
|
|
case -1:
|
|
val = JS_GetProperty(ctx, obj, atom1);
|
|
break;
|
|
case 0:
|
|
val = JS_GetProperty(ctx, ctx->global_obj, atom1);
|
|
break;
|
|
case 1:
|
|
val = JS_GetProperty(ctx, ctx->class_proto[JS_CLASS_ARRAY], atom1);
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
JS_FreeAtom(ctx, atom1);
|
|
if (atom == JS_ATOM_Symbol_toPrimitive) {
|
|
/* Symbol.toPrimitive functions are not writable */
|
|
prop_flags = JS_PROP_CONFIGURABLE;
|
|
} else if (atom == JS_ATOM_Symbol_hasInstance) {
|
|
/* Function.prototype[Symbol.hasInstance] is not writable nor configurable */
|
|
prop_flags = 0;
|
|
}
|
|
}
|
|
break;
|
|
case JS_DEF_CFUNC:
|
|
if (atom == JS_ATOM_Symbol_toPrimitive) {
|
|
/* Symbol.toPrimitive functions are not writable */
|
|
prop_flags = JS_PROP_CONFIGURABLE;
|
|
} else if (atom == JS_ATOM_Symbol_hasInstance) {
|
|
/* Function.prototype[Symbol.hasInstance] is not writable nor configurable */
|
|
prop_flags = 0;
|
|
}
|
|
JS_DefineAutoInitProperty(ctx, obj, atom, JS_AUTOINIT_ID_PROP,
|
|
(void *)e, prop_flags);
|
|
return 0;
|
|
case JS_DEF_CGETSET: /* XXX: use autoinit again ? */
|
|
case JS_DEF_CGETSET_MAGIC:
|
|
{
|
|
JSValue getter, setter;
|
|
char buf[64];
|
|
|
|
getter = JS_UNDEFINED;
|
|
if (e->u.getset.get.generic) {
|
|
snprintf(buf, sizeof(buf), "get %s", e->name);
|
|
getter = JS_NewCFunction2(ctx, e->u.getset.get.generic,
|
|
buf, 0, e->def_type == JS_DEF_CGETSET_MAGIC ? JS_CFUNC_getter_magic : JS_CFUNC_getter,
|
|
e->magic);
|
|
}
|
|
setter = JS_UNDEFINED;
|
|
if (e->u.getset.set.generic) {
|
|
snprintf(buf, sizeof(buf), "set %s", e->name);
|
|
setter = JS_NewCFunction2(ctx, e->u.getset.set.generic,
|
|
buf, 1, e->def_type == JS_DEF_CGETSET_MAGIC ? JS_CFUNC_setter_magic : JS_CFUNC_setter,
|
|
e->magic);
|
|
}
|
|
JS_DefinePropertyGetSet(ctx, obj, atom, getter, setter, prop_flags);
|
|
return 0;
|
|
}
|
|
break;
|
|
case JS_DEF_PROP_INT32:
|
|
val = js_int32(e->u.i32);
|
|
break;
|
|
case JS_DEF_PROP_INT64:
|
|
val = JS_NewInt64(ctx, e->u.i64);
|
|
break;
|
|
case JS_DEF_PROP_DOUBLE:
|
|
val = js_float64(e->u.f64);
|
|
break;
|
|
case JS_DEF_PROP_UNDEFINED:
|
|
val = JS_UNDEFINED;
|
|
break;
|
|
case JS_DEF_PROP_STRING:
|
|
case JS_DEF_OBJECT:
|
|
JS_DefineAutoInitProperty(ctx, obj, atom, JS_AUTOINIT_ID_PROP,
|
|
(void *)e, prop_flags);
|
|
return 0;
|
|
default:
|
|
abort();
|
|
}
|
|
JS_DefinePropertyValue(ctx, obj, atom, val, prop_flags);
|
|
return 0;
|
|
}
|
|
|
|
void JS_SetPropertyFunctionList(JSContext *ctx, JSValue obj,
|
|
const JSCFunctionListEntry *tab, int len)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < len; i++) {
|
|
const JSCFunctionListEntry *e = &tab[i];
|
|
JSAtom atom = find_atom(ctx, e->name);
|
|
JS_InstantiateFunctionListItem(ctx, obj, atom, e);
|
|
JS_FreeAtom(ctx, atom);
|
|
}
|
|
}
|
|
|
|
int JS_AddModuleExportList(JSContext *ctx, JSModuleDef *m,
|
|
const JSCFunctionListEntry *tab, int len)
|
|
{
|
|
int i;
|
|
for(i = 0; i < len; i++) {
|
|
if (JS_AddModuleExport(ctx, m, tab[i].name))
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int JS_SetModuleExportList(JSContext *ctx, JSModuleDef *m,
|
|
const JSCFunctionListEntry *tab, int len)
|
|
{
|
|
int i;
|
|
JSValue val;
|
|
|
|
for(i = 0; i < len; i++) {
|
|
const JSCFunctionListEntry *e = &tab[i];
|
|
switch(e->def_type) {
|
|
case JS_DEF_CFUNC:
|
|
val = JS_NewCFunction2(ctx, e->u.func.cfunc.generic,
|
|
e->name, e->u.func.length, e->u.func.cproto, e->magic);
|
|
break;
|
|
case JS_DEF_PROP_STRING:
|
|
/* `e->u.str` may be pure ASCII or UTF-8 encoded */
|
|
val = JS_NewString(ctx, e->u.str);
|
|
break;
|
|
case JS_DEF_PROP_INT32:
|
|
val = js_int32(e->u.i32);
|
|
break;
|
|
case JS_DEF_PROP_INT64:
|
|
val = JS_NewInt64(ctx, e->u.i64);
|
|
break;
|
|
case JS_DEF_PROP_DOUBLE:
|
|
val = js_float64(e->u.f64);
|
|
break;
|
|
case JS_DEF_OBJECT:
|
|
val = JS_NewObject(ctx);
|
|
JS_SetPropertyFunctionList(ctx, val, e->u.prop_list.tab, e->u.prop_list.len);
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
if (JS_SetModuleExport(ctx, m, e->name, val))
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Note: 'func_obj' is not necessarily a constructor */
|
|
static void JS_SetConstructor2(JSContext *ctx,
|
|
JSValue func_obj,
|
|
JSValue proto,
|
|
int proto_flags, int ctor_flags)
|
|
{
|
|
JS_DefinePropertyValue(ctx, func_obj, JS_ATOM_prototype,
|
|
js_dup(proto), proto_flags);
|
|
JS_DefinePropertyValue(ctx, proto, JS_ATOM_constructor,
|
|
js_dup(func_obj), ctor_flags);
|
|
set_cycle_flag(ctx, func_obj);
|
|
set_cycle_flag(ctx, proto);
|
|
}
|
|
|
|
void JS_SetConstructor(JSContext *ctx, JSValue func_obj,
|
|
JSValue proto)
|
|
{
|
|
JS_SetConstructor2(ctx, func_obj, proto,
|
|
0, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
|
|
}
|
|
|
|
static void JS_NewGlobalCConstructor2(JSContext *ctx,
|
|
JSValue func_obj,
|
|
const char *name,
|
|
JSValue proto)
|
|
{
|
|
JS_DefinePropertyValueStr(ctx, ctx->global_obj, name,
|
|
js_dup(func_obj),
|
|
JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
|
|
JS_SetConstructor(ctx, func_obj, proto);
|
|
JS_FreeValue(ctx, func_obj);
|
|
}
|
|
|
|
static JSValue JS_NewGlobalCConstructor(JSContext *ctx, const char *name,
|
|
JSCFunction *func, int length,
|
|
JSValue proto)
|
|
{
|
|
JSValue func_obj;
|
|
func_obj = JS_NewCFunction2(ctx, func, name, length, JS_CFUNC_constructor_or_func, 0);
|
|
JS_NewGlobalCConstructor2(ctx, func_obj, name, proto);
|
|
return func_obj;
|
|
}
|
|
|
|
static JSValue JS_NewGlobalCConstructorOnly(JSContext *ctx, const char *name,
|
|
JSCFunction *func, int length,
|
|
JSValue proto)
|
|
{
|
|
JSValue func_obj;
|
|
func_obj = JS_NewCFunction2(ctx, func, name, length, JS_CFUNC_constructor, 0);
|
|
JS_NewGlobalCConstructor2(ctx, func_obj, name, proto);
|
|
return func_obj;
|
|
}
|
|
|
|
static JSValue js_global_eval(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
return JS_EvalObject(ctx, ctx->global_obj, argv[0], JS_EVAL_TYPE_INDIRECT, -1);
|
|
}
|
|
|
|
static JSValue js_global_isNaN(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
double d;
|
|
|
|
if (unlikely(JS_ToFloat64(ctx, &d, argv[0])))
|
|
return JS_EXCEPTION;
|
|
return js_bool(isnan(d));
|
|
}
|
|
|
|
static JSValue js_global_isFinite(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
BOOL res;
|
|
double d;
|
|
if (unlikely(JS_ToFloat64(ctx, &d, argv[0])))
|
|
return JS_EXCEPTION;
|
|
res = isfinite(d);
|
|
return js_bool(res);
|
|
}
|
|
|
|
static JSValue js_microtask_job(JSContext *ctx,
|
|
int argc, JSValue *argv)
|
|
{
|
|
return JS_Call(ctx, argv[0], ctx->global_obj, 0, NULL);
|
|
}
|
|
|
|
static JSValue js_global_queueMicrotask(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
if (check_function(ctx, argv[0]))
|
|
return JS_EXCEPTION;
|
|
if (JS_EnqueueJob(ctx, js_microtask_job, 1, &argv[0]))
|
|
return JS_EXCEPTION;
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
/* Object class */
|
|
|
|
static JSValue JS_ToObject(JSContext *ctx, JSValue val)
|
|
{
|
|
int tag = JS_VALUE_GET_NORM_TAG(val);
|
|
JSValue obj;
|
|
|
|
switch(tag) {
|
|
default:
|
|
case JS_TAG_NULL:
|
|
case JS_TAG_UNDEFINED:
|
|
return JS_ThrowTypeError(ctx, "Cannot convert undefined or null to object");
|
|
case JS_TAG_OBJECT:
|
|
case JS_TAG_EXCEPTION:
|
|
return js_dup(val);
|
|
case JS_TAG_BIG_INT:
|
|
obj = JS_NewObjectClass(ctx, JS_CLASS_BIG_INT);
|
|
goto set_value;
|
|
case JS_TAG_INT:
|
|
case JS_TAG_FLOAT64:
|
|
obj = JS_NewObjectClass(ctx, JS_CLASS_NUMBER);
|
|
goto set_value;
|
|
case JS_TAG_STRING:
|
|
/* XXX: should call the string constructor */
|
|
{
|
|
JSString *p1 = JS_VALUE_GET_STRING(val);
|
|
obj = JS_NewObjectClass(ctx, JS_CLASS_STRING);
|
|
JS_DefinePropertyValue(ctx, obj, JS_ATOM_length, js_int32(p1->len), 0);
|
|
}
|
|
goto set_value;
|
|
case JS_TAG_BOOL:
|
|
obj = JS_NewObjectClass(ctx, JS_CLASS_BOOLEAN);
|
|
goto set_value;
|
|
case JS_TAG_SYMBOL:
|
|
obj = JS_NewObjectClass(ctx, JS_CLASS_SYMBOL);
|
|
set_value:
|
|
if (!JS_IsException(obj))
|
|
JS_SetObjectData(ctx, obj, js_dup(val));
|
|
return obj;
|
|
}
|
|
}
|
|
|
|
static JSValue JS_ToObjectFree(JSContext *ctx, JSValue val)
|
|
{
|
|
JSValue obj = JS_ToObject(ctx, val);
|
|
JS_FreeValue(ctx, val);
|
|
return obj;
|
|
}
|
|
|
|
static int js_obj_to_desc(JSContext *ctx, JSPropertyDescriptor *d,
|
|
JSValue desc)
|
|
{
|
|
JSValue val, getter, setter;
|
|
int present;
|
|
int flags;
|
|
|
|
if (!JS_IsObject(desc)) {
|
|
JS_ThrowTypeError(ctx, "Property description must be an object");
|
|
return -1;
|
|
}
|
|
flags = 0;
|
|
val = JS_UNDEFINED;
|
|
getter = JS_UNDEFINED;
|
|
setter = JS_UNDEFINED;
|
|
present = JS_HasProperty(ctx, desc, JS_ATOM_enumerable);
|
|
if (present < 0)
|
|
goto fail;
|
|
if (present) {
|
|
JSValue prop = JS_GetProperty(ctx, desc, JS_ATOM_enumerable);
|
|
if (JS_IsException(prop))
|
|
goto fail;
|
|
flags |= JS_PROP_HAS_ENUMERABLE;
|
|
if (JS_ToBoolFree(ctx, prop))
|
|
flags |= JS_PROP_ENUMERABLE;
|
|
}
|
|
present = JS_HasProperty(ctx, desc, JS_ATOM_configurable);
|
|
if (present < 0)
|
|
goto fail;
|
|
if (present) {
|
|
JSValue prop = JS_GetProperty(ctx, desc, JS_ATOM_configurable);
|
|
if (JS_IsException(prop))
|
|
goto fail;
|
|
flags |= JS_PROP_HAS_CONFIGURABLE;
|
|
if (JS_ToBoolFree(ctx, prop))
|
|
flags |= JS_PROP_CONFIGURABLE;
|
|
}
|
|
present = JS_HasProperty(ctx, desc, JS_ATOM_value);
|
|
if (present < 0)
|
|
goto fail;
|
|
if (present) {
|
|
flags |= JS_PROP_HAS_VALUE;
|
|
val = JS_GetProperty(ctx, desc, JS_ATOM_value);
|
|
if (JS_IsException(val))
|
|
goto fail;
|
|
}
|
|
present = JS_HasProperty(ctx, desc, JS_ATOM_writable);
|
|
if (present < 0)
|
|
goto fail;
|
|
if (present) {
|
|
JSValue prop = JS_GetProperty(ctx, desc, JS_ATOM_writable);
|
|
if (JS_IsException(prop))
|
|
goto fail;
|
|
flags |= JS_PROP_HAS_WRITABLE;
|
|
if (JS_ToBoolFree(ctx, prop))
|
|
flags |= JS_PROP_WRITABLE;
|
|
}
|
|
present = JS_HasProperty(ctx, desc, JS_ATOM_get);
|
|
if (present < 0)
|
|
goto fail;
|
|
if (present) {
|
|
flags |= JS_PROP_HAS_GET;
|
|
getter = JS_GetProperty(ctx, desc, JS_ATOM_get);
|
|
if (JS_IsException(getter) ||
|
|
!(JS_IsUndefined(getter) || JS_IsFunction(ctx, getter))) {
|
|
JS_ThrowTypeError(ctx, "Getter must be a function");
|
|
goto fail;
|
|
}
|
|
}
|
|
present = JS_HasProperty(ctx, desc, JS_ATOM_set);
|
|
if (present < 0)
|
|
goto fail;
|
|
if (present) {
|
|
flags |= JS_PROP_HAS_SET;
|
|
setter = JS_GetProperty(ctx, desc, JS_ATOM_set);
|
|
if (JS_IsException(setter) ||
|
|
!(JS_IsUndefined(setter) || JS_IsFunction(ctx, setter))) {
|
|
JS_ThrowTypeError(ctx, "Setter must be a function");
|
|
goto fail;
|
|
}
|
|
}
|
|
if ((flags & (JS_PROP_HAS_SET | JS_PROP_HAS_GET)) &&
|
|
(flags & (JS_PROP_HAS_VALUE | JS_PROP_HAS_WRITABLE))) {
|
|
JS_ThrowTypeError(ctx, "Invalid property descriptor. Cannot both specify accessors and a value or writable attribute");
|
|
goto fail;
|
|
}
|
|
d->flags = flags;
|
|
d->value = val;
|
|
d->getter = getter;
|
|
d->setter = setter;
|
|
return 0;
|
|
fail:
|
|
JS_FreeValue(ctx, val);
|
|
JS_FreeValue(ctx, getter);
|
|
JS_FreeValue(ctx, setter);
|
|
return -1;
|
|
}
|
|
|
|
static __exception int JS_DefinePropertyDesc(JSContext *ctx, JSValue obj,
|
|
JSAtom prop, JSValue desc,
|
|
int flags)
|
|
{
|
|
JSPropertyDescriptor d;
|
|
int ret;
|
|
|
|
if (js_obj_to_desc(ctx, &d, desc) < 0)
|
|
return -1;
|
|
|
|
ret = JS_DefineProperty(ctx, obj, prop,
|
|
d.value, d.getter, d.setter, d.flags | flags);
|
|
js_free_desc(ctx, &d);
|
|
return ret;
|
|
}
|
|
|
|
static __exception int JS_ObjectDefineProperties(JSContext *ctx,
|
|
JSValue obj,
|
|
JSValue properties)
|
|
{
|
|
JSValue props, desc;
|
|
JSObject *p;
|
|
JSPropertyEnum *atoms;
|
|
uint32_t len, i;
|
|
int ret = -1;
|
|
|
|
if (!JS_IsObject(obj)) {
|
|
JS_ThrowTypeError(ctx, "Object.defineProperties called on non-object");
|
|
return -1;
|
|
}
|
|
desc = JS_UNDEFINED;
|
|
props = JS_ToObject(ctx, properties);
|
|
if (JS_IsException(props))
|
|
return -1;
|
|
p = JS_VALUE_GET_OBJ(props);
|
|
if (JS_GetOwnPropertyNamesInternal(ctx, &atoms, &len, p, JS_GPN_ENUM_ONLY | JS_GPN_STRING_MASK | JS_GPN_SYMBOL_MASK) < 0)
|
|
goto exception;
|
|
// XXX: ECMA specifies that all descriptions should be validated before
|
|
// modifying the object. This would require allocating an array
|
|
// JSPropertyDescriptor and use 2 separate loops.
|
|
for(i = 0; i < len; i++) {
|
|
JS_FreeValue(ctx, desc);
|
|
desc = JS_GetProperty(ctx, props, atoms[i].atom);
|
|
if (JS_IsException(desc))
|
|
goto exception;
|
|
if (JS_DefinePropertyDesc(ctx, obj, atoms[i].atom, desc,
|
|
JS_PROP_THROW | JS_PROP_DEFINE_PROPERTY) < 0)
|
|
goto exception;
|
|
}
|
|
ret = 0;
|
|
|
|
exception:
|
|
js_free_prop_enum(ctx, atoms, len);
|
|
JS_FreeValue(ctx, props);
|
|
JS_FreeValue(ctx, desc);
|
|
return ret;
|
|
}
|
|
|
|
static JSValue js_object_constructor(JSContext *ctx, JSValue new_target,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue ret;
|
|
if (!JS_IsUndefined(new_target) &&
|
|
JS_VALUE_GET_OBJ(new_target) !=
|
|
JS_VALUE_GET_OBJ(JS_GetActiveFunction(ctx))) {
|
|
ret = js_create_from_ctor(ctx, new_target, JS_CLASS_OBJECT);
|
|
} else {
|
|
int tag = JS_VALUE_GET_NORM_TAG(argv[0]);
|
|
switch(tag) {
|
|
case JS_TAG_NULL:
|
|
case JS_TAG_UNDEFINED:
|
|
ret = JS_NewObject(ctx);
|
|
break;
|
|
default:
|
|
ret = JS_ToObject(ctx, argv[0]);
|
|
break;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static JSValue js_object_create(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue proto, props;
|
|
JSValue obj;
|
|
|
|
proto = argv[0];
|
|
if (!JS_IsObject(proto) && !JS_IsNull(proto))
|
|
return JS_ThrowTypeError(ctx, "object prototype may only be an Object or null");
|
|
obj = JS_NewObjectProto(ctx, proto);
|
|
if (JS_IsException(obj))
|
|
return JS_EXCEPTION;
|
|
props = argv[1];
|
|
if (!JS_IsUndefined(props)) {
|
|
if (JS_ObjectDefineProperties(ctx, obj, props)) {
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
}
|
|
return obj;
|
|
}
|
|
|
|
static JSValue js_object_getPrototypeOf(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv, int magic)
|
|
{
|
|
JSValue val;
|
|
|
|
val = argv[0];
|
|
if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) {
|
|
/* ES6 feature non compatible with ES5.1: primitive types are
|
|
accepted */
|
|
if (magic || JS_VALUE_GET_TAG(val) == JS_TAG_NULL ||
|
|
JS_VALUE_GET_TAG(val) == JS_TAG_UNDEFINED)
|
|
return JS_ThrowTypeErrorNotAnObject(ctx);
|
|
}
|
|
return JS_GetPrototype(ctx, val);
|
|
}
|
|
|
|
static JSValue js_object_setPrototypeOf(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue obj;
|
|
obj = argv[0];
|
|
if (JS_SetPrototypeInternal(ctx, obj, argv[1], TRUE) < 0)
|
|
return JS_EXCEPTION;
|
|
return js_dup(obj);
|
|
}
|
|
|
|
/* magic = 1 if called as Reflect.defineProperty */
|
|
static JSValue js_object_defineProperty(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv, int magic)
|
|
{
|
|
JSValue obj, prop, desc;
|
|
int ret, flags;
|
|
JSAtom atom;
|
|
|
|
obj = argv[0];
|
|
prop = argv[1];
|
|
desc = argv[2];
|
|
|
|
if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)
|
|
return JS_ThrowTypeErrorNotAnObject(ctx);
|
|
atom = JS_ValueToAtom(ctx, prop);
|
|
if (unlikely(atom == JS_ATOM_NULL))
|
|
return JS_EXCEPTION;
|
|
flags = JS_PROP_THROW | JS_PROP_DEFINE_PROPERTY;
|
|
if (magic)
|
|
flags = JS_PROP_REFLECT_DEFINE_PROPERTY;
|
|
ret = JS_DefinePropertyDesc(ctx, obj, atom, desc, flags);
|
|
JS_FreeAtom(ctx, atom);
|
|
if (ret < 0) {
|
|
return JS_EXCEPTION;
|
|
} else if (magic) {
|
|
return js_bool(ret);
|
|
} else {
|
|
return js_dup(obj);
|
|
}
|
|
}
|
|
|
|
static JSValue js_object_defineProperties(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
// defineProperties(obj, properties)
|
|
JSValue obj = argv[0];
|
|
|
|
if (JS_ObjectDefineProperties(ctx, obj, argv[1]))
|
|
return JS_EXCEPTION;
|
|
else
|
|
return js_dup(obj);
|
|
}
|
|
|
|
/* magic = 1 if called as __defineSetter__ */
|
|
static JSValue js_object___defineGetter__(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv, int magic)
|
|
{
|
|
JSValue obj;
|
|
JSValue prop, value, get, set;
|
|
int ret, flags;
|
|
JSAtom atom;
|
|
|
|
prop = argv[0];
|
|
value = argv[1];
|
|
|
|
obj = JS_ToObject(ctx, this_val);
|
|
if (JS_IsException(obj))
|
|
return JS_EXCEPTION;
|
|
|
|
if (check_function(ctx, value)) {
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
atom = JS_ValueToAtom(ctx, prop);
|
|
if (unlikely(atom == JS_ATOM_NULL)) {
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
flags = JS_PROP_THROW |
|
|
JS_PROP_HAS_ENUMERABLE | JS_PROP_ENUMERABLE |
|
|
JS_PROP_HAS_CONFIGURABLE | JS_PROP_CONFIGURABLE;
|
|
if (magic) {
|
|
get = JS_UNDEFINED;
|
|
set = value;
|
|
flags |= JS_PROP_HAS_SET;
|
|
} else {
|
|
get = value;
|
|
set = JS_UNDEFINED;
|
|
flags |= JS_PROP_HAS_GET;
|
|
}
|
|
ret = JS_DefineProperty(ctx, obj, atom, JS_UNDEFINED, get, set, flags);
|
|
JS_FreeValue(ctx, obj);
|
|
JS_FreeAtom(ctx, atom);
|
|
if (ret < 0) {
|
|
return JS_EXCEPTION;
|
|
} else {
|
|
return JS_UNDEFINED;
|
|
}
|
|
}
|
|
|
|
static JSValue js_object_getOwnPropertyDescriptor(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv, int magic)
|
|
{
|
|
JSValue prop;
|
|
JSAtom atom;
|
|
JSValue ret, obj;
|
|
JSPropertyDescriptor desc;
|
|
int res, flags;
|
|
|
|
if (magic) {
|
|
/* Reflect.getOwnPropertyDescriptor case */
|
|
if (JS_VALUE_GET_TAG(argv[0]) != JS_TAG_OBJECT)
|
|
return JS_ThrowTypeErrorNotAnObject(ctx);
|
|
obj = js_dup(argv[0]);
|
|
} else {
|
|
obj = JS_ToObject(ctx, argv[0]);
|
|
if (JS_IsException(obj))
|
|
return obj;
|
|
}
|
|
prop = argv[1];
|
|
atom = JS_ValueToAtom(ctx, prop);
|
|
if (unlikely(atom == JS_ATOM_NULL))
|
|
goto exception;
|
|
ret = JS_UNDEFINED;
|
|
if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) {
|
|
res = JS_GetOwnPropertyInternal(ctx, &desc, JS_VALUE_GET_OBJ(obj), atom);
|
|
if (res < 0)
|
|
goto exception;
|
|
if (res) {
|
|
ret = JS_NewObject(ctx);
|
|
if (JS_IsException(ret))
|
|
goto exception1;
|
|
flags = JS_PROP_C_W_E | JS_PROP_THROW;
|
|
if (desc.flags & JS_PROP_GETSET) {
|
|
if (JS_DefinePropertyValue(ctx, ret, JS_ATOM_get, js_dup(desc.getter), flags) < 0
|
|
|| JS_DefinePropertyValue(ctx, ret, JS_ATOM_set, js_dup(desc.setter), flags) < 0)
|
|
goto exception1;
|
|
} else {
|
|
if (JS_DefinePropertyValue(ctx, ret, JS_ATOM_value, js_dup(desc.value), flags) < 0
|
|
|| JS_DefinePropertyValue(ctx, ret, JS_ATOM_writable,
|
|
js_bool(desc.flags & JS_PROP_WRITABLE),
|
|
flags) < 0)
|
|
goto exception1;
|
|
}
|
|
if (JS_DefinePropertyValue(ctx, ret, JS_ATOM_enumerable,
|
|
js_bool(desc.flags & JS_PROP_ENUMERABLE),
|
|
flags) < 0
|
|
|| JS_DefinePropertyValue(ctx, ret, JS_ATOM_configurable,
|
|
js_bool(desc.flags & JS_PROP_CONFIGURABLE),
|
|
flags) < 0)
|
|
goto exception1;
|
|
js_free_desc(ctx, &desc);
|
|
}
|
|
}
|
|
JS_FreeAtom(ctx, atom);
|
|
JS_FreeValue(ctx, obj);
|
|
return ret;
|
|
|
|
exception1:
|
|
js_free_desc(ctx, &desc);
|
|
JS_FreeValue(ctx, ret);
|
|
exception:
|
|
JS_FreeAtom(ctx, atom);
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue js_object_getOwnPropertyDescriptors(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
//getOwnPropertyDescriptors(obj)
|
|
JSValue obj, r;
|
|
JSObject *p;
|
|
JSPropertyEnum *props;
|
|
uint32_t len, i;
|
|
|
|
r = JS_UNDEFINED;
|
|
obj = JS_ToObject(ctx, argv[0]);
|
|
if (JS_IsException(obj))
|
|
return JS_EXCEPTION;
|
|
|
|
p = JS_VALUE_GET_OBJ(obj);
|
|
if (JS_GetOwnPropertyNamesInternal(ctx, &props, &len, p,
|
|
JS_GPN_STRING_MASK | JS_GPN_SYMBOL_MASK))
|
|
goto exception;
|
|
r = JS_NewObject(ctx);
|
|
if (JS_IsException(r))
|
|
goto exception;
|
|
for(i = 0; i < len; i++) {
|
|
JSValue atomValue, desc;
|
|
JSValue args[2];
|
|
|
|
atomValue = JS_AtomToValue(ctx, props[i].atom);
|
|
if (JS_IsException(atomValue))
|
|
goto exception;
|
|
args[0] = obj;
|
|
args[1] = atomValue;
|
|
desc = js_object_getOwnPropertyDescriptor(ctx, JS_UNDEFINED, 2, args, 0);
|
|
JS_FreeValue(ctx, atomValue);
|
|
if (JS_IsException(desc))
|
|
goto exception;
|
|
if (!JS_IsUndefined(desc)) {
|
|
if (JS_DefinePropertyValue(ctx, r, props[i].atom, desc,
|
|
JS_PROP_C_W_E | JS_PROP_THROW) < 0)
|
|
goto exception;
|
|
}
|
|
}
|
|
js_free_prop_enum(ctx, props, len);
|
|
JS_FreeValue(ctx, obj);
|
|
return r;
|
|
|
|
exception:
|
|
js_free_prop_enum(ctx, props, len);
|
|
JS_FreeValue(ctx, obj);
|
|
JS_FreeValue(ctx, r);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue JS_GetOwnPropertyNames2(JSContext *ctx, JSValue obj1,
|
|
int flags, int kind)
|
|
{
|
|
JSValue obj, r, val, key, value;
|
|
JSObject *p;
|
|
JSPropertyEnum *atoms;
|
|
uint32_t len, i, j;
|
|
|
|
r = JS_UNDEFINED;
|
|
val = JS_UNDEFINED;
|
|
obj = JS_ToObject(ctx, obj1);
|
|
if (JS_IsException(obj))
|
|
return JS_EXCEPTION;
|
|
p = JS_VALUE_GET_OBJ(obj);
|
|
if (JS_GetOwnPropertyNamesInternal(ctx, &atoms, &len, p, flags & ~JS_GPN_ENUM_ONLY))
|
|
goto exception;
|
|
r = JS_NewArray(ctx);
|
|
if (JS_IsException(r))
|
|
goto exception;
|
|
for(j = i = 0; i < len; i++) {
|
|
JSAtom atom = atoms[i].atom;
|
|
if (flags & JS_GPN_ENUM_ONLY) {
|
|
JSPropertyDescriptor desc;
|
|
int res;
|
|
|
|
/* Check if property is still enumerable */
|
|
res = JS_GetOwnPropertyInternal(ctx, &desc, p, atom);
|
|
if (res < 0)
|
|
goto exception;
|
|
if (!res)
|
|
continue;
|
|
js_free_desc(ctx, &desc);
|
|
if (!(desc.flags & JS_PROP_ENUMERABLE))
|
|
continue;
|
|
}
|
|
switch(kind) {
|
|
default:
|
|
case JS_ITERATOR_KIND_KEY:
|
|
val = JS_AtomToValue(ctx, atom);
|
|
if (JS_IsException(val))
|
|
goto exception;
|
|
break;
|
|
case JS_ITERATOR_KIND_VALUE:
|
|
val = JS_GetProperty(ctx, obj, atom);
|
|
if (JS_IsException(val))
|
|
goto exception;
|
|
break;
|
|
case JS_ITERATOR_KIND_KEY_AND_VALUE:
|
|
val = JS_NewArray(ctx);
|
|
if (JS_IsException(val))
|
|
goto exception;
|
|
key = JS_AtomToValue(ctx, atom);
|
|
if (JS_IsException(key))
|
|
goto exception1;
|
|
if (JS_CreateDataPropertyUint32(ctx, val, 0, key, JS_PROP_THROW) < 0)
|
|
goto exception1;
|
|
value = JS_GetProperty(ctx, obj, atom);
|
|
if (JS_IsException(value))
|
|
goto exception1;
|
|
if (JS_CreateDataPropertyUint32(ctx, val, 1, value, JS_PROP_THROW) < 0)
|
|
goto exception1;
|
|
break;
|
|
}
|
|
if (JS_CreateDataPropertyUint32(ctx, r, j++, val, 0) < 0)
|
|
goto exception;
|
|
}
|
|
goto done;
|
|
|
|
exception1:
|
|
JS_FreeValue(ctx, val);
|
|
exception:
|
|
JS_FreeValue(ctx, r);
|
|
r = JS_EXCEPTION;
|
|
done:
|
|
js_free_prop_enum(ctx, atoms, len);
|
|
JS_FreeValue(ctx, obj);
|
|
return r;
|
|
}
|
|
|
|
static JSValue js_object_getOwnPropertyNames(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
return JS_GetOwnPropertyNames2(ctx, argv[0],
|
|
JS_GPN_STRING_MASK, JS_ITERATOR_KIND_KEY);
|
|
}
|
|
|
|
static JSValue js_object_getOwnPropertySymbols(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
return JS_GetOwnPropertyNames2(ctx, argv[0],
|
|
JS_GPN_SYMBOL_MASK, JS_ITERATOR_KIND_KEY);
|
|
}
|
|
|
|
static JSValue js_object_groupBy(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue cb, res, iter, next, groups, k, v, prop;
|
|
JSValue args[2];
|
|
int64_t idx;
|
|
BOOL done;
|
|
|
|
// "is function?" check must be observed before argv[0] is accessed
|
|
cb = argv[1];
|
|
if (check_function(ctx, cb))
|
|
return JS_EXCEPTION;
|
|
|
|
// TODO(bnoordhuis) add fast path for arrays but as groupBy() is
|
|
// defined in terms of iterators, the fast path must check that
|
|
// this[Symbol.iterator] is the built-in array iterator
|
|
iter = JS_GetIterator(ctx, argv[0], /*is_async*/FALSE);
|
|
if (JS_IsException(iter))
|
|
return JS_EXCEPTION;
|
|
|
|
k = JS_UNDEFINED;
|
|
v = JS_UNDEFINED;
|
|
prop = JS_UNDEFINED;
|
|
groups = JS_UNDEFINED;
|
|
|
|
next = JS_GetProperty(ctx, iter, JS_ATOM_next);
|
|
if (JS_IsException(next))
|
|
goto exception;
|
|
|
|
groups = JS_NewObjectProto(ctx, JS_NULL);
|
|
if (JS_IsException(groups))
|
|
goto exception;
|
|
|
|
for (idx = 0; ; idx++) {
|
|
v = JS_IteratorNext(ctx, iter, next, 0, NULL, &done);
|
|
if (JS_IsException(v))
|
|
goto exception;
|
|
if (done)
|
|
break; // v is JS_UNDEFINED
|
|
|
|
args[0] = v;
|
|
args[1] = JS_NewInt64(ctx, idx);
|
|
k = JS_Call(ctx, cb, ctx->global_obj, 2, args);
|
|
if (JS_IsException(k))
|
|
goto exception;
|
|
|
|
k = js_dup(k);
|
|
prop = JS_GetPropertyValue(ctx, groups, k);
|
|
if (JS_IsException(prop))
|
|
goto exception;
|
|
|
|
if (JS_IsUndefined(prop)) {
|
|
prop = JS_NewArray(ctx);
|
|
if (JS_IsException(prop))
|
|
goto exception;
|
|
k = js_dup(k);
|
|
prop = js_dup(prop);
|
|
if (JS_SetPropertyValue(ctx, groups, k, prop,
|
|
JS_PROP_C_W_E|JS_PROP_THROW) < 0) {
|
|
goto exception;
|
|
}
|
|
}
|
|
|
|
res = js_array_push(ctx, prop, 1, &v, /*unshift*/0);
|
|
if (JS_IsException(res))
|
|
goto exception;
|
|
// res is an int64
|
|
|
|
JS_FreeValue(ctx, prop);
|
|
JS_FreeValue(ctx, k);
|
|
JS_FreeValue(ctx, v);
|
|
prop = JS_UNDEFINED;
|
|
k = JS_UNDEFINED;
|
|
v = JS_UNDEFINED;
|
|
}
|
|
|
|
JS_FreeValue(ctx, iter);
|
|
JS_FreeValue(ctx, next);
|
|
return groups;
|
|
|
|
exception:
|
|
JS_FreeValue(ctx, prop);
|
|
JS_FreeValue(ctx, k);
|
|
JS_FreeValue(ctx, v);
|
|
JS_FreeValue(ctx, groups);
|
|
JS_FreeValue(ctx, iter);
|
|
JS_FreeValue(ctx, next);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue js_object_keys(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv, int kind)
|
|
{
|
|
return JS_GetOwnPropertyNames2(ctx, argv[0],
|
|
JS_GPN_ENUM_ONLY | JS_GPN_STRING_MASK, kind);
|
|
}
|
|
|
|
static JSValue js_object_isExtensible(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv, int reflect)
|
|
{
|
|
JSValue obj;
|
|
int ret;
|
|
|
|
obj = argv[0];
|
|
if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) {
|
|
if (reflect)
|
|
return JS_ThrowTypeErrorNotAnObject(ctx);
|
|
else
|
|
return JS_FALSE;
|
|
}
|
|
ret = JS_IsExtensible(ctx, obj);
|
|
if (ret < 0)
|
|
return JS_EXCEPTION;
|
|
else
|
|
return js_bool(ret);
|
|
}
|
|
|
|
static JSValue js_object_preventExtensions(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv, int reflect)
|
|
{
|
|
JSValue obj;
|
|
int ret;
|
|
|
|
obj = argv[0];
|
|
if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) {
|
|
if (reflect)
|
|
return JS_ThrowTypeErrorNotAnObject(ctx);
|
|
else
|
|
return js_dup(obj);
|
|
}
|
|
ret = JS_PreventExtensions(ctx, obj);
|
|
if (ret < 0)
|
|
return JS_EXCEPTION;
|
|
if (reflect) {
|
|
return js_bool(ret);
|
|
} else {
|
|
if (!ret)
|
|
return JS_ThrowTypeError(ctx, "proxy preventExtensions handler returned false");
|
|
return js_dup(obj);
|
|
}
|
|
}
|
|
|
|
static JSValue js_object_hasOwnProperty(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue obj;
|
|
JSAtom atom;
|
|
JSObject *p;
|
|
BOOL ret;
|
|
|
|
atom = JS_ValueToAtom(ctx, argv[0]); /* must be done first */
|
|
if (unlikely(atom == JS_ATOM_NULL))
|
|
return JS_EXCEPTION;
|
|
obj = JS_ToObject(ctx, this_val);
|
|
if (JS_IsException(obj)) {
|
|
JS_FreeAtom(ctx, atom);
|
|
return obj;
|
|
}
|
|
p = JS_VALUE_GET_OBJ(obj);
|
|
ret = JS_GetOwnPropertyInternal(ctx, NULL, p, atom);
|
|
JS_FreeAtom(ctx, atom);
|
|
JS_FreeValue(ctx, obj);
|
|
if (ret < 0)
|
|
return JS_EXCEPTION;
|
|
else
|
|
return js_bool(ret);
|
|
}
|
|
|
|
static JSValue js_object_hasOwn(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue obj;
|
|
JSAtom atom;
|
|
JSObject *p;
|
|
BOOL ret;
|
|
|
|
obj = JS_ToObject(ctx, argv[0]);
|
|
if (JS_IsException(obj))
|
|
return obj;
|
|
atom = JS_ValueToAtom(ctx, argv[1]);
|
|
if (unlikely(atom == JS_ATOM_NULL)) {
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
p = JS_VALUE_GET_OBJ(obj);
|
|
ret = JS_GetOwnPropertyInternal(ctx, NULL, p, atom);
|
|
JS_FreeAtom(ctx, atom);
|
|
JS_FreeValue(ctx, obj);
|
|
if (ret < 0)
|
|
return JS_EXCEPTION;
|
|
else
|
|
return js_bool(ret);
|
|
}
|
|
|
|
static JSValue js_object_valueOf(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
return JS_ToObject(ctx, this_val);
|
|
}
|
|
|
|
static JSValue js_object_toString(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue obj, tag;
|
|
int is_array;
|
|
JSAtom atom;
|
|
JSObject *p;
|
|
|
|
if (JS_IsNull(this_val)) {
|
|
tag = js_new_string8(ctx, "Null");
|
|
} else if (JS_IsUndefined(this_val)) {
|
|
tag = js_new_string8(ctx, "Undefined");
|
|
} else {
|
|
obj = JS_ToObject(ctx, this_val);
|
|
if (JS_IsException(obj))
|
|
return obj;
|
|
is_array = JS_IsArray(ctx, obj);
|
|
if (is_array < 0) {
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
if (is_array) {
|
|
atom = JS_ATOM_Array;
|
|
} else if (JS_IsFunction(ctx, obj)) {
|
|
atom = JS_ATOM_Function;
|
|
} else {
|
|
p = JS_VALUE_GET_OBJ(obj);
|
|
switch(p->class_id) {
|
|
case JS_CLASS_STRING:
|
|
case JS_CLASS_ARGUMENTS:
|
|
case JS_CLASS_MAPPED_ARGUMENTS:
|
|
case JS_CLASS_ERROR:
|
|
case JS_CLASS_BOOLEAN:
|
|
case JS_CLASS_NUMBER:
|
|
case JS_CLASS_DATE:
|
|
case JS_CLASS_REGEXP:
|
|
atom = ctx->rt->class_array[p->class_id].class_name;
|
|
break;
|
|
default:
|
|
atom = JS_ATOM_Object;
|
|
break;
|
|
}
|
|
}
|
|
tag = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_toStringTag);
|
|
JS_FreeValue(ctx, obj);
|
|
if (JS_IsException(tag))
|
|
return JS_EXCEPTION;
|
|
if (!JS_IsString(tag)) {
|
|
JS_FreeValue(ctx, tag);
|
|
tag = JS_AtomToString(ctx, atom);
|
|
}
|
|
}
|
|
return JS_ConcatString3(ctx, "[object ", tag, "]");
|
|
}
|
|
|
|
static JSValue js_object_toLocaleString(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
return JS_Invoke(ctx, this_val, JS_ATOM_toString, 0, NULL);
|
|
}
|
|
|
|
static JSValue js_object_assign(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
// Object.assign(obj, source1)
|
|
JSValue obj, s;
|
|
int i;
|
|
|
|
s = JS_UNDEFINED;
|
|
obj = JS_ToObject(ctx, argv[0]);
|
|
if (JS_IsException(obj))
|
|
goto exception;
|
|
for (i = 1; i < argc; i++) {
|
|
if (!JS_IsNull(argv[i]) && !JS_IsUndefined(argv[i])) {
|
|
s = JS_ToObject(ctx, argv[i]);
|
|
if (JS_IsException(s))
|
|
goto exception;
|
|
if (JS_CopyDataProperties(ctx, obj, s, JS_UNDEFINED, TRUE))
|
|
goto exception;
|
|
JS_FreeValue(ctx, s);
|
|
}
|
|
}
|
|
return obj;
|
|
exception:
|
|
JS_FreeValue(ctx, obj);
|
|
JS_FreeValue(ctx, s);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue js_object_seal(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv, int freeze_flag)
|
|
{
|
|
JSValue obj = argv[0];
|
|
JSObject *p;
|
|
JSPropertyEnum *props;
|
|
uint32_t len, i;
|
|
int flags, desc_flags, res;
|
|
|
|
if (!JS_IsObject(obj))
|
|
return js_dup(obj);
|
|
|
|
p = JS_VALUE_GET_OBJ(obj);
|
|
if (p->class_id == JS_CLASS_MODULE_NS) {
|
|
return JS_ThrowTypeError(ctx, "cannot %s module namespace",
|
|
freeze_flag ? "freeze" : "seal");
|
|
}
|
|
|
|
res = JS_PreventExtensions(ctx, obj);
|
|
if (res < 0)
|
|
return JS_EXCEPTION;
|
|
if (!res) {
|
|
return JS_ThrowTypeError(ctx, "proxy preventExtensions handler returned false");
|
|
}
|
|
|
|
flags = JS_GPN_STRING_MASK | JS_GPN_SYMBOL_MASK;
|
|
if (JS_GetOwnPropertyNamesInternal(ctx, &props, &len, p, flags))
|
|
return JS_EXCEPTION;
|
|
|
|
for(i = 0; i < len; i++) {
|
|
JSPropertyDescriptor desc;
|
|
JSAtom prop = props[i].atom;
|
|
|
|
desc_flags = JS_PROP_THROW | JS_PROP_HAS_CONFIGURABLE;
|
|
if (freeze_flag) {
|
|
res = JS_GetOwnPropertyInternal(ctx, &desc, p, prop);
|
|
if (res < 0)
|
|
goto exception;
|
|
if (res) {
|
|
if (desc.flags & JS_PROP_WRITABLE)
|
|
desc_flags |= JS_PROP_HAS_WRITABLE;
|
|
js_free_desc(ctx, &desc);
|
|
}
|
|
}
|
|
if (JS_DefineProperty(ctx, obj, prop, JS_UNDEFINED,
|
|
JS_UNDEFINED, JS_UNDEFINED, desc_flags) < 0)
|
|
goto exception;
|
|
}
|
|
js_free_prop_enum(ctx, props, len);
|
|
return js_dup(obj);
|
|
|
|
exception:
|
|
js_free_prop_enum(ctx, props, len);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue js_object_isSealed(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv, int is_frozen)
|
|
{
|
|
JSValue obj = argv[0];
|
|
JSObject *p;
|
|
JSPropertyEnum *props;
|
|
uint32_t len, i;
|
|
int flags, res;
|
|
|
|
if (!JS_IsObject(obj))
|
|
return JS_TRUE;
|
|
|
|
p = JS_VALUE_GET_OBJ(obj);
|
|
flags = JS_GPN_STRING_MASK | JS_GPN_SYMBOL_MASK;
|
|
if (JS_GetOwnPropertyNamesInternal(ctx, &props, &len, p, flags))
|
|
return JS_EXCEPTION;
|
|
|
|
for(i = 0; i < len; i++) {
|
|
JSPropertyDescriptor desc;
|
|
JSAtom prop = props[i].atom;
|
|
|
|
res = JS_GetOwnPropertyInternal(ctx, &desc, p, prop);
|
|
if (res < 0)
|
|
goto exception;
|
|
if (res) {
|
|
js_free_desc(ctx, &desc);
|
|
if ((desc.flags & JS_PROP_CONFIGURABLE)
|
|
|| (is_frozen && (desc.flags & JS_PROP_WRITABLE))) {
|
|
res = FALSE;
|
|
goto done;
|
|
}
|
|
}
|
|
}
|
|
res = JS_IsExtensible(ctx, obj);
|
|
if (res < 0)
|
|
return JS_EXCEPTION;
|
|
res ^= 1;
|
|
done:
|
|
js_free_prop_enum(ctx, props, len);
|
|
return js_bool(res);
|
|
|
|
exception:
|
|
js_free_prop_enum(ctx, props, len);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue js_object_fromEntries(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue obj, iter, next_method = JS_UNDEFINED;
|
|
JSValue iterable;
|
|
BOOL done;
|
|
|
|
/* RequireObjectCoercible() not necessary because it is tested in
|
|
JS_GetIterator() by JS_GetProperty() */
|
|
iterable = argv[0];
|
|
|
|
obj = JS_NewObject(ctx);
|
|
if (JS_IsException(obj))
|
|
return obj;
|
|
|
|
iter = JS_GetIterator(ctx, iterable, FALSE);
|
|
if (JS_IsException(iter))
|
|
goto fail;
|
|
next_method = JS_GetProperty(ctx, iter, JS_ATOM_next);
|
|
if (JS_IsException(next_method))
|
|
goto fail;
|
|
|
|
for(;;) {
|
|
JSValue key, value, item;
|
|
item = JS_IteratorNext(ctx, iter, next_method, 0, NULL, &done);
|
|
if (JS_IsException(item))
|
|
goto fail;
|
|
if (done) {
|
|
JS_FreeValue(ctx, item);
|
|
break;
|
|
}
|
|
|
|
key = JS_UNDEFINED;
|
|
value = JS_UNDEFINED;
|
|
if (!JS_IsObject(item)) {
|
|
JS_ThrowTypeErrorNotAnObject(ctx);
|
|
goto fail1;
|
|
}
|
|
key = JS_GetPropertyUint32(ctx, item, 0);
|
|
if (JS_IsException(key))
|
|
goto fail1;
|
|
value = JS_GetPropertyUint32(ctx, item, 1);
|
|
if (JS_IsException(value)) {
|
|
JS_FreeValue(ctx, key);
|
|
goto fail1;
|
|
}
|
|
if (JS_DefinePropertyValueValue(ctx, obj, key, value,
|
|
JS_PROP_C_W_E | JS_PROP_THROW) < 0) {
|
|
fail1:
|
|
JS_FreeValue(ctx, item);
|
|
goto fail;
|
|
}
|
|
JS_FreeValue(ctx, item);
|
|
}
|
|
JS_FreeValue(ctx, next_method);
|
|
JS_FreeValue(ctx, iter);
|
|
return obj;
|
|
fail:
|
|
if (JS_IsObject(iter)) {
|
|
/* close the iterator object, preserving pending exception */
|
|
JS_IteratorClose(ctx, iter, TRUE);
|
|
}
|
|
JS_FreeValue(ctx, next_method);
|
|
JS_FreeValue(ctx, iter);
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue js_object_is(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
return js_bool(js_same_value(ctx, argv[0], argv[1]));
|
|
}
|
|
|
|
static JSValue JS_SpeciesConstructor(JSContext *ctx, JSValue obj,
|
|
JSValue defaultConstructor)
|
|
{
|
|
JSValue ctor, species;
|
|
|
|
if (!JS_IsObject(obj))
|
|
return JS_ThrowTypeErrorNotAnObject(ctx);
|
|
ctor = JS_GetProperty(ctx, obj, JS_ATOM_constructor);
|
|
if (JS_IsException(ctor))
|
|
return ctor;
|
|
if (JS_IsUndefined(ctor))
|
|
return js_dup(defaultConstructor);
|
|
if (!JS_IsObject(ctor)) {
|
|
JS_FreeValue(ctx, ctor);
|
|
return JS_ThrowTypeErrorNotAnObject(ctx);
|
|
}
|
|
species = JS_GetProperty(ctx, ctor, JS_ATOM_Symbol_species);
|
|
JS_FreeValue(ctx, ctor);
|
|
if (JS_IsException(species))
|
|
return species;
|
|
if (JS_IsUndefined(species) || JS_IsNull(species))
|
|
return js_dup(defaultConstructor);
|
|
if (!JS_IsConstructor(ctx, species)) {
|
|
JS_FreeValue(ctx, species);
|
|
return JS_ThrowTypeError(ctx, "not a constructor");
|
|
}
|
|
return species;
|
|
}
|
|
|
|
static JSValue js_object_get___proto__(JSContext *ctx, JSValue this_val)
|
|
{
|
|
JSValue val, ret;
|
|
|
|
val = JS_ToObject(ctx, this_val);
|
|
if (JS_IsException(val))
|
|
return val;
|
|
ret = JS_GetPrototype(ctx, val);
|
|
JS_FreeValue(ctx, val);
|
|
return ret;
|
|
}
|
|
|
|
static JSValue js_object_set___proto__(JSContext *ctx, JSValue this_val,
|
|
JSValue proto)
|
|
{
|
|
if (JS_IsUndefined(this_val) || JS_IsNull(this_val))
|
|
return JS_ThrowTypeErrorNotAnObject(ctx);
|
|
if (!JS_IsObject(proto) && !JS_IsNull(proto))
|
|
return JS_UNDEFINED;
|
|
if (JS_SetPrototypeInternal(ctx, this_val, proto, TRUE) < 0)
|
|
return JS_EXCEPTION;
|
|
else
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
static JSValue js_object_isPrototypeOf(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue obj, v1;
|
|
JSValue v;
|
|
int res;
|
|
|
|
v = argv[0];
|
|
if (!JS_IsObject(v))
|
|
return JS_FALSE;
|
|
obj = JS_ToObject(ctx, this_val);
|
|
if (JS_IsException(obj))
|
|
return JS_EXCEPTION;
|
|
v1 = js_dup(v);
|
|
for(;;) {
|
|
v1 = JS_GetPrototypeFree(ctx, v1);
|
|
if (JS_IsException(v1))
|
|
goto exception;
|
|
if (JS_IsNull(v1)) {
|
|
res = FALSE;
|
|
break;
|
|
}
|
|
if (JS_VALUE_GET_OBJ(obj) == JS_VALUE_GET_OBJ(v1)) {
|
|
res = TRUE;
|
|
break;
|
|
}
|
|
/* avoid infinite loop (possible with proxies) */
|
|
if (js_poll_interrupts(ctx))
|
|
goto exception;
|
|
}
|
|
JS_FreeValue(ctx, v1);
|
|
JS_FreeValue(ctx, obj);
|
|
return js_bool(res);
|
|
|
|
exception:
|
|
JS_FreeValue(ctx, v1);
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue js_object_propertyIsEnumerable(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue obj, res = JS_EXCEPTION;
|
|
JSAtom prop = JS_ATOM_NULL;
|
|
JSPropertyDescriptor desc;
|
|
int has_prop;
|
|
|
|
obj = JS_ToObject(ctx, this_val);
|
|
if (JS_IsException(obj))
|
|
goto exception;
|
|
prop = JS_ValueToAtom(ctx, argv[0]);
|
|
if (unlikely(prop == JS_ATOM_NULL))
|
|
goto exception;
|
|
|
|
has_prop = JS_GetOwnPropertyInternal(ctx, &desc, JS_VALUE_GET_OBJ(obj), prop);
|
|
if (has_prop < 0)
|
|
goto exception;
|
|
if (has_prop) {
|
|
res = js_bool(desc.flags & JS_PROP_ENUMERABLE);
|
|
js_free_desc(ctx, &desc);
|
|
} else {
|
|
res = JS_FALSE;
|
|
}
|
|
|
|
exception:
|
|
JS_FreeAtom(ctx, prop);
|
|
JS_FreeValue(ctx, obj);
|
|
return res;
|
|
}
|
|
|
|
static JSValue js_object___lookupGetter__(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv, int setter)
|
|
{
|
|
JSValue obj, res = JS_EXCEPTION;
|
|
JSAtom prop = JS_ATOM_NULL;
|
|
JSPropertyDescriptor desc;
|
|
int has_prop;
|
|
|
|
obj = JS_ToObject(ctx, this_val);
|
|
if (JS_IsException(obj))
|
|
goto exception;
|
|
prop = JS_ValueToAtom(ctx, argv[0]);
|
|
if (unlikely(prop == JS_ATOM_NULL))
|
|
goto exception;
|
|
|
|
for (;;) {
|
|
has_prop = JS_GetOwnPropertyInternal(ctx, &desc, JS_VALUE_GET_OBJ(obj), prop);
|
|
if (has_prop < 0)
|
|
goto exception;
|
|
if (has_prop) {
|
|
if (desc.flags & JS_PROP_GETSET)
|
|
res = js_dup(setter ? desc.setter : desc.getter);
|
|
else
|
|
res = JS_UNDEFINED;
|
|
js_free_desc(ctx, &desc);
|
|
break;
|
|
}
|
|
obj = JS_GetPrototypeFree(ctx, obj);
|
|
if (JS_IsException(obj))
|
|
goto exception;
|
|
if (JS_IsNull(obj)) {
|
|
res = JS_UNDEFINED;
|
|
break;
|
|
}
|
|
/* avoid infinite loop (possible with proxies) */
|
|
if (js_poll_interrupts(ctx))
|
|
goto exception;
|
|
}
|
|
|
|
exception:
|
|
JS_FreeAtom(ctx, prop);
|
|
JS_FreeValue(ctx, obj);
|
|
return res;
|
|
}
|
|
|
|
static const JSCFunctionListEntry js_object_funcs[] = {
|
|
JS_CFUNC_DEF("create", 2, js_object_create ),
|
|
JS_CFUNC_MAGIC_DEF("getPrototypeOf", 1, js_object_getPrototypeOf, 0 ),
|
|
JS_CFUNC_DEF("setPrototypeOf", 2, js_object_setPrototypeOf ),
|
|
JS_CFUNC_MAGIC_DEF("defineProperty", 3, js_object_defineProperty, 0 ),
|
|
JS_CFUNC_DEF("defineProperties", 2, js_object_defineProperties ),
|
|
JS_CFUNC_DEF("getOwnPropertyNames", 1, js_object_getOwnPropertyNames ),
|
|
JS_CFUNC_DEF("getOwnPropertySymbols", 1, js_object_getOwnPropertySymbols ),
|
|
JS_CFUNC_DEF("groupBy", 2, js_object_groupBy ),
|
|
JS_CFUNC_MAGIC_DEF("keys", 1, js_object_keys, JS_ITERATOR_KIND_KEY ),
|
|
JS_CFUNC_MAGIC_DEF("values", 1, js_object_keys, JS_ITERATOR_KIND_VALUE ),
|
|
JS_CFUNC_MAGIC_DEF("entries", 1, js_object_keys, JS_ITERATOR_KIND_KEY_AND_VALUE ),
|
|
JS_CFUNC_MAGIC_DEF("isExtensible", 1, js_object_isExtensible, 0 ),
|
|
JS_CFUNC_MAGIC_DEF("preventExtensions", 1, js_object_preventExtensions, 0 ),
|
|
JS_CFUNC_MAGIC_DEF("getOwnPropertyDescriptor", 2, js_object_getOwnPropertyDescriptor, 0 ),
|
|
JS_CFUNC_DEF("getOwnPropertyDescriptors", 1, js_object_getOwnPropertyDescriptors ),
|
|
JS_CFUNC_DEF("is", 2, js_object_is ),
|
|
JS_CFUNC_DEF("assign", 2, js_object_assign ),
|
|
JS_CFUNC_MAGIC_DEF("seal", 1, js_object_seal, 0 ),
|
|
JS_CFUNC_MAGIC_DEF("freeze", 1, js_object_seal, 1 ),
|
|
JS_CFUNC_MAGIC_DEF("isSealed", 1, js_object_isSealed, 0 ),
|
|
JS_CFUNC_MAGIC_DEF("isFrozen", 1, js_object_isSealed, 1 ),
|
|
JS_CFUNC_DEF("fromEntries", 1, js_object_fromEntries ),
|
|
JS_CFUNC_DEF("hasOwn", 2, js_object_hasOwn ),
|
|
};
|
|
|
|
static const JSCFunctionListEntry js_object_proto_funcs[] = {
|
|
JS_CFUNC_DEF("toString", 0, js_object_toString ),
|
|
JS_CFUNC_DEF("toLocaleString", 0, js_object_toLocaleString ),
|
|
JS_CFUNC_DEF("valueOf", 0, js_object_valueOf ),
|
|
JS_CFUNC_DEF("hasOwnProperty", 1, js_object_hasOwnProperty ),
|
|
JS_CFUNC_DEF("isPrototypeOf", 1, js_object_isPrototypeOf ),
|
|
JS_CFUNC_DEF("propertyIsEnumerable", 1, js_object_propertyIsEnumerable ),
|
|
JS_CGETSET_DEF("__proto__", js_object_get___proto__, js_object_set___proto__ ),
|
|
JS_CFUNC_MAGIC_DEF("__defineGetter__", 2, js_object___defineGetter__, 0 ),
|
|
JS_CFUNC_MAGIC_DEF("__defineSetter__", 2, js_object___defineGetter__, 1 ),
|
|
JS_CFUNC_MAGIC_DEF("__lookupGetter__", 1, js_object___lookupGetter__, 0 ),
|
|
JS_CFUNC_MAGIC_DEF("__lookupSetter__", 1, js_object___lookupGetter__, 1 ),
|
|
};
|
|
|
|
/* Function class */
|
|
|
|
static JSValue js_function_proto(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
/* XXX: add a specific eval mode so that Function("}), ({") is rejected */
|
|
static JSValue js_function_constructor(JSContext *ctx, JSValue new_target,
|
|
int argc, JSValue *argv, int magic)
|
|
{
|
|
JSFunctionKindEnum func_kind = magic;
|
|
int i, n, ret;
|
|
JSValue s, proto, obj = JS_UNDEFINED;
|
|
StringBuffer b_s, *b = &b_s;
|
|
|
|
string_buffer_init(ctx, b, 0);
|
|
string_buffer_putc8(b, '(');
|
|
|
|
if (func_kind == JS_FUNC_ASYNC || func_kind == JS_FUNC_ASYNC_GENERATOR) {
|
|
string_buffer_puts8(b, "async ");
|
|
}
|
|
string_buffer_puts8(b, "function");
|
|
|
|
if (func_kind == JS_FUNC_GENERATOR || func_kind == JS_FUNC_ASYNC_GENERATOR) {
|
|
string_buffer_putc8(b, '*');
|
|
}
|
|
string_buffer_puts8(b, " anonymous(");
|
|
|
|
n = argc - 1;
|
|
for(i = 0; i < n; i++) {
|
|
if (i != 0) {
|
|
string_buffer_putc8(b, ',');
|
|
}
|
|
if (string_buffer_concat_value(b, argv[i]))
|
|
goto fail;
|
|
}
|
|
string_buffer_puts8(b, "\n) {\n");
|
|
if (n >= 0) {
|
|
if (string_buffer_concat_value(b, argv[n]))
|
|
goto fail;
|
|
}
|
|
string_buffer_puts8(b, "\n})");
|
|
s = string_buffer_end(b);
|
|
if (JS_IsException(s))
|
|
goto fail1;
|
|
|
|
obj = JS_EvalObject(ctx, ctx->global_obj, s, JS_EVAL_TYPE_INDIRECT, -1);
|
|
JS_FreeValue(ctx, s);
|
|
if (JS_IsException(obj))
|
|
goto fail1;
|
|
if (!JS_IsUndefined(new_target)) {
|
|
/* set the prototype */
|
|
proto = JS_GetProperty(ctx, new_target, JS_ATOM_prototype);
|
|
if (JS_IsException(proto))
|
|
goto fail1;
|
|
if (!JS_IsObject(proto)) {
|
|
JSContext *realm;
|
|
JS_FreeValue(ctx, proto);
|
|
realm = JS_GetFunctionRealm(ctx, new_target);
|
|
if (!realm)
|
|
goto fail1;
|
|
proto = js_dup(realm->class_proto[func_kind_to_class_id[func_kind]]);
|
|
}
|
|
ret = JS_SetPrototypeInternal(ctx, obj, proto, TRUE);
|
|
JS_FreeValue(ctx, proto);
|
|
if (ret < 0)
|
|
goto fail1;
|
|
}
|
|
return obj;
|
|
|
|
fail:
|
|
string_buffer_free(b);
|
|
fail1:
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static __exception int js_get_length32(JSContext *ctx, uint32_t *pres,
|
|
JSValue obj)
|
|
{
|
|
JSValue len_val;
|
|
len_val = JS_GetProperty(ctx, obj, JS_ATOM_length);
|
|
if (JS_IsException(len_val)) {
|
|
*pres = 0;
|
|
return -1;
|
|
}
|
|
return JS_ToUint32Free(ctx, pres, len_val);
|
|
}
|
|
|
|
static __exception int js_get_length64(JSContext *ctx, int64_t *pres,
|
|
JSValue obj)
|
|
{
|
|
JSValue len_val;
|
|
len_val = JS_GetProperty(ctx, obj, JS_ATOM_length);
|
|
if (JS_IsException(len_val)) {
|
|
*pres = 0;
|
|
return -1;
|
|
}
|
|
return JS_ToLengthFree(ctx, pres, len_val);
|
|
}
|
|
|
|
static void free_arg_list(JSContext *ctx, JSValue *tab, uint32_t len)
|
|
{
|
|
uint32_t i;
|
|
for(i = 0; i < len; i++) {
|
|
JS_FreeValue(ctx, tab[i]);
|
|
}
|
|
js_free(ctx, tab);
|
|
}
|
|
|
|
/* XXX: should use ValueArray */
|
|
static JSValue *build_arg_list(JSContext *ctx, uint32_t *plen,
|
|
JSValue array_arg)
|
|
{
|
|
uint32_t len, i;
|
|
JSValue *tab, ret;
|
|
JSObject *p;
|
|
|
|
if (JS_VALUE_GET_TAG(array_arg) != JS_TAG_OBJECT) {
|
|
JS_ThrowTypeError(ctx, "not a object");
|
|
return NULL;
|
|
}
|
|
if (js_get_length32(ctx, &len, array_arg))
|
|
return NULL;
|
|
if (len > JS_MAX_LOCAL_VARS) {
|
|
// XXX: check for stack overflow?
|
|
JS_ThrowRangeError(ctx, "too many arguments in function call (only %d allowed)",
|
|
JS_MAX_LOCAL_VARS);
|
|
return NULL;
|
|
}
|
|
/* avoid allocating 0 bytes */
|
|
tab = js_mallocz(ctx, sizeof(tab[0]) * max_uint32(1, len));
|
|
if (!tab)
|
|
return NULL;
|
|
p = JS_VALUE_GET_OBJ(array_arg);
|
|
if ((p->class_id == JS_CLASS_ARRAY || p->class_id == JS_CLASS_ARGUMENTS) &&
|
|
p->fast_array &&
|
|
len == p->u.array.count) {
|
|
for(i = 0; i < len; i++) {
|
|
tab[i] = js_dup(p->u.array.u.values[i]);
|
|
}
|
|
} else {
|
|
for(i = 0; i < len; i++) {
|
|
ret = JS_GetPropertyUint32(ctx, array_arg, i);
|
|
if (JS_IsException(ret)) {
|
|
free_arg_list(ctx, tab, i);
|
|
return NULL;
|
|
}
|
|
tab[i] = ret;
|
|
}
|
|
}
|
|
*plen = len;
|
|
return tab;
|
|
}
|
|
|
|
/* magic value: 0 = normal apply, 1 = apply for constructor, 2 =
|
|
Reflect.apply */
|
|
static JSValue js_function_apply(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv, int magic)
|
|
{
|
|
JSValue this_arg, array_arg;
|
|
uint32_t len;
|
|
JSValue *tab, ret;
|
|
|
|
if (check_function(ctx, this_val))
|
|
return JS_EXCEPTION;
|
|
this_arg = argv[0];
|
|
array_arg = argv[1];
|
|
if ((JS_VALUE_GET_TAG(array_arg) == JS_TAG_UNDEFINED ||
|
|
JS_VALUE_GET_TAG(array_arg) == JS_TAG_NULL) && magic != 2) {
|
|
return JS_Call(ctx, this_val, this_arg, 0, NULL);
|
|
}
|
|
tab = build_arg_list(ctx, &len, array_arg);
|
|
if (!tab)
|
|
return JS_EXCEPTION;
|
|
if (magic & 1) {
|
|
ret = JS_CallConstructor2(ctx, this_val, this_arg, len, tab);
|
|
} else {
|
|
ret = JS_Call(ctx, this_val, this_arg, len, tab);
|
|
}
|
|
free_arg_list(ctx, tab, len);
|
|
return ret;
|
|
}
|
|
|
|
static JSValue js_function_call(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
if (argc <= 0) {
|
|
return JS_Call(ctx, this_val, JS_UNDEFINED, 0, NULL);
|
|
} else {
|
|
return JS_Call(ctx, this_val, argv[0], argc - 1, argv + 1);
|
|
}
|
|
}
|
|
|
|
static JSValue js_function_bind(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSBoundFunction *bf;
|
|
JSValue func_obj, name1, len_val;
|
|
JSObject *p;
|
|
int arg_count, i, ret;
|
|
|
|
if (check_function(ctx, this_val))
|
|
return JS_EXCEPTION;
|
|
|
|
func_obj = JS_NewObjectProtoClass(ctx, ctx->function_proto,
|
|
JS_CLASS_BOUND_FUNCTION);
|
|
if (JS_IsException(func_obj))
|
|
return JS_EXCEPTION;
|
|
p = JS_VALUE_GET_OBJ(func_obj);
|
|
p->is_constructor = JS_IsConstructor(ctx, this_val);
|
|
arg_count = max_int(0, argc - 1);
|
|
bf = js_malloc(ctx, sizeof(*bf) + arg_count * sizeof(JSValue));
|
|
if (!bf)
|
|
goto exception;
|
|
bf->func_obj = js_dup(this_val);
|
|
bf->this_val = js_dup(argv[0]);
|
|
bf->argc = arg_count;
|
|
for(i = 0; i < arg_count; i++) {
|
|
bf->argv[i] = js_dup(argv[i + 1]);
|
|
}
|
|
p->u.bound_function = bf;
|
|
|
|
/* XXX: the spec could be simpler by only using GetOwnProperty */
|
|
ret = JS_GetOwnProperty(ctx, NULL, this_val, JS_ATOM_length);
|
|
if (ret < 0)
|
|
goto exception;
|
|
if (!ret) {
|
|
len_val = js_int32(0);
|
|
} else {
|
|
len_val = JS_GetProperty(ctx, this_val, JS_ATOM_length);
|
|
if (JS_IsException(len_val))
|
|
goto exception;
|
|
if (JS_VALUE_GET_TAG(len_val) == JS_TAG_INT) {
|
|
/* most common case */
|
|
int len1 = JS_VALUE_GET_INT(len_val);
|
|
if (len1 <= arg_count)
|
|
len1 = 0;
|
|
else
|
|
len1 -= arg_count;
|
|
len_val = js_int32(len1);
|
|
} else if (JS_VALUE_GET_NORM_TAG(len_val) == JS_TAG_FLOAT64) {
|
|
double d = JS_VALUE_GET_FLOAT64(len_val);
|
|
if (isnan(d)) {
|
|
d = 0.0;
|
|
} else {
|
|
d = trunc(d);
|
|
if (d <= (double)arg_count)
|
|
d = 0.0;
|
|
else
|
|
d -= (double)arg_count; /* also converts -0 to +0 */
|
|
}
|
|
len_val = js_number(d);
|
|
} else {
|
|
JS_FreeValue(ctx, len_val);
|
|
len_val = js_int32(0);
|
|
}
|
|
}
|
|
JS_DefinePropertyValue(ctx, func_obj, JS_ATOM_length,
|
|
len_val, JS_PROP_CONFIGURABLE);
|
|
|
|
name1 = JS_GetProperty(ctx, this_val, JS_ATOM_name);
|
|
if (JS_IsException(name1))
|
|
goto exception;
|
|
if (!JS_IsString(name1)) {
|
|
JS_FreeValue(ctx, name1);
|
|
name1 = JS_AtomToString(ctx, JS_ATOM_empty_string);
|
|
}
|
|
name1 = JS_ConcatString3(ctx, "bound ", name1, "");
|
|
if (JS_IsException(name1))
|
|
goto exception;
|
|
JS_DefinePropertyValue(ctx, func_obj, JS_ATOM_name, name1,
|
|
JS_PROP_CONFIGURABLE);
|
|
return func_obj;
|
|
exception:
|
|
JS_FreeValue(ctx, func_obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue js_function_toString(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSObject *p;
|
|
JSFunctionKindEnum func_kind = JS_FUNC_NORMAL;
|
|
|
|
if (check_function(ctx, this_val))
|
|
return JS_EXCEPTION;
|
|
|
|
p = JS_VALUE_GET_OBJ(this_val);
|
|
if (js_class_has_bytecode(p->class_id)) {
|
|
JSFunctionBytecode *b = p->u.func.function_bytecode;
|
|
/* `b->source` must be pure ASCII or UTF-8 encoded */
|
|
if (b->source)
|
|
return JS_NewStringLen(ctx, b->source, b->source_len);
|
|
}
|
|
{
|
|
JSValue name;
|
|
const char *pref, *suff;
|
|
|
|
switch(func_kind) {
|
|
default:
|
|
case JS_FUNC_NORMAL:
|
|
pref = "function ";
|
|
break;
|
|
case JS_FUNC_GENERATOR:
|
|
pref = "function *";
|
|
break;
|
|
case JS_FUNC_ASYNC:
|
|
pref = "async function ";
|
|
break;
|
|
case JS_FUNC_ASYNC_GENERATOR:
|
|
pref = "async function *";
|
|
break;
|
|
}
|
|
suff = "() {\n [native code]\n}";
|
|
name = JS_GetProperty(ctx, this_val, JS_ATOM_name);
|
|
if (JS_IsUndefined(name))
|
|
name = JS_AtomToString(ctx, JS_ATOM_empty_string);
|
|
return JS_ConcatString3(ctx, pref, name, suff);
|
|
}
|
|
}
|
|
|
|
static JSValue js_function_hasInstance(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
int ret;
|
|
ret = JS_OrdinaryIsInstanceOf(ctx, argv[0], this_val);
|
|
if (ret < 0)
|
|
return JS_EXCEPTION;
|
|
else
|
|
return js_bool(ret);
|
|
}
|
|
|
|
static const JSCFunctionListEntry js_function_proto_funcs[] = {
|
|
JS_CFUNC_DEF("call", 1, js_function_call ),
|
|
JS_CFUNC_MAGIC_DEF("apply", 2, js_function_apply, 0 ),
|
|
JS_CFUNC_DEF("bind", 1, js_function_bind ),
|
|
JS_CFUNC_DEF("toString", 0, js_function_toString ),
|
|
JS_CFUNC_DEF("[Symbol.hasInstance]", 1, js_function_hasInstance ),
|
|
JS_CGETSET_DEF("fileName", js_function_proto_fileName, NULL ),
|
|
JS_CGETSET_MAGIC_DEF("lineNumber", js_function_proto_int32, NULL,
|
|
offsetof(JSFunctionBytecode, line_num)),
|
|
JS_CGETSET_MAGIC_DEF("columnNumber", js_function_proto_int32, NULL,
|
|
offsetof(JSFunctionBytecode, col_num)),
|
|
};
|
|
|
|
/* Error class */
|
|
|
|
static JSValue iterator_to_array(JSContext *ctx, JSValue items)
|
|
{
|
|
JSValue iter, next_method = JS_UNDEFINED;
|
|
JSValue v, r = JS_UNDEFINED;
|
|
int64_t k;
|
|
BOOL done;
|
|
|
|
iter = JS_GetIterator(ctx, items, FALSE);
|
|
if (JS_IsException(iter))
|
|
goto exception;
|
|
next_method = JS_GetProperty(ctx, iter, JS_ATOM_next);
|
|
if (JS_IsException(next_method))
|
|
goto exception;
|
|
r = JS_NewArray(ctx);
|
|
if (JS_IsException(r))
|
|
goto exception;
|
|
for (k = 0;; k++) {
|
|
v = JS_IteratorNext(ctx, iter, next_method, 0, NULL, &done);
|
|
if (JS_IsException(v))
|
|
goto exception_close;
|
|
if (done)
|
|
break;
|
|
if (JS_DefinePropertyValueInt64(ctx, r, k, v,
|
|
JS_PROP_C_W_E | JS_PROP_THROW) < 0)
|
|
goto exception_close;
|
|
}
|
|
done:
|
|
JS_FreeValue(ctx, next_method);
|
|
JS_FreeValue(ctx, iter);
|
|
return r;
|
|
exception_close:
|
|
JS_IteratorClose(ctx, iter, TRUE);
|
|
exception:
|
|
JS_FreeValue(ctx, r);
|
|
r = JS_EXCEPTION;
|
|
goto done;
|
|
}
|
|
|
|
static JSValue js_error_constructor(JSContext *ctx, JSValue new_target,
|
|
int argc, JSValue *argv, int magic)
|
|
{
|
|
JSValue obj, msg, proto, cause;
|
|
JSValue message;
|
|
int opts;
|
|
BOOL present;
|
|
|
|
if (JS_IsUndefined(new_target))
|
|
new_target = JS_GetActiveFunction(ctx);
|
|
proto = JS_GetProperty(ctx, new_target, JS_ATOM_prototype);
|
|
if (JS_IsException(proto))
|
|
return proto;
|
|
if (!JS_IsObject(proto)) {
|
|
JSContext *realm;
|
|
JSValue proto1;
|
|
|
|
JS_FreeValue(ctx, proto);
|
|
realm = JS_GetFunctionRealm(ctx, new_target);
|
|
if (!realm)
|
|
return JS_EXCEPTION;
|
|
if (magic < 0) {
|
|
proto1 = realm->class_proto[JS_CLASS_ERROR];
|
|
} else {
|
|
proto1 = realm->native_error_proto[magic];
|
|
}
|
|
proto = js_dup(proto1);
|
|
}
|
|
obj = JS_NewObjectProtoClass(ctx, proto, JS_CLASS_ERROR);
|
|
JS_FreeValue(ctx, proto);
|
|
if (JS_IsException(obj))
|
|
return obj;
|
|
if (magic == JS_AGGREGATE_ERROR) {
|
|
message = argv[1];
|
|
opts = 2;
|
|
} else {
|
|
message = argv[0];
|
|
opts = 1;
|
|
}
|
|
|
|
if (!JS_IsUndefined(message)) {
|
|
msg = JS_ToString(ctx, message);
|
|
if (unlikely(JS_IsException(msg)))
|
|
goto exception;
|
|
JS_DefinePropertyValue(ctx, obj, JS_ATOM_message, msg,
|
|
JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
|
|
}
|
|
|
|
if (argc > opts && JS_VALUE_GET_TAG(argv[opts]) == JS_TAG_OBJECT) {
|
|
present = JS_HasProperty(ctx, argv[opts], JS_ATOM_cause);
|
|
if (unlikely(present < 0))
|
|
goto exception;
|
|
if (present) {
|
|
cause = JS_GetProperty(ctx, argv[opts], JS_ATOM_cause);
|
|
if (unlikely(JS_IsException(cause)))
|
|
goto exception;
|
|
JS_DefinePropertyValue(ctx, obj, JS_ATOM_cause, cause,
|
|
JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
|
|
}
|
|
}
|
|
|
|
if (magic == JS_AGGREGATE_ERROR) {
|
|
JSValue error_list = iterator_to_array(ctx, argv[0]);
|
|
if (JS_IsException(error_list))
|
|
goto exception;
|
|
JS_DefinePropertyValue(ctx, obj, JS_ATOM_errors, error_list,
|
|
JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
|
|
}
|
|
|
|
/* skip the Error() function in the backtrace */
|
|
build_backtrace(ctx, obj, NULL, 0, 0, JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL);
|
|
return obj;
|
|
exception:
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue js_error_toString(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue name, msg;
|
|
|
|
if (!JS_IsObject(this_val))
|
|
return JS_ThrowTypeErrorNotAnObject(ctx);
|
|
name = JS_GetProperty(ctx, this_val, JS_ATOM_name);
|
|
if (JS_IsUndefined(name))
|
|
name = JS_AtomToString(ctx, JS_ATOM_Error);
|
|
else
|
|
name = JS_ToStringFree(ctx, name);
|
|
if (JS_IsException(name))
|
|
return JS_EXCEPTION;
|
|
|
|
msg = JS_GetProperty(ctx, this_val, JS_ATOM_message);
|
|
if (JS_IsUndefined(msg))
|
|
msg = JS_AtomToString(ctx, JS_ATOM_empty_string);
|
|
else
|
|
msg = JS_ToStringFree(ctx, msg);
|
|
if (JS_IsException(msg)) {
|
|
JS_FreeValue(ctx, name);
|
|
return JS_EXCEPTION;
|
|
}
|
|
if (!JS_IsEmptyString(name) && !JS_IsEmptyString(msg))
|
|
name = JS_ConcatString3(ctx, "", name, ": ");
|
|
return JS_ConcatString(ctx, name, msg);
|
|
}
|
|
|
|
static const JSCFunctionListEntry js_error_proto_funcs[] = {
|
|
JS_CFUNC_DEF("toString", 0, js_error_toString ),
|
|
JS_PROP_STRING_DEF("name", "Error", JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE ),
|
|
JS_PROP_STRING_DEF("message", "", JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE ),
|
|
};
|
|
|
|
static JSValue js_error_get_stackTraceLimit(JSContext *ctx, JSValue this_val)
|
|
{
|
|
JSValue val;
|
|
|
|
val = JS_ToObject(ctx, this_val);
|
|
if (JS_IsException(val))
|
|
return val;
|
|
JS_FreeValue(ctx, val);
|
|
return js_int32(ctx->error_stack_trace_limit);
|
|
}
|
|
|
|
static JSValue js_error_set_stackTraceLimit(JSContext *ctx, JSValue this_val, JSValue value)
|
|
{
|
|
if (JS_IsUndefined(this_val) || JS_IsNull(this_val))
|
|
return JS_ThrowTypeErrorNotAnObject(ctx);
|
|
int limit;
|
|
if (JS_ToInt32(ctx, &limit, value) < 0)
|
|
return JS_EXCEPTION;
|
|
ctx->error_stack_trace_limit = limit;
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
static JSValue js_error_get_prepareStackTrace(JSContext *ctx, JSValue this_val)
|
|
{
|
|
JSValue val;
|
|
|
|
val = JS_ToObject(ctx, this_val);
|
|
if (JS_IsException(val))
|
|
return val;
|
|
JS_FreeValue(ctx, val);
|
|
return js_dup(ctx->error_prepare_stack);
|
|
}
|
|
|
|
static JSValue js_error_set_prepareStackTrace(JSContext *ctx, JSValue this_val, JSValue value)
|
|
{
|
|
if (JS_IsUndefined(this_val) || JS_IsNull(this_val))
|
|
return JS_ThrowTypeErrorNotAnObject(ctx);
|
|
JS_FreeValue(ctx, ctx->error_prepare_stack);
|
|
ctx->error_prepare_stack = js_dup(value);
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
static const JSCFunctionListEntry js_error_funcs[] = {
|
|
JS_CGETSET_DEF("stackTraceLimit", js_error_get_stackTraceLimit, js_error_set_stackTraceLimit ),
|
|
JS_CGETSET_DEF("prepareStackTrace", js_error_get_prepareStackTrace, js_error_set_prepareStackTrace ),
|
|
};
|
|
|
|
/* AggregateError */
|
|
|
|
/* used by C code. */
|
|
static JSValue js_aggregate_error_constructor(JSContext *ctx,
|
|
JSValue errors)
|
|
{
|
|
JSValue obj;
|
|
|
|
obj = JS_NewObjectProtoClass(ctx,
|
|
ctx->native_error_proto[JS_AGGREGATE_ERROR],
|
|
JS_CLASS_ERROR);
|
|
if (JS_IsException(obj))
|
|
return obj;
|
|
JS_DefinePropertyValue(ctx, obj, JS_ATOM_errors, js_dup(errors),
|
|
JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
|
|
return obj;
|
|
}
|
|
|
|
/* Array */
|
|
|
|
static int JS_CopySubArray(JSContext *ctx,
|
|
JSValue obj, int64_t to_pos,
|
|
int64_t from_pos, int64_t count, int dir)
|
|
{
|
|
JSObject *p;
|
|
int64_t i, from, to, len;
|
|
JSValue val;
|
|
int fromPresent;
|
|
|
|
p = NULL;
|
|
if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) {
|
|
p = JS_VALUE_GET_OBJ(obj);
|
|
if (p->class_id != JS_CLASS_ARRAY || !p->fast_array) {
|
|
p = NULL;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < count; ) {
|
|
if (dir < 0) {
|
|
from = from_pos + count - i - 1;
|
|
to = to_pos + count - i - 1;
|
|
} else {
|
|
from = from_pos + i;
|
|
to = to_pos + i;
|
|
}
|
|
if (p && p->fast_array &&
|
|
from >= 0 && from < (len = p->u.array.count) &&
|
|
to >= 0 && to < len) {
|
|
int64_t l, j;
|
|
/* Fast path for fast arrays. Since we don't look at the
|
|
prototype chain, we can optimize only the cases where
|
|
all the elements are present in the array. */
|
|
l = count - i;
|
|
if (dir < 0) {
|
|
l = min_int64(l, from + 1);
|
|
l = min_int64(l, to + 1);
|
|
for(j = 0; j < l; j++) {
|
|
set_value(ctx, &p->u.array.u.values[to - j],
|
|
js_dup(p->u.array.u.values[from - j]));
|
|
}
|
|
} else {
|
|
l = min_int64(l, len - from);
|
|
l = min_int64(l, len - to);
|
|
for(j = 0; j < l; j++) {
|
|
set_value(ctx, &p->u.array.u.values[to + j],
|
|
js_dup(p->u.array.u.values[from + j]));
|
|
}
|
|
}
|
|
i += l;
|
|
} else {
|
|
fromPresent = JS_TryGetPropertyInt64(ctx, obj, from, &val);
|
|
if (fromPresent < 0)
|
|
goto exception;
|
|
|
|
if (fromPresent) {
|
|
if (JS_SetPropertyInt64(ctx, obj, to, val) < 0)
|
|
goto exception;
|
|
} else {
|
|
if (JS_DeletePropertyInt64(ctx, obj, to, JS_PROP_THROW) < 0)
|
|
goto exception;
|
|
}
|
|
i++;
|
|
}
|
|
}
|
|
return 0;
|
|
|
|
exception:
|
|
return -1;
|
|
}
|
|
|
|
static JSValue js_array_constructor(JSContext *ctx, JSValue new_target,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue obj;
|
|
int i;
|
|
|
|
obj = js_create_from_ctor(ctx, new_target, JS_CLASS_ARRAY);
|
|
if (JS_IsException(obj))
|
|
return obj;
|
|
if (argc == 1 && JS_IsNumber(argv[0])) {
|
|
uint32_t len;
|
|
if (JS_ToArrayLengthFree(ctx, &len, js_dup(argv[0]), TRUE))
|
|
goto fail;
|
|
if (JS_SetProperty(ctx, obj, JS_ATOM_length, js_uint32(len)) < 0)
|
|
goto fail;
|
|
} else {
|
|
for(i = 0; i < argc; i++) {
|
|
if (JS_SetPropertyUint32(ctx, obj, i, js_dup(argv[i])) < 0)
|
|
goto fail;
|
|
}
|
|
}
|
|
return obj;
|
|
fail:
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue js_array_from(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
// from(items, mapfn = void 0, this_arg = void 0)
|
|
JSValue items = argv[0], mapfn, this_arg;
|
|
JSValue args[2];
|
|
JSValue stack[2];
|
|
JSValue iter, r, v, v2, arrayLike;
|
|
int64_t k, len;
|
|
int done, mapping;
|
|
|
|
mapping = FALSE;
|
|
mapfn = JS_UNDEFINED;
|
|
this_arg = JS_UNDEFINED;
|
|
r = JS_UNDEFINED;
|
|
arrayLike = JS_UNDEFINED;
|
|
stack[0] = JS_UNDEFINED;
|
|
stack[1] = JS_UNDEFINED;
|
|
|
|
if (argc > 1) {
|
|
mapfn = argv[1];
|
|
if (!JS_IsUndefined(mapfn)) {
|
|
if (check_function(ctx, mapfn))
|
|
goto exception;
|
|
mapping = 1;
|
|
if (argc > 2)
|
|
this_arg = argv[2];
|
|
}
|
|
}
|
|
iter = JS_GetProperty(ctx, items, JS_ATOM_Symbol_iterator);
|
|
if (JS_IsException(iter))
|
|
goto exception;
|
|
if (!JS_IsUndefined(iter)) {
|
|
JS_FreeValue(ctx, iter);
|
|
if (JS_IsConstructor(ctx, this_val))
|
|
r = JS_CallConstructor(ctx, this_val, 0, NULL);
|
|
else
|
|
r = JS_NewArray(ctx);
|
|
if (JS_IsException(r))
|
|
goto exception;
|
|
stack[0] = js_dup(items);
|
|
if (js_for_of_start(ctx, &stack[1], FALSE))
|
|
goto exception;
|
|
for (k = 0;; k++) {
|
|
v = JS_IteratorNext(ctx, stack[0], stack[1], 0, NULL, &done);
|
|
if (JS_IsException(v))
|
|
goto exception_close;
|
|
if (done)
|
|
break;
|
|
if (mapping) {
|
|
args[0] = v;
|
|
args[1] = js_int32(k);
|
|
v2 = JS_Call(ctx, mapfn, this_arg, 2, args);
|
|
JS_FreeValue(ctx, v);
|
|
v = v2;
|
|
if (JS_IsException(v))
|
|
goto exception_close;
|
|
}
|
|
if (JS_DefinePropertyValueInt64(ctx, r, k, v,
|
|
JS_PROP_C_W_E | JS_PROP_THROW) < 0)
|
|
goto exception_close;
|
|
}
|
|
} else {
|
|
arrayLike = JS_ToObject(ctx, items);
|
|
if (JS_IsException(arrayLike))
|
|
goto exception;
|
|
if (js_get_length64(ctx, &len, arrayLike) < 0)
|
|
goto exception;
|
|
v = JS_NewInt64(ctx, len);
|
|
args[0] = v;
|
|
if (JS_IsConstructor(ctx, this_val)) {
|
|
r = JS_CallConstructor(ctx, this_val, 1, args);
|
|
} else {
|
|
r = js_array_constructor(ctx, JS_UNDEFINED, 1, args);
|
|
}
|
|
JS_FreeValue(ctx, v);
|
|
if (JS_IsException(r))
|
|
goto exception;
|
|
for(k = 0; k < len; k++) {
|
|
v = JS_GetPropertyInt64(ctx, arrayLike, k);
|
|
if (JS_IsException(v))
|
|
goto exception;
|
|
if (mapping) {
|
|
args[0] = v;
|
|
args[1] = js_int32(k);
|
|
v2 = JS_Call(ctx, mapfn, this_arg, 2, args);
|
|
JS_FreeValue(ctx, v);
|
|
v = v2;
|
|
if (JS_IsException(v))
|
|
goto exception;
|
|
}
|
|
if (JS_DefinePropertyValueInt64(ctx, r, k, v,
|
|
JS_PROP_C_W_E | JS_PROP_THROW) < 0)
|
|
goto exception;
|
|
}
|
|
}
|
|
if (JS_SetProperty(ctx, r, JS_ATOM_length, js_uint32(k)) < 0)
|
|
goto exception;
|
|
goto done;
|
|
|
|
exception_close:
|
|
if (!JS_IsUndefined(stack[0]))
|
|
JS_IteratorClose(ctx, stack[0], TRUE);
|
|
exception:
|
|
JS_FreeValue(ctx, r);
|
|
r = JS_EXCEPTION;
|
|
done:
|
|
JS_FreeValue(ctx, arrayLike);
|
|
JS_FreeValue(ctx, stack[0]);
|
|
JS_FreeValue(ctx, stack[1]);
|
|
return r;
|
|
}
|
|
|
|
static JSValue js_array_of(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue obj, args[1];
|
|
int i;
|
|
|
|
if (JS_IsConstructor(ctx, this_val)) {
|
|
args[0] = js_int32(argc);
|
|
obj = JS_CallConstructor(ctx, this_val, 1, args);
|
|
} else {
|
|
obj = JS_NewArray(ctx);
|
|
}
|
|
if (JS_IsException(obj))
|
|
return JS_EXCEPTION;
|
|
for(i = 0; i < argc; i++) {
|
|
if (JS_CreateDataPropertyUint32(ctx, obj, i, js_dup(argv[i]),
|
|
JS_PROP_THROW) < 0) {
|
|
goto fail;
|
|
}
|
|
}
|
|
if (JS_SetProperty(ctx, obj, JS_ATOM_length, js_uint32(argc)) < 0) {
|
|
fail:
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
return obj;
|
|
}
|
|
|
|
static JSValue js_array_isArray(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
int ret;
|
|
ret = JS_IsArray(ctx, argv[0]);
|
|
if (ret < 0)
|
|
return JS_EXCEPTION;
|
|
else
|
|
return js_bool(ret);
|
|
}
|
|
|
|
static JSValue js_get_this(JSContext *ctx,
|
|
JSValue this_val)
|
|
{
|
|
return js_dup(this_val);
|
|
}
|
|
|
|
static JSValue JS_ArraySpeciesCreate(JSContext *ctx, JSValue obj,
|
|
JSValue len_val)
|
|
{
|
|
JSValue ctor, ret, species;
|
|
int res;
|
|
JSContext *realm;
|
|
|
|
res = JS_IsArray(ctx, obj);
|
|
if (res < 0)
|
|
return JS_EXCEPTION;
|
|
if (!res)
|
|
return js_array_constructor(ctx, JS_UNDEFINED, 1, &len_val);
|
|
ctor = JS_GetProperty(ctx, obj, JS_ATOM_constructor);
|
|
if (JS_IsException(ctor))
|
|
return ctor;
|
|
if (JS_IsConstructor(ctx, ctor)) {
|
|
/* legacy web compatibility */
|
|
realm = JS_GetFunctionRealm(ctx, ctor);
|
|
if (!realm) {
|
|
JS_FreeValue(ctx, ctor);
|
|
return JS_EXCEPTION;
|
|
}
|
|
if (realm != ctx &&
|
|
js_same_value(ctx, ctor, realm->array_ctor)) {
|
|
JS_FreeValue(ctx, ctor);
|
|
ctor = JS_UNDEFINED;
|
|
}
|
|
}
|
|
if (JS_IsObject(ctor)) {
|
|
species = JS_GetProperty(ctx, ctor, JS_ATOM_Symbol_species);
|
|
JS_FreeValue(ctx, ctor);
|
|
if (JS_IsException(species))
|
|
return species;
|
|
ctor = species;
|
|
if (JS_IsNull(ctor))
|
|
ctor = JS_UNDEFINED;
|
|
}
|
|
if (JS_IsUndefined(ctor)) {
|
|
return js_array_constructor(ctx, JS_UNDEFINED, 1, &len_val);
|
|
} else {
|
|
ret = JS_CallConstructor(ctx, ctor, 1, &len_val);
|
|
JS_FreeValue(ctx, ctor);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
static const JSCFunctionListEntry js_array_funcs[] = {
|
|
JS_CFUNC_DEF("isArray", 1, js_array_isArray ),
|
|
JS_CFUNC_DEF("from", 1, js_array_from ),
|
|
JS_CFUNC_DEF("of", 0, js_array_of ),
|
|
JS_CGETSET_DEF("[Symbol.species]", js_get_this, NULL ),
|
|
};
|
|
|
|
static int JS_isConcatSpreadable(JSContext *ctx, JSValue obj)
|
|
{
|
|
JSValue val;
|
|
|
|
if (!JS_IsObject(obj))
|
|
return FALSE;
|
|
val = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_isConcatSpreadable);
|
|
if (JS_IsException(val))
|
|
return -1;
|
|
if (!JS_IsUndefined(val))
|
|
return JS_ToBoolFree(ctx, val);
|
|
return JS_IsArray(ctx, obj);
|
|
}
|
|
|
|
static JSValue js_array_at(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue obj, ret;
|
|
int64_t len, idx;
|
|
JSValue *arrp;
|
|
uint32_t count;
|
|
|
|
ret = JS_EXCEPTION;
|
|
obj = JS_ToObject(ctx, this_val);
|
|
if (js_get_length64(ctx, &len, obj))
|
|
goto exception;
|
|
|
|
if (JS_ToInt64Sat(ctx, &idx, argv[0]))
|
|
goto exception;
|
|
|
|
if (idx < 0)
|
|
idx = len + idx;
|
|
|
|
if (idx < 0 || idx >= len) {
|
|
ret = JS_UNDEFINED;
|
|
} else {
|
|
ret = JS_GetPropertyInt64(ctx, obj, idx);
|
|
}
|
|
|
|
exception:
|
|
JS_FreeValue(ctx, obj);
|
|
return ret;
|
|
}
|
|
|
|
static JSValue js_array_with(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue arr, obj, ret, *arrp, *pval;
|
|
JSObject *p;
|
|
int64_t i, len, idx;
|
|
uint32_t count32;
|
|
|
|
ret = JS_EXCEPTION;
|
|
arr = JS_UNDEFINED;
|
|
obj = JS_ToObject(ctx, this_val);
|
|
if (js_get_length64(ctx, &len, obj))
|
|
goto exception;
|
|
|
|
if (len > UINT32_MAX) {
|
|
JS_ThrowRangeError(ctx, "invalid array length");
|
|
goto exception;
|
|
}
|
|
|
|
if (JS_ToInt64Sat(ctx, &idx, argv[0]))
|
|
goto exception;
|
|
|
|
if (idx < 0)
|
|
idx = len + idx;
|
|
|
|
if (idx < 0 || idx >= len) {
|
|
JS_ThrowRangeError(ctx, "invalid array index: %" PRId64, idx);
|
|
goto exception;
|
|
}
|
|
|
|
arr = JS_NewArray(ctx);
|
|
if (JS_IsException(arr))
|
|
goto exception;
|
|
|
|
p = JS_VALUE_GET_OBJ(arr);
|
|
if (expand_fast_array(ctx, p, len) < 0)
|
|
goto exception;
|
|
p->u.array.count = len;
|
|
|
|
i = 0;
|
|
pval = p->u.array.u.values;
|
|
if (js_get_fast_array(ctx, obj, &arrp, &count32) && count32 == len) {
|
|
for (; i < idx; i++, pval++)
|
|
*pval = js_dup(arrp[i]);
|
|
*pval = js_dup(argv[1]);
|
|
for (i++, pval++; i < len; i++, pval++)
|
|
*pval = js_dup(arrp[i]);
|
|
} else {
|
|
for (; i < idx; i++, pval++)
|
|
if (-1 == JS_TryGetPropertyInt64(ctx, obj, i, pval))
|
|
goto fill_and_fail;
|
|
*pval = js_dup(argv[1]);
|
|
for (i++, pval++; i < len; i++, pval++) {
|
|
if (-1 == JS_TryGetPropertyInt64(ctx, obj, i, pval)) {
|
|
fill_and_fail:
|
|
for (; i < len; i++, pval++)
|
|
*pval = JS_UNDEFINED;
|
|
goto exception;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (JS_SetProperty(ctx, arr, JS_ATOM_length, JS_NewInt64(ctx, len)) < 0)
|
|
goto exception;
|
|
|
|
ret = arr;
|
|
arr = JS_UNDEFINED;
|
|
|
|
exception:
|
|
JS_FreeValue(ctx, arr);
|
|
JS_FreeValue(ctx, obj);
|
|
return ret;
|
|
}
|
|
|
|
static JSValue js_array_concat(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue obj, arr, val;
|
|
JSValue e;
|
|
int64_t len, k, n;
|
|
int i, res;
|
|
|
|
arr = JS_UNDEFINED;
|
|
obj = JS_ToObject(ctx, this_val);
|
|
if (JS_IsException(obj))
|
|
goto exception;
|
|
|
|
arr = JS_ArraySpeciesCreate(ctx, obj, js_int32(0));
|
|
if (JS_IsException(arr))
|
|
goto exception;
|
|
n = 0;
|
|
for (i = -1; i < argc; i++) {
|
|
if (i < 0)
|
|
e = obj;
|
|
else
|
|
e = argv[i];
|
|
|
|
res = JS_isConcatSpreadable(ctx, e);
|
|
if (res < 0)
|
|
goto exception;
|
|
if (res) {
|
|
if (js_get_length64(ctx, &len, e))
|
|
goto exception;
|
|
if (n + len > MAX_SAFE_INTEGER) {
|
|
JS_ThrowTypeError(ctx, "Array loo long");
|
|
goto exception;
|
|
}
|
|
for (k = 0; k < len; k++, n++) {
|
|
res = JS_TryGetPropertyInt64(ctx, e, k, &val);
|
|
if (res < 0)
|
|
goto exception;
|
|
if (res) {
|
|
if (JS_DefinePropertyValueInt64(ctx, arr, n, val,
|
|
JS_PROP_C_W_E | JS_PROP_THROW) < 0)
|
|
goto exception;
|
|
}
|
|
}
|
|
} else {
|
|
if (n >= MAX_SAFE_INTEGER) {
|
|
JS_ThrowTypeError(ctx, "Array loo long");
|
|
goto exception;
|
|
}
|
|
if (JS_DefinePropertyValueInt64(ctx, arr, n, js_dup(e),
|
|
JS_PROP_C_W_E | JS_PROP_THROW) < 0)
|
|
goto exception;
|
|
n++;
|
|
}
|
|
}
|
|
if (JS_SetProperty(ctx, arr, JS_ATOM_length, JS_NewInt64(ctx, n)) < 0)
|
|
goto exception;
|
|
|
|
JS_FreeValue(ctx, obj);
|
|
return arr;
|
|
|
|
exception:
|
|
JS_FreeValue(ctx, arr);
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
#define special_every 0
|
|
#define special_some 1
|
|
#define special_forEach 2
|
|
#define special_map 3
|
|
#define special_filter 4
|
|
#define special_TA 8
|
|
|
|
static int js_typed_array_get_length_internal(JSContext *ctx, JSValue obj);
|
|
|
|
static JSValue js_typed_array___speciesCreate(JSContext *ctx,
|
|
JSValue this_val,
|
|
int argc, JSValue *argv);
|
|
|
|
static JSValue js_array_every(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv, int special)
|
|
{
|
|
JSValue obj, val, index_val, res, ret;
|
|
JSValue args[3];
|
|
JSValue func, this_arg;
|
|
int64_t len, k, n;
|
|
int present;
|
|
|
|
ret = JS_UNDEFINED;
|
|
val = JS_UNDEFINED;
|
|
if (special & special_TA) {
|
|
obj = js_dup(this_val);
|
|
len = js_typed_array_get_length_internal(ctx, obj);
|
|
if (len < 0)
|
|
goto exception;
|
|
} else {
|
|
obj = JS_ToObject(ctx, this_val);
|
|
if (js_get_length64(ctx, &len, obj))
|
|
goto exception;
|
|
}
|
|
func = argv[0];
|
|
this_arg = JS_UNDEFINED;
|
|
if (argc > 1)
|
|
this_arg = argv[1];
|
|
|
|
if (check_function(ctx, func))
|
|
goto exception;
|
|
|
|
switch (special) {
|
|
case special_every:
|
|
case special_every | special_TA:
|
|
ret = JS_TRUE;
|
|
break;
|
|
case special_some:
|
|
case special_some | special_TA:
|
|
ret = JS_FALSE;
|
|
break;
|
|
case special_map:
|
|
/* XXX: JS_ArraySpeciesCreate should take int64_t */
|
|
ret = JS_ArraySpeciesCreate(ctx, obj, JS_NewInt64(ctx, len));
|
|
if (JS_IsException(ret))
|
|
goto exception;
|
|
break;
|
|
case special_filter:
|
|
ret = JS_ArraySpeciesCreate(ctx, obj, js_int32(0));
|
|
if (JS_IsException(ret))
|
|
goto exception;
|
|
break;
|
|
case special_map | special_TA:
|
|
args[0] = obj;
|
|
args[1] = js_int32(len);
|
|
ret = js_typed_array___speciesCreate(ctx, JS_UNDEFINED, 2, args);
|
|
if (JS_IsException(ret))
|
|
goto exception;
|
|
break;
|
|
case special_filter | special_TA:
|
|
ret = JS_NewArray(ctx);
|
|
if (JS_IsException(ret))
|
|
goto exception;
|
|
break;
|
|
}
|
|
n = 0;
|
|
|
|
for(k = 0; k < len; k++) {
|
|
if (special & special_TA) {
|
|
val = JS_GetPropertyInt64(ctx, obj, k);
|
|
if (JS_IsException(val))
|
|
goto exception;
|
|
present = TRUE;
|
|
} else {
|
|
present = JS_TryGetPropertyInt64(ctx, obj, k, &val);
|
|
if (present < 0)
|
|
goto exception;
|
|
}
|
|
if (present) {
|
|
index_val = JS_NewInt64(ctx, k);
|
|
if (JS_IsException(index_val))
|
|
goto exception;
|
|
args[0] = val;
|
|
args[1] = index_val;
|
|
args[2] = obj;
|
|
res = JS_Call(ctx, func, this_arg, 3, args);
|
|
JS_FreeValue(ctx, index_val);
|
|
if (JS_IsException(res))
|
|
goto exception;
|
|
switch (special) {
|
|
case special_every:
|
|
case special_every | special_TA:
|
|
if (!JS_ToBoolFree(ctx, res)) {
|
|
ret = JS_FALSE;
|
|
goto done;
|
|
}
|
|
break;
|
|
case special_some:
|
|
case special_some | special_TA:
|
|
if (JS_ToBoolFree(ctx, res)) {
|
|
ret = JS_TRUE;
|
|
goto done;
|
|
}
|
|
break;
|
|
case special_map:
|
|
if (JS_DefinePropertyValueInt64(ctx, ret, k, res,
|
|
JS_PROP_C_W_E | JS_PROP_THROW) < 0)
|
|
goto exception;
|
|
break;
|
|
case special_map | special_TA:
|
|
if (JS_SetPropertyValue(ctx, ret, js_int32(k), res, JS_PROP_THROW) < 0)
|
|
goto exception;
|
|
break;
|
|
case special_filter:
|
|
case special_filter | special_TA:
|
|
if (JS_ToBoolFree(ctx, res)) {
|
|
if (JS_DefinePropertyValueInt64(ctx, ret, n++, js_dup(val),
|
|
JS_PROP_C_W_E | JS_PROP_THROW) < 0)
|
|
goto exception;
|
|
}
|
|
break;
|
|
default:
|
|
JS_FreeValue(ctx, res);
|
|
break;
|
|
}
|
|
JS_FreeValue(ctx, val);
|
|
val = JS_UNDEFINED;
|
|
}
|
|
}
|
|
done:
|
|
if (special == (special_filter | special_TA)) {
|
|
JSValue arr;
|
|
args[0] = obj;
|
|
args[1] = js_int32(n);
|
|
arr = js_typed_array___speciesCreate(ctx, JS_UNDEFINED, 2, args);
|
|
if (JS_IsException(arr))
|
|
goto exception;
|
|
args[0] = ret;
|
|
res = JS_Invoke(ctx, arr, JS_ATOM_set, 1, args);
|
|
if (check_exception_free(ctx, res))
|
|
goto exception;
|
|
JS_FreeValue(ctx, ret);
|
|
ret = arr;
|
|
}
|
|
JS_FreeValue(ctx, val);
|
|
JS_FreeValue(ctx, obj);
|
|
return ret;
|
|
|
|
exception:
|
|
JS_FreeValue(ctx, ret);
|
|
JS_FreeValue(ctx, val);
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
#define special_reduce 0
|
|
#define special_reduceRight 1
|
|
|
|
static JSValue js_array_reduce(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv, int special)
|
|
{
|
|
JSValue obj, val, index_val, acc, acc1;
|
|
JSValue args[4];
|
|
JSValue func;
|
|
int64_t len, k, k1;
|
|
int present;
|
|
|
|
acc = JS_UNDEFINED;
|
|
val = JS_UNDEFINED;
|
|
if (special & special_TA) {
|
|
obj = js_dup(this_val);
|
|
len = js_typed_array_get_length_internal(ctx, obj);
|
|
if (len < 0)
|
|
goto exception;
|
|
} else {
|
|
obj = JS_ToObject(ctx, this_val);
|
|
if (js_get_length64(ctx, &len, obj))
|
|
goto exception;
|
|
}
|
|
func = argv[0];
|
|
|
|
if (check_function(ctx, func))
|
|
goto exception;
|
|
|
|
k = 0;
|
|
if (argc > 1) {
|
|
acc = js_dup(argv[1]);
|
|
} else {
|
|
for(;;) {
|
|
if (k >= len) {
|
|
JS_ThrowTypeError(ctx, "empty array");
|
|
goto exception;
|
|
}
|
|
k1 = (special & special_reduceRight) ? len - k - 1 : k;
|
|
k++;
|
|
if (special & special_TA) {
|
|
acc = JS_GetPropertyInt64(ctx, obj, k1);
|
|
if (JS_IsException(acc))
|
|
goto exception;
|
|
break;
|
|
} else {
|
|
present = JS_TryGetPropertyInt64(ctx, obj, k1, &acc);
|
|
if (present < 0)
|
|
goto exception;
|
|
if (present)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
for (; k < len; k++) {
|
|
k1 = (special & special_reduceRight) ? len - k - 1 : k;
|
|
if (special & special_TA) {
|
|
val = JS_GetPropertyInt64(ctx, obj, k1);
|
|
if (JS_IsException(val))
|
|
goto exception;
|
|
present = TRUE;
|
|
} else {
|
|
present = JS_TryGetPropertyInt64(ctx, obj, k1, &val);
|
|
if (present < 0)
|
|
goto exception;
|
|
}
|
|
if (present) {
|
|
index_val = JS_NewInt64(ctx, k1);
|
|
if (JS_IsException(index_val))
|
|
goto exception;
|
|
args[0] = acc;
|
|
args[1] = val;
|
|
args[2] = index_val;
|
|
args[3] = obj;
|
|
acc1 = JS_Call(ctx, func, JS_UNDEFINED, 4, args);
|
|
JS_FreeValue(ctx, index_val);
|
|
JS_FreeValue(ctx, val);
|
|
val = JS_UNDEFINED;
|
|
if (JS_IsException(acc1))
|
|
goto exception;
|
|
JS_FreeValue(ctx, acc);
|
|
acc = acc1;
|
|
}
|
|
}
|
|
JS_FreeValue(ctx, obj);
|
|
return acc;
|
|
|
|
exception:
|
|
JS_FreeValue(ctx, acc);
|
|
JS_FreeValue(ctx, val);
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue js_array_fill(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue obj;
|
|
int64_t len, start, end;
|
|
|
|
obj = JS_ToObject(ctx, this_val);
|
|
if (js_get_length64(ctx, &len, obj))
|
|
goto exception;
|
|
|
|
start = 0;
|
|
if (argc > 1 && !JS_IsUndefined(argv[1])) {
|
|
if (JS_ToInt64Clamp(ctx, &start, argv[1], 0, len, len))
|
|
goto exception;
|
|
}
|
|
|
|
end = len;
|
|
if (argc > 2 && !JS_IsUndefined(argv[2])) {
|
|
if (JS_ToInt64Clamp(ctx, &end, argv[2], 0, len, len))
|
|
goto exception;
|
|
}
|
|
|
|
/* XXX: should special case fast arrays */
|
|
while (start < end) {
|
|
if (JS_SetPropertyInt64(ctx, obj, start, js_dup(argv[0])) < 0)
|
|
goto exception;
|
|
start++;
|
|
}
|
|
return obj;
|
|
|
|
exception:
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue js_array_includes(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue obj, val;
|
|
int64_t len, n;
|
|
JSValue *arrp;
|
|
uint32_t count;
|
|
int res;
|
|
|
|
obj = JS_ToObject(ctx, this_val);
|
|
if (js_get_length64(ctx, &len, obj))
|
|
goto exception;
|
|
|
|
res = TRUE;
|
|
if (len > 0) {
|
|
n = 0;
|
|
if (argc > 1) {
|
|
if (JS_ToInt64Clamp(ctx, &n, argv[1], 0, len, len))
|
|
goto exception;
|
|
}
|
|
if (js_get_fast_array(ctx, obj, &arrp, &count)) {
|
|
for (; n < count; n++) {
|
|
if (js_strict_eq2(ctx, js_dup(argv[0]), js_dup(arrp[n]),
|
|
JS_EQ_SAME_VALUE_ZERO)) {
|
|
goto done;
|
|
}
|
|
}
|
|
}
|
|
for (; n < len; n++) {
|
|
val = JS_GetPropertyInt64(ctx, obj, n);
|
|
if (JS_IsException(val))
|
|
goto exception;
|
|
if (js_strict_eq2(ctx, js_dup(argv[0]), val,
|
|
JS_EQ_SAME_VALUE_ZERO)) {
|
|
goto done;
|
|
}
|
|
}
|
|
}
|
|
res = FALSE;
|
|
done:
|
|
JS_FreeValue(ctx, obj);
|
|
return js_bool(res);
|
|
|
|
exception:
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue js_array_indexOf(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue obj, val;
|
|
int64_t len, n;
|
|
JSValue *arrp;
|
|
uint32_t count;
|
|
|
|
obj = JS_ToObject(ctx, this_val);
|
|
if (js_get_length64(ctx, &len, obj))
|
|
goto exception;
|
|
|
|
if (len > 0) {
|
|
n = 0;
|
|
if (argc > 1) {
|
|
if (JS_ToInt64Clamp(ctx, &n, argv[1], 0, len, len))
|
|
goto exception;
|
|
}
|
|
if (js_get_fast_array(ctx, obj, &arrp, &count)) {
|
|
for (; n < count; n++) {
|
|
if (js_strict_eq2(ctx, js_dup(argv[0]), js_dup(arrp[n]),
|
|
JS_EQ_STRICT)) {
|
|
goto done;
|
|
}
|
|
}
|
|
}
|
|
for (; n < len; n++) {
|
|
int present = JS_TryGetPropertyInt64(ctx, obj, n, &val);
|
|
if (present < 0)
|
|
goto exception;
|
|
if (present) {
|
|
if (js_strict_eq2(ctx, js_dup(argv[0]), val, JS_EQ_STRICT)) {
|
|
goto done;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
n = -1;
|
|
done:
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_NewInt64(ctx, n);
|
|
|
|
exception:
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue js_array_lastIndexOf(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue obj, val;
|
|
int64_t len, n;
|
|
JSValue *arrp;
|
|
uint32_t count;
|
|
|
|
obj = JS_ToObject(ctx, this_val);
|
|
if (js_get_length64(ctx, &len, obj))
|
|
goto exception;
|
|
|
|
if (len > 0) {
|
|
n = len - 1;
|
|
if (argc > 1) {
|
|
if (JS_ToInt64Clamp(ctx, &n, argv[1], -1, len - 1, len))
|
|
goto exception;
|
|
}
|
|
if (js_get_fast_array(ctx, obj, &arrp, &count) && count == len) {
|
|
for (; n >= 0; n--) {
|
|
if (js_strict_eq2(ctx, js_dup(argv[0]), js_dup(arrp[n]),
|
|
JS_EQ_STRICT)) {
|
|
goto done;
|
|
}
|
|
}
|
|
}
|
|
for (; n >= 0; n--) {
|
|
int present = JS_TryGetPropertyInt64(ctx, obj, n, &val);
|
|
if (present < 0)
|
|
goto exception;
|
|
if (present) {
|
|
if (js_strict_eq2(ctx, js_dup(argv[0]), val, JS_EQ_STRICT)) {
|
|
goto done;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
n = -1;
|
|
done:
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_NewInt64(ctx, n);
|
|
|
|
exception:
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
enum {
|
|
ArrayFind,
|
|
ArrayFindIndex,
|
|
ArrayFindLast,
|
|
ArrayFindLastIndex,
|
|
};
|
|
|
|
static JSValue js_array_find(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv, int mode)
|
|
{
|
|
JSValue func, this_arg;
|
|
JSValue args[3];
|
|
JSValue obj, val, index_val, res;
|
|
int64_t len, k, end;
|
|
int dir;
|
|
|
|
index_val = JS_UNDEFINED;
|
|
val = JS_UNDEFINED;
|
|
obj = JS_ToObject(ctx, this_val);
|
|
if (js_get_length64(ctx, &len, obj))
|
|
goto exception;
|
|
|
|
func = argv[0];
|
|
if (check_function(ctx, func))
|
|
goto exception;
|
|
|
|
this_arg = JS_UNDEFINED;
|
|
if (argc > 1)
|
|
this_arg = argv[1];
|
|
|
|
k = 0;
|
|
dir = 1;
|
|
end = len;
|
|
if (mode == ArrayFindLast || mode == ArrayFindLastIndex) {
|
|
k = len - 1;
|
|
dir = -1;
|
|
end = -1;
|
|
}
|
|
|
|
// TODO(bnoordhuis) add fast path for fast arrays
|
|
for(; k != end; k += dir) {
|
|
index_val = JS_NewInt64(ctx, k);
|
|
if (JS_IsException(index_val))
|
|
goto exception;
|
|
val = JS_GetPropertyValue(ctx, obj, index_val);
|
|
if (JS_IsException(val))
|
|
goto exception;
|
|
args[0] = val;
|
|
args[1] = index_val;
|
|
args[2] = this_val;
|
|
res = JS_Call(ctx, func, this_arg, 3, args);
|
|
if (JS_IsException(res))
|
|
goto exception;
|
|
if (JS_ToBoolFree(ctx, res)) {
|
|
if (mode == ArrayFindIndex || mode == ArrayFindLastIndex) {
|
|
JS_FreeValue(ctx, val);
|
|
JS_FreeValue(ctx, obj);
|
|
return index_val;
|
|
} else {
|
|
JS_FreeValue(ctx, index_val);
|
|
JS_FreeValue(ctx, obj);
|
|
return val;
|
|
}
|
|
}
|
|
JS_FreeValue(ctx, val);
|
|
JS_FreeValue(ctx, index_val);
|
|
}
|
|
JS_FreeValue(ctx, obj);
|
|
if (mode == ArrayFindIndex || mode == ArrayFindLastIndex)
|
|
return js_int32(-1);
|
|
else
|
|
return JS_UNDEFINED;
|
|
|
|
exception:
|
|
JS_FreeValue(ctx, index_val);
|
|
JS_FreeValue(ctx, val);
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue js_array_toString(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue obj, method, ret;
|
|
|
|
obj = JS_ToObject(ctx, this_val);
|
|
if (JS_IsException(obj))
|
|
return JS_EXCEPTION;
|
|
method = JS_GetProperty(ctx, obj, JS_ATOM_join);
|
|
if (JS_IsException(method)) {
|
|
ret = JS_EXCEPTION;
|
|
} else
|
|
if (!JS_IsFunction(ctx, method)) {
|
|
/* Use intrinsic Object.prototype.toString */
|
|
JS_FreeValue(ctx, method);
|
|
ret = js_object_toString(ctx, obj, 0, NULL);
|
|
} else {
|
|
ret = JS_CallFree(ctx, method, obj, 0, NULL);
|
|
}
|
|
JS_FreeValue(ctx, obj);
|
|
return ret;
|
|
}
|
|
|
|
static JSValue js_array_join(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv, int toLocaleString)
|
|
{
|
|
JSValue obj, sep = JS_UNDEFINED, el;
|
|
StringBuffer b_s, *b = &b_s;
|
|
JSString *p = NULL;
|
|
int64_t i, n;
|
|
int c;
|
|
|
|
obj = JS_ToObject(ctx, this_val);
|
|
if (js_get_length64(ctx, &n, obj))
|
|
goto exception;
|
|
|
|
c = ','; /* default separator */
|
|
if (!toLocaleString && argc > 0 && !JS_IsUndefined(argv[0])) {
|
|
sep = JS_ToString(ctx, argv[0]);
|
|
if (JS_IsException(sep))
|
|
goto exception;
|
|
p = JS_VALUE_GET_STRING(sep);
|
|
if (p->len == 1 && !p->is_wide_char)
|
|
c = p->u.str8[0];
|
|
else
|
|
c = -1;
|
|
}
|
|
string_buffer_init(ctx, b, 0);
|
|
|
|
for(i = 0; i < n; i++) {
|
|
if (i > 0) {
|
|
if (c >= 0) {
|
|
string_buffer_putc8(b, c);
|
|
} else {
|
|
string_buffer_concat(b, p, 0, p->len);
|
|
}
|
|
}
|
|
el = JS_GetPropertyUint32(ctx, obj, i);
|
|
if (JS_IsException(el))
|
|
goto fail;
|
|
if (!JS_IsNull(el) && !JS_IsUndefined(el)) {
|
|
if (toLocaleString) {
|
|
el = JS_ToLocaleStringFree(ctx, el);
|
|
}
|
|
if (string_buffer_concat_value_free(b, el))
|
|
goto fail;
|
|
}
|
|
}
|
|
JS_FreeValue(ctx, sep);
|
|
JS_FreeValue(ctx, obj);
|
|
return string_buffer_end(b);
|
|
|
|
fail:
|
|
string_buffer_free(b);
|
|
JS_FreeValue(ctx, sep);
|
|
exception:
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue js_array_pop(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv, int shift)
|
|
{
|
|
JSValue obj, res = JS_UNDEFINED;
|
|
int64_t len, newLen;
|
|
JSValue *arrp;
|
|
uint32_t count32;
|
|
|
|
obj = JS_ToObject(ctx, this_val);
|
|
if (js_get_length64(ctx, &len, obj))
|
|
goto exception;
|
|
newLen = 0;
|
|
if (len > 0) {
|
|
newLen = len - 1;
|
|
/* Special case fast arrays */
|
|
if (js_get_fast_array(ctx, obj, &arrp, &count32) && count32 == len) {
|
|
JSObject *p = JS_VALUE_GET_OBJ(obj);
|
|
if (shift) {
|
|
res = arrp[0];
|
|
memmove(arrp, arrp + 1, (count32 - 1) * sizeof(*arrp));
|
|
p->u.array.count--;
|
|
} else {
|
|
res = arrp[count32 - 1];
|
|
p->u.array.count--;
|
|
}
|
|
} else {
|
|
if (shift) {
|
|
res = JS_GetPropertyInt64(ctx, obj, 0);
|
|
if (JS_IsException(res))
|
|
goto exception;
|
|
if (JS_CopySubArray(ctx, obj, 0, 1, len - 1, +1))
|
|
goto exception;
|
|
} else {
|
|
res = JS_GetPropertyInt64(ctx, obj, newLen);
|
|
if (JS_IsException(res))
|
|
goto exception;
|
|
}
|
|
if (JS_DeletePropertyInt64(ctx, obj, newLen, JS_PROP_THROW) < 0)
|
|
goto exception;
|
|
}
|
|
}
|
|
if (JS_SetProperty(ctx, obj, JS_ATOM_length, JS_NewInt64(ctx, newLen)) < 0)
|
|
goto exception;
|
|
|
|
JS_FreeValue(ctx, obj);
|
|
return res;
|
|
|
|
exception:
|
|
JS_FreeValue(ctx, res);
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue js_array_push(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv, int unshift)
|
|
{
|
|
JSValue obj;
|
|
int i;
|
|
int64_t len, from, newLen;
|
|
|
|
obj = JS_ToObject(ctx, this_val);
|
|
if (js_get_length64(ctx, &len, obj))
|
|
goto exception;
|
|
newLen = len + argc;
|
|
if (newLen > MAX_SAFE_INTEGER) {
|
|
JS_ThrowTypeError(ctx, "Array loo long");
|
|
goto exception;
|
|
}
|
|
from = len;
|
|
if (unshift && argc > 0) {
|
|
if (JS_CopySubArray(ctx, obj, argc, 0, len, -1))
|
|
goto exception;
|
|
from = 0;
|
|
}
|
|
for(i = 0; i < argc; i++) {
|
|
if (JS_SetPropertyInt64(ctx, obj, from + i, js_dup(argv[i])) < 0)
|
|
goto exception;
|
|
}
|
|
if (JS_SetProperty(ctx, obj, JS_ATOM_length, JS_NewInt64(ctx, newLen)) < 0)
|
|
goto exception;
|
|
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_NewInt64(ctx, newLen);
|
|
|
|
exception:
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue js_array_reverse(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue obj, lval, hval;
|
|
JSValue *arrp;
|
|
int64_t len, l, h;
|
|
int l_present, h_present;
|
|
uint32_t count32;
|
|
|
|
lval = JS_UNDEFINED;
|
|
obj = JS_ToObject(ctx, this_val);
|
|
if (js_get_length64(ctx, &len, obj))
|
|
goto exception;
|
|
|
|
/* Special case fast arrays */
|
|
if (js_get_fast_array(ctx, obj, &arrp, &count32) && count32 == len) {
|
|
uint32_t ll, hh;
|
|
|
|
if (count32 > 1) {
|
|
for (ll = 0, hh = count32 - 1; ll < hh; ll++, hh--) {
|
|
lval = arrp[ll];
|
|
arrp[ll] = arrp[hh];
|
|
arrp[hh] = lval;
|
|
}
|
|
}
|
|
return obj;
|
|
}
|
|
|
|
for (l = 0, h = len - 1; l < h; l++, h--) {
|
|
l_present = JS_TryGetPropertyInt64(ctx, obj, l, &lval);
|
|
if (l_present < 0)
|
|
goto exception;
|
|
h_present = JS_TryGetPropertyInt64(ctx, obj, h, &hval);
|
|
if (h_present < 0)
|
|
goto exception;
|
|
if (h_present) {
|
|
if (JS_SetPropertyInt64(ctx, obj, l, hval) < 0)
|
|
goto exception;
|
|
|
|
if (l_present) {
|
|
if (JS_SetPropertyInt64(ctx, obj, h, lval) < 0) {
|
|
lval = JS_UNDEFINED;
|
|
goto exception;
|
|
}
|
|
lval = JS_UNDEFINED;
|
|
} else {
|
|
if (JS_DeletePropertyInt64(ctx, obj, h, JS_PROP_THROW) < 0)
|
|
goto exception;
|
|
}
|
|
} else {
|
|
if (l_present) {
|
|
if (JS_DeletePropertyInt64(ctx, obj, l, JS_PROP_THROW) < 0)
|
|
goto exception;
|
|
if (JS_SetPropertyInt64(ctx, obj, h, lval) < 0) {
|
|
lval = JS_UNDEFINED;
|
|
goto exception;
|
|
}
|
|
lval = JS_UNDEFINED;
|
|
}
|
|
}
|
|
}
|
|
return obj;
|
|
|
|
exception:
|
|
JS_FreeValue(ctx, lval);
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
// Note: a.toReversed() is a.slice().reverse() with the twist that a.slice()
|
|
// leaves holes in sparse arrays intact whereas a.toReversed() replaces them
|
|
// with undefined, thus in effect creating a dense array.
|
|
// Does not use Array[@@species], always returns a base Array.
|
|
static JSValue js_array_toReversed(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue arr, obj, ret, *arrp, *pval;
|
|
JSObject *p;
|
|
int64_t i, len;
|
|
uint32_t count32;
|
|
|
|
ret = JS_EXCEPTION;
|
|
arr = JS_UNDEFINED;
|
|
obj = JS_ToObject(ctx, this_val);
|
|
if (js_get_length64(ctx, &len, obj))
|
|
goto exception;
|
|
|
|
if (len > UINT32_MAX) {
|
|
JS_ThrowRangeError(ctx, "invalid array length");
|
|
goto exception;
|
|
}
|
|
|
|
arr = JS_NewArray(ctx);
|
|
if (JS_IsException(arr))
|
|
goto exception;
|
|
|
|
if (len > 0) {
|
|
p = JS_VALUE_GET_OBJ(arr);
|
|
if (expand_fast_array(ctx, p, len) < 0)
|
|
goto exception;
|
|
p->u.array.count = len;
|
|
|
|
i = len - 1;
|
|
pval = p->u.array.u.values;
|
|
if (js_get_fast_array(ctx, obj, &arrp, &count32) && count32 == len) {
|
|
for (; i >= 0; i--, pval++)
|
|
*pval = js_dup(arrp[i]);
|
|
} else {
|
|
// Query order is observable; test262 expects descending order.
|
|
for (; i >= 0; i--, pval++) {
|
|
if (-1 == JS_TryGetPropertyInt64(ctx, obj, i, pval)) {
|
|
// Exception; initialize remaining elements.
|
|
for (; i >= 0; i--, pval++)
|
|
*pval = JS_UNDEFINED;
|
|
goto exception;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (JS_SetProperty(ctx, arr, JS_ATOM_length, JS_NewInt64(ctx, len)) < 0)
|
|
goto exception;
|
|
}
|
|
|
|
ret = arr;
|
|
arr = JS_UNDEFINED;
|
|
|
|
exception:
|
|
JS_FreeValue(ctx, arr);
|
|
JS_FreeValue(ctx, obj);
|
|
return ret;
|
|
}
|
|
|
|
static JSValue js_array_slice(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv, int splice)
|
|
{
|
|
JSValue obj, arr, val, len_val;
|
|
int64_t len, start, k, final, n, count, del_count, new_len;
|
|
int kPresent;
|
|
JSValue *arrp;
|
|
uint32_t count32, i, item_count;
|
|
|
|
arr = JS_UNDEFINED;
|
|
obj = JS_ToObject(ctx, this_val);
|
|
if (js_get_length64(ctx, &len, obj))
|
|
goto exception;
|
|
|
|
if (JS_ToInt64Clamp(ctx, &start, argv[0], 0, len, len))
|
|
goto exception;
|
|
|
|
if (splice) {
|
|
if (argc == 0) {
|
|
item_count = 0;
|
|
del_count = 0;
|
|
} else if (argc == 1) {
|
|
item_count = 0;
|
|
del_count = len - start;
|
|
} else {
|
|
item_count = argc - 2;
|
|
if (JS_ToInt64Clamp(ctx, &del_count, argv[1], 0, len - start, 0))
|
|
goto exception;
|
|
}
|
|
if (len + item_count - del_count > MAX_SAFE_INTEGER) {
|
|
JS_ThrowTypeError(ctx, "Array loo long");
|
|
goto exception;
|
|
}
|
|
count = del_count;
|
|
} else {
|
|
item_count = 0; /* avoid warning */
|
|
final = len;
|
|
if (!JS_IsUndefined(argv[1])) {
|
|
if (JS_ToInt64Clamp(ctx, &final, argv[1], 0, len, len))
|
|
goto exception;
|
|
}
|
|
count = max_int64(final - start, 0);
|
|
}
|
|
len_val = JS_NewInt64(ctx, count);
|
|
arr = JS_ArraySpeciesCreate(ctx, obj, len_val);
|
|
JS_FreeValue(ctx, len_val);
|
|
if (JS_IsException(arr))
|
|
goto exception;
|
|
|
|
k = start;
|
|
final = start + count;
|
|
n = 0;
|
|
/* The fast array test on arr ensures that
|
|
JS_CreateDataPropertyUint32() won't modify obj in case arr is
|
|
an exotic object */
|
|
/* Special case fast arrays */
|
|
if (js_get_fast_array(ctx, obj, &arrp, &count32) &&
|
|
js_is_fast_array(ctx, arr)) {
|
|
/* XXX: should share code with fast array constructor */
|
|
for (; k < final && k < count32; k++, n++) {
|
|
if (JS_CreateDataPropertyUint32(ctx, arr, n, js_dup(arrp[k]), JS_PROP_THROW) < 0)
|
|
goto exception;
|
|
}
|
|
}
|
|
/* Copy the remaining elements if any (handle case of inherited properties) */
|
|
for (; k < final; k++, n++) {
|
|
kPresent = JS_TryGetPropertyInt64(ctx, obj, k, &val);
|
|
if (kPresent < 0)
|
|
goto exception;
|
|
if (kPresent) {
|
|
if (JS_CreateDataPropertyUint32(ctx, arr, n, val, JS_PROP_THROW) < 0)
|
|
goto exception;
|
|
}
|
|
}
|
|
if (JS_SetProperty(ctx, arr, JS_ATOM_length, JS_NewInt64(ctx, n)) < 0)
|
|
goto exception;
|
|
|
|
if (splice) {
|
|
new_len = len + item_count - del_count;
|
|
if (item_count != del_count) {
|
|
if (JS_CopySubArray(ctx, obj, start + item_count,
|
|
start + del_count, len - (start + del_count),
|
|
item_count <= del_count ? +1 : -1) < 0)
|
|
goto exception;
|
|
|
|
for (k = len; k-- > new_len; ) {
|
|
if (JS_DeletePropertyInt64(ctx, obj, k, JS_PROP_THROW) < 0)
|
|
goto exception;
|
|
}
|
|
}
|
|
for (i = 0; i < item_count; i++) {
|
|
if (JS_SetPropertyInt64(ctx, obj, start + i, js_dup(argv[i + 2])) < 0)
|
|
goto exception;
|
|
}
|
|
if (JS_SetProperty(ctx, obj, JS_ATOM_length, JS_NewInt64(ctx, new_len)) < 0)
|
|
goto exception;
|
|
}
|
|
JS_FreeValue(ctx, obj);
|
|
return arr;
|
|
|
|
exception:
|
|
JS_FreeValue(ctx, obj);
|
|
JS_FreeValue(ctx, arr);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue js_array_toSpliced(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue arr, obj, ret, *arrp, *pval, *last;
|
|
JSObject *p;
|
|
int64_t i, j, len, newlen, start, add, del;
|
|
uint32_t count32;
|
|
|
|
pval = NULL;
|
|
last = NULL;
|
|
ret = JS_EXCEPTION;
|
|
arr = JS_UNDEFINED;
|
|
|
|
obj = JS_ToObject(ctx, this_val);
|
|
if (js_get_length64(ctx, &len, obj))
|
|
goto exception;
|
|
|
|
start = 0;
|
|
if (argc > 0)
|
|
if (JS_ToInt64Clamp(ctx, &start, argv[0], 0, len, len))
|
|
goto exception;
|
|
|
|
del = 0;
|
|
if (argc > 0)
|
|
del = len - start;
|
|
if (argc > 1)
|
|
if (JS_ToInt64Clamp(ctx, &del, argv[1], 0, del, 0))
|
|
goto exception;
|
|
|
|
add = 0;
|
|
if (argc > 2)
|
|
add = argc - 2;
|
|
|
|
newlen = len + add - del;
|
|
if (newlen > UINT32_MAX) {
|
|
// Per spec: TypeError if newlen >= 2**53, RangeError below
|
|
if (newlen > MAX_SAFE_INTEGER) {
|
|
JS_ThrowTypeError(ctx, "invalid array length");
|
|
} else {
|
|
JS_ThrowRangeError(ctx, "invalid array length");
|
|
}
|
|
goto exception;
|
|
}
|
|
|
|
arr = JS_NewArray(ctx);
|
|
if (JS_IsException(arr))
|
|
goto exception;
|
|
|
|
if (newlen <= 0)
|
|
goto done;
|
|
|
|
p = JS_VALUE_GET_OBJ(arr);
|
|
if (expand_fast_array(ctx, p, newlen) < 0)
|
|
goto exception;
|
|
|
|
p->u.array.count = newlen;
|
|
pval = &p->u.array.u.values[0];
|
|
last = &p->u.array.u.values[newlen];
|
|
|
|
if (js_get_fast_array(ctx, obj, &arrp, &count32) && count32 == len) {
|
|
for (i = 0; i < start; i++, pval++)
|
|
*pval = js_dup(arrp[i]);
|
|
for (j = 0; j < add; j++, pval++)
|
|
*pval = js_dup(argv[2 + j]);
|
|
for (i += del; i < len; i++, pval++)
|
|
*pval = js_dup(arrp[i]);
|
|
} else {
|
|
for (i = 0; i < start; i++, pval++)
|
|
if (-1 == JS_TryGetPropertyInt64(ctx, obj, i, pval))
|
|
goto exception;
|
|
for (j = 0; j < add; j++, pval++)
|
|
*pval = js_dup(argv[2 + j]);
|
|
for (i += del; i < len; i++, pval++)
|
|
if (-1 == JS_TryGetPropertyInt64(ctx, obj, i, pval))
|
|
goto exception;
|
|
}
|
|
|
|
assert(pval == last);
|
|
|
|
if (JS_SetProperty(ctx, arr, JS_ATOM_length, JS_NewInt64(ctx, newlen)) < 0)
|
|
goto exception;
|
|
|
|
done:
|
|
ret = arr;
|
|
arr = JS_UNDEFINED;
|
|
|
|
exception:
|
|
while (pval != last)
|
|
*pval++ = JS_UNDEFINED;
|
|
|
|
JS_FreeValue(ctx, arr);
|
|
JS_FreeValue(ctx, obj);
|
|
return ret;
|
|
}
|
|
|
|
static JSValue js_array_copyWithin(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue obj;
|
|
int64_t len, from, to, final, count;
|
|
|
|
obj = JS_ToObject(ctx, this_val);
|
|
if (js_get_length64(ctx, &len, obj))
|
|
goto exception;
|
|
|
|
if (JS_ToInt64Clamp(ctx, &to, argv[0], 0, len, len))
|
|
goto exception;
|
|
|
|
if (JS_ToInt64Clamp(ctx, &from, argv[1], 0, len, len))
|
|
goto exception;
|
|
|
|
final = len;
|
|
if (argc > 2 && !JS_IsUndefined(argv[2])) {
|
|
if (JS_ToInt64Clamp(ctx, &final, argv[2], 0, len, len))
|
|
goto exception;
|
|
}
|
|
|
|
count = min_int64(final - from, len - to);
|
|
|
|
if (JS_CopySubArray(ctx, obj, to, from, count,
|
|
(from < to && to < from + count) ? -1 : +1))
|
|
goto exception;
|
|
|
|
return obj;
|
|
|
|
exception:
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static int64_t JS_FlattenIntoArray(JSContext *ctx, JSValue target,
|
|
JSValue source, int64_t sourceLen,
|
|
int64_t targetIndex, int depth,
|
|
JSValue mapperFunction,
|
|
JSValue thisArg)
|
|
{
|
|
JSValue element;
|
|
int64_t sourceIndex, elementLen;
|
|
int present, is_array;
|
|
|
|
if (js_check_stack_overflow(ctx->rt, 0)) {
|
|
JS_ThrowStackOverflow(ctx);
|
|
return -1;
|
|
}
|
|
|
|
for (sourceIndex = 0; sourceIndex < sourceLen; sourceIndex++) {
|
|
present = JS_TryGetPropertyInt64(ctx, source, sourceIndex, &element);
|
|
if (present < 0)
|
|
return -1;
|
|
if (!present)
|
|
continue;
|
|
if (!JS_IsUndefined(mapperFunction)) {
|
|
JSValue args[3] = { element, JS_NewInt64(ctx, sourceIndex), source };
|
|
element = JS_Call(ctx, mapperFunction, thisArg, 3, args);
|
|
JS_FreeValue(ctx, args[0]);
|
|
JS_FreeValue(ctx, args[1]);
|
|
if (JS_IsException(element))
|
|
return -1;
|
|
}
|
|
if (depth > 0) {
|
|
is_array = JS_IsArray(ctx, element);
|
|
if (is_array < 0)
|
|
goto fail;
|
|
if (is_array) {
|
|
if (js_get_length64(ctx, &elementLen, element) < 0)
|
|
goto fail;
|
|
targetIndex = JS_FlattenIntoArray(ctx, target, element,
|
|
elementLen, targetIndex,
|
|
depth - 1,
|
|
JS_UNDEFINED, JS_UNDEFINED);
|
|
if (targetIndex < 0)
|
|
goto fail;
|
|
JS_FreeValue(ctx, element);
|
|
continue;
|
|
}
|
|
}
|
|
if (targetIndex >= MAX_SAFE_INTEGER) {
|
|
JS_ThrowTypeError(ctx, "Array too long");
|
|
goto fail;
|
|
}
|
|
if (JS_DefinePropertyValueInt64(ctx, target, targetIndex, element,
|
|
JS_PROP_C_W_E | JS_PROP_THROW) < 0)
|
|
return -1;
|
|
targetIndex++;
|
|
}
|
|
return targetIndex;
|
|
|
|
fail:
|
|
JS_FreeValue(ctx, element);
|
|
return -1;
|
|
}
|
|
|
|
static JSValue js_array_flatten(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv, int map)
|
|
{
|
|
JSValue obj, arr;
|
|
JSValue mapperFunction, thisArg;
|
|
int64_t sourceLen;
|
|
int depthNum;
|
|
|
|
arr = JS_UNDEFINED;
|
|
obj = JS_ToObject(ctx, this_val);
|
|
if (js_get_length64(ctx, &sourceLen, obj))
|
|
goto exception;
|
|
|
|
depthNum = 1;
|
|
mapperFunction = JS_UNDEFINED;
|
|
thisArg = JS_UNDEFINED;
|
|
if (map) {
|
|
mapperFunction = argv[0];
|
|
if (argc > 1) {
|
|
thisArg = argv[1];
|
|
}
|
|
if (check_function(ctx, mapperFunction))
|
|
goto exception;
|
|
} else {
|
|
if (argc > 0 && !JS_IsUndefined(argv[0])) {
|
|
if (JS_ToInt32Sat(ctx, &depthNum, argv[0]) < 0)
|
|
goto exception;
|
|
}
|
|
}
|
|
arr = JS_ArraySpeciesCreate(ctx, obj, js_int32(0));
|
|
if (JS_IsException(arr))
|
|
goto exception;
|
|
if (JS_FlattenIntoArray(ctx, arr, obj, sourceLen, 0, depthNum,
|
|
mapperFunction, thisArg) < 0)
|
|
goto exception;
|
|
JS_FreeValue(ctx, obj);
|
|
return arr;
|
|
|
|
exception:
|
|
JS_FreeValue(ctx, obj);
|
|
JS_FreeValue(ctx, arr);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
/* Array sort */
|
|
|
|
typedef struct ValueSlot {
|
|
JSValue val;
|
|
JSString *str;
|
|
int64_t pos;
|
|
} ValueSlot;
|
|
|
|
struct array_sort_context {
|
|
JSContext *ctx;
|
|
int exception;
|
|
int has_method;
|
|
JSValue method;
|
|
};
|
|
|
|
static int js_array_cmp_generic(const void *a, const void *b, void *opaque) {
|
|
struct array_sort_context *psc = opaque;
|
|
JSContext *ctx = psc->ctx;
|
|
JSValue argv[2];
|
|
JSValue res;
|
|
ValueSlot *ap = (ValueSlot *)(void *)a;
|
|
ValueSlot *bp = (ValueSlot *)(void *)b;
|
|
int cmp;
|
|
|
|
if (psc->exception)
|
|
return 0;
|
|
|
|
if (psc->has_method) {
|
|
/* custom sort function is specified as returning 0 for identical
|
|
* objects: avoid method call overhead.
|
|
*/
|
|
if (!memcmp(&ap->val, &bp->val, sizeof(ap->val)))
|
|
goto cmp_same;
|
|
argv[0] = ap->val;
|
|
argv[1] = bp->val;
|
|
res = JS_Call(ctx, psc->method, JS_UNDEFINED, 2, argv);
|
|
if (JS_IsException(res))
|
|
goto exception;
|
|
if (JS_VALUE_GET_TAG(res) == JS_TAG_INT) {
|
|
int val = JS_VALUE_GET_INT(res);
|
|
cmp = (val > 0) - (val < 0);
|
|
} else {
|
|
double val;
|
|
if (JS_ToFloat64Free(ctx, &val, res) < 0)
|
|
goto exception;
|
|
cmp = (val > 0) - (val < 0);
|
|
}
|
|
} else {
|
|
/* Not supposed to bypass ToString even for identical objects as
|
|
* tested in test262/test/built-ins/Array/prototype/sort/bug_596_1.js
|
|
*/
|
|
if (!ap->str) {
|
|
JSValue str = JS_ToString(ctx, ap->val);
|
|
if (JS_IsException(str))
|
|
goto exception;
|
|
ap->str = JS_VALUE_GET_STRING(str);
|
|
}
|
|
if (!bp->str) {
|
|
JSValue str = JS_ToString(ctx, bp->val);
|
|
if (JS_IsException(str))
|
|
goto exception;
|
|
bp->str = JS_VALUE_GET_STRING(str);
|
|
}
|
|
cmp = js_string_compare(ctx, ap->str, bp->str);
|
|
}
|
|
if (cmp != 0)
|
|
return cmp;
|
|
cmp_same:
|
|
/* make sort stable: compare array offsets */
|
|
return (ap->pos > bp->pos) - (ap->pos < bp->pos);
|
|
|
|
exception:
|
|
psc->exception = 1;
|
|
return 0;
|
|
}
|
|
|
|
static JSValue js_array_sort(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
struct array_sort_context asc = { ctx, 0, 0, argv[0] };
|
|
JSValue obj = JS_UNDEFINED;
|
|
ValueSlot *array = NULL;
|
|
size_t array_size = 0, pos = 0, n = 0;
|
|
int64_t i, len, undefined_count = 0;
|
|
int present;
|
|
|
|
if (!JS_IsUndefined(asc.method)) {
|
|
if (check_function(ctx, asc.method))
|
|
goto exception;
|
|
asc.has_method = 1;
|
|
}
|
|
obj = JS_ToObject(ctx, this_val);
|
|
if (js_get_length64(ctx, &len, obj))
|
|
goto exception;
|
|
|
|
/* XXX: should special case fast arrays */
|
|
for (i = 0; i < len; i++) {
|
|
if (pos >= array_size) {
|
|
size_t new_size, slack;
|
|
ValueSlot *new_array;
|
|
new_size = (array_size + (array_size >> 1) + 31) & ~15;
|
|
new_array = js_realloc2(ctx, array, new_size * sizeof(*array), &slack);
|
|
if (new_array == NULL)
|
|
goto exception;
|
|
new_size += slack / sizeof(*new_array);
|
|
array = new_array;
|
|
array_size = new_size;
|
|
}
|
|
present = JS_TryGetPropertyInt64(ctx, obj, i, &array[pos].val);
|
|
if (present < 0)
|
|
goto exception;
|
|
if (present == 0)
|
|
continue;
|
|
if (JS_IsUndefined(array[pos].val)) {
|
|
undefined_count++;
|
|
continue;
|
|
}
|
|
array[pos].str = NULL;
|
|
array[pos].pos = i;
|
|
pos++;
|
|
}
|
|
rqsort(array, pos, sizeof(*array), js_array_cmp_generic, &asc);
|
|
if (asc.exception)
|
|
goto exception;
|
|
|
|
/* XXX: should special case fast arrays */
|
|
while (n < pos) {
|
|
if (array[n].str)
|
|
JS_FreeValue(ctx, JS_MKPTR(JS_TAG_STRING, array[n].str));
|
|
if (array[n].pos == n) {
|
|
JS_FreeValue(ctx, array[n].val);
|
|
} else {
|
|
if (JS_SetPropertyInt64(ctx, obj, n, array[n].val) < 0) {
|
|
n++;
|
|
goto exception;
|
|
}
|
|
}
|
|
n++;
|
|
}
|
|
js_free(ctx, array);
|
|
for (i = n; undefined_count-- > 0; i++) {
|
|
if (JS_SetPropertyInt64(ctx, obj, i, JS_UNDEFINED) < 0)
|
|
goto fail;
|
|
}
|
|
for (; i < len; i++) {
|
|
if (JS_DeletePropertyInt64(ctx, obj, i, JS_PROP_THROW) < 0)
|
|
goto fail;
|
|
}
|
|
return obj;
|
|
|
|
exception:
|
|
for (; n < pos; n++) {
|
|
JS_FreeValue(ctx, array[n].val);
|
|
if (array[n].str)
|
|
JS_FreeValue(ctx, JS_MKPTR(JS_TAG_STRING, array[n].str));
|
|
}
|
|
js_free(ctx, array);
|
|
fail:
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
// Note: a.toSorted() is a.slice().sort() with the twist that a.slice()
|
|
// leaves holes in sparse arrays intact whereas a.toSorted() replaces them
|
|
// with undefined, thus in effect creating a dense array.
|
|
// Does not use Array[@@species], always returns a base Array.
|
|
static JSValue js_array_toSorted(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue arr, obj, ret, *arrp, *pval;
|
|
JSObject *p;
|
|
int64_t i, len;
|
|
uint32_t count32;
|
|
int ok;
|
|
|
|
ok = JS_IsUndefined(argv[0]) || JS_IsFunction(ctx, argv[0]);
|
|
if (!ok)
|
|
return JS_ThrowTypeError(ctx, "not a function");
|
|
|
|
ret = JS_EXCEPTION;
|
|
arr = JS_UNDEFINED;
|
|
obj = JS_ToObject(ctx, this_val);
|
|
if (js_get_length64(ctx, &len, obj))
|
|
goto exception;
|
|
|
|
if (len > UINT32_MAX) {
|
|
JS_ThrowRangeError(ctx, "invalid array length");
|
|
goto exception;
|
|
}
|
|
|
|
arr = JS_NewArray(ctx);
|
|
if (JS_IsException(arr))
|
|
goto exception;
|
|
|
|
if (len > 0) {
|
|
p = JS_VALUE_GET_OBJ(arr);
|
|
if (expand_fast_array(ctx, p, len) < 0)
|
|
goto exception;
|
|
p->u.array.count = len;
|
|
|
|
i = 0;
|
|
pval = p->u.array.u.values;
|
|
if (js_get_fast_array(ctx, obj, &arrp, &count32) && count32 == len) {
|
|
for (; i < len; i++, pval++)
|
|
*pval = js_dup(arrp[i]);
|
|
} else {
|
|
for (; i < len; i++, pval++) {
|
|
if (-1 == JS_TryGetPropertyInt64(ctx, obj, i, pval)) {
|
|
for (; i < len; i++, pval++)
|
|
*pval = JS_UNDEFINED;
|
|
goto exception;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (JS_SetProperty(ctx, arr, JS_ATOM_length, JS_NewInt64(ctx, len)) < 0)
|
|
goto exception;
|
|
}
|
|
|
|
ret = js_array_sort(ctx, arr, argc, argv);
|
|
if (JS_IsException(ret))
|
|
goto exception;
|
|
JS_FreeValue(ctx, ret);
|
|
|
|
ret = arr;
|
|
arr = JS_UNDEFINED;
|
|
|
|
exception:
|
|
JS_FreeValue(ctx, arr);
|
|
JS_FreeValue(ctx, obj);
|
|
return ret;
|
|
}
|
|
|
|
typedef struct JSArrayIteratorData {
|
|
JSValue obj;
|
|
JSIteratorKindEnum kind;
|
|
uint32_t idx;
|
|
} JSArrayIteratorData;
|
|
|
|
static void js_array_iterator_finalizer(JSRuntime *rt, JSValue val)
|
|
{
|
|
JSObject *p = JS_VALUE_GET_OBJ(val);
|
|
JSArrayIteratorData *it = p->u.array_iterator_data;
|
|
if (it) {
|
|
JS_FreeValueRT(rt, it->obj);
|
|
js_free_rt(rt, it);
|
|
}
|
|
}
|
|
|
|
static void js_array_iterator_mark(JSRuntime *rt, JSValue val,
|
|
JS_MarkFunc *mark_func)
|
|
{
|
|
JSObject *p = JS_VALUE_GET_OBJ(val);
|
|
JSArrayIteratorData *it = p->u.array_iterator_data;
|
|
if (it) {
|
|
JS_MarkValue(rt, it->obj, mark_func);
|
|
}
|
|
}
|
|
|
|
static JSValue js_create_array(JSContext *ctx, int len, JSValue *tab)
|
|
{
|
|
JSValue obj;
|
|
int i;
|
|
|
|
obj = JS_NewArray(ctx);
|
|
if (JS_IsException(obj))
|
|
return JS_EXCEPTION;
|
|
for(i = 0; i < len; i++) {
|
|
if (JS_CreateDataPropertyUint32(ctx, obj, i, js_dup(tab[i]), 0) < 0) {
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
}
|
|
return obj;
|
|
}
|
|
|
|
static JSValue js_create_array_iterator(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv, int magic)
|
|
{
|
|
JSValue enum_obj, arr;
|
|
JSArrayIteratorData *it;
|
|
JSIteratorKindEnum kind;
|
|
int class_id;
|
|
|
|
kind = magic & 3;
|
|
if (magic & 4) {
|
|
/* string iterator case */
|
|
arr = JS_ToStringCheckObject(ctx, this_val);
|
|
class_id = JS_CLASS_STRING_ITERATOR;
|
|
} else {
|
|
arr = JS_ToObject(ctx, this_val);
|
|
class_id = JS_CLASS_ARRAY_ITERATOR;
|
|
}
|
|
if (JS_IsException(arr))
|
|
goto fail;
|
|
enum_obj = JS_NewObjectClass(ctx, class_id);
|
|
if (JS_IsException(enum_obj))
|
|
goto fail;
|
|
it = js_malloc(ctx, sizeof(*it));
|
|
if (!it)
|
|
goto fail1;
|
|
it->obj = arr;
|
|
it->kind = kind;
|
|
it->idx = 0;
|
|
JS_SetOpaque(enum_obj, it);
|
|
return enum_obj;
|
|
fail1:
|
|
JS_FreeValue(ctx, enum_obj);
|
|
fail:
|
|
JS_FreeValue(ctx, arr);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue js_array_iterator_next(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv,
|
|
BOOL *pdone, int magic)
|
|
{
|
|
JSArrayIteratorData *it;
|
|
uint32_t len, idx;
|
|
JSValue val, obj;
|
|
JSObject *p;
|
|
|
|
it = JS_GetOpaque2(ctx, this_val, JS_CLASS_ARRAY_ITERATOR);
|
|
if (!it)
|
|
goto fail1;
|
|
if (JS_IsUndefined(it->obj))
|
|
goto done;
|
|
p = JS_VALUE_GET_OBJ(it->obj);
|
|
if (p->class_id >= JS_CLASS_UINT8C_ARRAY &&
|
|
p->class_id <= JS_CLASS_FLOAT64_ARRAY) {
|
|
if (typed_array_is_detached(ctx, p)) {
|
|
JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
|
|
goto fail1;
|
|
}
|
|
len = p->u.array.count;
|
|
} else {
|
|
if (js_get_length32(ctx, &len, it->obj)) {
|
|
fail1:
|
|
*pdone = FALSE;
|
|
return JS_EXCEPTION;
|
|
}
|
|
}
|
|
idx = it->idx;
|
|
if (idx >= len) {
|
|
JS_FreeValue(ctx, it->obj);
|
|
it->obj = JS_UNDEFINED;
|
|
done:
|
|
*pdone = TRUE;
|
|
return JS_UNDEFINED;
|
|
}
|
|
it->idx = idx + 1;
|
|
*pdone = FALSE;
|
|
if (it->kind == JS_ITERATOR_KIND_KEY) {
|
|
return js_uint32(idx);
|
|
} else {
|
|
val = JS_GetPropertyUint32(ctx, it->obj, idx);
|
|
if (JS_IsException(val))
|
|
return JS_EXCEPTION;
|
|
if (it->kind == JS_ITERATOR_KIND_VALUE) {
|
|
return val;
|
|
} else {
|
|
JSValue args[2];
|
|
JSValue num;
|
|
num = js_uint32(idx);
|
|
args[0] = num;
|
|
args[1] = val;
|
|
obj = js_create_array(ctx, 2, args);
|
|
JS_FreeValue(ctx, val);
|
|
JS_FreeValue(ctx, num);
|
|
return obj;
|
|
}
|
|
}
|
|
}
|
|
|
|
static JSValue js_iterator_proto_iterator(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
return js_dup(this_val);
|
|
}
|
|
|
|
static const JSCFunctionListEntry js_iterator_proto_funcs[] = {
|
|
JS_CFUNC_DEF("[Symbol.iterator]", 0, js_iterator_proto_iterator ),
|
|
};
|
|
|
|
static const JSCFunctionListEntry js_array_proto_funcs[] = {
|
|
JS_CFUNC_DEF("at", 1, js_array_at ),
|
|
JS_CFUNC_DEF("with", 2, js_array_with ),
|
|
JS_CFUNC_DEF("concat", 1, js_array_concat ),
|
|
JS_CFUNC_MAGIC_DEF("every", 1, js_array_every, special_every ),
|
|
JS_CFUNC_MAGIC_DEF("some", 1, js_array_every, special_some ),
|
|
JS_CFUNC_MAGIC_DEF("forEach", 1, js_array_every, special_forEach ),
|
|
JS_CFUNC_MAGIC_DEF("map", 1, js_array_every, special_map ),
|
|
JS_CFUNC_MAGIC_DEF("filter", 1, js_array_every, special_filter ),
|
|
JS_CFUNC_MAGIC_DEF("reduce", 1, js_array_reduce, special_reduce ),
|
|
JS_CFUNC_MAGIC_DEF("reduceRight", 1, js_array_reduce, special_reduceRight ),
|
|
JS_CFUNC_DEF("fill", 1, js_array_fill ),
|
|
JS_CFUNC_MAGIC_DEF("find", 1, js_array_find, ArrayFind ),
|
|
JS_CFUNC_MAGIC_DEF("findIndex", 1, js_array_find, ArrayFindIndex ),
|
|
JS_CFUNC_MAGIC_DEF("findLast", 1, js_array_find, ArrayFindLast ),
|
|
JS_CFUNC_MAGIC_DEF("findLastIndex", 1, js_array_find, ArrayFindLastIndex ),
|
|
JS_CFUNC_DEF("indexOf", 1, js_array_indexOf ),
|
|
JS_CFUNC_DEF("lastIndexOf", 1, js_array_lastIndexOf ),
|
|
JS_CFUNC_DEF("includes", 1, js_array_includes ),
|
|
JS_CFUNC_MAGIC_DEF("join", 1, js_array_join, 0 ),
|
|
JS_CFUNC_DEF("toString", 0, js_array_toString ),
|
|
JS_CFUNC_MAGIC_DEF("toLocaleString", 0, js_array_join, 1 ),
|
|
JS_CFUNC_MAGIC_DEF("pop", 0, js_array_pop, 0 ),
|
|
JS_CFUNC_MAGIC_DEF("push", 1, js_array_push, 0 ),
|
|
JS_CFUNC_MAGIC_DEF("shift", 0, js_array_pop, 1 ),
|
|
JS_CFUNC_MAGIC_DEF("unshift", 1, js_array_push, 1 ),
|
|
JS_CFUNC_DEF("reverse", 0, js_array_reverse ),
|
|
JS_CFUNC_DEF("toReversed", 0, js_array_toReversed ),
|
|
JS_CFUNC_DEF("sort", 1, js_array_sort ),
|
|
JS_CFUNC_DEF("toSorted", 1, js_array_toSorted ),
|
|
JS_CFUNC_MAGIC_DEF("slice", 2, js_array_slice, 0 ),
|
|
JS_CFUNC_MAGIC_DEF("splice", 2, js_array_slice, 1 ),
|
|
JS_CFUNC_DEF("toSpliced", 2, js_array_toSpliced ),
|
|
JS_CFUNC_DEF("copyWithin", 2, js_array_copyWithin ),
|
|
JS_CFUNC_MAGIC_DEF("flatMap", 1, js_array_flatten, 1 ),
|
|
JS_CFUNC_MAGIC_DEF("flat", 0, js_array_flatten, 0 ),
|
|
JS_CFUNC_MAGIC_DEF("values", 0, js_create_array_iterator, JS_ITERATOR_KIND_VALUE ),
|
|
JS_ALIAS_DEF("[Symbol.iterator]", "values" ),
|
|
JS_CFUNC_MAGIC_DEF("keys", 0, js_create_array_iterator, JS_ITERATOR_KIND_KEY ),
|
|
JS_CFUNC_MAGIC_DEF("entries", 0, js_create_array_iterator, JS_ITERATOR_KIND_KEY_AND_VALUE ),
|
|
};
|
|
|
|
static const JSCFunctionListEntry js_array_iterator_proto_funcs[] = {
|
|
JS_ITERATOR_NEXT_DEF("next", 0, js_array_iterator_next, 0 ),
|
|
JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Array Iterator", JS_PROP_CONFIGURABLE ),
|
|
};
|
|
|
|
/* Number */
|
|
|
|
static JSValue js_number_constructor(JSContext *ctx, JSValue new_target,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue val, obj;
|
|
if (argc == 0) {
|
|
val = js_int32(0);
|
|
} else {
|
|
val = JS_ToNumeric(ctx, argv[0]);
|
|
if (JS_IsException(val))
|
|
return val;
|
|
switch(JS_VALUE_GET_TAG(val)) {
|
|
case JS_TAG_BIG_INT:
|
|
{
|
|
JSBigInt *p = JS_VALUE_GET_PTR(val);
|
|
double d;
|
|
bf_get_float64(&p->num, &d, BF_RNDN);
|
|
JS_FreeValue(ctx, val);
|
|
val = js_float64(d);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
if (!JS_IsUndefined(new_target)) {
|
|
obj = js_create_from_ctor(ctx, new_target, JS_CLASS_NUMBER);
|
|
if (!JS_IsException(obj))
|
|
JS_SetObjectData(ctx, obj, val);
|
|
return obj;
|
|
} else {
|
|
return val;
|
|
}
|
|
}
|
|
|
|
static JSValue js_number_isNaN(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
if (!JS_IsNumber(argv[0]))
|
|
return JS_FALSE;
|
|
return js_global_isNaN(ctx, this_val, argc, argv);
|
|
}
|
|
|
|
static JSValue js_number_isFinite(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
if (!JS_IsNumber(argv[0]))
|
|
return JS_FALSE;
|
|
return js_global_isFinite(ctx, this_val, argc, argv);
|
|
}
|
|
|
|
static JSValue js_number_isInteger(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
int ret;
|
|
ret = JS_NumberIsInteger(ctx, argv[0]);
|
|
if (ret < 0)
|
|
return JS_EXCEPTION;
|
|
else
|
|
return js_bool(ret);
|
|
}
|
|
|
|
static JSValue js_number_isSafeInteger(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
double d;
|
|
if (!JS_IsNumber(argv[0]))
|
|
return JS_FALSE;
|
|
if (unlikely(JS_ToFloat64(ctx, &d, argv[0])))
|
|
return JS_EXCEPTION;
|
|
return js_bool(is_safe_integer(d));
|
|
}
|
|
|
|
static const JSCFunctionListEntry js_number_funcs[] = {
|
|
/* global ParseInt and parseFloat should be defined already or delayed */
|
|
JS_ALIAS_BASE_DEF("parseInt", "parseInt", 0 ),
|
|
JS_ALIAS_BASE_DEF("parseFloat", "parseFloat", 0 ),
|
|
JS_CFUNC_DEF("isNaN", 1, js_number_isNaN ),
|
|
JS_CFUNC_DEF("isFinite", 1, js_number_isFinite ),
|
|
JS_CFUNC_DEF("isInteger", 1, js_number_isInteger ),
|
|
JS_CFUNC_DEF("isSafeInteger", 1, js_number_isSafeInteger ),
|
|
JS_PROP_DOUBLE_DEF("MAX_VALUE", 1.7976931348623157e+308, 0 ),
|
|
JS_PROP_DOUBLE_DEF("MIN_VALUE", 5e-324, 0 ),
|
|
JS_PROP_DOUBLE_DEF("NaN", NAN, 0 ),
|
|
JS_PROP_DOUBLE_DEF("NEGATIVE_INFINITY", -INFINITY, 0 ),
|
|
JS_PROP_DOUBLE_DEF("POSITIVE_INFINITY", INFINITY, 0 ),
|
|
JS_PROP_DOUBLE_DEF("EPSILON", 2.220446049250313e-16, 0 ), /* ES6 */
|
|
JS_PROP_DOUBLE_DEF("MAX_SAFE_INTEGER", 9007199254740991.0, 0 ), /* ES6 */
|
|
JS_PROP_DOUBLE_DEF("MIN_SAFE_INTEGER", -9007199254740991.0, 0 ), /* ES6 */
|
|
};
|
|
|
|
static JSValue js_thisNumberValue(JSContext *ctx, JSValue this_val)
|
|
{
|
|
if (JS_IsNumber(this_val))
|
|
return js_dup(this_val);
|
|
|
|
if (JS_VALUE_GET_TAG(this_val) == JS_TAG_OBJECT) {
|
|
JSObject *p = JS_VALUE_GET_OBJ(this_val);
|
|
if (p->class_id == JS_CLASS_NUMBER) {
|
|
if (JS_IsNumber(p->u.object_data))
|
|
return js_dup(p->u.object_data);
|
|
}
|
|
}
|
|
return JS_ThrowTypeError(ctx, "not a number");
|
|
}
|
|
|
|
static JSValue js_number_valueOf(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
return js_thisNumberValue(ctx, this_val);
|
|
}
|
|
|
|
static int js_get_radix(JSContext *ctx, JSValue val)
|
|
{
|
|
int radix;
|
|
if (JS_ToInt32Sat(ctx, &radix, val))
|
|
return -1;
|
|
if (radix < 2 || radix > 36) {
|
|
JS_ThrowRangeError(ctx, "toString() radix argument must be between 2 and 36");
|
|
return -1;
|
|
}
|
|
return radix;
|
|
}
|
|
|
|
static JSValue js_number_toString(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv, int magic)
|
|
{
|
|
char buf[72];
|
|
JSValue val;
|
|
int base;
|
|
double d;
|
|
|
|
val = js_thisNumberValue(ctx, this_val);
|
|
if (JS_IsException(val))
|
|
return val;
|
|
if (magic || JS_IsUndefined(argv[0])) {
|
|
if (JS_VALUE_GET_TAG(val) == JS_TAG_INT) {
|
|
size_t len = i32toa(buf, JS_VALUE_GET_INT(val));
|
|
return js_new_string8_len(ctx, buf, len);
|
|
}
|
|
base = 10;
|
|
} else {
|
|
base = js_get_radix(ctx, argv[0]);
|
|
if (base < 0) {
|
|
JS_FreeValue(ctx, val);
|
|
return JS_EXCEPTION;
|
|
}
|
|
}
|
|
if (JS_VALUE_GET_TAG(val) == JS_TAG_INT) {
|
|
size_t len = i32toa_radix(buf, JS_VALUE_GET_INT(val), base);
|
|
return js_new_string8_len(ctx, buf, len);
|
|
}
|
|
if (JS_ToFloat64Free(ctx, &d, val))
|
|
return JS_EXCEPTION;
|
|
if (base != 10)
|
|
return js_dtoa_radix(ctx, d, base);
|
|
|
|
return js_dtoa(ctx, d, 0, JS_DTOA_TOSTRING);
|
|
}
|
|
|
|
static JSValue js_number_toFixed(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue val;
|
|
int f;
|
|
double d;
|
|
|
|
val = js_thisNumberValue(ctx, this_val);
|
|
if (JS_IsException(val))
|
|
return val;
|
|
if (JS_ToFloat64Free(ctx, &d, val))
|
|
return JS_EXCEPTION;
|
|
if (JS_ToInt32Sat(ctx, &f, argv[0]))
|
|
return JS_EXCEPTION;
|
|
if (f < 0 || f > 100) {
|
|
return JS_ThrowRangeError(ctx, "toFixed() digits argument must be between 0 and 100");
|
|
}
|
|
if (fabs(d) >= 1e21) {
|
|
// use ToString(d)
|
|
return js_dtoa(ctx, d, 0, JS_DTOA_TOSTRING);
|
|
} else {
|
|
return js_dtoa(ctx, d, f, JS_DTOA_FIXED);
|
|
}
|
|
}
|
|
|
|
static JSValue js_number_toExponential(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue val;
|
|
double d;
|
|
int f;
|
|
|
|
val = js_thisNumberValue(ctx, this_val);
|
|
if (JS_IsException(val))
|
|
return val;
|
|
if (JS_ToFloat64Free(ctx, &d, val))
|
|
return JS_EXCEPTION;
|
|
if (JS_ToInt32Sat(ctx, &f, argv[0]))
|
|
return JS_EXCEPTION;
|
|
if (!isfinite(d))
|
|
return js_dtoa_infinite(ctx, d);
|
|
if (!JS_IsUndefined(argv[0])) {
|
|
if (f < 0 || f > 100) {
|
|
return JS_ThrowRangeError(ctx, "toExponential() argument must be between 0 and 100");
|
|
}
|
|
f += 1; /* number of significant digits between 1 and 101 */
|
|
}
|
|
return js_dtoa(ctx, d, f, JS_DTOA_EXPONENTIAL);
|
|
}
|
|
|
|
static JSValue js_number_toPrecision(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue val;
|
|
int p;
|
|
double d;
|
|
|
|
val = js_thisNumberValue(ctx, this_val);
|
|
if (JS_IsException(val))
|
|
return val;
|
|
if (JS_ToFloat64Free(ctx, &d, val))
|
|
return JS_EXCEPTION;
|
|
if (JS_IsUndefined(argv[0]))
|
|
return js_dtoa(ctx, d, 0, JS_DTOA_TOSTRING);
|
|
if (JS_ToInt32Sat(ctx, &p, argv[0]))
|
|
return JS_EXCEPTION;
|
|
if (!isfinite(d))
|
|
return js_dtoa_infinite(ctx, d);
|
|
if (p < 1 || p > 100) {
|
|
return JS_ThrowRangeError(ctx, "toPrecision() argument must be between 1 and 100");
|
|
}
|
|
return js_dtoa(ctx, d, p, JS_DTOA_PRECISION);
|
|
}
|
|
|
|
static const JSCFunctionListEntry js_number_proto_funcs[] = {
|
|
JS_CFUNC_DEF("toExponential", 1, js_number_toExponential ),
|
|
JS_CFUNC_DEF("toFixed", 1, js_number_toFixed ),
|
|
JS_CFUNC_DEF("toPrecision", 1, js_number_toPrecision ),
|
|
JS_CFUNC_MAGIC_DEF("toString", 1, js_number_toString, 0 ),
|
|
JS_CFUNC_MAGIC_DEF("toLocaleString", 0, js_number_toString, 1 ),
|
|
JS_CFUNC_DEF("valueOf", 0, js_number_valueOf ),
|
|
};
|
|
|
|
static JSValue js_parseInt(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
const char *str;
|
|
int radix, flags;
|
|
JSValue ret;
|
|
size_t len;
|
|
|
|
str = JS_ToCStringLen(ctx, &len, argv[0]);
|
|
if (!str)
|
|
return JS_EXCEPTION;
|
|
if (JS_ToInt32(ctx, &radix, argv[1])) {
|
|
JS_FreeCString(ctx, str);
|
|
return JS_EXCEPTION;
|
|
}
|
|
flags = ATOD_TRIM_SPACES;
|
|
if (radix == 0) {
|
|
flags |= ATOD_ACCEPT_HEX_PREFIX; // Only 0x and 0X are supported
|
|
radix = 10;
|
|
}
|
|
ret = js_atof(ctx, str, str + len, NULL, radix, flags);
|
|
JS_FreeCString(ctx, str);
|
|
return ret;
|
|
}
|
|
|
|
static JSValue js_parseFloat(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
const char *str;
|
|
JSValue ret;
|
|
int flags;
|
|
size_t len;
|
|
|
|
str = JS_ToCStringLen(ctx, &len, argv[0]);
|
|
if (!str)
|
|
return JS_EXCEPTION;
|
|
flags = ATOD_TRIM_SPACES | ATOD_ACCEPT_FLOAT | ATOD_ACCEPT_INFINITY;
|
|
ret = js_atof(ctx, str, str + len, NULL, 10, flags);
|
|
JS_FreeCString(ctx, str);
|
|
return ret;
|
|
}
|
|
|
|
/* Boolean */
|
|
static JSValue js_boolean_constructor(JSContext *ctx, JSValue new_target,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue val, obj;
|
|
val = js_bool(JS_ToBool(ctx, argv[0]));
|
|
if (!JS_IsUndefined(new_target)) {
|
|
obj = js_create_from_ctor(ctx, new_target, JS_CLASS_BOOLEAN);
|
|
if (!JS_IsException(obj))
|
|
JS_SetObjectData(ctx, obj, val);
|
|
return obj;
|
|
} else {
|
|
return val;
|
|
}
|
|
}
|
|
|
|
static JSValue js_thisBooleanValue(JSContext *ctx, JSValue this_val)
|
|
{
|
|
if (JS_VALUE_GET_TAG(this_val) == JS_TAG_BOOL)
|
|
return js_dup(this_val);
|
|
|
|
if (JS_VALUE_GET_TAG(this_val) == JS_TAG_OBJECT) {
|
|
JSObject *p = JS_VALUE_GET_OBJ(this_val);
|
|
if (p->class_id == JS_CLASS_BOOLEAN) {
|
|
if (JS_VALUE_GET_TAG(p->u.object_data) == JS_TAG_BOOL)
|
|
return p->u.object_data;
|
|
}
|
|
}
|
|
return JS_ThrowTypeError(ctx, "not a boolean");
|
|
}
|
|
|
|
static JSValue js_boolean_toString(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue val = js_thisBooleanValue(ctx, this_val);
|
|
if (JS_IsException(val))
|
|
return val;
|
|
return JS_AtomToString(ctx, JS_VALUE_GET_BOOL(val) ?
|
|
JS_ATOM_true : JS_ATOM_false);
|
|
}
|
|
|
|
static JSValue js_boolean_valueOf(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
return js_thisBooleanValue(ctx, this_val);
|
|
}
|
|
|
|
static const JSCFunctionListEntry js_boolean_proto_funcs[] = {
|
|
JS_CFUNC_DEF("toString", 0, js_boolean_toString ),
|
|
JS_CFUNC_DEF("valueOf", 0, js_boolean_valueOf ),
|
|
};
|
|
|
|
/* String */
|
|
|
|
static int js_string_get_own_property(JSContext *ctx,
|
|
JSPropertyDescriptor *desc,
|
|
JSValue obj, JSAtom prop)
|
|
{
|
|
JSObject *p;
|
|
JSString *p1;
|
|
uint32_t idx, ch;
|
|
|
|
/* This is a class exotic method: obj class_id is JS_CLASS_STRING */
|
|
if (__JS_AtomIsTaggedInt(prop)) {
|
|
p = JS_VALUE_GET_OBJ(obj);
|
|
if (JS_VALUE_GET_TAG(p->u.object_data) == JS_TAG_STRING) {
|
|
p1 = JS_VALUE_GET_STRING(p->u.object_data);
|
|
idx = __JS_AtomToUInt32(prop);
|
|
if (idx < p1->len) {
|
|
if (desc) {
|
|
ch = string_get(p1, idx);
|
|
desc->flags = JS_PROP_ENUMERABLE;
|
|
desc->value = js_new_string_char(ctx, ch);
|
|
desc->getter = JS_UNDEFINED;
|
|
desc->setter = JS_UNDEFINED;
|
|
}
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static int js_string_define_own_property(JSContext *ctx,
|
|
JSValue this_obj,
|
|
JSAtom prop, JSValue val,
|
|
JSValue getter,
|
|
JSValue setter, int flags)
|
|
{
|
|
uint32_t idx;
|
|
JSObject *p;
|
|
JSString *p1, *p2;
|
|
|
|
if (__JS_AtomIsTaggedInt(prop)) {
|
|
idx = __JS_AtomToUInt32(prop);
|
|
p = JS_VALUE_GET_OBJ(this_obj);
|
|
if (JS_VALUE_GET_TAG(p->u.object_data) != JS_TAG_STRING)
|
|
goto def;
|
|
p1 = JS_VALUE_GET_STRING(p->u.object_data);
|
|
if (idx >= p1->len)
|
|
goto def;
|
|
if (!check_define_prop_flags(JS_PROP_ENUMERABLE, flags))
|
|
goto fail;
|
|
/* check that the same value is configured */
|
|
if (flags & JS_PROP_HAS_VALUE) {
|
|
if (JS_VALUE_GET_TAG(val) != JS_TAG_STRING)
|
|
goto fail;
|
|
p2 = JS_VALUE_GET_STRING(val);
|
|
if (p2->len != 1)
|
|
goto fail;
|
|
if (string_get(p1, idx) != string_get(p2, 0)) {
|
|
fail:
|
|
return JS_ThrowTypeErrorOrFalse(ctx, flags, "property is not configurable");
|
|
}
|
|
}
|
|
return TRUE;
|
|
} else {
|
|
def:
|
|
return JS_DefineProperty(ctx, this_obj, prop, val, getter, setter,
|
|
flags | JS_PROP_NO_EXOTIC);
|
|
}
|
|
}
|
|
|
|
static int js_string_delete_property(JSContext *ctx,
|
|
JSValue obj, JSAtom prop)
|
|
{
|
|
uint32_t idx;
|
|
|
|
if (__JS_AtomIsTaggedInt(prop)) {
|
|
idx = __JS_AtomToUInt32(prop);
|
|
if (idx < js_string_obj_get_length(ctx, obj)) {
|
|
return FALSE;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static const JSClassExoticMethods js_string_exotic_methods = {
|
|
.get_own_property = js_string_get_own_property,
|
|
.define_own_property = js_string_define_own_property,
|
|
.delete_property = js_string_delete_property,
|
|
};
|
|
|
|
static JSValue js_string_constructor(JSContext *ctx, JSValue new_target,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue val, obj;
|
|
if (argc == 0) {
|
|
val = JS_AtomToString(ctx, JS_ATOM_empty_string);
|
|
} else {
|
|
if (JS_IsUndefined(new_target) && JS_IsSymbol(argv[0])) {
|
|
JSAtomStruct *p = JS_VALUE_GET_PTR(argv[0]);
|
|
val = JS_ConcatString3(ctx, "Symbol(", JS_AtomToString(ctx, js_get_atom_index(ctx->rt, p)), ")");
|
|
} else {
|
|
val = JS_ToString(ctx, argv[0]);
|
|
}
|
|
if (JS_IsException(val))
|
|
return val;
|
|
}
|
|
if (!JS_IsUndefined(new_target)) {
|
|
JSString *p1 = JS_VALUE_GET_STRING(val);
|
|
|
|
obj = js_create_from_ctor(ctx, new_target, JS_CLASS_STRING);
|
|
if (!JS_IsException(obj)) {
|
|
JS_SetObjectData(ctx, obj, val);
|
|
JS_DefinePropertyValue(ctx, obj, JS_ATOM_length, js_int32(p1->len), 0);
|
|
}
|
|
return obj;
|
|
} else {
|
|
return val;
|
|
}
|
|
}
|
|
|
|
static JSValue js_thisStringValue(JSContext *ctx, JSValue this_val)
|
|
{
|
|
if (JS_VALUE_GET_TAG(this_val) == JS_TAG_STRING)
|
|
return js_dup(this_val);
|
|
|
|
if (JS_VALUE_GET_TAG(this_val) == JS_TAG_OBJECT) {
|
|
JSObject *p = JS_VALUE_GET_OBJ(this_val);
|
|
if (p->class_id == JS_CLASS_STRING) {
|
|
if (JS_VALUE_GET_TAG(p->u.object_data) == JS_TAG_STRING)
|
|
return js_dup(p->u.object_data);
|
|
}
|
|
}
|
|
return JS_ThrowTypeError(ctx, "not a string");
|
|
}
|
|
|
|
static JSValue js_string_fromCharCode(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
int i;
|
|
StringBuffer b_s, *b = &b_s;
|
|
|
|
// shortcut for single argument common case
|
|
if (argc == 1 && JS_VALUE_GET_TAG(argv[0]) == JS_TAG_INT) {
|
|
uint16_t c16 = JS_VALUE_GET_INT(argv[0]);
|
|
return js_new_string_char(ctx, c16);
|
|
}
|
|
|
|
string_buffer_init(ctx, b, argc);
|
|
|
|
for(i = 0; i < argc; i++) {
|
|
int32_t c;
|
|
if (JS_ToInt32(ctx, &c, argv[i]) || string_buffer_putc16(b, c & 0xffff)) {
|
|
string_buffer_free(b);
|
|
return JS_EXCEPTION;
|
|
}
|
|
}
|
|
return string_buffer_end(b);
|
|
}
|
|
|
|
static JSValue js_string_fromCodePoint(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
double d;
|
|
int i, c;
|
|
StringBuffer b_s, *b = NULL;
|
|
|
|
// shortcut for single argument common case
|
|
if (argc == 1 && JS_VALUE_GET_TAG(argv[0]) == JS_TAG_INT) {
|
|
c = JS_VALUE_GET_INT(argv[0]);
|
|
if (c < 0 || c > 0x10ffff)
|
|
goto range_error;
|
|
if (c <= 0xffff) {
|
|
return js_new_string_char(ctx, c);
|
|
} else {
|
|
uint16_t c16[2];
|
|
c16[0] = get_hi_surrogate(c);
|
|
c16[1] = get_lo_surrogate(c);
|
|
return js_new_string16_len(ctx, c16, 2);
|
|
}
|
|
}
|
|
|
|
/* XXX: could pre-compute string length if all arguments are JS_TAG_INT */
|
|
b = &b_s;
|
|
if (string_buffer_init(ctx, b, argc))
|
|
goto fail;
|
|
for(i = 0; i < argc; i++) {
|
|
if (JS_VALUE_GET_TAG(argv[i]) == JS_TAG_INT) {
|
|
c = JS_VALUE_GET_INT(argv[i]);
|
|
if (c < 0 || c > 0x10ffff)
|
|
goto range_error;
|
|
} else {
|
|
if (JS_ToFloat64(ctx, &d, argv[i]))
|
|
goto fail;
|
|
if (!(d >= 0 && d <= 0x10ffff) || (c = (int)d) != d)
|
|
goto range_error;
|
|
}
|
|
if (string_buffer_putc(b, c))
|
|
goto fail;
|
|
}
|
|
return string_buffer_end(b);
|
|
|
|
range_error:
|
|
JS_ThrowRangeError(ctx, "invalid code point");
|
|
fail:
|
|
if (b) string_buffer_free(b);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue js_string_raw(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
// raw(temp,...a)
|
|
JSValue cooked, val, raw;
|
|
StringBuffer b_s, *b = &b_s;
|
|
int64_t i, n;
|
|
|
|
string_buffer_init(ctx, b, 0);
|
|
raw = JS_UNDEFINED;
|
|
cooked = JS_ToObject(ctx, argv[0]);
|
|
if (JS_IsException(cooked))
|
|
goto exception;
|
|
raw = JS_ToObjectFree(ctx, JS_GetProperty(ctx, cooked, JS_ATOM_raw));
|
|
if (JS_IsException(raw))
|
|
goto exception;
|
|
if (js_get_length64(ctx, &n, raw) < 0)
|
|
goto exception;
|
|
|
|
for (i = 0; i < n; i++) {
|
|
val = JS_ToStringFree(ctx, JS_GetPropertyInt64(ctx, raw, i));
|
|
if (JS_IsException(val))
|
|
goto exception;
|
|
string_buffer_concat_value_free(b, val);
|
|
if (i < n - 1 && i + 1 < argc) {
|
|
if (string_buffer_concat_value(b, argv[i + 1]))
|
|
goto exception;
|
|
}
|
|
}
|
|
JS_FreeValue(ctx, cooked);
|
|
JS_FreeValue(ctx, raw);
|
|
return string_buffer_end(b);
|
|
|
|
exception:
|
|
JS_FreeValue(ctx, cooked);
|
|
JS_FreeValue(ctx, raw);
|
|
string_buffer_free(b);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
/* only used in test262 */
|
|
JSValue js_string_codePointRange(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
uint32_t start, end, i, n;
|
|
StringBuffer b_s, *b = &b_s;
|
|
|
|
if (JS_ToUint32(ctx, &start, argv[0]) ||
|
|
JS_ToUint32(ctx, &end, argv[1]))
|
|
return JS_EXCEPTION;
|
|
end = min_uint32(end, 0x10ffff + 1);
|
|
|
|
if (start > end) {
|
|
start = end;
|
|
}
|
|
n = end - start;
|
|
if (end > 0x10000) {
|
|
n += end - max_uint32(start, 0x10000);
|
|
}
|
|
if (string_buffer_init2(ctx, b, n, end >= 0x100))
|
|
return JS_EXCEPTION;
|
|
for(i = start; i < end; i++) {
|
|
string_buffer_putc(b, i);
|
|
}
|
|
return string_buffer_end(b);
|
|
}
|
|
|
|
static JSValue js_string_at(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue val, ret;
|
|
JSString *p;
|
|
int idx, c;
|
|
|
|
val = JS_ToStringCheckObject(ctx, this_val);
|
|
if (JS_IsException(val))
|
|
return val;
|
|
p = JS_VALUE_GET_STRING(val);
|
|
if (JS_ToInt32Sat(ctx, &idx, argv[0])) {
|
|
JS_FreeValue(ctx, val);
|
|
return JS_EXCEPTION;
|
|
}
|
|
if (idx < 0)
|
|
idx = p->len + idx;
|
|
if (idx < 0 || idx >= p->len) {
|
|
ret = JS_UNDEFINED;
|
|
} else {
|
|
c = string_get(p, idx);
|
|
ret = js_new_string_char(ctx, c);
|
|
}
|
|
JS_FreeValue(ctx, val);
|
|
return ret;
|
|
}
|
|
|
|
static JSValue js_string_charCodeAt(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue val, ret;
|
|
JSString *p;
|
|
int idx, c;
|
|
|
|
val = JS_ToStringCheckObject(ctx, this_val);
|
|
if (JS_IsException(val))
|
|
return val;
|
|
p = JS_VALUE_GET_STRING(val);
|
|
if (JS_ToInt32Sat(ctx, &idx, argv[0])) {
|
|
JS_FreeValue(ctx, val);
|
|
return JS_EXCEPTION;
|
|
}
|
|
if (idx < 0 || idx >= p->len) {
|
|
ret = JS_NAN;
|
|
} else {
|
|
c = string_get(p, idx);
|
|
ret = js_int32(c);
|
|
}
|
|
JS_FreeValue(ctx, val);
|
|
return ret;
|
|
}
|
|
|
|
static JSValue js_string_charAt(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue val, ret;
|
|
JSString *p;
|
|
int idx, c;
|
|
|
|
val = JS_ToStringCheckObject(ctx, this_val);
|
|
if (JS_IsException(val))
|
|
return val;
|
|
p = JS_VALUE_GET_STRING(val);
|
|
if (JS_ToInt32Sat(ctx, &idx, argv[0])) {
|
|
JS_FreeValue(ctx, val);
|
|
return JS_EXCEPTION;
|
|
}
|
|
if (idx < 0 || idx >= p->len) {
|
|
ret = JS_AtomToString(ctx, JS_ATOM_empty_string);
|
|
} else {
|
|
c = string_get(p, idx);
|
|
ret = js_new_string_char(ctx, c);
|
|
}
|
|
JS_FreeValue(ctx, val);
|
|
return ret;
|
|
}
|
|
|
|
static JSValue js_string_codePointAt(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue val, ret;
|
|
JSString *p;
|
|
int idx, c;
|
|
|
|
val = JS_ToStringCheckObject(ctx, this_val);
|
|
if (JS_IsException(val))
|
|
return val;
|
|
p = JS_VALUE_GET_STRING(val);
|
|
if (JS_ToInt32Sat(ctx, &idx, argv[0])) {
|
|
JS_FreeValue(ctx, val);
|
|
return JS_EXCEPTION;
|
|
}
|
|
if (idx < 0 || idx >= p->len) {
|
|
ret = JS_UNDEFINED;
|
|
} else {
|
|
c = string_getc(p, &idx);
|
|
ret = js_int32(c);
|
|
}
|
|
JS_FreeValue(ctx, val);
|
|
return ret;
|
|
}
|
|
|
|
static JSValue js_string_concat(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue r;
|
|
int i;
|
|
|
|
/* XXX: Use more efficient method */
|
|
/* XXX: This method is OK if r has a single refcount */
|
|
/* XXX: should use string_buffer? */
|
|
r = JS_ToStringCheckObject(ctx, this_val);
|
|
for (i = 0; i < argc; i++) {
|
|
if (JS_IsException(r))
|
|
break;
|
|
r = JS_ConcatString(ctx, r, js_dup(argv[i]));
|
|
}
|
|
return r;
|
|
}
|
|
|
|
static int string_cmp(JSString *p1, JSString *p2, int x1, int x2, int len)
|
|
{
|
|
int i, c1, c2;
|
|
for (i = 0; i < len; i++) {
|
|
if ((c1 = string_get(p1, x1 + i)) != (c2 = string_get(p2, x2 + i)))
|
|
return c1 - c2;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int string_indexof_char(JSString *p, int c, int from)
|
|
{
|
|
/* assuming 0 <= from <= p->len */
|
|
int i, len = p->len;
|
|
if (p->is_wide_char) {
|
|
for (i = from; i < len; i++) {
|
|
if (p->u.str16[i] == c)
|
|
return i;
|
|
}
|
|
} else {
|
|
if ((c & ~0xff) == 0) {
|
|
for (i = from; i < len; i++) {
|
|
if (p->u.str8[i] == (uint8_t)c)
|
|
return i;
|
|
}
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static int string_indexof(JSString *p1, JSString *p2, int from)
|
|
{
|
|
/* assuming 0 <= from <= p1->len */
|
|
int c, i, j, len1 = p1->len, len2 = p2->len;
|
|
if (len2 == 0)
|
|
return from;
|
|
for (i = from, c = string_get(p2, 0); i + len2 <= len1; i = j + 1) {
|
|
j = string_indexof_char(p1, c, i);
|
|
if (j < 0 || j + len2 > len1)
|
|
break;
|
|
if (!string_cmp(p1, p2, j + 1, 1, len2 - 1))
|
|
return j;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static int64_t string_advance_index(JSString *p, int64_t index, BOOL unicode)
|
|
{
|
|
if (!unicode || index >= p->len || !p->is_wide_char) {
|
|
index++;
|
|
} else {
|
|
int index32 = (int)index;
|
|
string_getc(p, &index32);
|
|
index = index32;
|
|
}
|
|
return index;
|
|
}
|
|
|
|
static JSValue js_string_isWellFormed(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue str;
|
|
JSValue ret;
|
|
JSString *p;
|
|
uint32_t c, i, n;
|
|
|
|
ret = JS_TRUE;
|
|
str = JS_ToStringCheckObject(ctx, this_val);
|
|
if (JS_IsException(str))
|
|
return JS_EXCEPTION;
|
|
|
|
p = JS_VALUE_GET_STRING(str);
|
|
if (!p->is_wide_char || p->len == 0)
|
|
goto done; // by definition well-formed
|
|
|
|
for (i = 0, n = p->len; i < n; i++) {
|
|
c = p->u.str16[i];
|
|
if (!is_surrogate(c))
|
|
continue;
|
|
if (is_lo_surrogate(c) || i + 1 == n)
|
|
break;
|
|
c = p->u.str16[++i];
|
|
if (!is_lo_surrogate(c))
|
|
break;
|
|
}
|
|
|
|
if (i < n)
|
|
ret = JS_FALSE;
|
|
|
|
done:
|
|
JS_FreeValue(ctx, str);
|
|
return ret;
|
|
}
|
|
|
|
static JSValue js_string_toWellFormed(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue str;
|
|
JSValue ret;
|
|
JSString *p;
|
|
uint32_t c, i, n;
|
|
|
|
str = JS_ToStringCheckObject(ctx, this_val);
|
|
if (JS_IsException(str))
|
|
return JS_EXCEPTION;
|
|
|
|
p = JS_VALUE_GET_STRING(str);
|
|
if (!p->is_wide_char || p->len == 0)
|
|
return str; // by definition well-formed
|
|
|
|
// TODO(bnoordhuis) don't clone when input is well-formed
|
|
ret = js_new_string16_len(ctx, p->u.str16, p->len);
|
|
JS_FreeValue(ctx, str);
|
|
if (JS_IsException(ret))
|
|
return JS_EXCEPTION;
|
|
|
|
p = JS_VALUE_GET_STRING(ret);
|
|
for (i = 0, n = p->len; i < n; i++) {
|
|
c = p->u.str16[i];
|
|
if (!is_surrogate(c))
|
|
continue;
|
|
if (is_lo_surrogate(c) || i + 1 == n) {
|
|
p->u.str16[i] = 0xFFFD;
|
|
continue;
|
|
}
|
|
c = p->u.str16[++i];
|
|
if (!is_lo_surrogate(c))
|
|
p->u.str16[--i] = 0xFFFD;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static JSValue js_string_indexOf(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv, int lastIndexOf)
|
|
{
|
|
JSValue str, v;
|
|
int i, len, v_len, pos, start, stop, ret, inc;
|
|
JSString *p;
|
|
JSString *p1;
|
|
|
|
str = JS_ToStringCheckObject(ctx, this_val);
|
|
if (JS_IsException(str))
|
|
return str;
|
|
v = JS_ToString(ctx, argv[0]);
|
|
if (JS_IsException(v))
|
|
goto fail;
|
|
p = JS_VALUE_GET_STRING(str);
|
|
p1 = JS_VALUE_GET_STRING(v);
|
|
len = p->len;
|
|
v_len = p1->len;
|
|
if (lastIndexOf) {
|
|
pos = len - v_len;
|
|
if (argc > 1) {
|
|
double d;
|
|
if (JS_ToFloat64(ctx, &d, argv[1]))
|
|
goto fail;
|
|
if (!isnan(d)) {
|
|
if (d <= 0)
|
|
pos = 0;
|
|
else if (d < pos)
|
|
pos = d;
|
|
}
|
|
}
|
|
start = pos;
|
|
stop = 0;
|
|
inc = -1;
|
|
} else {
|
|
pos = 0;
|
|
if (argc > 1) {
|
|
if (JS_ToInt32Clamp(ctx, &pos, argv[1], 0, len, 0))
|
|
goto fail;
|
|
}
|
|
start = pos;
|
|
stop = len - v_len;
|
|
inc = 1;
|
|
}
|
|
ret = -1;
|
|
if (len >= v_len && inc * (stop - start) >= 0) {
|
|
for (i = start;; i += inc) {
|
|
if (!string_cmp(p, p1, i, 0, v_len)) {
|
|
ret = i;
|
|
break;
|
|
}
|
|
if (i == stop)
|
|
break;
|
|
}
|
|
}
|
|
JS_FreeValue(ctx, str);
|
|
JS_FreeValue(ctx, v);
|
|
return js_int32(ret);
|
|
|
|
fail:
|
|
JS_FreeValue(ctx, str);
|
|
JS_FreeValue(ctx, v);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
/* return < 0 if exception or TRUE/FALSE */
|
|
static int js_is_regexp(JSContext *ctx, JSValue obj);
|
|
|
|
static JSValue js_string_includes(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv, int magic)
|
|
{
|
|
JSValue str, v = JS_UNDEFINED;
|
|
int i, len, v_len, pos, start, stop, ret;
|
|
JSString *p;
|
|
JSString *p1;
|
|
|
|
str = JS_ToStringCheckObject(ctx, this_val);
|
|
if (JS_IsException(str))
|
|
return str;
|
|
ret = js_is_regexp(ctx, argv[0]);
|
|
if (ret) {
|
|
if (ret > 0)
|
|
JS_ThrowTypeError(ctx, "regexp not supported");
|
|
goto fail;
|
|
}
|
|
v = JS_ToString(ctx, argv[0]);
|
|
if (JS_IsException(v))
|
|
goto fail;
|
|
p = JS_VALUE_GET_STRING(str);
|
|
p1 = JS_VALUE_GET_STRING(v);
|
|
len = p->len;
|
|
v_len = p1->len;
|
|
pos = (magic == 2) ? len : 0;
|
|
if (argc > 1 && !JS_IsUndefined(argv[1])) {
|
|
if (JS_ToInt32Clamp(ctx, &pos, argv[1], 0, len, 0))
|
|
goto fail;
|
|
}
|
|
len -= v_len;
|
|
ret = 0;
|
|
if (magic == 0) {
|
|
start = pos;
|
|
stop = len;
|
|
} else {
|
|
if (magic == 1) {
|
|
if (pos > len)
|
|
goto done;
|
|
} else {
|
|
pos -= v_len;
|
|
}
|
|
start = stop = pos;
|
|
}
|
|
if (start >= 0 && start <= stop) {
|
|
for (i = start;; i++) {
|
|
if (!string_cmp(p, p1, i, 0, v_len)) {
|
|
ret = 1;
|
|
break;
|
|
}
|
|
if (i == stop)
|
|
break;
|
|
}
|
|
}
|
|
done:
|
|
JS_FreeValue(ctx, str);
|
|
JS_FreeValue(ctx, v);
|
|
return js_bool(ret);
|
|
|
|
fail:
|
|
JS_FreeValue(ctx, str);
|
|
JS_FreeValue(ctx, v);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static int check_regexp_g_flag(JSContext *ctx, JSValue regexp)
|
|
{
|
|
int ret;
|
|
JSValue flags;
|
|
|
|
ret = js_is_regexp(ctx, regexp);
|
|
if (ret < 0)
|
|
return -1;
|
|
if (ret) {
|
|
flags = JS_GetProperty(ctx, regexp, JS_ATOM_flags);
|
|
if (JS_IsException(flags))
|
|
return -1;
|
|
if (JS_IsUndefined(flags) || JS_IsNull(flags)) {
|
|
JS_ThrowTypeError(ctx, "cannot convert to object");
|
|
return -1;
|
|
}
|
|
flags = JS_ToStringFree(ctx, flags);
|
|
if (JS_IsException(flags))
|
|
return -1;
|
|
ret = string_indexof_char(JS_VALUE_GET_STRING(flags), 'g', 0);
|
|
JS_FreeValue(ctx, flags);
|
|
if (ret < 0) {
|
|
JS_ThrowTypeError(ctx, "regexp must have the 'g' flag");
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static JSValue js_string_match(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv, int atom)
|
|
{
|
|
// match(rx), search(rx), matchAll(rx)
|
|
// atom is JS_ATOM_Symbol_match, JS_ATOM_Symbol_search, or JS_ATOM_Symbol_matchAll
|
|
JSValue O = this_val, regexp = argv[0], args[2];
|
|
JSValue matcher, S, rx, result, str;
|
|
int args_len;
|
|
|
|
if (JS_IsUndefined(O) || JS_IsNull(O))
|
|
return JS_ThrowTypeError(ctx, "cannot convert to object");
|
|
|
|
if (!JS_IsUndefined(regexp) && !JS_IsNull(regexp)) {
|
|
matcher = JS_GetProperty(ctx, regexp, atom);
|
|
if (JS_IsException(matcher))
|
|
return JS_EXCEPTION;
|
|
if (atom == JS_ATOM_Symbol_matchAll) {
|
|
if (check_regexp_g_flag(ctx, regexp) < 0) {
|
|
JS_FreeValue(ctx, matcher);
|
|
return JS_EXCEPTION;
|
|
}
|
|
}
|
|
if (!JS_IsUndefined(matcher) && !JS_IsNull(matcher)) {
|
|
return JS_CallFree(ctx, matcher, regexp, 1, &O);
|
|
}
|
|
}
|
|
S = JS_ToString(ctx, O);
|
|
if (JS_IsException(S))
|
|
return JS_EXCEPTION;
|
|
args_len = 1;
|
|
args[0] = regexp;
|
|
str = JS_UNDEFINED;
|
|
if (atom == JS_ATOM_Symbol_matchAll) {
|
|
str = js_new_string8(ctx, "g");
|
|
if (JS_IsException(str))
|
|
goto fail;
|
|
args[args_len++] = str;
|
|
}
|
|
rx = JS_CallConstructor(ctx, ctx->regexp_ctor, args_len, args);
|
|
JS_FreeValue(ctx, str);
|
|
if (JS_IsException(rx)) {
|
|
fail:
|
|
JS_FreeValue(ctx, S);
|
|
return JS_EXCEPTION;
|
|
}
|
|
result = JS_InvokeFree(ctx, rx, atom, 1, &S);
|
|
JS_FreeValue(ctx, S);
|
|
return result;
|
|
}
|
|
|
|
static JSValue js_string___GetSubstitution(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
// GetSubstitution(matched, str, position, captures, namedCaptures, rep)
|
|
JSValue matched, str, captures, namedCaptures, rep;
|
|
JSValue capture, name, s;
|
|
uint32_t position, len, matched_len, captures_len;
|
|
int i, j, j0, k, k1;
|
|
int c, c1;
|
|
StringBuffer b_s, *b = &b_s;
|
|
JSString *sp, *rp;
|
|
|
|
matched = argv[0];
|
|
str = argv[1];
|
|
captures = argv[3];
|
|
namedCaptures = argv[4];
|
|
rep = argv[5];
|
|
|
|
if (!JS_IsString(rep) || !JS_IsString(str))
|
|
return JS_ThrowTypeError(ctx, "not a string");
|
|
|
|
sp = JS_VALUE_GET_STRING(str);
|
|
rp = JS_VALUE_GET_STRING(rep);
|
|
|
|
string_buffer_init(ctx, b, 0);
|
|
|
|
captures_len = 0;
|
|
if (!JS_IsUndefined(captures)) {
|
|
if (js_get_length32(ctx, &captures_len, captures))
|
|
goto exception;
|
|
}
|
|
if (js_get_length32(ctx, &matched_len, matched))
|
|
goto exception;
|
|
if (JS_ToUint32(ctx, &position, argv[2]) < 0)
|
|
goto exception;
|
|
|
|
len = rp->len;
|
|
i = 0;
|
|
for(;;) {
|
|
j = string_indexof_char(rp, '$', i);
|
|
if (j < 0 || j + 1 >= len)
|
|
break;
|
|
string_buffer_concat(b, rp, i, j);
|
|
j0 = j++;
|
|
c = string_get(rp, j++);
|
|
if (c == '$') {
|
|
string_buffer_putc8(b, '$');
|
|
} else if (c == '&') {
|
|
if (string_buffer_concat_value(b, matched))
|
|
goto exception;
|
|
} else if (c == '`') {
|
|
string_buffer_concat(b, sp, 0, position);
|
|
} else if (c == '\'') {
|
|
string_buffer_concat(b, sp, position + matched_len, sp->len);
|
|
} else if (c >= '0' && c <= '9') {
|
|
k = c - '0';
|
|
if (j < len) {
|
|
c1 = string_get(rp, j);
|
|
if (c1 >= '0' && c1 <= '9') {
|
|
/* This behavior is specified in ES6 and refined in ECMA 2019 */
|
|
/* ECMA 2019 does not have the extra test, but
|
|
Test262 S15.5.4.11_A3_T1..3 require this behavior */
|
|
k1 = k * 10 + c1 - '0';
|
|
if (k1 >= 1 && k1 < captures_len) {
|
|
k = k1;
|
|
j++;
|
|
}
|
|
}
|
|
}
|
|
if (k >= 1 && k < captures_len) {
|
|
s = JS_GetPropertyInt64(ctx, captures, k);
|
|
if (JS_IsException(s))
|
|
goto exception;
|
|
if (!JS_IsUndefined(s)) {
|
|
if (string_buffer_concat_value_free(b, s))
|
|
goto exception;
|
|
}
|
|
} else {
|
|
goto norep;
|
|
}
|
|
} else if (c == '<' && !JS_IsUndefined(namedCaptures)) {
|
|
k = string_indexof_char(rp, '>', j);
|
|
if (k < 0)
|
|
goto norep;
|
|
name = js_sub_string(ctx, rp, j, k);
|
|
if (JS_IsException(name))
|
|
goto exception;
|
|
capture = JS_GetPropertyValue(ctx, namedCaptures, name);
|
|
if (JS_IsException(capture))
|
|
goto exception;
|
|
if (!JS_IsUndefined(capture)) {
|
|
if (string_buffer_concat_value_free(b, capture))
|
|
goto exception;
|
|
}
|
|
j = k + 1;
|
|
} else {
|
|
norep:
|
|
string_buffer_concat(b, rp, j0, j);
|
|
}
|
|
i = j;
|
|
}
|
|
string_buffer_concat(b, rp, i, rp->len);
|
|
return string_buffer_end(b);
|
|
exception:
|
|
string_buffer_free(b);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue js_string_replace(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv,
|
|
int is_replaceAll)
|
|
{
|
|
// replace(rx, rep)
|
|
JSValue O = this_val, searchValue = argv[0], replaceValue = argv[1];
|
|
JSValue args[6];
|
|
JSValue str, search_str, replaceValue_str, repl_str;
|
|
JSString *sp, *searchp;
|
|
StringBuffer b_s, *b = &b_s;
|
|
int pos, functionalReplace, endOfLastMatch;
|
|
BOOL is_first;
|
|
|
|
if (JS_IsUndefined(O) || JS_IsNull(O))
|
|
return JS_ThrowTypeError(ctx, "cannot convert to object");
|
|
|
|
search_str = JS_UNDEFINED;
|
|
replaceValue_str = JS_UNDEFINED;
|
|
repl_str = JS_UNDEFINED;
|
|
|
|
if (!JS_IsUndefined(searchValue) && !JS_IsNull(searchValue)) {
|
|
JSValue replacer;
|
|
if (is_replaceAll) {
|
|
if (check_regexp_g_flag(ctx, searchValue) < 0)
|
|
return JS_EXCEPTION;
|
|
}
|
|
replacer = JS_GetProperty(ctx, searchValue, JS_ATOM_Symbol_replace);
|
|
if (JS_IsException(replacer))
|
|
return JS_EXCEPTION;
|
|
if (!JS_IsUndefined(replacer) && !JS_IsNull(replacer)) {
|
|
args[0] = O;
|
|
args[1] = replaceValue;
|
|
return JS_CallFree(ctx, replacer, searchValue, 2, args);
|
|
}
|
|
}
|
|
string_buffer_init(ctx, b, 0);
|
|
|
|
str = JS_ToString(ctx, O);
|
|
if (JS_IsException(str))
|
|
goto exception;
|
|
search_str = JS_ToString(ctx, searchValue);
|
|
if (JS_IsException(search_str))
|
|
goto exception;
|
|
functionalReplace = JS_IsFunction(ctx, replaceValue);
|
|
if (!functionalReplace) {
|
|
replaceValue_str = JS_ToString(ctx, replaceValue);
|
|
if (JS_IsException(replaceValue_str))
|
|
goto exception;
|
|
}
|
|
|
|
sp = JS_VALUE_GET_STRING(str);
|
|
searchp = JS_VALUE_GET_STRING(search_str);
|
|
endOfLastMatch = 0;
|
|
is_first = TRUE;
|
|
for(;;) {
|
|
if (unlikely(searchp->len == 0)) {
|
|
if (is_first)
|
|
pos = 0;
|
|
else if (endOfLastMatch >= sp->len)
|
|
pos = -1;
|
|
else
|
|
pos = endOfLastMatch + 1;
|
|
} else {
|
|
pos = string_indexof(sp, searchp, endOfLastMatch);
|
|
}
|
|
if (pos < 0) {
|
|
if (is_first) {
|
|
string_buffer_free(b);
|
|
JS_FreeValue(ctx, search_str);
|
|
JS_FreeValue(ctx, replaceValue_str);
|
|
return str;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
if (functionalReplace) {
|
|
args[0] = search_str;
|
|
args[1] = js_int32(pos);
|
|
args[2] = str;
|
|
repl_str = JS_ToStringFree(ctx, JS_Call(ctx, replaceValue, JS_UNDEFINED, 3, args));
|
|
} else {
|
|
args[0] = search_str;
|
|
args[1] = str;
|
|
args[2] = js_int32(pos);
|
|
args[3] = JS_UNDEFINED;
|
|
args[4] = JS_UNDEFINED;
|
|
args[5] = replaceValue_str;
|
|
repl_str = js_string___GetSubstitution(ctx, JS_UNDEFINED, 6, args);
|
|
}
|
|
if (JS_IsException(repl_str))
|
|
goto exception;
|
|
|
|
string_buffer_concat(b, sp, endOfLastMatch, pos);
|
|
string_buffer_concat_value_free(b, repl_str);
|
|
endOfLastMatch = pos + searchp->len;
|
|
is_first = FALSE;
|
|
if (!is_replaceAll)
|
|
break;
|
|
}
|
|
string_buffer_concat(b, sp, endOfLastMatch, sp->len);
|
|
JS_FreeValue(ctx, search_str);
|
|
JS_FreeValue(ctx, replaceValue_str);
|
|
JS_FreeValue(ctx, str);
|
|
return string_buffer_end(b);
|
|
|
|
exception:
|
|
string_buffer_free(b);
|
|
JS_FreeValue(ctx, search_str);
|
|
JS_FreeValue(ctx, replaceValue_str);
|
|
JS_FreeValue(ctx, str);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue js_string_split(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
// split(sep, limit)
|
|
JSValue O = this_val, separator = argv[0], limit = argv[1];
|
|
JSValue args[2];
|
|
JSValue S, A, R, T;
|
|
uint32_t lim, lengthA;
|
|
int64_t p, q, s, r, e;
|
|
JSString *sp, *rp;
|
|
|
|
if (JS_IsUndefined(O) || JS_IsNull(O))
|
|
return JS_ThrowTypeError(ctx, "cannot convert to object");
|
|
|
|
S = JS_UNDEFINED;
|
|
A = JS_UNDEFINED;
|
|
R = JS_UNDEFINED;
|
|
|
|
if (!JS_IsUndefined(separator) && !JS_IsNull(separator)) {
|
|
JSValue splitter;
|
|
splitter = JS_GetProperty(ctx, separator, JS_ATOM_Symbol_split);
|
|
if (JS_IsException(splitter))
|
|
return JS_EXCEPTION;
|
|
if (!JS_IsUndefined(splitter) && !JS_IsNull(splitter)) {
|
|
args[0] = O;
|
|
args[1] = limit;
|
|
return JS_CallFree(ctx, splitter, separator, 2, args);
|
|
}
|
|
}
|
|
S = JS_ToString(ctx, O);
|
|
if (JS_IsException(S))
|
|
goto exception;
|
|
A = JS_NewArray(ctx);
|
|
if (JS_IsException(A))
|
|
goto exception;
|
|
lengthA = 0;
|
|
if (JS_IsUndefined(limit)) {
|
|
lim = 0xffffffff;
|
|
} else {
|
|
if (JS_ToUint32(ctx, &lim, limit) < 0)
|
|
goto exception;
|
|
}
|
|
sp = JS_VALUE_GET_STRING(S);
|
|
s = sp->len;
|
|
R = JS_ToString(ctx, separator);
|
|
if (JS_IsException(R))
|
|
goto exception;
|
|
rp = JS_VALUE_GET_STRING(R);
|
|
r = rp->len;
|
|
p = 0;
|
|
if (lim == 0)
|
|
goto done;
|
|
if (JS_IsUndefined(separator))
|
|
goto add_tail;
|
|
if (s == 0) {
|
|
if (r != 0)
|
|
goto add_tail;
|
|
goto done;
|
|
}
|
|
q = p;
|
|
for (q = p; (q += !r) <= s - r - !r; q = p = e + r) {
|
|
e = string_indexof(sp, rp, q);
|
|
if (e < 0)
|
|
break;
|
|
T = js_sub_string(ctx, sp, p, e);
|
|
if (JS_IsException(T))
|
|
goto exception;
|
|
if (JS_CreateDataPropertyUint32(ctx, A, lengthA++, T, 0) < 0)
|
|
goto exception;
|
|
if (lengthA == lim)
|
|
goto done;
|
|
}
|
|
add_tail:
|
|
T = js_sub_string(ctx, sp, p, s);
|
|
if (JS_IsException(T))
|
|
goto exception;
|
|
if (JS_CreateDataPropertyUint32(ctx, A, lengthA++, T,0 ) < 0)
|
|
goto exception;
|
|
done:
|
|
JS_FreeValue(ctx, S);
|
|
JS_FreeValue(ctx, R);
|
|
return A;
|
|
|
|
exception:
|
|
JS_FreeValue(ctx, A);
|
|
JS_FreeValue(ctx, S);
|
|
JS_FreeValue(ctx, R);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue js_string_substring(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue str, ret;
|
|
int a, b, start, end;
|
|
JSString *p;
|
|
|
|
str = JS_ToStringCheckObject(ctx, this_val);
|
|
if (JS_IsException(str))
|
|
return str;
|
|
p = JS_VALUE_GET_STRING(str);
|
|
if (JS_ToInt32Clamp(ctx, &a, argv[0], 0, p->len, 0)) {
|
|
JS_FreeValue(ctx, str);
|
|
return JS_EXCEPTION;
|
|
}
|
|
b = p->len;
|
|
if (!JS_IsUndefined(argv[1])) {
|
|
if (JS_ToInt32Clamp(ctx, &b, argv[1], 0, p->len, 0)) {
|
|
JS_FreeValue(ctx, str);
|
|
return JS_EXCEPTION;
|
|
}
|
|
}
|
|
if (a < b) {
|
|
start = a;
|
|
end = b;
|
|
} else {
|
|
start = b;
|
|
end = a;
|
|
}
|
|
ret = js_sub_string(ctx, p, start, end);
|
|
JS_FreeValue(ctx, str);
|
|
return ret;
|
|
}
|
|
|
|
static JSValue js_string_substr(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue str, ret;
|
|
int a, len, n;
|
|
JSString *p;
|
|
|
|
str = JS_ToStringCheckObject(ctx, this_val);
|
|
if (JS_IsException(str))
|
|
return str;
|
|
p = JS_VALUE_GET_STRING(str);
|
|
len = p->len;
|
|
if (JS_ToInt32Clamp(ctx, &a, argv[0], 0, len, len)) {
|
|
JS_FreeValue(ctx, str);
|
|
return JS_EXCEPTION;
|
|
}
|
|
n = len - a;
|
|
if (!JS_IsUndefined(argv[1])) {
|
|
if (JS_ToInt32Clamp(ctx, &n, argv[1], 0, len - a, 0)) {
|
|
JS_FreeValue(ctx, str);
|
|
return JS_EXCEPTION;
|
|
}
|
|
}
|
|
ret = js_sub_string(ctx, p, a, a + n);
|
|
JS_FreeValue(ctx, str);
|
|
return ret;
|
|
}
|
|
|
|
static JSValue js_string_slice(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue str, ret;
|
|
int len, start, end;
|
|
JSString *p;
|
|
|
|
str = JS_ToStringCheckObject(ctx, this_val);
|
|
if (JS_IsException(str))
|
|
return str;
|
|
p = JS_VALUE_GET_STRING(str);
|
|
len = p->len;
|
|
if (JS_ToInt32Clamp(ctx, &start, argv[0], 0, len, len)) {
|
|
JS_FreeValue(ctx, str);
|
|
return JS_EXCEPTION;
|
|
}
|
|
end = len;
|
|
if (!JS_IsUndefined(argv[1])) {
|
|
if (JS_ToInt32Clamp(ctx, &end, argv[1], 0, len, len)) {
|
|
JS_FreeValue(ctx, str);
|
|
return JS_EXCEPTION;
|
|
}
|
|
}
|
|
ret = js_sub_string(ctx, p, start, max_int(end, start));
|
|
JS_FreeValue(ctx, str);
|
|
return ret;
|
|
}
|
|
|
|
static JSValue js_string_pad(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv, int padEnd)
|
|
{
|
|
JSValue str, v = JS_UNDEFINED;
|
|
StringBuffer b_s, *b = &b_s;
|
|
JSString *p, *p1 = NULL;
|
|
int n, len, c = ' ';
|
|
|
|
str = JS_ToStringCheckObject(ctx, this_val);
|
|
if (JS_IsException(str))
|
|
goto fail1;
|
|
if (JS_ToInt32Sat(ctx, &n, argv[0]))
|
|
goto fail2;
|
|
p = JS_VALUE_GET_STRING(str);
|
|
len = p->len;
|
|
if (len >= n)
|
|
return str;
|
|
if (argc > 1 && !JS_IsUndefined(argv[1])) {
|
|
v = JS_ToString(ctx, argv[1]);
|
|
if (JS_IsException(v))
|
|
goto fail2;
|
|
p1 = JS_VALUE_GET_STRING(v);
|
|
if (p1->len == 0) {
|
|
JS_FreeValue(ctx, v);
|
|
return str;
|
|
}
|
|
if (p1->len == 1) {
|
|
c = string_get(p1, 0);
|
|
p1 = NULL;
|
|
}
|
|
}
|
|
if (n > JS_STRING_LEN_MAX) {
|
|
JS_ThrowRangeError(ctx, "invalid string length");
|
|
goto fail2;
|
|
}
|
|
if (string_buffer_init(ctx, b, n))
|
|
goto fail3;
|
|
n -= len;
|
|
if (padEnd) {
|
|
if (string_buffer_concat(b, p, 0, len))
|
|
goto fail;
|
|
}
|
|
if (p1) {
|
|
while (n > 0) {
|
|
int chunk = min_int(n, p1->len);
|
|
if (string_buffer_concat(b, p1, 0, chunk))
|
|
goto fail;
|
|
n -= chunk;
|
|
}
|
|
} else {
|
|
if (string_buffer_fill(b, c, n))
|
|
goto fail;
|
|
}
|
|
if (!padEnd) {
|
|
if (string_buffer_concat(b, p, 0, len))
|
|
goto fail;
|
|
}
|
|
JS_FreeValue(ctx, v);
|
|
JS_FreeValue(ctx, str);
|
|
return string_buffer_end(b);
|
|
|
|
fail:
|
|
string_buffer_free(b);
|
|
fail3:
|
|
JS_FreeValue(ctx, v);
|
|
fail2:
|
|
JS_FreeValue(ctx, str);
|
|
fail1:
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue js_string_repeat(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue str;
|
|
StringBuffer b_s, *b = &b_s;
|
|
JSString *p;
|
|
int64_t val;
|
|
int n, len;
|
|
|
|
str = JS_ToStringCheckObject(ctx, this_val);
|
|
if (JS_IsException(str))
|
|
goto fail;
|
|
if (JS_ToInt64Sat(ctx, &val, argv[0]))
|
|
goto fail;
|
|
if (val < 0 || val > 2147483647) {
|
|
JS_ThrowRangeError(ctx, "invalid repeat count");
|
|
goto fail;
|
|
}
|
|
n = val;
|
|
p = JS_VALUE_GET_STRING(str);
|
|
len = p->len;
|
|
if (len == 0 || n == 1)
|
|
return str;
|
|
if (val * len > JS_STRING_LEN_MAX) {
|
|
JS_ThrowRangeError(ctx, "invalid string length");
|
|
goto fail;
|
|
}
|
|
if (string_buffer_init2(ctx, b, n * len, p->is_wide_char))
|
|
goto fail;
|
|
if (len == 1) {
|
|
string_buffer_fill(b, string_get(p, 0), n);
|
|
} else {
|
|
while (n-- > 0) {
|
|
string_buffer_concat(b, p, 0, len);
|
|
}
|
|
}
|
|
JS_FreeValue(ctx, str);
|
|
return string_buffer_end(b);
|
|
|
|
fail:
|
|
JS_FreeValue(ctx, str);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue js_string_trim(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv, int magic)
|
|
{
|
|
JSValue str, ret;
|
|
int a, b, len;
|
|
JSString *p;
|
|
|
|
str = JS_ToStringCheckObject(ctx, this_val);
|
|
if (JS_IsException(str))
|
|
return str;
|
|
p = JS_VALUE_GET_STRING(str);
|
|
a = 0;
|
|
b = len = p->len;
|
|
if (magic & 1) {
|
|
while (a < len && lre_is_space(string_get(p, a)))
|
|
a++;
|
|
}
|
|
if (magic & 2) {
|
|
while (b > a && lre_is_space(string_get(p, b - 1)))
|
|
b--;
|
|
}
|
|
ret = js_sub_string(ctx, p, a, b);
|
|
JS_FreeValue(ctx, str);
|
|
return ret;
|
|
}
|
|
|
|
/* return 0 if before the first char */
|
|
static int string_prevc(JSString *p, int *pidx)
|
|
{
|
|
int idx, c, c1;
|
|
|
|
idx = *pidx;
|
|
if (idx <= 0)
|
|
return 0;
|
|
idx--;
|
|
if (p->is_wide_char) {
|
|
c = p->u.str16[idx];
|
|
if (is_lo_surrogate(c) && idx > 0) {
|
|
c1 = p->u.str16[idx - 1];
|
|
if (is_hi_surrogate(c1)) {
|
|
c = from_surrogate(c1, c);
|
|
idx--;
|
|
}
|
|
}
|
|
} else {
|
|
c = p->u.str8[idx];
|
|
}
|
|
*pidx = idx;
|
|
return c;
|
|
}
|
|
|
|
static BOOL test_final_sigma(JSString *p, int sigma_pos)
|
|
{
|
|
int k, c1;
|
|
|
|
/* before C: skip case ignorable chars and check there is
|
|
a cased letter */
|
|
k = sigma_pos;
|
|
for(;;) {
|
|
c1 = string_prevc(p, &k);
|
|
if (!lre_is_case_ignorable(c1))
|
|
break;
|
|
}
|
|
if (!lre_is_cased(c1))
|
|
return FALSE;
|
|
|
|
/* after C: skip case ignorable chars and check there is
|
|
no cased letter */
|
|
k = sigma_pos + 1;
|
|
for(;;) {
|
|
if (k >= p->len)
|
|
return TRUE;
|
|
c1 = string_getc(p, &k);
|
|
if (!lre_is_case_ignorable(c1))
|
|
break;
|
|
}
|
|
return !lre_is_cased(c1);
|
|
}
|
|
|
|
static int to_utf32_buf(JSContext *ctx, JSString *p, uint32_t **pbuf)
|
|
{
|
|
uint32_t *b;
|
|
int i, j, n;
|
|
|
|
j = -1;
|
|
n = p->len;
|
|
b = js_malloc(ctx, max_int(1, n) * sizeof(*b));
|
|
if (b)
|
|
for (i = j = 0; i < n;)
|
|
b[j++] = string_getc(p, &i);
|
|
*pbuf = b;
|
|
return j;
|
|
}
|
|
|
|
static JSValue js_string_localeCompare(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
int i, n, an, bn, cmp;
|
|
uint32_t *as, *bs, *ts;
|
|
JSValue a, b, ret;
|
|
|
|
ret = JS_EXCEPTION;
|
|
as = NULL;
|
|
bs = NULL;
|
|
|
|
a = JS_ToStringCheckObject(ctx, this_val);
|
|
if (JS_IsException(a))
|
|
return JS_EXCEPTION;
|
|
|
|
b = JS_ToString(ctx, argv[0]);
|
|
if (JS_IsException(b))
|
|
goto exception;
|
|
|
|
an = to_utf32_buf(ctx, JS_VALUE_GET_STRING(a), &as);
|
|
if (an == -1)
|
|
goto exception;
|
|
|
|
bn = to_utf32_buf(ctx, JS_VALUE_GET_STRING(b), &bs);
|
|
if (bn == -1)
|
|
goto exception;
|
|
|
|
// TODO(bnoordhuis) skip normalization when input is latin1
|
|
an = unicode_normalize(&ts, as, an, UNICODE_NFC, ctx,
|
|
(DynBufReallocFunc *)js_realloc);
|
|
if (an == -1)
|
|
goto exception;
|
|
js_free(ctx, as);
|
|
as = ts;
|
|
|
|
// TODO(bnoordhuis) skip normalization when input is latin1
|
|
bn = unicode_normalize(&ts, bs, bn, UNICODE_NFC, ctx,
|
|
(DynBufReallocFunc *)js_realloc);
|
|
if (bn == -1)
|
|
goto exception;
|
|
js_free(ctx, bs);
|
|
bs = ts;
|
|
|
|
n = min_int(an, bn);
|
|
for (i = 0; i < n; i++)
|
|
if (as[i] != bs[i])
|
|
break;
|
|
if (i < n)
|
|
cmp = compare_u32(as[i], bs[i]);
|
|
else
|
|
cmp = compare_u32(an, bn);
|
|
ret = js_int32(cmp);
|
|
|
|
exception:
|
|
JS_FreeValue(ctx, a);
|
|
JS_FreeValue(ctx, b);
|
|
js_free(ctx, as);
|
|
js_free(ctx, bs);
|
|
return ret;
|
|
}
|
|
|
|
static JSValue js_string_toLowerCase(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv, int to_lower)
|
|
{
|
|
JSValue val;
|
|
StringBuffer b_s, *b = &b_s;
|
|
JSString *p;
|
|
int i, c, j, l;
|
|
uint32_t res[LRE_CC_RES_LEN_MAX];
|
|
|
|
val = JS_ToStringCheckObject(ctx, this_val);
|
|
if (JS_IsException(val))
|
|
return val;
|
|
p = JS_VALUE_GET_STRING(val);
|
|
if (p->len == 0)
|
|
return val;
|
|
if (string_buffer_init(ctx, b, p->len))
|
|
goto fail;
|
|
for(i = 0; i < p->len;) {
|
|
c = string_getc(p, &i);
|
|
if (c == 0x3a3 && to_lower && test_final_sigma(p, i - 1)) {
|
|
res[0] = 0x3c2; /* final sigma */
|
|
l = 1;
|
|
} else {
|
|
l = lre_case_conv(res, c, to_lower);
|
|
}
|
|
for(j = 0; j < l; j++) {
|
|
if (string_buffer_putc(b, res[j]))
|
|
goto fail;
|
|
}
|
|
}
|
|
JS_FreeValue(ctx, val);
|
|
return string_buffer_end(b);
|
|
fail:
|
|
JS_FreeValue(ctx, val);
|
|
string_buffer_free(b);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
/* return (-1, NULL) if exception, otherwise (len, buf) */
|
|
static int JS_ToUTF32String(JSContext *ctx, uint32_t **pbuf, JSValue val1)
|
|
{
|
|
JSValue val;
|
|
int len;
|
|
|
|
val = JS_ToString(ctx, val1);
|
|
if (JS_IsException(val))
|
|
return -1;
|
|
len = to_utf32_buf(ctx, JS_VALUE_GET_STRING(val), pbuf);
|
|
JS_FreeValue(ctx, val);
|
|
return len;
|
|
}
|
|
|
|
static JSValue JS_NewUTF32String(JSContext *ctx, const uint32_t *buf, int len)
|
|
{
|
|
int i;
|
|
StringBuffer b_s, *b = &b_s;
|
|
if (string_buffer_init(ctx, b, len))
|
|
return JS_EXCEPTION;
|
|
for(i = 0; i < len; i++) {
|
|
if (string_buffer_putc(b, buf[i]))
|
|
goto fail;
|
|
}
|
|
return string_buffer_end(b);
|
|
fail:
|
|
string_buffer_free(b);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue js_string_normalize(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
const char *form, *p;
|
|
size_t form_len;
|
|
int is_compat, buf_len, out_len;
|
|
UnicodeNormalizationEnum n_type;
|
|
JSValue val;
|
|
uint32_t *buf, *out_buf;
|
|
|
|
val = JS_ToStringCheckObject(ctx, this_val);
|
|
if (JS_IsException(val))
|
|
return val;
|
|
buf_len = JS_ToUTF32String(ctx, &buf, val);
|
|
JS_FreeValue(ctx, val);
|
|
if (buf_len < 0)
|
|
return JS_EXCEPTION;
|
|
|
|
if (argc == 0 || JS_IsUndefined(argv[0])) {
|
|
n_type = UNICODE_NFC;
|
|
} else {
|
|
form = JS_ToCStringLen(ctx, &form_len, argv[0]);
|
|
if (!form)
|
|
goto fail1;
|
|
p = form;
|
|
if (p[0] != 'N' || p[1] != 'F')
|
|
goto bad_form;
|
|
p += 2;
|
|
is_compat = FALSE;
|
|
if (*p == 'K') {
|
|
is_compat = TRUE;
|
|
p++;
|
|
}
|
|
if (*p == 'C' || *p == 'D') {
|
|
n_type = UNICODE_NFC + is_compat * 2 + (*p - 'C');
|
|
if ((p + 1 - form) != form_len)
|
|
goto bad_form;
|
|
} else {
|
|
bad_form:
|
|
JS_FreeCString(ctx, form);
|
|
JS_ThrowRangeError(ctx, "bad normalization form");
|
|
fail1:
|
|
js_free(ctx, buf);
|
|
return JS_EXCEPTION;
|
|
}
|
|
JS_FreeCString(ctx, form);
|
|
}
|
|
|
|
out_len = unicode_normalize(&out_buf, buf, buf_len, n_type,
|
|
ctx->rt, (DynBufReallocFunc *)js_realloc_rt);
|
|
js_free(ctx, buf);
|
|
if (out_len < 0)
|
|
return JS_EXCEPTION;
|
|
val = JS_NewUTF32String(ctx, out_buf, out_len);
|
|
js_free(ctx, out_buf);
|
|
return val;
|
|
}
|
|
|
|
/* also used for String.prototype.valueOf */
|
|
static JSValue js_string_toString(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
return js_thisStringValue(ctx, this_val);
|
|
}
|
|
|
|
/* String Iterator */
|
|
|
|
static JSValue js_string_iterator_next(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv,
|
|
BOOL *pdone, int magic)
|
|
{
|
|
JSArrayIteratorData *it;
|
|
uint32_t idx, c, start;
|
|
JSString *p;
|
|
|
|
it = JS_GetOpaque2(ctx, this_val, JS_CLASS_STRING_ITERATOR);
|
|
if (!it) {
|
|
*pdone = FALSE;
|
|
return JS_EXCEPTION;
|
|
}
|
|
if (JS_IsUndefined(it->obj))
|
|
goto done;
|
|
p = JS_VALUE_GET_STRING(it->obj);
|
|
idx = it->idx;
|
|
if (idx >= p->len) {
|
|
JS_FreeValue(ctx, it->obj);
|
|
it->obj = JS_UNDEFINED;
|
|
done:
|
|
*pdone = TRUE;
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
start = idx;
|
|
c = string_getc(p, (int *)&idx);
|
|
it->idx = idx;
|
|
*pdone = FALSE;
|
|
if (c <= 0xffff) {
|
|
return js_new_string_char(ctx, c);
|
|
} else {
|
|
return js_new_string16_len(ctx, p->u.str16 + start, 2);
|
|
}
|
|
}
|
|
|
|
/* ES6 Annex B 2.3.2 etc. */
|
|
enum {
|
|
magic_string_anchor,
|
|
magic_string_big,
|
|
magic_string_blink,
|
|
magic_string_bold,
|
|
magic_string_fixed,
|
|
magic_string_fontcolor,
|
|
magic_string_fontsize,
|
|
magic_string_italics,
|
|
magic_string_link,
|
|
magic_string_small,
|
|
magic_string_strike,
|
|
magic_string_sub,
|
|
magic_string_sup,
|
|
};
|
|
|
|
static JSValue js_string_CreateHTML(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv, int magic)
|
|
{
|
|
JSValue str;
|
|
const JSString *p;
|
|
StringBuffer b_s, *b = &b_s;
|
|
static struct { const char *tag, *attr; } const defs[] = {
|
|
{ "a", "name" }, { "big", NULL }, { "blink", NULL }, { "b", NULL },
|
|
{ "tt", NULL }, { "font", "color" }, { "font", "size" }, { "i", NULL },
|
|
{ "a", "href" }, { "small", NULL }, { "strike", NULL },
|
|
{ "sub", NULL }, { "sup", NULL },
|
|
};
|
|
|
|
str = JS_ToStringCheckObject(ctx, this_val);
|
|
if (JS_IsException(str))
|
|
return JS_EXCEPTION;
|
|
string_buffer_init(ctx, b, 7);
|
|
string_buffer_putc8(b, '<');
|
|
string_buffer_puts8(b, defs[magic].tag);
|
|
if (defs[magic].attr) {
|
|
// r += " " + attr + "=\"" + value + "\"";
|
|
JSValue value;
|
|
int i;
|
|
|
|
string_buffer_putc8(b, ' ');
|
|
string_buffer_puts8(b, defs[magic].attr);
|
|
string_buffer_puts8(b, "=\"");
|
|
value = JS_ToStringCheckObject(ctx, argv[0]);
|
|
if (JS_IsException(value)) {
|
|
JS_FreeValue(ctx, str);
|
|
string_buffer_free(b);
|
|
return JS_EXCEPTION;
|
|
}
|
|
p = JS_VALUE_GET_STRING(value);
|
|
for (i = 0; i < p->len; i++) {
|
|
int c = string_get(p, i);
|
|
if (c == '"') {
|
|
string_buffer_puts8(b, """);
|
|
} else {
|
|
string_buffer_putc16(b, c);
|
|
}
|
|
}
|
|
JS_FreeValue(ctx, value);
|
|
string_buffer_putc8(b, '\"');
|
|
}
|
|
// return r + ">" + str + "</" + tag + ">";
|
|
string_buffer_putc8(b, '>');
|
|
string_buffer_concat_value_free(b, str);
|
|
string_buffer_puts8(b, "</");
|
|
string_buffer_puts8(b, defs[magic].tag);
|
|
string_buffer_putc8(b, '>');
|
|
return string_buffer_end(b);
|
|
}
|
|
|
|
static const JSCFunctionListEntry js_string_funcs[] = {
|
|
JS_CFUNC_DEF("fromCharCode", 1, js_string_fromCharCode ),
|
|
JS_CFUNC_DEF("fromCodePoint", 1, js_string_fromCodePoint ),
|
|
JS_CFUNC_DEF("raw", 1, js_string_raw ),
|
|
};
|
|
|
|
static const JSCFunctionListEntry js_string_proto_funcs[] = {
|
|
JS_PROP_INT32_DEF("length", 0, JS_PROP_CONFIGURABLE ),
|
|
JS_CFUNC_DEF("at", 1, js_string_at ),
|
|
JS_CFUNC_DEF("charCodeAt", 1, js_string_charCodeAt ),
|
|
JS_CFUNC_DEF("charAt", 1, js_string_charAt ),
|
|
JS_CFUNC_DEF("concat", 1, js_string_concat ),
|
|
JS_CFUNC_DEF("codePointAt", 1, js_string_codePointAt ),
|
|
JS_CFUNC_DEF("isWellFormed", 0, js_string_isWellFormed ),
|
|
JS_CFUNC_DEF("toWellFormed", 0, js_string_toWellFormed ),
|
|
JS_CFUNC_MAGIC_DEF("indexOf", 1, js_string_indexOf, 0 ),
|
|
JS_CFUNC_MAGIC_DEF("lastIndexOf", 1, js_string_indexOf, 1 ),
|
|
JS_CFUNC_MAGIC_DEF("includes", 1, js_string_includes, 0 ),
|
|
JS_CFUNC_MAGIC_DEF("endsWith", 1, js_string_includes, 2 ),
|
|
JS_CFUNC_MAGIC_DEF("startsWith", 1, js_string_includes, 1 ),
|
|
JS_CFUNC_MAGIC_DEF("match", 1, js_string_match, JS_ATOM_Symbol_match ),
|
|
JS_CFUNC_MAGIC_DEF("matchAll", 1, js_string_match, JS_ATOM_Symbol_matchAll ),
|
|
JS_CFUNC_MAGIC_DEF("search", 1, js_string_match, JS_ATOM_Symbol_search ),
|
|
JS_CFUNC_DEF("split", 2, js_string_split ),
|
|
JS_CFUNC_DEF("substring", 2, js_string_substring ),
|
|
JS_CFUNC_DEF("substr", 2, js_string_substr ),
|
|
JS_CFUNC_DEF("slice", 2, js_string_slice ),
|
|
JS_CFUNC_DEF("repeat", 1, js_string_repeat ),
|
|
JS_CFUNC_MAGIC_DEF("replace", 2, js_string_replace, 0 ),
|
|
JS_CFUNC_MAGIC_DEF("replaceAll", 2, js_string_replace, 1 ),
|
|
JS_CFUNC_MAGIC_DEF("padEnd", 1, js_string_pad, 1 ),
|
|
JS_CFUNC_MAGIC_DEF("padStart", 1, js_string_pad, 0 ),
|
|
JS_CFUNC_MAGIC_DEF("trim", 0, js_string_trim, 3 ),
|
|
JS_CFUNC_MAGIC_DEF("trimEnd", 0, js_string_trim, 2 ),
|
|
JS_ALIAS_DEF("trimRight", "trimEnd" ),
|
|
JS_CFUNC_MAGIC_DEF("trimStart", 0, js_string_trim, 1 ),
|
|
JS_ALIAS_DEF("trimLeft", "trimStart" ),
|
|
JS_CFUNC_DEF("toString", 0, js_string_toString ),
|
|
JS_CFUNC_DEF("valueOf", 0, js_string_toString ),
|
|
JS_CFUNC_DEF("localeCompare", 1, js_string_localeCompare ),
|
|
JS_CFUNC_DEF("normalize", 0, js_string_normalize ),
|
|
JS_CFUNC_MAGIC_DEF("toLowerCase", 0, js_string_toLowerCase, 1 ),
|
|
JS_CFUNC_MAGIC_DEF("toUpperCase", 0, js_string_toLowerCase, 0 ),
|
|
JS_CFUNC_MAGIC_DEF("toLocaleLowerCase", 0, js_string_toLowerCase, 1 ),
|
|
JS_CFUNC_MAGIC_DEF("toLocaleUpperCase", 0, js_string_toLowerCase, 0 ),
|
|
JS_CFUNC_MAGIC_DEF("[Symbol.iterator]", 0, js_create_array_iterator, JS_ITERATOR_KIND_VALUE | 4 ),
|
|
/* ES6 Annex B 2.3.2 etc. */
|
|
JS_CFUNC_MAGIC_DEF("anchor", 1, js_string_CreateHTML, magic_string_anchor ),
|
|
JS_CFUNC_MAGIC_DEF("big", 0, js_string_CreateHTML, magic_string_big ),
|
|
JS_CFUNC_MAGIC_DEF("blink", 0, js_string_CreateHTML, magic_string_blink ),
|
|
JS_CFUNC_MAGIC_DEF("bold", 0, js_string_CreateHTML, magic_string_bold ),
|
|
JS_CFUNC_MAGIC_DEF("fixed", 0, js_string_CreateHTML, magic_string_fixed ),
|
|
JS_CFUNC_MAGIC_DEF("fontcolor", 1, js_string_CreateHTML, magic_string_fontcolor ),
|
|
JS_CFUNC_MAGIC_DEF("fontsize", 1, js_string_CreateHTML, magic_string_fontsize ),
|
|
JS_CFUNC_MAGIC_DEF("italics", 0, js_string_CreateHTML, magic_string_italics ),
|
|
JS_CFUNC_MAGIC_DEF("link", 1, js_string_CreateHTML, magic_string_link ),
|
|
JS_CFUNC_MAGIC_DEF("small", 0, js_string_CreateHTML, magic_string_small ),
|
|
JS_CFUNC_MAGIC_DEF("strike", 0, js_string_CreateHTML, magic_string_strike ),
|
|
JS_CFUNC_MAGIC_DEF("sub", 0, js_string_CreateHTML, magic_string_sub ),
|
|
JS_CFUNC_MAGIC_DEF("sup", 0, js_string_CreateHTML, magic_string_sup ),
|
|
};
|
|
|
|
static const JSCFunctionListEntry js_string_iterator_proto_funcs[] = {
|
|
JS_ITERATOR_NEXT_DEF("next", 0, js_string_iterator_next, 0 ),
|
|
JS_PROP_STRING_DEF("[Symbol.toStringTag]", "String Iterator", JS_PROP_CONFIGURABLE ),
|
|
};
|
|
|
|
|
|
/* Math */
|
|
|
|
/* precondition: a and b are not NaN */
|
|
static double js_fmin(double a, double b)
|
|
{
|
|
if (a == 0 && b == 0) {
|
|
JSFloat64Union a1, b1;
|
|
a1.d = a;
|
|
b1.d = b;
|
|
a1.u64 |= b1.u64;
|
|
return a1.d;
|
|
} else {
|
|
return fmin(a, b);
|
|
}
|
|
}
|
|
|
|
/* precondition: a and b are not NaN */
|
|
static double js_fmax(double a, double b)
|
|
{
|
|
if (a == 0 && b == 0) {
|
|
JSFloat64Union a1, b1;
|
|
a1.d = a;
|
|
b1.d = b;
|
|
a1.u64 &= b1.u64;
|
|
return a1.d;
|
|
} else {
|
|
return fmax(a, b);
|
|
}
|
|
}
|
|
|
|
static JSValue js_math_min_max(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv, int magic)
|
|
{
|
|
BOOL is_max = magic;
|
|
double r, a;
|
|
int i;
|
|
uint32_t tag;
|
|
|
|
if (unlikely(argc == 0)) {
|
|
return js_float64(is_max ? NEG_INF : INF);
|
|
}
|
|
|
|
tag = JS_VALUE_GET_TAG(argv[0]);
|
|
if (tag == JS_TAG_INT) {
|
|
int a1, r1 = JS_VALUE_GET_INT(argv[0]);
|
|
for(i = 1; i < argc; i++) {
|
|
tag = JS_VALUE_GET_TAG(argv[i]);
|
|
if (tag != JS_TAG_INT) {
|
|
r = r1;
|
|
goto generic_case;
|
|
}
|
|
a1 = JS_VALUE_GET_INT(argv[i]);
|
|
if (is_max)
|
|
r1 = max_int(r1, a1);
|
|
else
|
|
r1 = min_int(r1, a1);
|
|
|
|
}
|
|
return js_int32(r1);
|
|
} else {
|
|
if (JS_ToFloat64(ctx, &r, argv[0]))
|
|
return JS_EXCEPTION;
|
|
i = 1;
|
|
generic_case:
|
|
while (i < argc) {
|
|
if (JS_ToFloat64(ctx, &a, argv[i]))
|
|
return JS_EXCEPTION;
|
|
if (!isnan(r)) {
|
|
if (isnan(a)) {
|
|
r = a;
|
|
} else {
|
|
if (is_max)
|
|
r = js_fmax(r, a);
|
|
else
|
|
r = js_fmin(r, a);
|
|
}
|
|
}
|
|
i++;
|
|
}
|
|
return js_number(r);
|
|
}
|
|
}
|
|
|
|
static double js_math_sign(double a)
|
|
{
|
|
if (isnan(a) || a == 0.0)
|
|
return a;
|
|
if (a < 0)
|
|
return -1;
|
|
else
|
|
return 1;
|
|
}
|
|
|
|
static double js_math_round(double a)
|
|
{
|
|
JSFloat64Union u;
|
|
uint64_t frac_mask, one;
|
|
unsigned int e, s;
|
|
|
|
u.d = a;
|
|
e = (u.u64 >> 52) & 0x7ff;
|
|
if (e < 1023) {
|
|
/* abs(a) < 1 */
|
|
if (e == (1023 - 1) && u.u64 != 0xbfe0000000000000) {
|
|
/* abs(a) > 0.5 or a = 0.5: return +/-1.0 */
|
|
u.u64 = (u.u64 & ((uint64_t)1 << 63)) | ((uint64_t)1023 << 52);
|
|
} else {
|
|
/* return +/-0.0 */
|
|
u.u64 &= (uint64_t)1 << 63;
|
|
}
|
|
} else if (e < (1023 + 52)) {
|
|
s = u.u64 >> 63;
|
|
one = (uint64_t)1 << (52 - (e - 1023));
|
|
frac_mask = one - 1;
|
|
u.u64 += (one >> 1) - s;
|
|
u.u64 &= ~frac_mask; /* truncate to an integer */
|
|
}
|
|
/* otherwise: abs(a) >= 2^52, or NaN, +/-Infinity: no change */
|
|
return u.d;
|
|
}
|
|
|
|
static JSValue js_math_hypot(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
double r, a;
|
|
int i;
|
|
|
|
r = 0;
|
|
if (argc > 0) {
|
|
if (JS_ToFloat64(ctx, &r, argv[0]))
|
|
return JS_EXCEPTION;
|
|
if (argc == 1) {
|
|
r = fabs(r);
|
|
} else {
|
|
/* use the built-in function to minimize precision loss */
|
|
for (i = 1; i < argc; i++) {
|
|
if (JS_ToFloat64(ctx, &a, argv[i]))
|
|
return JS_EXCEPTION;
|
|
r = hypot(r, a);
|
|
}
|
|
}
|
|
}
|
|
return js_float64(r);
|
|
}
|
|
|
|
static double js_math_fround(double a)
|
|
{
|
|
return (float)a;
|
|
}
|
|
|
|
static JSValue js_math_imul(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
uint32_t a, b, c;
|
|
int32_t d;
|
|
|
|
if (JS_ToUint32(ctx, &a, argv[0]))
|
|
return JS_EXCEPTION;
|
|
if (JS_ToUint32(ctx, &b, argv[1]))
|
|
return JS_EXCEPTION;
|
|
c = a * b;
|
|
memcpy(&d, &c, sizeof(d));
|
|
return js_int32(d);
|
|
}
|
|
|
|
static JSValue js_math_clz32(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
uint32_t a, r;
|
|
|
|
if (JS_ToUint32(ctx, &a, argv[0]))
|
|
return JS_EXCEPTION;
|
|
if (a == 0)
|
|
r = 32;
|
|
else
|
|
r = clz32(a);
|
|
return js_int32(r);
|
|
}
|
|
|
|
/* xorshift* random number generator by Marsaglia */
|
|
static uint64_t xorshift64star(uint64_t *pstate)
|
|
{
|
|
uint64_t x;
|
|
x = *pstate;
|
|
x ^= x >> 12;
|
|
x ^= x << 25;
|
|
x ^= x >> 27;
|
|
*pstate = x;
|
|
return x * 0x2545F4914F6CDD1D;
|
|
}
|
|
|
|
static void js_random_init(JSContext *ctx)
|
|
{
|
|
ctx->random_state = js__gettimeofday_us();
|
|
/* the state must be non zero */
|
|
if (ctx->random_state == 0)
|
|
ctx->random_state = 1;
|
|
}
|
|
|
|
static JSValue js_math_random(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSFloat64Union u;
|
|
uint64_t v;
|
|
|
|
v = xorshift64star(&ctx->random_state);
|
|
/* 1.0 <= u.d < 2 */
|
|
u.u64 = ((uint64_t)0x3ff << 52) | (v >> 12);
|
|
return js_float64(u.d - 1.0);
|
|
}
|
|
|
|
/* use local wrappers for math functions to
|
|
- avoid initializing data with dynamic library entry points.
|
|
- avoid some overhead if the call can be inlined at compile or link time.
|
|
*/
|
|
static double js_math_fabs(double d) { return fabs(d); }
|
|
static double js_math_floor(double d) { return floor(d); }
|
|
static double js_math_ceil(double d) { return ceil(d); }
|
|
static double js_math_sqrt(double d) { return sqrt(d); }
|
|
static double js_math_acos(double d) { return acos(d); }
|
|
static double js_math_asin(double d) { return asin(d); }
|
|
static double js_math_atan(double d) { return atan(d); }
|
|
static double js_math_atan2(double a, double b) { return atan2(a, b); }
|
|
static double js_math_cos(double d) { return cos(d); }
|
|
static double js_math_exp(double d) { return exp(d); }
|
|
static double js_math_log(double d) { return log(d); }
|
|
static double js_math_sin(double d) { return sin(d); }
|
|
static double js_math_tan(double d) { return tan(d); }
|
|
static double js_math_trunc(double d) { return trunc(d); }
|
|
static double js_math_cosh(double d) { return cosh(d); }
|
|
static double js_math_sinh(double d) { return sinh(d); }
|
|
static double js_math_tanh(double d) { return tanh(d); }
|
|
static double js_math_acosh(double d) { return acosh(d); }
|
|
static double js_math_asinh(double d) { return asinh(d); }
|
|
static double js_math_atanh(double d) { return atanh(d); }
|
|
static double js_math_expm1(double d) { return expm1(d); }
|
|
static double js_math_log1p(double d) { return log1p(d); }
|
|
static double js_math_log2(double d) { return log2(d); }
|
|
static double js_math_log10(double d) { return log10(d); }
|
|
static double js_math_cbrt(double d) { return cbrt(d); }
|
|
|
|
static const JSCFunctionListEntry js_math_funcs[] = {
|
|
JS_CFUNC_MAGIC_DEF("min", 2, js_math_min_max, 0 ),
|
|
JS_CFUNC_MAGIC_DEF("max", 2, js_math_min_max, 1 ),
|
|
JS_CFUNC_SPECIAL_DEF("abs", 1, f_f, js_math_fabs ),
|
|
JS_CFUNC_SPECIAL_DEF("floor", 1, f_f, js_math_floor ),
|
|
JS_CFUNC_SPECIAL_DEF("ceil", 1, f_f, js_math_ceil ),
|
|
JS_CFUNC_SPECIAL_DEF("round", 1, f_f, js_math_round ),
|
|
JS_CFUNC_SPECIAL_DEF("sqrt", 1, f_f, js_math_sqrt ),
|
|
|
|
JS_CFUNC_SPECIAL_DEF("acos", 1, f_f, js_math_acos ),
|
|
JS_CFUNC_SPECIAL_DEF("asin", 1, f_f, js_math_asin ),
|
|
JS_CFUNC_SPECIAL_DEF("atan", 1, f_f, js_math_atan ),
|
|
JS_CFUNC_SPECIAL_DEF("atan2", 2, f_f_f, js_math_atan2 ),
|
|
JS_CFUNC_SPECIAL_DEF("cos", 1, f_f, js_math_cos ),
|
|
JS_CFUNC_SPECIAL_DEF("exp", 1, f_f, js_math_exp ),
|
|
JS_CFUNC_SPECIAL_DEF("log", 1, f_f, js_math_log ),
|
|
JS_CFUNC_SPECIAL_DEF("pow", 2, f_f_f, js_math_pow ),
|
|
JS_CFUNC_SPECIAL_DEF("sin", 1, f_f, js_math_sin ),
|
|
JS_CFUNC_SPECIAL_DEF("tan", 1, f_f, js_math_tan ),
|
|
/* ES6 */
|
|
JS_CFUNC_SPECIAL_DEF("trunc", 1, f_f, js_math_trunc ),
|
|
JS_CFUNC_SPECIAL_DEF("sign", 1, f_f, js_math_sign ),
|
|
JS_CFUNC_SPECIAL_DEF("cosh", 1, f_f, js_math_cosh ),
|
|
JS_CFUNC_SPECIAL_DEF("sinh", 1, f_f, js_math_sinh ),
|
|
JS_CFUNC_SPECIAL_DEF("tanh", 1, f_f, js_math_tanh ),
|
|
JS_CFUNC_SPECIAL_DEF("acosh", 1, f_f, js_math_acosh ),
|
|
JS_CFUNC_SPECIAL_DEF("asinh", 1, f_f, js_math_asinh ),
|
|
JS_CFUNC_SPECIAL_DEF("atanh", 1, f_f, js_math_atanh ),
|
|
JS_CFUNC_SPECIAL_DEF("expm1", 1, f_f, js_math_expm1 ),
|
|
JS_CFUNC_SPECIAL_DEF("log1p", 1, f_f, js_math_log1p ),
|
|
JS_CFUNC_SPECIAL_DEF("log2", 1, f_f, js_math_log2 ),
|
|
JS_CFUNC_SPECIAL_DEF("log10", 1, f_f, js_math_log10 ),
|
|
JS_CFUNC_SPECIAL_DEF("cbrt", 1, f_f, js_math_cbrt ),
|
|
JS_CFUNC_DEF("hypot", 2, js_math_hypot ),
|
|
JS_CFUNC_DEF("random", 0, js_math_random ),
|
|
JS_CFUNC_SPECIAL_DEF("fround", 1, f_f, js_math_fround ),
|
|
JS_CFUNC_DEF("imul", 2, js_math_imul ),
|
|
JS_CFUNC_DEF("clz32", 1, js_math_clz32 ),
|
|
JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Math", JS_PROP_CONFIGURABLE ),
|
|
JS_PROP_DOUBLE_DEF("E", 2.718281828459045, 0 ),
|
|
JS_PROP_DOUBLE_DEF("LN10", 2.302585092994046, 0 ),
|
|
JS_PROP_DOUBLE_DEF("LN2", 0.6931471805599453, 0 ),
|
|
JS_PROP_DOUBLE_DEF("LOG2E", 1.4426950408889634, 0 ),
|
|
JS_PROP_DOUBLE_DEF("LOG10E", 0.4342944819032518, 0 ),
|
|
JS_PROP_DOUBLE_DEF("PI", 3.141592653589793, 0 ),
|
|
JS_PROP_DOUBLE_DEF("SQRT1_2", 0.7071067811865476, 0 ),
|
|
JS_PROP_DOUBLE_DEF("SQRT2", 1.4142135623730951, 0 ),
|
|
};
|
|
|
|
static const JSCFunctionListEntry js_math_obj[] = {
|
|
JS_OBJECT_DEF("Math", js_math_funcs, countof(js_math_funcs), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE ),
|
|
};
|
|
|
|
/* Date */
|
|
|
|
/* OS dependent. d = argv[0] is in ms from 1970. Return the difference
|
|
between UTC time and local time 'd' in minutes */
|
|
static int getTimezoneOffset(int64_t time) {
|
|
#if defined(_WIN32)
|
|
TIME_ZONE_INFORMATION time_zone_info;
|
|
GetTimeZoneInformation(&time_zone_info);
|
|
return (int)time_zone_info.Bias / 60;
|
|
#else
|
|
time_t ti;
|
|
struct tm tm;
|
|
|
|
time /= 1000; /* convert to seconds */
|
|
if (sizeof(time_t) == 4) {
|
|
/* on 32-bit systems, we need to clamp the time value to the
|
|
range of `time_t`. This is better than truncating values to
|
|
32 bits and hopefully provides the same result as 64-bit
|
|
implementation of localtime_r.
|
|
*/
|
|
if ((time_t)-1 < 0) {
|
|
if (time < INT32_MIN) {
|
|
time = INT32_MIN;
|
|
} else if (time > INT32_MAX) {
|
|
time = INT32_MAX;
|
|
}
|
|
} else {
|
|
if (time < 0) {
|
|
time = 0;
|
|
} else if (time > UINT32_MAX) {
|
|
time = UINT32_MAX;
|
|
}
|
|
}
|
|
}
|
|
ti = time;
|
|
localtime_r(&ti, &tm);
|
|
#ifdef NO_TM_GMTOFF
|
|
struct tm gmt;
|
|
gmtime_r(&ti, &gmt);
|
|
|
|
/* disable DST adjustment on the local tm struct */
|
|
tm.tm_isdst = 0;
|
|
|
|
return (int)difftime(mktime(&gmt), mktime(&tm)) / 60;
|
|
#else
|
|
return -tm.tm_gmtoff / 60;
|
|
#endif /* NO_TM_GMTOFF */
|
|
#endif
|
|
}
|
|
|
|
/* RegExp */
|
|
|
|
static void js_regexp_finalizer(JSRuntime *rt, JSValue val)
|
|
{
|
|
JSObject *p = JS_VALUE_GET_OBJ(val);
|
|
JSRegExp *re = &p->u.regexp;
|
|
JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_STRING, re->bytecode));
|
|
JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_STRING, re->pattern));
|
|
}
|
|
|
|
/* create a string containing the RegExp bytecode */
|
|
static JSValue js_compile_regexp(JSContext *ctx, JSValue pattern,
|
|
JSValue flags)
|
|
{
|
|
const char *str;
|
|
int re_flags, mask;
|
|
uint8_t *re_bytecode_buf;
|
|
size_t i, len;
|
|
int re_bytecode_len;
|
|
JSValue ret;
|
|
char error_msg[64];
|
|
|
|
re_flags = 0;
|
|
if (!JS_IsUndefined(flags)) {
|
|
str = JS_ToCStringLen(ctx, &len, flags);
|
|
if (!str)
|
|
return JS_EXCEPTION;
|
|
/* XXX: re_flags = LRE_FLAG_OCTAL unless strict mode? */
|
|
for (i = 0; i < len; i++) {
|
|
switch(str[i]) {
|
|
case 'd':
|
|
mask = LRE_FLAG_INDICES;
|
|
break;
|
|
case 'g':
|
|
mask = LRE_FLAG_GLOBAL;
|
|
break;
|
|
case 'i':
|
|
mask = LRE_FLAG_IGNORECASE;
|
|
break;
|
|
case 'm':
|
|
mask = LRE_FLAG_MULTILINE;
|
|
break;
|
|
case 's':
|
|
mask = LRE_FLAG_DOTALL;
|
|
break;
|
|
case 'u':
|
|
mask = LRE_FLAG_UNICODE;
|
|
break;
|
|
case 'v':
|
|
mask = LRE_FLAG_UNICODE_SETS;
|
|
break;
|
|
case 'y':
|
|
mask = LRE_FLAG_STICKY;
|
|
break;
|
|
default:
|
|
goto bad_flags;
|
|
}
|
|
if ((re_flags & mask) != 0) {
|
|
bad_flags:
|
|
JS_FreeCString(ctx, str);
|
|
return JS_ThrowSyntaxError(ctx, "invalid regular expression flags");
|
|
}
|
|
re_flags |= mask;
|
|
}
|
|
JS_FreeCString(ctx, str);
|
|
}
|
|
|
|
if (re_flags & LRE_FLAG_UNICODE)
|
|
if (re_flags & LRE_FLAG_UNICODE_SETS)
|
|
return JS_ThrowSyntaxError(ctx, "invalid regular expression flags");
|
|
|
|
str = JS_ToCStringLen2(ctx, &len, pattern, !(re_flags & LRE_FLAG_UNICODE));
|
|
if (!str)
|
|
return JS_EXCEPTION;
|
|
re_bytecode_buf = lre_compile(&re_bytecode_len, error_msg,
|
|
sizeof(error_msg), str, len, re_flags, ctx);
|
|
JS_FreeCString(ctx, str);
|
|
if (!re_bytecode_buf) {
|
|
JS_ThrowSyntaxError(ctx, "%s", error_msg);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
ret = js_new_string8_len(ctx, (char *)re_bytecode_buf, re_bytecode_len);
|
|
js_free(ctx, re_bytecode_buf);
|
|
return ret;
|
|
}
|
|
|
|
/* create a RegExp object from a string containing the RegExp bytecode
|
|
and the source pattern */
|
|
static JSValue js_regexp_constructor_internal(JSContext *ctx, JSValue ctor,
|
|
JSValue pattern, JSValue bc)
|
|
{
|
|
JSValue obj;
|
|
JSObject *p;
|
|
JSRegExp *re;
|
|
|
|
/* sanity check */
|
|
if (JS_VALUE_GET_TAG(bc) != JS_TAG_STRING ||
|
|
JS_VALUE_GET_TAG(pattern) != JS_TAG_STRING) {
|
|
JS_ThrowTypeError(ctx, "string expected");
|
|
fail:
|
|
JS_FreeValue(ctx, bc);
|
|
JS_FreeValue(ctx, pattern);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
obj = js_create_from_ctor(ctx, ctor, JS_CLASS_REGEXP);
|
|
if (JS_IsException(obj))
|
|
goto fail;
|
|
p = JS_VALUE_GET_OBJ(obj);
|
|
re = &p->u.regexp;
|
|
re->pattern = JS_VALUE_GET_STRING(pattern);
|
|
re->bytecode = JS_VALUE_GET_STRING(bc);
|
|
JS_DefinePropertyValue(ctx, obj, JS_ATOM_lastIndex, js_int32(0),
|
|
JS_PROP_WRITABLE);
|
|
return obj;
|
|
}
|
|
|
|
static JSRegExp *js_get_regexp(JSContext *ctx, JSValue obj, BOOL throw_error)
|
|
{
|
|
if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) {
|
|
JSObject *p = JS_VALUE_GET_OBJ(obj);
|
|
if (p->class_id == JS_CLASS_REGEXP)
|
|
return &p->u.regexp;
|
|
}
|
|
if (throw_error) {
|
|
JS_ThrowTypeErrorInvalidClass(ctx, JS_CLASS_REGEXP);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* return < 0 if exception or TRUE/FALSE */
|
|
static int js_is_regexp(JSContext *ctx, JSValue obj)
|
|
{
|
|
JSValue m;
|
|
|
|
if (!JS_IsObject(obj))
|
|
return FALSE;
|
|
m = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_match);
|
|
if (JS_IsException(m))
|
|
return -1;
|
|
if (!JS_IsUndefined(m))
|
|
return JS_ToBoolFree(ctx, m);
|
|
return js_get_regexp(ctx, obj, FALSE) != NULL;
|
|
}
|
|
|
|
static JSValue js_regexp_constructor(JSContext *ctx, JSValue new_target,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue pattern, flags, bc, val;
|
|
JSValue pat, flags1;
|
|
JSRegExp *re;
|
|
int pat_is_regexp;
|
|
|
|
pat = argv[0];
|
|
flags1 = argv[1];
|
|
pat_is_regexp = js_is_regexp(ctx, pat);
|
|
if (pat_is_regexp < 0)
|
|
return JS_EXCEPTION;
|
|
if (JS_IsUndefined(new_target)) {
|
|
/* called as a function */
|
|
new_target = JS_GetActiveFunction(ctx);
|
|
if (pat_is_regexp && JS_IsUndefined(flags1)) {
|
|
JSValue ctor;
|
|
BOOL res;
|
|
ctor = JS_GetProperty(ctx, pat, JS_ATOM_constructor);
|
|
if (JS_IsException(ctor))
|
|
return ctor;
|
|
res = js_same_value(ctx, ctor, new_target);
|
|
JS_FreeValue(ctx, ctor);
|
|
if (res)
|
|
return js_dup(pat);
|
|
}
|
|
}
|
|
re = js_get_regexp(ctx, pat, FALSE);
|
|
if (re) {
|
|
pattern = js_dup(JS_MKPTR(JS_TAG_STRING, re->pattern));
|
|
if (JS_IsUndefined(flags1)) {
|
|
bc = js_dup(JS_MKPTR(JS_TAG_STRING, re->bytecode));
|
|
goto no_compilation;
|
|
} else {
|
|
flags = JS_ToString(ctx, flags1);
|
|
if (JS_IsException(flags))
|
|
goto fail;
|
|
}
|
|
} else {
|
|
flags = JS_UNDEFINED;
|
|
if (pat_is_regexp) {
|
|
pattern = JS_GetProperty(ctx, pat, JS_ATOM_source);
|
|
if (JS_IsException(pattern))
|
|
goto fail;
|
|
if (JS_IsUndefined(flags1)) {
|
|
flags = JS_GetProperty(ctx, pat, JS_ATOM_flags);
|
|
if (JS_IsException(flags))
|
|
goto fail;
|
|
} else {
|
|
flags = js_dup(flags1);
|
|
}
|
|
} else {
|
|
pattern = js_dup(pat);
|
|
flags = js_dup(flags1);
|
|
}
|
|
if (JS_IsUndefined(pattern)) {
|
|
pattern = JS_AtomToString(ctx, JS_ATOM_empty_string);
|
|
} else {
|
|
val = pattern;
|
|
pattern = JS_ToString(ctx, val);
|
|
JS_FreeValue(ctx, val);
|
|
if (JS_IsException(pattern))
|
|
goto fail;
|
|
}
|
|
}
|
|
bc = js_compile_regexp(ctx, pattern, flags);
|
|
if (JS_IsException(bc))
|
|
goto fail;
|
|
JS_FreeValue(ctx, flags);
|
|
no_compilation:
|
|
return js_regexp_constructor_internal(ctx, new_target, pattern, bc);
|
|
fail:
|
|
JS_FreeValue(ctx, pattern);
|
|
JS_FreeValue(ctx, flags);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue js_regexp_compile(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSRegExp *re1, *re;
|
|
JSValue pattern1, flags1;
|
|
JSValue bc, pattern;
|
|
|
|
re = js_get_regexp(ctx, this_val, TRUE);
|
|
if (!re)
|
|
return JS_EXCEPTION;
|
|
pattern1 = argv[0];
|
|
flags1 = argv[1];
|
|
re1 = js_get_regexp(ctx, pattern1, FALSE);
|
|
if (re1) {
|
|
if (!JS_IsUndefined(flags1))
|
|
return JS_ThrowTypeError(ctx, "flags must be undefined");
|
|
pattern = js_dup(JS_MKPTR(JS_TAG_STRING, re1->pattern));
|
|
bc = js_dup(JS_MKPTR(JS_TAG_STRING, re1->bytecode));
|
|
} else {
|
|
bc = JS_UNDEFINED;
|
|
if (JS_IsUndefined(pattern1))
|
|
pattern = JS_AtomToString(ctx, JS_ATOM_empty_string);
|
|
else
|
|
pattern = JS_ToString(ctx, pattern1);
|
|
if (JS_IsException(pattern))
|
|
goto fail;
|
|
bc = js_compile_regexp(ctx, pattern, flags1);
|
|
if (JS_IsException(bc))
|
|
goto fail;
|
|
}
|
|
JS_FreeValue(ctx, JS_MKPTR(JS_TAG_STRING, re->pattern));
|
|
JS_FreeValue(ctx, JS_MKPTR(JS_TAG_STRING, re->bytecode));
|
|
re->pattern = JS_VALUE_GET_STRING(pattern);
|
|
re->bytecode = JS_VALUE_GET_STRING(bc);
|
|
if (JS_SetProperty(ctx, this_val, JS_ATOM_lastIndex,
|
|
js_int32(0)) < 0)
|
|
return JS_EXCEPTION;
|
|
return js_dup(this_val);
|
|
fail:
|
|
JS_FreeValue(ctx, pattern);
|
|
JS_FreeValue(ctx, bc);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue js_regexp_get_source(JSContext *ctx, JSValue this_val)
|
|
{
|
|
JSRegExp *re;
|
|
JSString *p;
|
|
StringBuffer b_s, *b = &b_s;
|
|
int i, n, c, c2, bra;
|
|
|
|
if (JS_VALUE_GET_TAG(this_val) != JS_TAG_OBJECT)
|
|
return JS_ThrowTypeErrorNotAnObject(ctx);
|
|
|
|
if (js_same_value(ctx, this_val, ctx->class_proto[JS_CLASS_REGEXP]))
|
|
goto empty_regex;
|
|
|
|
re = js_get_regexp(ctx, this_val, TRUE);
|
|
if (!re)
|
|
return JS_EXCEPTION;
|
|
|
|
p = re->pattern;
|
|
|
|
if (p->len == 0) {
|
|
empty_regex:
|
|
return js_new_string8(ctx, "(?:)");
|
|
}
|
|
string_buffer_init2(ctx, b, p->len, p->is_wide_char);
|
|
|
|
/* Escape '/' and newline sequences as needed */
|
|
bra = 0;
|
|
for (i = 0, n = p->len; i < n;) {
|
|
c2 = -1;
|
|
switch (c = string_get(p, i++)) {
|
|
case '\\':
|
|
if (i < n)
|
|
c2 = string_get(p, i++);
|
|
break;
|
|
case ']':
|
|
bra = 0;
|
|
break;
|
|
case '[':
|
|
if (!bra) {
|
|
if (i < n && string_get(p, i) == ']')
|
|
c2 = string_get(p, i++);
|
|
bra = 1;
|
|
}
|
|
break;
|
|
case '\n':
|
|
c = '\\';
|
|
c2 = 'n';
|
|
break;
|
|
case '\r':
|
|
c = '\\';
|
|
c2 = 'r';
|
|
break;
|
|
case '/':
|
|
if (!bra) {
|
|
c = '\\';
|
|
c2 = '/';
|
|
}
|
|
break;
|
|
}
|
|
string_buffer_putc16(b, c);
|
|
if (c2 >= 0)
|
|
string_buffer_putc16(b, c2);
|
|
}
|
|
return string_buffer_end(b);
|
|
}
|
|
|
|
static JSValue js_regexp_get_flag(JSContext *ctx, JSValue this_val, int mask)
|
|
{
|
|
JSRegExp *re;
|
|
int flags;
|
|
|
|
if (JS_VALUE_GET_TAG(this_val) != JS_TAG_OBJECT)
|
|
return JS_ThrowTypeErrorNotAnObject(ctx);
|
|
|
|
re = js_get_regexp(ctx, this_val, FALSE);
|
|
if (!re) {
|
|
if (js_same_value(ctx, this_val, ctx->class_proto[JS_CLASS_REGEXP]))
|
|
return JS_UNDEFINED;
|
|
else
|
|
return JS_ThrowTypeErrorInvalidClass(ctx, JS_CLASS_REGEXP);
|
|
}
|
|
|
|
flags = lre_get_flags(re->bytecode->u.str8);
|
|
return js_bool(flags & mask);
|
|
}
|
|
|
|
static JSValue js_regexp_get_flags(JSContext *ctx, JSValue this_val)
|
|
{
|
|
char str[8], *p = str;
|
|
int res;
|
|
|
|
if (JS_VALUE_GET_TAG(this_val) != JS_TAG_OBJECT)
|
|
return JS_ThrowTypeErrorNotAnObject(ctx);
|
|
|
|
res = JS_ToBoolFree(ctx, JS_GetPropertyStr(ctx, this_val, "hasIndices"));
|
|
if (res < 0)
|
|
goto exception;
|
|
if (res)
|
|
*p++ = 'd';
|
|
res = JS_ToBoolFree(ctx, JS_GetProperty(ctx, this_val, JS_ATOM_global));
|
|
if (res < 0)
|
|
goto exception;
|
|
if (res)
|
|
*p++ = 'g';
|
|
res = JS_ToBoolFree(ctx, JS_GetPropertyStr(ctx, this_val, "ignoreCase"));
|
|
if (res < 0)
|
|
goto exception;
|
|
if (res)
|
|
*p++ = 'i';
|
|
res = JS_ToBoolFree(ctx, JS_GetPropertyStr(ctx, this_val, "multiline"));
|
|
if (res < 0)
|
|
goto exception;
|
|
if (res)
|
|
*p++ = 'm';
|
|
res = JS_ToBoolFree(ctx, JS_GetPropertyStr(ctx, this_val, "dotAll"));
|
|
if (res < 0)
|
|
goto exception;
|
|
if (res)
|
|
*p++ = 's';
|
|
res = JS_ToBoolFree(ctx, JS_GetProperty(ctx, this_val, JS_ATOM_unicode));
|
|
if (res < 0)
|
|
goto exception;
|
|
if (res)
|
|
*p++ = 'u';
|
|
res = JS_ToBoolFree(ctx, JS_GetPropertyStr(ctx, this_val, "unicodeSets"));
|
|
if (res < 0)
|
|
goto exception;
|
|
if (res)
|
|
*p++ = 'v';
|
|
res = JS_ToBoolFree(ctx, JS_GetPropertyStr(ctx, this_val, "sticky"));
|
|
if (res < 0)
|
|
goto exception;
|
|
if (res)
|
|
*p++ = 'y';
|
|
if (p == str)
|
|
return JS_AtomToString(ctx, JS_ATOM_empty_string);
|
|
return js_new_string8_len(ctx, str, p - str);
|
|
|
|
exception:
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue js_regexp_toString(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue pattern, flags;
|
|
StringBuffer b_s, *b = &b_s;
|
|
|
|
if (!JS_IsObject(this_val))
|
|
return JS_ThrowTypeErrorNotAnObject(ctx);
|
|
|
|
string_buffer_init(ctx, b, 0);
|
|
string_buffer_putc8(b, '/');
|
|
pattern = JS_GetProperty(ctx, this_val, JS_ATOM_source);
|
|
if (string_buffer_concat_value_free(b, pattern))
|
|
goto fail;
|
|
string_buffer_putc8(b, '/');
|
|
flags = JS_GetProperty(ctx, this_val, JS_ATOM_flags);
|
|
if (string_buffer_concat_value_free(b, flags))
|
|
goto fail;
|
|
return string_buffer_end(b);
|
|
|
|
fail:
|
|
string_buffer_free(b);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
BOOL lre_check_stack_overflow(void *opaque, size_t alloca_size)
|
|
{
|
|
JSContext *ctx = opaque;
|
|
return js_check_stack_overflow(ctx->rt, alloca_size);
|
|
}
|
|
|
|
void *lre_realloc(void *opaque, void *ptr, size_t size)
|
|
{
|
|
JSContext *ctx = opaque;
|
|
/* No JS exception is raised here */
|
|
return js_realloc_rt(ctx->rt, ptr, size);
|
|
}
|
|
|
|
static JSValue js_regexp_exec(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSRegExp *re = js_get_regexp(ctx, this_val, TRUE);
|
|
JSString *str;
|
|
JSValue t, ret, str_val, obj, val, groups;
|
|
JSValue indices, indices_groups;
|
|
uint8_t *re_bytecode;
|
|
uint8_t **capture, *str_buf;
|
|
int rc, capture_count, shift, i, re_flags;
|
|
int64_t last_index;
|
|
const char *group_name_ptr;
|
|
|
|
if (!re)
|
|
return JS_EXCEPTION;
|
|
|
|
str_val = JS_ToString(ctx, argv[0]);
|
|
if (JS_IsException(str_val))
|
|
return JS_EXCEPTION;
|
|
|
|
ret = JS_EXCEPTION;
|
|
obj = JS_NULL;
|
|
groups = JS_UNDEFINED;
|
|
indices = JS_UNDEFINED;
|
|
indices_groups = JS_UNDEFINED;
|
|
capture = NULL;
|
|
|
|
val = JS_GetProperty(ctx, this_val, JS_ATOM_lastIndex);
|
|
if (JS_IsException(val) || JS_ToLengthFree(ctx, &last_index, val))
|
|
goto fail;
|
|
|
|
re_bytecode = re->bytecode->u.str8;
|
|
re_flags = lre_get_flags(re_bytecode);
|
|
if ((re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) == 0) {
|
|
last_index = 0;
|
|
}
|
|
str = JS_VALUE_GET_STRING(str_val);
|
|
capture_count = lre_get_capture_count(re_bytecode);
|
|
if (capture_count > 0) {
|
|
capture = js_malloc(ctx, sizeof(capture[0]) * capture_count * 2);
|
|
if (!capture)
|
|
goto fail;
|
|
}
|
|
shift = str->is_wide_char;
|
|
str_buf = str->u.str8;
|
|
if (last_index > str->len) {
|
|
rc = 2;
|
|
} else {
|
|
rc = lre_exec(capture, re_bytecode,
|
|
str_buf, last_index, str->len,
|
|
shift, ctx);
|
|
}
|
|
if (rc != 1) {
|
|
if (rc >= 0) {
|
|
if (rc == 2 || (re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY))) {
|
|
if (JS_SetProperty(ctx, this_val, JS_ATOM_lastIndex,
|
|
js_int32(0)) < 0)
|
|
goto fail;
|
|
}
|
|
} else {
|
|
JS_ThrowInternalError(ctx, "out of memory in regexp execution");
|
|
goto fail;
|
|
}
|
|
} else {
|
|
int prop_flags;
|
|
if (re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) {
|
|
if (JS_SetProperty(ctx, this_val, JS_ATOM_lastIndex,
|
|
js_int32((capture[1] - str_buf) >> shift)) < 0)
|
|
goto fail;
|
|
}
|
|
obj = JS_NewArray(ctx);
|
|
if (JS_IsException(obj))
|
|
goto fail;
|
|
prop_flags = JS_PROP_C_W_E | JS_PROP_THROW;
|
|
group_name_ptr = lre_get_groupnames(re_bytecode);
|
|
if (group_name_ptr) {
|
|
groups = JS_NewObjectProto(ctx, JS_NULL);
|
|
if (JS_IsException(groups))
|
|
goto fail;
|
|
}
|
|
if (re_flags & LRE_FLAG_INDICES) {
|
|
indices = JS_NewArray(ctx);
|
|
if (JS_IsException(indices))
|
|
goto fail;
|
|
if (group_name_ptr) {
|
|
indices_groups = JS_NewObjectProto(ctx, JS_NULL);
|
|
if (JS_IsException(indices_groups))
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
for(i = 0; i < capture_count; i++) {
|
|
const char *name = NULL;
|
|
uint8_t **match = &capture[2 * i];
|
|
int start = -1;
|
|
int end = -1;
|
|
|
|
if (group_name_ptr && i > 0) {
|
|
if (*group_name_ptr) name = group_name_ptr;
|
|
group_name_ptr += strlen(group_name_ptr) + 1;
|
|
}
|
|
|
|
if (match[0] && match[1]) {
|
|
start = (match[0] - str_buf) >> shift;
|
|
end = (match[1] - str_buf) >> shift;
|
|
}
|
|
|
|
if (!JS_IsUndefined(indices)) {
|
|
JSValue val = JS_UNDEFINED;
|
|
if (start != -1) {
|
|
val = JS_NewArray(ctx);
|
|
if (JS_IsException(val))
|
|
goto fail;
|
|
if (JS_DefinePropertyValueUint32(ctx, val, 0,
|
|
js_int32(start),
|
|
prop_flags) < 0) {
|
|
JS_FreeValue(ctx, val);
|
|
goto fail;
|
|
}
|
|
if (JS_DefinePropertyValueUint32(ctx, val, 1,
|
|
js_int32(end),
|
|
prop_flags) < 0) {
|
|
JS_FreeValue(ctx, val);
|
|
goto fail;
|
|
}
|
|
}
|
|
if (name && !JS_IsUndefined(indices_groups)) {
|
|
val = js_dup(val);
|
|
if (JS_DefinePropertyValueStr(ctx, indices_groups,
|
|
name, val, prop_flags) < 0) {
|
|
JS_FreeValue(ctx, val);
|
|
goto fail;
|
|
}
|
|
}
|
|
if (JS_DefinePropertyValueUint32(ctx, indices, i, val,
|
|
prop_flags) < 0) {
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
JSValue val = JS_UNDEFINED;
|
|
if (start != -1) {
|
|
val = js_sub_string(ctx, str, start, end);
|
|
if (JS_IsException(val))
|
|
goto fail;
|
|
}
|
|
|
|
if (name) {
|
|
if (JS_DefinePropertyValueStr(ctx, groups, name,
|
|
js_dup(val),
|
|
prop_flags) < 0) {
|
|
JS_FreeValue(ctx, val);
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
if (JS_DefinePropertyValueUint32(ctx, obj, i, val, prop_flags) < 0)
|
|
goto fail;
|
|
}
|
|
|
|
t = groups, groups = JS_UNDEFINED;
|
|
if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_groups,
|
|
t, prop_flags) < 0) {
|
|
goto fail;
|
|
}
|
|
|
|
t = js_int32((capture[0] - str_buf) >> shift);
|
|
if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_index, t, prop_flags) < 0)
|
|
goto fail;
|
|
|
|
t = str_val, str_val = JS_UNDEFINED;
|
|
if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_input, t, prop_flags) < 0)
|
|
goto fail;
|
|
|
|
if (!JS_IsUndefined(indices)) {
|
|
t = indices_groups, indices_groups = JS_UNDEFINED;
|
|
if (JS_DefinePropertyValue(ctx, indices, JS_ATOM_groups,
|
|
t, prop_flags) < 0) {
|
|
goto fail;
|
|
}
|
|
t = indices, indices = JS_UNDEFINED;
|
|
if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_indices,
|
|
t, prop_flags) < 0) {
|
|
goto fail;
|
|
}
|
|
}
|
|
}
|
|
ret = obj;
|
|
obj = JS_UNDEFINED;
|
|
fail:
|
|
JS_FreeValue(ctx, indices_groups);
|
|
JS_FreeValue(ctx, indices);
|
|
JS_FreeValue(ctx, str_val);
|
|
JS_FreeValue(ctx, groups);
|
|
JS_FreeValue(ctx, obj);
|
|
js_free(ctx, capture);
|
|
return ret;
|
|
}
|
|
|
|
/* delete portions of a string that match a given regex */
|
|
static JSValue JS_RegExpDelete(JSContext *ctx, JSValue this_val, JSValue arg)
|
|
{
|
|
JSRegExp *re = js_get_regexp(ctx, this_val, TRUE);
|
|
JSString *str;
|
|
JSValue str_val, val;
|
|
uint8_t *re_bytecode;
|
|
int ret;
|
|
uint8_t **capture, *str_buf;
|
|
int capture_count, shift, re_flags;
|
|
int next_src_pos, start, end;
|
|
int64_t last_index;
|
|
StringBuffer b_s, *b = &b_s;
|
|
|
|
if (!re)
|
|
return JS_EXCEPTION;
|
|
|
|
string_buffer_init(ctx, b, 0);
|
|
|
|
capture = NULL;
|
|
str_val = JS_ToString(ctx, arg);
|
|
if (JS_IsException(str_val))
|
|
goto fail;
|
|
str = JS_VALUE_GET_STRING(str_val);
|
|
re_bytecode = re->bytecode->u.str8;
|
|
re_flags = lre_get_flags(re_bytecode);
|
|
if ((re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) == 0) {
|
|
last_index = 0;
|
|
} else {
|
|
val = JS_GetProperty(ctx, this_val, JS_ATOM_lastIndex);
|
|
if (JS_IsException(val) || JS_ToLengthFree(ctx, &last_index, val))
|
|
goto fail;
|
|
}
|
|
capture_count = lre_get_capture_count(re_bytecode);
|
|
if (capture_count > 0) {
|
|
capture = js_malloc(ctx, sizeof(capture[0]) * capture_count * 2);
|
|
if (!capture)
|
|
goto fail;
|
|
}
|
|
shift = str->is_wide_char;
|
|
str_buf = str->u.str8;
|
|
next_src_pos = 0;
|
|
for (;;) {
|
|
if (last_index > str->len)
|
|
break;
|
|
|
|
ret = lre_exec(capture, re_bytecode,
|
|
str_buf, last_index, str->len, shift, ctx);
|
|
if (ret != 1) {
|
|
if (ret >= 0) {
|
|
if (ret == 2 || (re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY))) {
|
|
if (JS_SetProperty(ctx, this_val, JS_ATOM_lastIndex,
|
|
js_int32(0)) < 0)
|
|
goto fail;
|
|
}
|
|
} else {
|
|
JS_ThrowInternalError(ctx, "out of memory in regexp execution");
|
|
goto fail;
|
|
}
|
|
break;
|
|
}
|
|
start = (capture[0] - str_buf) >> shift;
|
|
end = (capture[1] - str_buf) >> shift;
|
|
last_index = end;
|
|
if (next_src_pos < start) {
|
|
if (string_buffer_concat(b, str, next_src_pos, start))
|
|
goto fail;
|
|
}
|
|
next_src_pos = end;
|
|
if (!(re_flags & LRE_FLAG_GLOBAL)) {
|
|
if (JS_SetProperty(ctx, this_val, JS_ATOM_lastIndex,
|
|
js_int32(end)) < 0)
|
|
goto fail;
|
|
break;
|
|
}
|
|
if (end == start) {
|
|
if (!(re_flags & LRE_FLAG_UNICODE) || (unsigned)end >= str->len || !str->is_wide_char) {
|
|
end++;
|
|
} else {
|
|
string_getc(str, &end);
|
|
}
|
|
}
|
|
last_index = end;
|
|
}
|
|
if (string_buffer_concat(b, str, next_src_pos, str->len))
|
|
goto fail;
|
|
JS_FreeValue(ctx, str_val);
|
|
js_free(ctx, capture);
|
|
return string_buffer_end(b);
|
|
fail:
|
|
JS_FreeValue(ctx, str_val);
|
|
js_free(ctx, capture);
|
|
string_buffer_free(b);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue JS_RegExpExec(JSContext *ctx, JSValue r, JSValue s)
|
|
{
|
|
JSValue method, ret;
|
|
|
|
method = JS_GetProperty(ctx, r, JS_ATOM_exec);
|
|
if (JS_IsException(method))
|
|
return method;
|
|
if (JS_IsFunction(ctx, method)) {
|
|
ret = JS_CallFree(ctx, method, r, 1, &s);
|
|
if (JS_IsException(ret))
|
|
return ret;
|
|
if (!JS_IsObject(ret) && !JS_IsNull(ret)) {
|
|
JS_FreeValue(ctx, ret);
|
|
return JS_ThrowTypeError(ctx, "RegExp exec method must return an object or null");
|
|
}
|
|
return ret;
|
|
}
|
|
JS_FreeValue(ctx, method);
|
|
return js_regexp_exec(ctx, r, 1, &s);
|
|
}
|
|
|
|
static JSValue js_regexp_test(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue val;
|
|
BOOL ret;
|
|
|
|
val = JS_RegExpExec(ctx, this_val, argv[0]);
|
|
if (JS_IsException(val))
|
|
return JS_EXCEPTION;
|
|
ret = !JS_IsNull(val);
|
|
JS_FreeValue(ctx, val);
|
|
return js_bool(ret);
|
|
}
|
|
|
|
static JSValue js_regexp_Symbol_match(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
// [Symbol.match](str)
|
|
JSValue rx = this_val;
|
|
JSValue A, S, flags, result, matchStr;
|
|
int global, n, fullUnicode, isEmpty;
|
|
JSString *p;
|
|
|
|
if (!JS_IsObject(rx))
|
|
return JS_ThrowTypeErrorNotAnObject(ctx);
|
|
|
|
A = JS_UNDEFINED;
|
|
flags = JS_UNDEFINED;
|
|
result = JS_UNDEFINED;
|
|
matchStr = JS_UNDEFINED;
|
|
S = JS_ToString(ctx, argv[0]);
|
|
if (JS_IsException(S))
|
|
goto exception;
|
|
|
|
flags = JS_GetProperty(ctx, rx, JS_ATOM_flags);
|
|
if (JS_IsException(flags))
|
|
goto exception;
|
|
flags = JS_ToStringFree(ctx, flags);
|
|
if (JS_IsException(flags))
|
|
goto exception;
|
|
p = JS_VALUE_GET_STRING(flags);
|
|
|
|
// TODO(bnoordhuis) query 'u' flag the same way?
|
|
global = (-1 != string_indexof_char(p, 'g', 0));
|
|
if (!global) {
|
|
A = JS_RegExpExec(ctx, rx, S);
|
|
} else {
|
|
fullUnicode = JS_ToBoolFree(ctx, JS_GetProperty(ctx, rx, JS_ATOM_unicode));
|
|
if (fullUnicode < 0)
|
|
goto exception;
|
|
|
|
if (JS_SetProperty(ctx, rx, JS_ATOM_lastIndex, js_int32(0)) < 0)
|
|
goto exception;
|
|
A = JS_NewArray(ctx);
|
|
if (JS_IsException(A))
|
|
goto exception;
|
|
n = 0;
|
|
for(;;) {
|
|
JS_FreeValue(ctx, result);
|
|
result = JS_RegExpExec(ctx, rx, S);
|
|
if (JS_IsException(result))
|
|
goto exception;
|
|
if (JS_IsNull(result))
|
|
break;
|
|
matchStr = JS_ToStringFree(ctx, JS_GetPropertyInt64(ctx, result, 0));
|
|
if (JS_IsException(matchStr))
|
|
goto exception;
|
|
isEmpty = JS_IsEmptyString(matchStr);
|
|
if (JS_SetPropertyInt64(ctx, A, n++, matchStr) < 0)
|
|
goto exception;
|
|
if (isEmpty) {
|
|
int64_t thisIndex, nextIndex;
|
|
if (JS_ToLengthFree(ctx, &thisIndex,
|
|
JS_GetProperty(ctx, rx, JS_ATOM_lastIndex)) < 0)
|
|
goto exception;
|
|
p = JS_VALUE_GET_STRING(S);
|
|
nextIndex = string_advance_index(p, thisIndex, fullUnicode);
|
|
if (JS_SetProperty(ctx, rx, JS_ATOM_lastIndex, JS_NewInt64(ctx, nextIndex)) < 0)
|
|
goto exception;
|
|
}
|
|
}
|
|
if (n == 0) {
|
|
JS_FreeValue(ctx, A);
|
|
A = JS_NULL;
|
|
}
|
|
}
|
|
JS_FreeValue(ctx, result);
|
|
JS_FreeValue(ctx, flags);
|
|
JS_FreeValue(ctx, S);
|
|
return A;
|
|
|
|
exception:
|
|
JS_FreeValue(ctx, A);
|
|
JS_FreeValue(ctx, result);
|
|
JS_FreeValue(ctx, flags);
|
|
JS_FreeValue(ctx, S);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
typedef struct JSRegExpStringIteratorData {
|
|
JSValue iterating_regexp;
|
|
JSValue iterated_string;
|
|
BOOL global;
|
|
BOOL unicode;
|
|
BOOL done;
|
|
} JSRegExpStringIteratorData;
|
|
|
|
static void js_regexp_string_iterator_finalizer(JSRuntime *rt, JSValue val)
|
|
{
|
|
JSObject *p = JS_VALUE_GET_OBJ(val);
|
|
JSRegExpStringIteratorData *it = p->u.regexp_string_iterator_data;
|
|
if (it) {
|
|
JS_FreeValueRT(rt, it->iterating_regexp);
|
|
JS_FreeValueRT(rt, it->iterated_string);
|
|
js_free_rt(rt, it);
|
|
}
|
|
}
|
|
|
|
static void js_regexp_string_iterator_mark(JSRuntime *rt, JSValue val,
|
|
JS_MarkFunc *mark_func)
|
|
{
|
|
JSObject *p = JS_VALUE_GET_OBJ(val);
|
|
JSRegExpStringIteratorData *it = p->u.regexp_string_iterator_data;
|
|
if (it) {
|
|
JS_MarkValue(rt, it->iterating_regexp, mark_func);
|
|
JS_MarkValue(rt, it->iterated_string, mark_func);
|
|
}
|
|
}
|
|
|
|
static JSValue js_regexp_string_iterator_next(JSContext *ctx,
|
|
JSValue this_val,
|
|
int argc, JSValue *argv,
|
|
BOOL *pdone, int magic)
|
|
{
|
|
JSRegExpStringIteratorData *it;
|
|
JSValue R, S;
|
|
JSValue matchStr = JS_UNDEFINED, match = JS_UNDEFINED;
|
|
JSString *sp;
|
|
|
|
it = JS_GetOpaque2(ctx, this_val, JS_CLASS_REGEXP_STRING_ITERATOR);
|
|
if (!it)
|
|
goto exception;
|
|
if (it->done) {
|
|
*pdone = TRUE;
|
|
return JS_UNDEFINED;
|
|
}
|
|
R = it->iterating_regexp;
|
|
S = it->iterated_string;
|
|
match = JS_RegExpExec(ctx, R, S);
|
|
if (JS_IsException(match))
|
|
goto exception;
|
|
if (JS_IsNull(match)) {
|
|
it->done = TRUE;
|
|
*pdone = TRUE;
|
|
return JS_UNDEFINED;
|
|
} else if (it->global) {
|
|
matchStr = JS_ToStringFree(ctx, JS_GetPropertyInt64(ctx, match, 0));
|
|
if (JS_IsException(matchStr))
|
|
goto exception;
|
|
if (JS_IsEmptyString(matchStr)) {
|
|
int64_t thisIndex, nextIndex;
|
|
if (JS_ToLengthFree(ctx, &thisIndex,
|
|
JS_GetProperty(ctx, R, JS_ATOM_lastIndex)) < 0)
|
|
goto exception;
|
|
sp = JS_VALUE_GET_STRING(S);
|
|
nextIndex = string_advance_index(sp, thisIndex, it->unicode);
|
|
if (JS_SetProperty(ctx, R, JS_ATOM_lastIndex,
|
|
JS_NewInt64(ctx, nextIndex)) < 0)
|
|
goto exception;
|
|
}
|
|
JS_FreeValue(ctx, matchStr);
|
|
} else {
|
|
it->done = TRUE;
|
|
}
|
|
*pdone = FALSE;
|
|
return match;
|
|
exception:
|
|
JS_FreeValue(ctx, match);
|
|
JS_FreeValue(ctx, matchStr);
|
|
*pdone = FALSE;
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue js_regexp_Symbol_matchAll(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
// [Symbol.matchAll](str)
|
|
JSValue R = this_val;
|
|
JSValue S, C, flags, matcher, iter;
|
|
JSValue args[2];
|
|
JSString *strp;
|
|
int64_t lastIndex;
|
|
JSRegExpStringIteratorData *it;
|
|
|
|
if (!JS_IsObject(R))
|
|
return JS_ThrowTypeErrorNotAnObject(ctx);
|
|
|
|
C = JS_UNDEFINED;
|
|
flags = JS_UNDEFINED;
|
|
matcher = JS_UNDEFINED;
|
|
iter = JS_UNDEFINED;
|
|
|
|
S = JS_ToString(ctx, argv[0]);
|
|
if (JS_IsException(S))
|
|
goto exception;
|
|
C = JS_SpeciesConstructor(ctx, R, ctx->regexp_ctor);
|
|
if (JS_IsException(C))
|
|
goto exception;
|
|
flags = JS_ToStringFree(ctx, JS_GetProperty(ctx, R, JS_ATOM_flags));
|
|
if (JS_IsException(flags))
|
|
goto exception;
|
|
args[0] = R;
|
|
args[1] = flags;
|
|
matcher = JS_CallConstructor(ctx, C, 2, args);
|
|
if (JS_IsException(matcher))
|
|
goto exception;
|
|
if (JS_ToLengthFree(ctx, &lastIndex,
|
|
JS_GetProperty(ctx, R, JS_ATOM_lastIndex)))
|
|
goto exception;
|
|
if (JS_SetProperty(ctx, matcher, JS_ATOM_lastIndex,
|
|
JS_NewInt64(ctx, lastIndex)) < 0)
|
|
goto exception;
|
|
|
|
iter = JS_NewObjectClass(ctx, JS_CLASS_REGEXP_STRING_ITERATOR);
|
|
if (JS_IsException(iter))
|
|
goto exception;
|
|
it = js_malloc(ctx, sizeof(*it));
|
|
if (!it)
|
|
goto exception;
|
|
it->iterating_regexp = matcher;
|
|
it->iterated_string = S;
|
|
strp = JS_VALUE_GET_STRING(flags);
|
|
it->global = string_indexof_char(strp, 'g', 0) >= 0;
|
|
it->unicode = string_indexof_char(strp, 'u', 0) >= 0;
|
|
it->done = FALSE;
|
|
JS_SetOpaque(iter, it);
|
|
|
|
JS_FreeValue(ctx, C);
|
|
JS_FreeValue(ctx, flags);
|
|
return iter;
|
|
exception:
|
|
JS_FreeValue(ctx, S);
|
|
JS_FreeValue(ctx, C);
|
|
JS_FreeValue(ctx, flags);
|
|
JS_FreeValue(ctx, matcher);
|
|
JS_FreeValue(ctx, iter);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
typedef struct ValueBuffer {
|
|
JSContext *ctx;
|
|
JSValue *arr;
|
|
JSValue def[4];
|
|
int len;
|
|
int size;
|
|
int error_status;
|
|
} ValueBuffer;
|
|
|
|
static int value_buffer_init(JSContext *ctx, ValueBuffer *b)
|
|
{
|
|
b->ctx = ctx;
|
|
b->len = 0;
|
|
b->size = 4;
|
|
b->error_status = 0;
|
|
b->arr = b->def;
|
|
return 0;
|
|
}
|
|
|
|
static void value_buffer_free(ValueBuffer *b)
|
|
{
|
|
while (b->len > 0)
|
|
JS_FreeValue(b->ctx, b->arr[--b->len]);
|
|
if (b->arr != b->def)
|
|
js_free(b->ctx, b->arr);
|
|
b->arr = b->def;
|
|
b->size = 4;
|
|
}
|
|
|
|
static int value_buffer_append(ValueBuffer *b, JSValue val)
|
|
{
|
|
if (b->error_status)
|
|
return -1;
|
|
|
|
if (b->len >= b->size) {
|
|
int new_size = (b->len + (b->len >> 1) + 31) & ~16;
|
|
size_t slack;
|
|
JSValue *new_arr;
|
|
|
|
if (b->arr == b->def) {
|
|
new_arr = js_realloc2(b->ctx, NULL, sizeof(*b->arr) * new_size, &slack);
|
|
if (new_arr)
|
|
memcpy(new_arr, b->def, sizeof b->def);
|
|
} else {
|
|
new_arr = js_realloc2(b->ctx, b->arr, sizeof(*b->arr) * new_size, &slack);
|
|
}
|
|
if (!new_arr) {
|
|
value_buffer_free(b);
|
|
JS_FreeValue(b->ctx, val);
|
|
b->error_status = -1;
|
|
return -1;
|
|
}
|
|
new_size += slack / sizeof(*new_arr);
|
|
b->arr = new_arr;
|
|
b->size = new_size;
|
|
}
|
|
b->arr[b->len++] = val;
|
|
return 0;
|
|
}
|
|
|
|
static int js_is_standard_regexp(JSContext *ctx, JSValue rx)
|
|
{
|
|
JSValue val;
|
|
int res;
|
|
|
|
val = JS_GetProperty(ctx, rx, JS_ATOM_constructor);
|
|
if (JS_IsException(val))
|
|
return -1;
|
|
// rx.constructor === RegExp
|
|
res = js_same_value(ctx, val, ctx->regexp_ctor);
|
|
JS_FreeValue(ctx, val);
|
|
if (res) {
|
|
val = JS_GetProperty(ctx, rx, JS_ATOM_exec);
|
|
if (JS_IsException(val))
|
|
return -1;
|
|
// rx.exec === RE_exec
|
|
res = JS_IsCFunction(ctx, val, js_regexp_exec, 0);
|
|
JS_FreeValue(ctx, val);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
static JSValue js_regexp_Symbol_replace(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
// [Symbol.replace](str, rep)
|
|
JSValue rx = this_val, rep = argv[1];
|
|
JSValue args[6];
|
|
JSValue flags, str, rep_val, matched, tab, rep_str, namedCaptures, res;
|
|
JSString *p, *sp, *rp;
|
|
StringBuffer b_s, *b = &b_s;
|
|
ValueBuffer v_b, *results = &v_b;
|
|
int nextSourcePosition, n, j, functionalReplace, is_global, fullUnicode;
|
|
uint32_t nCaptures;
|
|
int64_t position;
|
|
|
|
if (!JS_IsObject(rx))
|
|
return JS_ThrowTypeErrorNotAnObject(ctx);
|
|
|
|
string_buffer_init(ctx, b, 0);
|
|
value_buffer_init(ctx, results);
|
|
|
|
rep_val = JS_UNDEFINED;
|
|
matched = JS_UNDEFINED;
|
|
tab = JS_UNDEFINED;
|
|
flags = JS_UNDEFINED;
|
|
rep_str = JS_UNDEFINED;
|
|
namedCaptures = JS_UNDEFINED;
|
|
|
|
str = JS_ToString(ctx, argv[0]);
|
|
if (JS_IsException(str))
|
|
goto exception;
|
|
|
|
sp = JS_VALUE_GET_STRING(str);
|
|
rp = NULL;
|
|
functionalReplace = JS_IsFunction(ctx, rep);
|
|
if (!functionalReplace) {
|
|
rep_val = JS_ToString(ctx, rep);
|
|
if (JS_IsException(rep_val))
|
|
goto exception;
|
|
rp = JS_VALUE_GET_STRING(rep_val);
|
|
}
|
|
|
|
flags = JS_GetProperty(ctx, rx, JS_ATOM_flags);
|
|
if (JS_IsException(flags))
|
|
goto exception;
|
|
flags = JS_ToStringFree(ctx, flags);
|
|
if (JS_IsException(flags))
|
|
goto exception;
|
|
p = JS_VALUE_GET_STRING(flags);
|
|
|
|
// TODO(bnoordhuis) query 'u' flag the same way?
|
|
fullUnicode = 0;
|
|
is_global = (-1 != string_indexof_char(p, 'g', 0));
|
|
if (is_global) {
|
|
fullUnicode = JS_ToBoolFree(ctx, JS_GetProperty(ctx, rx, JS_ATOM_unicode));
|
|
if (fullUnicode < 0)
|
|
goto exception;
|
|
if (JS_SetProperty(ctx, rx, JS_ATOM_lastIndex, js_int32(0)) < 0)
|
|
goto exception;
|
|
}
|
|
|
|
if (rp && rp->len == 0 && is_global && js_is_standard_regexp(ctx, rx)) {
|
|
/* use faster version for simple cases */
|
|
res = JS_RegExpDelete(ctx, rx, str);
|
|
goto done;
|
|
}
|
|
for(;;) {
|
|
JSValue result;
|
|
result = JS_RegExpExec(ctx, rx, str);
|
|
if (JS_IsException(result))
|
|
goto exception;
|
|
if (JS_IsNull(result))
|
|
break;
|
|
if (value_buffer_append(results, result) < 0)
|
|
goto exception;
|
|
if (!is_global)
|
|
break;
|
|
JS_FreeValue(ctx, matched);
|
|
matched = JS_ToStringFree(ctx, JS_GetPropertyInt64(ctx, result, 0));
|
|
if (JS_IsException(matched))
|
|
goto exception;
|
|
if (JS_IsEmptyString(matched)) {
|
|
/* always advance of at least one char */
|
|
int64_t thisIndex, nextIndex;
|
|
if (JS_ToLengthFree(ctx, &thisIndex, JS_GetProperty(ctx, rx, JS_ATOM_lastIndex)) < 0)
|
|
goto exception;
|
|
nextIndex = string_advance_index(sp, thisIndex, fullUnicode);
|
|
if (JS_SetProperty(ctx, rx, JS_ATOM_lastIndex, JS_NewInt64(ctx, nextIndex)) < 0)
|
|
goto exception;
|
|
}
|
|
}
|
|
nextSourcePosition = 0;
|
|
for(j = 0; j < results->len; j++) {
|
|
JSValue result;
|
|
result = results->arr[j];
|
|
if (js_get_length32(ctx, &nCaptures, result) < 0)
|
|
goto exception;
|
|
JS_FreeValue(ctx, matched);
|
|
matched = JS_ToStringFree(ctx, JS_GetPropertyInt64(ctx, result, 0));
|
|
if (JS_IsException(matched))
|
|
goto exception;
|
|
if (JS_ToLengthFree(ctx, &position, JS_GetProperty(ctx, result, JS_ATOM_index)))
|
|
goto exception;
|
|
if (position > sp->len)
|
|
position = sp->len;
|
|
else if (position < 0)
|
|
position = 0;
|
|
/* ignore substition if going backward (can happen
|
|
with custom regexp object) */
|
|
JS_FreeValue(ctx, tab);
|
|
tab = JS_NewArray(ctx);
|
|
if (JS_IsException(tab))
|
|
goto exception;
|
|
if (JS_DefinePropertyValueInt64(ctx, tab, 0, js_dup(matched),
|
|
JS_PROP_C_W_E | JS_PROP_THROW) < 0)
|
|
goto exception;
|
|
for(n = 1; n < nCaptures; n++) {
|
|
JSValue capN;
|
|
capN = JS_GetPropertyInt64(ctx, result, n);
|
|
if (JS_IsException(capN))
|
|
goto exception;
|
|
if (!JS_IsUndefined(capN)) {
|
|
capN = JS_ToStringFree(ctx, capN);
|
|
if (JS_IsException(capN))
|
|
goto exception;
|
|
}
|
|
if (JS_DefinePropertyValueInt64(ctx, tab, n, capN,
|
|
JS_PROP_C_W_E | JS_PROP_THROW) < 0)
|
|
goto exception;
|
|
}
|
|
JS_FreeValue(ctx, namedCaptures);
|
|
namedCaptures = JS_GetProperty(ctx, result, JS_ATOM_groups);
|
|
if (JS_IsException(namedCaptures))
|
|
goto exception;
|
|
if (functionalReplace) {
|
|
if (JS_DefinePropertyValueInt64(ctx, tab, n++, js_int32(position), JS_PROP_C_W_E | JS_PROP_THROW) < 0)
|
|
goto exception;
|
|
if (JS_DefinePropertyValueInt64(ctx, tab, n++, js_dup(str), JS_PROP_C_W_E | JS_PROP_THROW) < 0)
|
|
goto exception;
|
|
if (!JS_IsUndefined(namedCaptures)) {
|
|
if (JS_DefinePropertyValueInt64(ctx, tab, n++, js_dup(namedCaptures), JS_PROP_C_W_E | JS_PROP_THROW) < 0)
|
|
goto exception;
|
|
}
|
|
args[0] = JS_UNDEFINED;
|
|
args[1] = tab;
|
|
JS_FreeValue(ctx, rep_str);
|
|
rep_str = JS_ToStringFree(ctx, js_function_apply(ctx, rep, 2, args, 0));
|
|
} else {
|
|
JSValue namedCaptures1;
|
|
if (!JS_IsUndefined(namedCaptures)) {
|
|
namedCaptures1 = JS_ToObject(ctx, namedCaptures);
|
|
if (JS_IsException(namedCaptures1))
|
|
goto exception;
|
|
} else {
|
|
namedCaptures1 = JS_UNDEFINED;
|
|
}
|
|
args[0] = matched;
|
|
args[1] = str;
|
|
args[2] = js_int32(position);
|
|
args[3] = tab;
|
|
args[4] = namedCaptures1;
|
|
args[5] = rep_val;
|
|
JS_FreeValue(ctx, rep_str);
|
|
rep_str = js_string___GetSubstitution(ctx, JS_UNDEFINED, 6, args);
|
|
JS_FreeValue(ctx, namedCaptures1);
|
|
}
|
|
if (JS_IsException(rep_str))
|
|
goto exception;
|
|
if (position >= nextSourcePosition) {
|
|
string_buffer_concat(b, sp, nextSourcePosition, position);
|
|
string_buffer_concat_value(b, rep_str);
|
|
nextSourcePosition = position + JS_VALUE_GET_STRING(matched)->len;
|
|
}
|
|
}
|
|
string_buffer_concat(b, sp, nextSourcePosition, sp->len);
|
|
res = string_buffer_end(b);
|
|
goto done1;
|
|
|
|
exception:
|
|
res = JS_EXCEPTION;
|
|
done:
|
|
string_buffer_free(b);
|
|
done1:
|
|
value_buffer_free(results);
|
|
JS_FreeValue(ctx, rep_val);
|
|
JS_FreeValue(ctx, matched);
|
|
JS_FreeValue(ctx, flags);
|
|
JS_FreeValue(ctx, tab);
|
|
JS_FreeValue(ctx, rep_str);
|
|
JS_FreeValue(ctx, namedCaptures);
|
|
JS_FreeValue(ctx, str);
|
|
return res;
|
|
}
|
|
|
|
static JSValue js_regexp_Symbol_search(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue rx = this_val;
|
|
JSValue str, previousLastIndex, currentLastIndex, result, index;
|
|
|
|
if (!JS_IsObject(rx))
|
|
return JS_ThrowTypeErrorNotAnObject(ctx);
|
|
|
|
result = JS_UNDEFINED;
|
|
currentLastIndex = JS_UNDEFINED;
|
|
previousLastIndex = JS_UNDEFINED;
|
|
str = JS_ToString(ctx, argv[0]);
|
|
if (JS_IsException(str))
|
|
goto exception;
|
|
|
|
previousLastIndex = JS_GetProperty(ctx, rx, JS_ATOM_lastIndex);
|
|
if (JS_IsException(previousLastIndex))
|
|
goto exception;
|
|
|
|
if (!js_same_value(ctx, previousLastIndex, js_int32(0))) {
|
|
if (JS_SetProperty(ctx, rx, JS_ATOM_lastIndex, js_int32(0)) < 0) {
|
|
goto exception;
|
|
}
|
|
}
|
|
result = JS_RegExpExec(ctx, rx, str);
|
|
if (JS_IsException(result))
|
|
goto exception;
|
|
currentLastIndex = JS_GetProperty(ctx, rx, JS_ATOM_lastIndex);
|
|
if (JS_IsException(currentLastIndex))
|
|
goto exception;
|
|
if (js_same_value(ctx, currentLastIndex, previousLastIndex)) {
|
|
JS_FreeValue(ctx, previousLastIndex);
|
|
} else {
|
|
if (JS_SetProperty(ctx, rx, JS_ATOM_lastIndex, previousLastIndex) < 0) {
|
|
previousLastIndex = JS_UNDEFINED;
|
|
goto exception;
|
|
}
|
|
}
|
|
JS_FreeValue(ctx, str);
|
|
JS_FreeValue(ctx, currentLastIndex);
|
|
|
|
if (JS_IsNull(result)) {
|
|
return js_int32(-1);
|
|
} else {
|
|
index = JS_GetProperty(ctx, result, JS_ATOM_index);
|
|
JS_FreeValue(ctx, result);
|
|
return index;
|
|
}
|
|
|
|
exception:
|
|
JS_FreeValue(ctx, result);
|
|
JS_FreeValue(ctx, str);
|
|
JS_FreeValue(ctx, currentLastIndex);
|
|
JS_FreeValue(ctx, previousLastIndex);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue js_regexp_Symbol_split(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
// [Symbol.split](str, limit)
|
|
JSValue rx = this_val;
|
|
JSValue args[2];
|
|
JSValue str, ctor, splitter, A, flags, z, sub;
|
|
JSString *strp;
|
|
uint32_t lim, size, p, q;
|
|
int unicodeMatching;
|
|
int64_t lengthA, e, numberOfCaptures, i;
|
|
|
|
if (!JS_IsObject(rx))
|
|
return JS_ThrowTypeErrorNotAnObject(ctx);
|
|
|
|
ctor = JS_UNDEFINED;
|
|
splitter = JS_UNDEFINED;
|
|
A = JS_UNDEFINED;
|
|
flags = JS_UNDEFINED;
|
|
z = JS_UNDEFINED;
|
|
str = JS_ToString(ctx, argv[0]);
|
|
if (JS_IsException(str))
|
|
goto exception;
|
|
ctor = JS_SpeciesConstructor(ctx, rx, ctx->regexp_ctor);
|
|
if (JS_IsException(ctor))
|
|
goto exception;
|
|
flags = JS_ToStringFree(ctx, JS_GetProperty(ctx, rx, JS_ATOM_flags));
|
|
if (JS_IsException(flags))
|
|
goto exception;
|
|
strp = JS_VALUE_GET_STRING(flags);
|
|
unicodeMatching = string_indexof_char(strp, 'u', 0) >= 0;
|
|
if (string_indexof_char(strp, 'y', 0) < 0) {
|
|
flags = JS_ConcatString3(ctx, "", flags, "y");
|
|
if (JS_IsException(flags))
|
|
goto exception;
|
|
}
|
|
args[0] = rx;
|
|
args[1] = flags;
|
|
splitter = JS_CallConstructor(ctx, ctor, 2, args);
|
|
if (JS_IsException(splitter))
|
|
goto exception;
|
|
A = JS_NewArray(ctx);
|
|
if (JS_IsException(A))
|
|
goto exception;
|
|
lengthA = 0;
|
|
if (JS_IsUndefined(argv[1])) {
|
|
lim = 0xffffffff;
|
|
} else {
|
|
if (JS_ToUint32(ctx, &lim, argv[1]) < 0)
|
|
goto exception;
|
|
if (lim == 0)
|
|
goto done;
|
|
}
|
|
strp = JS_VALUE_GET_STRING(str);
|
|
p = q = 0;
|
|
size = strp->len;
|
|
if (size == 0) {
|
|
z = JS_RegExpExec(ctx, splitter, str);
|
|
if (JS_IsException(z))
|
|
goto exception;
|
|
if (JS_IsNull(z))
|
|
goto add_tail;
|
|
goto done;
|
|
}
|
|
while (q < size) {
|
|
if (JS_SetProperty(ctx, splitter, JS_ATOM_lastIndex, js_int32(q)) < 0)
|
|
goto exception;
|
|
JS_FreeValue(ctx, z);
|
|
z = JS_RegExpExec(ctx, splitter, str);
|
|
if (JS_IsException(z))
|
|
goto exception;
|
|
if (JS_IsNull(z)) {
|
|
q = string_advance_index(strp, q, unicodeMatching);
|
|
} else {
|
|
if (JS_ToLengthFree(ctx, &e, JS_GetProperty(ctx, splitter, JS_ATOM_lastIndex)))
|
|
goto exception;
|
|
if (e > size)
|
|
e = size;
|
|
if (e == p) {
|
|
q = string_advance_index(strp, q, unicodeMatching);
|
|
} else {
|
|
sub = js_sub_string(ctx, strp, p, q);
|
|
if (JS_IsException(sub))
|
|
goto exception;
|
|
if (JS_DefinePropertyValueInt64(ctx, A, lengthA++, sub,
|
|
JS_PROP_C_W_E | JS_PROP_THROW) < 0)
|
|
goto exception;
|
|
if (lengthA == lim)
|
|
goto done;
|
|
p = e;
|
|
if (js_get_length64(ctx, &numberOfCaptures, z))
|
|
goto exception;
|
|
for(i = 1; i < numberOfCaptures; i++) {
|
|
sub = JS_ToStringFree(ctx, JS_GetPropertyInt64(ctx, z, i));
|
|
if (JS_IsException(sub))
|
|
goto exception;
|
|
if (JS_DefinePropertyValueInt64(ctx, A, lengthA++, sub, JS_PROP_C_W_E | JS_PROP_THROW) < 0)
|
|
goto exception;
|
|
if (lengthA == lim)
|
|
goto done;
|
|
}
|
|
q = p;
|
|
}
|
|
}
|
|
}
|
|
add_tail:
|
|
if (p > size)
|
|
p = size;
|
|
sub = js_sub_string(ctx, strp, p, size);
|
|
if (JS_IsException(sub))
|
|
goto exception;
|
|
if (JS_DefinePropertyValueInt64(ctx, A, lengthA++, sub, JS_PROP_C_W_E | JS_PROP_THROW) < 0)
|
|
goto exception;
|
|
goto done;
|
|
exception:
|
|
JS_FreeValue(ctx, A);
|
|
A = JS_EXCEPTION;
|
|
done:
|
|
JS_FreeValue(ctx, str);
|
|
JS_FreeValue(ctx, ctor);
|
|
JS_FreeValue(ctx, splitter);
|
|
JS_FreeValue(ctx, flags);
|
|
JS_FreeValue(ctx, z);
|
|
return A;
|
|
}
|
|
|
|
static const JSCFunctionListEntry js_regexp_funcs[] = {
|
|
JS_CGETSET_DEF("[Symbol.species]", js_get_this, NULL ),
|
|
};
|
|
|
|
static const JSCFunctionListEntry js_regexp_proto_funcs[] = {
|
|
JS_CGETSET_DEF("flags", js_regexp_get_flags, NULL ),
|
|
JS_CGETSET_DEF("source", js_regexp_get_source, NULL ),
|
|
JS_CGETSET_MAGIC_DEF("global", js_regexp_get_flag, NULL, LRE_FLAG_GLOBAL ),
|
|
JS_CGETSET_MAGIC_DEF("ignoreCase", js_regexp_get_flag, NULL, LRE_FLAG_IGNORECASE ),
|
|
JS_CGETSET_MAGIC_DEF("multiline", js_regexp_get_flag, NULL, LRE_FLAG_MULTILINE ),
|
|
JS_CGETSET_MAGIC_DEF("dotAll", js_regexp_get_flag, NULL, LRE_FLAG_DOTALL ),
|
|
JS_CGETSET_MAGIC_DEF("unicode", js_regexp_get_flag, NULL, LRE_FLAG_UNICODE ),
|
|
JS_CGETSET_MAGIC_DEF("unicodeSets", js_regexp_get_flag, NULL, LRE_FLAG_UNICODE_SETS ),
|
|
JS_CGETSET_MAGIC_DEF("sticky", js_regexp_get_flag, NULL, LRE_FLAG_STICKY ),
|
|
JS_CGETSET_MAGIC_DEF("hasIndices", js_regexp_get_flag, NULL, LRE_FLAG_INDICES ),
|
|
JS_CFUNC_DEF("exec", 1, js_regexp_exec ),
|
|
JS_CFUNC_DEF("compile", 2, js_regexp_compile ),
|
|
JS_CFUNC_DEF("test", 1, js_regexp_test ),
|
|
JS_CFUNC_DEF("toString", 0, js_regexp_toString ),
|
|
JS_CFUNC_DEF("[Symbol.replace]", 2, js_regexp_Symbol_replace ),
|
|
JS_CFUNC_DEF("[Symbol.match]", 1, js_regexp_Symbol_match ),
|
|
JS_CFUNC_DEF("[Symbol.matchAll]", 1, js_regexp_Symbol_matchAll ),
|
|
JS_CFUNC_DEF("[Symbol.search]", 1, js_regexp_Symbol_search ),
|
|
JS_CFUNC_DEF("[Symbol.split]", 2, js_regexp_Symbol_split ),
|
|
};
|
|
|
|
static const JSCFunctionListEntry js_regexp_string_iterator_proto_funcs[] = {
|
|
JS_ITERATOR_NEXT_DEF("next", 0, js_regexp_string_iterator_next, 0 ),
|
|
JS_PROP_STRING_DEF("[Symbol.toStringTag]", "RegExp String Iterator", JS_PROP_CONFIGURABLE ),
|
|
};
|
|
|
|
void JS_AddIntrinsicRegExpCompiler(JSContext *ctx)
|
|
{
|
|
ctx->compile_regexp = js_compile_regexp;
|
|
}
|
|
|
|
void JS_AddIntrinsicRegExp(JSContext *ctx)
|
|
{
|
|
JSValue obj;
|
|
|
|
JS_AddIntrinsicRegExpCompiler(ctx);
|
|
|
|
ctx->class_proto[JS_CLASS_REGEXP] = JS_NewObject(ctx);
|
|
JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_REGEXP], js_regexp_proto_funcs,
|
|
countof(js_regexp_proto_funcs));
|
|
obj = JS_NewGlobalCConstructor(ctx, "RegExp", js_regexp_constructor, 2,
|
|
ctx->class_proto[JS_CLASS_REGEXP]);
|
|
ctx->regexp_ctor = js_dup(obj);
|
|
JS_SetPropertyFunctionList(ctx, obj, js_regexp_funcs, countof(js_regexp_funcs));
|
|
|
|
ctx->class_proto[JS_CLASS_REGEXP_STRING_ITERATOR] =
|
|
JS_NewObjectProto(ctx, ctx->iterator_proto);
|
|
JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_REGEXP_STRING_ITERATOR],
|
|
js_regexp_string_iterator_proto_funcs,
|
|
countof(js_regexp_string_iterator_proto_funcs));
|
|
}
|
|
|
|
/* JSON */
|
|
|
|
static JSValue json_parse_value(JSParseState *s)
|
|
{
|
|
JSContext *ctx = s->ctx;
|
|
JSValue val = JS_NULL;
|
|
int ret;
|
|
|
|
switch(s->token.val) {
|
|
case '{':
|
|
{
|
|
JSValue prop_val;
|
|
JSAtom prop_name;
|
|
|
|
if (json_next_token(s))
|
|
goto fail;
|
|
val = JS_NewObject(ctx);
|
|
if (JS_IsException(val))
|
|
goto fail;
|
|
if (s->token.val != '}') {
|
|
for(;;) {
|
|
if (s->token.val == TOK_STRING) {
|
|
prop_name = JS_ValueToAtom(ctx, s->token.u.str.str);
|
|
if (prop_name == JS_ATOM_NULL)
|
|
goto fail;
|
|
} else {
|
|
json_parse_error(s, s->token.ptr, "Expected property name or '}'");
|
|
goto fail;
|
|
}
|
|
if (json_next_token(s))
|
|
goto fail1;
|
|
if (s->token.val != ':') {
|
|
json_parse_error(s, s->token.ptr, "Expected ':' after property name");
|
|
goto fail1;
|
|
}
|
|
if (json_next_token(s))
|
|
goto fail1;
|
|
prop_val = json_parse_value(s);
|
|
if (JS_IsException(prop_val)) {
|
|
fail1:
|
|
JS_FreeAtom(ctx, prop_name);
|
|
goto fail;
|
|
}
|
|
ret = JS_DefinePropertyValue(ctx, val, prop_name,
|
|
prop_val, JS_PROP_C_W_E);
|
|
JS_FreeAtom(ctx, prop_name);
|
|
if (ret < 0)
|
|
goto fail;
|
|
|
|
if (s->token.val == '}')
|
|
break;
|
|
if (s->token.val != ',') {
|
|
json_parse_error(s, s->token.ptr, "Expected ',' or '}' after property value");
|
|
goto fail;
|
|
}
|
|
if (json_next_token(s))
|
|
goto fail;
|
|
}
|
|
}
|
|
if (json_next_token(s))
|
|
goto fail;
|
|
}
|
|
break;
|
|
case '[':
|
|
{
|
|
JSValue el;
|
|
uint32_t idx;
|
|
|
|
if (json_next_token(s))
|
|
goto fail;
|
|
val = JS_NewArray(ctx);
|
|
if (JS_IsException(val))
|
|
goto fail;
|
|
if (s->token.val != ']') {
|
|
for(idx = 0;; idx++) {
|
|
el = json_parse_value(s);
|
|
if (JS_IsException(el))
|
|
goto fail;
|
|
ret = JS_DefinePropertyValueUint32(ctx, val, idx, el, JS_PROP_C_W_E);
|
|
if (ret < 0)
|
|
goto fail;
|
|
if (s->token.val == ']')
|
|
break;
|
|
if (s->token.val != ',') {
|
|
json_parse_error(s, s->token.ptr, "Expected ',' or ']' after array element");
|
|
goto fail;
|
|
}
|
|
if (json_next_token(s))
|
|
goto fail;
|
|
}
|
|
}
|
|
if (json_next_token(s))
|
|
goto fail;
|
|
}
|
|
break;
|
|
case TOK_STRING:
|
|
val = js_dup(s->token.u.str.str);
|
|
if (json_next_token(s))
|
|
goto fail;
|
|
break;
|
|
case TOK_NUMBER:
|
|
val = s->token.u.num.val;
|
|
if (json_next_token(s))
|
|
goto fail;
|
|
break;
|
|
case TOK_IDENT:
|
|
if (s->token.u.ident.atom == JS_ATOM_false ||
|
|
s->token.u.ident.atom == JS_ATOM_true) {
|
|
val = js_bool(s->token.u.ident.atom == JS_ATOM_true);
|
|
} else if (s->token.u.ident.atom == JS_ATOM_null) {
|
|
val = JS_NULL;
|
|
} else {
|
|
goto def_token;
|
|
}
|
|
if (json_next_token(s))
|
|
goto fail;
|
|
break;
|
|
default:
|
|
def_token:
|
|
if (s->token.val == TOK_EOF) {
|
|
js_parse_error(s, "Unexpected end of JSON input");
|
|
} else {
|
|
js_parse_error(s, "unexpected token: '%.*s'",
|
|
(int)(s->buf_ptr - s->token.ptr), s->token.ptr);
|
|
}
|
|
goto fail;
|
|
}
|
|
return val;
|
|
fail:
|
|
JS_FreeValue(ctx, val);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
/* 'buf' must be zero terminated i.e. buf[buf_len] = '\0'. */
|
|
JSValue JS_ParseJSON(JSContext *ctx, const char *buf, size_t buf_len, const char *filename)
|
|
{
|
|
JSParseState s1, *s = &s1;
|
|
JSValue val = JS_UNDEFINED;
|
|
|
|
js_parse_init(ctx, s, buf, buf_len, filename);
|
|
if (json_next_token(s))
|
|
goto fail;
|
|
val = json_parse_value(s);
|
|
if (JS_IsException(val))
|
|
goto fail;
|
|
if (s->token.val != TOK_EOF) {
|
|
if (js_parse_error(s, "unexpected data at the end"))
|
|
goto fail;
|
|
}
|
|
return val;
|
|
fail:
|
|
JS_FreeValue(ctx, val);
|
|
free_token(s, &s->token);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue internalize_json_property(JSContext *ctx, JSValue holder,
|
|
JSAtom name, JSValue reviver)
|
|
{
|
|
JSValue val, new_el, name_val, res;
|
|
JSValue args[2];
|
|
int ret, is_array;
|
|
uint32_t i, len = 0;
|
|
JSAtom prop;
|
|
JSPropertyEnum *atoms = NULL;
|
|
|
|
if (js_check_stack_overflow(ctx->rt, 0)) {
|
|
return JS_ThrowStackOverflow(ctx);
|
|
}
|
|
|
|
val = JS_GetProperty(ctx, holder, name);
|
|
if (JS_IsException(val))
|
|
return val;
|
|
if (JS_IsObject(val)) {
|
|
is_array = JS_IsArray(ctx, val);
|
|
if (is_array < 0)
|
|
goto fail;
|
|
if (is_array) {
|
|
if (js_get_length32(ctx, &len, val))
|
|
goto fail;
|
|
} else {
|
|
ret = JS_GetOwnPropertyNamesInternal(ctx, &atoms, &len, JS_VALUE_GET_OBJ(val), JS_GPN_ENUM_ONLY | JS_GPN_STRING_MASK);
|
|
if (ret < 0)
|
|
goto fail;
|
|
}
|
|
for(i = 0; i < len; i++) {
|
|
if (is_array) {
|
|
prop = JS_NewAtomUInt32(ctx, i);
|
|
if (prop == JS_ATOM_NULL)
|
|
goto fail;
|
|
} else {
|
|
prop = JS_DupAtom(ctx, atoms[i].atom);
|
|
}
|
|
new_el = internalize_json_property(ctx, val, prop, reviver);
|
|
if (JS_IsException(new_el)) {
|
|
JS_FreeAtom(ctx, prop);
|
|
goto fail;
|
|
}
|
|
if (JS_IsUndefined(new_el)) {
|
|
ret = JS_DeleteProperty(ctx, val, prop, 0);
|
|
} else {
|
|
ret = JS_DefinePropertyValue(ctx, val, prop, new_el, JS_PROP_C_W_E);
|
|
}
|
|
JS_FreeAtom(ctx, prop);
|
|
if (ret < 0)
|
|
goto fail;
|
|
}
|
|
}
|
|
js_free_prop_enum(ctx, atoms, len);
|
|
atoms = NULL;
|
|
name_val = JS_AtomToValue(ctx, name);
|
|
if (JS_IsException(name_val))
|
|
goto fail;
|
|
args[0] = name_val;
|
|
args[1] = val;
|
|
res = JS_Call(ctx, reviver, holder, 2, args);
|
|
JS_FreeValue(ctx, name_val);
|
|
JS_FreeValue(ctx, val);
|
|
return res;
|
|
fail:
|
|
js_free_prop_enum(ctx, atoms, len);
|
|
JS_FreeValue(ctx, val);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue js_json_parse(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue obj, root;
|
|
JSValue reviver;
|
|
const char *str;
|
|
size_t len;
|
|
|
|
str = JS_ToCStringLen(ctx, &len, argv[0]);
|
|
if (!str)
|
|
return JS_EXCEPTION;
|
|
obj = JS_ParseJSON(ctx, str, len, "<input>");
|
|
JS_FreeCString(ctx, str);
|
|
if (JS_IsException(obj))
|
|
return obj;
|
|
if (argc > 1 && JS_IsFunction(ctx, argv[1])) {
|
|
reviver = argv[1];
|
|
root = JS_NewObject(ctx);
|
|
if (JS_IsException(root)) {
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
if (JS_DefinePropertyValue(ctx, root, JS_ATOM_empty_string, obj,
|
|
JS_PROP_C_W_E) < 0) {
|
|
JS_FreeValue(ctx, root);
|
|
return JS_EXCEPTION;
|
|
}
|
|
obj = internalize_json_property(ctx, root, JS_ATOM_empty_string,
|
|
reviver);
|
|
JS_FreeValue(ctx, root);
|
|
}
|
|
return obj;
|
|
}
|
|
|
|
typedef struct JSONStringifyContext {
|
|
JSValue replacer_func;
|
|
JSValue stack;
|
|
JSValue property_list;
|
|
JSValue gap;
|
|
JSValue empty;
|
|
StringBuffer *b;
|
|
} JSONStringifyContext;
|
|
|
|
static JSValue JS_ToQuotedStringFree(JSContext *ctx, JSValue val) {
|
|
JSValue r = JS_ToQuotedString(ctx, val);
|
|
JS_FreeValue(ctx, val);
|
|
return r;
|
|
}
|
|
|
|
static JSValue js_json_check(JSContext *ctx, JSONStringifyContext *jsc,
|
|
JSValue holder, JSValue val, JSValue key)
|
|
{
|
|
JSValue v;
|
|
JSValue args[2];
|
|
|
|
if (JS_IsObject(val) || JS_IsBigInt(ctx, val)) {
|
|
JSValue f = JS_GetProperty(ctx, val, JS_ATOM_toJSON);
|
|
if (JS_IsException(f))
|
|
goto exception;
|
|
if (JS_IsFunction(ctx, f)) {
|
|
v = JS_CallFree(ctx, f, val, 1, &key);
|
|
JS_FreeValue(ctx, val);
|
|
val = v;
|
|
if (JS_IsException(val))
|
|
goto exception;
|
|
} else {
|
|
JS_FreeValue(ctx, f);
|
|
}
|
|
}
|
|
|
|
if (!JS_IsUndefined(jsc->replacer_func)) {
|
|
args[0] = key;
|
|
args[1] = val;
|
|
v = JS_Call(ctx, jsc->replacer_func, holder, 2, args);
|
|
JS_FreeValue(ctx, val);
|
|
val = v;
|
|
if (JS_IsException(val))
|
|
goto exception;
|
|
}
|
|
|
|
switch (JS_VALUE_GET_NORM_TAG(val)) {
|
|
case JS_TAG_OBJECT:
|
|
if (JS_IsFunction(ctx, val))
|
|
break;
|
|
case JS_TAG_STRING:
|
|
case JS_TAG_INT:
|
|
case JS_TAG_FLOAT64:
|
|
case JS_TAG_BOOL:
|
|
case JS_TAG_NULL:
|
|
case JS_TAG_BIG_INT:
|
|
case JS_TAG_EXCEPTION:
|
|
return val;
|
|
default:
|
|
break;
|
|
}
|
|
JS_FreeValue(ctx, val);
|
|
return JS_UNDEFINED;
|
|
|
|
exception:
|
|
JS_FreeValue(ctx, val);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static int js_json_to_str(JSContext *ctx, JSONStringifyContext *jsc,
|
|
JSValue holder, JSValue val,
|
|
JSValue indent)
|
|
{
|
|
JSValue indent1, sep, sep1, tab, v, prop;
|
|
JSObject *p;
|
|
int64_t i, len;
|
|
int cl, ret;
|
|
BOOL has_content;
|
|
|
|
indent1 = JS_UNDEFINED;
|
|
sep = JS_UNDEFINED;
|
|
sep1 = JS_UNDEFINED;
|
|
tab = JS_UNDEFINED;
|
|
prop = JS_UNDEFINED;
|
|
|
|
if (JS_IsObject(val)) {
|
|
p = JS_VALUE_GET_OBJ(val);
|
|
cl = p->class_id;
|
|
if (cl == JS_CLASS_STRING) {
|
|
val = JS_ToStringFree(ctx, val);
|
|
if (JS_IsException(val))
|
|
goto exception;
|
|
goto concat_primitive;
|
|
} else if (cl == JS_CLASS_NUMBER) {
|
|
val = JS_ToNumberFree(ctx, val);
|
|
if (JS_IsException(val))
|
|
goto exception;
|
|
goto concat_primitive;
|
|
} else if (cl == JS_CLASS_BOOLEAN || cl == JS_CLASS_BIG_INT) {
|
|
set_value(ctx, &val, js_dup(p->u.object_data));
|
|
goto concat_primitive;
|
|
}
|
|
v = js_array_includes(ctx, jsc->stack, 1, &val);
|
|
if (JS_IsException(v))
|
|
goto exception;
|
|
if (JS_ToBoolFree(ctx, v)) {
|
|
JS_ThrowTypeError(ctx, "circular reference");
|
|
goto exception;
|
|
}
|
|
indent1 = JS_ConcatString(ctx, js_dup(indent), js_dup(jsc->gap));
|
|
if (JS_IsException(indent1))
|
|
goto exception;
|
|
if (!JS_IsEmptyString(jsc->gap)) {
|
|
sep = JS_ConcatString3(ctx, "\n", js_dup(indent1), "");
|
|
if (JS_IsException(sep))
|
|
goto exception;
|
|
sep1 = js_new_string8(ctx, " ");
|
|
if (JS_IsException(sep1))
|
|
goto exception;
|
|
} else {
|
|
sep = js_dup(jsc->empty);
|
|
sep1 = js_dup(jsc->empty);
|
|
}
|
|
v = js_array_push(ctx, jsc->stack, 1, &val, 0);
|
|
if (check_exception_free(ctx, v))
|
|
goto exception;
|
|
ret = JS_IsArray(ctx, val);
|
|
if (ret < 0)
|
|
goto exception;
|
|
if (ret) {
|
|
if (js_get_length64(ctx, &len, val))
|
|
goto exception;
|
|
string_buffer_putc8(jsc->b, '[');
|
|
for(i = 0; i < len; i++) {
|
|
if (i > 0)
|
|
string_buffer_putc8(jsc->b, ',');
|
|
string_buffer_concat_value(jsc->b, sep);
|
|
v = JS_GetPropertyInt64(ctx, val, i);
|
|
if (JS_IsException(v))
|
|
goto exception;
|
|
/* XXX: could do this string conversion only when needed */
|
|
prop = JS_ToStringFree(ctx, JS_NewInt64(ctx, i));
|
|
if (JS_IsException(prop))
|
|
goto exception;
|
|
v = js_json_check(ctx, jsc, val, v, prop);
|
|
JS_FreeValue(ctx, prop);
|
|
prop = JS_UNDEFINED;
|
|
if (JS_IsException(v))
|
|
goto exception;
|
|
if (JS_IsUndefined(v))
|
|
v = JS_NULL;
|
|
if (js_json_to_str(ctx, jsc, val, v, indent1))
|
|
goto exception;
|
|
}
|
|
if (len > 0 && !JS_IsEmptyString(jsc->gap)) {
|
|
string_buffer_putc8(jsc->b, '\n');
|
|
string_buffer_concat_value(jsc->b, indent);
|
|
}
|
|
string_buffer_putc8(jsc->b, ']');
|
|
} else {
|
|
if (!JS_IsUndefined(jsc->property_list))
|
|
tab = js_dup(jsc->property_list);
|
|
else
|
|
tab = js_object_keys(ctx, JS_UNDEFINED, 1, &val, JS_ITERATOR_KIND_KEY);
|
|
if (JS_IsException(tab))
|
|
goto exception;
|
|
if (js_get_length64(ctx, &len, tab))
|
|
goto exception;
|
|
string_buffer_putc8(jsc->b, '{');
|
|
has_content = FALSE;
|
|
for(i = 0; i < len; i++) {
|
|
JS_FreeValue(ctx, prop);
|
|
prop = JS_GetPropertyInt64(ctx, tab, i);
|
|
if (JS_IsException(prop))
|
|
goto exception;
|
|
v = JS_GetPropertyValue(ctx, val, js_dup(prop));
|
|
if (JS_IsException(v))
|
|
goto exception;
|
|
v = js_json_check(ctx, jsc, val, v, prop);
|
|
if (JS_IsException(v))
|
|
goto exception;
|
|
if (!JS_IsUndefined(v)) {
|
|
if (has_content)
|
|
string_buffer_putc8(jsc->b, ',');
|
|
prop = JS_ToQuotedStringFree(ctx, prop);
|
|
if (JS_IsException(prop)) {
|
|
JS_FreeValue(ctx, v);
|
|
goto exception;
|
|
}
|
|
string_buffer_concat_value(jsc->b, sep);
|
|
string_buffer_concat_value(jsc->b, prop);
|
|
string_buffer_putc8(jsc->b, ':');
|
|
string_buffer_concat_value(jsc->b, sep1);
|
|
if (js_json_to_str(ctx, jsc, val, v, indent1))
|
|
goto exception;
|
|
has_content = TRUE;
|
|
}
|
|
}
|
|
if (has_content && JS_VALUE_GET_STRING(jsc->gap)->len != 0) {
|
|
string_buffer_putc8(jsc->b, '\n');
|
|
string_buffer_concat_value(jsc->b, indent);
|
|
}
|
|
string_buffer_putc8(jsc->b, '}');
|
|
}
|
|
if (check_exception_free(ctx, js_array_pop(ctx, jsc->stack, 0, NULL, 0)))
|
|
goto exception;
|
|
JS_FreeValue(ctx, val);
|
|
JS_FreeValue(ctx, tab);
|
|
JS_FreeValue(ctx, sep);
|
|
JS_FreeValue(ctx, sep1);
|
|
JS_FreeValue(ctx, indent1);
|
|
JS_FreeValue(ctx, prop);
|
|
return 0;
|
|
}
|
|
concat_primitive:
|
|
switch (JS_VALUE_GET_NORM_TAG(val)) {
|
|
case JS_TAG_STRING:
|
|
val = JS_ToQuotedStringFree(ctx, val);
|
|
if (JS_IsException(val))
|
|
goto exception;
|
|
goto concat_value;
|
|
case JS_TAG_FLOAT64:
|
|
if (!isfinite(JS_VALUE_GET_FLOAT64(val))) {
|
|
val = JS_NULL;
|
|
}
|
|
goto concat_value;
|
|
case JS_TAG_INT:
|
|
case JS_TAG_BOOL:
|
|
case JS_TAG_NULL:
|
|
concat_value:
|
|
return string_buffer_concat_value_free(jsc->b, val);
|
|
case JS_TAG_BIG_INT:
|
|
JS_ThrowTypeError(ctx, "BigInt are forbidden in JSON.stringify");
|
|
goto exception;
|
|
default:
|
|
JS_FreeValue(ctx, val);
|
|
return 0;
|
|
}
|
|
|
|
exception:
|
|
JS_FreeValue(ctx, val);
|
|
JS_FreeValue(ctx, tab);
|
|
JS_FreeValue(ctx, sep);
|
|
JS_FreeValue(ctx, sep1);
|
|
JS_FreeValue(ctx, indent1);
|
|
JS_FreeValue(ctx, prop);
|
|
return -1;
|
|
}
|
|
|
|
JSValue JS_JSONStringify(JSContext *ctx, JSValue obj,
|
|
JSValue replacer, JSValue space0)
|
|
{
|
|
StringBuffer b_s;
|
|
JSONStringifyContext jsc_s, *jsc = &jsc_s;
|
|
JSValue val, v, space, ret, wrapper;
|
|
int res;
|
|
int64_t i, j, n;
|
|
|
|
jsc->replacer_func = JS_UNDEFINED;
|
|
jsc->stack = JS_UNDEFINED;
|
|
jsc->property_list = JS_UNDEFINED;
|
|
jsc->gap = JS_UNDEFINED;
|
|
jsc->b = &b_s;
|
|
jsc->empty = JS_AtomToString(ctx, JS_ATOM_empty_string);
|
|
ret = JS_UNDEFINED;
|
|
wrapper = JS_UNDEFINED;
|
|
|
|
string_buffer_init(ctx, jsc->b, 0);
|
|
jsc->stack = JS_NewArray(ctx);
|
|
if (JS_IsException(jsc->stack))
|
|
goto exception;
|
|
if (JS_IsFunction(ctx, replacer)) {
|
|
jsc->replacer_func = replacer;
|
|
} else {
|
|
res = JS_IsArray(ctx, replacer);
|
|
if (res < 0)
|
|
goto exception;
|
|
if (res) {
|
|
/* XXX: enumeration is not fully correct */
|
|
jsc->property_list = JS_NewArray(ctx);
|
|
if (JS_IsException(jsc->property_list))
|
|
goto exception;
|
|
if (js_get_length64(ctx, &n, replacer))
|
|
goto exception;
|
|
for (i = j = 0; i < n; i++) {
|
|
JSValue present;
|
|
v = JS_GetPropertyInt64(ctx, replacer, i);
|
|
if (JS_IsException(v))
|
|
goto exception;
|
|
if (JS_IsObject(v)) {
|
|
JSObject *p = JS_VALUE_GET_OBJ(v);
|
|
if (p->class_id == JS_CLASS_STRING ||
|
|
p->class_id == JS_CLASS_NUMBER) {
|
|
v = JS_ToStringFree(ctx, v);
|
|
if (JS_IsException(v))
|
|
goto exception;
|
|
} else {
|
|
JS_FreeValue(ctx, v);
|
|
continue;
|
|
}
|
|
} else if (JS_IsNumber(v)) {
|
|
v = JS_ToStringFree(ctx, v);
|
|
if (JS_IsException(v))
|
|
goto exception;
|
|
} else if (!JS_IsString(v)) {
|
|
JS_FreeValue(ctx, v);
|
|
continue;
|
|
}
|
|
present = js_array_includes(ctx, jsc->property_list,
|
|
1, &v);
|
|
if (JS_IsException(present)) {
|
|
JS_FreeValue(ctx, v);
|
|
goto exception;
|
|
}
|
|
if (!JS_ToBoolFree(ctx, present)) {
|
|
JS_SetPropertyInt64(ctx, jsc->property_list, j++, v);
|
|
} else {
|
|
JS_FreeValue(ctx, v);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
space = js_dup(space0);
|
|
if (JS_IsObject(space)) {
|
|
JSObject *p = JS_VALUE_GET_OBJ(space);
|
|
if (p->class_id == JS_CLASS_NUMBER) {
|
|
space = JS_ToNumberFree(ctx, space);
|
|
} else if (p->class_id == JS_CLASS_STRING) {
|
|
space = JS_ToStringFree(ctx, space);
|
|
}
|
|
if (JS_IsException(space)) {
|
|
JS_FreeValue(ctx, space);
|
|
goto exception;
|
|
}
|
|
}
|
|
if (JS_IsNumber(space)) {
|
|
int n;
|
|
if (JS_ToInt32Clamp(ctx, &n, space, 0, 10, 0))
|
|
goto exception;
|
|
jsc->gap = JS_NewStringLen(ctx, " ", n);
|
|
} else if (JS_IsString(space)) {
|
|
JSString *p = JS_VALUE_GET_STRING(space);
|
|
jsc->gap = js_sub_string(ctx, p, 0, min_int(p->len, 10));
|
|
} else {
|
|
jsc->gap = js_dup(jsc->empty);
|
|
}
|
|
JS_FreeValue(ctx, space);
|
|
if (JS_IsException(jsc->gap))
|
|
goto exception;
|
|
wrapper = JS_NewObject(ctx);
|
|
if (JS_IsException(wrapper))
|
|
goto exception;
|
|
if (JS_DefinePropertyValue(ctx, wrapper, JS_ATOM_empty_string,
|
|
js_dup(obj), JS_PROP_C_W_E) < 0)
|
|
goto exception;
|
|
val = js_dup(obj);
|
|
|
|
val = js_json_check(ctx, jsc, wrapper, val, jsc->empty);
|
|
if (JS_IsException(val))
|
|
goto exception;
|
|
if (JS_IsUndefined(val)) {
|
|
ret = JS_UNDEFINED;
|
|
goto done1;
|
|
}
|
|
if (js_json_to_str(ctx, jsc, wrapper, val, jsc->empty))
|
|
goto exception;
|
|
|
|
ret = string_buffer_end(jsc->b);
|
|
goto done;
|
|
|
|
exception:
|
|
ret = JS_EXCEPTION;
|
|
done1:
|
|
string_buffer_free(jsc->b);
|
|
done:
|
|
JS_FreeValue(ctx, wrapper);
|
|
JS_FreeValue(ctx, jsc->empty);
|
|
JS_FreeValue(ctx, jsc->gap);
|
|
JS_FreeValue(ctx, jsc->property_list);
|
|
JS_FreeValue(ctx, jsc->stack);
|
|
return ret;
|
|
}
|
|
|
|
static JSValue js_json_stringify(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
// stringify(val, replacer, space)
|
|
return JS_JSONStringify(ctx, argv[0], argv[1], argv[2]);
|
|
}
|
|
|
|
static const JSCFunctionListEntry js_json_funcs[] = {
|
|
JS_CFUNC_DEF("parse", 2, js_json_parse ),
|
|
JS_CFUNC_DEF("stringify", 3, js_json_stringify ),
|
|
JS_PROP_STRING_DEF("[Symbol.toStringTag]", "JSON", JS_PROP_CONFIGURABLE ),
|
|
};
|
|
|
|
static const JSCFunctionListEntry js_json_obj[] = {
|
|
JS_OBJECT_DEF("JSON", js_json_funcs, countof(js_json_funcs), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE ),
|
|
};
|
|
|
|
void JS_AddIntrinsicJSON(JSContext *ctx)
|
|
{
|
|
/* add JSON as autoinit object */
|
|
JS_SetPropertyFunctionList(ctx, ctx->global_obj, js_json_obj, countof(js_json_obj));
|
|
}
|
|
|
|
/* Reflect */
|
|
|
|
static JSValue js_reflect_apply(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
return js_function_apply(ctx, argv[0], max_int(0, argc - 1), argv + 1, 2);
|
|
}
|
|
|
|
static JSValue js_reflect_construct(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue func, array_arg, new_target;
|
|
JSValue *tab, ret;
|
|
uint32_t len;
|
|
|
|
func = argv[0];
|
|
array_arg = argv[1];
|
|
if (argc > 2) {
|
|
new_target = argv[2];
|
|
if (!JS_IsConstructor(ctx, new_target))
|
|
return JS_ThrowTypeError(ctx, "not a constructor");
|
|
} else {
|
|
new_target = func;
|
|
}
|
|
tab = build_arg_list(ctx, &len, array_arg);
|
|
if (!tab)
|
|
return JS_EXCEPTION;
|
|
ret = JS_CallConstructor2(ctx, func, new_target, len, tab);
|
|
free_arg_list(ctx, tab, len);
|
|
return ret;
|
|
}
|
|
|
|
static JSValue js_reflect_deleteProperty(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue obj;
|
|
JSAtom atom;
|
|
int ret;
|
|
|
|
obj = argv[0];
|
|
if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)
|
|
return JS_ThrowTypeErrorNotAnObject(ctx);
|
|
atom = JS_ValueToAtom(ctx, argv[1]);
|
|
if (unlikely(atom == JS_ATOM_NULL))
|
|
return JS_EXCEPTION;
|
|
ret = JS_DeleteProperty(ctx, obj, atom, 0);
|
|
JS_FreeAtom(ctx, atom);
|
|
if (ret < 0)
|
|
return JS_EXCEPTION;
|
|
else
|
|
return js_bool(ret);
|
|
}
|
|
|
|
static JSValue js_reflect_get(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue obj, prop, receiver;
|
|
JSAtom atom;
|
|
JSValue ret;
|
|
|
|
obj = argv[0];
|
|
prop = argv[1];
|
|
if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)
|
|
return JS_ThrowTypeErrorNotAnObject(ctx);
|
|
if (argc > 2)
|
|
receiver = argv[2];
|
|
else
|
|
receiver = obj;
|
|
atom = JS_ValueToAtom(ctx, prop);
|
|
if (unlikely(atom == JS_ATOM_NULL))
|
|
return JS_EXCEPTION;
|
|
ret = JS_GetPropertyInternal(ctx, obj, atom, receiver, FALSE);
|
|
JS_FreeAtom(ctx, atom);
|
|
return ret;
|
|
}
|
|
|
|
static JSValue js_reflect_has(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue obj, prop;
|
|
JSAtom atom;
|
|
int ret;
|
|
|
|
obj = argv[0];
|
|
prop = argv[1];
|
|
if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)
|
|
return JS_ThrowTypeErrorNotAnObject(ctx);
|
|
atom = JS_ValueToAtom(ctx, prop);
|
|
if (unlikely(atom == JS_ATOM_NULL))
|
|
return JS_EXCEPTION;
|
|
ret = JS_HasProperty(ctx, obj, atom);
|
|
JS_FreeAtom(ctx, atom);
|
|
if (ret < 0)
|
|
return JS_EXCEPTION;
|
|
else
|
|
return js_bool(ret);
|
|
}
|
|
|
|
static JSValue js_reflect_set(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue obj, prop, val, receiver;
|
|
int ret;
|
|
JSAtom atom;
|
|
|
|
obj = argv[0];
|
|
prop = argv[1];
|
|
val = argv[2];
|
|
if (argc > 3)
|
|
receiver = argv[3];
|
|
else
|
|
receiver = obj;
|
|
if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)
|
|
return JS_ThrowTypeErrorNotAnObject(ctx);
|
|
atom = JS_ValueToAtom(ctx, prop);
|
|
if (unlikely(atom == JS_ATOM_NULL))
|
|
return JS_EXCEPTION;
|
|
ret = JS_SetPropertyInternal2(ctx, obj, atom, js_dup(val), receiver,
|
|
0, NULL);
|
|
JS_FreeAtom(ctx, atom);
|
|
if (ret < 0)
|
|
return JS_EXCEPTION;
|
|
else
|
|
return js_bool(ret);
|
|
}
|
|
|
|
static JSValue js_reflect_setPrototypeOf(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
int ret;
|
|
ret = JS_SetPrototypeInternal(ctx, argv[0], argv[1], FALSE);
|
|
if (ret < 0)
|
|
return JS_EXCEPTION;
|
|
else
|
|
return js_bool(ret);
|
|
}
|
|
|
|
static JSValue js_reflect_ownKeys(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
if (JS_VALUE_GET_TAG(argv[0]) != JS_TAG_OBJECT)
|
|
return JS_ThrowTypeErrorNotAnObject(ctx);
|
|
return JS_GetOwnPropertyNames2(ctx, argv[0],
|
|
JS_GPN_STRING_MASK | JS_GPN_SYMBOL_MASK,
|
|
JS_ITERATOR_KIND_KEY);
|
|
}
|
|
|
|
static const JSCFunctionListEntry js_reflect_funcs[] = {
|
|
JS_CFUNC_DEF("apply", 3, js_reflect_apply ),
|
|
JS_CFUNC_DEF("construct", 2, js_reflect_construct ),
|
|
JS_CFUNC_MAGIC_DEF("defineProperty", 3, js_object_defineProperty, 1 ),
|
|
JS_CFUNC_DEF("deleteProperty", 2, js_reflect_deleteProperty ),
|
|
JS_CFUNC_DEF("get", 2, js_reflect_get ),
|
|
JS_CFUNC_MAGIC_DEF("getOwnPropertyDescriptor", 2, js_object_getOwnPropertyDescriptor, 1 ),
|
|
JS_CFUNC_MAGIC_DEF("getPrototypeOf", 1, js_object_getPrototypeOf, 1 ),
|
|
JS_CFUNC_DEF("has", 2, js_reflect_has ),
|
|
JS_CFUNC_MAGIC_DEF("isExtensible", 1, js_object_isExtensible, 1 ),
|
|
JS_CFUNC_DEF("ownKeys", 1, js_reflect_ownKeys ),
|
|
JS_CFUNC_MAGIC_DEF("preventExtensions", 1, js_object_preventExtensions, 1 ),
|
|
JS_CFUNC_DEF("set", 3, js_reflect_set ),
|
|
JS_CFUNC_DEF("setPrototypeOf", 2, js_reflect_setPrototypeOf ),
|
|
JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Reflect", JS_PROP_CONFIGURABLE ),
|
|
};
|
|
|
|
static const JSCFunctionListEntry js_reflect_obj[] = {
|
|
JS_OBJECT_DEF("Reflect", js_reflect_funcs, countof(js_reflect_funcs), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE ),
|
|
};
|
|
|
|
/* Proxy */
|
|
|
|
static void js_proxy_finalizer(JSRuntime *rt, JSValue val)
|
|
{
|
|
JSProxyData *s = JS_GetOpaque(val, JS_CLASS_PROXY);
|
|
if (s) {
|
|
JS_FreeValueRT(rt, s->target);
|
|
JS_FreeValueRT(rt, s->handler);
|
|
js_free_rt(rt, s);
|
|
}
|
|
}
|
|
|
|
static void js_proxy_mark(JSRuntime *rt, JSValue val,
|
|
JS_MarkFunc *mark_func)
|
|
{
|
|
JSProxyData *s = JS_GetOpaque(val, JS_CLASS_PROXY);
|
|
if (s) {
|
|
JS_MarkValue(rt, s->target, mark_func);
|
|
JS_MarkValue(rt, s->handler, mark_func);
|
|
}
|
|
}
|
|
|
|
static JSValue JS_ThrowTypeErrorRevokedProxy(JSContext *ctx)
|
|
{
|
|
return JS_ThrowTypeError(ctx, "revoked proxy");
|
|
}
|
|
|
|
static JSProxyData *get_proxy_method(JSContext *ctx, JSValue *pmethod,
|
|
JSValue obj, JSAtom name)
|
|
{
|
|
JSProxyData *s = JS_GetOpaque(obj, JS_CLASS_PROXY);
|
|
JSValue method;
|
|
|
|
/* safer to test recursion in all proxy methods */
|
|
if (js_check_stack_overflow(ctx->rt, 0)) {
|
|
JS_ThrowStackOverflow(ctx);
|
|
return NULL;
|
|
}
|
|
|
|
/* 's' should never be NULL */
|
|
if (s->is_revoked) {
|
|
JS_ThrowTypeErrorRevokedProxy(ctx);
|
|
return NULL;
|
|
}
|
|
method = JS_GetProperty(ctx, s->handler, name);
|
|
if (JS_IsException(method))
|
|
return NULL;
|
|
if (JS_IsNull(method))
|
|
method = JS_UNDEFINED;
|
|
*pmethod = method;
|
|
return s;
|
|
}
|
|
|
|
static JSValue js_proxy_getPrototypeOf(JSContext *ctx, JSValue obj)
|
|
{
|
|
JSProxyData *s;
|
|
JSValue method, ret, proto1;
|
|
int res;
|
|
|
|
s = get_proxy_method(ctx, &method, obj, JS_ATOM_getPrototypeOf);
|
|
if (!s)
|
|
return JS_EXCEPTION;
|
|
if (JS_IsUndefined(method))
|
|
return JS_GetPrototype(ctx, s->target);
|
|
ret = JS_CallFree(ctx, method, s->handler, 1, &s->target);
|
|
if (JS_IsException(ret))
|
|
return ret;
|
|
if (JS_VALUE_GET_TAG(ret) != JS_TAG_NULL &&
|
|
JS_VALUE_GET_TAG(ret) != JS_TAG_OBJECT) {
|
|
goto fail;
|
|
}
|
|
res = JS_IsExtensible(ctx, s->target);
|
|
if (res < 0) {
|
|
JS_FreeValue(ctx, ret);
|
|
return JS_EXCEPTION;
|
|
}
|
|
if (!res) {
|
|
/* check invariant */
|
|
proto1 = JS_GetPrototype(ctx, s->target);
|
|
if (JS_IsException(proto1)) {
|
|
JS_FreeValue(ctx, ret);
|
|
return JS_EXCEPTION;
|
|
}
|
|
if (JS_VALUE_GET_OBJ(proto1) != JS_VALUE_GET_OBJ(ret)) {
|
|
JS_FreeValue(ctx, proto1);
|
|
fail:
|
|
JS_FreeValue(ctx, ret);
|
|
return JS_ThrowTypeError(ctx, "proxy: inconsistent prototype");
|
|
}
|
|
JS_FreeValue(ctx, proto1);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int js_proxy_setPrototypeOf(JSContext *ctx, JSValue obj,
|
|
JSValue proto_val, BOOL throw_flag)
|
|
{
|
|
JSProxyData *s;
|
|
JSValue method, ret, proto1;
|
|
JSValue args[2];
|
|
BOOL res;
|
|
int res2;
|
|
|
|
s = get_proxy_method(ctx, &method, obj, JS_ATOM_setPrototypeOf);
|
|
if (!s)
|
|
return -1;
|
|
if (JS_IsUndefined(method))
|
|
return JS_SetPrototypeInternal(ctx, s->target, proto_val, throw_flag);
|
|
args[0] = s->target;
|
|
args[1] = proto_val;
|
|
ret = JS_CallFree(ctx, method, s->handler, 2, args);
|
|
if (JS_IsException(ret))
|
|
return -1;
|
|
res = JS_ToBoolFree(ctx, ret);
|
|
if (!res) {
|
|
if (throw_flag) {
|
|
JS_ThrowTypeError(ctx, "proxy: bad prototype");
|
|
return -1;
|
|
} else {
|
|
return FALSE;
|
|
}
|
|
}
|
|
res2 = JS_IsExtensible(ctx, s->target);
|
|
if (res2 < 0)
|
|
return -1;
|
|
if (!res2) {
|
|
proto1 = JS_GetPrototype(ctx, s->target);
|
|
if (JS_IsException(proto1))
|
|
return -1;
|
|
if (JS_VALUE_GET_OBJ(proto_val) != JS_VALUE_GET_OBJ(proto1)) {
|
|
JS_FreeValue(ctx, proto1);
|
|
JS_ThrowTypeError(ctx, "proxy: inconsistent prototype");
|
|
return -1;
|
|
}
|
|
JS_FreeValue(ctx, proto1);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static int js_proxy_isExtensible(JSContext *ctx, JSValue obj)
|
|
{
|
|
JSProxyData *s;
|
|
JSValue method, ret;
|
|
BOOL res;
|
|
int res2;
|
|
|
|
s = get_proxy_method(ctx, &method, obj, JS_ATOM_isExtensible);
|
|
if (!s)
|
|
return -1;
|
|
if (JS_IsUndefined(method))
|
|
return JS_IsExtensible(ctx, s->target);
|
|
ret = JS_CallFree(ctx, method, s->handler, 1, &s->target);
|
|
if (JS_IsException(ret))
|
|
return -1;
|
|
res = JS_ToBoolFree(ctx, ret);
|
|
res2 = JS_IsExtensible(ctx, s->target);
|
|
if (res2 < 0)
|
|
return res2;
|
|
if (res != res2) {
|
|
JS_ThrowTypeError(ctx, "proxy: inconsistent isExtensible");
|
|
return -1;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
static int js_proxy_preventExtensions(JSContext *ctx, JSValue obj)
|
|
{
|
|
JSProxyData *s;
|
|
JSValue method, ret;
|
|
BOOL res;
|
|
int res2;
|
|
|
|
s = get_proxy_method(ctx, &method, obj, JS_ATOM_preventExtensions);
|
|
if (!s)
|
|
return -1;
|
|
if (JS_IsUndefined(method))
|
|
return JS_PreventExtensions(ctx, s->target);
|
|
ret = JS_CallFree(ctx, method, s->handler, 1, &s->target);
|
|
if (JS_IsException(ret))
|
|
return -1;
|
|
res = JS_ToBoolFree(ctx, ret);
|
|
if (res) {
|
|
res2 = JS_IsExtensible(ctx, s->target);
|
|
if (res2 < 0)
|
|
return res2;
|
|
if (res2) {
|
|
JS_ThrowTypeError(ctx, "proxy: inconsistent preventExtensions");
|
|
return -1;
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
static int js_proxy_has(JSContext *ctx, JSValue obj, JSAtom atom)
|
|
{
|
|
JSProxyData *s;
|
|
JSValue method, ret1, atom_val;
|
|
int ret, res;
|
|
JSObject *p;
|
|
JSValue args[2];
|
|
BOOL res2;
|
|
|
|
s = get_proxy_method(ctx, &method, obj, JS_ATOM_has);
|
|
if (!s)
|
|
return -1;
|
|
if (JS_IsUndefined(method))
|
|
return JS_HasProperty(ctx, s->target, atom);
|
|
atom_val = JS_AtomToValue(ctx, atom);
|
|
if (JS_IsException(atom_val)) {
|
|
JS_FreeValue(ctx, method);
|
|
return -1;
|
|
}
|
|
args[0] = s->target;
|
|
args[1] = atom_val;
|
|
ret1 = JS_CallFree(ctx, method, s->handler, 2, args);
|
|
JS_FreeValue(ctx, atom_val);
|
|
if (JS_IsException(ret1))
|
|
return -1;
|
|
ret = JS_ToBoolFree(ctx, ret1);
|
|
if (!ret) {
|
|
JSPropertyDescriptor desc;
|
|
p = JS_VALUE_GET_OBJ(s->target);
|
|
res = JS_GetOwnPropertyInternal(ctx, &desc, p, atom);
|
|
if (res < 0)
|
|
return -1;
|
|
if (res) {
|
|
res2 = !(desc.flags & JS_PROP_CONFIGURABLE);
|
|
js_free_desc(ctx, &desc);
|
|
if (res2 || !p->extensible) {
|
|
JS_ThrowTypeError(ctx, "proxy: inconsistent has");
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static JSValue js_proxy_get(JSContext *ctx, JSValue obj, JSAtom atom,
|
|
JSValue receiver)
|
|
{
|
|
JSProxyData *s;
|
|
JSValue method, ret, atom_val;
|
|
int res;
|
|
JSValue args[3];
|
|
JSPropertyDescriptor desc;
|
|
|
|
s = get_proxy_method(ctx, &method, obj, JS_ATOM_get);
|
|
if (!s)
|
|
return JS_EXCEPTION;
|
|
/* Note: recursion is possible thru the prototype of s->target */
|
|
if (JS_IsUndefined(method))
|
|
return JS_GetPropertyInternal(ctx, s->target, atom, receiver, FALSE);
|
|
atom_val = JS_AtomToValue(ctx, atom);
|
|
if (JS_IsException(atom_val)) {
|
|
JS_FreeValue(ctx, method);
|
|
return JS_EXCEPTION;
|
|
}
|
|
args[0] = s->target;
|
|
args[1] = atom_val;
|
|
args[2] = receiver;
|
|
ret = JS_CallFree(ctx, method, s->handler, 3, args);
|
|
JS_FreeValue(ctx, atom_val);
|
|
if (JS_IsException(ret))
|
|
return JS_EXCEPTION;
|
|
res = JS_GetOwnPropertyInternal(ctx, &desc, JS_VALUE_GET_OBJ(s->target), atom);
|
|
if (res < 0)
|
|
return JS_EXCEPTION;
|
|
if (res) {
|
|
if ((desc.flags & (JS_PROP_GETSET | JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE)) == 0) {
|
|
if (!js_same_value(ctx, desc.value, ret)) {
|
|
goto fail;
|
|
}
|
|
} else if ((desc.flags & (JS_PROP_GETSET | JS_PROP_CONFIGURABLE)) == JS_PROP_GETSET) {
|
|
if (JS_IsUndefined(desc.getter) && !JS_IsUndefined(ret)) {
|
|
fail:
|
|
js_free_desc(ctx, &desc);
|
|
JS_FreeValue(ctx, ret);
|
|
return JS_ThrowTypeError(ctx, "proxy: inconsistent get");
|
|
}
|
|
}
|
|
js_free_desc(ctx, &desc);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int js_proxy_set(JSContext *ctx, JSValue obj, JSAtom atom,
|
|
JSValue value, JSValue receiver, int flags)
|
|
{
|
|
JSProxyData *s;
|
|
JSValue method, ret1, atom_val;
|
|
int ret, res;
|
|
JSValue args[4];
|
|
|
|
s = get_proxy_method(ctx, &method, obj, JS_ATOM_set);
|
|
if (!s)
|
|
return -1;
|
|
if (JS_IsUndefined(method)) {
|
|
return JS_SetPropertyInternal2(ctx, s->target, atom,
|
|
js_dup(value), receiver,
|
|
flags, NULL);
|
|
}
|
|
atom_val = JS_AtomToValue(ctx, atom);
|
|
if (JS_IsException(atom_val)) {
|
|
JS_FreeValue(ctx, method);
|
|
return -1;
|
|
}
|
|
args[0] = s->target;
|
|
args[1] = atom_val;
|
|
args[2] = value;
|
|
args[3] = receiver;
|
|
ret1 = JS_CallFree(ctx, method, s->handler, 4, args);
|
|
JS_FreeValue(ctx, atom_val);
|
|
if (JS_IsException(ret1))
|
|
return -1;
|
|
ret = JS_ToBoolFree(ctx, ret1);
|
|
if (ret) {
|
|
JSPropertyDescriptor desc;
|
|
res = JS_GetOwnPropertyInternal(ctx, &desc, JS_VALUE_GET_OBJ(s->target), atom);
|
|
if (res < 0)
|
|
return -1;
|
|
if (res) {
|
|
if ((desc.flags & (JS_PROP_GETSET | JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE)) == 0) {
|
|
if (!js_same_value(ctx, desc.value, value)) {
|
|
goto fail;
|
|
}
|
|
} else if ((desc.flags & (JS_PROP_GETSET | JS_PROP_CONFIGURABLE)) == JS_PROP_GETSET && JS_IsUndefined(desc.setter)) {
|
|
fail:
|
|
js_free_desc(ctx, &desc);
|
|
JS_ThrowTypeError(ctx, "proxy: inconsistent set");
|
|
return -1;
|
|
}
|
|
js_free_desc(ctx, &desc);
|
|
}
|
|
} else {
|
|
if ((flags & JS_PROP_THROW) ||
|
|
((flags & JS_PROP_THROW_STRICT) && is_strict_mode(ctx))) {
|
|
JS_ThrowTypeError(ctx, "proxy: cannot set property");
|
|
return -1;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static JSValue js_create_desc(JSContext *ctx, JSValue val,
|
|
JSValue getter, JSValue setter,
|
|
int flags)
|
|
{
|
|
JSValue ret;
|
|
ret = JS_NewObject(ctx);
|
|
if (JS_IsException(ret))
|
|
return ret;
|
|
if (flags & JS_PROP_HAS_GET) {
|
|
JS_DefinePropertyValue(ctx, ret, JS_ATOM_get, js_dup(getter),
|
|
JS_PROP_C_W_E);
|
|
}
|
|
if (flags & JS_PROP_HAS_SET) {
|
|
JS_DefinePropertyValue(ctx, ret, JS_ATOM_set, js_dup(setter),
|
|
JS_PROP_C_W_E);
|
|
}
|
|
if (flags & JS_PROP_HAS_VALUE) {
|
|
JS_DefinePropertyValue(ctx, ret, JS_ATOM_value, js_dup(val),
|
|
JS_PROP_C_W_E);
|
|
}
|
|
if (flags & JS_PROP_HAS_WRITABLE) {
|
|
JS_DefinePropertyValue(ctx, ret, JS_ATOM_writable,
|
|
js_bool(flags & JS_PROP_WRITABLE),
|
|
JS_PROP_C_W_E);
|
|
}
|
|
if (flags & JS_PROP_HAS_ENUMERABLE) {
|
|
JS_DefinePropertyValue(ctx, ret, JS_ATOM_enumerable,
|
|
js_bool(flags & JS_PROP_ENUMERABLE),
|
|
JS_PROP_C_W_E);
|
|
}
|
|
if (flags & JS_PROP_HAS_CONFIGURABLE) {
|
|
JS_DefinePropertyValue(ctx, ret, JS_ATOM_configurable,
|
|
js_bool(flags & JS_PROP_CONFIGURABLE),
|
|
JS_PROP_C_W_E);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int js_proxy_get_own_property(JSContext *ctx, JSPropertyDescriptor *pdesc,
|
|
JSValue obj, JSAtom prop)
|
|
{
|
|
JSProxyData *s;
|
|
JSValue method, trap_result_obj, prop_val;
|
|
int res, target_desc_ret, ret;
|
|
JSObject *p;
|
|
JSValue args[2];
|
|
JSPropertyDescriptor result_desc, target_desc;
|
|
|
|
s = get_proxy_method(ctx, &method, obj, JS_ATOM_getOwnPropertyDescriptor);
|
|
if (!s)
|
|
return -1;
|
|
p = JS_VALUE_GET_OBJ(s->target);
|
|
if (JS_IsUndefined(method)) {
|
|
return JS_GetOwnPropertyInternal(ctx, pdesc, p, prop);
|
|
}
|
|
prop_val = JS_AtomToValue(ctx, prop);
|
|
if (JS_IsException(prop_val)) {
|
|
JS_FreeValue(ctx, method);
|
|
return -1;
|
|
}
|
|
args[0] = s->target;
|
|
args[1] = prop_val;
|
|
trap_result_obj = JS_CallFree(ctx, method, s->handler, 2, args);
|
|
JS_FreeValue(ctx, prop_val);
|
|
if (JS_IsException(trap_result_obj))
|
|
return -1;
|
|
if (!JS_IsObject(trap_result_obj) && !JS_IsUndefined(trap_result_obj)) {
|
|
JS_FreeValue(ctx, trap_result_obj);
|
|
goto fail;
|
|
}
|
|
target_desc_ret = JS_GetOwnPropertyInternal(ctx, &target_desc, p, prop);
|
|
if (target_desc_ret < 0) {
|
|
JS_FreeValue(ctx, trap_result_obj);
|
|
return -1;
|
|
}
|
|
if (target_desc_ret)
|
|
js_free_desc(ctx, &target_desc);
|
|
if (JS_IsUndefined(trap_result_obj)) {
|
|
if (target_desc_ret) {
|
|
if (!(target_desc.flags & JS_PROP_CONFIGURABLE) || !p->extensible)
|
|
goto fail;
|
|
}
|
|
ret = FALSE;
|
|
} else {
|
|
int flags1, extensible_target;
|
|
extensible_target = JS_IsExtensible(ctx, s->target);
|
|
if (extensible_target < 0) {
|
|
JS_FreeValue(ctx, trap_result_obj);
|
|
return -1;
|
|
}
|
|
res = js_obj_to_desc(ctx, &result_desc, trap_result_obj);
|
|
JS_FreeValue(ctx, trap_result_obj);
|
|
if (res < 0)
|
|
return -1;
|
|
|
|
if (target_desc_ret) {
|
|
/* convert result_desc.flags to defineProperty flags */
|
|
flags1 = result_desc.flags | JS_PROP_HAS_CONFIGURABLE | JS_PROP_HAS_ENUMERABLE;
|
|
if (result_desc.flags & JS_PROP_GETSET)
|
|
flags1 |= JS_PROP_HAS_GET | JS_PROP_HAS_SET;
|
|
else
|
|
flags1 |= JS_PROP_HAS_VALUE | JS_PROP_HAS_WRITABLE;
|
|
/* XXX: not complete check: need to compare value &
|
|
getter/setter as in defineproperty */
|
|
if (!check_define_prop_flags(target_desc.flags, flags1))
|
|
goto fail1;
|
|
} else {
|
|
if (!extensible_target)
|
|
goto fail1;
|
|
}
|
|
if (!(result_desc.flags & JS_PROP_CONFIGURABLE)) {
|
|
if (!target_desc_ret || (target_desc.flags & JS_PROP_CONFIGURABLE))
|
|
goto fail1;
|
|
if ((result_desc.flags &
|
|
(JS_PROP_GETSET | JS_PROP_WRITABLE)) == 0 &&
|
|
target_desc_ret &&
|
|
(target_desc.flags & JS_PROP_WRITABLE) != 0) {
|
|
/* proxy-missing-checks */
|
|
fail1:
|
|
js_free_desc(ctx, &result_desc);
|
|
fail:
|
|
JS_ThrowTypeError(ctx, "proxy: inconsistent getOwnPropertyDescriptor");
|
|
return -1;
|
|
}
|
|
}
|
|
ret = TRUE;
|
|
if (pdesc) {
|
|
*pdesc = result_desc;
|
|
} else {
|
|
js_free_desc(ctx, &result_desc);
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int js_proxy_define_own_property(JSContext *ctx, JSValue obj,
|
|
JSAtom prop, JSValue val,
|
|
JSValue getter, JSValue setter,
|
|
int flags)
|
|
{
|
|
JSProxyData *s;
|
|
JSValue method, ret1, prop_val, desc_val;
|
|
int res, ret;
|
|
JSObject *p;
|
|
JSValue args[3];
|
|
JSPropertyDescriptor desc;
|
|
BOOL setting_not_configurable;
|
|
|
|
s = get_proxy_method(ctx, &method, obj, JS_ATOM_defineProperty);
|
|
if (!s)
|
|
return -1;
|
|
if (JS_IsUndefined(method)) {
|
|
return JS_DefineProperty(ctx, s->target, prop, val, getter, setter, flags);
|
|
}
|
|
prop_val = JS_AtomToValue(ctx, prop);
|
|
if (JS_IsException(prop_val)) {
|
|
JS_FreeValue(ctx, method);
|
|
return -1;
|
|
}
|
|
desc_val = js_create_desc(ctx, val, getter, setter, flags);
|
|
if (JS_IsException(desc_val)) {
|
|
JS_FreeValue(ctx, prop_val);
|
|
JS_FreeValue(ctx, method);
|
|
return -1;
|
|
}
|
|
args[0] = s->target;
|
|
args[1] = prop_val;
|
|
args[2] = desc_val;
|
|
ret1 = JS_CallFree(ctx, method, s->handler, 3, args);
|
|
JS_FreeValue(ctx, prop_val);
|
|
JS_FreeValue(ctx, desc_val);
|
|
if (JS_IsException(ret1))
|
|
return -1;
|
|
ret = JS_ToBoolFree(ctx, ret1);
|
|
if (!ret) {
|
|
if (flags & JS_PROP_THROW) {
|
|
JS_ThrowTypeError(ctx, "proxy: defineProperty exception");
|
|
return -1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
p = JS_VALUE_GET_OBJ(s->target);
|
|
res = JS_GetOwnPropertyInternal(ctx, &desc, p, prop);
|
|
if (res < 0)
|
|
return -1;
|
|
setting_not_configurable = ((flags & (JS_PROP_HAS_CONFIGURABLE |
|
|
JS_PROP_CONFIGURABLE)) ==
|
|
JS_PROP_HAS_CONFIGURABLE);
|
|
if (!res) {
|
|
if (!p->extensible || setting_not_configurable)
|
|
goto fail;
|
|
} else {
|
|
if (!check_define_prop_flags(desc.flags, flags) ||
|
|
((desc.flags & JS_PROP_CONFIGURABLE) && setting_not_configurable)) {
|
|
goto fail1;
|
|
}
|
|
if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) {
|
|
if ((desc.flags & (JS_PROP_GETSET | JS_PROP_CONFIGURABLE)) ==
|
|
JS_PROP_GETSET) {
|
|
if ((flags & JS_PROP_HAS_GET) &&
|
|
!js_same_value(ctx, getter, desc.getter)) {
|
|
goto fail1;
|
|
}
|
|
if ((flags & JS_PROP_HAS_SET) &&
|
|
!js_same_value(ctx, setter, desc.setter)) {
|
|
goto fail1;
|
|
}
|
|
}
|
|
} else if (flags & JS_PROP_HAS_VALUE) {
|
|
if ((desc.flags & (JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE)) ==
|
|
JS_PROP_WRITABLE && !(flags & JS_PROP_WRITABLE)) {
|
|
/* missing-proxy-check feature */
|
|
goto fail1;
|
|
} else if ((desc.flags & (JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE)) == 0 &&
|
|
!js_same_value(ctx, val, desc.value)) {
|
|
goto fail1;
|
|
}
|
|
}
|
|
if (flags & JS_PROP_HAS_WRITABLE) {
|
|
if ((desc.flags & (JS_PROP_GETSET | JS_PROP_CONFIGURABLE |
|
|
JS_PROP_WRITABLE)) == JS_PROP_WRITABLE) {
|
|
/* proxy-missing-checks */
|
|
fail1:
|
|
js_free_desc(ctx, &desc);
|
|
fail:
|
|
JS_ThrowTypeError(ctx, "proxy: inconsistent defineProperty");
|
|
return -1;
|
|
}
|
|
}
|
|
js_free_desc(ctx, &desc);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int js_proxy_delete_property(JSContext *ctx, JSValue obj,
|
|
JSAtom atom)
|
|
{
|
|
JSProxyData *s;
|
|
JSValue method, ret, atom_val;
|
|
int res, res2, is_extensible;
|
|
JSValue args[2];
|
|
|
|
s = get_proxy_method(ctx, &method, obj, JS_ATOM_deleteProperty);
|
|
if (!s)
|
|
return -1;
|
|
if (JS_IsUndefined(method)) {
|
|
return JS_DeleteProperty(ctx, s->target, atom, 0);
|
|
}
|
|
atom_val = JS_AtomToValue(ctx, atom);;
|
|
if (JS_IsException(atom_val)) {
|
|
JS_FreeValue(ctx, method);
|
|
return -1;
|
|
}
|
|
args[0] = s->target;
|
|
args[1] = atom_val;
|
|
ret = JS_CallFree(ctx, method, s->handler, 2, args);
|
|
JS_FreeValue(ctx, atom_val);
|
|
if (JS_IsException(ret))
|
|
return -1;
|
|
res = JS_ToBoolFree(ctx, ret);
|
|
if (res) {
|
|
JSPropertyDescriptor desc;
|
|
res2 = JS_GetOwnPropertyInternal(ctx, &desc, JS_VALUE_GET_OBJ(s->target), atom);
|
|
if (res2 < 0)
|
|
return -1;
|
|
if (res2) {
|
|
if (!(desc.flags & JS_PROP_CONFIGURABLE))
|
|
goto fail;
|
|
is_extensible = JS_IsExtensible(ctx, s->target);
|
|
if (is_extensible < 0)
|
|
goto fail1;
|
|
if (!is_extensible) {
|
|
/* proxy-missing-checks */
|
|
fail:
|
|
JS_ThrowTypeError(ctx, "proxy: inconsistent deleteProperty");
|
|
fail1:
|
|
js_free_desc(ctx, &desc);
|
|
return -1;
|
|
}
|
|
js_free_desc(ctx, &desc);
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
/* return the index of the property or -1 if not found */
|
|
static int find_prop_key(const JSPropertyEnum *tab, int n, JSAtom atom)
|
|
{
|
|
int i;
|
|
for(i = 0; i < n; i++) {
|
|
if (tab[i].atom == atom)
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static int js_proxy_get_own_property_names(JSContext *ctx,
|
|
JSPropertyEnum **ptab,
|
|
uint32_t *plen,
|
|
JSValue obj)
|
|
{
|
|
JSProxyData *s;
|
|
JSValue method, prop_array, val;
|
|
uint32_t len, i, len2;
|
|
JSPropertyEnum *tab, *tab2;
|
|
JSAtom atom;
|
|
JSPropertyDescriptor desc;
|
|
int res, is_extensible, idx;
|
|
|
|
s = get_proxy_method(ctx, &method, obj, JS_ATOM_ownKeys);
|
|
if (!s)
|
|
return -1;
|
|
if (JS_IsUndefined(method)) {
|
|
return JS_GetOwnPropertyNamesInternal(ctx, ptab, plen,
|
|
JS_VALUE_GET_OBJ(s->target),
|
|
JS_GPN_STRING_MASK | JS_GPN_SYMBOL_MASK);
|
|
}
|
|
prop_array = JS_CallFree(ctx, method, s->handler, 1, &s->target);
|
|
if (JS_IsException(prop_array))
|
|
return -1;
|
|
tab = NULL;
|
|
len = 0;
|
|
tab2 = NULL;
|
|
len2 = 0;
|
|
if (js_get_length32(ctx, &len, prop_array))
|
|
goto fail;
|
|
if (len > 0) {
|
|
tab = js_mallocz(ctx, sizeof(tab[0]) * len);
|
|
if (!tab)
|
|
goto fail;
|
|
}
|
|
for(i = 0; i < len; i++) {
|
|
val = JS_GetPropertyUint32(ctx, prop_array, i);
|
|
if (JS_IsException(val))
|
|
goto fail;
|
|
if (!JS_IsString(val) && !JS_IsSymbol(val)) {
|
|
JS_FreeValue(ctx, val);
|
|
JS_ThrowTypeError(ctx, "proxy: properties must be strings or symbols");
|
|
goto fail;
|
|
}
|
|
atom = JS_ValueToAtom(ctx, val);
|
|
JS_FreeValue(ctx, val);
|
|
if (atom == JS_ATOM_NULL)
|
|
goto fail;
|
|
tab[i].atom = atom;
|
|
tab[i].is_enumerable = FALSE; /* XXX: redundant? */
|
|
}
|
|
|
|
/* check duplicate properties (XXX: inefficient, could store the
|
|
* properties an a temporary object to use the hash) */
|
|
for(i = 1; i < len; i++) {
|
|
if (find_prop_key(tab, i, tab[i].atom) >= 0) {
|
|
JS_ThrowTypeError(ctx, "proxy: duplicate property");
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
is_extensible = JS_IsExtensible(ctx, s->target);
|
|
if (is_extensible < 0)
|
|
goto fail;
|
|
|
|
/* check if there are non configurable properties */
|
|
if (s->is_revoked) {
|
|
JS_ThrowTypeErrorRevokedProxy(ctx);
|
|
goto fail;
|
|
}
|
|
if (JS_GetOwnPropertyNamesInternal(ctx, &tab2, &len2, JS_VALUE_GET_OBJ(s->target),
|
|
JS_GPN_STRING_MASK | JS_GPN_SYMBOL_MASK))
|
|
goto fail;
|
|
for(i = 0; i < len2; i++) {
|
|
if (s->is_revoked) {
|
|
JS_ThrowTypeErrorRevokedProxy(ctx);
|
|
goto fail;
|
|
}
|
|
res = JS_GetOwnPropertyInternal(ctx, &desc, JS_VALUE_GET_OBJ(s->target),
|
|
tab2[i].atom);
|
|
if (res < 0)
|
|
goto fail;
|
|
if (res) { /* safety, property should be found */
|
|
js_free_desc(ctx, &desc);
|
|
if (!(desc.flags & JS_PROP_CONFIGURABLE) || !is_extensible) {
|
|
idx = find_prop_key(tab, len, tab2[i].atom);
|
|
if (idx < 0) {
|
|
JS_ThrowTypeError(ctx, "proxy: target property must be present in proxy ownKeys");
|
|
goto fail;
|
|
}
|
|
/* mark the property as found */
|
|
if (!is_extensible)
|
|
tab[idx].is_enumerable = TRUE;
|
|
}
|
|
}
|
|
}
|
|
if (!is_extensible) {
|
|
/* check that all property in 'tab' were checked */
|
|
for(i = 0; i < len; i++) {
|
|
if (!tab[i].is_enumerable) {
|
|
JS_ThrowTypeError(ctx, "proxy: property not present in target were returned by non extensible proxy");
|
|
goto fail;
|
|
}
|
|
}
|
|
}
|
|
|
|
js_free_prop_enum(ctx, tab2, len2);
|
|
JS_FreeValue(ctx, prop_array);
|
|
*ptab = tab;
|
|
*plen = len;
|
|
return 0;
|
|
fail:
|
|
js_free_prop_enum(ctx, tab2, len2);
|
|
js_free_prop_enum(ctx, tab, len);
|
|
JS_FreeValue(ctx, prop_array);
|
|
return -1;
|
|
}
|
|
|
|
static JSValue js_proxy_call_constructor(JSContext *ctx, JSValue func_obj,
|
|
JSValue new_target,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSProxyData *s;
|
|
JSValue method, arg_array, ret;
|
|
JSValue args[3];
|
|
|
|
s = get_proxy_method(ctx, &method, func_obj, JS_ATOM_construct);
|
|
if (!s)
|
|
return JS_EXCEPTION;
|
|
if (!JS_IsConstructor(ctx, s->target))
|
|
return JS_ThrowTypeError(ctx, "not a constructor");
|
|
if (JS_IsUndefined(method))
|
|
return JS_CallConstructor2(ctx, s->target, new_target, argc, argv);
|
|
arg_array = js_create_array(ctx, argc, argv);
|
|
if (JS_IsException(arg_array)) {
|
|
ret = JS_EXCEPTION;
|
|
goto fail;
|
|
}
|
|
args[0] = s->target;
|
|
args[1] = arg_array;
|
|
args[2] = new_target;
|
|
ret = JS_Call(ctx, method, s->handler, 3, args);
|
|
if (!JS_IsException(ret) && JS_VALUE_GET_TAG(ret) != JS_TAG_OBJECT) {
|
|
JS_FreeValue(ctx, ret);
|
|
ret = JS_ThrowTypeErrorNotAnObject(ctx);
|
|
}
|
|
fail:
|
|
JS_FreeValue(ctx, method);
|
|
JS_FreeValue(ctx, arg_array);
|
|
return ret;
|
|
}
|
|
|
|
static JSValue js_proxy_call(JSContext *ctx, JSValue func_obj,
|
|
JSValue this_obj,
|
|
int argc, JSValue *argv, int flags)
|
|
{
|
|
JSProxyData *s;
|
|
JSValue method, arg_array, ret;
|
|
JSValue args[3];
|
|
|
|
if (flags & JS_CALL_FLAG_CONSTRUCTOR)
|
|
return js_proxy_call_constructor(ctx, func_obj, this_obj, argc, argv);
|
|
|
|
s = get_proxy_method(ctx, &method, func_obj, JS_ATOM_apply);
|
|
if (!s)
|
|
return JS_EXCEPTION;
|
|
if (!s->is_func) {
|
|
JS_FreeValue(ctx, method);
|
|
return JS_ThrowTypeError(ctx, "not a function");
|
|
}
|
|
if (JS_IsUndefined(method))
|
|
return JS_Call(ctx, s->target, this_obj, argc, argv);
|
|
arg_array = js_create_array(ctx, argc, argv);
|
|
if (JS_IsException(arg_array)) {
|
|
ret = JS_EXCEPTION;
|
|
goto fail;
|
|
}
|
|
args[0] = s->target;
|
|
args[1] = this_obj;
|
|
args[2] = arg_array;
|
|
ret = JS_Call(ctx, method, s->handler, 3, args);
|
|
fail:
|
|
JS_FreeValue(ctx, method);
|
|
JS_FreeValue(ctx, arg_array);
|
|
return ret;
|
|
}
|
|
|
|
static int js_proxy_isArray(JSContext *ctx, JSValue obj)
|
|
{
|
|
JSProxyData *s = JS_GetOpaque(obj, JS_CLASS_PROXY);
|
|
if (!s)
|
|
return FALSE;
|
|
|
|
if (js_check_stack_overflow(ctx->rt, 0)) {
|
|
JS_ThrowStackOverflow(ctx);
|
|
return -1;
|
|
}
|
|
|
|
if (s->is_revoked) {
|
|
JS_ThrowTypeErrorRevokedProxy(ctx);
|
|
return -1;
|
|
}
|
|
return JS_IsArray(ctx, s->target);
|
|
}
|
|
|
|
static const JSClassExoticMethods js_proxy_exotic_methods = {
|
|
.get_own_property = js_proxy_get_own_property,
|
|
.define_own_property = js_proxy_define_own_property,
|
|
.delete_property = js_proxy_delete_property,
|
|
.get_own_property_names = js_proxy_get_own_property_names,
|
|
.has_property = js_proxy_has,
|
|
.get_property = js_proxy_get,
|
|
.set_property = js_proxy_set,
|
|
};
|
|
|
|
static JSValue js_proxy_constructor(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue target, handler;
|
|
JSValue obj;
|
|
JSProxyData *s;
|
|
|
|
target = argv[0];
|
|
handler = argv[1];
|
|
if (JS_VALUE_GET_TAG(target) != JS_TAG_OBJECT ||
|
|
JS_VALUE_GET_TAG(handler) != JS_TAG_OBJECT)
|
|
return JS_ThrowTypeErrorNotAnObject(ctx);
|
|
|
|
obj = JS_NewObjectProtoClass(ctx, JS_NULL, JS_CLASS_PROXY);
|
|
if (JS_IsException(obj))
|
|
return obj;
|
|
s = js_malloc(ctx, sizeof(JSProxyData));
|
|
if (!s) {
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
s->target = js_dup(target);
|
|
s->handler = js_dup(handler);
|
|
s->is_func = JS_IsFunction(ctx, target);
|
|
s->is_revoked = FALSE;
|
|
JS_SetOpaque(obj, s);
|
|
JS_SetConstructorBit(ctx, obj, JS_IsConstructor(ctx, target));
|
|
return obj;
|
|
}
|
|
|
|
static JSValue js_proxy_revoke(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv, int magic,
|
|
JSValue *func_data)
|
|
{
|
|
JSProxyData *s = JS_GetOpaque(func_data[0], JS_CLASS_PROXY);
|
|
if (s) {
|
|
/* We do not free the handler and target in case they are
|
|
referenced as constants in the C call stack */
|
|
s->is_revoked = TRUE;
|
|
JS_FreeValue(ctx, func_data[0]);
|
|
func_data[0] = JS_NULL;
|
|
}
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
static JSValue js_proxy_revoke_constructor(JSContext *ctx,
|
|
JSValue proxy_obj)
|
|
{
|
|
return JS_NewCFunctionData(ctx, js_proxy_revoke, 0, 0, 1, &proxy_obj);
|
|
}
|
|
|
|
static JSValue js_proxy_revocable(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue proxy_obj, revoke_obj = JS_UNDEFINED, obj;
|
|
|
|
proxy_obj = js_proxy_constructor(ctx, JS_UNDEFINED, argc, argv);
|
|
if (JS_IsException(proxy_obj))
|
|
goto fail;
|
|
revoke_obj = js_proxy_revoke_constructor(ctx, proxy_obj);
|
|
if (JS_IsException(revoke_obj))
|
|
goto fail;
|
|
obj = JS_NewObject(ctx);
|
|
if (JS_IsException(obj))
|
|
goto fail;
|
|
// XXX: exceptions?
|
|
JS_DefinePropertyValue(ctx, obj, JS_ATOM_proxy, proxy_obj, JS_PROP_C_W_E);
|
|
JS_DefinePropertyValue(ctx, obj, JS_ATOM_revoke, revoke_obj, JS_PROP_C_W_E);
|
|
return obj;
|
|
fail:
|
|
JS_FreeValue(ctx, proxy_obj);
|
|
JS_FreeValue(ctx, revoke_obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static const JSCFunctionListEntry js_proxy_funcs[] = {
|
|
JS_CFUNC_DEF("revocable", 2, js_proxy_revocable ),
|
|
};
|
|
|
|
static const JSClassShortDef js_proxy_class_def[] = {
|
|
{ JS_ATOM_Object, js_proxy_finalizer, js_proxy_mark }, /* JS_CLASS_PROXY */
|
|
};
|
|
|
|
void JS_AddIntrinsicProxy(JSContext *ctx)
|
|
{
|
|
JSRuntime *rt = ctx->rt;
|
|
JSValue obj1;
|
|
|
|
if (!JS_IsRegisteredClass(rt, JS_CLASS_PROXY)) {
|
|
init_class_range(rt, js_proxy_class_def, JS_CLASS_PROXY,
|
|
countof(js_proxy_class_def));
|
|
rt->class_array[JS_CLASS_PROXY].exotic = &js_proxy_exotic_methods;
|
|
rt->class_array[JS_CLASS_PROXY].call = js_proxy_call;
|
|
}
|
|
|
|
obj1 = JS_NewCFunction2(ctx, js_proxy_constructor, "Proxy", 2,
|
|
JS_CFUNC_constructor, 0);
|
|
JS_SetConstructorBit(ctx, obj1, TRUE);
|
|
JS_SetPropertyFunctionList(ctx, obj1, js_proxy_funcs,
|
|
countof(js_proxy_funcs));
|
|
JS_DefinePropertyValueStr(ctx, ctx->global_obj, "Proxy",
|
|
obj1, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
|
|
}
|
|
|
|
/* Symbol */
|
|
|
|
static JSValue js_symbol_constructor(JSContext *ctx, JSValue new_target,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue str;
|
|
JSString *p;
|
|
|
|
if (!JS_IsUndefined(new_target))
|
|
return JS_ThrowTypeError(ctx, "not a constructor");
|
|
if (argc == 0 || JS_IsUndefined(argv[0])) {
|
|
p = NULL;
|
|
} else {
|
|
str = JS_ToString(ctx, argv[0]);
|
|
if (JS_IsException(str))
|
|
return JS_EXCEPTION;
|
|
p = JS_VALUE_GET_STRING(str);
|
|
}
|
|
return JS_NewSymbolInternal(ctx, p, JS_ATOM_TYPE_SYMBOL);
|
|
}
|
|
|
|
static JSValue js_thisSymbolValue(JSContext *ctx, JSValue this_val)
|
|
{
|
|
if (JS_VALUE_GET_TAG(this_val) == JS_TAG_SYMBOL)
|
|
return js_dup(this_val);
|
|
|
|
if (JS_VALUE_GET_TAG(this_val) == JS_TAG_OBJECT) {
|
|
JSObject *p = JS_VALUE_GET_OBJ(this_val);
|
|
if (p->class_id == JS_CLASS_SYMBOL) {
|
|
if (JS_VALUE_GET_TAG(p->u.object_data) == JS_TAG_SYMBOL)
|
|
return js_dup(p->u.object_data);
|
|
}
|
|
}
|
|
return JS_ThrowTypeError(ctx, "not a symbol");
|
|
}
|
|
|
|
static JSValue js_symbol_toString(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue val, ret;
|
|
val = js_thisSymbolValue(ctx, this_val);
|
|
if (JS_IsException(val))
|
|
return val;
|
|
/* XXX: use JS_ToStringInternal() with a flags */
|
|
ret = js_string_constructor(ctx, JS_UNDEFINED, 1, &val);
|
|
JS_FreeValue(ctx, val);
|
|
return ret;
|
|
}
|
|
|
|
static JSValue js_symbol_valueOf(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
return js_thisSymbolValue(ctx, this_val);
|
|
}
|
|
|
|
static JSValue js_symbol_get_description(JSContext *ctx, JSValue this_val)
|
|
{
|
|
JSValue val, ret;
|
|
JSAtomStruct *p;
|
|
|
|
val = js_thisSymbolValue(ctx, this_val);
|
|
if (JS_IsException(val))
|
|
return val;
|
|
p = JS_VALUE_GET_PTR(val);
|
|
if (p->len == 0 && p->is_wide_char != 0) {
|
|
ret = JS_UNDEFINED;
|
|
} else {
|
|
ret = JS_AtomToString(ctx, js_get_atom_index(ctx->rt, p));
|
|
}
|
|
JS_FreeValue(ctx, val);
|
|
return ret;
|
|
}
|
|
|
|
static const JSCFunctionListEntry js_symbol_proto_funcs[] = {
|
|
JS_CFUNC_DEF("toString", 0, js_symbol_toString ),
|
|
JS_CFUNC_DEF("valueOf", 0, js_symbol_valueOf ),
|
|
// XXX: should have writable: false
|
|
JS_CFUNC_DEF("[Symbol.toPrimitive]", 1, js_symbol_valueOf ),
|
|
JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Symbol", JS_PROP_CONFIGURABLE ),
|
|
JS_CGETSET_DEF("description", js_symbol_get_description, NULL ),
|
|
};
|
|
|
|
static JSValue js_symbol_for(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue str;
|
|
|
|
str = JS_ToString(ctx, argv[0]);
|
|
if (JS_IsException(str))
|
|
return JS_EXCEPTION;
|
|
return JS_NewSymbolInternal(ctx, JS_VALUE_GET_STRING(str), JS_ATOM_TYPE_GLOBAL_SYMBOL);
|
|
}
|
|
|
|
static JSValue js_symbol_keyFor(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSAtomStruct *p;
|
|
|
|
if (!JS_IsSymbol(argv[0]))
|
|
return JS_ThrowTypeError(ctx, "not a symbol");
|
|
p = JS_VALUE_GET_PTR(argv[0]);
|
|
if (p->atom_type != JS_ATOM_TYPE_GLOBAL_SYMBOL)
|
|
return JS_UNDEFINED;
|
|
return js_dup(JS_MKPTR(JS_TAG_STRING, p));
|
|
}
|
|
|
|
static const JSCFunctionListEntry js_symbol_funcs[] = {
|
|
JS_CFUNC_DEF("for", 1, js_symbol_for ),
|
|
JS_CFUNC_DEF("keyFor", 1, js_symbol_keyFor ),
|
|
};
|
|
|
|
/* Set/Map/WeakSet/WeakMap */
|
|
|
|
typedef struct JSMapRecord {
|
|
int ref_count; /* used during enumeration to avoid freeing the record */
|
|
BOOL empty; /* TRUE if the record is deleted */
|
|
struct JSMapState *map;
|
|
struct list_head link;
|
|
struct list_head hash_link;
|
|
JSValue key;
|
|
JSValue value;
|
|
} JSMapRecord;
|
|
|
|
typedef struct JSMapState {
|
|
BOOL is_weak; /* TRUE if WeakSet/WeakMap */
|
|
struct list_head records; /* list of JSMapRecord.link */
|
|
uint32_t record_count;
|
|
struct list_head *hash_table;
|
|
uint32_t hash_size; /* must be a power of two */
|
|
uint32_t record_count_threshold; /* count at which a hash table
|
|
resize is needed */
|
|
} JSMapState;
|
|
|
|
#define MAGIC_SET (1 << 0)
|
|
#define MAGIC_WEAK (1 << 1)
|
|
|
|
static JSValue js_map_constructor(JSContext *ctx, JSValue new_target,
|
|
int argc, JSValue *argv, int magic)
|
|
{
|
|
JSMapState *s;
|
|
JSValue obj, adder = JS_UNDEFINED, iter = JS_UNDEFINED, next_method = JS_UNDEFINED;
|
|
JSValue arr;
|
|
BOOL is_set, is_weak;
|
|
|
|
is_set = magic & MAGIC_SET;
|
|
is_weak = ((magic & MAGIC_WEAK) != 0);
|
|
obj = js_create_from_ctor(ctx, new_target, JS_CLASS_MAP + magic);
|
|
if (JS_IsException(obj))
|
|
return JS_EXCEPTION;
|
|
s = js_mallocz(ctx, sizeof(*s));
|
|
if (!s)
|
|
goto fail;
|
|
init_list_head(&s->records);
|
|
s->is_weak = is_weak;
|
|
JS_SetOpaque(obj, s);
|
|
s->hash_size = 1;
|
|
s->hash_table = js_malloc(ctx, sizeof(s->hash_table[0]) * s->hash_size);
|
|
if (!s->hash_table)
|
|
goto fail;
|
|
init_list_head(&s->hash_table[0]);
|
|
s->record_count_threshold = 4;
|
|
|
|
arr = JS_UNDEFINED;
|
|
if (argc > 0)
|
|
arr = argv[0];
|
|
if (!JS_IsUndefined(arr) && !JS_IsNull(arr)) {
|
|
JSValue item, ret;
|
|
BOOL done;
|
|
|
|
adder = JS_GetProperty(ctx, obj, is_set ? JS_ATOM_add : JS_ATOM_set);
|
|
if (JS_IsException(adder))
|
|
goto fail;
|
|
if (!JS_IsFunction(ctx, adder)) {
|
|
JS_ThrowTypeError(ctx, "set/add is not a function");
|
|
goto fail;
|
|
}
|
|
|
|
iter = JS_GetIterator(ctx, arr, FALSE);
|
|
if (JS_IsException(iter))
|
|
goto fail;
|
|
next_method = JS_GetProperty(ctx, iter, JS_ATOM_next);
|
|
if (JS_IsException(next_method))
|
|
goto fail;
|
|
|
|
for(;;) {
|
|
item = JS_IteratorNext(ctx, iter, next_method, 0, NULL, &done);
|
|
if (JS_IsException(item))
|
|
goto fail;
|
|
if (done) {
|
|
JS_FreeValue(ctx, item);
|
|
break;
|
|
}
|
|
if (is_set) {
|
|
ret = JS_Call(ctx, adder, obj, 1, &item);
|
|
if (JS_IsException(ret)) {
|
|
JS_FreeValue(ctx, item);
|
|
goto fail;
|
|
}
|
|
} else {
|
|
JSValue key, value;
|
|
JSValue args[2];
|
|
key = JS_UNDEFINED;
|
|
value = JS_UNDEFINED;
|
|
if (!JS_IsObject(item)) {
|
|
JS_ThrowTypeErrorNotAnObject(ctx);
|
|
goto fail1;
|
|
}
|
|
key = JS_GetPropertyUint32(ctx, item, 0);
|
|
if (JS_IsException(key))
|
|
goto fail1;
|
|
value = JS_GetPropertyUint32(ctx, item, 1);
|
|
if (JS_IsException(value))
|
|
goto fail1;
|
|
args[0] = key;
|
|
args[1] = value;
|
|
ret = JS_Call(ctx, adder, obj, 2, args);
|
|
if (JS_IsException(ret)) {
|
|
fail1:
|
|
JS_FreeValue(ctx, item);
|
|
JS_FreeValue(ctx, key);
|
|
JS_FreeValue(ctx, value);
|
|
goto fail;
|
|
}
|
|
JS_FreeValue(ctx, key);
|
|
JS_FreeValue(ctx, value);
|
|
}
|
|
JS_FreeValue(ctx, ret);
|
|
JS_FreeValue(ctx, item);
|
|
}
|
|
JS_FreeValue(ctx, next_method);
|
|
JS_FreeValue(ctx, iter);
|
|
JS_FreeValue(ctx, adder);
|
|
}
|
|
return obj;
|
|
fail:
|
|
if (JS_IsObject(iter)) {
|
|
/* close the iterator object, preserving pending exception */
|
|
JS_IteratorClose(ctx, iter, TRUE);
|
|
}
|
|
JS_FreeValue(ctx, next_method);
|
|
JS_FreeValue(ctx, iter);
|
|
JS_FreeValue(ctx, adder);
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
/* XXX: could normalize strings to speed up comparison */
|
|
static JSValue map_normalize_key(JSContext *ctx, JSValue key)
|
|
{
|
|
uint32_t tag = JS_VALUE_GET_TAG(key);
|
|
/* convert -0.0 to +0.0 */
|
|
if (JS_TAG_IS_FLOAT64(tag) && JS_VALUE_GET_FLOAT64(key) == 0.0) {
|
|
key = js_int32(0);
|
|
}
|
|
return key;
|
|
}
|
|
|
|
/* XXX: better hash ? */
|
|
static uint32_t map_hash_key(JSContext *ctx, JSValue key)
|
|
{
|
|
uint32_t tag = JS_VALUE_GET_NORM_TAG(key);
|
|
uint32_t h;
|
|
double d;
|
|
JSFloat64Union u;
|
|
bf_t *a;
|
|
|
|
switch(tag) {
|
|
case JS_TAG_BOOL:
|
|
h = JS_VALUE_GET_INT(key);
|
|
break;
|
|
case JS_TAG_STRING:
|
|
h = hash_string(JS_VALUE_GET_STRING(key), 0);
|
|
break;
|
|
case JS_TAG_OBJECT:
|
|
case JS_TAG_SYMBOL:
|
|
h = (uintptr_t)JS_VALUE_GET_PTR(key) * 3163;
|
|
break;
|
|
case JS_TAG_INT:
|
|
d = JS_VALUE_GET_INT(key);
|
|
goto hash_float64;
|
|
case JS_TAG_BIG_INT:
|
|
a = JS_GetBigInt(key);
|
|
h = hash_string8((void *)a->tab, a->len * sizeof(*a->tab), 0);
|
|
break;
|
|
case JS_TAG_FLOAT64:
|
|
d = JS_VALUE_GET_FLOAT64(key);
|
|
/* normalize the NaN */
|
|
if (isnan(d))
|
|
d = JS_FLOAT64_NAN;
|
|
hash_float64:
|
|
u.d = d;
|
|
h = (u.u32[0] ^ u.u32[1]) * 3163;
|
|
return h ^= JS_TAG_FLOAT64;
|
|
default:
|
|
h = 0;
|
|
break;
|
|
}
|
|
h ^= tag;
|
|
return h;
|
|
}
|
|
|
|
static JSMapRecord *map_find_record(JSContext *ctx, JSMapState *s,
|
|
JSValue key)
|
|
{
|
|
struct list_head *el;
|
|
JSMapRecord *mr;
|
|
uint32_t h;
|
|
h = map_hash_key(ctx, key) & (s->hash_size - 1);
|
|
list_for_each(el, &s->hash_table[h]) {
|
|
mr = list_entry(el, JSMapRecord, hash_link);
|
|
if (js_same_value_zero(ctx, mr->key, key))
|
|
return mr;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static void map_hash_resize(JSContext *ctx, JSMapState *s)
|
|
{
|
|
uint32_t new_hash_size, i, h;
|
|
size_t slack;
|
|
struct list_head *new_hash_table, *el;
|
|
JSMapRecord *mr;
|
|
|
|
/* XXX: no reporting of memory allocation failure */
|
|
if (s->hash_size == 1)
|
|
new_hash_size = 4;
|
|
else
|
|
new_hash_size = s->hash_size * 2;
|
|
new_hash_table = js_realloc2(ctx, s->hash_table,
|
|
sizeof(new_hash_table[0]) * new_hash_size, &slack);
|
|
if (!new_hash_table)
|
|
return;
|
|
new_hash_size += slack / sizeof(*new_hash_table);
|
|
|
|
for(i = 0; i < new_hash_size; i++)
|
|
init_list_head(&new_hash_table[i]);
|
|
|
|
list_for_each(el, &s->records) {
|
|
mr = list_entry(el, JSMapRecord, link);
|
|
if (!mr->empty) {
|
|
h = map_hash_key(ctx, mr->key) & (new_hash_size - 1);
|
|
list_add_tail(&mr->hash_link, &new_hash_table[h]);
|
|
}
|
|
}
|
|
s->hash_table = new_hash_table;
|
|
s->hash_size = new_hash_size;
|
|
s->record_count_threshold = new_hash_size * 2;
|
|
}
|
|
|
|
static JSWeakRefRecord **get_first_weak_ref(JSValue key)
|
|
{
|
|
switch (JS_VALUE_GET_TAG(key)) {
|
|
case JS_TAG_OBJECT:
|
|
{
|
|
JSObject *p = JS_VALUE_GET_OBJ(key);
|
|
return &p->first_weak_ref;
|
|
}
|
|
break;
|
|
case JS_TAG_SYMBOL:
|
|
{
|
|
JSAtomStruct *p = JS_VALUE_GET_PTR(key);
|
|
return &p->first_weak_ref;
|
|
}
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
return NULL; // pacify compiler
|
|
}
|
|
|
|
static JSMapRecord *map_add_record(JSContext *ctx, JSMapState *s,
|
|
JSValue key)
|
|
{
|
|
uint32_t h;
|
|
JSMapRecord *mr;
|
|
|
|
mr = js_malloc(ctx, sizeof(*mr));
|
|
if (!mr)
|
|
return NULL;
|
|
mr->ref_count = 1;
|
|
mr->map = s;
|
|
mr->empty = FALSE;
|
|
if (s->is_weak) {
|
|
JSWeakRefRecord *wr = js_malloc(ctx, sizeof(*wr));
|
|
if (!wr) {
|
|
js_free(ctx, mr);
|
|
return NULL;
|
|
}
|
|
wr->kind = JS_WEAK_REF_KIND_MAP;
|
|
wr->u.map_record = mr;
|
|
insert_weakref_record(key, wr);
|
|
} else {
|
|
js_dup(key);
|
|
}
|
|
mr->key = key;
|
|
h = map_hash_key(ctx, key) & (s->hash_size - 1);
|
|
list_add_tail(&mr->hash_link, &s->hash_table[h]);
|
|
list_add_tail(&mr->link, &s->records);
|
|
s->record_count++;
|
|
if (s->record_count >= s->record_count_threshold) {
|
|
map_hash_resize(ctx, s);
|
|
}
|
|
return mr;
|
|
}
|
|
|
|
/* Remove the weak reference from the object weak
|
|
reference list. we don't use a doubly linked list to
|
|
save space, assuming a given object has few weak
|
|
references to it */
|
|
static void delete_map_weak_ref(JSRuntime *rt, JSMapRecord *mr)
|
|
{
|
|
JSWeakRefRecord **pwr, *wr;
|
|
|
|
pwr = get_first_weak_ref(mr->key);
|
|
for(;;) {
|
|
wr = *pwr;
|
|
assert(wr != NULL);
|
|
if (wr->kind == JS_WEAK_REF_KIND_MAP && wr->u.map_record == mr)
|
|
break;
|
|
pwr = &wr->next_weak_ref;
|
|
}
|
|
*pwr = wr->next_weak_ref;
|
|
js_free_rt(rt, wr);
|
|
}
|
|
|
|
static void map_delete_record(JSRuntime *rt, JSMapState *s, JSMapRecord *mr)
|
|
{
|
|
if (mr->empty)
|
|
return;
|
|
list_del(&mr->hash_link);
|
|
if (s->is_weak) {
|
|
delete_map_weak_ref(rt, mr);
|
|
} else {
|
|
JS_FreeValueRT(rt, mr->key);
|
|
}
|
|
JS_FreeValueRT(rt, mr->value);
|
|
if (--mr->ref_count == 0) {
|
|
list_del(&mr->link);
|
|
js_free_rt(rt, mr);
|
|
} else {
|
|
/* keep a zombie record for iterators */
|
|
mr->empty = TRUE;
|
|
mr->key = JS_UNDEFINED;
|
|
mr->value = JS_UNDEFINED;
|
|
}
|
|
s->record_count--;
|
|
}
|
|
|
|
static void map_decref_record(JSRuntime *rt, JSMapRecord *mr)
|
|
{
|
|
if (--mr->ref_count == 0) {
|
|
/* the record can be safely removed */
|
|
assert(mr->empty);
|
|
list_del(&mr->link);
|
|
js_free_rt(rt, mr);
|
|
}
|
|
}
|
|
|
|
static JSValue js_map_set(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv, int magic)
|
|
{
|
|
JSMapState *s = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic);
|
|
JSMapRecord *mr;
|
|
JSValue key, value;
|
|
int is_set;
|
|
|
|
if (!s)
|
|
return JS_EXCEPTION;
|
|
is_set = (magic & MAGIC_SET);
|
|
key = map_normalize_key(ctx, argv[0]);
|
|
if (s->is_weak && !is_valid_weakref_target(key))
|
|
return JS_ThrowTypeError(ctx, "invalid value used as %s key", is_set ? "WeakSet" : "WeakMap");
|
|
if (is_set)
|
|
value = JS_UNDEFINED;
|
|
else
|
|
value = argv[1];
|
|
mr = map_find_record(ctx, s, key);
|
|
if (mr) {
|
|
JS_FreeValue(ctx, mr->value);
|
|
} else {
|
|
mr = map_add_record(ctx, s, key);
|
|
if (!mr)
|
|
return JS_EXCEPTION;
|
|
}
|
|
mr->value = js_dup(value);
|
|
return js_dup(this_val);
|
|
}
|
|
|
|
static JSValue js_map_get(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv, int magic)
|
|
{
|
|
JSMapState *s = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic);
|
|
JSMapRecord *mr;
|
|
JSValue key;
|
|
|
|
if (!s)
|
|
return JS_EXCEPTION;
|
|
key = map_normalize_key(ctx, argv[0]);
|
|
mr = map_find_record(ctx, s, key);
|
|
if (!mr)
|
|
return JS_UNDEFINED;
|
|
else
|
|
return js_dup(mr->value);
|
|
}
|
|
|
|
static JSValue js_map_has(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv, int magic)
|
|
{
|
|
JSMapState *s = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic);
|
|
JSMapRecord *mr;
|
|
JSValue key;
|
|
|
|
if (!s)
|
|
return JS_EXCEPTION;
|
|
key = map_normalize_key(ctx, argv[0]);
|
|
mr = map_find_record(ctx, s, key);
|
|
return js_bool(mr != NULL);
|
|
}
|
|
|
|
static JSValue js_map_delete(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv, int magic)
|
|
{
|
|
JSMapState *s = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic);
|
|
JSMapRecord *mr;
|
|
JSValue key;
|
|
|
|
if (!s)
|
|
return JS_EXCEPTION;
|
|
key = map_normalize_key(ctx, argv[0]);
|
|
mr = map_find_record(ctx, s, key);
|
|
if (!mr)
|
|
return JS_FALSE;
|
|
map_delete_record(ctx->rt, s, mr);
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSValue js_map_clear(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv, int magic)
|
|
{
|
|
JSMapState *s = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic);
|
|
struct list_head *el, *el1;
|
|
JSMapRecord *mr;
|
|
|
|
if (!s)
|
|
return JS_EXCEPTION;
|
|
list_for_each_safe(el, el1, &s->records) {
|
|
mr = list_entry(el, JSMapRecord, link);
|
|
map_delete_record(ctx->rt, s, mr);
|
|
}
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
static JSValue js_map_get_size(JSContext *ctx, JSValue this_val, int magic)
|
|
{
|
|
JSMapState *s = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic);
|
|
if (!s)
|
|
return JS_EXCEPTION;
|
|
return js_uint32(s->record_count);
|
|
}
|
|
|
|
static JSValue js_map_forEach(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv, int magic)
|
|
{
|
|
JSMapState *s = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic);
|
|
JSValue func, this_arg;
|
|
JSValue ret, args[3];
|
|
struct list_head *el;
|
|
JSMapRecord *mr;
|
|
|
|
if (!s)
|
|
return JS_EXCEPTION;
|
|
func = argv[0];
|
|
if (argc > 1)
|
|
this_arg = argv[1];
|
|
else
|
|
this_arg = JS_UNDEFINED;
|
|
if (check_function(ctx, func))
|
|
return JS_EXCEPTION;
|
|
/* Note: the list can be modified while traversing it, but the
|
|
current element is locked */
|
|
el = s->records.next;
|
|
while (el != &s->records) {
|
|
mr = list_entry(el, JSMapRecord, link);
|
|
if (!mr->empty) {
|
|
mr->ref_count++;
|
|
/* must duplicate in case the record is deleted */
|
|
args[1] = js_dup(mr->key);
|
|
if (magic)
|
|
args[0] = args[1];
|
|
else
|
|
args[0] = js_dup(mr->value);
|
|
args[2] = this_val;
|
|
ret = JS_Call(ctx, func, this_arg, 3, args);
|
|
JS_FreeValue(ctx, args[0]);
|
|
if (!magic)
|
|
JS_FreeValue(ctx, args[1]);
|
|
el = el->next;
|
|
map_decref_record(ctx->rt, mr);
|
|
if (JS_IsException(ret))
|
|
return ret;
|
|
JS_FreeValue(ctx, ret);
|
|
} else {
|
|
el = el->next;
|
|
}
|
|
}
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
static JSValue js_map_groupBy(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue cb, res, iter, next, groups, k, v, prop;
|
|
JSValue args[2];
|
|
int64_t idx;
|
|
BOOL done;
|
|
|
|
// "is function?" check must be observed before argv[0] is accessed
|
|
cb = argv[1];
|
|
if (check_function(ctx, cb))
|
|
return JS_EXCEPTION;
|
|
|
|
iter = JS_GetIterator(ctx, argv[0], /*is_async*/FALSE);
|
|
if (JS_IsException(iter))
|
|
return JS_EXCEPTION;
|
|
|
|
k = JS_UNDEFINED;
|
|
v = JS_UNDEFINED;
|
|
prop = JS_UNDEFINED;
|
|
groups = JS_UNDEFINED;
|
|
|
|
next = JS_GetProperty(ctx, iter, JS_ATOM_next);
|
|
if (JS_IsException(next))
|
|
goto exception;
|
|
|
|
groups = js_map_constructor(ctx, JS_UNDEFINED, 0, NULL, 0);
|
|
if (JS_IsException(groups))
|
|
goto exception;
|
|
|
|
for (idx = 0; ; idx++) {
|
|
v = JS_IteratorNext(ctx, iter, next, 0, NULL, &done);
|
|
if (JS_IsException(v))
|
|
goto exception;
|
|
if (done)
|
|
break; // v is JS_UNDEFINED
|
|
|
|
args[0] = v;
|
|
args[1] = JS_NewInt64(ctx, idx);
|
|
k = JS_Call(ctx, cb, ctx->global_obj, 2, args);
|
|
if (JS_IsException(k))
|
|
goto exception;
|
|
|
|
prop = js_map_get(ctx, groups, 1, &k, 0);
|
|
if (JS_IsException(prop))
|
|
goto exception;
|
|
|
|
if (JS_IsUndefined(prop)) {
|
|
prop = JS_NewArray(ctx);
|
|
if (JS_IsException(prop))
|
|
goto exception;
|
|
args[0] = k;
|
|
args[1] = prop;
|
|
res = js_map_set(ctx, groups, 2, args, 0);
|
|
if (JS_IsException(res))
|
|
goto exception;
|
|
JS_FreeValue(ctx, res);
|
|
}
|
|
|
|
res = js_array_push(ctx, prop, 1, &v, /*unshift*/0);
|
|
if (JS_IsException(res))
|
|
goto exception;
|
|
// res is an int64
|
|
|
|
JS_FreeValue(ctx, prop);
|
|
JS_FreeValue(ctx, k);
|
|
JS_FreeValue(ctx, v);
|
|
prop = JS_UNDEFINED;
|
|
k = JS_UNDEFINED;
|
|
v = JS_UNDEFINED;
|
|
}
|
|
|
|
JS_FreeValue(ctx, iter);
|
|
JS_FreeValue(ctx, next);
|
|
return groups;
|
|
|
|
exception:
|
|
JS_FreeValue(ctx, prop);
|
|
JS_FreeValue(ctx, k);
|
|
JS_FreeValue(ctx, v);
|
|
JS_FreeValue(ctx, groups);
|
|
JS_FreeValue(ctx, iter);
|
|
JS_FreeValue(ctx, next);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static void js_map_finalizer(JSRuntime *rt, JSValue val)
|
|
{
|
|
JSObject *p;
|
|
JSMapState *s;
|
|
struct list_head *el, *el1;
|
|
JSMapRecord *mr;
|
|
|
|
p = JS_VALUE_GET_OBJ(val);
|
|
s = p->u.map_state;
|
|
if (s) {
|
|
/* if the object is deleted we are sure that no iterator is
|
|
using it */
|
|
list_for_each_safe(el, el1, &s->records) {
|
|
mr = list_entry(el, JSMapRecord, link);
|
|
if (!mr->empty) {
|
|
if (s->is_weak)
|
|
delete_map_weak_ref(rt, mr);
|
|
else
|
|
JS_FreeValueRT(rt, mr->key);
|
|
JS_FreeValueRT(rt, mr->value);
|
|
}
|
|
js_free_rt(rt, mr);
|
|
}
|
|
js_free_rt(rt, s->hash_table);
|
|
js_free_rt(rt, s);
|
|
}
|
|
}
|
|
|
|
static void js_map_mark(JSRuntime *rt, JSValue val, JS_MarkFunc *mark_func)
|
|
{
|
|
JSObject *p = JS_VALUE_GET_OBJ(val);
|
|
JSMapState *s;
|
|
struct list_head *el;
|
|
JSMapRecord *mr;
|
|
|
|
s = p->u.map_state;
|
|
if (s) {
|
|
list_for_each(el, &s->records) {
|
|
mr = list_entry(el, JSMapRecord, link);
|
|
if (!s->is_weak)
|
|
JS_MarkValue(rt, mr->key, mark_func);
|
|
JS_MarkValue(rt, mr->value, mark_func);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Map Iterator */
|
|
|
|
typedef struct JSMapIteratorData {
|
|
JSValue obj;
|
|
JSIteratorKindEnum kind;
|
|
JSMapRecord *cur_record;
|
|
} JSMapIteratorData;
|
|
|
|
static void js_map_iterator_finalizer(JSRuntime *rt, JSValue val)
|
|
{
|
|
JSObject *p;
|
|
JSMapIteratorData *it;
|
|
|
|
p = JS_VALUE_GET_OBJ(val);
|
|
it = p->u.map_iterator_data;
|
|
if (it) {
|
|
/* During the GC sweep phase the Map finalizer may be
|
|
called before the Map iterator finalizer */
|
|
if (JS_IsLiveObject(rt, it->obj) && it->cur_record) {
|
|
map_decref_record(rt, it->cur_record);
|
|
}
|
|
JS_FreeValueRT(rt, it->obj);
|
|
js_free_rt(rt, it);
|
|
}
|
|
}
|
|
|
|
static void js_map_iterator_mark(JSRuntime *rt, JSValue val,
|
|
JS_MarkFunc *mark_func)
|
|
{
|
|
JSObject *p = JS_VALUE_GET_OBJ(val);
|
|
JSMapIteratorData *it;
|
|
it = p->u.map_iterator_data;
|
|
if (it) {
|
|
/* the record is already marked by the object */
|
|
JS_MarkValue(rt, it->obj, mark_func);
|
|
}
|
|
}
|
|
|
|
static JSValue js_create_map_iterator(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv, int magic)
|
|
{
|
|
JSIteratorKindEnum kind;
|
|
JSMapState *s;
|
|
JSMapIteratorData *it;
|
|
JSValue enum_obj;
|
|
|
|
kind = magic >> 2;
|
|
magic &= 3;
|
|
s = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic);
|
|
if (!s)
|
|
return JS_EXCEPTION;
|
|
enum_obj = JS_NewObjectClass(ctx, JS_CLASS_MAP_ITERATOR + magic);
|
|
if (JS_IsException(enum_obj))
|
|
goto fail;
|
|
it = js_malloc(ctx, sizeof(*it));
|
|
if (!it) {
|
|
JS_FreeValue(ctx, enum_obj);
|
|
goto fail;
|
|
}
|
|
it->obj = js_dup(this_val);
|
|
it->kind = kind;
|
|
it->cur_record = NULL;
|
|
JS_SetOpaque(enum_obj, it);
|
|
return enum_obj;
|
|
fail:
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue js_map_iterator_next(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv,
|
|
BOOL *pdone, int magic)
|
|
{
|
|
JSMapIteratorData *it;
|
|
JSMapState *s;
|
|
JSMapRecord *mr;
|
|
struct list_head *el;
|
|
|
|
it = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP_ITERATOR + magic);
|
|
if (!it) {
|
|
*pdone = FALSE;
|
|
return JS_EXCEPTION;
|
|
}
|
|
if (JS_IsUndefined(it->obj))
|
|
goto done;
|
|
s = JS_GetOpaque(it->obj, JS_CLASS_MAP + magic);
|
|
assert(s != NULL);
|
|
if (!it->cur_record) {
|
|
el = s->records.next;
|
|
} else {
|
|
mr = it->cur_record;
|
|
el = mr->link.next;
|
|
map_decref_record(ctx->rt, mr); /* the record can be freed here */
|
|
}
|
|
for(;;) {
|
|
if (el == &s->records) {
|
|
/* no more record */
|
|
it->cur_record = NULL;
|
|
JS_FreeValue(ctx, it->obj);
|
|
it->obj = JS_UNDEFINED;
|
|
done:
|
|
/* end of enumeration */
|
|
*pdone = TRUE;
|
|
return JS_UNDEFINED;
|
|
}
|
|
mr = list_entry(el, JSMapRecord, link);
|
|
if (!mr->empty)
|
|
break;
|
|
/* get the next record */
|
|
el = mr->link.next;
|
|
}
|
|
|
|
/* lock the record so that it won't be freed */
|
|
mr->ref_count++;
|
|
it->cur_record = mr;
|
|
*pdone = FALSE;
|
|
|
|
if (it->kind == JS_ITERATOR_KIND_KEY) {
|
|
return js_dup(mr->key);
|
|
} else {
|
|
JSValue args[2];
|
|
args[0] = mr->key;
|
|
if (magic)
|
|
args[1] = mr->key;
|
|
else
|
|
args[1] = mr->value;
|
|
if (it->kind == JS_ITERATOR_KIND_VALUE) {
|
|
return js_dup(args[1]);
|
|
} else {
|
|
return js_create_array(ctx, 2, args);
|
|
}
|
|
}
|
|
}
|
|
|
|
static const JSCFunctionListEntry js_map_funcs[] = {
|
|
JS_CFUNC_DEF("groupBy", 2, js_map_groupBy ),
|
|
JS_CGETSET_DEF("[Symbol.species]", js_get_this, NULL ),
|
|
};
|
|
|
|
static const JSCFunctionListEntry js_map_proto_funcs[] = {
|
|
JS_CFUNC_MAGIC_DEF("set", 2, js_map_set, 0 ),
|
|
JS_CFUNC_MAGIC_DEF("get", 1, js_map_get, 0 ),
|
|
JS_CFUNC_MAGIC_DEF("has", 1, js_map_has, 0 ),
|
|
JS_CFUNC_MAGIC_DEF("delete", 1, js_map_delete, 0 ),
|
|
JS_CFUNC_MAGIC_DEF("clear", 0, js_map_clear, 0 ),
|
|
JS_CGETSET_MAGIC_DEF("size", js_map_get_size, NULL, 0),
|
|
JS_CFUNC_MAGIC_DEF("forEach", 1, js_map_forEach, 0 ),
|
|
JS_CFUNC_MAGIC_DEF("values", 0, js_create_map_iterator, (JS_ITERATOR_KIND_VALUE << 2) | 0 ),
|
|
JS_CFUNC_MAGIC_DEF("keys", 0, js_create_map_iterator, (JS_ITERATOR_KIND_KEY << 2) | 0 ),
|
|
JS_CFUNC_MAGIC_DEF("entries", 0, js_create_map_iterator, (JS_ITERATOR_KIND_KEY_AND_VALUE << 2) | 0 ),
|
|
JS_ALIAS_DEF("[Symbol.iterator]", "entries" ),
|
|
JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Map", JS_PROP_CONFIGURABLE ),
|
|
};
|
|
|
|
static const JSCFunctionListEntry js_map_iterator_proto_funcs[] = {
|
|
JS_ITERATOR_NEXT_DEF("next", 0, js_map_iterator_next, 0 ),
|
|
JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Map Iterator", JS_PROP_CONFIGURABLE ),
|
|
};
|
|
|
|
static const JSCFunctionListEntry js_set_proto_funcs[] = {
|
|
JS_CFUNC_MAGIC_DEF("add", 1, js_map_set, MAGIC_SET ),
|
|
JS_CFUNC_MAGIC_DEF("has", 1, js_map_has, MAGIC_SET ),
|
|
JS_CFUNC_MAGIC_DEF("delete", 1, js_map_delete, MAGIC_SET ),
|
|
JS_CFUNC_MAGIC_DEF("clear", 0, js_map_clear, MAGIC_SET ),
|
|
JS_CGETSET_MAGIC_DEF("size", js_map_get_size, NULL, MAGIC_SET ),
|
|
JS_CFUNC_MAGIC_DEF("forEach", 1, js_map_forEach, MAGIC_SET ),
|
|
JS_CFUNC_MAGIC_DEF("values", 0, js_create_map_iterator, (JS_ITERATOR_KIND_KEY << 2) | MAGIC_SET ),
|
|
JS_ALIAS_DEF("keys", "values" ),
|
|
JS_ALIAS_DEF("[Symbol.iterator]", "values" ),
|
|
JS_CFUNC_MAGIC_DEF("entries", 0, js_create_map_iterator, (JS_ITERATOR_KIND_KEY_AND_VALUE << 2) | MAGIC_SET ),
|
|
JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Set", JS_PROP_CONFIGURABLE ),
|
|
};
|
|
|
|
static const JSCFunctionListEntry js_set_iterator_proto_funcs[] = {
|
|
JS_ITERATOR_NEXT_DEF("next", 0, js_map_iterator_next, MAGIC_SET ),
|
|
JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Set Iterator", JS_PROP_CONFIGURABLE ),
|
|
};
|
|
|
|
static const JSCFunctionListEntry js_weak_map_proto_funcs[] = {
|
|
JS_CFUNC_MAGIC_DEF("set", 2, js_map_set, MAGIC_WEAK ),
|
|
JS_CFUNC_MAGIC_DEF("get", 1, js_map_get, MAGIC_WEAK ),
|
|
JS_CFUNC_MAGIC_DEF("has", 1, js_map_has, MAGIC_WEAK ),
|
|
JS_CFUNC_MAGIC_DEF("delete", 1, js_map_delete, MAGIC_WEAK ),
|
|
JS_PROP_STRING_DEF("[Symbol.toStringTag]", "WeakMap", JS_PROP_CONFIGURABLE ),
|
|
};
|
|
|
|
static const JSCFunctionListEntry js_weak_set_proto_funcs[] = {
|
|
JS_CFUNC_MAGIC_DEF("add", 1, js_map_set, MAGIC_SET | MAGIC_WEAK ),
|
|
JS_CFUNC_MAGIC_DEF("has", 1, js_map_has, MAGIC_SET | MAGIC_WEAK ),
|
|
JS_CFUNC_MAGIC_DEF("delete", 1, js_map_delete, MAGIC_SET | MAGIC_WEAK ),
|
|
JS_PROP_STRING_DEF("[Symbol.toStringTag]", "WeakSet", JS_PROP_CONFIGURABLE ),
|
|
};
|
|
|
|
static const JSCFunctionListEntry * const js_map_proto_funcs_ptr[6] = {
|
|
js_map_proto_funcs,
|
|
js_set_proto_funcs,
|
|
js_weak_map_proto_funcs,
|
|
js_weak_set_proto_funcs,
|
|
js_map_iterator_proto_funcs,
|
|
js_set_iterator_proto_funcs,
|
|
};
|
|
|
|
static const uint8_t js_map_proto_funcs_count[6] = {
|
|
countof(js_map_proto_funcs),
|
|
countof(js_set_proto_funcs),
|
|
countof(js_weak_map_proto_funcs),
|
|
countof(js_weak_set_proto_funcs),
|
|
countof(js_map_iterator_proto_funcs),
|
|
countof(js_set_iterator_proto_funcs),
|
|
};
|
|
|
|
void JS_AddIntrinsicMapSet(JSContext *ctx)
|
|
{
|
|
int i;
|
|
JSValue obj1;
|
|
char buf[ATOM_GET_STR_BUF_SIZE];
|
|
|
|
for(i = 0; i < 4; i++) {
|
|
const char *name = JS_AtomGetStr(ctx, buf, sizeof(buf),
|
|
JS_ATOM_Map + i);
|
|
ctx->class_proto[JS_CLASS_MAP + i] = JS_NewObject(ctx);
|
|
JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_MAP + i],
|
|
js_map_proto_funcs_ptr[i],
|
|
js_map_proto_funcs_count[i]);
|
|
obj1 = JS_NewCFunctionMagic(ctx, js_map_constructor, name, 0,
|
|
JS_CFUNC_constructor_magic, i);
|
|
if (i < 2) {
|
|
JS_SetPropertyFunctionList(ctx, obj1, js_map_funcs,
|
|
countof(js_map_funcs));
|
|
}
|
|
JS_NewGlobalCConstructor2(ctx, obj1, name, ctx->class_proto[JS_CLASS_MAP + i]);
|
|
}
|
|
|
|
for(i = 0; i < 2; i++) {
|
|
ctx->class_proto[JS_CLASS_MAP_ITERATOR + i] =
|
|
JS_NewObjectProto(ctx, ctx->iterator_proto);
|
|
JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_MAP_ITERATOR + i],
|
|
js_map_proto_funcs_ptr[i + 4],
|
|
js_map_proto_funcs_count[i + 4]);
|
|
}
|
|
}
|
|
|
|
/* Generator */
|
|
static const JSCFunctionListEntry js_generator_function_proto_funcs[] = {
|
|
JS_PROP_STRING_DEF("[Symbol.toStringTag]", "GeneratorFunction", JS_PROP_CONFIGURABLE),
|
|
};
|
|
|
|
static const JSCFunctionListEntry js_generator_proto_funcs[] = {
|
|
JS_ITERATOR_NEXT_DEF("next", 1, js_generator_next, GEN_MAGIC_NEXT ),
|
|
JS_ITERATOR_NEXT_DEF("return", 1, js_generator_next, GEN_MAGIC_RETURN ),
|
|
JS_ITERATOR_NEXT_DEF("throw", 1, js_generator_next, GEN_MAGIC_THROW ),
|
|
JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Generator", JS_PROP_CONFIGURABLE),
|
|
};
|
|
|
|
/* Promise */
|
|
|
|
typedef struct JSPromiseFunctionDataResolved {
|
|
int ref_count;
|
|
BOOL already_resolved;
|
|
} JSPromiseFunctionDataResolved;
|
|
|
|
typedef struct JSPromiseFunctionData {
|
|
JSValue promise;
|
|
JSPromiseFunctionDataResolved *presolved;
|
|
} JSPromiseFunctionData;
|
|
|
|
typedef struct JSPromiseReactionData {
|
|
struct list_head link; /* not used in promise_reaction_job */
|
|
JSValue resolving_funcs[2];
|
|
JSValue handler;
|
|
} JSPromiseReactionData;
|
|
|
|
JSPromiseStateEnum JS_PromiseState(JSContext *ctx, JSValue promise)
|
|
{
|
|
JSPromiseData *s = JS_GetOpaque(promise, JS_CLASS_PROMISE);
|
|
if (!s)
|
|
return JS_INVALID_PROMISE_STATE;
|
|
return s->promise_state;
|
|
}
|
|
|
|
JSValue JS_PromiseResult(JSContext *ctx, JSValue promise)
|
|
{
|
|
JSPromiseData *s = JS_GetOpaque(promise, JS_CLASS_PROMISE);
|
|
if (!s)
|
|
return JS_UNDEFINED;
|
|
return js_dup(s->promise_result);
|
|
}
|
|
|
|
static int js_create_resolving_functions(JSContext *ctx, JSValue *args,
|
|
JSValue promise);
|
|
|
|
static void promise_reaction_data_free(JSRuntime *rt,
|
|
JSPromiseReactionData *rd)
|
|
{
|
|
JS_FreeValueRT(rt, rd->resolving_funcs[0]);
|
|
JS_FreeValueRT(rt, rd->resolving_funcs[1]);
|
|
JS_FreeValueRT(rt, rd->handler);
|
|
js_free_rt(rt, rd);
|
|
}
|
|
|
|
#ifdef DUMP_PROMISE
|
|
#define promise_trace(ctx, ...) \
|
|
do if (check_dump_flag(ctx->rt, DUMP_PROMISE)) \
|
|
printf(__VA_ARGS__); while (0)
|
|
#else
|
|
#define promise_trace(...)
|
|
#endif
|
|
|
|
static JSValue promise_reaction_job(JSContext *ctx, int argc,
|
|
JSValue *argv)
|
|
{
|
|
JSValue handler, arg, func;
|
|
JSValue res, res2;
|
|
BOOL is_reject;
|
|
|
|
assert(argc == 5);
|
|
handler = argv[2];
|
|
is_reject = JS_ToBool(ctx, argv[3]);
|
|
arg = argv[4];
|
|
|
|
promise_trace(ctx, "promise_reaction_job: is_reject=%d\n", is_reject);
|
|
|
|
if (JS_IsUndefined(handler)) {
|
|
if (is_reject) {
|
|
res = JS_Throw(ctx, js_dup(arg));
|
|
} else {
|
|
res = js_dup(arg);
|
|
}
|
|
} else {
|
|
res = JS_Call(ctx, handler, JS_UNDEFINED, 1, &arg);
|
|
}
|
|
is_reject = JS_IsException(res);
|
|
if (is_reject)
|
|
res = JS_GetException(ctx);
|
|
func = argv[is_reject];
|
|
/* as an extension, we support undefined as value to avoid
|
|
creating a dummy promise in the 'await' implementation of async
|
|
functions */
|
|
if (!JS_IsUndefined(func)) {
|
|
res2 = JS_Call(ctx, func, JS_UNDEFINED,
|
|
1, &res);
|
|
} else {
|
|
res2 = JS_UNDEFINED;
|
|
}
|
|
JS_FreeValue(ctx, res);
|
|
|
|
return res2;
|
|
}
|
|
|
|
void JS_SetHostPromiseRejectionTracker(JSRuntime *rt,
|
|
JSHostPromiseRejectionTracker *cb,
|
|
void *opaque)
|
|
{
|
|
rt->host_promise_rejection_tracker = cb;
|
|
rt->host_promise_rejection_tracker_opaque = opaque;
|
|
}
|
|
|
|
static void fulfill_or_reject_promise(JSContext *ctx, JSValue promise,
|
|
JSValue value, BOOL is_reject)
|
|
{
|
|
JSPromiseData *s = JS_GetOpaque(promise, JS_CLASS_PROMISE);
|
|
struct list_head *el, *el1;
|
|
JSPromiseReactionData *rd;
|
|
JSValue args[5];
|
|
|
|
if (!s || s->promise_state != JS_PROMISE_PENDING)
|
|
return; /* should never happen */
|
|
set_value(ctx, &s->promise_result, js_dup(value));
|
|
s->promise_state = JS_PROMISE_FULFILLED + is_reject;
|
|
|
|
promise_trace(ctx, "fulfill_or_reject_promise: is_reject=%d\n", is_reject);
|
|
|
|
if (s->promise_state == JS_PROMISE_REJECTED && !s->is_handled) {
|
|
JSRuntime *rt = ctx->rt;
|
|
if (rt->host_promise_rejection_tracker) {
|
|
rt->host_promise_rejection_tracker(ctx, promise, value, FALSE,
|
|
rt->host_promise_rejection_tracker_opaque);
|
|
}
|
|
}
|
|
|
|
list_for_each_safe(el, el1, &s->promise_reactions[is_reject]) {
|
|
rd = list_entry(el, JSPromiseReactionData, link);
|
|
args[0] = rd->resolving_funcs[0];
|
|
args[1] = rd->resolving_funcs[1];
|
|
args[2] = rd->handler;
|
|
args[3] = js_bool(is_reject);
|
|
args[4] = value;
|
|
JS_EnqueueJob(ctx, promise_reaction_job, 5, args);
|
|
list_del(&rd->link);
|
|
promise_reaction_data_free(ctx->rt, rd);
|
|
}
|
|
|
|
list_for_each_safe(el, el1, &s->promise_reactions[1 - is_reject]) {
|
|
rd = list_entry(el, JSPromiseReactionData, link);
|
|
list_del(&rd->link);
|
|
promise_reaction_data_free(ctx->rt, rd);
|
|
}
|
|
}
|
|
|
|
static void reject_promise(JSContext *ctx, JSValue promise,
|
|
JSValue value)
|
|
{
|
|
fulfill_or_reject_promise(ctx, promise, value, TRUE);
|
|
}
|
|
|
|
static JSValue js_promise_resolve_thenable_job(JSContext *ctx,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue promise, thenable, then;
|
|
JSValue args[2], res;
|
|
|
|
promise_trace(ctx, "js_promise_resolve_thenable_job\n");
|
|
|
|
assert(argc == 3);
|
|
promise = argv[0];
|
|
thenable = argv[1];
|
|
then = argv[2];
|
|
if (js_create_resolving_functions(ctx, args, promise) < 0)
|
|
return JS_EXCEPTION;
|
|
res = JS_Call(ctx, then, thenable, 2, args);
|
|
if (JS_IsException(res)) {
|
|
JSValue error = JS_GetException(ctx);
|
|
res = JS_Call(ctx, args[1], JS_UNDEFINED, 1, &error);
|
|
JS_FreeValue(ctx, error);
|
|
}
|
|
JS_FreeValue(ctx, args[0]);
|
|
JS_FreeValue(ctx, args[1]);
|
|
return res;
|
|
}
|
|
|
|
static void js_promise_resolve_function_free_resolved(JSRuntime *rt,
|
|
JSPromiseFunctionDataResolved *sr)
|
|
{
|
|
if (--sr->ref_count == 0) {
|
|
js_free_rt(rt, sr);
|
|
}
|
|
}
|
|
|
|
static int js_create_resolving_functions(JSContext *ctx,
|
|
JSValue *resolving_funcs,
|
|
JSValue promise)
|
|
|
|
{
|
|
JSValue obj;
|
|
JSPromiseFunctionData *s;
|
|
JSPromiseFunctionDataResolved *sr;
|
|
int i, ret;
|
|
|
|
sr = js_malloc(ctx, sizeof(*sr));
|
|
if (!sr)
|
|
return -1;
|
|
sr->ref_count = 1;
|
|
sr->already_resolved = FALSE; /* must be shared between the two functions */
|
|
ret = 0;
|
|
for(i = 0; i < 2; i++) {
|
|
obj = JS_NewObjectProtoClass(ctx, ctx->function_proto,
|
|
JS_CLASS_PROMISE_RESOLVE_FUNCTION + i);
|
|
if (JS_IsException(obj))
|
|
goto fail;
|
|
s = js_malloc(ctx, sizeof(*s));
|
|
if (!s) {
|
|
JS_FreeValue(ctx, obj);
|
|
fail:
|
|
|
|
if (i != 0)
|
|
JS_FreeValue(ctx, resolving_funcs[0]);
|
|
ret = -1;
|
|
break;
|
|
}
|
|
sr->ref_count++;
|
|
s->presolved = sr;
|
|
s->promise = js_dup(promise);
|
|
JS_SetOpaque(obj, s);
|
|
js_function_set_properties(ctx, obj, JS_ATOM_empty_string, 1);
|
|
resolving_funcs[i] = obj;
|
|
}
|
|
js_promise_resolve_function_free_resolved(ctx->rt, sr);
|
|
return ret;
|
|
}
|
|
|
|
static void js_promise_resolve_function_finalizer(JSRuntime *rt, JSValue val)
|
|
{
|
|
JSPromiseFunctionData *s = JS_VALUE_GET_OBJ(val)->u.promise_function_data;
|
|
if (s) {
|
|
js_promise_resolve_function_free_resolved(rt, s->presolved);
|
|
JS_FreeValueRT(rt, s->promise);
|
|
js_free_rt(rt, s);
|
|
}
|
|
}
|
|
|
|
static void js_promise_resolve_function_mark(JSRuntime *rt, JSValue val,
|
|
JS_MarkFunc *mark_func)
|
|
{
|
|
JSPromiseFunctionData *s = JS_VALUE_GET_OBJ(val)->u.promise_function_data;
|
|
if (s) {
|
|
JS_MarkValue(rt, s->promise, mark_func);
|
|
}
|
|
}
|
|
|
|
static JSValue js_promise_resolve_function_call(JSContext *ctx,
|
|
JSValue func_obj,
|
|
JSValue this_val,
|
|
int argc, JSValue *argv,
|
|
int flags)
|
|
{
|
|
JSObject *p = JS_VALUE_GET_OBJ(func_obj);
|
|
JSPromiseFunctionData *s;
|
|
JSValue resolution, args[3];
|
|
JSValue then;
|
|
BOOL is_reject;
|
|
|
|
s = p->u.promise_function_data;
|
|
if (!s || s->presolved->already_resolved)
|
|
return JS_UNDEFINED;
|
|
s->presolved->already_resolved = TRUE;
|
|
is_reject = p->class_id - JS_CLASS_PROMISE_RESOLVE_FUNCTION;
|
|
if (argc > 0)
|
|
resolution = argv[0];
|
|
else
|
|
resolution = JS_UNDEFINED;
|
|
#ifdef DUMP_PROMISE
|
|
if (check_dump_flag(ctx->rt, DUMP_PROMISE)) {
|
|
printf("js_promise_resolving_function_call: is_reject=%d resolution=", is_reject);
|
|
JS_DumpValue(ctx->rt, resolution);
|
|
printf("\n");
|
|
}
|
|
#endif
|
|
if (is_reject || !JS_IsObject(resolution)) {
|
|
goto done;
|
|
} else if (js_same_value(ctx, resolution, s->promise)) {
|
|
JS_ThrowTypeError(ctx, "promise self resolution");
|
|
goto fail_reject;
|
|
}
|
|
then = JS_GetProperty(ctx, resolution, JS_ATOM_then);
|
|
if (JS_IsException(then)) {
|
|
JSValue error;
|
|
fail_reject:
|
|
error = JS_GetException(ctx);
|
|
reject_promise(ctx, s->promise, error);
|
|
JS_FreeValue(ctx, error);
|
|
} else if (!JS_IsFunction(ctx, then)) {
|
|
JS_FreeValue(ctx, then);
|
|
done:
|
|
fulfill_or_reject_promise(ctx, s->promise, resolution, is_reject);
|
|
} else {
|
|
args[0] = s->promise;
|
|
args[1] = resolution;
|
|
args[2] = then;
|
|
JS_EnqueueJob(ctx, js_promise_resolve_thenable_job, 3, args);
|
|
JS_FreeValue(ctx, then);
|
|
}
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
static void js_promise_finalizer(JSRuntime *rt, JSValue val)
|
|
{
|
|
JSPromiseData *s = JS_GetOpaque(val, JS_CLASS_PROMISE);
|
|
struct list_head *el, *el1;
|
|
int i;
|
|
|
|
if (!s)
|
|
return;
|
|
for(i = 0; i < 2; i++) {
|
|
list_for_each_safe(el, el1, &s->promise_reactions[i]) {
|
|
JSPromiseReactionData *rd =
|
|
list_entry(el, JSPromiseReactionData, link);
|
|
promise_reaction_data_free(rt, rd);
|
|
}
|
|
}
|
|
JS_FreeValueRT(rt, s->promise_result);
|
|
js_free_rt(rt, s);
|
|
}
|
|
|
|
static void js_promise_mark(JSRuntime *rt, JSValue val,
|
|
JS_MarkFunc *mark_func)
|
|
{
|
|
JSPromiseData *s = JS_GetOpaque(val, JS_CLASS_PROMISE);
|
|
struct list_head *el;
|
|
int i;
|
|
|
|
if (!s)
|
|
return;
|
|
for(i = 0; i < 2; i++) {
|
|
list_for_each(el, &s->promise_reactions[i]) {
|
|
JSPromiseReactionData *rd =
|
|
list_entry(el, JSPromiseReactionData, link);
|
|
JS_MarkValue(rt, rd->resolving_funcs[0], mark_func);
|
|
JS_MarkValue(rt, rd->resolving_funcs[1], mark_func);
|
|
JS_MarkValue(rt, rd->handler, mark_func);
|
|
}
|
|
}
|
|
JS_MarkValue(rt, s->promise_result, mark_func);
|
|
}
|
|
|
|
static JSValue js_promise_constructor(JSContext *ctx, JSValue new_target,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue executor;
|
|
JSValue obj;
|
|
JSPromiseData *s;
|
|
JSValue args[2], ret;
|
|
int i;
|
|
|
|
executor = argv[0];
|
|
if (check_function(ctx, executor))
|
|
return JS_EXCEPTION;
|
|
obj = js_create_from_ctor(ctx, new_target, JS_CLASS_PROMISE);
|
|
if (JS_IsException(obj))
|
|
return JS_EXCEPTION;
|
|
s = js_mallocz(ctx, sizeof(*s));
|
|
if (!s)
|
|
goto fail;
|
|
s->promise_state = JS_PROMISE_PENDING;
|
|
s->is_handled = FALSE;
|
|
for(i = 0; i < 2; i++)
|
|
init_list_head(&s->promise_reactions[i]);
|
|
s->promise_result = JS_UNDEFINED;
|
|
JS_SetOpaque(obj, s);
|
|
if (js_create_resolving_functions(ctx, args, obj))
|
|
goto fail;
|
|
ret = JS_Call(ctx, executor, JS_UNDEFINED, 2, args);
|
|
if (JS_IsException(ret)) {
|
|
JSValue ret2, error;
|
|
error = JS_GetException(ctx);
|
|
ret2 = JS_Call(ctx, args[1], JS_UNDEFINED, 1, &error);
|
|
JS_FreeValue(ctx, error);
|
|
if (JS_IsException(ret2))
|
|
goto fail1;
|
|
JS_FreeValue(ctx, ret2);
|
|
}
|
|
JS_FreeValue(ctx, ret);
|
|
JS_FreeValue(ctx, args[0]);
|
|
JS_FreeValue(ctx, args[1]);
|
|
return obj;
|
|
fail1:
|
|
JS_FreeValue(ctx, args[0]);
|
|
JS_FreeValue(ctx, args[1]);
|
|
fail:
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue js_promise_executor(JSContext *ctx,
|
|
JSValue this_val,
|
|
int argc, JSValue *argv,
|
|
int magic, JSValue *func_data)
|
|
{
|
|
int i;
|
|
|
|
for(i = 0; i < 2; i++) {
|
|
if (!JS_IsUndefined(func_data[i]))
|
|
return JS_ThrowTypeError(ctx, "resolving function already set");
|
|
func_data[i] = js_dup(argv[i]);
|
|
}
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
static JSValue js_promise_executor_new(JSContext *ctx)
|
|
{
|
|
JSValue func_data[2];
|
|
|
|
func_data[0] = JS_UNDEFINED;
|
|
func_data[1] = JS_UNDEFINED;
|
|
return JS_NewCFunctionData(ctx, js_promise_executor, 2,
|
|
0, 2, func_data);
|
|
}
|
|
|
|
static JSValue js_new_promise_capability(JSContext *ctx,
|
|
JSValue *resolving_funcs,
|
|
JSValue ctor)
|
|
{
|
|
JSValue executor, result_promise;
|
|
JSCFunctionDataRecord *s;
|
|
int i;
|
|
|
|
executor = js_promise_executor_new(ctx);
|
|
if (JS_IsException(executor))
|
|
return executor;
|
|
|
|
if (JS_IsUndefined(ctor)) {
|
|
result_promise = js_promise_constructor(ctx, ctor, 1,
|
|
&executor);
|
|
} else {
|
|
result_promise = JS_CallConstructor(ctx, ctor, 1,
|
|
&executor);
|
|
}
|
|
if (JS_IsException(result_promise))
|
|
goto fail;
|
|
s = JS_GetOpaque(executor, JS_CLASS_C_FUNCTION_DATA);
|
|
for(i = 0; i < 2; i++) {
|
|
if (check_function(ctx, s->data[i]))
|
|
goto fail;
|
|
}
|
|
for(i = 0; i < 2; i++)
|
|
resolving_funcs[i] = js_dup(s->data[i]);
|
|
JS_FreeValue(ctx, executor);
|
|
return result_promise;
|
|
fail:
|
|
JS_FreeValue(ctx, executor);
|
|
JS_FreeValue(ctx, result_promise);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
JSValue JS_NewPromiseCapability(JSContext *ctx, JSValue *resolving_funcs)
|
|
{
|
|
return js_new_promise_capability(ctx, resolving_funcs, JS_UNDEFINED);
|
|
}
|
|
|
|
static JSValue js_promise_resolve(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv, int magic)
|
|
{
|
|
JSValue result_promise, resolving_funcs[2], ret;
|
|
BOOL is_reject = magic;
|
|
|
|
if (!JS_IsObject(this_val))
|
|
return JS_ThrowTypeErrorNotAnObject(ctx);
|
|
if (!is_reject && JS_GetOpaque(argv[0], JS_CLASS_PROMISE)) {
|
|
JSValue ctor;
|
|
BOOL is_same;
|
|
ctor = JS_GetProperty(ctx, argv[0], JS_ATOM_constructor);
|
|
if (JS_IsException(ctor))
|
|
return ctor;
|
|
is_same = js_same_value(ctx, ctor, this_val);
|
|
JS_FreeValue(ctx, ctor);
|
|
if (is_same)
|
|
return js_dup(argv[0]);
|
|
}
|
|
result_promise = js_new_promise_capability(ctx, resolving_funcs, this_val);
|
|
if (JS_IsException(result_promise))
|
|
return result_promise;
|
|
ret = JS_Call(ctx, resolving_funcs[is_reject], JS_UNDEFINED, 1, argv);
|
|
JS_FreeValue(ctx, resolving_funcs[0]);
|
|
JS_FreeValue(ctx, resolving_funcs[1]);
|
|
if (JS_IsException(ret)) {
|
|
JS_FreeValue(ctx, result_promise);
|
|
return ret;
|
|
}
|
|
JS_FreeValue(ctx, ret);
|
|
return result_promise;
|
|
}
|
|
|
|
static JSValue js_promise_withResolvers(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue result_promise, resolving_funcs[2], obj;
|
|
if (!JS_IsObject(this_val))
|
|
return JS_ThrowTypeErrorNotAnObject(ctx);
|
|
result_promise = js_new_promise_capability(ctx, resolving_funcs, this_val);
|
|
if (JS_IsException(result_promise))
|
|
return JS_EXCEPTION;
|
|
obj = JS_NewObject(ctx);
|
|
if (JS_IsException(obj)) {
|
|
JS_FreeValue(ctx, resolving_funcs[0]);
|
|
JS_FreeValue(ctx, resolving_funcs[1]);
|
|
JS_FreeValue(ctx, result_promise);
|
|
return JS_EXCEPTION;
|
|
}
|
|
JS_DefinePropertyValue(ctx, obj, JS_ATOM_promise, result_promise, JS_PROP_C_W_E);
|
|
JS_DefinePropertyValue(ctx, obj, JS_ATOM_resolve, resolving_funcs[0], JS_PROP_C_W_E);
|
|
JS_DefinePropertyValue(ctx, obj, JS_ATOM_reject, resolving_funcs[1], JS_PROP_C_W_E);
|
|
return obj;
|
|
}
|
|
|
|
static __exception int remainingElementsCount_add(JSContext *ctx,
|
|
JSValue resolve_element_env,
|
|
int addend)
|
|
{
|
|
JSValue val;
|
|
int remainingElementsCount;
|
|
|
|
val = JS_GetPropertyUint32(ctx, resolve_element_env, 0);
|
|
if (JS_IsException(val))
|
|
return -1;
|
|
if (JS_ToInt32Free(ctx, &remainingElementsCount, val))
|
|
return -1;
|
|
remainingElementsCount += addend;
|
|
if (JS_SetPropertyUint32(ctx, resolve_element_env, 0,
|
|
js_int32(remainingElementsCount)) < 0)
|
|
return -1;
|
|
return (remainingElementsCount == 0);
|
|
}
|
|
|
|
#define PROMISE_MAGIC_all 0
|
|
#define PROMISE_MAGIC_allSettled 1
|
|
#define PROMISE_MAGIC_any 2
|
|
|
|
static JSValue js_promise_all_resolve_element(JSContext *ctx,
|
|
JSValue this_val,
|
|
int argc, JSValue *argv,
|
|
int magic,
|
|
JSValue *func_data)
|
|
{
|
|
int resolve_type = magic & 3;
|
|
int is_reject = magic & 4;
|
|
BOOL alreadyCalled = JS_ToBool(ctx, func_data[0]);
|
|
JSValue values = func_data[2];
|
|
JSValue resolve = func_data[3];
|
|
JSValue resolve_element_env = func_data[4];
|
|
JSValue ret, obj;
|
|
int is_zero, index;
|
|
|
|
if (JS_ToInt32(ctx, &index, func_data[1]))
|
|
return JS_EXCEPTION;
|
|
if (alreadyCalled)
|
|
return JS_UNDEFINED;
|
|
func_data[0] = js_bool(TRUE);
|
|
|
|
if (resolve_type == PROMISE_MAGIC_allSettled) {
|
|
JSValue str;
|
|
|
|
obj = JS_NewObject(ctx);
|
|
if (JS_IsException(obj))
|
|
return JS_EXCEPTION;
|
|
str = js_new_string8(ctx, is_reject ? "rejected" : "fulfilled");
|
|
if (JS_IsException(str))
|
|
goto fail1;
|
|
if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_status,
|
|
str,
|
|
JS_PROP_C_W_E) < 0)
|
|
goto fail1;
|
|
if (JS_DefinePropertyValue(ctx, obj,
|
|
is_reject ? JS_ATOM_reason : JS_ATOM_value,
|
|
js_dup(argv[0]),
|
|
JS_PROP_C_W_E) < 0) {
|
|
fail1:
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
} else {
|
|
obj = js_dup(argv[0]);
|
|
}
|
|
if (JS_DefinePropertyValueUint32(ctx, values, index,
|
|
obj, JS_PROP_C_W_E) < 0)
|
|
return JS_EXCEPTION;
|
|
|
|
is_zero = remainingElementsCount_add(ctx, resolve_element_env, -1);
|
|
if (is_zero < 0)
|
|
return JS_EXCEPTION;
|
|
if (is_zero) {
|
|
if (resolve_type == PROMISE_MAGIC_any) {
|
|
JSValue error;
|
|
error = js_aggregate_error_constructor(ctx, values);
|
|
if (JS_IsException(error))
|
|
return JS_EXCEPTION;
|
|
ret = JS_Call(ctx, resolve, JS_UNDEFINED, 1, &error);
|
|
JS_FreeValue(ctx, error);
|
|
} else {
|
|
ret = JS_Call(ctx, resolve, JS_UNDEFINED, 1, &values);
|
|
}
|
|
if (JS_IsException(ret))
|
|
return ret;
|
|
JS_FreeValue(ctx, ret);
|
|
}
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
/* magic = 0: Promise.all 1: Promise.allSettled */
|
|
static JSValue js_promise_all(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv, int magic)
|
|
{
|
|
JSValue result_promise, resolving_funcs[2], item, next_promise, ret;
|
|
JSValue next_method = JS_UNDEFINED, values = JS_UNDEFINED;
|
|
JSValue resolve_element_env = JS_UNDEFINED, resolve_element, reject_element;
|
|
JSValue promise_resolve = JS_UNDEFINED, iter = JS_UNDEFINED;
|
|
JSValue then_args[2], resolve_element_data[5];
|
|
BOOL done;
|
|
int index, is_zero, is_promise_any = (magic == PROMISE_MAGIC_any);
|
|
|
|
if (!JS_IsObject(this_val))
|
|
return JS_ThrowTypeErrorNotAnObject(ctx);
|
|
result_promise = js_new_promise_capability(ctx, resolving_funcs, this_val);
|
|
if (JS_IsException(result_promise))
|
|
return result_promise;
|
|
promise_resolve = JS_GetProperty(ctx, this_val, JS_ATOM_resolve);
|
|
if (JS_IsException(promise_resolve) ||
|
|
check_function(ctx, promise_resolve))
|
|
goto fail_reject;
|
|
iter = JS_GetIterator(ctx, argv[0], FALSE);
|
|
if (JS_IsException(iter)) {
|
|
JSValue error;
|
|
fail_reject:
|
|
error = JS_GetException(ctx);
|
|
ret = JS_Call(ctx, resolving_funcs[1], JS_UNDEFINED, 1,
|
|
&error);
|
|
JS_FreeValue(ctx, error);
|
|
if (JS_IsException(ret))
|
|
goto fail;
|
|
JS_FreeValue(ctx, ret);
|
|
} else {
|
|
next_method = JS_GetProperty(ctx, iter, JS_ATOM_next);
|
|
if (JS_IsException(next_method))
|
|
goto fail_reject;
|
|
values = JS_NewArray(ctx);
|
|
if (JS_IsException(values))
|
|
goto fail_reject;
|
|
resolve_element_env = JS_NewArray(ctx);
|
|
if (JS_IsException(resolve_element_env))
|
|
goto fail_reject;
|
|
/* remainingElementsCount field */
|
|
if (JS_DefinePropertyValueUint32(ctx, resolve_element_env, 0,
|
|
js_int32(1),
|
|
JS_PROP_CONFIGURABLE | JS_PROP_ENUMERABLE | JS_PROP_WRITABLE) < 0)
|
|
goto fail_reject;
|
|
|
|
index = 0;
|
|
for(;;) {
|
|
/* XXX: conformance: should close the iterator if error on 'done'
|
|
access, but not on 'value' access */
|
|
item = JS_IteratorNext(ctx, iter, next_method, 0, NULL, &done);
|
|
if (JS_IsException(item))
|
|
goto fail_reject;
|
|
if (done)
|
|
break;
|
|
next_promise = JS_Call(ctx, promise_resolve,
|
|
this_val, 1, &item);
|
|
JS_FreeValue(ctx, item);
|
|
if (JS_IsException(next_promise)) {
|
|
fail_reject1:
|
|
JS_IteratorClose(ctx, iter, TRUE);
|
|
goto fail_reject;
|
|
}
|
|
resolve_element_data[0] = js_bool(FALSE);
|
|
resolve_element_data[1] = js_int32(index);
|
|
resolve_element_data[2] = values;
|
|
resolve_element_data[3] = resolving_funcs[is_promise_any];
|
|
resolve_element_data[4] = resolve_element_env;
|
|
resolve_element =
|
|
JS_NewCFunctionData(ctx, js_promise_all_resolve_element, 1,
|
|
magic, 5, resolve_element_data);
|
|
if (JS_IsException(resolve_element)) {
|
|
JS_FreeValue(ctx, next_promise);
|
|
goto fail_reject1;
|
|
}
|
|
|
|
if (magic == PROMISE_MAGIC_allSettled) {
|
|
reject_element =
|
|
JS_NewCFunctionData(ctx, js_promise_all_resolve_element, 1,
|
|
magic | 4, 5, resolve_element_data);
|
|
if (JS_IsException(reject_element)) {
|
|
JS_FreeValue(ctx, next_promise);
|
|
goto fail_reject1;
|
|
}
|
|
} else if (magic == PROMISE_MAGIC_any) {
|
|
if (JS_DefinePropertyValueUint32(ctx, values, index,
|
|
JS_UNDEFINED, JS_PROP_C_W_E) < 0)
|
|
goto fail_reject1;
|
|
reject_element = resolve_element;
|
|
resolve_element = js_dup(resolving_funcs[0]);
|
|
} else {
|
|
reject_element = js_dup(resolving_funcs[1]);
|
|
}
|
|
|
|
if (remainingElementsCount_add(ctx, resolve_element_env, 1) < 0) {
|
|
JS_FreeValue(ctx, next_promise);
|
|
JS_FreeValue(ctx, resolve_element);
|
|
JS_FreeValue(ctx, reject_element);
|
|
goto fail_reject1;
|
|
}
|
|
|
|
then_args[0] = resolve_element;
|
|
then_args[1] = reject_element;
|
|
ret = JS_InvokeFree(ctx, next_promise, JS_ATOM_then, 2, then_args);
|
|
JS_FreeValue(ctx, resolve_element);
|
|
JS_FreeValue(ctx, reject_element);
|
|
if (check_exception_free(ctx, ret))
|
|
goto fail_reject1;
|
|
index++;
|
|
}
|
|
|
|
is_zero = remainingElementsCount_add(ctx, resolve_element_env, -1);
|
|
if (is_zero < 0)
|
|
goto fail_reject;
|
|
if (is_zero) {
|
|
if (magic == PROMISE_MAGIC_any) {
|
|
JSValue error;
|
|
error = js_aggregate_error_constructor(ctx, values);
|
|
if (JS_IsException(error))
|
|
goto fail_reject;
|
|
JS_FreeValue(ctx, values);
|
|
values = error;
|
|
}
|
|
ret = JS_Call(ctx, resolving_funcs[is_promise_any], JS_UNDEFINED,
|
|
1, &values);
|
|
if (check_exception_free(ctx, ret))
|
|
goto fail_reject;
|
|
}
|
|
}
|
|
done:
|
|
JS_FreeValue(ctx, promise_resolve);
|
|
JS_FreeValue(ctx, resolve_element_env);
|
|
JS_FreeValue(ctx, values);
|
|
JS_FreeValue(ctx, next_method);
|
|
JS_FreeValue(ctx, iter);
|
|
JS_FreeValue(ctx, resolving_funcs[0]);
|
|
JS_FreeValue(ctx, resolving_funcs[1]);
|
|
return result_promise;
|
|
fail:
|
|
JS_FreeValue(ctx, result_promise);
|
|
result_promise = JS_EXCEPTION;
|
|
goto done;
|
|
}
|
|
|
|
static JSValue js_promise_race(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue result_promise, resolving_funcs[2], item, next_promise, ret;
|
|
JSValue next_method = JS_UNDEFINED, iter = JS_UNDEFINED;
|
|
JSValue promise_resolve = JS_UNDEFINED;
|
|
BOOL done;
|
|
|
|
if (!JS_IsObject(this_val))
|
|
return JS_ThrowTypeErrorNotAnObject(ctx);
|
|
result_promise = js_new_promise_capability(ctx, resolving_funcs, this_val);
|
|
if (JS_IsException(result_promise))
|
|
return result_promise;
|
|
promise_resolve = JS_GetProperty(ctx, this_val, JS_ATOM_resolve);
|
|
if (JS_IsException(promise_resolve) ||
|
|
check_function(ctx, promise_resolve))
|
|
goto fail_reject;
|
|
iter = JS_GetIterator(ctx, argv[0], FALSE);
|
|
if (JS_IsException(iter)) {
|
|
JSValue error;
|
|
fail_reject:
|
|
error = JS_GetException(ctx);
|
|
ret = JS_Call(ctx, resolving_funcs[1], JS_UNDEFINED, 1,
|
|
&error);
|
|
JS_FreeValue(ctx, error);
|
|
if (JS_IsException(ret))
|
|
goto fail;
|
|
JS_FreeValue(ctx, ret);
|
|
} else {
|
|
next_method = JS_GetProperty(ctx, iter, JS_ATOM_next);
|
|
if (JS_IsException(next_method))
|
|
goto fail_reject;
|
|
|
|
for(;;) {
|
|
/* XXX: conformance: should close the iterator if error on 'done'
|
|
access, but not on 'value' access */
|
|
item = JS_IteratorNext(ctx, iter, next_method, 0, NULL, &done);
|
|
if (JS_IsException(item))
|
|
goto fail_reject;
|
|
if (done)
|
|
break;
|
|
next_promise = JS_Call(ctx, promise_resolve,
|
|
this_val, 1, &item);
|
|
JS_FreeValue(ctx, item);
|
|
if (JS_IsException(next_promise)) {
|
|
fail_reject1:
|
|
JS_IteratorClose(ctx, iter, TRUE);
|
|
goto fail_reject;
|
|
}
|
|
ret = JS_InvokeFree(ctx, next_promise, JS_ATOM_then, 2,
|
|
resolving_funcs);
|
|
if (check_exception_free(ctx, ret))
|
|
goto fail_reject1;
|
|
}
|
|
}
|
|
done:
|
|
JS_FreeValue(ctx, promise_resolve);
|
|
JS_FreeValue(ctx, next_method);
|
|
JS_FreeValue(ctx, iter);
|
|
JS_FreeValue(ctx, resolving_funcs[0]);
|
|
JS_FreeValue(ctx, resolving_funcs[1]);
|
|
return result_promise;
|
|
fail:
|
|
//JS_FreeValue(ctx, next_method); // why not???
|
|
JS_FreeValue(ctx, result_promise);
|
|
result_promise = JS_EXCEPTION;
|
|
goto done;
|
|
}
|
|
|
|
static __exception int perform_promise_then(JSContext *ctx,
|
|
JSValue promise,
|
|
JSValue *resolve_reject,
|
|
JSValue *cap_resolving_funcs)
|
|
{
|
|
JSPromiseData *s = JS_GetOpaque(promise, JS_CLASS_PROMISE);
|
|
JSPromiseReactionData *rd_array[2], *rd;
|
|
int i, j;
|
|
|
|
rd_array[0] = NULL;
|
|
rd_array[1] = NULL;
|
|
for(i = 0; i < 2; i++) {
|
|
JSValue handler;
|
|
rd = js_mallocz(ctx, sizeof(*rd));
|
|
if (!rd) {
|
|
if (i == 1)
|
|
promise_reaction_data_free(ctx->rt, rd_array[0]);
|
|
return -1;
|
|
}
|
|
for(j = 0; j < 2; j++)
|
|
rd->resolving_funcs[j] = js_dup(cap_resolving_funcs[j]);
|
|
handler = resolve_reject[i];
|
|
if (!JS_IsFunction(ctx, handler))
|
|
handler = JS_UNDEFINED;
|
|
rd->handler = js_dup(handler);
|
|
rd_array[i] = rd;
|
|
}
|
|
|
|
if (s->promise_state == JS_PROMISE_PENDING) {
|
|
for(i = 0; i < 2; i++)
|
|
list_add_tail(&rd_array[i]->link, &s->promise_reactions[i]);
|
|
} else {
|
|
JSValue args[5];
|
|
if (s->promise_state == JS_PROMISE_REJECTED && !s->is_handled) {
|
|
JSRuntime *rt = ctx->rt;
|
|
if (rt->host_promise_rejection_tracker) {
|
|
rt->host_promise_rejection_tracker(ctx, promise, s->promise_result,
|
|
TRUE, rt->host_promise_rejection_tracker_opaque);
|
|
}
|
|
}
|
|
i = s->promise_state - JS_PROMISE_FULFILLED;
|
|
rd = rd_array[i];
|
|
args[0] = rd->resolving_funcs[0];
|
|
args[1] = rd->resolving_funcs[1];
|
|
args[2] = rd->handler;
|
|
args[3] = js_bool(i);
|
|
args[4] = s->promise_result;
|
|
JS_EnqueueJob(ctx, promise_reaction_job, 5, args);
|
|
for(i = 0; i < 2; i++)
|
|
promise_reaction_data_free(ctx->rt, rd_array[i]);
|
|
}
|
|
s->is_handled = TRUE;
|
|
return 0;
|
|
}
|
|
|
|
static JSValue js_promise_then(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue ctor, result_promise, resolving_funcs[2];
|
|
JSPromiseData *s;
|
|
int i, ret;
|
|
|
|
s = JS_GetOpaque2(ctx, this_val, JS_CLASS_PROMISE);
|
|
if (!s)
|
|
return JS_EXCEPTION;
|
|
|
|
ctor = JS_SpeciesConstructor(ctx, this_val, JS_UNDEFINED);
|
|
if (JS_IsException(ctor))
|
|
return ctor;
|
|
result_promise = js_new_promise_capability(ctx, resolving_funcs, ctor);
|
|
JS_FreeValue(ctx, ctor);
|
|
if (JS_IsException(result_promise))
|
|
return result_promise;
|
|
ret = perform_promise_then(ctx, this_val, argv,
|
|
resolving_funcs);
|
|
for(i = 0; i < 2; i++)
|
|
JS_FreeValue(ctx, resolving_funcs[i]);
|
|
if (ret) {
|
|
JS_FreeValue(ctx, result_promise);
|
|
return JS_EXCEPTION;
|
|
}
|
|
return result_promise;
|
|
}
|
|
|
|
static JSValue js_promise_catch(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue args[2];
|
|
args[0] = JS_UNDEFINED;
|
|
args[1] = argv[0];
|
|
return JS_Invoke(ctx, this_val, JS_ATOM_then, 2, args);
|
|
}
|
|
|
|
static JSValue js_promise_finally_value_thunk(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv,
|
|
int magic, JSValue *func_data)
|
|
{
|
|
return js_dup(func_data[0]);
|
|
}
|
|
|
|
static JSValue js_promise_finally_thrower(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv,
|
|
int magic, JSValue *func_data)
|
|
{
|
|
return JS_Throw(ctx, js_dup(func_data[0]));
|
|
}
|
|
|
|
static JSValue js_promise_then_finally_func(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv,
|
|
int magic, JSValue *func_data)
|
|
{
|
|
JSValue ctor = func_data[0];
|
|
JSValue onFinally = func_data[1];
|
|
JSValue res, promise, ret, then_func;
|
|
|
|
res = JS_Call(ctx, onFinally, JS_UNDEFINED, 0, NULL);
|
|
if (JS_IsException(res))
|
|
return res;
|
|
promise = js_promise_resolve(ctx, ctor, 1, &res, 0);
|
|
JS_FreeValue(ctx, res);
|
|
if (JS_IsException(promise))
|
|
return promise;
|
|
if (magic == 0) {
|
|
then_func = JS_NewCFunctionData(ctx, js_promise_finally_value_thunk, 0,
|
|
0, 1, argv);
|
|
} else {
|
|
then_func = JS_NewCFunctionData(ctx, js_promise_finally_thrower, 0,
|
|
0, 1, argv);
|
|
}
|
|
if (JS_IsException(then_func)) {
|
|
JS_FreeValue(ctx, promise);
|
|
return then_func;
|
|
}
|
|
ret = JS_InvokeFree(ctx, promise, JS_ATOM_then, 1, &then_func);
|
|
JS_FreeValue(ctx, then_func);
|
|
return ret;
|
|
}
|
|
|
|
static JSValue js_promise_finally(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue onFinally = argv[0];
|
|
JSValue ctor, ret;
|
|
JSValue then_funcs[2];
|
|
JSValue func_data[2];
|
|
int i;
|
|
|
|
ctor = JS_SpeciesConstructor(ctx, this_val, JS_UNDEFINED);
|
|
if (JS_IsException(ctor))
|
|
return ctor;
|
|
if (!JS_IsFunction(ctx, onFinally)) {
|
|
then_funcs[0] = js_dup(onFinally);
|
|
then_funcs[1] = js_dup(onFinally);
|
|
} else {
|
|
func_data[0] = ctor;
|
|
func_data[1] = onFinally;
|
|
for(i = 0; i < 2; i++) {
|
|
then_funcs[i] = JS_NewCFunctionData(ctx, js_promise_then_finally_func, 1, i, 2, func_data);
|
|
if (JS_IsException(then_funcs[i])) {
|
|
if (i == 1)
|
|
JS_FreeValue(ctx, then_funcs[0]);
|
|
JS_FreeValue(ctx, ctor);
|
|
return JS_EXCEPTION;
|
|
}
|
|
}
|
|
}
|
|
JS_FreeValue(ctx, ctor);
|
|
ret = JS_Invoke(ctx, this_val, JS_ATOM_then, 2, then_funcs);
|
|
JS_FreeValue(ctx, then_funcs[0]);
|
|
JS_FreeValue(ctx, then_funcs[1]);
|
|
return ret;
|
|
}
|
|
|
|
static const JSCFunctionListEntry js_promise_funcs[] = {
|
|
JS_CFUNC_MAGIC_DEF("resolve", 1, js_promise_resolve, 0 ),
|
|
JS_CFUNC_MAGIC_DEF("reject", 1, js_promise_resolve, 1 ),
|
|
JS_CFUNC_MAGIC_DEF("all", 1, js_promise_all, PROMISE_MAGIC_all ),
|
|
JS_CFUNC_MAGIC_DEF("allSettled", 1, js_promise_all, PROMISE_MAGIC_allSettled ),
|
|
JS_CFUNC_MAGIC_DEF("any", 1, js_promise_all, PROMISE_MAGIC_any ),
|
|
JS_CFUNC_DEF("race", 1, js_promise_race ),
|
|
JS_CFUNC_DEF("withResolvers", 0, js_promise_withResolvers ),
|
|
JS_CGETSET_DEF("[Symbol.species]", js_get_this, NULL),
|
|
};
|
|
|
|
static const JSCFunctionListEntry js_promise_proto_funcs[] = {
|
|
JS_CFUNC_DEF("then", 2, js_promise_then ),
|
|
JS_CFUNC_DEF("catch", 1, js_promise_catch ),
|
|
JS_CFUNC_DEF("finally", 1, js_promise_finally ),
|
|
JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Promise", JS_PROP_CONFIGURABLE ),
|
|
};
|
|
|
|
/* AsyncFunction */
|
|
static const JSCFunctionListEntry js_async_function_proto_funcs[] = {
|
|
JS_PROP_STRING_DEF("[Symbol.toStringTag]", "AsyncFunction", JS_PROP_CONFIGURABLE ),
|
|
};
|
|
|
|
static JSValue js_async_from_sync_iterator_unwrap(JSContext *ctx,
|
|
JSValue this_val,
|
|
int argc, JSValue *argv,
|
|
int magic, JSValue *func_data)
|
|
{
|
|
return js_create_iterator_result(ctx, js_dup(argv[0]),
|
|
JS_ToBool(ctx, func_data[0]));
|
|
}
|
|
|
|
static JSValue js_async_from_sync_iterator_unwrap_func_create(JSContext *ctx,
|
|
BOOL done)
|
|
{
|
|
JSValue func_data[1];
|
|
|
|
func_data[0] = js_bool(done);
|
|
return JS_NewCFunctionData(ctx, js_async_from_sync_iterator_unwrap,
|
|
1, 0, 1, func_data);
|
|
}
|
|
|
|
/* AsyncIteratorPrototype */
|
|
|
|
static const JSCFunctionListEntry js_async_iterator_proto_funcs[] = {
|
|
JS_CFUNC_DEF("[Symbol.asyncIterator]", 0, js_iterator_proto_iterator ),
|
|
};
|
|
|
|
/* AsyncFromSyncIteratorPrototype */
|
|
|
|
typedef struct JSAsyncFromSyncIteratorData {
|
|
JSValue sync_iter;
|
|
JSValue next_method;
|
|
} JSAsyncFromSyncIteratorData;
|
|
|
|
static void js_async_from_sync_iterator_finalizer(JSRuntime *rt, JSValue val)
|
|
{
|
|
JSAsyncFromSyncIteratorData *s =
|
|
JS_GetOpaque(val, JS_CLASS_ASYNC_FROM_SYNC_ITERATOR);
|
|
if (s) {
|
|
JS_FreeValueRT(rt, s->sync_iter);
|
|
JS_FreeValueRT(rt, s->next_method);
|
|
js_free_rt(rt, s);
|
|
}
|
|
}
|
|
|
|
static void js_async_from_sync_iterator_mark(JSRuntime *rt, JSValue val,
|
|
JS_MarkFunc *mark_func)
|
|
{
|
|
JSAsyncFromSyncIteratorData *s =
|
|
JS_GetOpaque(val, JS_CLASS_ASYNC_FROM_SYNC_ITERATOR);
|
|
if (s) {
|
|
JS_MarkValue(rt, s->sync_iter, mark_func);
|
|
JS_MarkValue(rt, s->next_method, mark_func);
|
|
}
|
|
}
|
|
|
|
static JSValue JS_CreateAsyncFromSyncIterator(JSContext *ctx,
|
|
JSValue sync_iter)
|
|
{
|
|
JSValue async_iter, next_method;
|
|
JSAsyncFromSyncIteratorData *s;
|
|
|
|
next_method = JS_GetProperty(ctx, sync_iter, JS_ATOM_next);
|
|
if (JS_IsException(next_method))
|
|
return JS_EXCEPTION;
|
|
async_iter = JS_NewObjectClass(ctx, JS_CLASS_ASYNC_FROM_SYNC_ITERATOR);
|
|
if (JS_IsException(async_iter)) {
|
|
JS_FreeValue(ctx, next_method);
|
|
return async_iter;
|
|
}
|
|
s = js_mallocz(ctx, sizeof(*s));
|
|
if (!s) {
|
|
JS_FreeValue(ctx, async_iter);
|
|
JS_FreeValue(ctx, next_method);
|
|
return JS_EXCEPTION;
|
|
}
|
|
s->sync_iter = js_dup(sync_iter);
|
|
s->next_method = next_method;
|
|
JS_SetOpaque(async_iter, s);
|
|
return async_iter;
|
|
}
|
|
|
|
static JSValue js_async_from_sync_iterator_next(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv,
|
|
int magic)
|
|
{
|
|
JSValue promise, resolving_funcs[2], value, err, method;
|
|
JSAsyncFromSyncIteratorData *s;
|
|
int done;
|
|
int is_reject;
|
|
|
|
promise = JS_NewPromiseCapability(ctx, resolving_funcs);
|
|
if (JS_IsException(promise))
|
|
return JS_EXCEPTION;
|
|
s = JS_GetOpaque(this_val, JS_CLASS_ASYNC_FROM_SYNC_ITERATOR);
|
|
if (!s) {
|
|
JS_ThrowTypeError(ctx, "not an Async-from-Sync Iterator");
|
|
goto reject;
|
|
}
|
|
|
|
if (magic == GEN_MAGIC_NEXT) {
|
|
method = js_dup(s->next_method);
|
|
} else {
|
|
method = JS_GetProperty(ctx, s->sync_iter,
|
|
magic == GEN_MAGIC_RETURN ? JS_ATOM_return :
|
|
JS_ATOM_throw);
|
|
if (JS_IsException(method))
|
|
goto reject;
|
|
if (JS_IsUndefined(method) || JS_IsNull(method)) {
|
|
if (magic == GEN_MAGIC_RETURN) {
|
|
err = js_create_iterator_result(ctx, js_dup(argv[0]), TRUE);
|
|
is_reject = 0;
|
|
} else {
|
|
err = js_dup(argv[0]);
|
|
is_reject = 1;
|
|
}
|
|
goto done_resolve;
|
|
}
|
|
}
|
|
value = JS_IteratorNext2(ctx, s->sync_iter, method,
|
|
argc >= 1 ? 1 : 0, argv, &done);
|
|
JS_FreeValue(ctx, method);
|
|
if (JS_IsException(value))
|
|
goto reject;
|
|
if (done == 2) {
|
|
JSValue obj = value;
|
|
value = JS_IteratorGetCompleteValue(ctx, obj, &done);
|
|
JS_FreeValue(ctx, obj);
|
|
if (JS_IsException(value))
|
|
goto reject;
|
|
}
|
|
|
|
if (JS_IsException(value)) {
|
|
JSValue res2;
|
|
reject:
|
|
err = JS_GetException(ctx);
|
|
is_reject = 1;
|
|
done_resolve:
|
|
res2 = JS_Call(ctx, resolving_funcs[is_reject], JS_UNDEFINED,
|
|
1, &err);
|
|
JS_FreeValue(ctx, err);
|
|
JS_FreeValue(ctx, res2);
|
|
JS_FreeValue(ctx, resolving_funcs[0]);
|
|
JS_FreeValue(ctx, resolving_funcs[1]);
|
|
return promise;
|
|
}
|
|
{
|
|
JSValue value_wrapper_promise, resolve_reject[2];
|
|
int res;
|
|
|
|
value_wrapper_promise = js_promise_resolve(ctx, ctx->promise_ctor,
|
|
1, &value, 0);
|
|
if (JS_IsException(value_wrapper_promise)) {
|
|
JS_FreeValue(ctx, value);
|
|
goto reject;
|
|
}
|
|
|
|
resolve_reject[0] =
|
|
js_async_from_sync_iterator_unwrap_func_create(ctx, done);
|
|
if (JS_IsException(resolve_reject[0])) {
|
|
JS_FreeValue(ctx, value_wrapper_promise);
|
|
goto fail;
|
|
}
|
|
JS_FreeValue(ctx, value);
|
|
resolve_reject[1] = JS_UNDEFINED;
|
|
|
|
res = perform_promise_then(ctx, value_wrapper_promise,
|
|
resolve_reject,
|
|
resolving_funcs);
|
|
JS_FreeValue(ctx, resolve_reject[0]);
|
|
JS_FreeValue(ctx, value_wrapper_promise);
|
|
JS_FreeValue(ctx, resolving_funcs[0]);
|
|
JS_FreeValue(ctx, resolving_funcs[1]);
|
|
if (res) {
|
|
JS_FreeValue(ctx, promise);
|
|
return JS_EXCEPTION;
|
|
}
|
|
}
|
|
return promise;
|
|
fail:
|
|
JS_FreeValue(ctx, value);
|
|
JS_FreeValue(ctx, resolving_funcs[0]);
|
|
JS_FreeValue(ctx, resolving_funcs[1]);
|
|
JS_FreeValue(ctx, promise);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static const JSCFunctionListEntry js_async_from_sync_iterator_proto_funcs[] = {
|
|
JS_CFUNC_MAGIC_DEF("next", 1, js_async_from_sync_iterator_next, GEN_MAGIC_NEXT ),
|
|
JS_CFUNC_MAGIC_DEF("return", 1, js_async_from_sync_iterator_next, GEN_MAGIC_RETURN ),
|
|
JS_CFUNC_MAGIC_DEF("throw", 1, js_async_from_sync_iterator_next, GEN_MAGIC_THROW ),
|
|
};
|
|
|
|
/* AsyncGeneratorFunction */
|
|
|
|
static const JSCFunctionListEntry js_async_generator_function_proto_funcs[] = {
|
|
JS_PROP_STRING_DEF("[Symbol.toStringTag]", "AsyncGeneratorFunction", JS_PROP_CONFIGURABLE ),
|
|
};
|
|
|
|
/* AsyncGenerator prototype */
|
|
|
|
static const JSCFunctionListEntry js_async_generator_proto_funcs[] = {
|
|
JS_CFUNC_MAGIC_DEF("next", 1, js_async_generator_next, GEN_MAGIC_NEXT ),
|
|
JS_CFUNC_MAGIC_DEF("return", 1, js_async_generator_next, GEN_MAGIC_RETURN ),
|
|
JS_CFUNC_MAGIC_DEF("throw", 1, js_async_generator_next, GEN_MAGIC_THROW ),
|
|
JS_PROP_STRING_DEF("[Symbol.toStringTag]", "AsyncGenerator", JS_PROP_CONFIGURABLE ),
|
|
};
|
|
|
|
static JSClassShortDef const js_async_class_def[] = {
|
|
{ JS_ATOM_Promise, js_promise_finalizer, js_promise_mark }, /* JS_CLASS_PROMISE */
|
|
{ JS_ATOM_PromiseResolveFunction, js_promise_resolve_function_finalizer, js_promise_resolve_function_mark }, /* JS_CLASS_PROMISE_RESOLVE_FUNCTION */
|
|
{ JS_ATOM_PromiseRejectFunction, js_promise_resolve_function_finalizer, js_promise_resolve_function_mark }, /* JS_CLASS_PROMISE_REJECT_FUNCTION */
|
|
{ JS_ATOM_AsyncFunction, js_bytecode_function_finalizer, js_bytecode_function_mark }, /* JS_CLASS_ASYNC_FUNCTION */
|
|
{ JS_ATOM_AsyncFunctionResolve, js_async_function_resolve_finalizer, js_async_function_resolve_mark }, /* JS_CLASS_ASYNC_FUNCTION_RESOLVE */
|
|
{ JS_ATOM_AsyncFunctionReject, js_async_function_resolve_finalizer, js_async_function_resolve_mark }, /* JS_CLASS_ASYNC_FUNCTION_REJECT */
|
|
{ JS_ATOM_empty_string, js_async_from_sync_iterator_finalizer, js_async_from_sync_iterator_mark }, /* JS_CLASS_ASYNC_FROM_SYNC_ITERATOR */
|
|
{ JS_ATOM_AsyncGeneratorFunction, js_bytecode_function_finalizer, js_bytecode_function_mark }, /* JS_CLASS_ASYNC_GENERATOR_FUNCTION */
|
|
{ JS_ATOM_AsyncGenerator, js_async_generator_finalizer, js_async_generator_mark }, /* JS_CLASS_ASYNC_GENERATOR */
|
|
};
|
|
|
|
void JS_AddIntrinsicPromise(JSContext *ctx)
|
|
{
|
|
JSRuntime *rt = ctx->rt;
|
|
JSValue obj1;
|
|
|
|
if (!JS_IsRegisteredClass(rt, JS_CLASS_PROMISE)) {
|
|
init_class_range(rt, js_async_class_def, JS_CLASS_PROMISE,
|
|
countof(js_async_class_def));
|
|
rt->class_array[JS_CLASS_PROMISE_RESOLVE_FUNCTION].call = js_promise_resolve_function_call;
|
|
rt->class_array[JS_CLASS_PROMISE_REJECT_FUNCTION].call = js_promise_resolve_function_call;
|
|
rt->class_array[JS_CLASS_ASYNC_FUNCTION].call = js_async_function_call;
|
|
rt->class_array[JS_CLASS_ASYNC_FUNCTION_RESOLVE].call = js_async_function_resolve_call;
|
|
rt->class_array[JS_CLASS_ASYNC_FUNCTION_REJECT].call = js_async_function_resolve_call;
|
|
rt->class_array[JS_CLASS_ASYNC_GENERATOR_FUNCTION].call = js_async_generator_function_call;
|
|
}
|
|
|
|
/* Promise */
|
|
ctx->class_proto[JS_CLASS_PROMISE] = JS_NewObject(ctx);
|
|
JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_PROMISE],
|
|
js_promise_proto_funcs,
|
|
countof(js_promise_proto_funcs));
|
|
obj1 = JS_NewCFunction2(ctx, js_promise_constructor, "Promise", 1,
|
|
JS_CFUNC_constructor, 0);
|
|
ctx->promise_ctor = js_dup(obj1);
|
|
JS_SetPropertyFunctionList(ctx, obj1,
|
|
js_promise_funcs,
|
|
countof(js_promise_funcs));
|
|
JS_NewGlobalCConstructor2(ctx, obj1, "Promise",
|
|
ctx->class_proto[JS_CLASS_PROMISE]);
|
|
|
|
/* Used to squelch a -Wcast-function-type warning. */
|
|
JSCFunctionType ft;
|
|
|
|
/* AsyncFunction */
|
|
ctx->class_proto[JS_CLASS_ASYNC_FUNCTION] = JS_NewObjectProto(ctx, ctx->function_proto);
|
|
ft.generic_magic = js_function_constructor;
|
|
obj1 = JS_NewCFunction3(ctx, ft.generic,
|
|
"AsyncFunction", 1,
|
|
JS_CFUNC_constructor_or_func_magic, JS_FUNC_ASYNC,
|
|
ctx->function_ctor);
|
|
JS_SetPropertyFunctionList(ctx,
|
|
ctx->class_proto[JS_CLASS_ASYNC_FUNCTION],
|
|
js_async_function_proto_funcs,
|
|
countof(js_async_function_proto_funcs));
|
|
JS_SetConstructor2(ctx, obj1, ctx->class_proto[JS_CLASS_ASYNC_FUNCTION],
|
|
0, JS_PROP_CONFIGURABLE);
|
|
JS_FreeValue(ctx, obj1);
|
|
|
|
/* AsyncIteratorPrototype */
|
|
ctx->async_iterator_proto = JS_NewObject(ctx);
|
|
JS_SetPropertyFunctionList(ctx, ctx->async_iterator_proto,
|
|
js_async_iterator_proto_funcs,
|
|
countof(js_async_iterator_proto_funcs));
|
|
|
|
/* AsyncFromSyncIteratorPrototype */
|
|
ctx->class_proto[JS_CLASS_ASYNC_FROM_SYNC_ITERATOR] =
|
|
JS_NewObjectProto(ctx, ctx->async_iterator_proto);
|
|
JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_ASYNC_FROM_SYNC_ITERATOR],
|
|
js_async_from_sync_iterator_proto_funcs,
|
|
countof(js_async_from_sync_iterator_proto_funcs));
|
|
|
|
/* AsyncGeneratorPrototype */
|
|
ctx->class_proto[JS_CLASS_ASYNC_GENERATOR] =
|
|
JS_NewObjectProto(ctx, ctx->async_iterator_proto);
|
|
JS_SetPropertyFunctionList(ctx,
|
|
ctx->class_proto[JS_CLASS_ASYNC_GENERATOR],
|
|
js_async_generator_proto_funcs,
|
|
countof(js_async_generator_proto_funcs));
|
|
|
|
/* AsyncGeneratorFunction */
|
|
ctx->class_proto[JS_CLASS_ASYNC_GENERATOR_FUNCTION] =
|
|
JS_NewObjectProto(ctx, ctx->function_proto);
|
|
ft.generic_magic = js_function_constructor;
|
|
obj1 = JS_NewCFunction3(ctx, ft.generic,
|
|
"AsyncGeneratorFunction", 1,
|
|
JS_CFUNC_constructor_or_func_magic,
|
|
JS_FUNC_ASYNC_GENERATOR,
|
|
ctx->function_ctor);
|
|
JS_SetPropertyFunctionList(ctx,
|
|
ctx->class_proto[JS_CLASS_ASYNC_GENERATOR_FUNCTION],
|
|
js_async_generator_function_proto_funcs,
|
|
countof(js_async_generator_function_proto_funcs));
|
|
JS_SetConstructor2(ctx, ctx->class_proto[JS_CLASS_ASYNC_GENERATOR_FUNCTION],
|
|
ctx->class_proto[JS_CLASS_ASYNC_GENERATOR],
|
|
JS_PROP_CONFIGURABLE, JS_PROP_CONFIGURABLE);
|
|
JS_SetConstructor2(ctx, obj1, ctx->class_proto[JS_CLASS_ASYNC_GENERATOR_FUNCTION],
|
|
0, JS_PROP_CONFIGURABLE);
|
|
JS_FreeValue(ctx, obj1);
|
|
}
|
|
|
|
/* URI handling */
|
|
|
|
static int string_get_hex(JSString *p, int k, int n) {
|
|
int c = 0, h;
|
|
while (n-- > 0) {
|
|
if ((h = from_hex(string_get(p, k++))) < 0)
|
|
return -1;
|
|
c = (c << 4) | h;
|
|
}
|
|
return c;
|
|
}
|
|
|
|
static int isURIReserved(int c) {
|
|
return c < 0x100 && memchr(";/?:@&=+$,#", c, sizeof(";/?:@&=+$,#") - 1) != NULL;
|
|
}
|
|
|
|
static int __attribute__((format(printf, 2, 3))) js_throw_URIError(JSContext *ctx, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
JS_ThrowError(ctx, JS_URI_ERROR, fmt, ap);
|
|
va_end(ap);
|
|
return -1;
|
|
}
|
|
|
|
static int hex_decode(JSContext *ctx, JSString *p, int k) {
|
|
int c;
|
|
|
|
if (k >= p->len || string_get(p, k) != '%')
|
|
return js_throw_URIError(ctx, "expecting %%");
|
|
if (k + 2 >= p->len || (c = string_get_hex(p, k + 1, 2)) < 0)
|
|
return js_throw_URIError(ctx, "expecting hex digit");
|
|
|
|
return c;
|
|
}
|
|
|
|
static JSValue js_global_decodeURI(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv, int isComponent)
|
|
{
|
|
JSValue str;
|
|
StringBuffer b_s, *b = &b_s;
|
|
JSString *p;
|
|
int k, c, c1, n, c_min;
|
|
|
|
str = JS_ToString(ctx, argv[0]);
|
|
if (JS_IsException(str))
|
|
return str;
|
|
|
|
string_buffer_init(ctx, b, 0);
|
|
|
|
p = JS_VALUE_GET_STRING(str);
|
|
for (k = 0; k < p->len;) {
|
|
c = string_get(p, k);
|
|
if (c == '%') {
|
|
c = hex_decode(ctx, p, k);
|
|
if (c < 0)
|
|
goto fail;
|
|
k += 3;
|
|
if (c < 0x80) {
|
|
if (!isComponent && isURIReserved(c)) {
|
|
c = '%';
|
|
k -= 2;
|
|
}
|
|
} else {
|
|
/* Decode URI-encoded UTF-8 sequence */
|
|
if (c >= 0xc0 && c <= 0xdf) {
|
|
n = 1;
|
|
c_min = 0x80;
|
|
c &= 0x1f;
|
|
} else if (c >= 0xe0 && c <= 0xef) {
|
|
n = 2;
|
|
c_min = 0x800;
|
|
c &= 0xf;
|
|
} else if (c >= 0xf0 && c <= 0xf7) {
|
|
n = 3;
|
|
c_min = 0x10000;
|
|
c &= 0x7;
|
|
} else {
|
|
n = 0;
|
|
c_min = 1;
|
|
c = 0;
|
|
}
|
|
while (n-- > 0) {
|
|
c1 = hex_decode(ctx, p, k);
|
|
if (c1 < 0)
|
|
goto fail;
|
|
k += 3;
|
|
if ((c1 & 0xc0) != 0x80) {
|
|
c = 0;
|
|
break;
|
|
}
|
|
c = (c << 6) | (c1 & 0x3f);
|
|
}
|
|
if (c < c_min || c > 0x10FFFF || is_surrogate(c)) {
|
|
js_throw_URIError(ctx, "malformed UTF-8");
|
|
goto fail;
|
|
}
|
|
}
|
|
} else {
|
|
k++;
|
|
}
|
|
string_buffer_putc(b, c);
|
|
}
|
|
JS_FreeValue(ctx, str);
|
|
return string_buffer_end(b);
|
|
|
|
fail:
|
|
JS_FreeValue(ctx, str);
|
|
string_buffer_free(b);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static int isUnescaped(int c) {
|
|
static char const unescaped_chars[] =
|
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
"abcdefghijklmnopqrstuvwxyz"
|
|
"0123456789"
|
|
"@*_+-./";
|
|
return c < 0x100 &&
|
|
memchr(unescaped_chars, c, sizeof(unescaped_chars) - 1);
|
|
}
|
|
|
|
static int isURIUnescaped(int c, int isComponent) {
|
|
return c < 0x100 &&
|
|
((c >= 0x61 && c <= 0x7a) ||
|
|
(c >= 0x41 && c <= 0x5a) ||
|
|
(c >= 0x30 && c <= 0x39) ||
|
|
memchr("-_.!~*'()", c, sizeof("-_.!~*'()") - 1) != NULL ||
|
|
(!isComponent && isURIReserved(c)));
|
|
}
|
|
|
|
static int encodeURI_hex(StringBuffer *b, int c) {
|
|
uint8_t buf[6];
|
|
int n = 0;
|
|
const char *hex = "0123456789ABCDEF";
|
|
|
|
buf[n++] = '%';
|
|
if (c >= 256) {
|
|
buf[n++] = 'u';
|
|
buf[n++] = hex[(c >> 12) & 15];
|
|
buf[n++] = hex[(c >> 8) & 15];
|
|
}
|
|
buf[n++] = hex[(c >> 4) & 15];
|
|
buf[n++] = hex[(c >> 0) & 15];
|
|
return string_buffer_write8(b, buf, n);
|
|
}
|
|
|
|
static JSValue js_global_encodeURI(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv,
|
|
int isComponent)
|
|
{
|
|
JSValue str;
|
|
StringBuffer b_s, *b = &b_s;
|
|
JSString *p;
|
|
int k, c, c1;
|
|
|
|
str = JS_ToString(ctx, argv[0]);
|
|
if (JS_IsException(str))
|
|
return str;
|
|
|
|
p = JS_VALUE_GET_STRING(str);
|
|
string_buffer_init(ctx, b, p->len);
|
|
for (k = 0; k < p->len;) {
|
|
c = string_get(p, k);
|
|
k++;
|
|
if (isURIUnescaped(c, isComponent)) {
|
|
string_buffer_putc16(b, c);
|
|
} else {
|
|
if (is_lo_surrogate(c)) {
|
|
js_throw_URIError(ctx, "invalid character");
|
|
goto fail;
|
|
} else if (is_hi_surrogate(c)) {
|
|
if (k >= p->len) {
|
|
js_throw_URIError(ctx, "expecting surrogate pair");
|
|
goto fail;
|
|
}
|
|
c1 = string_get(p, k);
|
|
k++;
|
|
if (!is_lo_surrogate(c1)) {
|
|
js_throw_URIError(ctx, "expecting surrogate pair");
|
|
goto fail;
|
|
}
|
|
c = from_surrogate(c, c1);
|
|
}
|
|
if (c < 0x80) {
|
|
encodeURI_hex(b, c);
|
|
} else {
|
|
/* XXX: use C UTF-8 conversion ? */
|
|
if (c < 0x800) {
|
|
encodeURI_hex(b, (c >> 6) | 0xc0);
|
|
} else {
|
|
if (c < 0x10000) {
|
|
encodeURI_hex(b, (c >> 12) | 0xe0);
|
|
} else {
|
|
encodeURI_hex(b, (c >> 18) | 0xf0);
|
|
encodeURI_hex(b, ((c >> 12) & 0x3f) | 0x80);
|
|
}
|
|
encodeURI_hex(b, ((c >> 6) & 0x3f) | 0x80);
|
|
}
|
|
encodeURI_hex(b, (c & 0x3f) | 0x80);
|
|
}
|
|
}
|
|
}
|
|
JS_FreeValue(ctx, str);
|
|
return string_buffer_end(b);
|
|
|
|
fail:
|
|
JS_FreeValue(ctx, str);
|
|
string_buffer_free(b);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue js_global_escape(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue str;
|
|
StringBuffer b_s, *b = &b_s;
|
|
JSString *p;
|
|
int i, len, c;
|
|
|
|
str = JS_ToString(ctx, argv[0]);
|
|
if (JS_IsException(str))
|
|
return str;
|
|
|
|
p = JS_VALUE_GET_STRING(str);
|
|
string_buffer_init(ctx, b, p->len);
|
|
for (i = 0, len = p->len; i < len; i++) {
|
|
c = string_get(p, i);
|
|
if (isUnescaped(c)) {
|
|
string_buffer_putc16(b, c);
|
|
} else {
|
|
encodeURI_hex(b, c);
|
|
}
|
|
}
|
|
JS_FreeValue(ctx, str);
|
|
return string_buffer_end(b);
|
|
}
|
|
|
|
static JSValue js_global_unescape(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue str;
|
|
StringBuffer b_s, *b = &b_s;
|
|
JSString *p;
|
|
int i, len, c, n;
|
|
|
|
str = JS_ToString(ctx, argv[0]);
|
|
if (JS_IsException(str))
|
|
return str;
|
|
|
|
string_buffer_init(ctx, b, 0);
|
|
p = JS_VALUE_GET_STRING(str);
|
|
for (i = 0, len = p->len; i < len; i++) {
|
|
c = string_get(p, i);
|
|
if (c == '%') {
|
|
if (i + 6 <= len
|
|
&& string_get(p, i + 1) == 'u'
|
|
&& (n = string_get_hex(p, i + 2, 4)) >= 0) {
|
|
c = n;
|
|
i += 6 - 1;
|
|
} else
|
|
if (i + 3 <= len
|
|
&& (n = string_get_hex(p, i + 1, 2)) >= 0) {
|
|
c = n;
|
|
i += 3 - 1;
|
|
}
|
|
}
|
|
string_buffer_putc16(b, c);
|
|
}
|
|
JS_FreeValue(ctx, str);
|
|
return string_buffer_end(b);
|
|
}
|
|
|
|
/* global object */
|
|
|
|
static const JSCFunctionListEntry js_global_funcs[] = {
|
|
JS_CFUNC_DEF("parseInt", 2, js_parseInt ),
|
|
JS_CFUNC_DEF("parseFloat", 1, js_parseFloat ),
|
|
JS_CFUNC_DEF("isNaN", 1, js_global_isNaN ),
|
|
JS_CFUNC_DEF("isFinite", 1, js_global_isFinite ),
|
|
JS_CFUNC_DEF("queueMicrotask", 1, js_global_queueMicrotask ),
|
|
|
|
JS_CFUNC_MAGIC_DEF("decodeURI", 1, js_global_decodeURI, 0 ),
|
|
JS_CFUNC_MAGIC_DEF("decodeURIComponent", 1, js_global_decodeURI, 1 ),
|
|
JS_CFUNC_MAGIC_DEF("encodeURI", 1, js_global_encodeURI, 0 ),
|
|
JS_CFUNC_MAGIC_DEF("encodeURIComponent", 1, js_global_encodeURI, 1 ),
|
|
JS_CFUNC_DEF("escape", 1, js_global_escape ),
|
|
JS_CFUNC_DEF("unescape", 1, js_global_unescape ),
|
|
JS_PROP_DOUBLE_DEF("Infinity", 1.0 / 0.0, 0 ),
|
|
JS_PROP_DOUBLE_DEF("NaN", NAN, 0 ),
|
|
JS_PROP_UNDEFINED_DEF("undefined", 0 ),
|
|
JS_PROP_STRING_DEF("[Symbol.toStringTag]", "global", JS_PROP_CONFIGURABLE ),
|
|
};
|
|
|
|
/* Date */
|
|
|
|
static int64_t math_mod(int64_t a, int64_t b) {
|
|
/* return positive modulo */
|
|
int64_t m = a % b;
|
|
return m + (m < 0) * b;
|
|
}
|
|
|
|
static int64_t floor_div(int64_t a, int64_t b) {
|
|
/* integer division rounding toward -Infinity */
|
|
int64_t m = a % b;
|
|
return (a - (m + (m < 0) * b)) / b;
|
|
}
|
|
|
|
static JSValue js_Date_parse(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv);
|
|
|
|
static __exception int JS_ThisTimeValue(JSContext *ctx, double *valp, JSValue this_val)
|
|
{
|
|
if (JS_VALUE_GET_TAG(this_val) == JS_TAG_OBJECT) {
|
|
JSObject *p = JS_VALUE_GET_OBJ(this_val);
|
|
if (p->class_id == JS_CLASS_DATE && JS_IsNumber(p->u.object_data))
|
|
return JS_ToFloat64(ctx, valp, p->u.object_data);
|
|
}
|
|
JS_ThrowTypeError(ctx, "not a Date object");
|
|
return -1;
|
|
}
|
|
|
|
static JSValue JS_SetThisTimeValue(JSContext *ctx, JSValue this_val, double v)
|
|
{
|
|
if (JS_VALUE_GET_TAG(this_val) == JS_TAG_OBJECT) {
|
|
JSObject *p = JS_VALUE_GET_OBJ(this_val);
|
|
if (p->class_id == JS_CLASS_DATE) {
|
|
JS_FreeValue(ctx, p->u.object_data);
|
|
p->u.object_data = js_float64(v);
|
|
return js_dup(p->u.object_data);
|
|
}
|
|
}
|
|
return JS_ThrowTypeError(ctx, "not a Date object");
|
|
}
|
|
|
|
static int64_t days_from_year(int64_t y) {
|
|
return 365 * (y - 1970) + floor_div(y - 1969, 4) -
|
|
floor_div(y - 1901, 100) + floor_div(y - 1601, 400);
|
|
}
|
|
|
|
static int64_t days_in_year(int64_t y) {
|
|
return 365 + !(y % 4) - !(y % 100) + !(y % 400);
|
|
}
|
|
|
|
/* return the year, update days */
|
|
static int64_t year_from_days(int64_t *days) {
|
|
int64_t y, d1, nd, d = *days;
|
|
y = floor_div(d * 10000, 3652425) + 1970;
|
|
/* the initial approximation is very good, so only a few
|
|
iterations are necessary */
|
|
for(;;) {
|
|
d1 = d - days_from_year(y);
|
|
if (d1 < 0) {
|
|
y--;
|
|
d1 += days_in_year(y);
|
|
} else {
|
|
nd = days_in_year(y);
|
|
if (d1 < nd)
|
|
break;
|
|
d1 -= nd;
|
|
y++;
|
|
}
|
|
}
|
|
*days = d1;
|
|
return y;
|
|
}
|
|
|
|
static int const month_days[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
|
|
static char const month_names[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
|
|
static char const day_names[] = "SunMonTueWedThuFriSat";
|
|
|
|
static __exception int get_date_fields(JSContext *ctx, JSValue obj,
|
|
double fields[minimum_length(9)],
|
|
int is_local, int force)
|
|
{
|
|
double dval;
|
|
int64_t d, days, wd, y, i, md, h, m, s, ms, tz = 0;
|
|
|
|
if (JS_ThisTimeValue(ctx, &dval, obj))
|
|
return -1;
|
|
|
|
if (isnan(dval)) {
|
|
if (!force)
|
|
return FALSE; /* NaN */
|
|
d = 0; /* initialize all fields to 0 */
|
|
} else {
|
|
d = dval; /* assuming -8.64e15 <= dval <= -8.64e15 */
|
|
if (is_local) {
|
|
tz = -getTimezoneOffset(d);
|
|
d += tz * 60000;
|
|
}
|
|
}
|
|
|
|
/* result is >= 0, we can use % */
|
|
h = math_mod(d, 86400000);
|
|
days = (d - h) / 86400000;
|
|
ms = h % 1000;
|
|
h = (h - ms) / 1000;
|
|
s = h % 60;
|
|
h = (h - s) / 60;
|
|
m = h % 60;
|
|
h = (h - m) / 60;
|
|
wd = math_mod(days + 4, 7); /* week day */
|
|
y = year_from_days(&days);
|
|
|
|
for(i = 0; i < 11; i++) {
|
|
md = month_days[i];
|
|
if (i == 1)
|
|
md += days_in_year(y) - 365;
|
|
if (days < md)
|
|
break;
|
|
days -= md;
|
|
}
|
|
fields[0] = y;
|
|
fields[1] = i;
|
|
fields[2] = days + 1;
|
|
fields[3] = h;
|
|
fields[4] = m;
|
|
fields[5] = s;
|
|
fields[6] = ms;
|
|
fields[7] = wd;
|
|
fields[8] = tz;
|
|
return TRUE;
|
|
}
|
|
|
|
static double time_clip(double t) {
|
|
if (t >= -8.64e15 && t <= 8.64e15)
|
|
return trunc(t) + 0.0; /* convert -0 to +0 */
|
|
else
|
|
return NAN;
|
|
}
|
|
|
|
/* The spec mandates the use of 'double' and it specifies the order
|
|
of the operations */
|
|
static double set_date_fields(double fields[minimum_length(7)], int is_local) {
|
|
double y, m, dt, ym, mn, day, h, s, milli, time, tv;
|
|
int yi, mi, i;
|
|
int64_t days;
|
|
volatile double temp; /* enforce evaluation order */
|
|
|
|
/* emulate 21.4.1.15 MakeDay ( year, month, date ) */
|
|
y = fields[0];
|
|
m = fields[1];
|
|
dt = fields[2];
|
|
ym = y + floor(m / 12);
|
|
mn = fmod(m, 12);
|
|
if (mn < 0)
|
|
mn += 12;
|
|
if (ym < -271821 || ym > 275760)
|
|
return NAN;
|
|
|
|
yi = ym;
|
|
mi = mn;
|
|
days = days_from_year(yi);
|
|
for(i = 0; i < mi; i++) {
|
|
days += month_days[i];
|
|
if (i == 1)
|
|
days += days_in_year(yi) - 365;
|
|
}
|
|
day = days + dt - 1;
|
|
|
|
/* emulate 21.4.1.14 MakeTime ( hour, min, sec, ms ) */
|
|
h = fields[3];
|
|
m = fields[4];
|
|
s = fields[5];
|
|
milli = fields[6];
|
|
/* Use a volatile intermediary variable to ensure order of evaluation
|
|
* as specified in ECMA. This fixes a test262 error on
|
|
* test262/test/built-ins/Date/UTC/fp-evaluation-order.js.
|
|
* Without the volatile qualifier, the compile can generate code
|
|
* that performs the computation in a different order or with instructions
|
|
* that produce a different result such as FMA (float multiply and add).
|
|
*/
|
|
time = h * 3600000;
|
|
time += (temp = m * 60000);
|
|
time += (temp = s * 1000);
|
|
time += milli;
|
|
|
|
/* emulate 21.4.1.16 MakeDate ( day, time ) */
|
|
tv = (temp = day * 86400000) + time; /* prevent generation of FMA */
|
|
if (!isfinite(tv))
|
|
return NAN;
|
|
|
|
/* adjust for local time and clip */
|
|
if (is_local) {
|
|
int64_t ti = tv < INT64_MIN ? INT64_MIN : tv >= 0x1p63 ? INT64_MAX : (int64_t)tv;
|
|
tv += getTimezoneOffset(ti) * 60000;
|
|
}
|
|
return time_clip(tv);
|
|
}
|
|
|
|
static JSValue get_date_field(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv, int magic)
|
|
{
|
|
// get_date_field(obj, n, is_local)
|
|
double fields[9];
|
|
int res, n, is_local;
|
|
|
|
is_local = magic & 0x0F;
|
|
n = (magic >> 4) & 0x0F;
|
|
res = get_date_fields(ctx, this_val, fields, is_local, 0);
|
|
if (res < 0)
|
|
return JS_EXCEPTION;
|
|
if (!res)
|
|
return JS_NAN;
|
|
|
|
if (magic & 0x100) { // getYear
|
|
fields[0] -= 1900;
|
|
}
|
|
return js_number(fields[n]);
|
|
}
|
|
|
|
static JSValue set_date_field(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv, int magic)
|
|
{
|
|
// _field(obj, first_field, end_field, args, is_local)
|
|
double fields[9];
|
|
int res, first_field, end_field, is_local, i, n;
|
|
double d, a;
|
|
|
|
d = NAN;
|
|
first_field = (magic >> 8) & 0x0F;
|
|
end_field = (magic >> 4) & 0x0F;
|
|
is_local = magic & 0x0F;
|
|
|
|
res = get_date_fields(ctx, this_val, fields, is_local, first_field == 0);
|
|
if (res < 0)
|
|
return JS_EXCEPTION;
|
|
|
|
// Argument coercion is observable and must be done unconditionally.
|
|
n = min_int(argc, end_field - first_field);
|
|
for(i = 0; i < n; i++) {
|
|
if (JS_ToFloat64(ctx, &a, argv[i]))
|
|
return JS_EXCEPTION;
|
|
if (!isfinite(a))
|
|
res = FALSE;
|
|
fields[first_field + i] = trunc(a);
|
|
}
|
|
|
|
if (res && argc > 0)
|
|
d = set_date_fields(fields, is_local);
|
|
|
|
return JS_SetThisTimeValue(ctx, this_val, d);
|
|
}
|
|
|
|
/* fmt:
|
|
0: toUTCString: "Tue, 02 Jan 2018 23:04:46 GMT"
|
|
1: toString: "Wed Jan 03 2018 00:05:22 GMT+0100 (CET)"
|
|
2: toISOString: "2018-01-02T23:02:56.927Z"
|
|
3: toLocaleString: "1/2/2018, 11:40:40 PM"
|
|
part: 1=date, 2=time 3=all
|
|
XXX: should use a variant of strftime().
|
|
*/
|
|
static JSValue get_date_string(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv, int magic)
|
|
{
|
|
// _string(obj, fmt, part)
|
|
char buf[64];
|
|
double fields[9];
|
|
int res, fmt, part, pos;
|
|
int y, mon, d, h, m, s, ms, wd, tz;
|
|
|
|
fmt = (magic >> 4) & 0x0F;
|
|
part = magic & 0x0F;
|
|
|
|
res = get_date_fields(ctx, this_val, fields, fmt & 1, 0);
|
|
if (res < 0)
|
|
return JS_EXCEPTION;
|
|
if (!res) {
|
|
if (fmt == 2)
|
|
return JS_ThrowRangeError(ctx, "Date value is NaN");
|
|
else
|
|
return js_new_string8(ctx, "Invalid Date");
|
|
}
|
|
|
|
y = fields[0];
|
|
mon = fields[1];
|
|
d = fields[2];
|
|
h = fields[3];
|
|
m = fields[4];
|
|
s = fields[5];
|
|
ms = fields[6];
|
|
wd = fields[7];
|
|
tz = fields[8];
|
|
|
|
pos = 0;
|
|
|
|
if (part & 1) { /* date part */
|
|
switch(fmt) {
|
|
case 0:
|
|
pos += snprintf(buf + pos, sizeof(buf) - pos,
|
|
"%.3s, %02d %.3s %0*d ",
|
|
day_names + wd * 3, d,
|
|
month_names + mon * 3, 4 + (y < 0), y);
|
|
break;
|
|
case 1:
|
|
pos += snprintf(buf + pos, sizeof(buf) - pos,
|
|
"%.3s %.3s %02d %0*d",
|
|
day_names + wd * 3,
|
|
month_names + mon * 3, d, 4 + (y < 0), y);
|
|
if (part == 3) {
|
|
buf[pos++] = ' ';
|
|
}
|
|
break;
|
|
case 2:
|
|
if (y >= 0 && y <= 9999) {
|
|
pos += snprintf(buf + pos, sizeof(buf) - pos,
|
|
"%04d", y);
|
|
} else {
|
|
pos += snprintf(buf + pos, sizeof(buf) - pos,
|
|
"%+07d", y);
|
|
}
|
|
pos += snprintf(buf + pos, sizeof(buf) - pos,
|
|
"-%02d-%02dT", mon + 1, d);
|
|
break;
|
|
case 3:
|
|
pos += snprintf(buf + pos, sizeof(buf) - pos,
|
|
"%02d/%02d/%0*d", mon + 1, d, 4 + (y < 0), y);
|
|
if (part == 3) {
|
|
buf[pos++] = ',';
|
|
buf[pos++] = ' ';
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (part & 2) { /* time part */
|
|
switch(fmt) {
|
|
case 0:
|
|
pos += snprintf(buf + pos, sizeof(buf) - pos,
|
|
"%02d:%02d:%02d GMT", h, m, s);
|
|
break;
|
|
case 1:
|
|
pos += snprintf(buf + pos, sizeof(buf) - pos,
|
|
"%02d:%02d:%02d GMT", h, m, s);
|
|
if (tz < 0) {
|
|
buf[pos++] = '-';
|
|
tz = -tz;
|
|
} else {
|
|
buf[pos++] = '+';
|
|
}
|
|
/* tz is >= 0, can use % */
|
|
pos += snprintf(buf + pos, sizeof(buf) - pos,
|
|
"%02d%02d", tz / 60, tz % 60);
|
|
/* XXX: tack the time zone code? */
|
|
break;
|
|
case 2:
|
|
pos += snprintf(buf + pos, sizeof(buf) - pos,
|
|
"%02d:%02d:%02d.%03dZ", h, m, s, ms);
|
|
break;
|
|
case 3:
|
|
pos += snprintf(buf + pos, sizeof(buf) - pos,
|
|
"%02d:%02d:%02d %cM", (h + 11) % 12 + 1, m, s,
|
|
(h < 12) ? 'A' : 'P');
|
|
break;
|
|
}
|
|
}
|
|
if (!pos) {
|
|
// XXX: should throw exception?
|
|
return JS_AtomToString(ctx, JS_ATOM_empty_string);
|
|
}
|
|
return js_new_string8_len(ctx, buf, pos);
|
|
}
|
|
|
|
/* OS dependent: return the UTC time in ms since 1970. */
|
|
static int64_t date_now(void) {
|
|
return js__gettimeofday_us() / 1000;
|
|
}
|
|
|
|
static JSValue js_date_constructor(JSContext *ctx, JSValue new_target,
|
|
int argc, JSValue *argv)
|
|
{
|
|
// Date(y, mon, d, h, m, s, ms)
|
|
JSValue rv;
|
|
int i, n;
|
|
double a, val;
|
|
|
|
if (JS_IsUndefined(new_target)) {
|
|
/* invoked as function */
|
|
argc = 0;
|
|
}
|
|
n = argc;
|
|
if (n == 0) {
|
|
val = date_now();
|
|
} else if (n == 1) {
|
|
JSValue v, dv;
|
|
if (JS_VALUE_GET_TAG(argv[0]) == JS_TAG_OBJECT) {
|
|
JSObject *p = JS_VALUE_GET_OBJ(argv[0]);
|
|
if (p->class_id == JS_CLASS_DATE && JS_IsNumber(p->u.object_data)) {
|
|
if (JS_ToFloat64(ctx, &val, p->u.object_data))
|
|
return JS_EXCEPTION;
|
|
val = time_clip(val);
|
|
goto has_val;
|
|
}
|
|
}
|
|
v = JS_ToPrimitive(ctx, argv[0], HINT_NONE);
|
|
if (JS_IsString(v)) {
|
|
dv = js_Date_parse(ctx, JS_UNDEFINED, 1, &v);
|
|
JS_FreeValue(ctx, v);
|
|
if (JS_IsException(dv))
|
|
return JS_EXCEPTION;
|
|
if (JS_ToFloat64Free(ctx, &val, dv))
|
|
return JS_EXCEPTION;
|
|
} else {
|
|
if (JS_ToFloat64Free(ctx, &val, v))
|
|
return JS_EXCEPTION;
|
|
}
|
|
val = time_clip(val);
|
|
} else {
|
|
double fields[] = { 0, 0, 1, 0, 0, 0, 0 };
|
|
if (n > 7)
|
|
n = 7;
|
|
for(i = 0; i < n; i++) {
|
|
if (JS_ToFloat64(ctx, &a, argv[i]))
|
|
return JS_EXCEPTION;
|
|
if (!isfinite(a))
|
|
break;
|
|
fields[i] = trunc(a);
|
|
if (i == 0 && fields[0] >= 0 && fields[0] < 100)
|
|
fields[0] += 1900;
|
|
}
|
|
val = (i == n) ? set_date_fields(fields, 1) : NAN;
|
|
}
|
|
has_val:
|
|
rv = js_create_from_ctor(ctx, new_target, JS_CLASS_DATE);
|
|
if (!JS_IsException(rv))
|
|
JS_SetObjectData(ctx, rv, js_float64(val));
|
|
if (!JS_IsException(rv) && JS_IsUndefined(new_target)) {
|
|
/* invoked as a function, return (new Date()).toString(); */
|
|
JSValue s;
|
|
s = get_date_string(ctx, rv, 0, NULL, 0x13);
|
|
JS_FreeValue(ctx, rv);
|
|
rv = s;
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
static JSValue js_Date_UTC(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
// UTC(y, mon, d, h, m, s, ms)
|
|
double fields[] = { 0, 0, 1, 0, 0, 0, 0 };
|
|
int i, n;
|
|
double a;
|
|
|
|
n = argc;
|
|
if (n == 0)
|
|
return JS_NAN;
|
|
if (n > 7)
|
|
n = 7;
|
|
for(i = 0; i < n; i++) {
|
|
if (JS_ToFloat64(ctx, &a, argv[i]))
|
|
return JS_EXCEPTION;
|
|
if (!isfinite(a))
|
|
return JS_NAN;
|
|
fields[i] = trunc(a);
|
|
if (i == 0 && fields[0] >= 0 && fields[0] < 100)
|
|
fields[0] += 1900;
|
|
}
|
|
return js_float64(set_date_fields(fields, 0));
|
|
}
|
|
|
|
/* Date string parsing */
|
|
|
|
static BOOL string_skip_char(const uint8_t *sp, int *pp, int c) {
|
|
if (sp[*pp] == c) {
|
|
*pp += 1;
|
|
return TRUE;
|
|
} else {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* skip spaces, update offset, return next char */
|
|
static int string_skip_spaces(const uint8_t *sp, int *pp) {
|
|
int c;
|
|
while ((c = sp[*pp]) == ' ')
|
|
*pp += 1;
|
|
return c;
|
|
}
|
|
|
|
/* skip dashes dots and commas */
|
|
static int string_skip_separators(const uint8_t *sp, int *pp) {
|
|
int c;
|
|
while ((c = sp[*pp]) == '-' || c == '/' || c == '.' || c == ',')
|
|
*pp += 1;
|
|
return c;
|
|
}
|
|
|
|
/* skip a word, stop on spaces, digits and separators, update offset */
|
|
static int string_skip_until(const uint8_t *sp, int *pp, const char *stoplist) {
|
|
int c;
|
|
while (!strchr(stoplist, c = sp[*pp]))
|
|
*pp += 1;
|
|
return c;
|
|
}
|
|
|
|
/* parse a numeric field (max_digits = 0 -> no maximum) */
|
|
static BOOL string_get_digits(const uint8_t *sp, int *pp, int *pval,
|
|
int min_digits, int max_digits)
|
|
{
|
|
int v = 0;
|
|
int c, p = *pp, p_start;
|
|
|
|
p_start = p;
|
|
while ((c = sp[p]) >= '0' && c <= '9') {
|
|
v = v * 10 + c - '0';
|
|
p++;
|
|
if (p - p_start == max_digits)
|
|
break;
|
|
}
|
|
if (p - p_start < min_digits)
|
|
return FALSE;
|
|
*pval = v;
|
|
*pp = p;
|
|
return TRUE;
|
|
}
|
|
|
|
static BOOL string_get_milliseconds(const uint8_t *sp, int *pp, int *pval) {
|
|
/* parse optional fractional part as milliseconds and truncate. */
|
|
/* spec does not indicate which rounding should be used */
|
|
int mul = 100, ms = 0, c, p_start, p = *pp;
|
|
|
|
c = sp[p];
|
|
if (c == '.' || c == ',') {
|
|
p++;
|
|
p_start = p;
|
|
while ((c = sp[p]) >= '0' && c <= '9') {
|
|
ms += (c - '0') * mul;
|
|
mul /= 10;
|
|
p++;
|
|
if (p - p_start == 9)
|
|
break;
|
|
}
|
|
if (p > p_start) {
|
|
/* only consume the separator if digits are present */
|
|
*pval = ms;
|
|
*pp = p;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static BOOL string_get_tzoffset(const uint8_t *sp, int *pp, int *tzp, BOOL strict) {
|
|
int tz = 0, sgn, hh, mm, p = *pp;
|
|
|
|
sgn = sp[p++];
|
|
if (sgn == '+' || sgn == '-') {
|
|
int n = p;
|
|
if (!string_get_digits(sp, &p, &hh, 1, 9))
|
|
return FALSE;
|
|
n = p - n;
|
|
if (strict && n != 2 && n != 4)
|
|
return FALSE;
|
|
while (n > 4) {
|
|
n -= 2;
|
|
hh /= 100;
|
|
}
|
|
if (n > 2) {
|
|
mm = hh % 100;
|
|
hh = hh / 100;
|
|
} else {
|
|
mm = 0;
|
|
if (string_skip_char(sp, &p, ':') /* optional separator */
|
|
&& !string_get_digits(sp, &p, &mm, 2, 2))
|
|
return FALSE;
|
|
}
|
|
if (hh > 23 || mm > 59)
|
|
return FALSE;
|
|
tz = hh * 60 + mm;
|
|
if (sgn != '+')
|
|
tz = -tz;
|
|
} else
|
|
if (sgn != 'Z') {
|
|
return FALSE;
|
|
}
|
|
*pp = p;
|
|
*tzp = tz;
|
|
return TRUE;
|
|
}
|
|
|
|
static BOOL string_match(const uint8_t *sp, int *pp, const char *s) {
|
|
int p = *pp;
|
|
while (*s != '\0') {
|
|
if (to_upper_ascii(sp[p]) != to_upper_ascii(*s++))
|
|
return FALSE;
|
|
p++;
|
|
}
|
|
*pp = p;
|
|
return TRUE;
|
|
}
|
|
|
|
static int find_abbrev(const uint8_t *sp, int p, const char *list, int count) {
|
|
int n, i;
|
|
|
|
for (n = 0; n < count; n++) {
|
|
for (i = 0;; i++) {
|
|
if (to_upper_ascii(sp[p + i]) != to_upper_ascii(list[n * 3 + i]))
|
|
break;
|
|
if (i == 2)
|
|
return n;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static BOOL string_get_month(const uint8_t *sp, int *pp, int *pval) {
|
|
int n;
|
|
|
|
n = find_abbrev(sp, *pp, month_names, 12);
|
|
if (n < 0)
|
|
return FALSE;
|
|
|
|
*pval = n + 1;
|
|
*pp += 3;
|
|
return TRUE;
|
|
}
|
|
|
|
/* parse toISOString format */
|
|
static BOOL js_date_parse_isostring(const uint8_t *sp, int fields[9], BOOL *is_local) {
|
|
int sgn, i, p = 0;
|
|
|
|
/* initialize fields to the beginning of the Epoch */
|
|
for (i = 0; i < 9; i++) {
|
|
fields[i] = (i == 2);
|
|
}
|
|
*is_local = FALSE;
|
|
|
|
/* year is either yyyy digits or [+-]yyyyyy */
|
|
sgn = sp[p];
|
|
if (sgn == '-' || sgn == '+') {
|
|
p++;
|
|
if (!string_get_digits(sp, &p, &fields[0], 6, 6))
|
|
return FALSE;
|
|
if (sgn == '-') {
|
|
if (fields[0] == 0)
|
|
return FALSE; // reject -000000
|
|
fields[0] = -fields[0];
|
|
}
|
|
} else {
|
|
if (!string_get_digits(sp, &p, &fields[0], 4, 4))
|
|
return FALSE;
|
|
}
|
|
if (string_skip_char(sp, &p, '-')) {
|
|
if (!string_get_digits(sp, &p, &fields[1], 2, 2)) /* month */
|
|
return FALSE;
|
|
if (fields[1] < 1)
|
|
return FALSE;
|
|
fields[1] -= 1;
|
|
if (string_skip_char(sp, &p, '-')) {
|
|
if (!string_get_digits(sp, &p, &fields[2], 2, 2)) /* day */
|
|
return FALSE;
|
|
if (fields[2] < 1)
|
|
return FALSE;
|
|
}
|
|
}
|
|
if (string_skip_char(sp, &p, 'T')) {
|
|
*is_local = TRUE;
|
|
if (!string_get_digits(sp, &p, &fields[3], 2, 2) /* hour */
|
|
|| !string_skip_char(sp, &p, ':')
|
|
|| !string_get_digits(sp, &p, &fields[4], 2, 2)) { /* minute */
|
|
fields[3] = 100; // reject unconditionally
|
|
return TRUE;
|
|
}
|
|
if (string_skip_char(sp, &p, ':')) {
|
|
if (!string_get_digits(sp, &p, &fields[5], 2, 2)) /* second */
|
|
return FALSE;
|
|
string_get_milliseconds(sp, &p, &fields[6]);
|
|
}
|
|
}
|
|
/* parse the time zone offset if present: [+-]HH:mm or [+-]HHmm */
|
|
if (sp[p]) {
|
|
*is_local = FALSE;
|
|
if (!string_get_tzoffset(sp, &p, &fields[8], TRUE))
|
|
return FALSE;
|
|
}
|
|
/* error if extraneous characters */
|
|
return sp[p] == '\0';
|
|
}
|
|
|
|
static struct {
|
|
char name[6];
|
|
int16_t offset;
|
|
} const js_tzabbr[] = {
|
|
{ "GMT", 0 }, // Greenwich Mean Time
|
|
{ "UTC", 0 }, // Coordinated Universal Time
|
|
{ "UT", 0 }, // Universal Time
|
|
{ "Z", 0 }, // Zulu Time
|
|
{ "EDT", -4 * 60 }, // Eastern Daylight Time
|
|
{ "EST", -5 * 60 }, // Eastern Standard Time
|
|
{ "CDT", -5 * 60 }, // Central Daylight Time
|
|
{ "CST", -6 * 60 }, // Central Standard Time
|
|
{ "MDT", -6 * 60 }, // Mountain Daylight Time
|
|
{ "MST", -7 * 60 }, // Mountain Standard Time
|
|
{ "PDT", -7 * 60 }, // Pacific Daylight Time
|
|
{ "PST", -8 * 60 }, // Pacific Standard Time
|
|
{ "WET", +0 * 60 }, // Western European Time
|
|
{ "WEST", +1 * 60 }, // Western European Summer Time
|
|
{ "CET", +1 * 60 }, // Central European Time
|
|
{ "CEST", +2 * 60 }, // Central European Summer Time
|
|
{ "EET", +2 * 60 }, // Eastern European Time
|
|
{ "EEST", +3 * 60 }, // Eastern European Summer Time
|
|
};
|
|
|
|
static BOOL string_get_tzabbr(const uint8_t *sp, int *pp, int *offset) {
|
|
for (size_t i = 0; i < countof(js_tzabbr); i++) {
|
|
if (string_match(sp, pp, js_tzabbr[i].name)) {
|
|
*offset = js_tzabbr[i].offset;
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/* parse toString, toUTCString and other formats */
|
|
static BOOL js_date_parse_otherstring(const uint8_t *sp,
|
|
int fields[minimum_length(9)],
|
|
BOOL *is_local) {
|
|
int c, i, val, p = 0, p_start;
|
|
int num[3];
|
|
BOOL has_year = FALSE;
|
|
BOOL has_mon = FALSE;
|
|
BOOL has_time = FALSE;
|
|
int num_index = 0;
|
|
|
|
/* initialize fields to the beginning of 2001-01-01 */
|
|
fields[0] = 2001;
|
|
fields[1] = 1;
|
|
fields[2] = 1;
|
|
for (i = 3; i < 9; i++) {
|
|
fields[i] = 0;
|
|
}
|
|
*is_local = TRUE;
|
|
|
|
while (string_skip_spaces(sp, &p)) {
|
|
p_start = p;
|
|
if ((c = sp[p]) == '+' || c == '-') {
|
|
if (has_time && string_get_tzoffset(sp, &p, &fields[8], FALSE)) {
|
|
*is_local = FALSE;
|
|
} else {
|
|
p++;
|
|
if (string_get_digits(sp, &p, &val, 1, 9)) {
|
|
if (c == '-') {
|
|
if (val == 0)
|
|
return FALSE;
|
|
val = -val;
|
|
}
|
|
fields[0] = val;
|
|
has_year = TRUE;
|
|
}
|
|
}
|
|
} else
|
|
if (string_get_digits(sp, &p, &val, 1, 9)) {
|
|
if (string_skip_char(sp, &p, ':')) {
|
|
/* time part */
|
|
fields[3] = val;
|
|
if (!string_get_digits(sp, &p, &fields[4], 1, 2))
|
|
return FALSE;
|
|
if (string_skip_char(sp, &p, ':')) {
|
|
if (!string_get_digits(sp, &p, &fields[5], 1, 2))
|
|
return FALSE;
|
|
string_get_milliseconds(sp, &p, &fields[6]);
|
|
} else
|
|
if (sp[p] != '\0' && sp[p] != ' ')
|
|
return FALSE;
|
|
has_time = TRUE;
|
|
} else {
|
|
if (p - p_start > 2) {
|
|
fields[0] = val;
|
|
has_year = TRUE;
|
|
} else
|
|
if (val < 1 || val > 31) {
|
|
fields[0] = val + (val < 100) * 1900 + (val < 50) * 100;
|
|
has_year = TRUE;
|
|
} else {
|
|
if (num_index == 3)
|
|
return FALSE;
|
|
num[num_index++] = val;
|
|
}
|
|
}
|
|
} else
|
|
if (string_get_month(sp, &p, &fields[1])) {
|
|
has_mon = TRUE;
|
|
string_skip_until(sp, &p, "0123456789 -/(");
|
|
} else
|
|
if (has_time && string_match(sp, &p, "PM")) {
|
|
/* hours greater than 12 will be incremented and
|
|
rejected by the range check in js_Date_parse */
|
|
/* 00:00 PM will be silently converted as 12:00 */
|
|
if (fields[3] != 12)
|
|
fields[3] += 12;
|
|
continue;
|
|
} else
|
|
if (has_time && string_match(sp, &p, "AM")) {
|
|
/* 00:00 AM will be silently accepted */
|
|
if (fields[3] > 12)
|
|
return FALSE;
|
|
if (fields[3] == 12)
|
|
fields[3] -= 12;
|
|
continue;
|
|
} else
|
|
if (string_get_tzabbr(sp, &p, &fields[8])) {
|
|
*is_local = FALSE;
|
|
continue;
|
|
} else
|
|
if (c == '(') { /* skip parenthesized phrase */
|
|
int level = 0;
|
|
while ((c = sp[p]) != '\0') {
|
|
p++;
|
|
level += (c == '(');
|
|
level -= (c == ')');
|
|
if (!level)
|
|
break;
|
|
}
|
|
if (level > 0)
|
|
return FALSE;
|
|
} else
|
|
if (c == ')') {
|
|
return FALSE;
|
|
} else {
|
|
if (has_year + has_mon + has_time + num_index)
|
|
return FALSE;
|
|
/* skip a word */
|
|
string_skip_until(sp, &p, " -/(");
|
|
}
|
|
string_skip_separators(sp, &p);
|
|
}
|
|
if (num_index + has_year + has_mon > 3)
|
|
return FALSE;
|
|
|
|
switch (num_index) {
|
|
case 0:
|
|
if (!has_year)
|
|
return FALSE;
|
|
break;
|
|
case 1:
|
|
if (has_mon)
|
|
fields[2] = num[0];
|
|
else
|
|
fields[1] = num[0];
|
|
break;
|
|
case 2:
|
|
if (has_year) {
|
|
fields[1] = num[0];
|
|
fields[2] = num[1];
|
|
} else
|
|
if (has_mon) {
|
|
fields[0] = num[1] + (num[1] < 100) * 1900 + (num[1] < 50) * 100;
|
|
fields[2] = num[0];
|
|
} else {
|
|
fields[1] = num[0];
|
|
fields[2] = num[1];
|
|
}
|
|
break;
|
|
case 3:
|
|
fields[0] = num[2] + (num[2] < 100) * 1900 + (num[2] < 50) * 100;
|
|
fields[1] = num[0];
|
|
fields[2] = num[1];
|
|
break;
|
|
default:
|
|
return FALSE;
|
|
}
|
|
if (fields[1] < 1 || fields[2] < 1)
|
|
return FALSE;
|
|
fields[1] -= 1;
|
|
return TRUE;
|
|
}
|
|
|
|
static JSValue js_Date_parse(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue s, rv;
|
|
int fields[9];
|
|
double fields1[9];
|
|
double d;
|
|
int i, c;
|
|
JSString *sp;
|
|
uint8_t buf[128];
|
|
BOOL is_local;
|
|
|
|
rv = JS_NAN;
|
|
|
|
s = JS_ToString(ctx, argv[0]);
|
|
if (JS_IsException(s))
|
|
return JS_EXCEPTION;
|
|
|
|
sp = JS_VALUE_GET_STRING(s);
|
|
/* convert the string as a byte array */
|
|
for (i = 0; i < sp->len && i < (int)countof(buf) - 1; i++) {
|
|
c = string_get(sp, i);
|
|
if (c > 255)
|
|
c = (c == 0x2212) ? '-' : 'x';
|
|
buf[i] = c;
|
|
}
|
|
buf[i] = '\0';
|
|
if (js_date_parse_isostring(buf, fields, &is_local)
|
|
|| js_date_parse_otherstring(buf, fields, &is_local)) {
|
|
static int const field_max[6] = { 0, 11, 31, 24, 59, 59 };
|
|
BOOL valid = TRUE;
|
|
/* check field maximum values */
|
|
for (i = 1; i < 6; i++) {
|
|
if (fields[i] > field_max[i])
|
|
valid = FALSE;
|
|
}
|
|
/* special case 24:00:00.000 */
|
|
if (fields[3] == 24 && (fields[4] | fields[5] | fields[6]))
|
|
valid = FALSE;
|
|
if (valid) {
|
|
for(i = 0; i < 7; i++)
|
|
fields1[i] = fields[i];
|
|
d = set_date_fields(fields1, is_local) - fields[8] * 60000;
|
|
rv = JS_NewFloat64(ctx, d);
|
|
}
|
|
}
|
|
JS_FreeValue(ctx, s);
|
|
return rv;
|
|
}
|
|
|
|
static JSValue js_Date_now(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
// now()
|
|
return JS_NewInt64(ctx, date_now());
|
|
}
|
|
|
|
static JSValue js_date_Symbol_toPrimitive(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
// Symbol_toPrimitive(hint)
|
|
JSValue obj = this_val;
|
|
JSAtom hint = JS_ATOM_NULL;
|
|
int hint_num;
|
|
|
|
if (!JS_IsObject(obj))
|
|
return JS_ThrowTypeErrorNotAnObject(ctx);
|
|
|
|
if (JS_IsString(argv[0])) {
|
|
hint = JS_ValueToAtom(ctx, argv[0]);
|
|
if (hint == JS_ATOM_NULL)
|
|
return JS_EXCEPTION;
|
|
JS_FreeAtom(ctx, hint);
|
|
}
|
|
switch (hint) {
|
|
case JS_ATOM_number:
|
|
case JS_ATOM_integer:
|
|
hint_num = HINT_NUMBER;
|
|
break;
|
|
case JS_ATOM_string:
|
|
case JS_ATOM_default:
|
|
hint_num = HINT_STRING;
|
|
break;
|
|
default:
|
|
return JS_ThrowTypeError(ctx, "invalid hint");
|
|
}
|
|
return JS_ToPrimitive(ctx, obj, hint_num | HINT_FORCE_ORDINARY);
|
|
}
|
|
|
|
static JSValue js_date_getTimezoneOffset(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
// getTimezoneOffset()
|
|
double v;
|
|
|
|
if (JS_ThisTimeValue(ctx, &v, this_val))
|
|
return JS_EXCEPTION;
|
|
if (isnan(v))
|
|
return JS_NAN;
|
|
else
|
|
/* assuming -8.64e15 <= v <= -8.64e15 */
|
|
return JS_NewInt64(ctx, getTimezoneOffset((int64_t)trunc(v)));
|
|
}
|
|
|
|
static JSValue js_date_getTime(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
// getTime()
|
|
double v;
|
|
|
|
if (JS_ThisTimeValue(ctx, &v, this_val))
|
|
return JS_EXCEPTION;
|
|
return js_float64(v);
|
|
}
|
|
|
|
static JSValue js_date_setTime(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
// setTime(v)
|
|
double v;
|
|
|
|
if (JS_ThisTimeValue(ctx, &v, this_val) || JS_ToFloat64(ctx, &v, argv[0]))
|
|
return JS_EXCEPTION;
|
|
return JS_SetThisTimeValue(ctx, this_val, time_clip(v));
|
|
}
|
|
|
|
static JSValue js_date_setYear(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
// setYear(y)
|
|
double y;
|
|
JSValue args[1];
|
|
|
|
if (JS_ThisTimeValue(ctx, &y, this_val) || JS_ToFloat64(ctx, &y, argv[0]))
|
|
return JS_EXCEPTION;
|
|
y = +y;
|
|
if (isfinite(y)) {
|
|
y = trunc(y);
|
|
if (y >= 0 && y < 100)
|
|
y += 1900;
|
|
}
|
|
args[0] = js_float64(y);
|
|
return set_date_field(ctx, this_val, 1, args, 0x011);
|
|
}
|
|
|
|
static JSValue js_date_toJSON(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
// toJSON(key)
|
|
JSValue obj, tv, method, rv;
|
|
double d;
|
|
|
|
rv = JS_EXCEPTION;
|
|
tv = JS_UNDEFINED;
|
|
|
|
obj = JS_ToObject(ctx, this_val);
|
|
tv = JS_ToPrimitive(ctx, obj, HINT_NUMBER);
|
|
if (JS_IsException(tv))
|
|
goto exception;
|
|
if (JS_IsNumber(tv)) {
|
|
if (JS_ToFloat64(ctx, &d, tv) < 0)
|
|
goto exception;
|
|
if (!isfinite(d)) {
|
|
rv = JS_NULL;
|
|
goto done;
|
|
}
|
|
}
|
|
method = JS_GetPropertyStr(ctx, obj, "toISOString");
|
|
if (JS_IsException(method))
|
|
goto exception;
|
|
if (!JS_IsFunction(ctx, method)) {
|
|
JS_ThrowTypeError(ctx, "object needs toISOString method");
|
|
JS_FreeValue(ctx, method);
|
|
goto exception;
|
|
}
|
|
rv = JS_CallFree(ctx, method, obj, 0, NULL);
|
|
exception:
|
|
done:
|
|
JS_FreeValue(ctx, obj);
|
|
JS_FreeValue(ctx, tv);
|
|
return rv;
|
|
}
|
|
|
|
static const JSCFunctionListEntry js_date_funcs[] = {
|
|
JS_CFUNC_DEF("now", 0, js_Date_now ),
|
|
JS_CFUNC_DEF("parse", 1, js_Date_parse ),
|
|
JS_CFUNC_DEF("UTC", 7, js_Date_UTC ),
|
|
};
|
|
|
|
static const JSCFunctionListEntry js_date_proto_funcs[] = {
|
|
JS_CFUNC_DEF("valueOf", 0, js_date_getTime ),
|
|
JS_CFUNC_MAGIC_DEF("toString", 0, get_date_string, 0x13 ),
|
|
JS_CFUNC_DEF("[Symbol.toPrimitive]", 1, js_date_Symbol_toPrimitive ),
|
|
JS_CFUNC_MAGIC_DEF("toUTCString", 0, get_date_string, 0x03 ),
|
|
JS_ALIAS_DEF("toGMTString", "toUTCString" ),
|
|
JS_CFUNC_MAGIC_DEF("toISOString", 0, get_date_string, 0x23 ),
|
|
JS_CFUNC_MAGIC_DEF("toDateString", 0, get_date_string, 0x11 ),
|
|
JS_CFUNC_MAGIC_DEF("toTimeString", 0, get_date_string, 0x12 ),
|
|
JS_CFUNC_MAGIC_DEF("toLocaleString", 0, get_date_string, 0x33 ),
|
|
JS_CFUNC_MAGIC_DEF("toLocaleDateString", 0, get_date_string, 0x31 ),
|
|
JS_CFUNC_MAGIC_DEF("toLocaleTimeString", 0, get_date_string, 0x32 ),
|
|
JS_CFUNC_DEF("getTimezoneOffset", 0, js_date_getTimezoneOffset ),
|
|
JS_CFUNC_DEF("getTime", 0, js_date_getTime ),
|
|
JS_CFUNC_MAGIC_DEF("getYear", 0, get_date_field, 0x101 ),
|
|
JS_CFUNC_MAGIC_DEF("getFullYear", 0, get_date_field, 0x01 ),
|
|
JS_CFUNC_MAGIC_DEF("getUTCFullYear", 0, get_date_field, 0x00 ),
|
|
JS_CFUNC_MAGIC_DEF("getMonth", 0, get_date_field, 0x11 ),
|
|
JS_CFUNC_MAGIC_DEF("getUTCMonth", 0, get_date_field, 0x10 ),
|
|
JS_CFUNC_MAGIC_DEF("getDate", 0, get_date_field, 0x21 ),
|
|
JS_CFUNC_MAGIC_DEF("getUTCDate", 0, get_date_field, 0x20 ),
|
|
JS_CFUNC_MAGIC_DEF("getHours", 0, get_date_field, 0x31 ),
|
|
JS_CFUNC_MAGIC_DEF("getUTCHours", 0, get_date_field, 0x30 ),
|
|
JS_CFUNC_MAGIC_DEF("getMinutes", 0, get_date_field, 0x41 ),
|
|
JS_CFUNC_MAGIC_DEF("getUTCMinutes", 0, get_date_field, 0x40 ),
|
|
JS_CFUNC_MAGIC_DEF("getSeconds", 0, get_date_field, 0x51 ),
|
|
JS_CFUNC_MAGIC_DEF("getUTCSeconds", 0, get_date_field, 0x50 ),
|
|
JS_CFUNC_MAGIC_DEF("getMilliseconds", 0, get_date_field, 0x61 ),
|
|
JS_CFUNC_MAGIC_DEF("getUTCMilliseconds", 0, get_date_field, 0x60 ),
|
|
JS_CFUNC_MAGIC_DEF("getDay", 0, get_date_field, 0x71 ),
|
|
JS_CFUNC_MAGIC_DEF("getUTCDay", 0, get_date_field, 0x70 ),
|
|
JS_CFUNC_DEF("setTime", 1, js_date_setTime ),
|
|
JS_CFUNC_MAGIC_DEF("setMilliseconds", 1, set_date_field, 0x671 ),
|
|
JS_CFUNC_MAGIC_DEF("setUTCMilliseconds", 1, set_date_field, 0x670 ),
|
|
JS_CFUNC_MAGIC_DEF("setSeconds", 2, set_date_field, 0x571 ),
|
|
JS_CFUNC_MAGIC_DEF("setUTCSeconds", 2, set_date_field, 0x570 ),
|
|
JS_CFUNC_MAGIC_DEF("setMinutes", 3, set_date_field, 0x471 ),
|
|
JS_CFUNC_MAGIC_DEF("setUTCMinutes", 3, set_date_field, 0x470 ),
|
|
JS_CFUNC_MAGIC_DEF("setHours", 4, set_date_field, 0x371 ),
|
|
JS_CFUNC_MAGIC_DEF("setUTCHours", 4, set_date_field, 0x370 ),
|
|
JS_CFUNC_MAGIC_DEF("setDate", 1, set_date_field, 0x231 ),
|
|
JS_CFUNC_MAGIC_DEF("setUTCDate", 1, set_date_field, 0x230 ),
|
|
JS_CFUNC_MAGIC_DEF("setMonth", 2, set_date_field, 0x131 ),
|
|
JS_CFUNC_MAGIC_DEF("setUTCMonth", 2, set_date_field, 0x130 ),
|
|
JS_CFUNC_DEF("setYear", 1, js_date_setYear ),
|
|
JS_CFUNC_MAGIC_DEF("setFullYear", 3, set_date_field, 0x031 ),
|
|
JS_CFUNC_MAGIC_DEF("setUTCFullYear", 3, set_date_field, 0x030 ),
|
|
JS_CFUNC_DEF("toJSON", 1, js_date_toJSON ),
|
|
};
|
|
|
|
JSValue JS_NewDate(JSContext *ctx, double epoch_ms)
|
|
{
|
|
JSValue obj = js_create_from_ctor(ctx, JS_UNDEFINED, JS_CLASS_DATE);
|
|
if (JS_IsException(obj))
|
|
return JS_EXCEPTION;
|
|
JS_SetObjectData(ctx, obj, js_float64(time_clip(epoch_ms)));
|
|
return obj;
|
|
}
|
|
|
|
void JS_AddIntrinsicDate(JSContext *ctx)
|
|
{
|
|
JSValue obj;
|
|
|
|
/* Date */
|
|
ctx->class_proto[JS_CLASS_DATE] = JS_NewObject(ctx);
|
|
JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_DATE], js_date_proto_funcs,
|
|
countof(js_date_proto_funcs));
|
|
obj = JS_NewGlobalCConstructor(ctx, "Date", js_date_constructor, 7,
|
|
ctx->class_proto[JS_CLASS_DATE]);
|
|
JS_SetPropertyFunctionList(ctx, obj, js_date_funcs, countof(js_date_funcs));
|
|
}
|
|
|
|
/* eval */
|
|
|
|
void JS_AddIntrinsicEval(JSContext *ctx)
|
|
{
|
|
ctx->eval_internal = __JS_EvalInternal;
|
|
}
|
|
|
|
/* BigInt */
|
|
|
|
static JSValue JS_ToBigIntCtorFree(JSContext *ctx, JSValue val)
|
|
{
|
|
uint32_t tag;
|
|
|
|
redo:
|
|
tag = JS_VALUE_GET_NORM_TAG(val);
|
|
switch(tag) {
|
|
case JS_TAG_INT:
|
|
case JS_TAG_BOOL:
|
|
val = JS_NewBigInt64(ctx, JS_VALUE_GET_INT(val));
|
|
break;
|
|
case JS_TAG_BIG_INT:
|
|
break;
|
|
case JS_TAG_FLOAT64:
|
|
{
|
|
bf_t *a, a_s;
|
|
|
|
a = JS_ToBigInt1(ctx, &a_s, val);
|
|
if (!bf_is_finite(a)) {
|
|
JS_FreeValue(ctx, val);
|
|
val = JS_ThrowRangeError(ctx, "cannot convert NaN or Infinity to BigInt");
|
|
} else {
|
|
JSValue val1 = JS_NewBigInt(ctx);
|
|
bf_t *r;
|
|
int ret;
|
|
if (JS_IsException(val1)) {
|
|
JS_FreeValue(ctx, val);
|
|
return JS_EXCEPTION;
|
|
}
|
|
r = JS_GetBigInt(val1);
|
|
ret = bf_set(r, a);
|
|
ret |= bf_rint(r, BF_RNDZ);
|
|
JS_FreeValue(ctx, val);
|
|
if (ret & BF_ST_MEM_ERROR) {
|
|
JS_FreeValue(ctx, val1);
|
|
val = JS_ThrowOutOfMemory(ctx);
|
|
} else if (ret & BF_ST_INEXACT) {
|
|
JS_FreeValue(ctx, val1);
|
|
val = JS_ThrowRangeError(ctx, "cannot convert to BigInt: not an integer");
|
|
} else {
|
|
val = JS_CompactBigInt(ctx, val1);
|
|
}
|
|
}
|
|
if (a == &a_s)
|
|
bf_delete(a);
|
|
}
|
|
break;
|
|
case JS_TAG_STRING:
|
|
val = JS_StringToBigIntErr(ctx, val);
|
|
break;
|
|
case JS_TAG_OBJECT:
|
|
val = JS_ToPrimitiveFree(ctx, val, HINT_NUMBER);
|
|
if (JS_IsException(val))
|
|
break;
|
|
goto redo;
|
|
case JS_TAG_NULL:
|
|
case JS_TAG_UNDEFINED:
|
|
default:
|
|
JS_FreeValue(ctx, val);
|
|
return JS_ThrowTypeError(ctx, "cannot convert to BigInt");
|
|
}
|
|
return val;
|
|
}
|
|
|
|
static JSValue js_bigint_constructor(JSContext *ctx,
|
|
JSValue new_target,
|
|
int argc, JSValue *argv)
|
|
{
|
|
if (!JS_IsUndefined(new_target))
|
|
return JS_ThrowTypeError(ctx, "not a constructor");
|
|
return JS_ToBigIntCtorFree(ctx, js_dup(argv[0]));
|
|
}
|
|
|
|
static JSValue js_thisBigIntValue(JSContext *ctx, JSValue this_val)
|
|
{
|
|
if (JS_IsBigInt(ctx, this_val))
|
|
return js_dup(this_val);
|
|
|
|
if (JS_VALUE_GET_TAG(this_val) == JS_TAG_OBJECT) {
|
|
JSObject *p = JS_VALUE_GET_OBJ(this_val);
|
|
if (p->class_id == JS_CLASS_BIG_INT) {
|
|
if (JS_IsBigInt(ctx, p->u.object_data))
|
|
return js_dup(p->u.object_data);
|
|
}
|
|
}
|
|
return JS_ThrowTypeError(ctx, "not a BigInt");
|
|
}
|
|
|
|
static JSValue js_bigint_toString(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue val;
|
|
int base;
|
|
JSValue ret;
|
|
|
|
val = js_thisBigIntValue(ctx, this_val);
|
|
if (JS_IsException(val))
|
|
return val;
|
|
if (argc == 0 || JS_IsUndefined(argv[0])) {
|
|
base = 10;
|
|
} else {
|
|
base = js_get_radix(ctx, argv[0]);
|
|
if (base < 0)
|
|
goto fail;
|
|
}
|
|
ret = js_bigint_to_string1(ctx, val, base);
|
|
JS_FreeValue(ctx, val);
|
|
return ret;
|
|
fail:
|
|
JS_FreeValue(ctx, val);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue js_bigint_valueOf(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
return js_thisBigIntValue(ctx, this_val);
|
|
}
|
|
|
|
static JSValue js_bigint_asUintN(JSContext *ctx,
|
|
JSValue this_val,
|
|
int argc, JSValue *argv, int asIntN)
|
|
{
|
|
uint64_t bits;
|
|
bf_t a_s, *a = &a_s, *r, mask_s, *mask = &mask_s;
|
|
JSValue res;
|
|
|
|
if (JS_ToIndex(ctx, &bits, argv[0]))
|
|
return JS_EXCEPTION;
|
|
res = JS_NewBigInt(ctx);
|
|
if (JS_IsException(res))
|
|
return JS_EXCEPTION;
|
|
a = JS_ToBigInt(ctx, &a_s, argv[1]);
|
|
if (!a) {
|
|
JS_FreeValue(ctx, res);
|
|
return JS_EXCEPTION;
|
|
}
|
|
/* XXX: optimize */
|
|
r = JS_GetBigInt(res);
|
|
bf_init(ctx->bf_ctx, mask);
|
|
bf_set_ui(mask, 1);
|
|
bf_mul_2exp(mask, bits, BF_PREC_INF, BF_RNDZ);
|
|
bf_add_si(mask, mask, -1, BF_PREC_INF, BF_RNDZ);
|
|
bf_logic_and(r, a, mask);
|
|
if (asIntN && bits != 0) {
|
|
bf_set_ui(mask, 1);
|
|
bf_mul_2exp(mask, bits - 1, BF_PREC_INF, BF_RNDZ);
|
|
if (bf_cmpu(r, mask) >= 0) {
|
|
bf_set_ui(mask, 1);
|
|
bf_mul_2exp(mask, bits, BF_PREC_INF, BF_RNDZ);
|
|
bf_sub(r, r, mask, BF_PREC_INF, BF_RNDZ);
|
|
}
|
|
}
|
|
bf_delete(mask);
|
|
JS_FreeBigInt(ctx, a, &a_s);
|
|
return JS_CompactBigInt(ctx, res);
|
|
}
|
|
|
|
static const JSCFunctionListEntry js_bigint_funcs[] = {
|
|
JS_CFUNC_MAGIC_DEF("asUintN", 2, js_bigint_asUintN, 0 ),
|
|
JS_CFUNC_MAGIC_DEF("asIntN", 2, js_bigint_asUintN, 1 ),
|
|
};
|
|
|
|
static const JSCFunctionListEntry js_bigint_proto_funcs[] = {
|
|
JS_CFUNC_DEF("toString", 0, js_bigint_toString ),
|
|
JS_CFUNC_DEF("valueOf", 0, js_bigint_valueOf ),
|
|
JS_PROP_STRING_DEF("[Symbol.toStringTag]", "BigInt", JS_PROP_CONFIGURABLE ),
|
|
};
|
|
|
|
void JS_AddIntrinsicBigInt(JSContext *ctx)
|
|
{
|
|
JSValue obj1;
|
|
|
|
ctx->class_proto[JS_CLASS_BIG_INT] = JS_NewObject(ctx);
|
|
JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_BIG_INT],
|
|
js_bigint_proto_funcs,
|
|
countof(js_bigint_proto_funcs));
|
|
obj1 = JS_NewGlobalCConstructor(ctx, "BigInt", js_bigint_constructor, 1,
|
|
ctx->class_proto[JS_CLASS_BIG_INT]);
|
|
JS_SetPropertyFunctionList(ctx, obj1, js_bigint_funcs,
|
|
countof(js_bigint_funcs));
|
|
}
|
|
|
|
static const char * const native_error_name[JS_NATIVE_ERROR_COUNT] = {
|
|
"EvalError", "RangeError", "ReferenceError",
|
|
"SyntaxError", "TypeError", "URIError",
|
|
"InternalError", "AggregateError",
|
|
};
|
|
|
|
/* Minimum amount of objects to be able to compile code and display
|
|
error messages. No JSAtom should be allocated by this function. */
|
|
static void JS_AddIntrinsicBasicObjects(JSContext *ctx)
|
|
{
|
|
JSValue proto;
|
|
int i;
|
|
|
|
ctx->class_proto[JS_CLASS_OBJECT] = JS_NewObjectProto(ctx, JS_NULL);
|
|
ctx->function_proto = JS_NewCFunction3(ctx, js_function_proto, "", 0,
|
|
JS_CFUNC_generic, 0,
|
|
ctx->class_proto[JS_CLASS_OBJECT]);
|
|
ctx->class_proto[JS_CLASS_BYTECODE_FUNCTION] = js_dup(ctx->function_proto);
|
|
ctx->class_proto[JS_CLASS_ERROR] = JS_NewObject(ctx);
|
|
JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_ERROR],
|
|
js_error_proto_funcs,
|
|
countof(js_error_proto_funcs));
|
|
|
|
for(i = 0; i < JS_NATIVE_ERROR_COUNT; i++) {
|
|
proto = JS_NewObjectProto(ctx, ctx->class_proto[JS_CLASS_ERROR]);
|
|
JS_DefinePropertyValue(ctx, proto, JS_ATOM_name,
|
|
JS_NewAtomString(ctx, native_error_name[i]),
|
|
JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
|
|
JS_DefinePropertyValue(ctx, proto, JS_ATOM_message,
|
|
JS_AtomToString(ctx, JS_ATOM_empty_string),
|
|
JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
|
|
ctx->native_error_proto[i] = proto;
|
|
}
|
|
|
|
/* the array prototype is an array */
|
|
ctx->class_proto[JS_CLASS_ARRAY] =
|
|
JS_NewObjectProtoClass(ctx, ctx->class_proto[JS_CLASS_OBJECT],
|
|
JS_CLASS_ARRAY);
|
|
|
|
ctx->array_shape = js_new_shape2(ctx, get_proto_obj(ctx->class_proto[JS_CLASS_ARRAY]),
|
|
JS_PROP_INITIAL_HASH_SIZE, 1);
|
|
add_shape_property(ctx, &ctx->array_shape, NULL,
|
|
JS_ATOM_length, JS_PROP_WRITABLE | JS_PROP_LENGTH);
|
|
|
|
/* XXX: could test it on first context creation to ensure that no
|
|
new atoms are created in JS_AddIntrinsicBasicObjects(). It is
|
|
necessary to avoid useless renumbering of atoms after
|
|
JS_EvalBinary() if it is done just after
|
|
JS_AddIntrinsicBasicObjects(). */
|
|
// assert(ctx->rt->atom_count == JS_ATOM_END);
|
|
}
|
|
|
|
void JS_AddIntrinsicBaseObjects(JSContext *ctx)
|
|
{
|
|
int i;
|
|
JSValue obj, number_obj;
|
|
JSValue obj1;
|
|
|
|
ctx->throw_type_error = JS_NewCFunction(ctx, js_throw_type_error, NULL, 0);
|
|
|
|
/* add caller and arguments properties to throw a TypeError */
|
|
obj1 = JS_NewCFunction(ctx, js_function_proto_caller, NULL, 0);
|
|
JS_DefineProperty(ctx, ctx->function_proto, JS_ATOM_caller, JS_UNDEFINED,
|
|
obj1, ctx->throw_type_error,
|
|
JS_PROP_HAS_GET | JS_PROP_HAS_SET |
|
|
JS_PROP_HAS_CONFIGURABLE | JS_PROP_CONFIGURABLE);
|
|
JS_DefineProperty(ctx, ctx->function_proto, JS_ATOM_arguments, JS_UNDEFINED,
|
|
obj1, ctx->throw_type_error,
|
|
JS_PROP_HAS_GET | JS_PROP_HAS_SET |
|
|
JS_PROP_HAS_CONFIGURABLE | JS_PROP_CONFIGURABLE);
|
|
JS_FreeValue(ctx, obj1);
|
|
JS_FreeValue(ctx, js_object_seal(ctx, JS_UNDEFINED, 1, &ctx->throw_type_error, 1));
|
|
|
|
ctx->global_obj = JS_NewObject(ctx);
|
|
ctx->global_var_obj = JS_NewObjectProto(ctx, JS_NULL);
|
|
|
|
/* Object */
|
|
obj = JS_NewGlobalCConstructor(ctx, "Object", js_object_constructor, 1,
|
|
ctx->class_proto[JS_CLASS_OBJECT]);
|
|
JS_SetPropertyFunctionList(ctx, obj, js_object_funcs, countof(js_object_funcs));
|
|
JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_OBJECT],
|
|
js_object_proto_funcs, countof(js_object_proto_funcs));
|
|
|
|
/* Function */
|
|
JS_SetPropertyFunctionList(ctx, ctx->function_proto, js_function_proto_funcs, countof(js_function_proto_funcs));
|
|
ctx->function_ctor = JS_NewCFunctionMagic(ctx, js_function_constructor,
|
|
"Function", 1, JS_CFUNC_constructor_or_func_magic,
|
|
JS_FUNC_NORMAL);
|
|
JS_NewGlobalCConstructor2(ctx, js_dup(ctx->function_ctor), "Function",
|
|
ctx->function_proto);
|
|
|
|
/* Error */
|
|
ctx->error_ctor = JS_NewCFunctionMagic(ctx, js_error_constructor,
|
|
"Error", 1, JS_CFUNC_constructor_or_func_magic, -1);
|
|
JS_NewGlobalCConstructor2(ctx, js_dup(ctx->error_ctor),
|
|
"Error", ctx->class_proto[JS_CLASS_ERROR]);
|
|
JS_SetPropertyFunctionList(ctx, ctx->error_ctor, js_error_funcs, countof(js_error_funcs));
|
|
|
|
/* Used to squelch a -Wcast-function-type warning. */
|
|
JSCFunctionType ft = { .generic_magic = js_error_constructor };
|
|
for(i = 0; i < JS_NATIVE_ERROR_COUNT; i++) {
|
|
JSValue func_obj;
|
|
int n_args;
|
|
n_args = 1 + (i == JS_AGGREGATE_ERROR);
|
|
func_obj = JS_NewCFunction3(ctx, ft.generic,
|
|
native_error_name[i], n_args,
|
|
JS_CFUNC_constructor_or_func_magic, i,
|
|
ctx->error_ctor);
|
|
JS_NewGlobalCConstructor2(ctx, func_obj, native_error_name[i],
|
|
ctx->native_error_proto[i]);
|
|
}
|
|
|
|
/* CallSite */
|
|
_JS_AddIntrinsicCallSite(ctx);
|
|
|
|
/* Iterator prototype */
|
|
ctx->iterator_proto = JS_NewObject(ctx);
|
|
JS_SetPropertyFunctionList(ctx, ctx->iterator_proto,
|
|
js_iterator_proto_funcs,
|
|
countof(js_iterator_proto_funcs));
|
|
|
|
/* Array */
|
|
JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_ARRAY],
|
|
js_array_proto_funcs,
|
|
countof(js_array_proto_funcs));
|
|
|
|
obj = JS_NewGlobalCConstructor(ctx, "Array", js_array_constructor, 1,
|
|
ctx->class_proto[JS_CLASS_ARRAY]);
|
|
ctx->array_ctor = js_dup(obj);
|
|
JS_SetPropertyFunctionList(ctx, obj, js_array_funcs,
|
|
countof(js_array_funcs));
|
|
|
|
/* XXX: create auto_initializer */
|
|
{
|
|
/* initialize Array.prototype[Symbol.unscopables] */
|
|
static const char unscopables[] =
|
|
"copyWithin" "\0"
|
|
"entries" "\0"
|
|
"fill" "\0"
|
|
"find" "\0"
|
|
"findIndex" "\0"
|
|
"findLast" "\0"
|
|
"findLastIndex" "\0"
|
|
"flat" "\0"
|
|
"flatMap" "\0"
|
|
"includes" "\0"
|
|
"keys" "\0"
|
|
"toReversed" "\0"
|
|
"toSorted" "\0"
|
|
"toSpliced" "\0"
|
|
"values" "\0";
|
|
const char *p = unscopables;
|
|
obj1 = JS_NewObjectProto(ctx, JS_NULL);
|
|
for(p = unscopables; *p; p += strlen(p) + 1) {
|
|
JS_DefinePropertyValueStr(ctx, obj1, p, JS_TRUE, JS_PROP_C_W_E);
|
|
}
|
|
JS_DefinePropertyValue(ctx, ctx->class_proto[JS_CLASS_ARRAY],
|
|
JS_ATOM_Symbol_unscopables, obj1,
|
|
JS_PROP_CONFIGURABLE);
|
|
}
|
|
|
|
/* needed to initialize arguments[Symbol.iterator] */
|
|
ctx->array_proto_values =
|
|
JS_GetProperty(ctx, ctx->class_proto[JS_CLASS_ARRAY], JS_ATOM_values);
|
|
|
|
ctx->class_proto[JS_CLASS_ARRAY_ITERATOR] = JS_NewObjectProto(ctx, ctx->iterator_proto);
|
|
JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_ARRAY_ITERATOR],
|
|
js_array_iterator_proto_funcs,
|
|
countof(js_array_iterator_proto_funcs));
|
|
|
|
/* parseFloat and parseInteger must be defined before Number
|
|
because of the Number.parseFloat and Number.parseInteger
|
|
aliases */
|
|
JS_SetPropertyFunctionList(ctx, ctx->global_obj, js_global_funcs,
|
|
countof(js_global_funcs));
|
|
|
|
/* Number */
|
|
ctx->class_proto[JS_CLASS_NUMBER] = JS_NewObjectProtoClass(ctx, ctx->class_proto[JS_CLASS_OBJECT],
|
|
JS_CLASS_NUMBER);
|
|
JS_SetObjectData(ctx, ctx->class_proto[JS_CLASS_NUMBER], js_int32(0));
|
|
JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_NUMBER],
|
|
js_number_proto_funcs,
|
|
countof(js_number_proto_funcs));
|
|
number_obj = JS_NewGlobalCConstructor(ctx, "Number", js_number_constructor, 1,
|
|
ctx->class_proto[JS_CLASS_NUMBER]);
|
|
JS_SetPropertyFunctionList(ctx, number_obj, js_number_funcs, countof(js_number_funcs));
|
|
|
|
/* Boolean */
|
|
ctx->class_proto[JS_CLASS_BOOLEAN] = JS_NewObjectProtoClass(ctx, ctx->class_proto[JS_CLASS_OBJECT],
|
|
JS_CLASS_BOOLEAN);
|
|
JS_SetObjectData(ctx, ctx->class_proto[JS_CLASS_BOOLEAN], js_bool(FALSE));
|
|
JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_BOOLEAN], js_boolean_proto_funcs,
|
|
countof(js_boolean_proto_funcs));
|
|
JS_NewGlobalCConstructor(ctx, "Boolean", js_boolean_constructor, 1,
|
|
ctx->class_proto[JS_CLASS_BOOLEAN]);
|
|
|
|
/* String */
|
|
ctx->class_proto[JS_CLASS_STRING] = JS_NewObjectProtoClass(ctx, ctx->class_proto[JS_CLASS_OBJECT],
|
|
JS_CLASS_STRING);
|
|
JS_SetObjectData(ctx, ctx->class_proto[JS_CLASS_STRING], JS_AtomToString(ctx, JS_ATOM_empty_string));
|
|
obj = JS_NewGlobalCConstructor(ctx, "String", js_string_constructor, 1,
|
|
ctx->class_proto[JS_CLASS_STRING]);
|
|
JS_SetPropertyFunctionList(ctx, obj, js_string_funcs,
|
|
countof(js_string_funcs));
|
|
JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_STRING], js_string_proto_funcs,
|
|
countof(js_string_proto_funcs));
|
|
|
|
ctx->class_proto[JS_CLASS_STRING_ITERATOR] = JS_NewObjectProto(ctx, ctx->iterator_proto);
|
|
JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_STRING_ITERATOR],
|
|
js_string_iterator_proto_funcs,
|
|
countof(js_string_iterator_proto_funcs));
|
|
|
|
/* Math: create as autoinit object */
|
|
js_random_init(ctx);
|
|
JS_SetPropertyFunctionList(ctx, ctx->global_obj, js_math_obj, countof(js_math_obj));
|
|
|
|
/* ES6 Reflect: create as autoinit object */
|
|
JS_SetPropertyFunctionList(ctx, ctx->global_obj, js_reflect_obj, countof(js_reflect_obj));
|
|
|
|
/* ES6 Symbol */
|
|
ctx->class_proto[JS_CLASS_SYMBOL] = JS_NewObject(ctx);
|
|
JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_SYMBOL], js_symbol_proto_funcs,
|
|
countof(js_symbol_proto_funcs));
|
|
obj = JS_NewGlobalCConstructor(ctx, "Symbol", js_symbol_constructor, 0,
|
|
ctx->class_proto[JS_CLASS_SYMBOL]);
|
|
JS_SetPropertyFunctionList(ctx, obj, js_symbol_funcs,
|
|
countof(js_symbol_funcs));
|
|
for(i = JS_ATOM_Symbol_toPrimitive; i < JS_ATOM_END; i++) {
|
|
char buf[ATOM_GET_STR_BUF_SIZE];
|
|
const char *str, *p;
|
|
str = JS_AtomGetStr(ctx, buf, sizeof(buf), i);
|
|
/* skip "Symbol." */
|
|
p = strchr(str, '.');
|
|
if (p)
|
|
str = p + 1;
|
|
JS_DefinePropertyValueStr(ctx, obj, str, JS_AtomToValue(ctx, i), 0);
|
|
}
|
|
|
|
/* ES6 Generator */
|
|
ctx->class_proto[JS_CLASS_GENERATOR] = JS_NewObjectProto(ctx, ctx->iterator_proto);
|
|
JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_GENERATOR],
|
|
js_generator_proto_funcs,
|
|
countof(js_generator_proto_funcs));
|
|
|
|
ctx->class_proto[JS_CLASS_GENERATOR_FUNCTION] = JS_NewObjectProto(ctx, ctx->function_proto);
|
|
obj1 = JS_NewCFunctionMagic(ctx, js_function_constructor,
|
|
"GeneratorFunction", 1,
|
|
JS_CFUNC_constructor_or_func_magic, JS_FUNC_GENERATOR);
|
|
JS_SetPropertyFunctionList(ctx,
|
|
ctx->class_proto[JS_CLASS_GENERATOR_FUNCTION],
|
|
js_generator_function_proto_funcs,
|
|
countof(js_generator_function_proto_funcs));
|
|
JS_SetConstructor2(ctx, ctx->class_proto[JS_CLASS_GENERATOR_FUNCTION],
|
|
ctx->class_proto[JS_CLASS_GENERATOR],
|
|
JS_PROP_CONFIGURABLE, JS_PROP_CONFIGURABLE);
|
|
JS_SetConstructor2(ctx, obj1, ctx->class_proto[JS_CLASS_GENERATOR_FUNCTION],
|
|
0, JS_PROP_CONFIGURABLE);
|
|
JS_FreeValue(ctx, obj1);
|
|
|
|
/* global properties */
|
|
ctx->eval_obj = JS_NewCFunction(ctx, js_global_eval, "eval", 1);
|
|
JS_DefinePropertyValue(ctx, ctx->global_obj, JS_ATOM_eval,
|
|
js_dup(ctx->eval_obj),
|
|
JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
|
|
|
|
JS_DefinePropertyValue(ctx, ctx->global_obj, JS_ATOM_globalThis,
|
|
js_dup(ctx->global_obj),
|
|
JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE);
|
|
}
|
|
|
|
/* Typed Arrays */
|
|
|
|
static uint8_t const typed_array_size_log2[JS_TYPED_ARRAY_COUNT] = {
|
|
0, 0, 0, 1, 1, 2, 2,
|
|
3, 3, /* BigInt64Array, BigUint64Array */
|
|
2, 3
|
|
};
|
|
|
|
static JSValue js_array_buffer_constructor3(JSContext *ctx,
|
|
JSValue new_target,
|
|
uint64_t len, JSClassID class_id,
|
|
uint8_t *buf,
|
|
JSFreeArrayBufferDataFunc *free_func,
|
|
void *opaque, BOOL alloc_flag)
|
|
{
|
|
JSRuntime *rt = ctx->rt;
|
|
JSValue obj;
|
|
JSArrayBuffer *abuf = NULL;
|
|
|
|
obj = js_create_from_ctor(ctx, new_target, class_id);
|
|
if (JS_IsException(obj))
|
|
return obj;
|
|
/* XXX: we are currently limited to 2 GB */
|
|
if (len > INT32_MAX) {
|
|
JS_ThrowRangeError(ctx, "invalid array buffer length");
|
|
goto fail;
|
|
}
|
|
abuf = js_malloc(ctx, sizeof(*abuf));
|
|
if (!abuf)
|
|
goto fail;
|
|
abuf->byte_length = len;
|
|
if (alloc_flag) {
|
|
if (class_id == JS_CLASS_SHARED_ARRAY_BUFFER &&
|
|
rt->sab_funcs.sab_alloc) {
|
|
abuf->data = rt->sab_funcs.sab_alloc(rt->sab_funcs.sab_opaque,
|
|
max_int(len, 1));
|
|
if (!abuf->data)
|
|
goto fail;
|
|
memset(abuf->data, 0, len);
|
|
} else {
|
|
/* the allocation must be done after the object creation */
|
|
abuf->data = js_mallocz(ctx, max_int(len, 1));
|
|
if (!abuf->data)
|
|
goto fail;
|
|
}
|
|
} else {
|
|
if (class_id == JS_CLASS_SHARED_ARRAY_BUFFER &&
|
|
rt->sab_funcs.sab_dup) {
|
|
rt->sab_funcs.sab_dup(rt->sab_funcs.sab_opaque, buf);
|
|
}
|
|
abuf->data = buf;
|
|
}
|
|
init_list_head(&abuf->array_list);
|
|
abuf->detached = FALSE;
|
|
abuf->shared = (class_id == JS_CLASS_SHARED_ARRAY_BUFFER);
|
|
abuf->opaque = opaque;
|
|
abuf->free_func = free_func;
|
|
if (alloc_flag && buf)
|
|
memcpy(abuf->data, buf, len);
|
|
JS_SetOpaque(obj, abuf);
|
|
return obj;
|
|
fail:
|
|
JS_FreeValue(ctx, obj);
|
|
js_free(ctx, abuf);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static void js_array_buffer_free(JSRuntime *rt, void *opaque, void *ptr)
|
|
{
|
|
js_free_rt(rt, ptr);
|
|
}
|
|
|
|
static JSValue js_array_buffer_constructor2(JSContext *ctx,
|
|
JSValue new_target,
|
|
uint64_t len, JSClassID class_id)
|
|
{
|
|
return js_array_buffer_constructor3(ctx, new_target, len, class_id,
|
|
NULL, js_array_buffer_free, NULL,
|
|
TRUE);
|
|
}
|
|
|
|
static JSValue js_array_buffer_constructor1(JSContext *ctx,
|
|
JSValue new_target,
|
|
uint64_t len)
|
|
{
|
|
return js_array_buffer_constructor2(ctx, new_target, len,
|
|
JS_CLASS_ARRAY_BUFFER);
|
|
}
|
|
|
|
JSValue JS_NewArrayBuffer(JSContext *ctx, uint8_t *buf, size_t len,
|
|
JSFreeArrayBufferDataFunc *free_func, void *opaque,
|
|
BOOL is_shared)
|
|
{
|
|
return js_array_buffer_constructor3(ctx, JS_UNDEFINED, len,
|
|
is_shared ? JS_CLASS_SHARED_ARRAY_BUFFER : JS_CLASS_ARRAY_BUFFER,
|
|
buf, free_func, opaque, FALSE);
|
|
}
|
|
|
|
JS_BOOL JS_IsArrayBuffer(JSValue obj) {
|
|
return JS_GetClassID(obj) == JS_CLASS_ARRAY_BUFFER;
|
|
}
|
|
|
|
/* create a new ArrayBuffer of length 'len' and copy 'buf' to it */
|
|
JSValue JS_NewArrayBufferCopy(JSContext *ctx, const uint8_t *buf, size_t len)
|
|
{
|
|
return js_array_buffer_constructor3(ctx, JS_UNDEFINED, len,
|
|
JS_CLASS_ARRAY_BUFFER,
|
|
(uint8_t *)buf,
|
|
js_array_buffer_free, NULL,
|
|
TRUE);
|
|
}
|
|
|
|
static JSValue js_array_buffer_constructor(JSContext *ctx,
|
|
JSValue new_target,
|
|
int argc, JSValue *argv)
|
|
{
|
|
uint64_t len;
|
|
if (JS_ToIndex(ctx, &len, argv[0]))
|
|
return JS_EXCEPTION;
|
|
return js_array_buffer_constructor1(ctx, new_target, len);
|
|
}
|
|
|
|
static JSValue js_shared_array_buffer_constructor(JSContext *ctx,
|
|
JSValue new_target,
|
|
int argc, JSValue *argv)
|
|
{
|
|
uint64_t len;
|
|
if (JS_ToIndex(ctx, &len, argv[0]))
|
|
return JS_EXCEPTION;
|
|
return js_array_buffer_constructor2(ctx, new_target, len,
|
|
JS_CLASS_SHARED_ARRAY_BUFFER);
|
|
}
|
|
|
|
/* also used for SharedArrayBuffer */
|
|
static void js_array_buffer_finalizer(JSRuntime *rt, JSValue val)
|
|
{
|
|
JSObject *p = JS_VALUE_GET_OBJ(val);
|
|
JSArrayBuffer *abuf = p->u.array_buffer;
|
|
struct list_head *el, *el1;
|
|
|
|
if (abuf) {
|
|
/* The ArrayBuffer finalizer may be called before the typed
|
|
array finalizers using it, so abuf->array_list is not
|
|
necessarily empty. */
|
|
list_for_each_safe(el, el1, &abuf->array_list) {
|
|
JSTypedArray *ta;
|
|
JSObject *p1;
|
|
|
|
ta = list_entry(el, JSTypedArray, link);
|
|
ta->link.prev = NULL;
|
|
ta->link.next = NULL;
|
|
p1 = ta->obj;
|
|
/* Note: the typed array length and offset fields are not modified */
|
|
if (p1->class_id != JS_CLASS_DATAVIEW) {
|
|
p1->u.array.count = 0;
|
|
p1->u.array.u.ptr = NULL;
|
|
}
|
|
}
|
|
if (abuf->shared && rt->sab_funcs.sab_free) {
|
|
rt->sab_funcs.sab_free(rt->sab_funcs.sab_opaque, abuf->data);
|
|
} else {
|
|
if (abuf->free_func)
|
|
abuf->free_func(rt, abuf->opaque, abuf->data);
|
|
}
|
|
js_free_rt(rt, abuf);
|
|
}
|
|
}
|
|
|
|
static JSValue js_array_buffer_isView(JSContext *ctx,
|
|
JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSObject *p;
|
|
BOOL res;
|
|
res = FALSE;
|
|
if (JS_VALUE_GET_TAG(argv[0]) == JS_TAG_OBJECT) {
|
|
p = JS_VALUE_GET_OBJ(argv[0]);
|
|
if (p->class_id >= JS_CLASS_UINT8C_ARRAY &&
|
|
p->class_id <= JS_CLASS_DATAVIEW) {
|
|
res = TRUE;
|
|
}
|
|
}
|
|
return js_bool(res);
|
|
}
|
|
|
|
static const JSCFunctionListEntry js_array_buffer_funcs[] = {
|
|
JS_CFUNC_DEF("isView", 1, js_array_buffer_isView ),
|
|
JS_CGETSET_DEF("[Symbol.species]", js_get_this, NULL ),
|
|
};
|
|
|
|
static JSValue JS_ThrowTypeErrorDetachedArrayBuffer(JSContext *ctx)
|
|
{
|
|
return JS_ThrowTypeError(ctx, "ArrayBuffer is detached");
|
|
}
|
|
|
|
// #sec-get-arraybuffer.prototype.detached
|
|
static JSValue js_array_buffer_get_detached(JSContext *ctx,
|
|
JSValue this_val)
|
|
{
|
|
JSArrayBuffer *abuf = JS_GetOpaque2(ctx, this_val, JS_CLASS_ARRAY_BUFFER);
|
|
if (!abuf)
|
|
return JS_EXCEPTION;
|
|
if (abuf->shared)
|
|
return JS_ThrowTypeError(ctx, "detached called on SharedArrayBuffer");
|
|
return js_bool(abuf->detached);
|
|
}
|
|
|
|
static JSValue js_array_buffer_get_byteLength(JSContext *ctx,
|
|
JSValue this_val,
|
|
int class_id)
|
|
{
|
|
JSArrayBuffer *abuf = JS_GetOpaque2(ctx, this_val, class_id);
|
|
if (!abuf)
|
|
return JS_EXCEPTION;
|
|
/* return 0 if detached */
|
|
return js_uint32(abuf->byte_length);
|
|
}
|
|
|
|
void JS_DetachArrayBuffer(JSContext *ctx, JSValue obj)
|
|
{
|
|
JSArrayBuffer *abuf = JS_GetOpaque(obj, JS_CLASS_ARRAY_BUFFER);
|
|
struct list_head *el;
|
|
|
|
if (!abuf || abuf->detached)
|
|
return;
|
|
if (abuf->free_func)
|
|
abuf->free_func(ctx->rt, abuf->opaque, abuf->data);
|
|
abuf->data = NULL;
|
|
abuf->byte_length = 0;
|
|
abuf->detached = TRUE;
|
|
|
|
list_for_each(el, &abuf->array_list) {
|
|
JSTypedArray *ta;
|
|
JSObject *p;
|
|
|
|
ta = list_entry(el, JSTypedArray, link);
|
|
p = ta->obj;
|
|
/* Note: the typed array length and offset fields are not modified */
|
|
if (p->class_id != JS_CLASS_DATAVIEW) {
|
|
p->u.array.count = 0;
|
|
p->u.array.u.ptr = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* get an ArrayBuffer or SharedArrayBuffer */
|
|
static JSArrayBuffer *js_get_array_buffer(JSContext *ctx, JSValue obj)
|
|
{
|
|
JSObject *p;
|
|
if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)
|
|
goto fail;
|
|
p = JS_VALUE_GET_OBJ(obj);
|
|
if (p->class_id != JS_CLASS_ARRAY_BUFFER &&
|
|
p->class_id != JS_CLASS_SHARED_ARRAY_BUFFER) {
|
|
fail:
|
|
JS_ThrowTypeErrorInvalidClass(ctx, JS_CLASS_ARRAY_BUFFER);
|
|
return NULL;
|
|
}
|
|
return p->u.array_buffer;
|
|
}
|
|
|
|
/* return NULL if exception. WARNING: any JS call can detach the
|
|
buffer and render the returned pointer invalid */
|
|
uint8_t *JS_GetArrayBuffer(JSContext *ctx, size_t *psize, JSValue obj)
|
|
{
|
|
JSArrayBuffer *abuf = js_get_array_buffer(ctx, obj);
|
|
if (!abuf)
|
|
goto fail;
|
|
if (abuf->detached) {
|
|
JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
|
|
goto fail;
|
|
}
|
|
*psize = abuf->byte_length;
|
|
return abuf->data;
|
|
fail:
|
|
*psize = 0;
|
|
return NULL;
|
|
}
|
|
|
|
// ES #sec-arraybuffer.prototype.transfer
|
|
static JSValue js_array_buffer_transfer(JSContext *ctx,
|
|
JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSArrayBuffer *abuf;
|
|
uint64_t new_len, old_len;
|
|
uint8_t *bs, *new_bs;
|
|
|
|
abuf = JS_GetOpaque2(ctx, this_val, JS_CLASS_ARRAY_BUFFER);
|
|
if (!abuf)
|
|
return JS_EXCEPTION;
|
|
if (abuf->shared)
|
|
return JS_ThrowTypeError(ctx, "cannot transfer a SharedArrayBuffer");
|
|
if (argc < 1 || JS_IsUndefined(argv[0]))
|
|
new_len = abuf->byte_length;
|
|
else if (JS_ToIndex(ctx, &new_len, argv[0]))
|
|
return JS_EXCEPTION;
|
|
if (abuf->detached)
|
|
return JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
|
|
/* create an empty AB */
|
|
if (new_len == 0) {
|
|
JS_DetachArrayBuffer(ctx, this_val);
|
|
return js_array_buffer_constructor2(ctx, JS_UNDEFINED, 0, JS_CLASS_ARRAY_BUFFER);
|
|
}
|
|
bs = abuf->data;
|
|
old_len = abuf->byte_length;
|
|
/* if length mismatch, realloc. Otherwise, use the same backing buffer. */
|
|
if (new_len != old_len) {
|
|
new_bs = js_realloc(ctx, bs, new_len);
|
|
if (!new_bs)
|
|
return JS_EXCEPTION;
|
|
bs = new_bs;
|
|
if (new_len > old_len)
|
|
memset(bs + old_len, 0, new_len - old_len);
|
|
}
|
|
/* neuter the backing buffer */
|
|
abuf->data = NULL;
|
|
abuf->byte_length = 0;
|
|
abuf->detached = TRUE;
|
|
return js_array_buffer_constructor3(ctx, JS_UNDEFINED, new_len,
|
|
JS_CLASS_ARRAY_BUFFER,
|
|
bs, abuf->free_func,
|
|
NULL, FALSE);
|
|
}
|
|
|
|
static JSValue js_array_buffer_slice(JSContext *ctx,
|
|
JSValue this_val,
|
|
int argc, JSValue *argv, int class_id)
|
|
{
|
|
JSArrayBuffer *abuf, *new_abuf;
|
|
int64_t len, start, end, new_len;
|
|
JSValue ctor, new_obj;
|
|
|
|
abuf = JS_GetOpaque2(ctx, this_val, class_id);
|
|
if (!abuf)
|
|
return JS_EXCEPTION;
|
|
if (abuf->detached)
|
|
return JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
|
|
len = abuf->byte_length;
|
|
|
|
if (JS_ToInt64Clamp(ctx, &start, argv[0], 0, len, len))
|
|
return JS_EXCEPTION;
|
|
|
|
end = len;
|
|
if (!JS_IsUndefined(argv[1])) {
|
|
if (JS_ToInt64Clamp(ctx, &end, argv[1], 0, len, len))
|
|
return JS_EXCEPTION;
|
|
}
|
|
new_len = max_int64(end - start, 0);
|
|
ctor = JS_SpeciesConstructor(ctx, this_val, JS_UNDEFINED);
|
|
if (JS_IsException(ctor))
|
|
return ctor;
|
|
if (JS_IsUndefined(ctor)) {
|
|
new_obj = js_array_buffer_constructor2(ctx, JS_UNDEFINED, new_len,
|
|
class_id);
|
|
} else {
|
|
JSValue args[1];
|
|
args[0] = JS_NewInt64(ctx, new_len);
|
|
new_obj = JS_CallConstructor(ctx, ctor, 1, args);
|
|
JS_FreeValue(ctx, ctor);
|
|
JS_FreeValue(ctx, args[0]);
|
|
}
|
|
if (JS_IsException(new_obj))
|
|
return new_obj;
|
|
new_abuf = JS_GetOpaque2(ctx, new_obj, class_id);
|
|
if (!new_abuf)
|
|
goto fail;
|
|
if (js_same_value(ctx, new_obj, this_val)) {
|
|
JS_ThrowTypeError(ctx, "cannot use identical ArrayBuffer");
|
|
goto fail;
|
|
}
|
|
if (new_abuf->detached) {
|
|
JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
|
|
goto fail;
|
|
}
|
|
if (new_abuf->byte_length < new_len) {
|
|
JS_ThrowTypeError(ctx, "new ArrayBuffer is too small");
|
|
goto fail;
|
|
}
|
|
/* must test again because of side effects */
|
|
if (abuf->detached) {
|
|
JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
|
|
goto fail;
|
|
}
|
|
memcpy(new_abuf->data, abuf->data + start, new_len);
|
|
return new_obj;
|
|
fail:
|
|
JS_FreeValue(ctx, new_obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static const JSCFunctionListEntry js_array_buffer_proto_funcs[] = {
|
|
JS_CGETSET_MAGIC_DEF("byteLength", js_array_buffer_get_byteLength, NULL, JS_CLASS_ARRAY_BUFFER ),
|
|
JS_CGETSET_DEF("detached", js_array_buffer_get_detached, NULL ),
|
|
JS_CFUNC_MAGIC_DEF("slice", 2, js_array_buffer_slice, JS_CLASS_ARRAY_BUFFER ),
|
|
JS_CFUNC_DEF("transfer", 0, js_array_buffer_transfer ),
|
|
JS_CFUNC_DEF("transferToFixedLength", 0, js_array_buffer_transfer ),
|
|
JS_PROP_STRING_DEF("[Symbol.toStringTag]", "ArrayBuffer", JS_PROP_CONFIGURABLE ),
|
|
};
|
|
|
|
/* SharedArrayBuffer */
|
|
|
|
static const JSCFunctionListEntry js_shared_array_buffer_funcs[] = {
|
|
JS_CGETSET_DEF("[Symbol.species]", js_get_this, NULL ),
|
|
};
|
|
|
|
static const JSCFunctionListEntry js_shared_array_buffer_proto_funcs[] = {
|
|
JS_CGETSET_MAGIC_DEF("byteLength", js_array_buffer_get_byteLength, NULL, JS_CLASS_SHARED_ARRAY_BUFFER ),
|
|
JS_CFUNC_MAGIC_DEF("slice", 2, js_array_buffer_slice, JS_CLASS_SHARED_ARRAY_BUFFER ),
|
|
JS_PROP_STRING_DEF("[Symbol.toStringTag]", "SharedArrayBuffer", JS_PROP_CONFIGURABLE ),
|
|
};
|
|
|
|
static JSObject *get_typed_array(JSContext *ctx,
|
|
JSValue this_val,
|
|
int is_dataview)
|
|
{
|
|
JSObject *p;
|
|
if (JS_VALUE_GET_TAG(this_val) != JS_TAG_OBJECT)
|
|
goto fail;
|
|
p = JS_VALUE_GET_OBJ(this_val);
|
|
if (is_dataview) {
|
|
if (p->class_id != JS_CLASS_DATAVIEW)
|
|
goto fail;
|
|
} else {
|
|
if (!(p->class_id >= JS_CLASS_UINT8C_ARRAY &&
|
|
p->class_id <= JS_CLASS_FLOAT64_ARRAY)) {
|
|
fail:
|
|
JS_ThrowTypeError(ctx, "not a %s", is_dataview ? "DataView" : "TypedArray");
|
|
return NULL;
|
|
}
|
|
}
|
|
return p;
|
|
}
|
|
|
|
/* WARNING: 'p' must be a typed array */
|
|
static BOOL typed_array_is_detached(JSContext *ctx, JSObject *p)
|
|
{
|
|
JSTypedArray *ta = p->u.typed_array;
|
|
JSArrayBuffer *abuf = ta->buffer->u.array_buffer;
|
|
/* XXX: could simplify test by ensuring that
|
|
p->u.array.u.ptr is NULL iff it is detached */
|
|
return abuf->detached;
|
|
}
|
|
|
|
/* WARNING: 'p' must be a typed array. Works even if the array buffer
|
|
is detached */
|
|
static uint32_t typed_array_get_length(JSContext *ctx, JSObject *p)
|
|
{
|
|
JSTypedArray *ta = p->u.typed_array;
|
|
int size_log2 = typed_array_size_log2(p->class_id);
|
|
return ta->length >> size_log2;
|
|
}
|
|
|
|
static int validate_typed_array(JSContext *ctx, JSValue this_val)
|
|
{
|
|
JSObject *p;
|
|
p = get_typed_array(ctx, this_val, 0);
|
|
if (!p)
|
|
return -1;
|
|
if (typed_array_is_detached(ctx, p)) {
|
|
JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static JSValue js_typed_array_get_length(JSContext *ctx,
|
|
JSValue this_val)
|
|
{
|
|
JSObject *p;
|
|
p = get_typed_array(ctx, this_val, 0);
|
|
if (!p)
|
|
return JS_EXCEPTION;
|
|
return js_int32(p->u.array.count);
|
|
}
|
|
|
|
static JSValue js_typed_array_get_buffer(JSContext *ctx,
|
|
JSValue this_val, int is_dataview)
|
|
{
|
|
JSObject *p;
|
|
JSTypedArray *ta;
|
|
p = get_typed_array(ctx, this_val, is_dataview);
|
|
if (!p)
|
|
return JS_EXCEPTION;
|
|
ta = p->u.typed_array;
|
|
return js_dup(JS_MKPTR(JS_TAG_OBJECT, ta->buffer));
|
|
}
|
|
|
|
static JSValue js_typed_array_get_byteLength(JSContext *ctx,
|
|
JSValue this_val,
|
|
int is_dataview)
|
|
{
|
|
JSObject *p;
|
|
JSTypedArray *ta;
|
|
p = get_typed_array(ctx, this_val, is_dataview);
|
|
if (!p)
|
|
return JS_EXCEPTION;
|
|
if (typed_array_is_detached(ctx, p)) {
|
|
if (is_dataview) {
|
|
return JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
|
|
} else {
|
|
return js_int32(0);
|
|
}
|
|
}
|
|
ta = p->u.typed_array;
|
|
return js_int32(ta->length);
|
|
}
|
|
|
|
static JSValue js_typed_array_get_byteOffset(JSContext *ctx,
|
|
JSValue this_val,
|
|
int is_dataview)
|
|
{
|
|
JSObject *p;
|
|
JSTypedArray *ta;
|
|
p = get_typed_array(ctx, this_val, is_dataview);
|
|
if (!p)
|
|
return JS_EXCEPTION;
|
|
if (typed_array_is_detached(ctx, p)) {
|
|
if (is_dataview) {
|
|
return JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
|
|
} else {
|
|
return js_int32(0);
|
|
}
|
|
}
|
|
ta = p->u.typed_array;
|
|
return js_int32(ta->offset);
|
|
}
|
|
|
|
/* Return the buffer associated to the typed array or an exception if
|
|
it is not a typed array or if the buffer is detached. pbyte_offset,
|
|
pbyte_length or pbytes_per_element can be NULL. */
|
|
JSValue JS_GetTypedArrayBuffer(JSContext *ctx, JSValue obj,
|
|
size_t *pbyte_offset,
|
|
size_t *pbyte_length,
|
|
size_t *pbytes_per_element)
|
|
{
|
|
JSObject *p;
|
|
JSTypedArray *ta;
|
|
p = get_typed_array(ctx, obj, FALSE);
|
|
if (!p)
|
|
return JS_EXCEPTION;
|
|
if (typed_array_is_detached(ctx, p))
|
|
return JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
|
|
ta = p->u.typed_array;
|
|
if (pbyte_offset)
|
|
*pbyte_offset = ta->offset;
|
|
if (pbyte_length)
|
|
*pbyte_length = ta->length;
|
|
if (pbytes_per_element) {
|
|
*pbytes_per_element = 1 << typed_array_size_log2(p->class_id);
|
|
}
|
|
return js_dup(JS_MKPTR(JS_TAG_OBJECT, ta->buffer));
|
|
}
|
|
|
|
/* return NULL if exception. WARNING: any JS call can detach the
|
|
buffer and render the returned pointer invalid */
|
|
uint8_t *JS_GetUint8Array(JSContext *ctx, size_t *psize, JSValue obj)
|
|
{
|
|
JSObject *p;
|
|
JSTypedArray *ta;
|
|
JSArrayBuffer *abuf;
|
|
p = get_typed_array(ctx, obj, FALSE);
|
|
if (!p)
|
|
goto fail;
|
|
if (typed_array_is_detached(ctx, p)) {
|
|
JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
|
|
goto fail;
|
|
}
|
|
if (p->class_id != JS_CLASS_UINT8_ARRAY && p->class_id != JS_CLASS_UINT8C_ARRAY) {
|
|
JS_ThrowTypeError(ctx, "not a Uint8Array");
|
|
goto fail;
|
|
}
|
|
ta = p->u.typed_array;
|
|
abuf = ta->buffer->u.array_buffer;
|
|
|
|
*psize = ta->length;
|
|
return abuf->data + ta->offset;
|
|
fail:
|
|
*psize = 0;
|
|
return NULL;
|
|
}
|
|
|
|
static JSValue js_typed_array_get_toStringTag(JSContext *ctx,
|
|
JSValue this_val)
|
|
{
|
|
JSObject *p;
|
|
if (JS_VALUE_GET_TAG(this_val) != JS_TAG_OBJECT)
|
|
return JS_UNDEFINED;
|
|
p = JS_VALUE_GET_OBJ(this_val);
|
|
if (!(p->class_id >= JS_CLASS_UINT8C_ARRAY &&
|
|
p->class_id <= JS_CLASS_FLOAT64_ARRAY))
|
|
return JS_UNDEFINED;
|
|
return JS_AtomToString(ctx, ctx->rt->class_array[p->class_id].class_name);
|
|
}
|
|
|
|
static JSValue js_typed_array_set_internal(JSContext *ctx,
|
|
JSValue dst,
|
|
JSValue src,
|
|
JSValue off)
|
|
{
|
|
JSObject *p;
|
|
JSObject *src_p;
|
|
uint32_t i;
|
|
int64_t src_len, offset;
|
|
JSValue val, src_obj = JS_UNDEFINED;
|
|
|
|
p = get_typed_array(ctx, dst, 0);
|
|
if (!p)
|
|
goto fail;
|
|
if (JS_ToInt64Sat(ctx, &offset, off))
|
|
goto fail;
|
|
if (offset < 0)
|
|
goto range_error;
|
|
if (typed_array_is_detached(ctx, p)) {
|
|
detached:
|
|
JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
|
|
goto fail;
|
|
}
|
|
src_obj = JS_ToObject(ctx, src);
|
|
if (JS_IsException(src_obj))
|
|
goto fail;
|
|
src_p = JS_VALUE_GET_OBJ(src_obj);
|
|
if (src_p->class_id >= JS_CLASS_UINT8C_ARRAY &&
|
|
src_p->class_id <= JS_CLASS_FLOAT64_ARRAY) {
|
|
JSTypedArray *dest_ta = p->u.typed_array;
|
|
JSArrayBuffer *dest_abuf = dest_ta->buffer->u.array_buffer;
|
|
JSTypedArray *src_ta = src_p->u.typed_array;
|
|
JSArrayBuffer *src_abuf = src_ta->buffer->u.array_buffer;
|
|
int shift = typed_array_size_log2(p->class_id);
|
|
|
|
if (src_abuf->detached)
|
|
goto detached;
|
|
|
|
src_len = src_p->u.array.count;
|
|
if (offset > (int64_t)(p->u.array.count - src_len))
|
|
goto range_error;
|
|
|
|
/* copying between typed objects */
|
|
if (src_p->class_id == p->class_id) {
|
|
/* same type, use memmove */
|
|
memmove(dest_abuf->data + dest_ta->offset + (offset << shift),
|
|
src_abuf->data + src_ta->offset, src_len << shift);
|
|
goto done;
|
|
}
|
|
if (dest_abuf->data == src_abuf->data) {
|
|
/* copying between the same buffer using different types of mappings
|
|
would require a temporary buffer */
|
|
}
|
|
/* otherwise, default behavior is slow but correct */
|
|
} else {
|
|
if (js_get_length64(ctx, &src_len, src_obj))
|
|
goto fail;
|
|
if (offset > (int64_t)(p->u.array.count - src_len)) {
|
|
range_error:
|
|
JS_ThrowRangeError(ctx, "invalid array length");
|
|
goto fail;
|
|
}
|
|
}
|
|
for(i = 0; i < src_len; i++) {
|
|
val = JS_GetPropertyUint32(ctx, src_obj, i);
|
|
if (JS_IsException(val))
|
|
goto fail;
|
|
// Per spec: detaching the TA mid-iteration is allowed and should
|
|
// not throw an exception. Because iteration over the source array is
|
|
// observable, we cannot bail out early when the TA is first detached.
|
|
if (typed_array_is_detached(ctx, p)) {
|
|
JS_FreeValue(ctx, val);
|
|
} else if (JS_SetPropertyUint32(ctx, dst, offset + i, val) < 0) {
|
|
goto fail;
|
|
}
|
|
}
|
|
done:
|
|
JS_FreeValue(ctx, src_obj);
|
|
return JS_UNDEFINED;
|
|
fail:
|
|
JS_FreeValue(ctx, src_obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue js_typed_array_at(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSObject *p;
|
|
int64_t idx, len;
|
|
|
|
p = get_typed_array(ctx, this_val, /*is_dataview*/0);
|
|
if (!p)
|
|
return JS_EXCEPTION;
|
|
|
|
if (typed_array_is_detached(ctx, p)) {
|
|
JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
if (JS_ToInt64Sat(ctx, &idx, argv[0]))
|
|
return JS_EXCEPTION;
|
|
|
|
len = p->u.array.count;
|
|
if (idx < 0)
|
|
idx = len + idx;
|
|
|
|
if (idx < 0 || idx >= len)
|
|
return JS_UNDEFINED;
|
|
|
|
switch (p->class_id) {
|
|
case JS_CLASS_INT8_ARRAY:
|
|
return js_int32(p->u.array.u.int8_ptr[idx]);
|
|
case JS_CLASS_UINT8C_ARRAY:
|
|
case JS_CLASS_UINT8_ARRAY:
|
|
return js_int32(p->u.array.u.uint8_ptr[idx]);
|
|
case JS_CLASS_INT16_ARRAY:
|
|
return js_int32(p->u.array.u.int16_ptr[idx]);
|
|
case JS_CLASS_UINT16_ARRAY:
|
|
return js_int32(p->u.array.u.uint16_ptr[idx]);
|
|
case JS_CLASS_INT32_ARRAY:
|
|
return js_int32(p->u.array.u.int32_ptr[idx]);
|
|
case JS_CLASS_UINT32_ARRAY:
|
|
return js_uint32(p->u.array.u.uint32_ptr[idx]);
|
|
case JS_CLASS_FLOAT32_ARRAY:
|
|
return js_float64(p->u.array.u.float_ptr[idx]);
|
|
case JS_CLASS_FLOAT64_ARRAY:
|
|
return js_float64(p->u.array.u.double_ptr[idx]);
|
|
case JS_CLASS_BIG_INT64_ARRAY:
|
|
return JS_NewBigInt64(ctx, p->u.array.u.int64_ptr[idx]);
|
|
case JS_CLASS_BIG_UINT64_ARRAY:
|
|
return JS_NewBigUint64(ctx, p->u.array.u.uint64_ptr[idx]);
|
|
}
|
|
|
|
abort(); /* unreachable */
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
static JSValue js_typed_array_with(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue arr, val;
|
|
JSObject *p;
|
|
int64_t idx, len;
|
|
|
|
p = get_typed_array(ctx, this_val, /*is_dataview*/0);
|
|
if (!p)
|
|
return JS_EXCEPTION;
|
|
|
|
if (JS_ToInt64Sat(ctx, &idx, argv[0]))
|
|
return JS_EXCEPTION;
|
|
|
|
len = p->u.array.count;
|
|
if (idx < 0)
|
|
idx = len + idx;
|
|
if (idx < 0 || idx >= len)
|
|
return JS_ThrowRangeError(ctx, "invalid array index");
|
|
|
|
val = JS_ToPrimitive(ctx, argv[1], HINT_NUMBER);
|
|
if (JS_IsException(val))
|
|
return JS_EXCEPTION;
|
|
|
|
arr = js_typed_array_constructor_ta(ctx, JS_UNDEFINED, this_val,
|
|
p->class_id);
|
|
if (JS_IsException(arr)) {
|
|
JS_FreeValue(ctx, val);
|
|
return JS_EXCEPTION;
|
|
}
|
|
if (JS_SetPropertyInt64(ctx, arr, idx, val) < 0) {
|
|
JS_FreeValue(ctx, arr);
|
|
return JS_EXCEPTION;
|
|
}
|
|
return arr;
|
|
}
|
|
|
|
static JSValue js_typed_array_set(JSContext *ctx,
|
|
JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue offset = JS_UNDEFINED;
|
|
if (argc > 1) {
|
|
offset = argv[1];
|
|
}
|
|
return js_typed_array_set_internal(ctx, this_val, argv[0], offset);
|
|
}
|
|
|
|
static JSValue js_create_typed_array_iterator(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv, int magic)
|
|
{
|
|
if (validate_typed_array(ctx, this_val))
|
|
return JS_EXCEPTION;
|
|
return js_create_array_iterator(ctx, this_val, argc, argv, magic);
|
|
}
|
|
|
|
/* return < 0 if exception */
|
|
static int js_typed_array_get_length_internal(JSContext *ctx,
|
|
JSValue obj)
|
|
{
|
|
JSObject *p;
|
|
p = get_typed_array(ctx, obj, 0);
|
|
if (!p)
|
|
return -1;
|
|
if (typed_array_is_detached(ctx, p)) {
|
|
JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
|
|
return -1;
|
|
}
|
|
return p->u.array.count;
|
|
}
|
|
|
|
static JSValue js_typed_array_create(JSContext *ctx, JSValue ctor,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue ret;
|
|
int new_len;
|
|
int64_t len;
|
|
|
|
ret = JS_CallConstructor(ctx, ctor, argc, argv);
|
|
if (JS_IsException(ret))
|
|
return ret;
|
|
/* validate the typed array */
|
|
new_len = js_typed_array_get_length_internal(ctx, ret);
|
|
if (new_len < 0)
|
|
goto fail;
|
|
if (argc == 1) {
|
|
/* ensure that it is large enough */
|
|
if (JS_ToLengthFree(ctx, &len, js_dup(argv[0])))
|
|
goto fail;
|
|
if (new_len < len) {
|
|
JS_ThrowTypeError(ctx, "TypedArray length is too small");
|
|
fail:
|
|
JS_FreeValue(ctx, ret);
|
|
return JS_EXCEPTION;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static JSValue js_typed_array___speciesCreate(JSContext *ctx,
|
|
JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue obj;
|
|
JSObject *p;
|
|
JSValue ctor, ret;
|
|
int argc1;
|
|
|
|
obj = argv[0];
|
|
p = get_typed_array(ctx, obj, 0);
|
|
if (!p)
|
|
return JS_EXCEPTION;
|
|
ctor = JS_SpeciesConstructor(ctx, obj, JS_UNDEFINED);
|
|
if (JS_IsException(ctor))
|
|
return ctor;
|
|
argc1 = max_int(argc - 1, 0);
|
|
if (JS_IsUndefined(ctor)) {
|
|
ret = js_typed_array_constructor(ctx, JS_UNDEFINED, argc1, argv + 1,
|
|
p->class_id);
|
|
} else {
|
|
ret = js_typed_array_create(ctx, ctor, argc1, argv + 1);
|
|
JS_FreeValue(ctx, ctor);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static JSValue js_typed_array_from(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
// from(items, mapfn = void 0, this_arg = void 0)
|
|
JSValue items = argv[0], mapfn, this_arg;
|
|
JSValue args[2];
|
|
JSValue stack[2];
|
|
JSValue iter, arr, r, v, v2;
|
|
int64_t k, len;
|
|
int done, mapping;
|
|
|
|
mapping = FALSE;
|
|
mapfn = JS_UNDEFINED;
|
|
this_arg = JS_UNDEFINED;
|
|
r = JS_UNDEFINED;
|
|
arr = JS_UNDEFINED;
|
|
stack[0] = JS_UNDEFINED;
|
|
stack[1] = JS_UNDEFINED;
|
|
|
|
if (argc > 1) {
|
|
mapfn = argv[1];
|
|
if (!JS_IsUndefined(mapfn)) {
|
|
if (check_function(ctx, mapfn))
|
|
goto exception;
|
|
mapping = 1;
|
|
if (argc > 2)
|
|
this_arg = argv[2];
|
|
}
|
|
}
|
|
iter = JS_GetProperty(ctx, items, JS_ATOM_Symbol_iterator);
|
|
if (JS_IsException(iter))
|
|
goto exception;
|
|
if (!JS_IsUndefined(iter)) {
|
|
JS_FreeValue(ctx, iter);
|
|
arr = JS_NewArray(ctx);
|
|
if (JS_IsException(arr))
|
|
goto exception;
|
|
stack[0] = js_dup(items);
|
|
if (js_for_of_start(ctx, &stack[1], FALSE))
|
|
goto exception;
|
|
for (k = 0;; k++) {
|
|
v = JS_IteratorNext(ctx, stack[0], stack[1], 0, NULL, &done);
|
|
if (JS_IsException(v))
|
|
goto exception_close;
|
|
if (done)
|
|
break;
|
|
if (JS_DefinePropertyValueInt64(ctx, arr, k, v, JS_PROP_C_W_E | JS_PROP_THROW) < 0)
|
|
goto exception_close;
|
|
}
|
|
} else {
|
|
arr = JS_ToObject(ctx, items);
|
|
if (JS_IsException(arr))
|
|
goto exception;
|
|
}
|
|
if (js_get_length64(ctx, &len, arr) < 0)
|
|
goto exception;
|
|
v = JS_NewInt64(ctx, len);
|
|
args[0] = v;
|
|
r = js_typed_array_create(ctx, this_val, 1, args);
|
|
JS_FreeValue(ctx, v);
|
|
if (JS_IsException(r))
|
|
goto exception;
|
|
for(k = 0; k < len; k++) {
|
|
v = JS_GetPropertyInt64(ctx, arr, k);
|
|
if (JS_IsException(v))
|
|
goto exception;
|
|
if (mapping) {
|
|
args[0] = v;
|
|
args[1] = js_int32(k);
|
|
v2 = JS_Call(ctx, mapfn, this_arg, 2, args);
|
|
JS_FreeValue(ctx, v);
|
|
v = v2;
|
|
if (JS_IsException(v))
|
|
goto exception;
|
|
}
|
|
if (JS_SetPropertyInt64(ctx, r, k, v) < 0)
|
|
goto exception;
|
|
}
|
|
goto done;
|
|
|
|
exception_close:
|
|
if (!JS_IsUndefined(stack[0]))
|
|
JS_IteratorClose(ctx, stack[0], TRUE);
|
|
exception:
|
|
JS_FreeValue(ctx, r);
|
|
r = JS_EXCEPTION;
|
|
done:
|
|
JS_FreeValue(ctx, arr);
|
|
JS_FreeValue(ctx, stack[0]);
|
|
JS_FreeValue(ctx, stack[1]);
|
|
return r;
|
|
}
|
|
|
|
static JSValue js_typed_array_of(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue obj;
|
|
JSValue args[1];
|
|
int i;
|
|
|
|
args[0] = js_int32(argc);
|
|
obj = js_typed_array_create(ctx, this_val, 1, args);
|
|
if (JS_IsException(obj))
|
|
return obj;
|
|
|
|
for(i = 0; i < argc; i++) {
|
|
if (JS_SetPropertyUint32(ctx, obj, i, js_dup(argv[i])) < 0) {
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
}
|
|
return obj;
|
|
}
|
|
|
|
static JSValue js_typed_array_copyWithin(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSObject *p;
|
|
int len, to, from, final, count, shift;
|
|
|
|
len = js_typed_array_get_length_internal(ctx, this_val);
|
|
if (len < 0)
|
|
return JS_EXCEPTION;
|
|
|
|
if (JS_ToInt32Clamp(ctx, &to, argv[0], 0, len, len))
|
|
return JS_EXCEPTION;
|
|
|
|
if (JS_ToInt32Clamp(ctx, &from, argv[1], 0, len, len))
|
|
return JS_EXCEPTION;
|
|
|
|
final = len;
|
|
if (argc > 2 && !JS_IsUndefined(argv[2])) {
|
|
if (JS_ToInt32Clamp(ctx, &final, argv[2], 0, len, len))
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
count = min_int(final - from, len - to);
|
|
if (count > 0) {
|
|
p = JS_VALUE_GET_OBJ(this_val);
|
|
if (typed_array_is_detached(ctx, p))
|
|
return JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
|
|
shift = typed_array_size_log2(p->class_id);
|
|
memmove(p->u.array.u.uint8_ptr + (to << shift),
|
|
p->u.array.u.uint8_ptr + (from << shift),
|
|
count << shift);
|
|
}
|
|
return js_dup(this_val);
|
|
}
|
|
|
|
static JSValue js_typed_array_fill(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSObject *p;
|
|
int len, k, final, shift;
|
|
uint64_t v64;
|
|
|
|
len = js_typed_array_get_length_internal(ctx, this_val);
|
|
if (len < 0)
|
|
return JS_EXCEPTION;
|
|
p = JS_VALUE_GET_OBJ(this_val);
|
|
|
|
if (p->class_id == JS_CLASS_UINT8C_ARRAY) {
|
|
int32_t v;
|
|
if (JS_ToUint8ClampFree(ctx, &v, js_dup(argv[0])))
|
|
return JS_EXCEPTION;
|
|
v64 = v;
|
|
} else if (p->class_id <= JS_CLASS_UINT32_ARRAY) {
|
|
uint32_t v;
|
|
if (JS_ToUint32(ctx, &v, argv[0]))
|
|
return JS_EXCEPTION;
|
|
v64 = v;
|
|
} else
|
|
if (p->class_id <= JS_CLASS_BIG_UINT64_ARRAY) {
|
|
if (JS_ToBigInt64(ctx, (int64_t *)&v64, argv[0]))
|
|
return JS_EXCEPTION;
|
|
} else {
|
|
double d;
|
|
if (JS_ToFloat64(ctx, &d, argv[0]))
|
|
return JS_EXCEPTION;
|
|
if (p->class_id == JS_CLASS_FLOAT32_ARRAY) {
|
|
union {
|
|
float f;
|
|
uint32_t u32;
|
|
} u;
|
|
u.f = d;
|
|
v64 = u.u32;
|
|
} else {
|
|
JSFloat64Union u;
|
|
u.d = d;
|
|
v64 = u.u64;
|
|
}
|
|
}
|
|
|
|
k = 0;
|
|
if (argc > 1) {
|
|
if (JS_ToInt32Clamp(ctx, &k, argv[1], 0, len, len))
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
final = len;
|
|
if (argc > 2 && !JS_IsUndefined(argv[2])) {
|
|
if (JS_ToInt32Clamp(ctx, &final, argv[2], 0, len, len))
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
if (typed_array_is_detached(ctx, p))
|
|
return JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
|
|
|
|
shift = typed_array_size_log2(p->class_id);
|
|
switch(shift) {
|
|
case 0:
|
|
if (k < final) {
|
|
memset(p->u.array.u.uint8_ptr + k, v64, final - k);
|
|
}
|
|
break;
|
|
case 1:
|
|
for(; k < final; k++) {
|
|
p->u.array.u.uint16_ptr[k] = v64;
|
|
}
|
|
break;
|
|
case 2:
|
|
for(; k < final; k++) {
|
|
p->u.array.u.uint32_ptr[k] = v64;
|
|
}
|
|
break;
|
|
case 3:
|
|
for(; k < final; k++) {
|
|
p->u.array.u.uint64_ptr[k] = v64;
|
|
}
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
return js_dup(this_val);
|
|
}
|
|
|
|
static JSValue js_typed_array_find(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv, int mode)
|
|
{
|
|
JSValue func, this_arg;
|
|
JSValue args[3];
|
|
JSValue val, index_val, res;
|
|
int len, k, end;
|
|
int dir;
|
|
|
|
val = JS_UNDEFINED;
|
|
len = js_typed_array_get_length_internal(ctx, this_val);
|
|
if (len < 0)
|
|
goto exception;
|
|
|
|
func = argv[0];
|
|
if (check_function(ctx, func))
|
|
goto exception;
|
|
|
|
this_arg = JS_UNDEFINED;
|
|
if (argc > 1)
|
|
this_arg = argv[1];
|
|
|
|
k = 0;
|
|
dir = 1;
|
|
end = len;
|
|
if (mode == ArrayFindLast || mode == ArrayFindLastIndex) {
|
|
k = len - 1;
|
|
dir = -1;
|
|
end = -1;
|
|
}
|
|
|
|
for(; k != end; k += dir) {
|
|
index_val = js_int32(k);
|
|
val = JS_GetPropertyValue(ctx, this_val, index_val);
|
|
if (JS_IsException(val))
|
|
goto exception;
|
|
args[0] = val;
|
|
args[1] = index_val;
|
|
args[2] = this_val;
|
|
res = JS_Call(ctx, func, this_arg, 3, args);
|
|
if (JS_IsException(res))
|
|
goto exception;
|
|
if (JS_ToBoolFree(ctx, res)) {
|
|
if (mode == ArrayFindIndex || mode == ArrayFindLastIndex) {
|
|
JS_FreeValue(ctx, val);
|
|
return index_val;
|
|
} else {
|
|
return val;
|
|
}
|
|
}
|
|
JS_FreeValue(ctx, val);
|
|
}
|
|
if (mode == ArrayFindIndex || mode == ArrayFindLastIndex)
|
|
return js_int32(-1);
|
|
else
|
|
return JS_UNDEFINED;
|
|
|
|
exception:
|
|
JS_FreeValue(ctx, val);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
#define special_indexOf 0
|
|
#define special_lastIndexOf 1
|
|
#define special_includes -1
|
|
|
|
static JSValue js_typed_array_indexOf(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv, int special)
|
|
{
|
|
JSObject *p;
|
|
int len, tag, is_int, is_bigint, k, stop, inc, res = -1;
|
|
int64_t v64;
|
|
double d;
|
|
float f;
|
|
|
|
len = js_typed_array_get_length_internal(ctx, this_val);
|
|
if (len < 0)
|
|
goto exception;
|
|
if (len == 0)
|
|
goto done;
|
|
|
|
if (special == special_lastIndexOf) {
|
|
k = len - 1;
|
|
if (argc > 1) {
|
|
if (JS_ToFloat64(ctx, &d, argv[1]))
|
|
goto exception;
|
|
if (isnan(d)) {
|
|
k = 0;
|
|
} else {
|
|
if (d >= 0) {
|
|
if (d < k) {
|
|
k = d;
|
|
}
|
|
} else {
|
|
d += len;
|
|
if (d < 0)
|
|
goto done;
|
|
k = d;
|
|
}
|
|
}
|
|
}
|
|
stop = -1;
|
|
inc = -1;
|
|
} else {
|
|
k = 0;
|
|
if (argc > 1) {
|
|
if (JS_ToInt32Clamp(ctx, &k, argv[1], 0, len, len))
|
|
goto exception;
|
|
}
|
|
stop = len;
|
|
inc = 1;
|
|
}
|
|
|
|
p = JS_VALUE_GET_OBJ(this_val);
|
|
/* if the array was detached, no need to go further (but no
|
|
exception is raised) */
|
|
if (typed_array_is_detached(ctx, p)) {
|
|
/* "includes" scans all the properties, so "undefined" can match */
|
|
if (special == special_includes && JS_IsUndefined(argv[0]) && len > 0)
|
|
res = 0;
|
|
goto done;
|
|
}
|
|
|
|
is_bigint = 0;
|
|
is_int = 0; /* avoid warning */
|
|
v64 = 0; /* avoid warning */
|
|
tag = JS_VALUE_GET_NORM_TAG(argv[0]);
|
|
if (tag == JS_TAG_INT) {
|
|
is_int = 1;
|
|
v64 = JS_VALUE_GET_INT(argv[0]);
|
|
d = v64;
|
|
} else
|
|
if (tag == JS_TAG_FLOAT64) {
|
|
d = JS_VALUE_GET_FLOAT64(argv[0]);
|
|
if (d >= INT64_MIN && d < 0x1p63) {
|
|
v64 = d;
|
|
is_int = (v64 == d);
|
|
}
|
|
} else
|
|
if (tag == JS_TAG_BIG_INT) {
|
|
JSBigInt *p1 = JS_VALUE_GET_PTR(argv[0]);
|
|
|
|
if (p->class_id == JS_CLASS_BIG_INT64_ARRAY) {
|
|
if (bf_get_int64(&v64, &p1->num, 0) != 0)
|
|
goto done;
|
|
} else if (p->class_id == JS_CLASS_BIG_UINT64_ARRAY) {
|
|
if (bf_get_uint64((uint64_t *)&v64, &p1->num) != 0)
|
|
goto done;
|
|
} else {
|
|
goto done;
|
|
}
|
|
d = 0;
|
|
is_bigint = 1;
|
|
} else {
|
|
goto done;
|
|
}
|
|
|
|
switch (p->class_id) {
|
|
case JS_CLASS_INT8_ARRAY:
|
|
if (is_int && (int8_t)v64 == v64)
|
|
goto scan8;
|
|
break;
|
|
case JS_CLASS_UINT8C_ARRAY:
|
|
case JS_CLASS_UINT8_ARRAY:
|
|
if (is_int && (uint8_t)v64 == v64) {
|
|
const uint8_t *pv, *pp;
|
|
uint16_t v;
|
|
scan8:
|
|
pv = p->u.array.u.uint8_ptr;
|
|
v = v64;
|
|
if (inc > 0) {
|
|
pp = memchr(pv + k, v, len - k);
|
|
if (pp)
|
|
res = pp - pv;
|
|
} else {
|
|
for (; k != stop; k += inc) {
|
|
if (pv[k] == v) {
|
|
res = k;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case JS_CLASS_INT16_ARRAY:
|
|
if (is_int && (int16_t)v64 == v64)
|
|
goto scan16;
|
|
break;
|
|
case JS_CLASS_UINT16_ARRAY:
|
|
if (is_int && (uint16_t)v64 == v64) {
|
|
const uint16_t *pv;
|
|
uint16_t v;
|
|
scan16:
|
|
pv = p->u.array.u.uint16_ptr;
|
|
v = v64;
|
|
for (; k != stop; k += inc) {
|
|
if (pv[k] == v) {
|
|
res = k;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case JS_CLASS_INT32_ARRAY:
|
|
if (is_int && (int32_t)v64 == v64)
|
|
goto scan32;
|
|
break;
|
|
case JS_CLASS_UINT32_ARRAY:
|
|
if (is_int && (uint32_t)v64 == v64) {
|
|
const uint32_t *pv;
|
|
uint32_t v;
|
|
scan32:
|
|
pv = p->u.array.u.uint32_ptr;
|
|
v = v64;
|
|
for (; k != stop; k += inc) {
|
|
if (pv[k] == v) {
|
|
res = k;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case JS_CLASS_FLOAT32_ARRAY:
|
|
if (is_bigint)
|
|
break;
|
|
if (isnan(d)) {
|
|
const float *pv = p->u.array.u.float_ptr;
|
|
/* special case: indexOf returns -1, includes finds NaN */
|
|
if (special != special_includes)
|
|
goto done;
|
|
for (; k != stop; k += inc) {
|
|
if (isnan(pv[k])) {
|
|
res = k;
|
|
break;
|
|
}
|
|
}
|
|
} else if ((f = (float)d) == d) {
|
|
const float *pv = p->u.array.u.float_ptr;
|
|
for (; k != stop; k += inc) {
|
|
if (pv[k] == f) {
|
|
res = k;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case JS_CLASS_FLOAT64_ARRAY:
|
|
if (is_bigint)
|
|
break;
|
|
if (isnan(d)) {
|
|
const double *pv = p->u.array.u.double_ptr;
|
|
/* special case: indexOf returns -1, includes finds NaN */
|
|
if (special != special_includes)
|
|
goto done;
|
|
for (; k != stop; k += inc) {
|
|
if (isnan(pv[k])) {
|
|
res = k;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
const double *pv = p->u.array.u.double_ptr;
|
|
for (; k != stop; k += inc) {
|
|
if (pv[k] == d) {
|
|
res = k;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case JS_CLASS_BIG_INT64_ARRAY:
|
|
if (is_bigint) {
|
|
goto scan64;
|
|
}
|
|
break;
|
|
case JS_CLASS_BIG_UINT64_ARRAY:
|
|
if (is_bigint) {
|
|
const uint64_t *pv;
|
|
uint64_t v;
|
|
scan64:
|
|
pv = p->u.array.u.uint64_ptr;
|
|
v = v64;
|
|
for (; k != stop; k += inc) {
|
|
if (pv[k] == v) {
|
|
res = k;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
done:
|
|
if (special == special_includes)
|
|
return js_bool(res >= 0);
|
|
else
|
|
return js_int32(res);
|
|
|
|
exception:
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue js_typed_array_join(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv, int toLocaleString)
|
|
{
|
|
JSValue sep = JS_UNDEFINED, el;
|
|
StringBuffer b_s, *b = &b_s;
|
|
JSString *p = NULL;
|
|
int i, n;
|
|
int c;
|
|
|
|
n = js_typed_array_get_length_internal(ctx, this_val);
|
|
if (n < 0)
|
|
goto exception;
|
|
|
|
c = ','; /* default separator */
|
|
if (!toLocaleString && argc > 0 && !JS_IsUndefined(argv[0])) {
|
|
sep = JS_ToString(ctx, argv[0]);
|
|
if (JS_IsException(sep))
|
|
goto exception;
|
|
p = JS_VALUE_GET_STRING(sep);
|
|
if (p->len == 1 && !p->is_wide_char)
|
|
c = p->u.str8[0];
|
|
else
|
|
c = -1;
|
|
}
|
|
string_buffer_init(ctx, b, 0);
|
|
|
|
/* XXX: optimize with direct access */
|
|
for(i = 0; i < n; i++) {
|
|
if (i > 0) {
|
|
if (c >= 0) {
|
|
if (string_buffer_putc8(b, c))
|
|
goto fail;
|
|
} else {
|
|
if (string_buffer_concat(b, p, 0, p->len))
|
|
goto fail;
|
|
}
|
|
}
|
|
el = JS_GetPropertyUint32(ctx, this_val, i);
|
|
/* Can return undefined for example if the typed array is detached */
|
|
if (!JS_IsNull(el) && !JS_IsUndefined(el)) {
|
|
if (JS_IsException(el))
|
|
goto fail;
|
|
if (toLocaleString) {
|
|
el = JS_ToLocaleStringFree(ctx, el);
|
|
}
|
|
if (string_buffer_concat_value_free(b, el))
|
|
goto fail;
|
|
}
|
|
}
|
|
JS_FreeValue(ctx, sep);
|
|
return string_buffer_end(b);
|
|
|
|
fail:
|
|
string_buffer_free(b);
|
|
JS_FreeValue(ctx, sep);
|
|
exception:
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue js_typed_array_reverse(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSObject *p;
|
|
int len;
|
|
|
|
len = js_typed_array_get_length_internal(ctx, this_val);
|
|
if (len < 0)
|
|
return JS_EXCEPTION;
|
|
if (len > 0) {
|
|
p = JS_VALUE_GET_OBJ(this_val);
|
|
switch (typed_array_size_log2(p->class_id)) {
|
|
case 0:
|
|
{
|
|
uint8_t *p1 = p->u.array.u.uint8_ptr;
|
|
uint8_t *p2 = p1 + len - 1;
|
|
while (p1 < p2) {
|
|
uint8_t v = *p1;
|
|
*p1++ = *p2;
|
|
*p2-- = v;
|
|
}
|
|
}
|
|
break;
|
|
case 1:
|
|
{
|
|
uint16_t *p1 = p->u.array.u.uint16_ptr;
|
|
uint16_t *p2 = p1 + len - 1;
|
|
while (p1 < p2) {
|
|
uint16_t v = *p1;
|
|
*p1++ = *p2;
|
|
*p2-- = v;
|
|
}
|
|
}
|
|
break;
|
|
case 2:
|
|
{
|
|
uint32_t *p1 = p->u.array.u.uint32_ptr;
|
|
uint32_t *p2 = p1 + len - 1;
|
|
while (p1 < p2) {
|
|
uint32_t v = *p1;
|
|
*p1++ = *p2;
|
|
*p2-- = v;
|
|
}
|
|
}
|
|
break;
|
|
case 3:
|
|
{
|
|
uint64_t *p1 = p->u.array.u.uint64_ptr;
|
|
uint64_t *p2 = p1 + len - 1;
|
|
while (p1 < p2) {
|
|
uint64_t v = *p1;
|
|
*p1++ = *p2;
|
|
*p2-- = v;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
}
|
|
return js_dup(this_val);
|
|
}
|
|
|
|
static JSValue js_typed_array_toReversed(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue arr, ret;
|
|
JSObject *p;
|
|
|
|
p = get_typed_array(ctx, this_val, /*is_dataview*/0);
|
|
if (!p)
|
|
return JS_EXCEPTION;
|
|
arr = js_typed_array_constructor_ta(ctx, JS_UNDEFINED, this_val,
|
|
p->class_id);
|
|
if (JS_IsException(arr))
|
|
return JS_EXCEPTION;
|
|
ret = js_typed_array_reverse(ctx, arr, argc, argv);
|
|
JS_FreeValue(ctx, arr);
|
|
return ret;
|
|
}
|
|
|
|
static JSValue js_typed_array_slice(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue args[2];
|
|
JSValue arr, val;
|
|
JSObject *p, *p1;
|
|
int n, len, start, final, count, shift;
|
|
|
|
arr = JS_UNDEFINED;
|
|
len = js_typed_array_get_length_internal(ctx, this_val);
|
|
if (len < 0)
|
|
goto exception;
|
|
|
|
if (JS_ToInt32Clamp(ctx, &start, argv[0], 0, len, len))
|
|
goto exception;
|
|
final = len;
|
|
if (!JS_IsUndefined(argv[1])) {
|
|
if (JS_ToInt32Clamp(ctx, &final, argv[1], 0, len, len))
|
|
goto exception;
|
|
}
|
|
count = max_int(final - start, 0);
|
|
|
|
p = get_typed_array(ctx, this_val, 0);
|
|
if (p == NULL)
|
|
goto exception;
|
|
shift = typed_array_size_log2(p->class_id);
|
|
|
|
args[0] = this_val;
|
|
args[1] = js_int32(count);
|
|
arr = js_typed_array___speciesCreate(ctx, JS_UNDEFINED, 2, args);
|
|
if (JS_IsException(arr))
|
|
goto exception;
|
|
|
|
if (count > 0) {
|
|
if (validate_typed_array(ctx, this_val)
|
|
|| validate_typed_array(ctx, arr))
|
|
goto exception;
|
|
|
|
p1 = get_typed_array(ctx, arr, 0);
|
|
if (p1 != NULL && p->class_id == p1->class_id &&
|
|
typed_array_get_length(ctx, p1) >= count &&
|
|
typed_array_get_length(ctx, p) >= start + count) {
|
|
memmove(p1->u.array.u.uint8_ptr,
|
|
p->u.array.u.uint8_ptr + (start << shift),
|
|
count << shift);
|
|
} else {
|
|
for (n = 0; n < count; n++) {
|
|
val = JS_GetPropertyValue(ctx, this_val, js_int32(start + n));
|
|
if (JS_IsException(val))
|
|
goto exception;
|
|
if (JS_SetPropertyValue(ctx, arr, js_int32(n), val,
|
|
JS_PROP_THROW) < 0)
|
|
goto exception;
|
|
}
|
|
}
|
|
}
|
|
return arr;
|
|
|
|
exception:
|
|
JS_FreeValue(ctx, arr);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue js_typed_array_subarray(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue args[4];
|
|
JSValue arr, byteOffset, ta_buffer;
|
|
JSObject *p;
|
|
int len, start, final, count, shift, offset;
|
|
|
|
p = get_typed_array(ctx, this_val, 0);
|
|
if (!p)
|
|
goto exception;
|
|
len = p->u.array.count;
|
|
if (JS_ToInt32Clamp(ctx, &start, argv[0], 0, len, len))
|
|
goto exception;
|
|
|
|
final = len;
|
|
if (!JS_IsUndefined(argv[1])) {
|
|
if (JS_ToInt32Clamp(ctx, &final, argv[1], 0, len, len))
|
|
goto exception;
|
|
}
|
|
count = max_int(final - start, 0);
|
|
byteOffset = js_typed_array_get_byteOffset(ctx, this_val, 0);
|
|
if (JS_IsException(byteOffset))
|
|
goto exception;
|
|
shift = typed_array_size_log2(p->class_id);
|
|
offset = JS_VALUE_GET_INT(byteOffset) + (start << shift);
|
|
JS_FreeValue(ctx, byteOffset);
|
|
ta_buffer = js_typed_array_get_buffer(ctx, this_val, 0);
|
|
if (JS_IsException(ta_buffer))
|
|
goto exception;
|
|
args[0] = this_val;
|
|
args[1] = ta_buffer;
|
|
args[2] = js_int32(offset);
|
|
args[3] = js_int32(count);
|
|
arr = js_typed_array___speciesCreate(ctx, JS_UNDEFINED, 4, args);
|
|
JS_FreeValue(ctx, ta_buffer);
|
|
return arr;
|
|
|
|
exception:
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
/* TypedArray.prototype.sort */
|
|
|
|
static int js_cmp_doubles(double x, double y)
|
|
{
|
|
if (isnan(x)) return isnan(y) ? 0 : +1;
|
|
if (isnan(y)) return -1;
|
|
if (x < y) return -1;
|
|
if (x > y) return 1;
|
|
if (x != 0) return 0;
|
|
if (signbit(x)) return signbit(y) ? 0 : -1;
|
|
else return signbit(y) ? 1 : 0;
|
|
}
|
|
|
|
static int js_TA_cmp_int8(const void *a, const void *b, void *opaque) {
|
|
return *(const int8_t *)a - *(const int8_t *)b;
|
|
}
|
|
|
|
static int js_TA_cmp_uint8(const void *a, const void *b, void *opaque) {
|
|
return *(const uint8_t *)a - *(const uint8_t *)b;
|
|
}
|
|
|
|
static int js_TA_cmp_int16(const void *a, const void *b, void *opaque) {
|
|
return *(const int16_t *)a - *(const int16_t *)b;
|
|
}
|
|
|
|
static int js_TA_cmp_uint16(const void *a, const void *b, void *opaque) {
|
|
return *(const uint16_t *)a - *(const uint16_t *)b;
|
|
}
|
|
|
|
static int js_TA_cmp_int32(const void *a, const void *b, void *opaque) {
|
|
int32_t x = *(const int32_t *)a;
|
|
int32_t y = *(const int32_t *)b;
|
|
return (y < x) - (y > x);
|
|
}
|
|
|
|
static int js_TA_cmp_uint32(const void *a, const void *b, void *opaque) {
|
|
uint32_t x = *(const uint32_t *)a;
|
|
uint32_t y = *(const uint32_t *)b;
|
|
return (y < x) - (y > x);
|
|
}
|
|
|
|
static int js_TA_cmp_int64(const void *a, const void *b, void *opaque) {
|
|
int64_t x = *(const int64_t *)a;
|
|
int64_t y = *(const int64_t *)b;
|
|
return (y < x) - (y > x);
|
|
}
|
|
|
|
static int js_TA_cmp_uint64(const void *a, const void *b, void *opaque) {
|
|
uint64_t x = *(const uint64_t *)a;
|
|
uint64_t y = *(const uint64_t *)b;
|
|
return (y < x) - (y > x);
|
|
}
|
|
|
|
static int js_TA_cmp_float32(const void *a, const void *b, void *opaque) {
|
|
return js_cmp_doubles(*(const float *)a, *(const float *)b);
|
|
}
|
|
|
|
static int js_TA_cmp_float64(const void *a, const void *b, void *opaque) {
|
|
return js_cmp_doubles(*(const double *)a, *(const double *)b);
|
|
}
|
|
|
|
static JSValue js_TA_get_int8(JSContext *ctx, const void *a) {
|
|
return js_int32(*(const int8_t *)a);
|
|
}
|
|
|
|
static JSValue js_TA_get_uint8(JSContext *ctx, const void *a) {
|
|
return js_int32(*(const uint8_t *)a);
|
|
}
|
|
|
|
static JSValue js_TA_get_int16(JSContext *ctx, const void *a) {
|
|
return js_int32(*(const int16_t *)a);
|
|
}
|
|
|
|
static JSValue js_TA_get_uint16(JSContext *ctx, const void *a) {
|
|
return js_int32(*(const uint16_t *)a);
|
|
}
|
|
|
|
static JSValue js_TA_get_int32(JSContext *ctx, const void *a) {
|
|
return js_int32(*(const int32_t *)a);
|
|
}
|
|
|
|
static JSValue js_TA_get_uint32(JSContext *ctx, const void *a) {
|
|
return js_uint32(*(const uint32_t *)a);
|
|
}
|
|
|
|
static JSValue js_TA_get_int64(JSContext *ctx, const void *a) {
|
|
return JS_NewBigInt64(ctx, *(int64_t *)a);
|
|
}
|
|
|
|
static JSValue js_TA_get_uint64(JSContext *ctx, const void *a) {
|
|
return JS_NewBigUint64(ctx, *(uint64_t *)a);
|
|
}
|
|
|
|
static JSValue js_TA_get_float32(JSContext *ctx, const void *a) {
|
|
return js_float64(*(const float *)a);
|
|
}
|
|
|
|
static JSValue js_TA_get_float64(JSContext *ctx, const void *a) {
|
|
return js_float64(*(const double *)a);
|
|
}
|
|
|
|
struct TA_sort_context {
|
|
JSContext *ctx;
|
|
int exception;
|
|
JSValue arr;
|
|
JSValue cmp;
|
|
JSValue (*getfun)(JSContext *ctx, const void *a);
|
|
uint8_t *array_ptr; /* cannot change unless the array is detached */
|
|
int elt_size;
|
|
};
|
|
|
|
static int js_TA_cmp_generic(const void *a, const void *b, void *opaque) {
|
|
struct TA_sort_context *psc = opaque;
|
|
JSContext *ctx = psc->ctx;
|
|
uint32_t a_idx, b_idx;
|
|
JSValue argv[2];
|
|
JSValue res;
|
|
JSObject *p;
|
|
int cmp;
|
|
|
|
p = JS_VALUE_GET_OBJ(psc->arr);
|
|
if (typed_array_is_detached(ctx, p))
|
|
return 0;
|
|
|
|
cmp = 0;
|
|
if (!psc->exception) {
|
|
a_idx = *(uint32_t *)a;
|
|
b_idx = *(uint32_t *)b;
|
|
argv[0] = psc->getfun(ctx, psc->array_ptr +
|
|
a_idx * (size_t)psc->elt_size);
|
|
argv[1] = psc->getfun(ctx, psc->array_ptr +
|
|
b_idx * (size_t)(psc->elt_size));
|
|
res = JS_Call(ctx, psc->cmp, JS_UNDEFINED, 2, argv);
|
|
if (JS_IsException(res)) {
|
|
psc->exception = 1;
|
|
goto done;
|
|
}
|
|
if (JS_VALUE_GET_TAG(res) == JS_TAG_INT) {
|
|
int val = JS_VALUE_GET_INT(res);
|
|
cmp = (val > 0) - (val < 0);
|
|
} else {
|
|
double val;
|
|
if (JS_ToFloat64Free(ctx, &val, res) < 0) {
|
|
psc->exception = 1;
|
|
goto done;
|
|
} else {
|
|
cmp = (val > 0) - (val < 0);
|
|
}
|
|
}
|
|
if (cmp == 0) {
|
|
/* make sort stable: compare array offsets */
|
|
cmp = (a_idx > b_idx) - (a_idx < b_idx);
|
|
}
|
|
done:
|
|
JS_FreeValue(ctx, argv[0]);
|
|
JS_FreeValue(ctx, argv[1]);
|
|
}
|
|
return cmp;
|
|
}
|
|
|
|
static JSValue js_typed_array_sort(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSObject *p;
|
|
int len;
|
|
size_t elt_size;
|
|
struct TA_sort_context tsc;
|
|
void *array_ptr;
|
|
int (*cmpfun)(const void *a, const void *b, void *opaque);
|
|
|
|
tsc.ctx = ctx;
|
|
tsc.exception = 0;
|
|
tsc.arr = this_val;
|
|
tsc.cmp = argv[0];
|
|
|
|
len = js_typed_array_get_length_internal(ctx, this_val);
|
|
if (len < 0)
|
|
return JS_EXCEPTION;
|
|
if (!JS_IsUndefined(tsc.cmp) && check_function(ctx, tsc.cmp))
|
|
return JS_EXCEPTION;
|
|
|
|
if (len > 1) {
|
|
p = JS_VALUE_GET_OBJ(this_val);
|
|
switch (p->class_id) {
|
|
case JS_CLASS_INT8_ARRAY:
|
|
tsc.getfun = js_TA_get_int8;
|
|
cmpfun = js_TA_cmp_int8;
|
|
break;
|
|
case JS_CLASS_UINT8C_ARRAY:
|
|
case JS_CLASS_UINT8_ARRAY:
|
|
tsc.getfun = js_TA_get_uint8;
|
|
cmpfun = js_TA_cmp_uint8;
|
|
break;
|
|
case JS_CLASS_INT16_ARRAY:
|
|
tsc.getfun = js_TA_get_int16;
|
|
cmpfun = js_TA_cmp_int16;
|
|
break;
|
|
case JS_CLASS_UINT16_ARRAY:
|
|
tsc.getfun = js_TA_get_uint16;
|
|
cmpfun = js_TA_cmp_uint16;
|
|
break;
|
|
case JS_CLASS_INT32_ARRAY:
|
|
tsc.getfun = js_TA_get_int32;
|
|
cmpfun = js_TA_cmp_int32;
|
|
break;
|
|
case JS_CLASS_UINT32_ARRAY:
|
|
tsc.getfun = js_TA_get_uint32;
|
|
cmpfun = js_TA_cmp_uint32;
|
|
break;
|
|
case JS_CLASS_BIG_INT64_ARRAY:
|
|
tsc.getfun = js_TA_get_int64;
|
|
cmpfun = js_TA_cmp_int64;
|
|
break;
|
|
case JS_CLASS_BIG_UINT64_ARRAY:
|
|
tsc.getfun = js_TA_get_uint64;
|
|
cmpfun = js_TA_cmp_uint64;
|
|
break;
|
|
case JS_CLASS_FLOAT32_ARRAY:
|
|
tsc.getfun = js_TA_get_float32;
|
|
cmpfun = js_TA_cmp_float32;
|
|
break;
|
|
case JS_CLASS_FLOAT64_ARRAY:
|
|
tsc.getfun = js_TA_get_float64;
|
|
cmpfun = js_TA_cmp_float64;
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
array_ptr = p->u.array.u.ptr;
|
|
elt_size = 1 << typed_array_size_log2(p->class_id);
|
|
if (!JS_IsUndefined(tsc.cmp)) {
|
|
uint32_t *array_idx;
|
|
void *array_tmp;
|
|
size_t i, j;
|
|
|
|
/* XXX: a stable sort would use less memory */
|
|
array_idx = js_malloc(ctx, len * sizeof(array_idx[0]));
|
|
if (!array_idx)
|
|
return JS_EXCEPTION;
|
|
for(i = 0; i < len; i++)
|
|
array_idx[i] = i;
|
|
tsc.array_ptr = array_ptr;
|
|
tsc.elt_size = elt_size;
|
|
rqsort(array_idx, len, sizeof(array_idx[0]),
|
|
js_TA_cmp_generic, &tsc);
|
|
if (tsc.exception)
|
|
goto fail;
|
|
// per spec: typed array can be detached mid-iteration
|
|
if (typed_array_is_detached(ctx, p))
|
|
goto done;
|
|
array_tmp = js_malloc(ctx, len * elt_size);
|
|
if (!array_tmp) {
|
|
fail:
|
|
js_free(ctx, array_idx);
|
|
return JS_EXCEPTION;
|
|
}
|
|
memcpy(array_tmp, array_ptr, len * elt_size);
|
|
switch(elt_size) {
|
|
case 1:
|
|
for(i = 0; i < len; i++) {
|
|
j = array_idx[i];
|
|
((uint8_t *)array_ptr)[i] = ((uint8_t *)array_tmp)[j];
|
|
}
|
|
break;
|
|
case 2:
|
|
for(i = 0; i < len; i++) {
|
|
j = array_idx[i];
|
|
((uint16_t *)array_ptr)[i] = ((uint16_t *)array_tmp)[j];
|
|
}
|
|
break;
|
|
case 4:
|
|
for(i = 0; i < len; i++) {
|
|
j = array_idx[i];
|
|
((uint32_t *)array_ptr)[i] = ((uint32_t *)array_tmp)[j];
|
|
}
|
|
break;
|
|
case 8:
|
|
for(i = 0; i < len; i++) {
|
|
j = array_idx[i];
|
|
((uint64_t *)array_ptr)[i] = ((uint64_t *)array_tmp)[j];
|
|
}
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
js_free(ctx, array_tmp);
|
|
done:
|
|
js_free(ctx, array_idx);
|
|
} else {
|
|
rqsort(array_ptr, len, elt_size, cmpfun, &tsc);
|
|
if (tsc.exception)
|
|
return JS_EXCEPTION;
|
|
}
|
|
}
|
|
return js_dup(this_val);
|
|
}
|
|
|
|
static JSValue js_typed_array_toSorted(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue arr, ret;
|
|
JSObject *p;
|
|
|
|
p = get_typed_array(ctx, this_val, /*is_dataview*/0);
|
|
if (!p)
|
|
return JS_EXCEPTION;
|
|
arr = js_typed_array_constructor_ta(ctx, JS_UNDEFINED, this_val,
|
|
p->class_id);
|
|
if (JS_IsException(arr))
|
|
return JS_EXCEPTION;
|
|
ret = js_typed_array_sort(ctx, arr, argc, argv);
|
|
JS_FreeValue(ctx, arr);
|
|
return ret;
|
|
}
|
|
|
|
static const JSCFunctionListEntry js_typed_array_base_funcs[] = {
|
|
JS_CFUNC_DEF("from", 1, js_typed_array_from ),
|
|
JS_CFUNC_DEF("of", 0, js_typed_array_of ),
|
|
JS_CGETSET_DEF("[Symbol.species]", js_get_this, NULL ),
|
|
};
|
|
|
|
static const JSCFunctionListEntry js_typed_array_base_proto_funcs[] = {
|
|
JS_CGETSET_DEF("length", js_typed_array_get_length, NULL ),
|
|
JS_CFUNC_DEF("at", 1, js_typed_array_at ),
|
|
JS_CFUNC_DEF("with", 2, js_typed_array_with ),
|
|
JS_CGETSET_MAGIC_DEF("buffer", js_typed_array_get_buffer, NULL, 0 ),
|
|
JS_CGETSET_MAGIC_DEF("byteLength", js_typed_array_get_byteLength, NULL, 0 ),
|
|
JS_CGETSET_MAGIC_DEF("byteOffset", js_typed_array_get_byteOffset, NULL, 0 ),
|
|
JS_CFUNC_DEF("set", 1, js_typed_array_set ),
|
|
JS_CFUNC_MAGIC_DEF("values", 0, js_create_typed_array_iterator, JS_ITERATOR_KIND_VALUE ),
|
|
JS_ALIAS_DEF("[Symbol.iterator]", "values" ),
|
|
JS_CFUNC_MAGIC_DEF("keys", 0, js_create_typed_array_iterator, JS_ITERATOR_KIND_KEY ),
|
|
JS_CFUNC_MAGIC_DEF("entries", 0, js_create_typed_array_iterator, JS_ITERATOR_KIND_KEY_AND_VALUE ),
|
|
JS_CGETSET_DEF("[Symbol.toStringTag]", js_typed_array_get_toStringTag, NULL ),
|
|
JS_CFUNC_DEF("copyWithin", 2, js_typed_array_copyWithin ),
|
|
JS_CFUNC_MAGIC_DEF("every", 1, js_array_every, special_every | special_TA ),
|
|
JS_CFUNC_MAGIC_DEF("some", 1, js_array_every, special_some | special_TA ),
|
|
JS_CFUNC_MAGIC_DEF("forEach", 1, js_array_every, special_forEach | special_TA ),
|
|
JS_CFUNC_MAGIC_DEF("map", 1, js_array_every, special_map | special_TA ),
|
|
JS_CFUNC_MAGIC_DEF("filter", 1, js_array_every, special_filter | special_TA ),
|
|
JS_CFUNC_MAGIC_DEF("reduce", 1, js_array_reduce, special_reduce | special_TA ),
|
|
JS_CFUNC_MAGIC_DEF("reduceRight", 1, js_array_reduce, special_reduceRight | special_TA ),
|
|
JS_CFUNC_DEF("fill", 1, js_typed_array_fill ),
|
|
JS_CFUNC_MAGIC_DEF("find", 1, js_typed_array_find, ArrayFind ),
|
|
JS_CFUNC_MAGIC_DEF("findIndex", 1, js_typed_array_find, ArrayFindIndex ),
|
|
JS_CFUNC_MAGIC_DEF("findLast", 1, js_typed_array_find, ArrayFindLast ),
|
|
JS_CFUNC_MAGIC_DEF("findLastIndex", 1, js_typed_array_find, ArrayFindLastIndex ),
|
|
JS_CFUNC_DEF("reverse", 0, js_typed_array_reverse ),
|
|
JS_CFUNC_DEF("toReversed", 0, js_typed_array_toReversed ),
|
|
JS_CFUNC_DEF("slice", 2, js_typed_array_slice ),
|
|
JS_CFUNC_DEF("subarray", 2, js_typed_array_subarray ),
|
|
JS_CFUNC_DEF("sort", 1, js_typed_array_sort ),
|
|
JS_CFUNC_DEF("toSorted", 1, js_typed_array_toSorted ),
|
|
JS_CFUNC_MAGIC_DEF("join", 1, js_typed_array_join, 0 ),
|
|
JS_CFUNC_MAGIC_DEF("toLocaleString", 0, js_typed_array_join, 1 ),
|
|
JS_CFUNC_MAGIC_DEF("indexOf", 1, js_typed_array_indexOf, special_indexOf ),
|
|
JS_CFUNC_MAGIC_DEF("lastIndexOf", 1, js_typed_array_indexOf, special_lastIndexOf ),
|
|
JS_CFUNC_MAGIC_DEF("includes", 1, js_typed_array_indexOf, special_includes ),
|
|
//JS_ALIAS_BASE_DEF("toString", "toString", 2 /* Array.prototype. */), @@@
|
|
};
|
|
|
|
static JSValue js_typed_array_base_constructor(JSContext *ctx,
|
|
JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
return JS_ThrowTypeError(ctx, "cannot be called");
|
|
}
|
|
|
|
/* 'obj' must be an allocated typed array object */
|
|
static int typed_array_init(JSContext *ctx, JSValue obj,
|
|
JSValue buffer, uint64_t offset, uint64_t len)
|
|
{
|
|
JSTypedArray *ta;
|
|
JSObject *p, *pbuffer;
|
|
JSArrayBuffer *abuf;
|
|
int size_log2;
|
|
|
|
p = JS_VALUE_GET_OBJ(obj);
|
|
size_log2 = typed_array_size_log2(p->class_id);
|
|
ta = js_malloc(ctx, sizeof(*ta));
|
|
if (!ta) {
|
|
JS_FreeValue(ctx, buffer);
|
|
return -1;
|
|
}
|
|
pbuffer = JS_VALUE_GET_OBJ(buffer);
|
|
abuf = pbuffer->u.array_buffer;
|
|
ta->obj = p;
|
|
ta->buffer = pbuffer;
|
|
ta->offset = offset;
|
|
ta->length = len << size_log2;
|
|
list_add_tail(&ta->link, &abuf->array_list);
|
|
p->u.typed_array = ta;
|
|
p->u.array.count = len;
|
|
p->u.array.u.ptr = abuf->data + offset;
|
|
return 0;
|
|
}
|
|
|
|
|
|
static JSValue js_array_from_iterator(JSContext *ctx, uint32_t *plen,
|
|
JSValue obj, JSValue method)
|
|
{
|
|
JSValue arr, iter, next_method = JS_UNDEFINED, val;
|
|
BOOL done;
|
|
uint32_t k;
|
|
|
|
*plen = 0;
|
|
arr = JS_NewArray(ctx);
|
|
if (JS_IsException(arr))
|
|
return arr;
|
|
iter = JS_GetIterator2(ctx, obj, method);
|
|
if (JS_IsException(iter))
|
|
goto fail;
|
|
next_method = JS_GetProperty(ctx, iter, JS_ATOM_next);
|
|
if (JS_IsException(next_method))
|
|
goto fail;
|
|
k = 0;
|
|
for(;;) {
|
|
val = JS_IteratorNext(ctx, iter, next_method, 0, NULL, &done);
|
|
if (JS_IsException(val))
|
|
goto fail;
|
|
if (done) {
|
|
JS_FreeValue(ctx, val);
|
|
break;
|
|
}
|
|
if (JS_CreateDataPropertyUint32(ctx, arr, k, val, JS_PROP_THROW) < 0)
|
|
goto fail;
|
|
k++;
|
|
}
|
|
JS_FreeValue(ctx, next_method);
|
|
JS_FreeValue(ctx, iter);
|
|
*plen = k;
|
|
return arr;
|
|
fail:
|
|
JS_FreeValue(ctx, next_method);
|
|
JS_FreeValue(ctx, iter);
|
|
JS_FreeValue(ctx, arr);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue js_typed_array_constructor_obj(JSContext *ctx,
|
|
JSValue new_target,
|
|
JSValue obj,
|
|
int classid)
|
|
{
|
|
JSValue iter, ret, arr = JS_UNDEFINED, val, buffer;
|
|
uint32_t i;
|
|
int size_log2;
|
|
int64_t len;
|
|
|
|
size_log2 = typed_array_size_log2(classid);
|
|
ret = js_create_from_ctor(ctx, new_target, classid);
|
|
if (JS_IsException(ret))
|
|
return JS_EXCEPTION;
|
|
|
|
iter = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_iterator);
|
|
if (JS_IsException(iter))
|
|
goto fail;
|
|
if (!JS_IsUndefined(iter) && !JS_IsNull(iter)) {
|
|
uint32_t len1;
|
|
arr = js_array_from_iterator(ctx, &len1, obj, iter);
|
|
JS_FreeValue(ctx, iter);
|
|
if (JS_IsException(arr))
|
|
goto fail;
|
|
len = len1;
|
|
} else {
|
|
if (js_get_length64(ctx, &len, obj))
|
|
goto fail;
|
|
arr = js_dup(obj);
|
|
}
|
|
|
|
buffer = js_array_buffer_constructor1(ctx, JS_UNDEFINED,
|
|
len << size_log2);
|
|
if (JS_IsException(buffer))
|
|
goto fail;
|
|
if (typed_array_init(ctx, ret, buffer, 0, len))
|
|
goto fail;
|
|
|
|
for(i = 0; i < len; i++) {
|
|
val = JS_GetPropertyUint32(ctx, arr, i);
|
|
if (JS_IsException(val))
|
|
goto fail;
|
|
if (JS_SetPropertyUint32(ctx, ret, i, val) < 0)
|
|
goto fail;
|
|
}
|
|
JS_FreeValue(ctx, arr);
|
|
return ret;
|
|
fail:
|
|
JS_FreeValue(ctx, arr);
|
|
JS_FreeValue(ctx, ret);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue js_typed_array_constructor_ta(JSContext *ctx,
|
|
JSValue new_target,
|
|
JSValue src_obj,
|
|
int classid)
|
|
{
|
|
JSObject *p, *src_buffer;
|
|
JSTypedArray *ta;
|
|
JSValue obj, buffer;
|
|
uint32_t len, i;
|
|
int size_log2;
|
|
JSArrayBuffer *src_abuf, *abuf;
|
|
|
|
obj = js_create_from_ctor(ctx, new_target, classid);
|
|
if (JS_IsException(obj))
|
|
return obj;
|
|
p = JS_VALUE_GET_OBJ(src_obj);
|
|
if (typed_array_is_detached(ctx, p)) {
|
|
JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
|
|
goto fail;
|
|
}
|
|
ta = p->u.typed_array;
|
|
len = p->u.array.count;
|
|
src_buffer = ta->buffer;
|
|
src_abuf = src_buffer->u.array_buffer;
|
|
size_log2 = typed_array_size_log2(classid);
|
|
buffer = js_array_buffer_constructor1(ctx, JS_UNDEFINED,
|
|
(uint64_t)len << size_log2);
|
|
if (JS_IsException(buffer))
|
|
goto fail;
|
|
/* necessary because it could have been detached */
|
|
if (typed_array_is_detached(ctx, p)) {
|
|
JS_FreeValue(ctx, buffer);
|
|
JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
|
|
goto fail;
|
|
}
|
|
abuf = JS_GetOpaque(buffer, JS_CLASS_ARRAY_BUFFER);
|
|
if (typed_array_init(ctx, obj, buffer, 0, len))
|
|
goto fail;
|
|
if (p->class_id == classid) {
|
|
/* same type: copy the content */
|
|
memcpy(abuf->data, src_abuf->data + ta->offset, abuf->byte_length);
|
|
} else {
|
|
for(i = 0; i < len; i++) {
|
|
JSValue val;
|
|
val = JS_GetPropertyUint32(ctx, src_obj, i);
|
|
if (JS_IsException(val))
|
|
goto fail;
|
|
if (JS_SetPropertyUint32(ctx, obj, i, val) < 0)
|
|
goto fail;
|
|
}
|
|
}
|
|
return obj;
|
|
fail:
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue js_typed_array_constructor(JSContext *ctx,
|
|
JSValue new_target,
|
|
int argc, JSValue *argv,
|
|
int classid)
|
|
{
|
|
JSValue buffer, obj;
|
|
JSArrayBuffer *abuf;
|
|
int size_log2;
|
|
uint64_t len, offset;
|
|
|
|
size_log2 = typed_array_size_log2(classid);
|
|
if (JS_VALUE_GET_TAG(argv[0]) != JS_TAG_OBJECT) {
|
|
if (JS_ToIndex(ctx, &len, argv[0]))
|
|
return JS_EXCEPTION;
|
|
buffer = js_array_buffer_constructor1(ctx, JS_UNDEFINED,
|
|
len << size_log2);
|
|
if (JS_IsException(buffer))
|
|
return JS_EXCEPTION;
|
|
offset = 0;
|
|
} else {
|
|
JSObject *p = JS_VALUE_GET_OBJ(argv[0]);
|
|
if (p->class_id == JS_CLASS_ARRAY_BUFFER ||
|
|
p->class_id == JS_CLASS_SHARED_ARRAY_BUFFER) {
|
|
abuf = p->u.array_buffer;
|
|
if (JS_ToIndex(ctx, &offset, argv[1]))
|
|
return JS_EXCEPTION;
|
|
if (abuf->detached)
|
|
return JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
|
|
if ((offset & ((1 << size_log2) - 1)) != 0 ||
|
|
offset > abuf->byte_length)
|
|
return JS_ThrowRangeError(ctx, "invalid offset");
|
|
if (JS_IsUndefined(argv[2])) {
|
|
if ((abuf->byte_length & ((1 << size_log2) - 1)) != 0)
|
|
goto invalid_length;
|
|
len = (abuf->byte_length - offset) >> size_log2;
|
|
} else {
|
|
if (JS_ToIndex(ctx, &len, argv[2]))
|
|
return JS_EXCEPTION;
|
|
if (abuf->detached)
|
|
return JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
|
|
if ((offset + (len << size_log2)) > abuf->byte_length) {
|
|
invalid_length:
|
|
return JS_ThrowRangeError(ctx, "invalid length");
|
|
}
|
|
}
|
|
buffer = js_dup(argv[0]);
|
|
} else {
|
|
if (p->class_id >= JS_CLASS_UINT8C_ARRAY &&
|
|
p->class_id <= JS_CLASS_FLOAT64_ARRAY) {
|
|
return js_typed_array_constructor_ta(ctx, new_target, argv[0], classid);
|
|
} else {
|
|
return js_typed_array_constructor_obj(ctx, new_target, argv[0], classid);
|
|
}
|
|
}
|
|
}
|
|
|
|
obj = js_create_from_ctor(ctx, new_target, classid);
|
|
if (JS_IsException(obj)) {
|
|
JS_FreeValue(ctx, buffer);
|
|
return JS_EXCEPTION;
|
|
}
|
|
if (typed_array_init(ctx, obj, buffer, offset, len)) {
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
return obj;
|
|
}
|
|
|
|
static void js_typed_array_finalizer(JSRuntime *rt, JSValue val)
|
|
{
|
|
JSObject *p = JS_VALUE_GET_OBJ(val);
|
|
JSTypedArray *ta = p->u.typed_array;
|
|
if (ta) {
|
|
/* during the GC the finalizers are called in an arbitrary
|
|
order so the ArrayBuffer finalizer may have been called */
|
|
if (ta->link.next) {
|
|
list_del(&ta->link);
|
|
}
|
|
JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_OBJECT, ta->buffer));
|
|
js_free_rt(rt, ta);
|
|
}
|
|
}
|
|
|
|
static void js_typed_array_mark(JSRuntime *rt, JSValue val,
|
|
JS_MarkFunc *mark_func)
|
|
{
|
|
JSObject *p = JS_VALUE_GET_OBJ(val);
|
|
JSTypedArray *ta = p->u.typed_array;
|
|
if (ta) {
|
|
JS_MarkValue(rt, JS_MKPTR(JS_TAG_OBJECT, ta->buffer), mark_func);
|
|
}
|
|
}
|
|
|
|
static JSValue js_dataview_constructor(JSContext *ctx,
|
|
JSValue new_target,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSArrayBuffer *abuf;
|
|
uint64_t offset;
|
|
uint32_t len;
|
|
JSValue buffer;
|
|
JSValue obj;
|
|
JSTypedArray *ta;
|
|
JSObject *p;
|
|
|
|
buffer = argv[0];
|
|
abuf = js_get_array_buffer(ctx, buffer);
|
|
if (!abuf)
|
|
return JS_EXCEPTION;
|
|
offset = 0;
|
|
if (argc > 1) {
|
|
if (JS_ToIndex(ctx, &offset, argv[1]))
|
|
return JS_EXCEPTION;
|
|
}
|
|
if (abuf->detached)
|
|
return JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
|
|
if (offset > abuf->byte_length)
|
|
return JS_ThrowRangeError(ctx, "invalid byteOffset");
|
|
len = abuf->byte_length - offset;
|
|
if (argc > 2 && !JS_IsUndefined(argv[2])) {
|
|
uint64_t l;
|
|
if (JS_ToIndex(ctx, &l, argv[2]))
|
|
return JS_EXCEPTION;
|
|
if (l > len)
|
|
return JS_ThrowRangeError(ctx, "invalid byteLength");
|
|
len = l;
|
|
}
|
|
|
|
obj = js_create_from_ctor(ctx, new_target, JS_CLASS_DATAVIEW);
|
|
if (JS_IsException(obj))
|
|
return JS_EXCEPTION;
|
|
if (abuf->detached) {
|
|
/* could have been detached in js_create_from_ctor() */
|
|
JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
|
|
goto fail;
|
|
}
|
|
ta = js_malloc(ctx, sizeof(*ta));
|
|
if (!ta) {
|
|
fail:
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
p = JS_VALUE_GET_OBJ(obj);
|
|
ta->obj = p;
|
|
ta->buffer = JS_VALUE_GET_OBJ(js_dup(buffer));
|
|
ta->offset = offset;
|
|
ta->length = len;
|
|
list_add_tail(&ta->link, &abuf->array_list);
|
|
p->u.typed_array = ta;
|
|
return obj;
|
|
}
|
|
|
|
static JSValue js_dataview_getValue(JSContext *ctx,
|
|
JSValue this_obj,
|
|
int argc, JSValue *argv, int class_id)
|
|
{
|
|
JSTypedArray *ta;
|
|
JSArrayBuffer *abuf;
|
|
BOOL littleEndian, is_swap;
|
|
int size;
|
|
uint8_t *ptr;
|
|
uint32_t v;
|
|
uint64_t pos;
|
|
|
|
ta = JS_GetOpaque2(ctx, this_obj, JS_CLASS_DATAVIEW);
|
|
if (!ta)
|
|
return JS_EXCEPTION;
|
|
size = 1 << typed_array_size_log2(class_id);
|
|
if (JS_ToIndex(ctx, &pos, argv[0]))
|
|
return JS_EXCEPTION;
|
|
littleEndian = argc > 1 && JS_ToBool(ctx, argv[1]);
|
|
is_swap = littleEndian ^ !is_be();
|
|
abuf = ta->buffer->u.array_buffer;
|
|
if (abuf->detached)
|
|
return JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
|
|
if ((pos + size) > ta->length)
|
|
return JS_ThrowRangeError(ctx, "out of bound");
|
|
ptr = abuf->data + ta->offset + pos;
|
|
|
|
switch(class_id) {
|
|
case JS_CLASS_INT8_ARRAY:
|
|
return js_int32(*(int8_t *)ptr);
|
|
case JS_CLASS_UINT8_ARRAY:
|
|
return js_int32(*(uint8_t *)ptr);
|
|
case JS_CLASS_INT16_ARRAY:
|
|
v = get_u16(ptr);
|
|
if (is_swap)
|
|
v = bswap16(v);
|
|
return js_int32((int16_t)v);
|
|
case JS_CLASS_UINT16_ARRAY:
|
|
v = get_u16(ptr);
|
|
if (is_swap)
|
|
v = bswap16(v);
|
|
return js_int32(v);
|
|
case JS_CLASS_INT32_ARRAY:
|
|
v = get_u32(ptr);
|
|
if (is_swap)
|
|
v = bswap32(v);
|
|
return js_int32(v);
|
|
case JS_CLASS_UINT32_ARRAY:
|
|
v = get_u32(ptr);
|
|
if (is_swap)
|
|
v = bswap32(v);
|
|
return js_uint32(v);
|
|
case JS_CLASS_BIG_INT64_ARRAY:
|
|
{
|
|
uint64_t v;
|
|
v = get_u64(ptr);
|
|
if (is_swap)
|
|
v = bswap64(v);
|
|
return JS_NewBigInt64(ctx, v);
|
|
}
|
|
break;
|
|
case JS_CLASS_BIG_UINT64_ARRAY:
|
|
{
|
|
uint64_t v;
|
|
v = get_u64(ptr);
|
|
if (is_swap)
|
|
v = bswap64(v);
|
|
return JS_NewBigUint64(ctx, v);
|
|
}
|
|
break;
|
|
case JS_CLASS_FLOAT32_ARRAY:
|
|
{
|
|
union {
|
|
float f;
|
|
uint32_t i;
|
|
} u;
|
|
v = get_u32(ptr);
|
|
if (is_swap)
|
|
v = bswap32(v);
|
|
u.i = v;
|
|
return js_float64(u.f);
|
|
}
|
|
case JS_CLASS_FLOAT64_ARRAY:
|
|
{
|
|
union {
|
|
double f;
|
|
uint64_t i;
|
|
} u;
|
|
u.i = get_u64(ptr);
|
|
if (is_swap)
|
|
u.i = bswap64(u.i);
|
|
return js_float64(u.f);
|
|
}
|
|
default:
|
|
abort();
|
|
}
|
|
return JS_EXCEPTION; // pacify compiler
|
|
}
|
|
|
|
static JSValue js_dataview_setValue(JSContext *ctx,
|
|
JSValue this_obj,
|
|
int argc, JSValue *argv, int class_id)
|
|
{
|
|
JSTypedArray *ta;
|
|
JSArrayBuffer *abuf;
|
|
BOOL littleEndian, is_swap;
|
|
int size;
|
|
uint8_t *ptr;
|
|
uint64_t v64;
|
|
uint32_t v;
|
|
uint64_t pos;
|
|
JSValue val;
|
|
|
|
ta = JS_GetOpaque2(ctx, this_obj, JS_CLASS_DATAVIEW);
|
|
if (!ta)
|
|
return JS_EXCEPTION;
|
|
size = 1 << typed_array_size_log2(class_id);
|
|
if (JS_ToIndex(ctx, &pos, argv[0]))
|
|
return JS_EXCEPTION;
|
|
val = argv[1];
|
|
v = 0; /* avoid warning */
|
|
v64 = 0; /* avoid warning */
|
|
if (class_id <= JS_CLASS_UINT32_ARRAY) {
|
|
if (JS_ToUint32(ctx, &v, val))
|
|
return JS_EXCEPTION;
|
|
} else
|
|
if (class_id <= JS_CLASS_BIG_UINT64_ARRAY) {
|
|
if (JS_ToBigInt64(ctx, (int64_t *)&v64, val))
|
|
return JS_EXCEPTION;
|
|
} else {
|
|
double d;
|
|
if (JS_ToFloat64(ctx, &d, val))
|
|
return JS_EXCEPTION;
|
|
if (class_id == JS_CLASS_FLOAT32_ARRAY) {
|
|
union {
|
|
float f;
|
|
uint32_t i;
|
|
} u;
|
|
u.f = d;
|
|
v = u.i;
|
|
} else {
|
|
JSFloat64Union u;
|
|
u.d = d;
|
|
v64 = u.u64;
|
|
}
|
|
}
|
|
littleEndian = argc > 2 && JS_ToBool(ctx, argv[2]);
|
|
is_swap = littleEndian ^ !is_be();
|
|
abuf = ta->buffer->u.array_buffer;
|
|
if (abuf->detached)
|
|
return JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
|
|
if ((pos + size) > ta->length)
|
|
return JS_ThrowRangeError(ctx, "out of bound");
|
|
ptr = abuf->data + ta->offset + pos;
|
|
|
|
switch(class_id) {
|
|
case JS_CLASS_INT8_ARRAY:
|
|
case JS_CLASS_UINT8_ARRAY:
|
|
*ptr = v;
|
|
break;
|
|
case JS_CLASS_INT16_ARRAY:
|
|
case JS_CLASS_UINT16_ARRAY:
|
|
if (is_swap)
|
|
v = bswap16(v);
|
|
put_u16(ptr, v);
|
|
break;
|
|
case JS_CLASS_INT32_ARRAY:
|
|
case JS_CLASS_UINT32_ARRAY:
|
|
case JS_CLASS_FLOAT32_ARRAY:
|
|
if (is_swap)
|
|
v = bswap32(v);
|
|
put_u32(ptr, v);
|
|
break;
|
|
case JS_CLASS_BIG_INT64_ARRAY:
|
|
case JS_CLASS_BIG_UINT64_ARRAY:
|
|
case JS_CLASS_FLOAT64_ARRAY:
|
|
if (is_swap)
|
|
v64 = bswap64(v64);
|
|
put_u64(ptr, v64);
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
static const JSCFunctionListEntry js_dataview_proto_funcs[] = {
|
|
JS_CGETSET_MAGIC_DEF("buffer", js_typed_array_get_buffer, NULL, 1 ),
|
|
JS_CGETSET_MAGIC_DEF("byteLength", js_typed_array_get_byteLength, NULL, 1 ),
|
|
JS_CGETSET_MAGIC_DEF("byteOffset", js_typed_array_get_byteOffset, NULL, 1 ),
|
|
JS_CFUNC_MAGIC_DEF("getInt8", 1, js_dataview_getValue, JS_CLASS_INT8_ARRAY ),
|
|
JS_CFUNC_MAGIC_DEF("getUint8", 1, js_dataview_getValue, JS_CLASS_UINT8_ARRAY ),
|
|
JS_CFUNC_MAGIC_DEF("getInt16", 1, js_dataview_getValue, JS_CLASS_INT16_ARRAY ),
|
|
JS_CFUNC_MAGIC_DEF("getUint16", 1, js_dataview_getValue, JS_CLASS_UINT16_ARRAY ),
|
|
JS_CFUNC_MAGIC_DEF("getInt32", 1, js_dataview_getValue, JS_CLASS_INT32_ARRAY ),
|
|
JS_CFUNC_MAGIC_DEF("getUint32", 1, js_dataview_getValue, JS_CLASS_UINT32_ARRAY ),
|
|
JS_CFUNC_MAGIC_DEF("getBigInt64", 1, js_dataview_getValue, JS_CLASS_BIG_INT64_ARRAY ),
|
|
JS_CFUNC_MAGIC_DEF("getBigUint64", 1, js_dataview_getValue, JS_CLASS_BIG_UINT64_ARRAY ),
|
|
JS_CFUNC_MAGIC_DEF("getFloat32", 1, js_dataview_getValue, JS_CLASS_FLOAT32_ARRAY ),
|
|
JS_CFUNC_MAGIC_DEF("getFloat64", 1, js_dataview_getValue, JS_CLASS_FLOAT64_ARRAY ),
|
|
JS_CFUNC_MAGIC_DEF("setInt8", 2, js_dataview_setValue, JS_CLASS_INT8_ARRAY ),
|
|
JS_CFUNC_MAGIC_DEF("setUint8", 2, js_dataview_setValue, JS_CLASS_UINT8_ARRAY ),
|
|
JS_CFUNC_MAGIC_DEF("setInt16", 2, js_dataview_setValue, JS_CLASS_INT16_ARRAY ),
|
|
JS_CFUNC_MAGIC_DEF("setUint16", 2, js_dataview_setValue, JS_CLASS_UINT16_ARRAY ),
|
|
JS_CFUNC_MAGIC_DEF("setInt32", 2, js_dataview_setValue, JS_CLASS_INT32_ARRAY ),
|
|
JS_CFUNC_MAGIC_DEF("setUint32", 2, js_dataview_setValue, JS_CLASS_UINT32_ARRAY ),
|
|
JS_CFUNC_MAGIC_DEF("setBigInt64", 2, js_dataview_setValue, JS_CLASS_BIG_INT64_ARRAY ),
|
|
JS_CFUNC_MAGIC_DEF("setBigUint64", 2, js_dataview_setValue, JS_CLASS_BIG_UINT64_ARRAY ),
|
|
JS_CFUNC_MAGIC_DEF("setFloat32", 2, js_dataview_setValue, JS_CLASS_FLOAT32_ARRAY ),
|
|
JS_CFUNC_MAGIC_DEF("setFloat64", 2, js_dataview_setValue, JS_CLASS_FLOAT64_ARRAY ),
|
|
JS_PROP_STRING_DEF("[Symbol.toStringTag]", "DataView", JS_PROP_CONFIGURABLE ),
|
|
};
|
|
|
|
static JSValue js_new_uint8array(JSContext *ctx, JSValue buffer)
|
|
{
|
|
if (JS_IsException(buffer))
|
|
return JS_EXCEPTION;
|
|
JSValue obj = js_create_from_ctor(ctx, JS_UNDEFINED, JS_CLASS_UINT8_ARRAY);
|
|
if (JS_IsException(obj)) {
|
|
JS_FreeValue(ctx, buffer);
|
|
return JS_EXCEPTION;
|
|
}
|
|
JSArrayBuffer *abuf = js_get_array_buffer(ctx, buffer);
|
|
assert(abuf != NULL);
|
|
if (typed_array_init(ctx, obj, buffer, 0, abuf->byte_length)) {
|
|
// 'buffer' is freed on error above.
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
return obj;
|
|
}
|
|
|
|
JSValue JS_NewUint8Array(JSContext *ctx, uint8_t *buf, size_t len,
|
|
JSFreeArrayBufferDataFunc *free_func, void *opaque,
|
|
JS_BOOL is_shared)
|
|
{
|
|
JSValue buffer = js_array_buffer_constructor3(ctx, JS_UNDEFINED, len,
|
|
is_shared ? JS_CLASS_SHARED_ARRAY_BUFFER : JS_CLASS_ARRAY_BUFFER,
|
|
buf, free_func, opaque, FALSE);
|
|
return js_new_uint8array(ctx, buffer);
|
|
}
|
|
|
|
JSValue JS_NewUint8ArrayCopy(JSContext *ctx, const uint8_t *buf, size_t len)
|
|
{
|
|
JSValue buffer = js_array_buffer_constructor3(ctx, JS_UNDEFINED, len,
|
|
JS_CLASS_ARRAY_BUFFER,
|
|
(uint8_t *)buf,
|
|
js_array_buffer_free, NULL,
|
|
TRUE);
|
|
return js_new_uint8array(ctx, buffer);
|
|
}
|
|
|
|
JS_BOOL JS_IsUint8Array(JSValue obj) {
|
|
return JS_GetClassID(obj) == JS_CLASS_UINT8_ARRAY;
|
|
}
|
|
|
|
/* Atomics */
|
|
#ifdef CONFIG_ATOMICS
|
|
|
|
typedef enum AtomicsOpEnum {
|
|
ATOMICS_OP_ADD,
|
|
ATOMICS_OP_AND,
|
|
ATOMICS_OP_OR,
|
|
ATOMICS_OP_SUB,
|
|
ATOMICS_OP_XOR,
|
|
ATOMICS_OP_EXCHANGE,
|
|
ATOMICS_OP_COMPARE_EXCHANGE,
|
|
ATOMICS_OP_LOAD,
|
|
} AtomicsOpEnum;
|
|
|
|
static void *js_atomics_get_ptr(JSContext *ctx,
|
|
JSArrayBuffer **pabuf,
|
|
int *psize_log2, JSClassID *pclass_id,
|
|
JSValue obj, JSValue idx_val,
|
|
int is_waitable)
|
|
{
|
|
JSObject *p;
|
|
JSTypedArray *ta;
|
|
JSArrayBuffer *abuf;
|
|
void *ptr;
|
|
uint64_t idx;
|
|
BOOL err;
|
|
int size_log2;
|
|
|
|
if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)
|
|
goto fail;
|
|
p = JS_VALUE_GET_OBJ(obj);
|
|
if (is_waitable)
|
|
err = (p->class_id != JS_CLASS_INT32_ARRAY &&
|
|
p->class_id != JS_CLASS_BIG_INT64_ARRAY);
|
|
else
|
|
err = !(p->class_id >= JS_CLASS_INT8_ARRAY &&
|
|
p->class_id <= JS_CLASS_BIG_UINT64_ARRAY);
|
|
if (err) {
|
|
fail:
|
|
JS_ThrowTypeError(ctx, "integer TypedArray expected");
|
|
return NULL;
|
|
}
|
|
ta = p->u.typed_array;
|
|
abuf = ta->buffer->u.array_buffer;
|
|
if (!abuf->shared) {
|
|
if (is_waitable == 2) {
|
|
JS_ThrowTypeError(ctx, "not a SharedArrayBuffer TypedArray");
|
|
return NULL;
|
|
}
|
|
if (abuf->detached) {
|
|
JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
|
|
return NULL;
|
|
}
|
|
}
|
|
if (JS_ToIndex(ctx, &idx, idx_val)) {
|
|
return NULL;
|
|
}
|
|
/* if the array buffer is detached, p->u.array.count = 0 */
|
|
if (idx >= p->u.array.count) {
|
|
JS_ThrowRangeError(ctx, "out-of-bound access");
|
|
return NULL;
|
|
}
|
|
size_log2 = typed_array_size_log2(p->class_id);
|
|
ptr = p->u.array.u.uint8_ptr + ((uintptr_t)idx << size_log2);
|
|
if (pabuf)
|
|
*pabuf = abuf;
|
|
if (psize_log2)
|
|
*psize_log2 = size_log2;
|
|
if (pclass_id)
|
|
*pclass_id = p->class_id;
|
|
return ptr;
|
|
}
|
|
|
|
static JSValue js_atomics_op(JSContext *ctx,
|
|
JSValue this_obj,
|
|
int argc, JSValue *argv, int op)
|
|
{
|
|
int size_log2;
|
|
uint64_t v, a, rep_val;
|
|
void *ptr;
|
|
JSValue ret;
|
|
JSClassID class_id;
|
|
JSArrayBuffer *abuf;
|
|
|
|
ptr = js_atomics_get_ptr(ctx, &abuf, &size_log2, &class_id,
|
|
argv[0], argv[1], 0);
|
|
if (!ptr)
|
|
return JS_EXCEPTION;
|
|
rep_val = 0;
|
|
if (op == ATOMICS_OP_LOAD) {
|
|
v = 0;
|
|
} else {
|
|
if (size_log2 == 3) {
|
|
int64_t v64;
|
|
if (JS_ToBigInt64(ctx, &v64, argv[2]))
|
|
return JS_EXCEPTION;
|
|
v = v64;
|
|
if (op == ATOMICS_OP_COMPARE_EXCHANGE) {
|
|
if (JS_ToBigInt64(ctx, &v64, argv[3]))
|
|
return JS_EXCEPTION;
|
|
rep_val = v64;
|
|
}
|
|
} else {
|
|
uint32_t v32;
|
|
if (JS_ToUint32(ctx, &v32, argv[2]))
|
|
return JS_EXCEPTION;
|
|
v = v32;
|
|
if (op == ATOMICS_OP_COMPARE_EXCHANGE) {
|
|
if (JS_ToUint32(ctx, &v32, argv[3]))
|
|
return JS_EXCEPTION;
|
|
rep_val = v32;
|
|
}
|
|
}
|
|
if (abuf->detached)
|
|
return JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
|
|
}
|
|
|
|
switch(op | (size_log2 << 3)) {
|
|
|
|
#define OP(op_name, func_name) \
|
|
case ATOMICS_OP_ ## op_name | (0 << 3): \
|
|
a = func_name((_Atomic uint8_t *)ptr, v); \
|
|
break; \
|
|
case ATOMICS_OP_ ## op_name | (1 << 3): \
|
|
a = func_name((_Atomic uint16_t *)ptr, v); \
|
|
break; \
|
|
case ATOMICS_OP_ ## op_name | (2 << 3): \
|
|
a = func_name((_Atomic uint32_t *)ptr, v); \
|
|
break; \
|
|
case ATOMICS_OP_ ## op_name | (3 << 3): \
|
|
a = func_name((_Atomic uint64_t *)ptr, v); \
|
|
break;
|
|
OP(ADD, atomic_fetch_add)
|
|
OP(AND, atomic_fetch_and)
|
|
OP(OR, atomic_fetch_or)
|
|
OP(SUB, atomic_fetch_sub)
|
|
OP(XOR, atomic_fetch_xor)
|
|
OP(EXCHANGE, atomic_exchange)
|
|
#undef OP
|
|
|
|
case ATOMICS_OP_LOAD | (0 << 3):
|
|
a = atomic_load((_Atomic uint8_t *)ptr);
|
|
break;
|
|
case ATOMICS_OP_LOAD | (1 << 3):
|
|
a = atomic_load((_Atomic uint16_t *)ptr);
|
|
break;
|
|
case ATOMICS_OP_LOAD | (2 << 3):
|
|
a = atomic_load((_Atomic uint32_t *)ptr);
|
|
break;
|
|
case ATOMICS_OP_LOAD | (3 << 3):
|
|
a = atomic_load((_Atomic uint64_t *)ptr);
|
|
break;
|
|
case ATOMICS_OP_COMPARE_EXCHANGE | (0 << 3):
|
|
{
|
|
uint8_t v1 = v;
|
|
atomic_compare_exchange_strong((_Atomic uint8_t *)ptr, &v1, rep_val);
|
|
a = v1;
|
|
}
|
|
break;
|
|
case ATOMICS_OP_COMPARE_EXCHANGE | (1 << 3):
|
|
{
|
|
uint16_t v1 = v;
|
|
atomic_compare_exchange_strong((_Atomic uint16_t *)ptr, &v1, rep_val);
|
|
a = v1;
|
|
}
|
|
break;
|
|
case ATOMICS_OP_COMPARE_EXCHANGE | (2 << 3):
|
|
{
|
|
uint32_t v1 = v;
|
|
atomic_compare_exchange_strong((_Atomic uint32_t *)ptr, &v1, rep_val);
|
|
a = v1;
|
|
}
|
|
break;
|
|
case ATOMICS_OP_COMPARE_EXCHANGE | (3 << 3):
|
|
{
|
|
uint64_t v1 = v;
|
|
atomic_compare_exchange_strong((_Atomic uint64_t *)ptr, &v1, rep_val);
|
|
a = v1;
|
|
}
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
|
|
switch(class_id) {
|
|
case JS_CLASS_INT8_ARRAY:
|
|
a = (int8_t)a;
|
|
goto done;
|
|
case JS_CLASS_UINT8_ARRAY:
|
|
a = (uint8_t)a;
|
|
goto done;
|
|
case JS_CLASS_INT16_ARRAY:
|
|
a = (int16_t)a;
|
|
goto done;
|
|
case JS_CLASS_UINT16_ARRAY:
|
|
a = (uint16_t)a;
|
|
goto done;
|
|
case JS_CLASS_INT32_ARRAY:
|
|
done:
|
|
ret = js_int32(a);
|
|
break;
|
|
case JS_CLASS_UINT32_ARRAY:
|
|
ret = js_uint32(a);
|
|
break;
|
|
case JS_CLASS_BIG_INT64_ARRAY:
|
|
ret = JS_NewBigInt64(ctx, a);
|
|
break;
|
|
case JS_CLASS_BIG_UINT64_ARRAY:
|
|
ret = JS_NewBigUint64(ctx, a);
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static JSValue js_atomics_store(JSContext *ctx,
|
|
JSValue this_obj,
|
|
int argc, JSValue *argv)
|
|
{
|
|
int size_log2;
|
|
void *ptr;
|
|
JSValue ret;
|
|
JSArrayBuffer *abuf;
|
|
|
|
ptr = js_atomics_get_ptr(ctx, &abuf, &size_log2, NULL,
|
|
argv[0], argv[1], 0);
|
|
if (!ptr)
|
|
return JS_EXCEPTION;
|
|
if (size_log2 == 3) {
|
|
int64_t v64;
|
|
ret = JS_ToBigIntValueFree(ctx, js_dup(argv[2]));
|
|
if (JS_IsException(ret))
|
|
return ret;
|
|
if (JS_ToBigInt64(ctx, &v64, ret)) {
|
|
JS_FreeValue(ctx, ret);
|
|
return JS_EXCEPTION;
|
|
}
|
|
if (abuf->detached)
|
|
return JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
|
|
atomic_store((_Atomic uint64_t *)ptr, v64);
|
|
} else {
|
|
uint32_t v;
|
|
/* XXX: spec, would be simpler to return the written value */
|
|
ret = JS_ToIntegerFree(ctx, js_dup(argv[2]));
|
|
if (JS_IsException(ret))
|
|
return ret;
|
|
if (JS_ToUint32(ctx, &v, ret)) {
|
|
JS_FreeValue(ctx, ret);
|
|
return JS_EXCEPTION;
|
|
}
|
|
if (abuf->detached)
|
|
return JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
|
|
switch(size_log2) {
|
|
case 0:
|
|
atomic_store((_Atomic uint8_t *)ptr, v);
|
|
break;
|
|
case 1:
|
|
atomic_store((_Atomic uint16_t *)ptr, v);
|
|
break;
|
|
case 2:
|
|
atomic_store((_Atomic uint32_t *)ptr, v);
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static JSValue js_atomics_isLockFree(JSContext *ctx,
|
|
JSValue this_obj,
|
|
int argc, JSValue *argv)
|
|
{
|
|
int v, ret;
|
|
if (JS_ToInt32Sat(ctx, &v, argv[0]))
|
|
return JS_EXCEPTION;
|
|
ret = (v == 1 || v == 2 || v == 4 || v == 8);
|
|
return js_bool(ret);
|
|
}
|
|
|
|
typedef struct JSAtomicsWaiter {
|
|
struct list_head link;
|
|
BOOL linked;
|
|
js_cond_t cond;
|
|
int32_t *ptr;
|
|
} JSAtomicsWaiter;
|
|
|
|
static js_once_t js_atomics_once = JS_ONCE_INIT;
|
|
static js_mutex_t js_atomics_mutex;
|
|
static struct list_head js_atomics_waiter_list =
|
|
LIST_HEAD_INIT(js_atomics_waiter_list);
|
|
|
|
static JSValue js_atomics_wait(JSContext *ctx,
|
|
JSValue this_obj,
|
|
int argc, JSValue *argv)
|
|
{
|
|
int64_t v;
|
|
int32_t v32;
|
|
void *ptr;
|
|
int64_t timeout;
|
|
struct timespec ts;
|
|
JSAtomicsWaiter waiter_s, *waiter;
|
|
int ret, size_log2, res;
|
|
double d;
|
|
|
|
ptr = js_atomics_get_ptr(ctx, NULL, &size_log2, NULL,
|
|
argv[0], argv[1], 2);
|
|
if (!ptr)
|
|
return JS_EXCEPTION;
|
|
if (size_log2 == 3) {
|
|
if (JS_ToBigInt64(ctx, &v, argv[2]))
|
|
return JS_EXCEPTION;
|
|
} else {
|
|
if (JS_ToInt32(ctx, &v32, argv[2]))
|
|
return JS_EXCEPTION;
|
|
v = v32;
|
|
}
|
|
if (JS_ToFloat64(ctx, &d, argv[3]))
|
|
return JS_EXCEPTION;
|
|
if (isnan(d) || d >= 0x1p63)
|
|
timeout = INT64_MAX;
|
|
else if (d < 0)
|
|
timeout = 0;
|
|
else
|
|
timeout = (int64_t)d;
|
|
if (!ctx->rt->can_block)
|
|
return JS_ThrowTypeError(ctx, "cannot block in this thread");
|
|
|
|
/* XXX: inefficient if large number of waiters, should hash on
|
|
'ptr' value */
|
|
js_mutex_lock(&js_atomics_mutex);
|
|
if (size_log2 == 3) {
|
|
res = *(int64_t *)ptr != v;
|
|
} else {
|
|
res = *(int32_t *)ptr != v;
|
|
}
|
|
if (res) {
|
|
js_mutex_unlock(&js_atomics_mutex);
|
|
return JS_AtomToString(ctx, JS_ATOM_not_equal);
|
|
}
|
|
|
|
waiter = &waiter_s;
|
|
waiter->ptr = ptr;
|
|
js_cond_init(&waiter->cond);
|
|
waiter->linked = TRUE;
|
|
list_add_tail(&waiter->link, &js_atomics_waiter_list);
|
|
|
|
if (timeout == INT64_MAX) {
|
|
js_cond_wait(&waiter->cond, &js_atomics_mutex);
|
|
ret = 0;
|
|
} else {
|
|
ret = js_cond_timedwait(&waiter->cond, &js_atomics_mutex, timeout * 1e6 /* to ns */);
|
|
}
|
|
if (waiter->linked)
|
|
list_del(&waiter->link);
|
|
js_mutex_unlock(&js_atomics_mutex);
|
|
js_cond_destroy(&waiter->cond);
|
|
if (ret == -1) {
|
|
return JS_AtomToString(ctx, JS_ATOM_timed_out);
|
|
} else {
|
|
return JS_AtomToString(ctx, JS_ATOM_ok);
|
|
}
|
|
}
|
|
|
|
static JSValue js_atomics_notify(JSContext *ctx,
|
|
JSValue this_obj,
|
|
int argc, JSValue *argv)
|
|
{
|
|
struct list_head *el, *el1, waiter_list;
|
|
int32_t count, n;
|
|
void *ptr;
|
|
JSAtomicsWaiter *waiter;
|
|
JSArrayBuffer *abuf;
|
|
|
|
ptr = js_atomics_get_ptr(ctx, &abuf, NULL, NULL, argv[0], argv[1], 1);
|
|
if (!ptr)
|
|
return JS_EXCEPTION;
|
|
|
|
if (JS_IsUndefined(argv[2])) {
|
|
count = INT32_MAX;
|
|
} else {
|
|
if (JS_ToInt32Clamp(ctx, &count, argv[2], 0, INT32_MAX, 0))
|
|
return JS_EXCEPTION;
|
|
}
|
|
if (abuf->detached)
|
|
return JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
|
|
|
|
n = 0;
|
|
if (abuf->shared && count > 0) {
|
|
js_mutex_lock(&js_atomics_mutex);
|
|
init_list_head(&waiter_list);
|
|
list_for_each_safe(el, el1, &js_atomics_waiter_list) {
|
|
waiter = list_entry(el, JSAtomicsWaiter, link);
|
|
if (waiter->ptr == ptr) {
|
|
list_del(&waiter->link);
|
|
waiter->linked = FALSE;
|
|
list_add_tail(&waiter->link, &waiter_list);
|
|
n++;
|
|
if (n >= count)
|
|
break;
|
|
}
|
|
}
|
|
list_for_each(el, &waiter_list) {
|
|
waiter = list_entry(el, JSAtomicsWaiter, link);
|
|
js_cond_signal(&waiter->cond);
|
|
}
|
|
js_mutex_unlock(&js_atomics_mutex);
|
|
}
|
|
return js_int32(n);
|
|
}
|
|
|
|
static const JSCFunctionListEntry js_atomics_funcs[] = {
|
|
JS_CFUNC_MAGIC_DEF("add", 3, js_atomics_op, ATOMICS_OP_ADD ),
|
|
JS_CFUNC_MAGIC_DEF("and", 3, js_atomics_op, ATOMICS_OP_AND ),
|
|
JS_CFUNC_MAGIC_DEF("or", 3, js_atomics_op, ATOMICS_OP_OR ),
|
|
JS_CFUNC_MAGIC_DEF("sub", 3, js_atomics_op, ATOMICS_OP_SUB ),
|
|
JS_CFUNC_MAGIC_DEF("xor", 3, js_atomics_op, ATOMICS_OP_XOR ),
|
|
JS_CFUNC_MAGIC_DEF("exchange", 3, js_atomics_op, ATOMICS_OP_EXCHANGE ),
|
|
JS_CFUNC_MAGIC_DEF("compareExchange", 4, js_atomics_op, ATOMICS_OP_COMPARE_EXCHANGE ),
|
|
JS_CFUNC_MAGIC_DEF("load", 2, js_atomics_op, ATOMICS_OP_LOAD ),
|
|
JS_CFUNC_DEF("store", 3, js_atomics_store ),
|
|
JS_CFUNC_DEF("isLockFree", 1, js_atomics_isLockFree ),
|
|
JS_CFUNC_DEF("wait", 4, js_atomics_wait ),
|
|
JS_CFUNC_DEF("notify", 3, js_atomics_notify ),
|
|
JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Atomics", JS_PROP_CONFIGURABLE ),
|
|
};
|
|
|
|
static const JSCFunctionListEntry js_atomics_obj[] = {
|
|
JS_OBJECT_DEF("Atomics", js_atomics_funcs, countof(js_atomics_funcs), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE ),
|
|
};
|
|
|
|
static void js__atomics_init(void) {
|
|
js_mutex_init(&js_atomics_mutex);
|
|
}
|
|
|
|
/* TODO(saghul) make this public and not dependent on typed arrays? */
|
|
void JS_AddIntrinsicAtomics(JSContext *ctx)
|
|
{
|
|
js_once(&js_atomics_once, js__atomics_init);
|
|
|
|
/* add Atomics as autoinit object */
|
|
JS_SetPropertyFunctionList(ctx, ctx->global_obj, js_atomics_obj, countof(js_atomics_obj));
|
|
}
|
|
|
|
#endif /* CONFIG_ATOMICS */
|
|
|
|
void JS_AddIntrinsicTypedArrays(JSContext *ctx)
|
|
{
|
|
JSValue typed_array_base_proto, typed_array_base_func;
|
|
JSValue array_buffer_func, shared_array_buffer_func;
|
|
int i;
|
|
|
|
ctx->class_proto[JS_CLASS_ARRAY_BUFFER] = JS_NewObject(ctx);
|
|
JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_ARRAY_BUFFER],
|
|
js_array_buffer_proto_funcs,
|
|
countof(js_array_buffer_proto_funcs));
|
|
|
|
array_buffer_func = JS_NewGlobalCConstructorOnly(ctx, "ArrayBuffer",
|
|
js_array_buffer_constructor, 1,
|
|
ctx->class_proto[JS_CLASS_ARRAY_BUFFER]);
|
|
JS_SetPropertyFunctionList(ctx, array_buffer_func,
|
|
js_array_buffer_funcs,
|
|
countof(js_array_buffer_funcs));
|
|
|
|
ctx->class_proto[JS_CLASS_SHARED_ARRAY_BUFFER] = JS_NewObject(ctx);
|
|
JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_SHARED_ARRAY_BUFFER],
|
|
js_shared_array_buffer_proto_funcs,
|
|
countof(js_shared_array_buffer_proto_funcs));
|
|
|
|
shared_array_buffer_func = JS_NewGlobalCConstructorOnly(ctx, "SharedArrayBuffer",
|
|
js_shared_array_buffer_constructor, 1,
|
|
ctx->class_proto[JS_CLASS_SHARED_ARRAY_BUFFER]);
|
|
JS_SetPropertyFunctionList(ctx, shared_array_buffer_func,
|
|
js_shared_array_buffer_funcs,
|
|
countof(js_shared_array_buffer_funcs));
|
|
|
|
typed_array_base_proto = JS_NewObject(ctx);
|
|
JS_SetPropertyFunctionList(ctx, typed_array_base_proto,
|
|
js_typed_array_base_proto_funcs,
|
|
countof(js_typed_array_base_proto_funcs));
|
|
|
|
/* TypedArray.prototype.toString must be the same object as Array.prototype.toString */
|
|
JSValue obj = JS_GetProperty(ctx, ctx->class_proto[JS_CLASS_ARRAY], JS_ATOM_toString);
|
|
/* XXX: should use alias method in JSCFunctionListEntry */ //@@@
|
|
JS_DefinePropertyValue(ctx, typed_array_base_proto, JS_ATOM_toString, obj,
|
|
JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
|
|
|
|
typed_array_base_func = JS_NewCFunction(ctx, js_typed_array_base_constructor,
|
|
"TypedArray", 0);
|
|
JS_SetPropertyFunctionList(ctx, typed_array_base_func,
|
|
js_typed_array_base_funcs,
|
|
countof(js_typed_array_base_funcs));
|
|
JS_SetConstructor(ctx, typed_array_base_func, typed_array_base_proto);
|
|
|
|
/* Used to squelch a -Wcast-function-type warning. */
|
|
JSCFunctionType ft = { .generic_magic = js_typed_array_constructor };
|
|
for(i = JS_CLASS_UINT8C_ARRAY; i < JS_CLASS_UINT8C_ARRAY + JS_TYPED_ARRAY_COUNT; i++) {
|
|
JSValue func_obj;
|
|
char buf[ATOM_GET_STR_BUF_SIZE];
|
|
const char *name;
|
|
|
|
ctx->class_proto[i] = JS_NewObjectProto(ctx, typed_array_base_proto);
|
|
JS_DefinePropertyValueStr(ctx, ctx->class_proto[i],
|
|
"BYTES_PER_ELEMENT",
|
|
js_int32(1 << typed_array_size_log2(i)),
|
|
0);
|
|
name = JS_AtomGetStr(ctx, buf, sizeof(buf),
|
|
JS_ATOM_Uint8ClampedArray + i - JS_CLASS_UINT8C_ARRAY);
|
|
func_obj = JS_NewCFunction3(ctx, ft.generic,
|
|
name, 3, JS_CFUNC_constructor_magic, i,
|
|
typed_array_base_func);
|
|
JS_NewGlobalCConstructor2(ctx, func_obj, name, ctx->class_proto[i]);
|
|
JS_DefinePropertyValueStr(ctx, func_obj,
|
|
"BYTES_PER_ELEMENT",
|
|
js_int32(1 << typed_array_size_log2(i)),
|
|
0);
|
|
}
|
|
JS_FreeValue(ctx, typed_array_base_proto);
|
|
JS_FreeValue(ctx, typed_array_base_func);
|
|
|
|
/* DataView */
|
|
ctx->class_proto[JS_CLASS_DATAVIEW] = JS_NewObject(ctx);
|
|
JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_DATAVIEW],
|
|
js_dataview_proto_funcs,
|
|
countof(js_dataview_proto_funcs));
|
|
JS_NewGlobalCConstructorOnly(ctx, "DataView",
|
|
js_dataview_constructor, 1,
|
|
ctx->class_proto[JS_CLASS_DATAVIEW]);
|
|
/* Atomics */
|
|
#ifdef CONFIG_ATOMICS
|
|
JS_AddIntrinsicAtomics(ctx);
|
|
#endif
|
|
}
|
|
|
|
/* Performance */
|
|
|
|
static double js__now_ms(void)
|
|
{
|
|
return js__hrtime_ns() / 1e6;
|
|
}
|
|
|
|
static JSValue js_perf_now(JSContext *ctx, JSValue this_val, int argc, JSValue *argv)
|
|
{
|
|
return js_float64(js__now_ms() - ctx->time_origin);
|
|
}
|
|
|
|
static const JSCFunctionListEntry js_perf_proto_funcs[] = {
|
|
JS_CFUNC_DEF2("now", 0, js_perf_now, JS_PROP_ENUMERABLE),
|
|
};
|
|
|
|
void JS_AddPerformance(JSContext *ctx)
|
|
{
|
|
ctx->time_origin = js__now_ms();
|
|
|
|
JSValue performance = JS_NewObject(ctx);
|
|
JS_SetPropertyFunctionList(ctx, performance, js_perf_proto_funcs, countof(js_perf_proto_funcs));
|
|
JS_DefinePropertyValueStr(ctx, performance, "timeOrigin",
|
|
js_float64(ctx->time_origin),
|
|
JS_PROP_ENUMERABLE);
|
|
JS_DefinePropertyValueStr(ctx, ctx->global_obj, "performance",
|
|
js_dup(performance),
|
|
JS_PROP_ENUMERABLE | JS_PROP_CONFIGURABLE);
|
|
JS_FreeValue(ctx, performance);
|
|
}
|
|
|
|
/* Equality comparisons and sameness */
|
|
int JS_IsEqual(JSContext *ctx, JSValue op1, JSValue op2)
|
|
{
|
|
JSValue sp[2] = { js_dup(op1), js_dup(op2) };
|
|
if (js_eq_slow(ctx, endof(sp), 0))
|
|
return -1;
|
|
return JS_VALUE_GET_BOOL(sp[0]);
|
|
}
|
|
|
|
JS_BOOL JS_IsStrictEqual(JSContext *ctx, JSValue op1, JSValue op2)
|
|
{
|
|
return js_strict_eq2(ctx, js_dup(op1), js_dup(op2), JS_EQ_STRICT);
|
|
}
|
|
|
|
JS_BOOL JS_IsSameValue(JSContext *ctx, JSValue op1, JSValue op2)
|
|
{
|
|
return js_same_value(ctx, op1, op2);
|
|
}
|
|
|
|
JS_BOOL JS_IsSameValueZero(JSContext *ctx, JSValue op1, JSValue op2)
|
|
{
|
|
return js_same_value_zero(ctx, op1, op2);
|
|
}
|
|
|
|
/* WeakRef */
|
|
|
|
typedef struct JSWeakRefData {
|
|
JSValue target;
|
|
JSValue obj;
|
|
} JSWeakRefData;
|
|
|
|
static void js_weakref_finalizer(JSRuntime *rt, JSValue val)
|
|
{
|
|
JSWeakRefData *wrd = JS_GetOpaque(val, JS_CLASS_WEAK_REF);
|
|
if (!wrd)
|
|
return;
|
|
|
|
/* Delete weak ref */
|
|
JSWeakRefRecord **pwr, *wr;
|
|
|
|
pwr = get_first_weak_ref(wrd->target);
|
|
for(;;) {
|
|
wr = *pwr;
|
|
assert(wr != NULL);
|
|
if (wr->kind == JS_WEAK_REF_KIND_WEAK_REF && wr->u.weak_ref_data == wrd)
|
|
break;
|
|
pwr = &wr->next_weak_ref;
|
|
}
|
|
*pwr = wr->next_weak_ref;
|
|
js_free_rt(rt, wrd);
|
|
js_free_rt(rt, wr);
|
|
}
|
|
|
|
static JSValue js_weakref_constructor(JSContext *ctx, JSValue new_target, int argc, JSValue *argv)
|
|
{
|
|
if (JS_IsUndefined(new_target))
|
|
return JS_ThrowTypeError(ctx, "constructor requires 'new'");
|
|
JSValue arg = argv[0];
|
|
if (!is_valid_weakref_target(arg))
|
|
return JS_ThrowTypeError(ctx, "invalid target");
|
|
// TODO(saghul): short-circuit if the refcount is 1?
|
|
JSValue obj = js_create_from_ctor(ctx, new_target, JS_CLASS_WEAK_REF);
|
|
if (JS_IsException(obj))
|
|
return JS_EXCEPTION;
|
|
JSWeakRefData *wrd = js_malloc(ctx, sizeof(*wrd));
|
|
if (!wrd) {
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
JSWeakRefRecord *wr = js_malloc(ctx, sizeof(*wr));
|
|
if (!wr) {
|
|
JS_FreeValue(ctx, obj);
|
|
js_free(ctx, wrd);
|
|
return JS_EXCEPTION;
|
|
}
|
|
wrd->target = arg;
|
|
wrd->obj = obj;
|
|
wr->kind = JS_WEAK_REF_KIND_WEAK_REF;
|
|
wr->u.weak_ref_data = wrd;
|
|
insert_weakref_record(arg, wr);
|
|
|
|
JS_SetOpaque(obj, wrd);
|
|
return obj;
|
|
}
|
|
|
|
static JSValue js_weakref_deref(JSContext *ctx, JSValue this_val, int argc, JSValue *argv)
|
|
{
|
|
JSWeakRefData *wrd = JS_GetOpaque2(ctx, this_val, JS_CLASS_WEAK_REF);
|
|
if (!wrd)
|
|
return JS_EXCEPTION;
|
|
return js_dup(wrd->target);
|
|
}
|
|
|
|
static const JSCFunctionListEntry js_weakref_proto_funcs[] = {
|
|
JS_CFUNC_DEF("deref", 0, js_weakref_deref ),
|
|
JS_PROP_STRING_DEF("[Symbol.toStringTag]", "WeakRef", JS_PROP_CONFIGURABLE ),
|
|
};
|
|
|
|
static const JSClassShortDef js_weakref_class_def[] = {
|
|
{ JS_ATOM_WeakRef, js_weakref_finalizer, NULL }, /* JS_CLASS_WEAK_REF */
|
|
};
|
|
|
|
typedef struct JSFinRecEntry {
|
|
struct list_head link;
|
|
JSValue obj;
|
|
JSValue target;
|
|
JSValue held_val;
|
|
JSValue token;
|
|
} JSFinRecEntry;
|
|
|
|
typedef struct JSFinalizationRegistryData {
|
|
struct list_head entries;
|
|
JSContext *ctx;
|
|
JSValue cb;
|
|
} JSFinalizationRegistryData;
|
|
|
|
static void delete_finrec_weakref(JSRuntime *rt, JSFinRecEntry *fre)
|
|
{
|
|
JSWeakRefRecord **pwr, *wr;
|
|
|
|
pwr = get_first_weak_ref(fre->target);
|
|
for(;;) {
|
|
wr = *pwr;
|
|
assert(wr != NULL);
|
|
if (wr->kind == JS_WEAK_REF_KIND_FINALIZATION_REGISTRY_ENTRY && wr->u.fin_rec_entry == fre)
|
|
break;
|
|
pwr = &wr->next_weak_ref;
|
|
}
|
|
*pwr = wr->next_weak_ref;
|
|
js_free_rt(rt, wr);
|
|
}
|
|
|
|
static void js_finrec_finalizer(JSRuntime *rt, JSValue val)
|
|
{
|
|
JSFinalizationRegistryData *frd = JS_GetOpaque(val, JS_CLASS_FINALIZATION_REGISTRY);
|
|
if (frd) {
|
|
struct list_head *el, *el1;
|
|
/* first pass to remove the weak ref entries and avoid having them modified
|
|
by freeing a token / held value. */
|
|
list_for_each_safe(el, el1, &frd->entries) {
|
|
JSFinRecEntry *fre = list_entry(el, JSFinRecEntry, link);
|
|
delete_finrec_weakref(rt, fre);
|
|
}
|
|
/* second pass to actually free all objects. */
|
|
list_for_each_safe(el, el1, &frd->entries) {
|
|
JSFinRecEntry *fre = list_entry(el, JSFinRecEntry, link);
|
|
list_del(&fre->link);
|
|
JS_FreeValueRT(rt, fre->held_val);
|
|
JS_FreeValueRT(rt, fre->token);
|
|
js_free_rt(rt, fre);
|
|
}
|
|
JS_FreeValueRT(rt, frd->cb);
|
|
js_free_rt(rt, frd);
|
|
}
|
|
}
|
|
|
|
static void js_finrec_mark(JSRuntime *rt, JSValue val, JS_MarkFunc *mark_func)
|
|
{
|
|
JSFinalizationRegistryData *frd = JS_GetOpaque(val, JS_CLASS_FINALIZATION_REGISTRY);
|
|
if (frd) {
|
|
JS_MarkValue(rt, frd->cb, mark_func);
|
|
struct list_head *el;
|
|
list_for_each(el, &frd->entries) {
|
|
JSFinRecEntry *fre = list_entry(el, JSFinRecEntry, link);
|
|
JS_MarkValue(rt, fre->held_val, mark_func);
|
|
JS_MarkValue(rt, fre->token, mark_func);
|
|
}
|
|
}
|
|
}
|
|
|
|
static JSValue js_finrec_constructor(JSContext *ctx, JSValue new_target, int argc, JSValue *argv)
|
|
{
|
|
if (JS_IsUndefined(new_target))
|
|
return JS_ThrowTypeError(ctx, "constructor requires 'new'");
|
|
JSValue cb = argv[0];
|
|
if (!JS_IsFunction(ctx, cb))
|
|
return JS_ThrowTypeError(ctx, "argument must be a function");
|
|
|
|
JSValue obj = js_create_from_ctor(ctx, new_target, JS_CLASS_FINALIZATION_REGISTRY);
|
|
if (JS_IsException(obj))
|
|
return JS_EXCEPTION;
|
|
JSFinalizationRegistryData *frd = js_malloc(ctx, sizeof(*frd));
|
|
if (!frd) {
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
init_list_head(&frd->entries);
|
|
frd->ctx = ctx;
|
|
frd->cb = js_dup(cb);
|
|
JS_SetOpaque(obj, frd);
|
|
return obj;
|
|
}
|
|
|
|
static JSValue js_finrec_register(JSContext *ctx, JSValue this_val, int argc, JSValue *argv)
|
|
{
|
|
JSFinalizationRegistryData *frd = JS_GetOpaque2(ctx, this_val, JS_CLASS_FINALIZATION_REGISTRY);
|
|
if (!frd)
|
|
return JS_EXCEPTION;
|
|
|
|
JSValue target = argv[0];
|
|
JSValue held_val = argv[1];
|
|
// The function length needs to return 2, so the 3rd argument won't be initialized.
|
|
JSValue token = argc > 2 ? argv[2] : JS_UNDEFINED;
|
|
|
|
if (!is_valid_weakref_target(target))
|
|
return JS_ThrowTypeError(ctx, "invalid target");
|
|
if (js_same_value(ctx, target, this_val))
|
|
return JS_UNDEFINED;
|
|
if (!JS_IsUndefined(held_val) && js_same_value(ctx, target, held_val))
|
|
return JS_ThrowTypeError(ctx, "held value cannot be the target");
|
|
if (!JS_IsUndefined(token) && !is_valid_weakref_target(token))
|
|
return JS_ThrowTypeError(ctx, "invalid unregister token");
|
|
|
|
JSFinRecEntry *fre = js_malloc(ctx, sizeof(*fre));
|
|
if (!fre)
|
|
return JS_EXCEPTION;
|
|
JSWeakRefRecord *wr = js_malloc(ctx, sizeof(*wr));
|
|
if (!wr) {
|
|
js_free(ctx, fre);
|
|
return JS_EXCEPTION;
|
|
}
|
|
fre->obj = this_val;
|
|
fre->target = target;
|
|
fre->held_val = js_dup(held_val);
|
|
fre->token = js_dup(token);
|
|
list_add_tail(&fre->link, &frd->entries);
|
|
wr->kind = JS_WEAK_REF_KIND_FINALIZATION_REGISTRY_ENTRY;
|
|
wr->u.fin_rec_entry = fre;
|
|
insert_weakref_record(target, wr);
|
|
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
static JSValue js_finrec_unregister(JSContext *ctx, JSValue this_val, int argc, JSValue *argv)
|
|
{
|
|
JSFinalizationRegistryData *frd = JS_GetOpaque2(ctx, this_val, JS_CLASS_FINALIZATION_REGISTRY);
|
|
if (!frd)
|
|
return JS_EXCEPTION;
|
|
|
|
JSValue token = argv[0];
|
|
if (!is_valid_weakref_target(token))
|
|
return JS_ThrowTypeError(ctx, "invalid unregister token");
|
|
|
|
struct list_head *el, *el1;
|
|
BOOL removed = FALSE;
|
|
list_for_each_safe(el, el1, &frd->entries) {
|
|
JSFinRecEntry *fre = list_entry(el, JSFinRecEntry, link);
|
|
if (js_same_value(ctx, fre->token, token)) {
|
|
list_del(&fre->link);
|
|
delete_finrec_weakref(ctx->rt, fre);
|
|
JS_FreeValue(ctx, fre->held_val);
|
|
JS_FreeValue(ctx, fre->token);
|
|
js_free(ctx, fre);
|
|
removed = TRUE;
|
|
}
|
|
}
|
|
|
|
return js_bool(removed);
|
|
}
|
|
|
|
static const JSCFunctionListEntry js_finrec_proto_funcs[] = {
|
|
JS_CFUNC_DEF("register", 2, js_finrec_register ),
|
|
JS_CFUNC_DEF("unregister", 1, js_finrec_unregister ),
|
|
JS_PROP_STRING_DEF("[Symbol.toStringTag]", "FinalizationRegistry", JS_PROP_CONFIGURABLE ),
|
|
};
|
|
|
|
static const JSClassShortDef js_finrec_class_def[] = {
|
|
{ JS_ATOM_FinalizationRegistry, js_finrec_finalizer, js_finrec_mark }, /* JS_CLASS_FINALIZATION_REGISTRY */
|
|
};
|
|
|
|
void JS_AddIntrinsicWeakRef(JSContext *ctx)
|
|
{
|
|
JSRuntime *rt = ctx->rt;
|
|
|
|
/* WeakRef */
|
|
if (!JS_IsRegisteredClass(rt, JS_CLASS_WEAK_REF)) {
|
|
init_class_range(rt, js_weakref_class_def, JS_CLASS_WEAK_REF,
|
|
countof(js_weakref_class_def));
|
|
}
|
|
ctx->class_proto[JS_CLASS_WEAK_REF] = JS_NewObject(ctx);
|
|
JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_WEAK_REF],
|
|
js_weakref_proto_funcs,
|
|
countof(js_weakref_proto_funcs));
|
|
JS_NewGlobalCConstructor(ctx, "WeakRef", js_weakref_constructor, 1, ctx->class_proto[JS_CLASS_WEAK_REF]);
|
|
|
|
/* FinalizationRegistry */
|
|
if (!JS_IsRegisteredClass(rt, JS_CLASS_FINALIZATION_REGISTRY)) {
|
|
init_class_range(rt, js_finrec_class_def, JS_CLASS_FINALIZATION_REGISTRY,
|
|
countof(js_finrec_class_def));
|
|
}
|
|
ctx->class_proto[JS_CLASS_FINALIZATION_REGISTRY] = JS_NewObject(ctx);
|
|
JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_FINALIZATION_REGISTRY],
|
|
js_finrec_proto_funcs,
|
|
countof(js_finrec_proto_funcs));
|
|
JS_NewGlobalCConstructor(ctx, "FinalizationRegistry", js_finrec_constructor, 1, ctx->class_proto[JS_CLASS_FINALIZATION_REGISTRY]);
|
|
}
|
|
|
|
static void reset_weak_ref(JSRuntime *rt, JSWeakRefRecord **first_weak_ref)
|
|
{
|
|
JSWeakRefRecord *wr, *wr_next;
|
|
JSWeakRefData *wrd;
|
|
JSMapRecord *mr;
|
|
JSMapState *s;
|
|
JSFinRecEntry *fre;
|
|
|
|
/* first pass to remove the records from the WeakMap/WeakSet
|
|
lists */
|
|
for(wr = *first_weak_ref; wr != NULL; wr = wr->next_weak_ref) {
|
|
switch(wr->kind) {
|
|
case JS_WEAK_REF_KIND_MAP:
|
|
mr = wr->u.map_record;
|
|
s = mr->map;
|
|
assert(s->is_weak);
|
|
assert(!mr->empty); /* no iterator on WeakMap/WeakSet */
|
|
list_del(&mr->hash_link);
|
|
list_del(&mr->link);
|
|
break;
|
|
case JS_WEAK_REF_KIND_WEAK_REF:
|
|
wrd = wr->u.weak_ref_data;
|
|
wrd->target = JS_UNDEFINED;
|
|
break;
|
|
case JS_WEAK_REF_KIND_FINALIZATION_REGISTRY_ENTRY:
|
|
fre = wr->u.fin_rec_entry;
|
|
list_del(&fre->link);
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
}
|
|
|
|
/* second pass to free the values to avoid modifying the weak
|
|
reference list while traversing it. */
|
|
for(wr = *first_weak_ref; wr != NULL; wr = wr_next) {
|
|
wr_next = wr->next_weak_ref;
|
|
switch(wr->kind) {
|
|
case JS_WEAK_REF_KIND_MAP:
|
|
mr = wr->u.map_record;
|
|
JS_FreeValueRT(rt, mr->value);
|
|
js_free_rt(rt, mr);
|
|
break;
|
|
case JS_WEAK_REF_KIND_WEAK_REF:
|
|
wrd = wr->u.weak_ref_data;
|
|
JS_SetOpaque(wrd->obj, NULL);
|
|
js_free_rt(rt, wrd);
|
|
break;
|
|
case JS_WEAK_REF_KIND_FINALIZATION_REGISTRY_ENTRY: {
|
|
fre = wr->u.fin_rec_entry;
|
|
JSFinalizationRegistryData *frd = JS_GetOpaque(fre->obj, JS_CLASS_FINALIZATION_REGISTRY);
|
|
assert(frd != NULL);
|
|
/**
|
|
* During the GC sweep phase the held object might be collected first.
|
|
*/
|
|
if (JS_IsLiveObject(frd->ctx->rt, fre->held_val)) {
|
|
JSValue func = js_dup(frd->cb);
|
|
JSValue ret = JS_Call(frd->ctx, func, JS_UNDEFINED, 1, &fre->held_val);
|
|
JS_FreeValueRT(rt, func);
|
|
JS_FreeValueRT(rt, ret);
|
|
JS_FreeValueRT(rt, fre->held_val);
|
|
}
|
|
JS_FreeValueRT(rt, fre->token);
|
|
js_free_rt(rt, fre);
|
|
break;
|
|
}
|
|
default:
|
|
abort();
|
|
}
|
|
js_free_rt(rt, wr);
|
|
}
|
|
|
|
*first_weak_ref = NULL; /* fail safe */
|
|
}
|
|
|
|
static BOOL is_valid_weakref_target(JSValue val)
|
|
{
|
|
switch (JS_VALUE_GET_TAG(val)) {
|
|
case JS_TAG_OBJECT:
|
|
break;
|
|
case JS_TAG_SYMBOL: {
|
|
// Per spec: prohibit symbols registered with Symbol.for()
|
|
JSAtomStruct *p = JS_VALUE_GET_PTR(val);
|
|
if (p->atom_type != JS_ATOM_TYPE_GLOBAL_SYMBOL)
|
|
break;
|
|
// fallthru
|
|
}
|
|
default:
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void insert_weakref_record(JSValue target, struct JSWeakRefRecord *wr)
|
|
{
|
|
JSWeakRefRecord **pwr = get_first_weak_ref(target);
|
|
/* Add the weak reference */
|
|
wr->next_weak_ref = *pwr;
|
|
*pwr = wr;
|
|
}
|
|
|
|
/* Poly IC */
|
|
|
|
JSInlineCache *init_ic(JSContext *ctx)
|
|
{
|
|
JSInlineCache *ic;
|
|
ic = js_malloc(ctx, sizeof(JSInlineCache));
|
|
if (unlikely(!ic))
|
|
goto fail;
|
|
ic->count = 0;
|
|
ic->hash_bits = 2;
|
|
ic->capacity = 1 << ic->hash_bits;
|
|
ic->hash = js_mallocz(ctx, sizeof(ic->hash[0]) * ic->capacity);
|
|
if (unlikely(!ic->hash))
|
|
goto fail;
|
|
ic->cache = NULL;
|
|
ic->updated = FALSE;
|
|
ic->updated_offset = 0;
|
|
return ic;
|
|
fail:
|
|
js_free(ctx, ic);
|
|
return NULL;
|
|
}
|
|
|
|
int rebuild_ic(JSContext *ctx, JSInlineCache *ic)
|
|
{
|
|
uint32_t i, count;
|
|
JSInlineCacheHashSlot *ch;
|
|
if (ic->count == 0)
|
|
goto end;
|
|
count = 0;
|
|
ic->cache = js_mallocz(ctx, sizeof(JSInlineCacheRingSlot) * ic->count);
|
|
if (unlikely(!ic->cache))
|
|
goto fail;
|
|
for (i = 0; i < ic->capacity; i++) {
|
|
for (ch = ic->hash[i]; ch != NULL; ch = ch->next) {
|
|
ch->index = count++;
|
|
ic->cache[ch->index].atom = JS_DupAtom(ctx, ch->atom);
|
|
ic->cache[ch->index].index = 0;
|
|
}
|
|
}
|
|
end:
|
|
return 0;
|
|
fail:
|
|
return -1;
|
|
}
|
|
|
|
int resize_ic_hash(JSContext *ctx, JSInlineCache *ic)
|
|
{
|
|
uint32_t new_capacity, i, h;
|
|
JSInlineCacheHashSlot *ch, *ch_next;
|
|
JSInlineCacheHashSlot **new_hash;
|
|
new_capacity = 1 << (ic->hash_bits + 1);
|
|
new_hash = js_mallocz(ctx, sizeof(ic->hash[0]) * new_capacity);
|
|
if (unlikely(!new_hash))
|
|
goto fail;
|
|
ic->hash_bits += 1;
|
|
for (i = 0; i < ic->capacity; i++) {
|
|
for (ch = ic->hash[i]; ch != NULL; ch = ch_next) {
|
|
h = get_index_hash(ch->atom, ic->hash_bits);
|
|
ch_next = ch->next;
|
|
ch->next = new_hash[h];
|
|
new_hash[h] = ch;
|
|
}
|
|
}
|
|
js_free(ctx, ic->hash);
|
|
ic->hash = new_hash;
|
|
ic->capacity = new_capacity;
|
|
return 0;
|
|
fail:
|
|
return -1;
|
|
}
|
|
|
|
int free_ic(JSRuntime* rt, JSInlineCache *ic)
|
|
{
|
|
uint32_t i;
|
|
JSInlineCacheHashSlot *ch, *ch_next;
|
|
JSShape **shape, *(*shapes)[IC_CACHE_ITEM_CAPACITY];
|
|
if (ic->cache) {
|
|
for (i = 0; i < ic->count; i++) {
|
|
shapes = &ic->cache[i].shape;
|
|
JS_FreeAtomRT(rt, ic->cache[i].atom);
|
|
for (shape = *shapes; shape != endof(*shapes); shape++)
|
|
js_free_shape_null(rt, *shape);
|
|
}
|
|
}
|
|
for (i = 0; i < ic->capacity; i++) {
|
|
for (ch = ic->hash[i]; ch != NULL; ch = ch_next) {
|
|
ch_next = ch->next;
|
|
JS_FreeAtomRT(rt, ch->atom);
|
|
js_free_rt(rt, ch);
|
|
}
|
|
}
|
|
if (ic->count > 0)
|
|
js_free_rt(rt, ic->cache);
|
|
js_free_rt(rt, ic->hash);
|
|
js_free_rt(rt, ic);
|
|
return 0;
|
|
}
|
|
|
|
uint32_t add_ic_slot(JSContext *ctx, JSInlineCache *ic, JSAtom atom, JSObject *object,
|
|
uint32_t prop_offset)
|
|
{
|
|
int32_t i;
|
|
uint32_t h;
|
|
JSInlineCacheHashSlot *ch;
|
|
JSInlineCacheRingSlot *cr;
|
|
JSShape *sh;
|
|
cr = NULL;
|
|
h = get_index_hash(atom, ic->hash_bits);
|
|
for (ch = ic->hash[h]; ch != NULL; ch = ch->next) {
|
|
if (ch->atom == atom) {
|
|
cr = ic->cache + ch->index;
|
|
break;
|
|
}
|
|
}
|
|
|
|
assert(cr != NULL);
|
|
i = cr->index;
|
|
for (;;) {
|
|
if (object->shape == cr->shape[i]) {
|
|
cr->prop_offset[i] = prop_offset;
|
|
goto end;
|
|
}
|
|
i = (i + 1) % countof(cr->shape);
|
|
if (unlikely(i == cr->index))
|
|
break;
|
|
}
|
|
sh = cr->shape[i];
|
|
cr->shape[i] = js_dup_shape(object->shape);
|
|
js_free_shape_null(ctx->rt, sh);
|
|
cr->prop_offset[i] = prop_offset;
|
|
end:
|
|
return ch->index;
|
|
}
|
|
|
|
/* CallSite */
|
|
|
|
static void js_callsite_finalizer(JSRuntime *rt, JSValue val)
|
|
{
|
|
JSCallSiteData *csd = JS_GetOpaque(val, JS_CLASS_CALL_SITE);
|
|
if (csd) {
|
|
JS_FreeValueRT(rt, csd->filename);
|
|
JS_FreeValueRT(rt, csd->func);
|
|
JS_FreeValueRT(rt, csd->func_name);
|
|
js_free_rt(rt, csd);
|
|
}
|
|
}
|
|
|
|
static void js_callsite_mark(JSRuntime *rt, JSValue val, JS_MarkFunc *mark_func)
|
|
{
|
|
JSCallSiteData *csd = JS_GetOpaque(val, JS_CLASS_CALL_SITE);
|
|
if (csd) {
|
|
JS_MarkValue(rt, csd->filename, mark_func);
|
|
JS_MarkValue(rt, csd->func, mark_func);
|
|
JS_MarkValue(rt, csd->func_name, mark_func);
|
|
}
|
|
}
|
|
|
|
static JSValue js_new_callsite(JSContext *ctx, JSCallSiteData *csd) {
|
|
JSValue obj = js_create_from_ctor(ctx, JS_UNDEFINED, JS_CLASS_CALL_SITE);
|
|
if (JS_IsException(obj))
|
|
return JS_EXCEPTION;
|
|
|
|
JSCallSiteData *csd1 = js_malloc(ctx, sizeof(*csd));
|
|
if (!csd1) {
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
memcpy(csd1, csd, sizeof(*csd));
|
|
|
|
JS_SetOpaque(obj, csd1);
|
|
|
|
return obj;
|
|
}
|
|
|
|
static void js_new_callsite_data(JSContext *ctx, JSCallSiteData *csd, JSStackFrame *sf)
|
|
{
|
|
const char *func_name_str;
|
|
JSObject *p;
|
|
|
|
csd->func = js_dup(sf->cur_func);
|
|
/* func_name_str is UTF-8 encoded if needed */
|
|
func_name_str = get_func_name(ctx, sf->cur_func);
|
|
if (!func_name_str || func_name_str[0] == '\0')
|
|
csd->func_name = JS_NULL;
|
|
else
|
|
csd->func_name = JS_NewString(ctx, func_name_str);
|
|
JS_FreeCString(ctx, func_name_str);
|
|
if (JS_IsException(csd->func_name))
|
|
csd->func_name = JS_NULL;
|
|
|
|
p = JS_VALUE_GET_OBJ(sf->cur_func);
|
|
if (js_class_has_bytecode(p->class_id)) {
|
|
JSFunctionBytecode *b = p->u.func.function_bytecode;
|
|
int line_num1, col_num1;
|
|
line_num1 = find_line_num(ctx, b,
|
|
sf->cur_pc - b->byte_code_buf - 1,
|
|
&col_num1);
|
|
csd->native = FALSE;
|
|
csd->line_num = line_num1;
|
|
csd->col_num = col_num1;
|
|
csd->filename = JS_AtomToString(ctx, b->filename);
|
|
if (JS_IsException(csd->filename)) {
|
|
csd->filename = JS_NULL;
|
|
JS_FreeValue(ctx, JS_GetException(ctx)); // Clear exception.
|
|
}
|
|
} else {
|
|
csd->native = TRUE;
|
|
csd->line_num = -1;
|
|
csd->col_num = -1;
|
|
csd->filename = JS_NULL;
|
|
}
|
|
}
|
|
|
|
static void js_new_callsite_data2(JSContext *ctx, JSCallSiteData *csd, const char *filename, int line_num, int col_num)
|
|
{
|
|
csd->func = JS_NULL;
|
|
csd->func_name = JS_NULL;
|
|
csd->native = FALSE;
|
|
csd->line_num = line_num;
|
|
csd->col_num = col_num;
|
|
/* filename is UTF-8 encoded if needed (original argument to __JS_EvalInternal()) */
|
|
csd->filename = JS_NewString(ctx, filename);
|
|
if (JS_IsException(csd->filename)) {
|
|
csd->filename = JS_NULL;
|
|
JS_FreeValue(ctx, JS_GetException(ctx)); // Clear exception.
|
|
}
|
|
}
|
|
|
|
static JSValue js_callsite_getfield(JSContext *ctx, JSValue this_val, int argc, JSValue *argv, int magic)
|
|
{
|
|
JSCallSiteData *csd = JS_GetOpaque2(ctx, this_val, JS_CLASS_CALL_SITE);
|
|
if (!csd)
|
|
return JS_EXCEPTION;
|
|
JSValue *field = (void *)((char *)csd + magic);
|
|
return js_dup(*field);
|
|
}
|
|
|
|
static JSValue js_callsite_isnative(JSContext *ctx, JSValue this_val, int argc, JSValue *argv)
|
|
{
|
|
JSCallSiteData *csd = JS_GetOpaque2(ctx, this_val, JS_CLASS_CALL_SITE);
|
|
if (!csd)
|
|
return JS_EXCEPTION;
|
|
return JS_NewBool(ctx, csd->native);
|
|
}
|
|
|
|
static JSValue js_callsite_getnumber(JSContext *ctx, JSValue this_val, int argc, JSValue *argv, int magic)
|
|
{
|
|
JSCallSiteData *csd = JS_GetOpaque2(ctx, this_val, JS_CLASS_CALL_SITE);
|
|
if (!csd)
|
|
return JS_EXCEPTION;
|
|
int *field = (void *)((char *)csd + magic);
|
|
return JS_NewInt32(ctx, *field);
|
|
}
|
|
|
|
static const JSCFunctionListEntry js_callsite_proto_funcs[] = {
|
|
JS_CFUNC_DEF("isNative", 0, js_callsite_isnative),
|
|
JS_CFUNC_MAGIC_DEF("getFileName", 0, js_callsite_getfield, offsetof(JSCallSiteData, filename)),
|
|
JS_CFUNC_MAGIC_DEF("getFunction", 0, js_callsite_getfield, offsetof(JSCallSiteData, func)),
|
|
JS_CFUNC_MAGIC_DEF("getFunctionName", 0, js_callsite_getfield, offsetof(JSCallSiteData, func_name)),
|
|
JS_CFUNC_MAGIC_DEF("getColumnNumber", 0, js_callsite_getnumber, offsetof(JSCallSiteData, col_num)),
|
|
JS_CFUNC_MAGIC_DEF("getLineNumber", 0, js_callsite_getnumber, offsetof(JSCallSiteData, line_num)),
|
|
JS_PROP_STRING_DEF("[Symbol.toStringTag]", "CallSite", JS_PROP_CONFIGURABLE ),
|
|
};
|
|
|
|
static const JSClassShortDef js_callsite_class_def[] = {
|
|
{ JS_ATOM_CallSite, js_callsite_finalizer, js_callsite_mark }, /* JS_CLASS_CALL_SITE */
|
|
};
|
|
|
|
static void _JS_AddIntrinsicCallSite(JSContext *ctx)
|
|
{
|
|
JSRuntime *rt = ctx->rt;
|
|
|
|
if (!JS_IsRegisteredClass(rt, JS_CLASS_CALL_SITE)) {
|
|
init_class_range(rt, js_callsite_class_def, JS_CLASS_CALL_SITE,
|
|
countof(js_callsite_class_def));
|
|
}
|
|
ctx->class_proto[JS_CLASS_CALL_SITE] = JS_NewObject(ctx);
|
|
JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_CALL_SITE],
|
|
js_callsite_proto_funcs,
|
|
countof(js_callsite_proto_funcs));
|
|
}
|