mirror of
https://github.com/daveallie/crosspoint-reader.git
synced 2026-02-04 14:47:37 +03:00
18325 lines
558 KiB
C
18325 lines
558 KiB
C
/*
|
|
* Micro QuickJS Javascript Engine
|
|
*
|
|
* Copyright (c) 2017-2025 Fabrice Bellard
|
|
* Copyright (c) 2017-2025 Charlie Gordon
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
*/
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <stdarg.h>
|
|
#include <inttypes.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
#include <math.h>
|
|
#include <setjmp.h>
|
|
|
|
#include "cutils.h"
|
|
#include "dtoa.h"
|
|
#include "mquickjs_priv.h"
|
|
|
|
/*
|
|
TODO:
|
|
- regexp: better error position info
|
|
- use a specific MTAG for short functions instead of an immediate value
|
|
- use hash table for atoms
|
|
- set the length accessors as non configurable so that the
|
|
'get_length' instruction optimizations are always safe.
|
|
- memory:
|
|
- fix stack_bottom logic
|
|
- launch gc at regular intervals
|
|
- only launch compaction when needed (handle free blocks in malloc())
|
|
- avoid pass to rehash the properties
|
|
- ensure no undefined bytes (e.g. at end of JSString) in
|
|
saved bytecode ?
|
|
- reduced memory usage:
|
|
- reduce JSFunctionBytecode size (remove source_pos)
|
|
- do not explicitly store function names for get/set/bound
|
|
- use JSSTDLibraryDef fields instead of copying them to JSContext ?
|
|
*/
|
|
|
|
#define __exception __attribute__((warn_unused_result))
|
|
|
|
#define JS_STACK_SLACK 16 /* additional free space on the stack */
|
|
/* min free size in bytes between heap_free and the bottom of the stack */
|
|
#define JS_MIN_FREE_SIZE 512
|
|
/* minimum free size in bytes to create the out of memory object */
|
|
#define JS_MIN_CRITICAL_FREE_SIZE (JS_MIN_FREE_SIZE - 256)
|
|
#define JS_MAX_LOCAL_VARS 65535
|
|
#define JS_MAX_FUNC_STACK_SIZE 65535
|
|
#define JS_MAX_ARGC 65535
|
|
/* maximum number of recursing JS_Call() */
|
|
#define JS_MAX_CALL_RECURSE 8
|
|
|
|
|
|
#define JS_VALUE_IS_BOTH_INT(a, b) ((((a) | (b)) & 1) == 0)
|
|
#define JS_VALUE_IS_BOTH_SHORT_FLOAT(a, b) (((((a) - JS_TAG_SHORT_FLOAT) | ((b) - JS_TAG_SHORT_FLOAT)) & 7) == 0)
|
|
|
|
static __maybe_unused const char *js_mtag_name[JS_MTAG_COUNT] = {
|
|
"free",
|
|
"object",
|
|
"float64",
|
|
"string",
|
|
"func_bytecode",
|
|
"value_array",
|
|
"byte_array",
|
|
"varref",
|
|
};
|
|
|
|
/* function call flags (max 31 bits) */
|
|
#define FRAME_CF_ARGC_MASK 0xffff
|
|
/* FRAME_CF_CTOR */
|
|
#define FRAME_CF_POP_RET (1 << 17) /* pop the return value */
|
|
#define FRAME_CF_PC_ADD1 (1 << 18) /* increment the PC by 1 instead of 3 */
|
|
|
|
#define JS_MB_PAD(n) (JSW * 8 - (n))
|
|
|
|
typedef struct {
|
|
JS_MB_HEADER;
|
|
JSWord dummy: JS_MB_PAD(JS_MTAG_BITS);
|
|
} JSMemBlockHeader;
|
|
|
|
typedef struct {
|
|
JS_MB_HEADER;
|
|
/* in JSWords excluding the header. Free blocks of JSW bytes
|
|
are only generated by js_shrink() and may not be always
|
|
compacted */
|
|
JSWord size: JS_MB_PAD(JS_MTAG_BITS);
|
|
} JSFreeBlock;
|
|
|
|
#if JSW == 8
|
|
#define JS_STRING_LEN_MAX 0x7ffffffe
|
|
#else
|
|
#define JS_STRING_LEN_MAX ((1 << (32 - JS_MTAG_BITS - 3)) - 1)
|
|
#endif
|
|
|
|
typedef struct {
|
|
JS_MB_HEADER;
|
|
JSWord is_unique: 1;
|
|
JSWord is_ascii: 1;
|
|
/* true if the string content represents a number, only meaningful
|
|
is is_unique = true */
|
|
JSWord is_numeric: 1;
|
|
JSWord len: JS_MB_PAD(JS_MTAG_BITS + 3);
|
|
uint8_t buf[];
|
|
} JSString;
|
|
|
|
typedef struct {
|
|
JSWord string_buf[sizeof(JSString) / sizeof(JSWord)]; /* for JSString */
|
|
uint8_t buf[5];
|
|
} JSStringCharBuf;
|
|
|
|
#define JS_BYTE_ARRAY_SIZE_MAX ((1 << (32 - JS_MTAG_BITS)) - 1)
|
|
|
|
typedef struct {
|
|
JS_MB_HEADER;
|
|
JSWord size: JS_MB_PAD(JS_MTAG_BITS);
|
|
uint8_t buf[];
|
|
} JSByteArray;
|
|
|
|
#define JS_VALUE_ARRAY_SIZE_MAX ((1 << (32 - JS_MTAG_BITS)) - 1)
|
|
|
|
typedef struct {
|
|
JS_MB_HEADER;
|
|
JSWord size: JS_MB_PAD(JS_MTAG_BITS);
|
|
JSValue arr[];
|
|
} JSValueArray;
|
|
|
|
typedef struct JSVarRef {
|
|
JS_MB_HEADER;
|
|
JSWord is_detached : 1;
|
|
JSWord dummy: JS_MB_PAD(JS_MTAG_BITS + 1);
|
|
union {
|
|
JSValue value; /* is_detached = true */
|
|
struct {
|
|
JSValue next; /* is_detached = false: JS_NULL or JSVarRef,
|
|
must be at the same address as 'value' */
|
|
JSValue *pvalue;
|
|
};
|
|
} u;
|
|
} JSVarRef;
|
|
|
|
typedef struct {
|
|
JS_MB_HEADER;
|
|
JSWord dummy: JS_MB_PAD(JS_MTAG_BITS);
|
|
#ifdef JS_PTR64
|
|
struct {
|
|
double dval;
|
|
} u;
|
|
#else
|
|
/* unaligned 64 bit access in 32-bit mode */
|
|
struct __attribute__((packed)) {
|
|
double dval;
|
|
} u;
|
|
#endif
|
|
} JSFloat64;
|
|
|
|
typedef struct JSROMClass {
|
|
JS_MB_HEADER;
|
|
JSWord dummy: JS_MB_PAD(JS_MTAG_BITS);
|
|
JSValue props;
|
|
int32_t ctor_idx; /* -1 if defining a normal object */
|
|
JSValue proto_props;
|
|
JSValue parent_class; /* JSROMClass or JS_NULL */
|
|
} JSROMClass;
|
|
|
|
#define N_ROM_ATOM_TABLES_MAX 2
|
|
|
|
/* 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
|
|
|
|
#define JS_STRING_POS_CACHE_SIZE 2
|
|
#define JS_STRING_POS_CACHE_MIN_LEN 16
|
|
|
|
typedef enum {
|
|
POS_TYPE_UTF8,
|
|
POS_TYPE_UTF16,
|
|
} StringPosTypeEnum;
|
|
|
|
typedef struct {
|
|
JSValue str; /* JS_NULL or weak reference to a JSString. It
|
|
contains at least JS_STRING_POS_CACHE_MIN_LEN
|
|
bytes and is a non ascii string */
|
|
uint32_t str_pos[2]; /* 0 = UTF-8 pos (in bytes), 1 = UTF-16 pos */
|
|
} JSStringPosCacheEntry;
|
|
|
|
struct JSContext {
|
|
/* memory map:
|
|
Stack
|
|
Free area
|
|
Heap
|
|
JSContext
|
|
*/
|
|
uint8_t *heap_base;
|
|
uint8_t *heap_free; /* first free area */
|
|
uint8_t *stack_top;
|
|
JSValue *stack_bottom; /* sp must always be higher than stack_bottom */
|
|
JSValue *sp; /* current stack pointer */
|
|
JSValue *fp; /* current frame pointer, stack_top if none */
|
|
uint32_t min_free_size; /* min free size between heap_free and the
|
|
bottom of the stack */
|
|
BOOL in_out_of_memory : 8; /* != 0 if generating the out of memory object */
|
|
uint8_t n_rom_atom_tables;
|
|
uint8_t string_pos_cache_counter; /* used for string_pos_cache[] update */
|
|
uint16_t class_count; /* number of classes including user classes */
|
|
int16_t interrupt_counter;
|
|
BOOL current_exception_is_uncatchable : 8;
|
|
struct JSParseState *parse_state; /* != NULL during JS_Eval() */
|
|
int unique_strings_len;
|
|
int js_call_rec_count; /* number of recursing JS_Call() */
|
|
JSGCRef *top_gc_ref; /* used to reference temporary GC roots (stack top) */
|
|
JSGCRef *last_gc_ref; /* used to reference temporary GC roots (list) */
|
|
const JSWord *atom_table; /* constant atom table */
|
|
/* 'n_rom_atom_tables' atom tables from code loaded from rom */
|
|
const JSValueArray *rom_atom_tables[N_ROM_ATOM_TABLES_MAX];
|
|
const JSCFunctionDef *c_function_table;
|
|
const JSCFinalizer *c_finalizer_table;
|
|
uint64_t random_state;
|
|
JSInterruptHandler *interrupt_handler;
|
|
JSWriteFunc *write_func; /* for the various dump functions */
|
|
void *opaque;
|
|
JSValue *class_obj; /* same as class_proto + class_count */
|
|
JSStringPosCacheEntry string_pos_cache[JS_STRING_POS_CACHE_SIZE];
|
|
|
|
/* must only contain JSValue from this point (see JS_GC()) */
|
|
JSValue unique_strings; /* JSValueArray of sorted strings or JS_NULL */
|
|
|
|
JSValue current_exception; /* currently pending exception, must
|
|
come after unique_strings */
|
|
#ifdef DEBUG_GC
|
|
JSValue dummy_block; /* dummy memory block near the start of the memory */
|
|
#endif
|
|
JSValue empty_props; /* empty prop list, for objects with no properties */
|
|
JSValue global_obj;
|
|
JSValue minus_zero; /* minus zero float64 value */
|
|
JSValue class_proto[]; /* prototype for each class (class_count
|
|
element, then class_count elements for
|
|
class_obj */
|
|
};
|
|
|
|
typedef enum {
|
|
JS_VARREF_KIND_ARG, /* var_idx is an argument of the parent function */
|
|
JS_VARREF_KIND_VAR, /* var_idx is a local variable of the parent function */
|
|
JS_VARREF_KIND_VAR_REF, /* var_idx is a var ref of the parent function */
|
|
JS_VARREF_KIND_GLOBAL, /* to debug */
|
|
} JSVarRefKindEnum;
|
|
|
|
typedef struct JSObject JSObject;
|
|
|
|
typedef struct {
|
|
/* string, short integer or JS_UNINITIALIZED if no property. If
|
|
the last property is uninitialized, hash_next = 2 *
|
|
first_free. */
|
|
JSValue key;
|
|
/* JS_PROP_GETSET: JSValueArray of two elements
|
|
JS_PROP_VARREF: JSVarRef */
|
|
JSValue value;
|
|
/* XXX: when JSW = 8, could use 32 bits for hash_next (faster) */
|
|
uint32_t hash_next : 30; /* low bit at zero */
|
|
uint32_t prop_type : 2;
|
|
} JSProperty;
|
|
|
|
typedef struct {
|
|
JSValue func_bytecode; /* JSFunctionBytecode */
|
|
JSValue var_refs[]; /* JSValueArray */
|
|
} JSClosureData;
|
|
|
|
typedef struct {
|
|
uint32_t idx;
|
|
JSValue params; /* optional associated parameters */
|
|
} JSCFunctionData;
|
|
|
|
typedef struct {
|
|
JSValue tab; /* JS_NULL or JSValueArray */
|
|
uint32_t len; /* maximum value: 2^30-1 */
|
|
} JSArrayData;
|
|
|
|
typedef struct {
|
|
JSValue message; /* string or JS_NULL */
|
|
JSValue stack; /* string or JS_NULL */
|
|
} JSErrorData;
|
|
|
|
typedef struct {
|
|
JSValue byte_buffer; /* JSByteBuffer */
|
|
} JSArrayBuffer;
|
|
|
|
typedef struct {
|
|
JSValue buffer; /* corresponding array buffer */
|
|
uint32_t len; /* in elements */
|
|
uint32_t offset; /* in elements */
|
|
} JSTypedArray;
|
|
|
|
typedef struct {
|
|
JSValue source;
|
|
JSValue byte_code;
|
|
int last_index;
|
|
} JSRegExp;
|
|
|
|
typedef struct {
|
|
void *opaque;
|
|
} JSObjectUserData;
|
|
|
|
struct JSObject {
|
|
JS_MB_HEADER;
|
|
JSWord class_id: 8;
|
|
JSWord extra_size: JS_MB_PAD(JS_MTAG_BITS + 8); /* object additional size, in JSValue */
|
|
|
|
JSValue proto; /* JSObject or JS_NULL */
|
|
/* JSValueArray. structure:
|
|
prop_count (number of properties excluding deleted ones)
|
|
hash_mask (= hash_size - 1)
|
|
hash_table[hash_size] (0 = end of list or offset in array)
|
|
JSProperty props[]
|
|
*/
|
|
JSValue props;
|
|
/* number of additional fields depends on the object */
|
|
union {
|
|
JSClosureData closure;
|
|
JSCFunctionData cfunc;
|
|
JSArrayData array;
|
|
JSErrorData error;
|
|
JSArrayBuffer array_buffer;
|
|
JSTypedArray typed_array;
|
|
JSRegExp regexp;
|
|
JSObjectUserData user;
|
|
} u;
|
|
};
|
|
|
|
typedef struct JSFunctionBytecode {
|
|
JS_MB_HEADER;
|
|
JSWord has_arguments : 1; /* only used during parsing */
|
|
JSWord has_local_func_name : 1; /* only used during parsing */
|
|
JSWord has_column : 1; /* column debug info is present */
|
|
/* during parse: variable index + 1 of hoisted function, 0 otherwise */
|
|
JSWord arg_count : 16;
|
|
JSWord dummy: JS_MB_PAD(JS_MTAG_BITS + 3 + 16);
|
|
|
|
JSValue func_name; /* JS_NULL if anonymous function */
|
|
JSValue byte_code; /* JS_NULL if the function is not parsed yet */
|
|
JSValue cpool; /* constant pool */
|
|
JSValue vars; /* only for debug */
|
|
JSValue ext_vars; /* records of (var_name, var_kind (2 bits) var_idx (16 bits)) */
|
|
uint16_t stack_size; /* maximum stack size */
|
|
uint16_t ext_vars_len; /* XXX: only used during parsing */
|
|
JSValue filename; /* filename in which the function is defined */
|
|
JSValue pc2line; /* JSByteArray or JS_NULL if not initialized */
|
|
uint32_t source_pos; /* only used during parsing (XXX: shrink) */
|
|
} JSFunctionBytecode;
|
|
|
|
static JSValue js_resize_value_array(JSContext *ctx, JSValue val, int new_size);
|
|
static int get_mblock_size(const void *ptr);
|
|
static JSValue JS_NewObjectProtoClass(JSContext *ctx, JSValue proto, int class_id, int extra_size);
|
|
static void js_shrink_byte_array(JSContext *ctx, JSValue *pval, int new_size);
|
|
static void build_backtrace(JSContext *ctx, JSValue error_obj,
|
|
const char *filename, int line_num, int col_num, int skip_level);
|
|
static JSValue JS_ToPropertyKey(JSContext *ctx, JSValue val);
|
|
static JSByteArray *js_alloc_byte_array(JSContext *ctx, int size);
|
|
static JSValue js_new_c_function_proto(JSContext *ctx, int func_idx, JSValue proto, BOOL has_params,
|
|
JSValue params);
|
|
static int JS_ToUint8Clamp(JSContext *ctx, int *pres, JSValue val);
|
|
static JSValue js_set_prototype_internal(JSContext *ctx, JSValue obj, JSValue proto);
|
|
static JSValue js_resize_byte_array(JSContext *ctx, JSValue val, int new_size);
|
|
static JSValueArray *js_alloc_props(JSContext *ctx, int n);
|
|
|
|
typedef enum OPCodeFormat {
|
|
#define FMT(f) OP_FMT_ ## f,
|
|
#define DEF(id, size, n_pop, n_push, f)
|
|
#include "mquickjs_opcode.h"
|
|
#undef DEF
|
|
#undef FMT
|
|
} OPCodeFormat;
|
|
|
|
typedef 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 "mquickjs_opcode.h"
|
|
#undef def
|
|
#undef DEF
|
|
#undef FMT
|
|
OP_COUNT,
|
|
} OPCodeEnum;
|
|
|
|
typedef struct {
|
|
#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_pusch items */
|
|
uint8_t n_pop;
|
|
uint8_t n_push;
|
|
uint8_t fmt;
|
|
} JSOpCode;
|
|
|
|
static __maybe_unused const JSOpCode opcode_info[OP_COUNT] = {
|
|
#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 "mquickjs_opcode.h"
|
|
#undef DEF
|
|
#undef FMT
|
|
};
|
|
|
|
#include "mquickjs_atom.h"
|
|
|
|
JSValue *JS_PushGCRef(JSContext *ctx, JSGCRef *ref)
|
|
{
|
|
ref->prev = ctx->top_gc_ref;
|
|
ctx->top_gc_ref = ref;
|
|
ref->val = JS_UNDEFINED;
|
|
return &ref->val;
|
|
}
|
|
|
|
JSValue JS_PopGCRef(JSContext *ctx, JSGCRef *ref)
|
|
{
|
|
ctx->top_gc_ref = ref->prev;
|
|
return ref->val;
|
|
}
|
|
|
|
JSValue *JS_AddGCRef(JSContext *ctx, JSGCRef *ref)
|
|
{
|
|
ref->prev = ctx->last_gc_ref;
|
|
ctx->last_gc_ref = ref;
|
|
ref->val = JS_UNDEFINED;
|
|
return &ref->val;
|
|
}
|
|
|
|
void JS_DeleteGCRef(JSContext *ctx, JSGCRef *ref)
|
|
{
|
|
JSGCRef **pref, *ref1;
|
|
pref = &ctx->last_gc_ref;
|
|
for(;;) {
|
|
ref1 = *pref;
|
|
if (ref1 == NULL)
|
|
abort();
|
|
if (ref1 == ref) {
|
|
*pref = ref1->prev;
|
|
break;
|
|
}
|
|
pref = &ref1->prev;
|
|
}
|
|
}
|
|
|
|
#undef JS_PUSH_VALUE
|
|
#undef JS_POP_VALUE
|
|
|
|
#define JS_PUSH_VALUE(ctx, v) do { \
|
|
v ## _ref.prev = ctx->top_gc_ref; \
|
|
ctx->top_gc_ref = &v ## _ref; \
|
|
v ## _ref.val = v; \
|
|
} while (0)
|
|
|
|
#define JS_POP_VALUE(ctx, v) do { \
|
|
v = v ## _ref.val; \
|
|
ctx->top_gc_ref = v ## _ref.prev; \
|
|
} while (0)
|
|
|
|
static JSValue js_get_atom(JSContext *ctx, int a)
|
|
{
|
|
return JS_VALUE_FROM_PTR(&ctx->atom_table[a]);
|
|
}
|
|
|
|
static force_inline JSValue JS_NewTailCall(int val)
|
|
{
|
|
return JS_VALUE_MAKE_SPECIAL(JS_TAG_EXCEPTION, JS_EX_CALL + val);
|
|
}
|
|
|
|
static inline JS_BOOL JS_IsExceptionOrTailCall(JSValue v)
|
|
{
|
|
return JS_VALUE_GET_SPECIAL_TAG(v) == JS_TAG_EXCEPTION;
|
|
}
|
|
|
|
static int js_get_mtag(void *ptr)
|
|
{
|
|
return ((JSMemBlockHeader *)ptr)->mtag;
|
|
}
|
|
|
|
static int check_free_mem(JSContext *ctx, JSValue *stack_bottom, uint32_t size)
|
|
{
|
|
#ifdef DEBUG_GC
|
|
assert(ctx->sp >= stack_bottom);
|
|
/* don't start the GC before dummy_block is allocated */
|
|
if (JS_IsPtr(ctx->dummy_block)) {
|
|
JS_GC(ctx);
|
|
}
|
|
#endif
|
|
if (((uint8_t *)stack_bottom - ctx->heap_free) < size + ctx->min_free_size) {
|
|
JS_GC(ctx);
|
|
if (((uint8_t *)stack_bottom - ctx->heap_free) < size + ctx->min_free_size) {
|
|
JS_ThrowOutOfMemory(ctx);
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* check that 'len' values can be pushed on the stack. Return 0 if OK,
|
|
-1 if not enough space. May trigger a GC(). */
|
|
int JS_StackCheck(JSContext *ctx, uint32_t len)
|
|
{
|
|
JSValue *new_stack_bottom;
|
|
|
|
len += JS_STACK_SLACK;
|
|
new_stack_bottom = ctx->sp - len;
|
|
if (check_free_mem(ctx, new_stack_bottom, len * sizeof(JSValue)))
|
|
return -1;
|
|
ctx->stack_bottom = new_stack_bottom;
|
|
return 0;
|
|
}
|
|
|
|
static void *js_malloc(JSContext *ctx, uint32_t size, int mtag)
|
|
{
|
|
JSMemBlockHeader *p;
|
|
|
|
if (size == 0)
|
|
return NULL;
|
|
size = (size + JSW - 1) & ~(JSW - 1);
|
|
|
|
if (check_free_mem(ctx, ctx->stack_bottom, size))
|
|
return NULL;
|
|
|
|
p = (JSMemBlockHeader *)ctx->heap_free;
|
|
ctx->heap_free += size;
|
|
|
|
p->mtag = mtag;
|
|
p->gc_mark = 0;
|
|
p->dummy = 0;
|
|
return p;
|
|
}
|
|
|
|
static void *js_mallocz(JSContext *ctx, uint32_t size, int mtag)
|
|
{
|
|
uint8_t *ptr;
|
|
ptr = js_malloc(ctx, size, mtag);
|
|
if (!ptr)
|
|
return NULL;
|
|
if (size > sizeof(uint32_t)) {
|
|
memset(ptr + sizeof(uint32_t), 0, size - sizeof(uint32_t));
|
|
}
|
|
return ptr;
|
|
}
|
|
|
|
/* currently only free the last element */
|
|
static void js_free(JSContext *ctx, void *ptr)
|
|
{
|
|
uint8_t *ptr1;
|
|
if (!ptr)
|
|
return;
|
|
ptr1 = ptr;
|
|
ptr1 += get_mblock_size(ptr1);
|
|
if (ptr1 == ctx->heap_free)
|
|
ctx->heap_free = ptr;
|
|
}
|
|
|
|
/* 'size' is in bytes and must be multiple of JSW and > 0 */
|
|
static void set_free_block(void *ptr, uint32_t size)
|
|
{
|
|
JSFreeBlock *p;
|
|
p = (JSFreeBlock *)ptr;
|
|
p->mtag = JS_MTAG_FREE;
|
|
p->gc_mark = 0;
|
|
p->size = (size - sizeof(JSFreeBlock)) / sizeof(JSWord);
|
|
}
|
|
|
|
/* 'ptr' must be != NULL. new_size must be less or equal to the
|
|
current block size. */
|
|
static void *js_shrink(JSContext *ctx, void *ptr, uint32_t new_size)
|
|
{
|
|
uint32_t old_size;
|
|
uint32_t diff;
|
|
|
|
new_size = (new_size + (JSW - 1)) & ~(JSW - 1);
|
|
|
|
if (new_size == 0) {
|
|
js_free(ctx, ptr);
|
|
return NULL;
|
|
}
|
|
old_size = get_mblock_size(ptr);
|
|
assert(new_size <= old_size);
|
|
diff = old_size - new_size;
|
|
if (diff == 0)
|
|
return ptr;
|
|
set_free_block((uint8_t *)ptr + new_size, diff);
|
|
/* add a new free block after 'ptr' */
|
|
return ptr;
|
|
}
|
|
|
|
JSValue JS_Throw(JSContext *ctx, JSValue obj)
|
|
{
|
|
ctx->current_exception = obj;
|
|
ctx->current_exception_is_uncatchable = FALSE;
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
/* return the byte length. 'buf' must contain UTF8_CHAR_LEN_MAX + 1 bytes */
|
|
static int get_short_string(uint8_t *buf, JSValue val)
|
|
{
|
|
int len;
|
|
len = unicode_to_utf8(buf, JS_VALUE_GET_SPECIAL_VALUE(val));
|
|
buf[len] = '\0';
|
|
return len;
|
|
}
|
|
|
|
/* printf utility */
|
|
|
|
#define PF_ZERO_PAD (1 << 0) /* 0 */
|
|
#define PF_ALT_FORM (1 << 1) /* # */
|
|
#define PF_MARK_POS (1 << 2) /* + */
|
|
#define PF_LEFT_ADJ (1 << 3) /* - */
|
|
#define PF_PAD_POS (1 << 4) /* ' ' */
|
|
#define PF_INT64 (1 << 5) /* l/ll */
|
|
|
|
static BOOL is_digit(int c)
|
|
{
|
|
return (c >= '0' && c <= '9');
|
|
}
|
|
|
|
/* pad with chars 'c' */
|
|
static void pad(JSWriteFunc *write_func, void *opaque, char c,
|
|
int width, int len)
|
|
{
|
|
char buf[16];
|
|
int l;
|
|
if (len >= width)
|
|
return;
|
|
width -= len;
|
|
memset(buf, c, min_int(sizeof(buf), width));
|
|
while (width != 0) {
|
|
l = min_int(width, sizeof(buf));
|
|
write_func(opaque, buf, l);
|
|
width -= l;
|
|
}
|
|
}
|
|
|
|
/* The 'o' format can be used to print a JSValue. Only short int,
|
|
bool, null, undefined and string types are supported. */
|
|
static void js_vprintf(JSWriteFunc *write_func, void *opaque, const char *fmt, va_list ap)
|
|
{
|
|
const char *p;
|
|
int width, prec, flags, c;
|
|
char tmp_buf[32], *buf;
|
|
size_t len;
|
|
|
|
while (*fmt != '\0') {
|
|
p = fmt;
|
|
while (*fmt != '%' && *fmt != '\0')
|
|
fmt++;
|
|
if (fmt > p)
|
|
write_func(opaque, p, fmt - p);
|
|
if (*fmt == '\0')
|
|
break;
|
|
fmt++;
|
|
/* get the flags */
|
|
flags = 0;
|
|
for(;;) {
|
|
c = *fmt;
|
|
if (c == '0') {
|
|
flags |= PF_ZERO_PAD;
|
|
} else if (c == '#') {
|
|
flags |= PF_ALT_FORM;
|
|
} else if (c == '+') {
|
|
flags |= PF_MARK_POS;
|
|
} else if (c == '-') {
|
|
flags |= PF_LEFT_ADJ;
|
|
} else if (c == ' ') {
|
|
flags |= PF_MARK_POS;
|
|
} else {
|
|
break;
|
|
}
|
|
fmt++;
|
|
}
|
|
width = 0;
|
|
if (*fmt == '*') {
|
|
width = va_arg(ap, int);
|
|
} else {
|
|
while (is_digit(*fmt)) {
|
|
width = width * 10 + *fmt - '0';
|
|
fmt++;
|
|
}
|
|
}
|
|
prec = 0;
|
|
if (*fmt == '.') {
|
|
fmt++;
|
|
if (*fmt == '*') {
|
|
prec = va_arg(ap, int);
|
|
} else {
|
|
while (is_digit(*fmt)) {
|
|
prec = prec * 10 + *fmt - '0';
|
|
fmt++;
|
|
}
|
|
}
|
|
}
|
|
/* modifiers */
|
|
for(;;) {
|
|
c = *fmt;
|
|
if (c == 'l') {
|
|
if (sizeof(long) == sizeof(int64_t) || fmt[-1] == 'l')
|
|
flags |= PF_INT64;
|
|
} else
|
|
if (c == 'z' || c == 't') {
|
|
if (sizeof(size_t) == sizeof(uint64_t))
|
|
flags |= PF_INT64;
|
|
} else {
|
|
break;
|
|
}
|
|
fmt++;
|
|
}
|
|
|
|
c = *fmt++;
|
|
/* XXX: not complete, just enough for our needs */
|
|
buf = tmp_buf;
|
|
len = 0;
|
|
switch(c) {
|
|
case '%':
|
|
write_func(opaque, fmt - 1, 1);
|
|
break;
|
|
case 'c':
|
|
buf[0] = va_arg(ap, int);
|
|
len = 1;
|
|
flags &= ~PF_ZERO_PAD;
|
|
break;
|
|
case 's':
|
|
buf = va_arg(ap, char *);
|
|
if (!buf)
|
|
buf = "null";
|
|
len = strlen(buf);
|
|
flags &= ~PF_ZERO_PAD;
|
|
break;
|
|
case 'd':
|
|
if (flags & PF_INT64)
|
|
len = i64toa(buf, va_arg(ap, int64_t));
|
|
else
|
|
len = i32toa(buf, va_arg(ap, int32_t));
|
|
break;
|
|
case 'u':
|
|
if (flags & PF_INT64)
|
|
len = u64toa(buf, va_arg(ap, uint64_t));
|
|
else
|
|
len = u32toa(buf, va_arg(ap, uint32_t));
|
|
break;
|
|
case 'x':
|
|
if (flags & PF_INT64)
|
|
len = u64toa_radix(buf, va_arg(ap, uint64_t), 16);
|
|
else
|
|
len = u64toa_radix(buf, va_arg(ap, uint32_t), 16);
|
|
break;
|
|
case 'p':
|
|
buf[0] = '0';
|
|
buf[1] = 'x';
|
|
len = u64toa_radix(buf + 2, (uintptr_t)va_arg(ap, void *), 16);
|
|
len += 2;
|
|
break;
|
|
case 'o':
|
|
{
|
|
JSValue val = (flags & PF_INT64) ? va_arg(ap, uint64_t) : va_arg(ap, uint32_t);
|
|
if (JS_IsInt(val)) {
|
|
len = i32toa(buf, JS_VALUE_GET_INT(val));
|
|
} else
|
|
#ifdef JS_USE_SHORT_FLOAT
|
|
if (JS_IsShortFloat(val)) {
|
|
/* XXX: print it */
|
|
buf = "[short_float]";
|
|
goto do_strlen;
|
|
} else
|
|
#endif
|
|
if (!JS_IsPtr(val)) {
|
|
switch(JS_VALUE_GET_SPECIAL_TAG(val)) {
|
|
case JS_TAG_NULL:
|
|
buf = "null";
|
|
goto do_strlen;
|
|
case JS_TAG_UNDEFINED:
|
|
buf = "undefined";
|
|
goto do_strlen;
|
|
case JS_TAG_UNINITIALIZED:
|
|
buf = "uninitialized";
|
|
goto do_strlen;
|
|
case JS_TAG_BOOL:
|
|
buf = JS_VALUE_GET_SPECIAL_VALUE(val) ? "true" : "false";
|
|
goto do_strlen;
|
|
case JS_TAG_STRING_CHAR:
|
|
len = get_short_string((uint8_t *)buf, val);
|
|
break;
|
|
default:
|
|
buf = "[tag]";
|
|
goto do_strlen;
|
|
}
|
|
} else {
|
|
void *ptr = JS_VALUE_TO_PTR(val);
|
|
int mtag = ((JSMemBlockHeader *)ptr)->mtag;
|
|
switch(mtag) {
|
|
case JS_MTAG_STRING:
|
|
{
|
|
JSString *p = ptr;
|
|
buf = (char *)p->buf;
|
|
len = p->len;
|
|
}
|
|
break;
|
|
default:
|
|
buf = "[mtag]";
|
|
do_strlen:
|
|
len = strlen(buf);
|
|
break;
|
|
}
|
|
}
|
|
/* remove the trailing '\n' if any (used in error output) */
|
|
if ((flags & PF_ALT_FORM) && len > 0 && buf[len - 1] == '\n')
|
|
len--;
|
|
flags &= ~PF_ZERO_PAD;
|
|
}
|
|
break;
|
|
default:
|
|
goto error;
|
|
}
|
|
if (flags & PF_ZERO_PAD) {
|
|
/* XXX: incorrect with prefix */
|
|
pad(write_func, opaque, '0', width, len);
|
|
} else {
|
|
if (!(flags & PF_LEFT_ADJ))
|
|
pad(write_func, opaque, ' ', width, len);
|
|
}
|
|
write_func(opaque, buf, len);
|
|
if (flags & PF_LEFT_ADJ)
|
|
pad(write_func, opaque, ' ', width, len);
|
|
}
|
|
return;
|
|
error:
|
|
return;
|
|
}
|
|
|
|
/* used for the debug output */
|
|
static void __js_printf_like(2, 3) js_printf(JSContext *ctx,
|
|
const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
js_vprintf(ctx->write_func, ctx->opaque, fmt, ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
static __maybe_unused void js_putchar(JSContext *ctx, uint8_t c)
|
|
{
|
|
ctx->write_func(ctx->opaque, &c, 1);
|
|
}
|
|
|
|
typedef struct {
|
|
char *ptr;
|
|
char *buf_end;
|
|
int len;
|
|
} SNPrintfState;
|
|
|
|
static void snprintf_write_func(void *opaque, const void *buf, size_t buf_len)
|
|
{
|
|
SNPrintfState *s = opaque;
|
|
size_t l;
|
|
s->len += buf_len;
|
|
l = min_size_t(buf_len, s->buf_end - s->ptr);
|
|
if (l != 0) {
|
|
memcpy(s->ptr, buf, l);
|
|
s->ptr += l;
|
|
}
|
|
}
|
|
|
|
static int js_vsnprintf(char *buf, size_t buf_size, const char *fmt, va_list ap)
|
|
{
|
|
SNPrintfState ss, *s = &ss;
|
|
s->ptr = buf;
|
|
s->buf_end = buf + max_size_t(buf_size, 1) - 1;
|
|
s->len = 0;
|
|
js_vprintf(snprintf_write_func, s, fmt, ap);
|
|
if (buf_size > 0)
|
|
*s->ptr = '\0';
|
|
return s->len;
|
|
}
|
|
|
|
static int __maybe_unused __js_printf_like(3, 4) js_snprintf(char *buf, size_t buf_size, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
int ret;
|
|
va_start(ap, fmt);
|
|
ret = js_vsnprintf(buf, buf_size, fmt, ap);
|
|
va_end(ap);
|
|
return ret;
|
|
}
|
|
|
|
JSValue __js_printf_like(3, 4) JS_ThrowError(JSContext *ctx, JSObjectClassEnum error_num,
|
|
const char *fmt, ...)
|
|
{
|
|
JSObject *p;
|
|
va_list ap;
|
|
char buf[128];
|
|
JSValue msg, error_obj;
|
|
JSGCRef msg_ref, error_obj_ref;
|
|
|
|
va_start(ap, fmt);
|
|
js_vsnprintf(buf, sizeof(buf), fmt, ap);
|
|
va_end(ap);
|
|
msg = JS_NewString(ctx, buf);
|
|
|
|
JS_PUSH_VALUE(ctx, msg);
|
|
error_obj = JS_NewObjectProtoClass(ctx, ctx->class_proto[error_num], JS_CLASS_ERROR,
|
|
sizeof(JSErrorData));
|
|
JS_POP_VALUE(ctx, msg);
|
|
if (JS_IsException(error_obj))
|
|
return error_obj;
|
|
|
|
p = JS_VALUE_TO_PTR(error_obj);
|
|
p->u.error.message = msg;
|
|
p->u.error.stack = JS_NULL;
|
|
|
|
/* in case of syntax error, the backtrace is added later */
|
|
if (error_num != JS_CLASS_SYNTAX_ERROR) {
|
|
JS_PUSH_VALUE(ctx, error_obj);
|
|
build_backtrace(ctx, error_obj, NULL, 0, 0, 0);
|
|
JS_POP_VALUE(ctx, error_obj);
|
|
}
|
|
|
|
return JS_Throw(ctx, error_obj);
|
|
}
|
|
|
|
JSValue JS_ThrowOutOfMemory(JSContext *ctx)
|
|
{
|
|
JSValue val;
|
|
if (ctx->in_out_of_memory)
|
|
return JS_Throw(ctx, JS_NULL);
|
|
ctx->in_out_of_memory = TRUE;
|
|
ctx->min_free_size = JS_MIN_CRITICAL_FREE_SIZE;
|
|
val = JS_ThrowInternalError(ctx, "out of memory");
|
|
ctx->in_out_of_memory = FALSE;
|
|
ctx->min_free_size = JS_MIN_FREE_SIZE;
|
|
return val;
|
|
}
|
|
|
|
#define JS_SHORTINT_MIN (-(1 << 30))
|
|
#define JS_SHORTINT_MAX ((1 << 30) - 1)
|
|
|
|
#ifdef JS_USE_SHORT_FLOAT
|
|
|
|
#define JS_FLOAT64_VALUE_EXP_MIN (1023 - 127)
|
|
#define JS_FLOAT64_VALUE_ADDEND ((uint64_t)(JS_FLOAT64_VALUE_EXP_MIN - (JS_TAG_SHORT_FLOAT << 8)) << 52)
|
|
|
|
/* 1 <= n <= 63 */
|
|
static inline uint64_t rotl64(uint64_t a, int n)
|
|
{
|
|
return (a << n) | (a >> (64 - n));
|
|
}
|
|
|
|
static double js_get_short_float(JSValue v)
|
|
{
|
|
return uint64_as_float64(rotl64(v, 60) + JS_FLOAT64_VALUE_ADDEND);
|
|
}
|
|
|
|
static JSValue js_to_short_float(double d)
|
|
{
|
|
return rotl64(float64_as_uint64(d) - JS_FLOAT64_VALUE_ADDEND, 4);
|
|
}
|
|
|
|
#endif /* JS_USE_SHORT_FLOAT */
|
|
|
|
static JSValue js_alloc_float64(JSContext *ctx, double d)
|
|
{
|
|
JSFloat64 *f;
|
|
f = js_malloc(ctx, sizeof(JSFloat64), JS_MTAG_FLOAT64);
|
|
if (!f)
|
|
return JS_EXCEPTION;
|
|
f->u.dval = d;
|
|
return JS_VALUE_FROM_PTR(f);
|
|
}
|
|
|
|
/* create a new float64 value which is known not to be a short integer */
|
|
static JSValue __JS_NewFloat64(JSContext *ctx, double d)
|
|
{
|
|
if (float64_as_uint64(d) == 0x8000000000000000) {
|
|
/* minus zero often happens, so it is worth having a constant
|
|
value */
|
|
return ctx->minus_zero;
|
|
} else
|
|
#ifdef JS_USE_SHORT_FLOAT
|
|
/* Note: this test is false for NaN */
|
|
if (fabs(d) >= 0x1p-127 && fabs(d) <= 0x1p+128) {
|
|
return js_to_short_float(d);
|
|
} else
|
|
#endif
|
|
{
|
|
return js_alloc_float64(ctx, d);
|
|
}
|
|
}
|
|
|
|
static inline JSValue JS_NewShortInt(int32_t val)
|
|
{
|
|
return JS_TAG_INT + (val << 1);
|
|
}
|
|
|
|
#if defined(USE_SOFTFLOAT)
|
|
JSValue JS_NewFloat64(JSContext *ctx, double d)
|
|
{
|
|
uint64_t a, m;
|
|
int e, b, shift;
|
|
JSValue v;
|
|
|
|
a = float64_as_uint64(d);
|
|
if (a == 0) {
|
|
v = JS_NewShortInt(0);
|
|
} else {
|
|
e = (a >> 52) & 0x7ff;
|
|
if (e >= 1023 && e <= 1023 + 30 - 1) {
|
|
m = (a & (((uint64_t)1 << 52) - 1)) | ((uint64_t)1 << 52);
|
|
shift = 52 - (e - 1023);
|
|
/* test if exact integer */
|
|
if ((m & (((uint64_t)1 << shift) - 1)) != 0)
|
|
goto not_int;
|
|
b = m >> shift;
|
|
if (a >> 63)
|
|
b = -b;
|
|
v = JS_NewShortInt(b);
|
|
} else if (a == 0xc1d0000000000000) {
|
|
v = JS_NewShortInt(-(1 << 30));
|
|
} else {
|
|
not_int:
|
|
v = __JS_NewFloat64(ctx, d);
|
|
}
|
|
}
|
|
return v;
|
|
}
|
|
#else
|
|
JSValue JS_NewFloat64(JSContext *ctx, double d)
|
|
{
|
|
int32_t val;
|
|
if (d >= JS_SHORTINT_MIN && d <= JS_SHORTINT_MAX) {
|
|
val = (int32_t)d;
|
|
/* -0 cannot be represented as integer, so we compare the bit
|
|
representation */
|
|
if (float64_as_uint64(d) == float64_as_uint64((double)val))
|
|
return JS_NewShortInt(val);
|
|
}
|
|
return __JS_NewFloat64(ctx, d);
|
|
}
|
|
#endif
|
|
|
|
static inline BOOL int64_is_short_int(int64_t val)
|
|
{
|
|
return val >= JS_SHORTINT_MIN && val <= JS_SHORTINT_MAX;
|
|
}
|
|
|
|
JSValue JS_NewInt64(JSContext *ctx, int64_t val)
|
|
{
|
|
JSValue v;
|
|
if (likely(int64_is_short_int(val))) {
|
|
v = JS_NewShortInt(val);
|
|
} else {
|
|
v = __JS_NewFloat64(ctx, val);
|
|
}
|
|
return v;
|
|
}
|
|
|
|
JSValue JS_NewInt32(JSContext *ctx, int32_t val)
|
|
{
|
|
return JS_NewInt64(ctx, val);
|
|
}
|
|
|
|
JSValue JS_NewUint32(JSContext *ctx, uint32_t val)
|
|
{
|
|
return JS_NewInt64(ctx, val);
|
|
}
|
|
|
|
static BOOL JS_IsPrimitive(JSContext *ctx, JSValue val)
|
|
{
|
|
if (!JS_IsPtr(val)) {
|
|
return JS_VALUE_GET_SPECIAL_TAG(val) != JS_TAG_SHORT_FUNC;
|
|
} else {
|
|
return (js_get_mtag(JS_VALUE_TO_PTR(val)) != JS_MTAG_OBJECT);
|
|
}
|
|
}
|
|
|
|
/* Note: short functions are not considered as objects by this function */
|
|
static BOOL JS_IsObject(JSContext *ctx, JSValue val)
|
|
{
|
|
if (!JS_IsPtr(val)) {
|
|
return FALSE;
|
|
} else {
|
|
JSObject *p = JS_VALUE_TO_PTR(val);
|
|
return (p->mtag == JS_MTAG_OBJECT);
|
|
}
|
|
}
|
|
|
|
/* return -1 if not an object */
|
|
int JS_GetClassID(JSContext *ctx, JSValue val)
|
|
{
|
|
if (!JS_IsPtr(val)) {
|
|
return -1;
|
|
} else {
|
|
JSObject *p = JS_VALUE_TO_PTR(val);
|
|
if (p->mtag != JS_MTAG_OBJECT)
|
|
return -1;
|
|
else
|
|
return p->class_id;
|
|
}
|
|
}
|
|
|
|
void JS_SetOpaque(JSContext *ctx, JSValue val, void *opaque)
|
|
{
|
|
JSObject *p;
|
|
assert(JS_IsPtr(val));
|
|
p = JS_VALUE_TO_PTR(val);
|
|
assert(p->mtag == JS_MTAG_OBJECT);
|
|
assert(p->class_id >= JS_CLASS_USER);
|
|
p->u.user.opaque = opaque;
|
|
}
|
|
|
|
void *JS_GetOpaque(JSContext *ctx, JSValue val)
|
|
{
|
|
JSObject *p;
|
|
assert(JS_IsPtr(val));
|
|
p = JS_VALUE_TO_PTR(val);
|
|
assert(p->mtag == JS_MTAG_OBJECT);
|
|
assert(p->class_id >= JS_CLASS_USER);
|
|
return p->u.user.opaque;
|
|
}
|
|
|
|
static JSObject *js_get_object_class(JSContext *ctx, JSValue val, int class_id)
|
|
{
|
|
if (!JS_IsPtr(val)) {
|
|
return NULL;
|
|
} else {
|
|
JSObject *p = JS_VALUE_TO_PTR(val);
|
|
if (p->mtag != JS_MTAG_OBJECT || p->class_id != class_id)
|
|
return NULL;
|
|
else
|
|
return p;
|
|
}
|
|
}
|
|
|
|
BOOL JS_IsFunction(JSContext *ctx, JSValue val)
|
|
{
|
|
if (!JS_IsPtr(val)) {
|
|
return JS_VALUE_GET_SPECIAL_TAG(val) == JS_TAG_SHORT_FUNC;
|
|
} else {
|
|
JSObject *p = JS_VALUE_TO_PTR(val);
|
|
return (p->mtag == JS_MTAG_OBJECT &&
|
|
(p->class_id == JS_CLASS_CLOSURE ||
|
|
p->class_id == JS_CLASS_C_FUNCTION));
|
|
}
|
|
}
|
|
|
|
static BOOL JS_IsFunctionObject(JSContext *ctx, JSValue val)
|
|
{
|
|
if (!JS_IsPtr(val)) {
|
|
return FALSE;
|
|
} else {
|
|
JSObject *p = JS_VALUE_TO_PTR(val);
|
|
return (p->mtag == JS_MTAG_OBJECT &&
|
|
(p->class_id == JS_CLASS_CLOSURE ||
|
|
p->class_id == JS_CLASS_C_FUNCTION));
|
|
}
|
|
}
|
|
|
|
BOOL JS_IsError(JSContext *ctx, JSValue val)
|
|
{
|
|
if (!JS_IsPtr(val)) {
|
|
return FALSE;
|
|
} else {
|
|
JSObject *p = JS_VALUE_TO_PTR(val);
|
|
return (p->mtag == JS_MTAG_OBJECT && p->class_id == JS_CLASS_ERROR);
|
|
}
|
|
}
|
|
|
|
static force_inline BOOL JS_IsIntOrShortFloat(JSValue val)
|
|
{
|
|
#ifdef JS_USE_SHORT_FLOAT
|
|
return JS_IsInt(val) || JS_IsShortFloat(val);
|
|
#else
|
|
return JS_IsInt(val);
|
|
#endif
|
|
}
|
|
|
|
BOOL JS_IsNumber(JSContext *ctx, JSValue val)
|
|
{
|
|
if (JS_IsIntOrShortFloat(val)) {
|
|
return TRUE;
|
|
} else if (JS_IsPtr(val)) {
|
|
void *ptr = JS_VALUE_TO_PTR(val);
|
|
return (js_get_mtag(ptr) == JS_MTAG_FLOAT64);
|
|
} else {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
BOOL JS_IsString(JSContext *ctx, JSValue val)
|
|
{
|
|
if (!JS_IsPtr(val)) {
|
|
return JS_VALUE_GET_SPECIAL_TAG(val) == JS_TAG_STRING_CHAR;
|
|
} else {
|
|
void *ptr = JS_VALUE_TO_PTR(val);
|
|
return (js_get_mtag(ptr) == JS_MTAG_STRING);
|
|
}
|
|
}
|
|
|
|
static JSString *js_alloc_string(JSContext *ctx, uint32_t buf_len)
|
|
{
|
|
JSString *p;
|
|
|
|
if (buf_len > JS_STRING_LEN_MAX) {
|
|
JS_ThrowInternalError(ctx, "string too long");
|
|
return NULL;
|
|
}
|
|
p = js_malloc(ctx, sizeof(JSString) + buf_len + 1, JS_MTAG_STRING);
|
|
if (!p)
|
|
return NULL;
|
|
p->is_unique = FALSE;
|
|
p->is_ascii = FALSE;
|
|
p->is_numeric = FALSE;
|
|
p->len = buf_len;
|
|
p->buf[buf_len] = '\0';
|
|
return p;
|
|
}
|
|
|
|
/* 0 <= c <= 0x10ffff */
|
|
static inline JSValue JS_NewStringChar(uint32_t c)
|
|
{
|
|
return JS_VALUE_MAKE_SPECIAL(JS_TAG_STRING_CHAR, c);
|
|
}
|
|
|
|
static force_inline int utf8_char_len(int c)
|
|
{
|
|
int l;
|
|
if (c < 0x80) {
|
|
l = 1;
|
|
} else if (c < 0xc0) {
|
|
l = 1;
|
|
} else if (c < 0xe0) {
|
|
l = 2;
|
|
} else if (c < 0xf0) {
|
|
l = 3;
|
|
} else if (c < 0xf8) {
|
|
l = 4;
|
|
} else {
|
|
l = 1;
|
|
}
|
|
return l;
|
|
}
|
|
|
|
static BOOL is_ascii_string(const char *buf, size_t len)
|
|
{
|
|
size_t i;
|
|
for(i = 0; i < len; i++) {
|
|
if ((uint8_t)buf[i] > 0x7f)
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static JSString *get_string_ptr(JSContext *ctx, JSStringCharBuf *buf,
|
|
JSValue val)
|
|
{
|
|
if (JS_VALUE_GET_SPECIAL_TAG(val) == JS_TAG_STRING_CHAR) {
|
|
JSString *p = (JSString *)buf;
|
|
p->is_unique = FALSE;
|
|
p->is_ascii = JS_VALUE_GET_SPECIAL_VALUE(val) <= 0x7f;
|
|
p->len = get_short_string(p->buf, val);
|
|
return p;
|
|
} else {
|
|
return JS_VALUE_TO_PTR(val);
|
|
}
|
|
}
|
|
|
|
static JSValue js_sub_string_utf8(JSContext *ctx, JSValue val,
|
|
uint32_t start0, uint32_t end0)
|
|
{
|
|
JSString *p, *p1;
|
|
int len, start, end, c;
|
|
BOOL start_surrogate, end_surrogate;
|
|
JSStringCharBuf buf;
|
|
JSGCRef val_ref;
|
|
const uint8_t *ptr;
|
|
size_t clen;
|
|
|
|
if (end0 - start0 == 0) {
|
|
return js_get_atom(ctx, JS_ATOM_empty);
|
|
}
|
|
start_surrogate = start0 & 1;
|
|
end_surrogate = end0 & 1;
|
|
start = start0 >> 1;
|
|
end = end0 >> 1;
|
|
len = end - start;
|
|
p1 = get_string_ptr(ctx, &buf, val);
|
|
ptr = p1->buf;
|
|
if (!start_surrogate && !end_surrogate && utf8_char_len(ptr[start]) == len) {
|
|
c = utf8_get(ptr + start, &clen);
|
|
return JS_NewStringChar(c);
|
|
}
|
|
|
|
JS_PUSH_VALUE(ctx, val);
|
|
p = js_alloc_string(ctx, len - start_surrogate + (end_surrogate ? 3 : 0));
|
|
JS_POP_VALUE(ctx, val);
|
|
if (!p)
|
|
return JS_EXCEPTION;
|
|
p1 = get_string_ptr(ctx, &buf, val);
|
|
ptr = p1->buf;
|
|
if (unlikely(start_surrogate || end_surrogate)) {
|
|
uint8_t *q = p->buf;
|
|
p->is_ascii = FALSE;
|
|
if (start_surrogate) {
|
|
c = utf8_get(ptr + start, &clen);
|
|
c = 0xdc00 + ((c - 0x10000) & 0x3ff); /* right surrogate */
|
|
q += unicode_to_utf8(q, c);
|
|
start += 4;
|
|
}
|
|
memcpy(q, ptr + start, end - start);
|
|
q += end - start;
|
|
if (end_surrogate) {
|
|
c = utf8_get(ptr + end, &clen);
|
|
c = 0xd800 + ((c - 0x10000) >> 10); /* left surrogate */
|
|
q += unicode_to_utf8(q, c);
|
|
}
|
|
assert((q - p->buf) == p->len);
|
|
} else {
|
|
p->is_ascii = p1->is_ascii ? TRUE : is_ascii_string((const char *)(ptr + start), len);
|
|
memcpy(p->buf, ptr + start, len);
|
|
}
|
|
return JS_VALUE_FROM_PTR(p);
|
|
}
|
|
|
|
/* Warning: the string must be a valid WTF-8 string (= UTF-8 +
|
|
unpaired surrogates). */
|
|
JSValue JS_NewStringLen(JSContext *ctx, const char *buf, size_t len)
|
|
{
|
|
JSString *p;
|
|
|
|
if (len == 0) {
|
|
return js_get_atom(ctx, JS_ATOM_empty);
|
|
} else {
|
|
if (utf8_char_len(buf[0]) == len) {
|
|
size_t clen;
|
|
int c;
|
|
c = utf8_get((const uint8_t *)buf, &clen);
|
|
return JS_NewStringChar(c);
|
|
}
|
|
}
|
|
p = js_alloc_string(ctx, len);
|
|
if (!p)
|
|
return JS_EXCEPTION;
|
|
p->is_ascii = is_ascii_string((const char *)buf, len);
|
|
memcpy(p->buf, buf, len);
|
|
return JS_VALUE_FROM_PTR(p);
|
|
}
|
|
|
|
/* Warning: the string must be a valid UTF-8 string. */
|
|
JSValue JS_NewString(JSContext *ctx, const char *buf)
|
|
{
|
|
return JS_NewStringLen(ctx, buf, strlen(buf));
|
|
}
|
|
|
|
/* the byte array must be zero terminated. */
|
|
static JSValue js_byte_array_to_string(JSContext *ctx, JSValue val, int len, BOOL is_ascii)
|
|
{
|
|
JSByteArray *arr = JS_VALUE_TO_PTR(val);
|
|
JSString *p;
|
|
|
|
assert(len + 1 <= arr->size);
|
|
if (len == 0) {
|
|
return js_get_atom(ctx, JS_ATOM_empty);
|
|
} else if (utf8_char_len(arr->buf[0]) == len) {
|
|
size_t clen;
|
|
return JS_NewStringChar(utf8_get(arr->buf, &clen));
|
|
} else {
|
|
js_shrink_byte_array(ctx, &val, len + 1);
|
|
p = (JSString *)arr;
|
|
p->mtag = JS_MTAG_STRING;
|
|
p->is_ascii = is_ascii;
|
|
p->is_unique = FALSE;
|
|
p->is_numeric = FALSE;
|
|
p->len = len;
|
|
return val;
|
|
}
|
|
}
|
|
|
|
/* in bytes */
|
|
static __maybe_unused int js_string_byte_len(JSContext *ctx, JSValue val)
|
|
{
|
|
if (JS_VALUE_GET_SPECIAL_TAG(val) == JS_TAG_STRING_CHAR) {
|
|
int c = JS_VALUE_GET_SPECIAL_VALUE(val);
|
|
if (c < 0x80)
|
|
return 1;
|
|
else if (c < 0x800)
|
|
return 2;
|
|
else if (c < 0x10000)
|
|
return 3;
|
|
else
|
|
return 4;
|
|
} else {
|
|
JSString *p = JS_VALUE_TO_PTR(val);
|
|
return p->len;
|
|
}
|
|
}
|
|
|
|
/* assuming that utf8_next() returns 4, validate the corresponding UTF-8 sequence */
|
|
static BOOL is_valid_len4_utf8(const uint8_t *buf)
|
|
{
|
|
return (((buf[0] & 0xf) << 6) | (buf[1] & 0x3f)) >= 0x10;
|
|
}
|
|
|
|
static __maybe_unused void dump_string_pos_cache(JSContext *ctx)
|
|
{
|
|
int i;
|
|
JSStringPosCacheEntry *ce;
|
|
for(i = 0; i < JS_STRING_POS_CACHE_SIZE; i++) {
|
|
ce = &ctx->string_pos_cache[i];
|
|
printf("%d: ", i);
|
|
if (ce->str == JS_NULL) {
|
|
printf("<empty>\n");
|
|
} else {
|
|
JSString *p = JS_VALUE_TO_PTR(ce->str);
|
|
printf(" utf8_pos=%u/%u utf16_pos=%u\n",
|
|
ce->str_pos[POS_TYPE_UTF8], (int)p->len, ce->str_pos[POS_TYPE_UTF16]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* an UTF-8 position is the byte position multiplied by 2. One is
|
|
added when the corresponding UTF-16 character represents the right
|
|
surrogate if the code is >= 0x10000.
|
|
*/
|
|
static uint32_t js_string_convert_pos(JSContext *ctx, JSValue val, uint32_t pos,
|
|
StringPosTypeEnum pos_type)
|
|
{
|
|
JSStringCharBuf buf;
|
|
JSString *p;
|
|
size_t i, clen, len, start;
|
|
uint32_t d_min, d, j;
|
|
JSStringPosCacheEntry *ce, *ce1;
|
|
uint32_t surrogate_flag, has_surrogate, limit;
|
|
int ce_idx;
|
|
|
|
p = get_string_ptr(ctx, &buf, val);
|
|
len = p->len;
|
|
if (p->is_ascii) {
|
|
if (pos_type == POS_TYPE_UTF8)
|
|
return min_int(len, pos / 2);
|
|
else
|
|
return min_int(len, pos) * 2;
|
|
}
|
|
|
|
if (pos_type == POS_TYPE_UTF8) {
|
|
has_surrogate = pos & 1;
|
|
pos >>= 1;
|
|
} else {
|
|
has_surrogate = 0;
|
|
}
|
|
|
|
ce = NULL;
|
|
if (len < JS_STRING_POS_CACHE_MIN_LEN) {
|
|
j = 0;
|
|
i = 0;
|
|
goto uncached;
|
|
}
|
|
|
|
d_min = pos;
|
|
for(ce_idx = 0; ce_idx < JS_STRING_POS_CACHE_SIZE; ce_idx++) {
|
|
ce1 = &ctx->string_pos_cache[ce_idx];
|
|
if (ce1->str == val) {
|
|
d = ce1->str_pos[pos_type];
|
|
d = d >= pos ? d - pos : pos - d;
|
|
if (d < d_min) {
|
|
d_min = d;
|
|
ce = ce1;
|
|
}
|
|
}
|
|
}
|
|
if (!ce) {
|
|
/* "random" replacement */
|
|
ce = &ctx->string_pos_cache[ctx->string_pos_cache_counter];
|
|
if (++ctx->string_pos_cache_counter == JS_STRING_POS_CACHE_SIZE)
|
|
ctx->string_pos_cache_counter = 0;
|
|
ce->str = val;
|
|
ce->str_pos[POS_TYPE_UTF8] = 0;
|
|
ce->str_pos[POS_TYPE_UTF16] = 0;
|
|
}
|
|
|
|
i = ce->str_pos[POS_TYPE_UTF8];
|
|
j = ce->str_pos[POS_TYPE_UTF16];
|
|
if (ce->str_pos[pos_type] <= pos) {
|
|
uncached:
|
|
surrogate_flag = 0;
|
|
if (pos_type == POS_TYPE_UTF8) {
|
|
limit = INT32_MAX;
|
|
len = pos;
|
|
} else {
|
|
limit = pos;
|
|
}
|
|
for(; i < len; i += clen) {
|
|
if (j == limit)
|
|
break;
|
|
clen = utf8_char_len(p->buf[i]);
|
|
if (clen == 4 && is_valid_len4_utf8(p->buf + i)) {
|
|
if ((j + 1) == limit) {
|
|
surrogate_flag = 1;
|
|
break;
|
|
}
|
|
j += 2;
|
|
} else {
|
|
j++;
|
|
}
|
|
}
|
|
} else {
|
|
surrogate_flag = 0;
|
|
if (pos_type == POS_TYPE_UTF8) {
|
|
start = pos;
|
|
limit = INT32_MAX;
|
|
} else {
|
|
limit = pos;
|
|
start = 0;
|
|
}
|
|
while (i > start) {
|
|
size_t i0 = i;
|
|
i--;
|
|
while ((p->buf[i] & 0xc0) == 0x80)
|
|
i--;
|
|
clen = i0 - i;
|
|
if (clen == 4 && is_valid_len4_utf8(p->buf + i)) {
|
|
j -= 2;
|
|
if ((j + 1) == limit) {
|
|
surrogate_flag = 1;
|
|
break;
|
|
}
|
|
} else {
|
|
j--;
|
|
}
|
|
if (j == limit)
|
|
break;
|
|
}
|
|
}
|
|
if (ce) {
|
|
ce->str_pos[POS_TYPE_UTF8] = i;
|
|
ce->str_pos[POS_TYPE_UTF16] = j;
|
|
}
|
|
if (pos_type == POS_TYPE_UTF8)
|
|
return j + has_surrogate;
|
|
else
|
|
return i * 2 + surrogate_flag;
|
|
}
|
|
|
|
static uint32_t js_string_utf16_to_utf8_pos(JSContext *ctx, JSValue val, uint32_t utf16_pos)
|
|
{
|
|
return js_string_convert_pos(ctx, val, utf16_pos, POS_TYPE_UTF16);
|
|
}
|
|
|
|
static uint32_t js_string_utf8_to_utf16_pos(JSContext *ctx, JSValue val, uint32_t utf8_pos)
|
|
{
|
|
return js_string_convert_pos(ctx, val, utf8_pos, POS_TYPE_UTF8);
|
|
}
|
|
|
|
/* Testing the third byte is not needed as the UTF-8 encoding must be
|
|
correct */
|
|
static BOOL is_utf8_left_surrogate(const uint8_t *p)
|
|
{
|
|
return p[0] == 0xed && (p[1] >= 0xa0 && p[1] <= 0xaf);
|
|
}
|
|
|
|
static BOOL is_utf8_right_surrogate(const uint8_t *p)
|
|
{
|
|
return p[0] == 0xed && (p[1] >= 0xb0 && p[1] <= 0xbf);
|
|
}
|
|
|
|
typedef struct {
|
|
JSGCRef buffer_ref; /* string, JSByteBuffer or JS_EXCEPTION */
|
|
int len; /* current string length (in bytes) */
|
|
BOOL is_ascii;
|
|
} StringBuffer;
|
|
|
|
/* return 0 if OK, -1 in case of exception (exception possible if len > 0) */
|
|
static int string_buffer_push(JSContext *ctx, StringBuffer *s, int len)
|
|
{
|
|
s->len = 0;
|
|
s->is_ascii = TRUE;
|
|
if (len > 0) {
|
|
JSByteArray *arr;
|
|
arr = js_alloc_byte_array(ctx, len);
|
|
if (!arr)
|
|
return -1;
|
|
s->buffer_ref.val = JS_VALUE_FROM_PTR(arr);
|
|
} else {
|
|
s->buffer_ref.val = js_get_atom(ctx, JS_ATOM_empty);
|
|
}
|
|
s->buffer_ref.prev = ctx->top_gc_ref;
|
|
ctx->top_gc_ref = &s->buffer_ref;
|
|
return 0;
|
|
}
|
|
|
|
/* val2 must be a string. Return 0 if OK, -1 in case of exception */
|
|
static int string_buffer_concat_str(JSContext *ctx, StringBuffer *s, JSValue val2)
|
|
{
|
|
JSStringCharBuf buf1, buf2;
|
|
JSByteArray *arr;
|
|
JSString *p1, *p2;
|
|
int len, len1, len2;
|
|
JSValue val1;
|
|
uint8_t *q;
|
|
|
|
if (JS_IsException(s->buffer_ref.val))
|
|
return -1;
|
|
p2 = get_string_ptr(ctx, &buf2, val2);
|
|
len2 = p2->len;
|
|
if (len2 == 0)
|
|
return 0;
|
|
if (JS_IsString(ctx, s->buffer_ref.val)) {
|
|
p1 = get_string_ptr(ctx, &buf1, s->buffer_ref.val);
|
|
len1 = p1->len;
|
|
if (len1 == 0) {
|
|
/* empty string in buffer: just keep 'val2' */
|
|
s->buffer_ref.val = val2;
|
|
return 0;
|
|
}
|
|
arr = NULL;
|
|
val1 = s->buffer_ref.val;
|
|
s->buffer_ref.val = JS_NULL;
|
|
} else {
|
|
arr = JS_VALUE_TO_PTR(s->buffer_ref.val);
|
|
len1 = s->len;
|
|
val1 = JS_NULL;
|
|
}
|
|
|
|
len = len1 + len2;
|
|
if (len > JS_STRING_LEN_MAX) {
|
|
s->buffer_ref.val = JS_ThrowInternalError(ctx, "string too long");
|
|
return -1;
|
|
}
|
|
|
|
if (!arr || (len + 1) > arr->size) {
|
|
JSGCRef val1_ref, val2_ref;
|
|
|
|
JS_PUSH_VALUE(ctx, val1);
|
|
JS_PUSH_VALUE(ctx, val2);
|
|
s->buffer_ref.val = js_resize_byte_array(ctx, s->buffer_ref.val, len + 1);
|
|
JS_POP_VALUE(ctx, val2);
|
|
JS_POP_VALUE(ctx, val1);
|
|
if (JS_IsException(s->buffer_ref.val))
|
|
return -1;
|
|
arr = JS_VALUE_TO_PTR(s->buffer_ref.val);
|
|
if (val1 != JS_NULL) {
|
|
p1 = get_string_ptr(ctx, &buf1, val1);
|
|
s->is_ascii = p1->is_ascii;
|
|
memcpy(arr->buf, p1->buf, len1);
|
|
}
|
|
p2 = get_string_ptr(ctx, &buf2, val2);
|
|
}
|
|
|
|
q = arr->buf + len1;
|
|
if (len2 >= 3 && unlikely(is_utf8_right_surrogate(p2->buf)) &&
|
|
len1 >= 3 && is_utf8_left_surrogate(q - 3)) {
|
|
size_t clen;
|
|
int c;
|
|
/* contract the two surrogates to 4 bytes */
|
|
c = (utf8_get(q - 3, &clen) & 0x3ff) << 10;
|
|
c |= (utf8_get(p2->buf, &clen) & 0x3ff);
|
|
c += 0x10000;
|
|
len -= 2;
|
|
len2 -= 3;
|
|
q -= 3;
|
|
q += unicode_to_utf8(q, c);
|
|
s->is_ascii = FALSE;
|
|
}
|
|
memcpy(q, p2->buf + p2->len - len2, len2);
|
|
s->len = len;
|
|
s->is_ascii &= p2->is_ascii;
|
|
return 0;
|
|
}
|
|
|
|
/* 'str' must be a string */
|
|
static int string_buffer_concat_utf8(JSContext *ctx, StringBuffer *s, JSValue str,
|
|
uint32_t start, uint32_t end)
|
|
{
|
|
JSValue val2;
|
|
|
|
if (end <= start)
|
|
return 0;
|
|
/* XXX: avoid explicitly constructing the substring */
|
|
val2 = js_sub_string_utf8(ctx, str, start, end);
|
|
if (JS_IsException(val2)) {
|
|
s->buffer_ref.val = JS_EXCEPTION;
|
|
return -1;
|
|
}
|
|
return string_buffer_concat_str(ctx, s, val2);
|
|
}
|
|
|
|
static int string_buffer_concat_utf16(JSContext *ctx, StringBuffer *s, JSValue str,
|
|
uint32_t start, uint32_t end)
|
|
{
|
|
uint32_t start_utf8, end_utf8;
|
|
if (end <= start)
|
|
return 0;
|
|
start_utf8 = js_string_utf16_to_utf8_pos(ctx, str, start);
|
|
end_utf8 = js_string_utf16_to_utf8_pos(ctx, str, end);
|
|
return string_buffer_concat_utf8(ctx, s, str, start_utf8, end_utf8);
|
|
}
|
|
|
|
static int string_buffer_concat(JSContext *ctx, StringBuffer *s, JSValue val2)
|
|
{
|
|
val2 = JS_ToString(ctx, val2);
|
|
if (JS_IsException(val2)) {
|
|
s->buffer_ref.val = JS_EXCEPTION;
|
|
return -1;
|
|
}
|
|
return string_buffer_concat_str(ctx, s, val2);
|
|
}
|
|
|
|
/* XXX: could optimize */
|
|
static int string_buffer_putc(JSContext *ctx, StringBuffer *s, int c)
|
|
{
|
|
return string_buffer_concat_str(ctx, s, JS_NewStringChar(c));
|
|
}
|
|
|
|
static int string_buffer_puts(JSContext *ctx, StringBuffer *s, const char *str)
|
|
{
|
|
JSValue val;
|
|
|
|
/* XXX: avoid this allocation */
|
|
val = JS_NewString(ctx, str);
|
|
if (JS_IsException(val))
|
|
return -1;
|
|
return string_buffer_concat_str(ctx, s, val);
|
|
}
|
|
|
|
static JSValue string_buffer_pop(JSContext *ctx, StringBuffer *s)
|
|
{
|
|
JSValue res;
|
|
if (JS_IsException(s->buffer_ref.val) ||
|
|
JS_IsString(ctx, s->buffer_ref.val)) {
|
|
res = s->buffer_ref.val;
|
|
} else {
|
|
if (s->len != 0) {
|
|
/* add the trailing '\0' */
|
|
JSByteArray *arr = JS_VALUE_TO_PTR(s->buffer_ref.val);
|
|
arr->buf[s->len] = '\0';
|
|
}
|
|
res = js_byte_array_to_string(ctx, s->buffer_ref.val, s->len, s->is_ascii);
|
|
}
|
|
ctx->top_gc_ref = s->buffer_ref.prev;
|
|
return res;
|
|
}
|
|
|
|
/* val1 and val2 must be strings or exception */
|
|
static JSValue JS_ConcatString(JSContext *ctx, JSValue val1, JSValue val2)
|
|
{
|
|
StringBuffer b_s, *b = &b_s;
|
|
|
|
if (JS_IsException(val1) ||
|
|
JS_IsException(val2))
|
|
return JS_EXCEPTION;
|
|
|
|
string_buffer_push(ctx, b, 0);
|
|
string_buffer_concat_str(ctx, b, val1); /* no memory allocation */
|
|
string_buffer_concat_str(ctx, b, val2);
|
|
return string_buffer_pop(ctx, b);
|
|
}
|
|
|
|
static BOOL js_string_eq(JSContext *ctx, JSValue val1, JSValue val2)
|
|
{
|
|
JSStringCharBuf buf1, buf2;
|
|
JSString *p1, *p2;
|
|
|
|
p1 = get_string_ptr(ctx, &buf1, val1);
|
|
p2 = get_string_ptr(ctx, &buf2, val2);
|
|
if (p1->len != p2->len)
|
|
return FALSE;
|
|
return !memcmp(p1->buf, p2->buf, p1->len);
|
|
}
|
|
|
|
/* Return the unicode character containing the byte at position
|
|
'i'. Return -1 in case of error. */
|
|
static int string_get_cp(const uint8_t *p)
|
|
{
|
|
size_t clen;
|
|
while ((*p & 0xc0) == 0x80)
|
|
p--;
|
|
return utf8_get(p, &clen);
|
|
}
|
|
|
|
static int js_string_compare(JSContext *ctx, JSValue val1, JSValue val2)
|
|
{
|
|
JSStringCharBuf buf1, buf2;
|
|
int len, i, res;
|
|
JSString *p1, *p2;
|
|
|
|
p1 = get_string_ptr(ctx, &buf1, val1);
|
|
p2 = get_string_ptr(ctx, &buf2, val2);
|
|
len = min_int(p1->len, p2->len);
|
|
for(i = 0; i < len; i++) {
|
|
if (p1->buf[i] != p2->buf[i])
|
|
break;
|
|
}
|
|
if (i != len) {
|
|
int c1, c2;
|
|
/* if valid UTF-8, the strings cannot be equal at this point */
|
|
/* Note: UTF-16 does not preserve unicode order like UTF-8 */
|
|
c1 = string_get_cp(p1->buf + i);
|
|
c2 = string_get_cp(p2->buf + i);
|
|
if ((c1 < 0x10000 && c2 < 0x10000) ||
|
|
(c1 >= 0x10000 && c2 >= 0x10000)) {
|
|
if (c1 < c2)
|
|
res = -1;
|
|
else
|
|
res = 1;
|
|
} else if (c1 < 0x10000) {
|
|
/* p1 < p2 if same first UTF-16 char */
|
|
c2 = 0xd800 + ((c2 - 0x10000) >> 10);
|
|
if (c1 <= c2)
|
|
res = -1;
|
|
else
|
|
res = 1;
|
|
} else {
|
|
/* p1 > p2 if same first UTF-16 char */
|
|
c1 = 0xd800 + ((c1 - 0x10000) >> 10);
|
|
if (c1 < c2)
|
|
res = -1;
|
|
else
|
|
res = 1;
|
|
}
|
|
} else {
|
|
if (p1->len == p2->len)
|
|
res = 0;
|
|
else if (p1->len < p2->len)
|
|
res = -1;
|
|
else
|
|
res = 1;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
/* return the string length in UTF16 characters. 'val' must be a
|
|
string char or a string */
|
|
static int js_string_len(JSContext *ctx, JSValue val)
|
|
{
|
|
if (JS_VALUE_GET_SPECIAL_TAG(val) == JS_TAG_STRING_CHAR) {
|
|
return JS_VALUE_GET_SPECIAL_VALUE(val) >= 0x10000 ? 2 : 1;
|
|
} else {
|
|
JSString *p;
|
|
p = JS_VALUE_TO_PTR(val);
|
|
if (p->is_ascii)
|
|
return p->len;
|
|
else
|
|
return js_string_utf8_to_utf16_pos(ctx, val, p->len * 2);
|
|
}
|
|
}
|
|
|
|
/* return the UTF-16 code or the unicode character at a given UTF-8
|
|
position or -1 if outside the string */
|
|
static int string_getcp(JSContext *ctx, JSValue str, uint32_t utf16_pos, BOOL is_codepoint)
|
|
{
|
|
JSString *p;
|
|
JSStringCharBuf buf;
|
|
uint32_t surrogate_flag, c, utf8_pos;
|
|
size_t clen;
|
|
|
|
utf8_pos = js_string_utf16_to_utf8_pos(ctx, str, utf16_pos);
|
|
surrogate_flag = utf8_pos & 1;
|
|
utf8_pos >>= 1;
|
|
p = get_string_ptr(ctx, &buf, str);
|
|
if (utf8_pos >= p->len)
|
|
return -1;
|
|
c = utf8_get(p->buf + utf8_pos, &clen);
|
|
if (c < 0x10000 || (!surrogate_flag && is_codepoint)) {
|
|
return c;
|
|
} else {
|
|
c -= 0x10000;
|
|
if (!surrogate_flag)
|
|
return 0xd800 + (c >> 10); /* left surrogate */
|
|
else
|
|
return 0xdc00 + (c & 0x3ff); /* right surrogate */
|
|
}
|
|
}
|
|
|
|
static int string_getc(JSContext *ctx, JSValue str, uint32_t utf16_pos)
|
|
{
|
|
return string_getcp(ctx, str, utf16_pos, FALSE);
|
|
}
|
|
|
|
/* precondition: 0 <= start <= end <= string length */
|
|
static JSValue js_sub_string(JSContext *ctx, JSValue val, int start, int end)
|
|
{
|
|
uint32_t start_utf8, end_utf8;
|
|
|
|
if (end <= start)
|
|
return js_get_atom(ctx, JS_ATOM_empty);
|
|
start_utf8 = js_string_utf16_to_utf8_pos(ctx, val, start);
|
|
end_utf8 = js_string_utf16_to_utf8_pos(ctx, val, end);
|
|
return js_sub_string_utf8(ctx, val, start_utf8, end_utf8);
|
|
}
|
|
|
|
static inline int is_num(int c)
|
|
{
|
|
return c >= '0' && c <= '9';
|
|
}
|
|
|
|
/* return TRUE if the property 'val' represents a numeric property. -1
|
|
is returned in case of exception. 'val' must be a string. It is
|
|
assumed that NaN and infinities have already been handled. */
|
|
static int js_is_numeric_string(JSContext *ctx, JSValue val)
|
|
{
|
|
int c, len;
|
|
double d;
|
|
const char *r, *q;
|
|
JSString *p;
|
|
JSByteArray *tmp_arr;
|
|
JSGCRef val_ref;
|
|
char buf[32]; /* enough for js_dtoa() */
|
|
|
|
p = JS_VALUE_TO_PTR(val);
|
|
/* the fast case is when the string is not a number */
|
|
if (p->len == 0 || !p->is_ascii)
|
|
return FALSE;
|
|
q = (const char *)p->buf;
|
|
c = *q;
|
|
if (c == '-') {
|
|
if (p->len == 1)
|
|
return FALSE;
|
|
q++;
|
|
c = *q;
|
|
}
|
|
if (!is_num(c))
|
|
return FALSE;
|
|
|
|
JS_PUSH_VALUE(ctx, val);
|
|
tmp_arr = js_alloc_byte_array(ctx, max_int(sizeof(JSATODTempMem),
|
|
sizeof(JSDTOATempMem)));
|
|
JS_POP_VALUE(ctx, val);
|
|
if (!tmp_arr)
|
|
return -1;
|
|
p = JS_VALUE_TO_PTR(val);
|
|
d = js_atod((char *)p->buf, &r, 10, 0, (JSATODTempMem *)tmp_arr->buf);
|
|
if ((r - (char *)p->buf) != p->len) {
|
|
js_free(ctx, tmp_arr);
|
|
return FALSE;
|
|
}
|
|
len = js_dtoa(buf, d, 10, 0, JS_DTOA_FORMAT_FREE, (JSDTOATempMem *)tmp_arr->buf);
|
|
js_free(ctx, tmp_arr);
|
|
return (p->len == len && !memcmp(buf, p->buf, len));
|
|
}
|
|
|
|
/* return JS_NULL if not found */
|
|
static JSValue find_atom(JSContext *ctx, int *pidx, const JSValueArray *arr, int len, JSValue val)
|
|
{
|
|
int a, b, m, r;
|
|
JSValue val1;
|
|
|
|
a = 0;
|
|
b = len - 1;
|
|
while (a <= b) {
|
|
m = (a + b) >> 1;
|
|
val1 = arr->arr[m];
|
|
r = js_string_compare(ctx, val, val1);
|
|
if (r == 0) {
|
|
/* found */
|
|
*pidx = m;
|
|
return val1;
|
|
} else if (r < 0) {
|
|
b = m - 1;
|
|
} else {
|
|
a = m + 1;
|
|
}
|
|
}
|
|
*pidx = a;
|
|
return JS_NULL;
|
|
}
|
|
|
|
/* if 'val' is not a string, it is returned */
|
|
/* XXX: use hash table */
|
|
static JSValue JS_MakeUniqueString(JSContext *ctx, JSValue val)
|
|
{
|
|
JSString *p;
|
|
int a, is_numeric, i;
|
|
JSValueArray *arr;
|
|
const JSValueArray *arr1;
|
|
JSValue val1, new_tab;
|
|
JSGCRef val_ref;
|
|
|
|
if (!JS_IsPtr(val))
|
|
return val;
|
|
p = JS_VALUE_TO_PTR(val);
|
|
if (p->mtag != JS_MTAG_STRING || p->is_unique)
|
|
return val;
|
|
|
|
/* not unique: find it in the ROM or RAM sorted unique string table */
|
|
for(i = 0; i < ctx->n_rom_atom_tables; i++) {
|
|
arr1 = ctx->rom_atom_tables[i];
|
|
if (arr1) {
|
|
val1 = find_atom(ctx, &a, arr1, arr1->size, val);
|
|
if (!JS_IsNull(val1))
|
|
return val1;
|
|
}
|
|
}
|
|
|
|
arr = JS_VALUE_TO_PTR( ctx->unique_strings);
|
|
val1 = find_atom(ctx, &a, arr, ctx->unique_strings_len, val);
|
|
if (!JS_IsNull(val1))
|
|
return val1;
|
|
|
|
JS_PUSH_VALUE(ctx, val);
|
|
is_numeric = js_is_numeric_string(ctx, val);
|
|
JS_POP_VALUE(ctx, val);
|
|
if (is_numeric < 0)
|
|
return JS_EXCEPTION;
|
|
|
|
/* not found: add it in the table */
|
|
JS_PUSH_VALUE(ctx, val);
|
|
new_tab = js_resize_value_array(ctx, ctx->unique_strings,
|
|
ctx->unique_strings_len + 1);
|
|
JS_POP_VALUE(ctx, val);
|
|
if (JS_IsException(new_tab))
|
|
return JS_EXCEPTION;
|
|
ctx->unique_strings = new_tab;
|
|
arr = JS_VALUE_TO_PTR( ctx->unique_strings);
|
|
memmove(&arr->arr[a + 1], &arr->arr[a],
|
|
sizeof(arr->arr[0]) * (ctx->unique_strings_len - a));
|
|
arr->arr[a] = val;
|
|
p = JS_VALUE_TO_PTR(val);
|
|
p->is_unique = TRUE;
|
|
p->is_numeric = is_numeric;
|
|
ctx->unique_strings_len++;
|
|
return val;
|
|
}
|
|
|
|
static int JS_ToBool(JSContext *ctx, JSValue val)
|
|
{
|
|
if (JS_IsInt(val)) {
|
|
return JS_VALUE_GET_INT(val) != 0;
|
|
} else
|
|
#ifdef JS_USE_SHORT_FLOAT
|
|
if (JS_IsShortFloat(val)) {
|
|
double d;
|
|
d = js_get_short_float(val);
|
|
return !isnan(d) && d != 0;
|
|
} else
|
|
#endif
|
|
if (!JS_IsPtr(val)) {
|
|
switch(JS_VALUE_GET_SPECIAL_TAG(val)) {
|
|
case JS_TAG_BOOL:
|
|
case JS_TAG_NULL:
|
|
case JS_TAG_UNDEFINED:
|
|
return JS_VALUE_GET_SPECIAL_VALUE(val);
|
|
case JS_TAG_SHORT_FUNC:
|
|
case JS_TAG_STRING_CHAR:
|
|
return TRUE;
|
|
default:
|
|
return FALSE;
|
|
}
|
|
} else {
|
|
JSMemBlockHeader *h = JS_VALUE_TO_PTR(val);
|
|
switch(h->mtag) {
|
|
case JS_MTAG_STRING:
|
|
{
|
|
JSString *p = (JSString *)h;
|
|
return p->len != 0;
|
|
}
|
|
case JS_MTAG_FLOAT64:
|
|
{
|
|
JSFloat64 *p = (JSFloat64 *)h;
|
|
return !isnan(p->u.dval) && p->u.dval != 0;
|
|
}
|
|
default:
|
|
case JS_MTAG_OBJECT:
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* plen can be NULL. No memory allocation is done if 'val' already is
|
|
a string. */
|
|
const char *JS_ToCStringLen(JSContext *ctx, size_t *plen, JSValue val,
|
|
JSCStringBuf *buf)
|
|
{
|
|
const char *p;
|
|
int len;
|
|
|
|
val = JS_ToString(ctx, val);
|
|
if (JS_IsException(val))
|
|
return NULL;
|
|
if (JS_VALUE_GET_SPECIAL_TAG(val) == JS_TAG_STRING_CHAR) {
|
|
len = get_short_string(buf->buf, val);
|
|
p = (const char *)buf->buf;
|
|
} else {
|
|
JSString *r;
|
|
r = JS_VALUE_TO_PTR(val);
|
|
p = (const char *)r->buf;
|
|
len = r->len;
|
|
}
|
|
if (plen)
|
|
*plen = len;
|
|
return p;
|
|
}
|
|
|
|
const char *JS_ToCString(JSContext *ctx, JSValue val, JSCStringBuf *buf)
|
|
{
|
|
return JS_ToCStringLen(ctx, NULL, val, buf);
|
|
}
|
|
|
|
JSValue JS_GetException(JSContext *ctx)
|
|
{
|
|
JSValue obj;
|
|
obj = ctx->current_exception;
|
|
ctx->current_exception = JS_UNDEFINED;
|
|
return obj;
|
|
}
|
|
|
|
static JSValue JS_ToStringCheckObject(JSContext *ctx, JSValue val)
|
|
{
|
|
if (val == JS_NULL || val == JS_UNDEFINED)
|
|
return JS_ThrowTypeError(ctx, "null or undefined are forbidden");
|
|
return JS_ToString(ctx, val);
|
|
}
|
|
|
|
static JSValue JS_ThrowTypeErrorNotAnObject(JSContext *ctx)
|
|
{
|
|
return JS_ThrowTypeError(ctx, "not an object");
|
|
}
|
|
|
|
/* 'val' must be a string. return TRUE if the string represents a
|
|
short integer */
|
|
static inline BOOL is_num_string(JSContext *ctx, int32_t *pval, JSValue val)
|
|
{
|
|
JSStringCharBuf buf;
|
|
uint32_t n;
|
|
uint64_t n64;
|
|
JSString *p1;
|
|
int c, is_neg;
|
|
const uint8_t *p, *p_end;
|
|
|
|
p1 = get_string_ptr(ctx, &buf, val);
|
|
if (p1->len == 0 || p1->len > 11 || !p1->is_ascii)
|
|
return FALSE;
|
|
p = p1->buf;
|
|
p_end = p + p1->len;
|
|
c = *p++;
|
|
is_neg = 0;
|
|
if (c == '-') {
|
|
if (p >= p_end)
|
|
return FALSE;
|
|
is_neg = 1;
|
|
c = *p++;
|
|
}
|
|
if (!is_num(c))
|
|
return FALSE;
|
|
if (c == '0') {
|
|
if (p != p_end || is_neg)
|
|
return FALSE;
|
|
n = 0;
|
|
} else {
|
|
n = c - '0';
|
|
while (p < p_end) {
|
|
c = *p++;
|
|
if (!is_num(c))
|
|
return FALSE;
|
|
/* XXX: simplify ? */
|
|
n64 = (uint64_t)n * 10 + (c - '0');
|
|
if (n64 > (JS_SHORTINT_MAX + is_neg))
|
|
return FALSE;
|
|
n = n64;
|
|
}
|
|
if (is_neg)
|
|
n = -n;
|
|
}
|
|
*pval = n;
|
|
return TRUE;
|
|
}
|
|
|
|
/* return TRUE if the property 'val' represent a numeric property. It
|
|
is assumed that the shortint case has been tested before */
|
|
static BOOL JS_IsNumericProperty(JSContext *ctx, JSValue val)
|
|
{
|
|
JSString *p;
|
|
if (!JS_IsPtr(val))
|
|
return FALSE; /* JS_TAG_STRING_CHAR */
|
|
p = JS_VALUE_TO_PTR(val);
|
|
return p->is_numeric;
|
|
}
|
|
|
|
static JSValueArray *js_alloc_value_array(JSContext *ctx, int init_base, int new_size)
|
|
{
|
|
JSValueArray *arr;
|
|
int i;
|
|
|
|
if (new_size > JS_VALUE_ARRAY_SIZE_MAX) {
|
|
JS_ThrowOutOfMemory(ctx);
|
|
return NULL;
|
|
}
|
|
arr = js_malloc(ctx, sizeof(JSValueArray) + new_size * sizeof(JSValue), JS_MTAG_VALUE_ARRAY);
|
|
if (!arr)
|
|
return NULL;
|
|
arr->size = new_size;
|
|
for(i = init_base; i < new_size; i++)
|
|
arr->arr[i] = JS_UNDEFINED;
|
|
return arr;
|
|
}
|
|
|
|
/* val can be JS_NULL (zero size). 'prop_base' is non zero only when
|
|
* resizing the property arrays so that the property array has a size
|
|
* which is a multiple of 3 */
|
|
static JSValue js_resize_value_array2(JSContext *ctx, JSValue val, int new_size, int prop_base)
|
|
{
|
|
JSValueArray *slots, *new_slots;
|
|
int old_size, new_size1;
|
|
JSGCRef val_ref;
|
|
|
|
if (val == JS_NULL) {
|
|
slots = NULL;
|
|
old_size = 0;
|
|
} else {
|
|
slots = JS_VALUE_TO_PTR(val);
|
|
old_size = slots->size;
|
|
}
|
|
if (unlikely(new_size > old_size)) {
|
|
new_size1 = old_size + old_size / 2;
|
|
if (new_size1 > new_size) {
|
|
new_size = new_size1;
|
|
/* ensure that the property array has a size which is a
|
|
* multiple of 3 */
|
|
if (prop_base != 0) {
|
|
int align = (new_size - prop_base) % 3;
|
|
if (align != 0)
|
|
new_size += 3 - align;
|
|
}
|
|
}
|
|
new_size = max_int(new_size, old_size + old_size / 2);
|
|
JS_PUSH_VALUE(ctx, val);
|
|
new_slots = js_alloc_value_array(ctx, old_size, new_size);
|
|
JS_POP_VALUE(ctx, val);
|
|
if (!new_slots)
|
|
return JS_EXCEPTION;
|
|
if (old_size > 0) {
|
|
slots = JS_VALUE_TO_PTR(val);
|
|
memcpy(new_slots->arr, slots->arr, old_size * sizeof(JSValue));
|
|
}
|
|
val = JS_VALUE_FROM_PTR(new_slots);
|
|
}
|
|
return val;
|
|
}
|
|
|
|
static JSValue js_resize_value_array(JSContext *ctx, JSValue val, int new_size)
|
|
{
|
|
return js_resize_value_array2(ctx, val, new_size, 0);
|
|
}
|
|
|
|
/* no allocation is done */
|
|
static void js_shrink_value_array(JSContext *ctx, JSValue *pval, int new_size)
|
|
{
|
|
JSValueArray *arr;
|
|
if (*pval == JS_NULL)
|
|
return;
|
|
arr = JS_VALUE_TO_PTR(*pval);
|
|
assert(new_size <= arr->size);
|
|
if (new_size == 0) {
|
|
js_free(ctx, arr);
|
|
*pval = JS_NULL;
|
|
} else {
|
|
arr = js_shrink(ctx, arr, sizeof(JSValueArray) + new_size * sizeof(JSValue));
|
|
arr->size = new_size;
|
|
}
|
|
}
|
|
|
|
static JSByteArray *js_alloc_byte_array(JSContext *ctx, int size)
|
|
{
|
|
JSByteArray *arr;
|
|
|
|
if (size > JS_BYTE_ARRAY_SIZE_MAX) {
|
|
JS_ThrowOutOfMemory(ctx);
|
|
return NULL;
|
|
}
|
|
arr = js_malloc(ctx, sizeof(JSByteArray) + size, JS_MTAG_BYTE_ARRAY);
|
|
if (!arr)
|
|
return NULL;
|
|
arr->size = size;
|
|
return arr;
|
|
}
|
|
|
|
static JSValue js_resize_byte_array(JSContext *ctx, JSValue val, int new_size)
|
|
{
|
|
JSByteArray *arr, *new_arr;
|
|
int old_size;
|
|
JSGCRef val_ref;
|
|
|
|
if (val == JS_NULL) {
|
|
arr = NULL;
|
|
old_size = 0;
|
|
} else {
|
|
arr = JS_VALUE_TO_PTR(val);
|
|
old_size = arr->size;
|
|
}
|
|
if (unlikely(new_size > old_size)) {
|
|
new_size = max_int(new_size, old_size + old_size / 2);
|
|
JS_PUSH_VALUE(ctx, val);
|
|
new_arr = js_alloc_byte_array(ctx, new_size);
|
|
JS_POP_VALUE(ctx, val);
|
|
if (!new_arr)
|
|
return JS_EXCEPTION;
|
|
if (old_size > 0) {
|
|
arr = JS_VALUE_TO_PTR(val);
|
|
memcpy(new_arr->buf, arr->buf, old_size);
|
|
}
|
|
val = JS_VALUE_FROM_PTR(new_arr);
|
|
}
|
|
return val;
|
|
}
|
|
|
|
static void js_shrink_byte_array(JSContext *ctx, JSValue *pval, int new_size)
|
|
{
|
|
JSByteArray *arr;
|
|
if (*pval == JS_NULL)
|
|
return;
|
|
arr = JS_VALUE_TO_PTR(*pval);
|
|
assert(new_size <= arr->size);
|
|
if (new_size == 0) {
|
|
js_free(ctx, arr);
|
|
*pval = JS_NULL;
|
|
} else {
|
|
arr = js_shrink(ctx, arr, sizeof(JSByteArray) + new_size);
|
|
arr->size = new_size;
|
|
}
|
|
}
|
|
|
|
/* extra_size is in bytes */
|
|
static JSObject *JS_NewObjectProtoClass1(JSContext *ctx, JSValue proto,
|
|
int class_id, int extra_size)
|
|
{
|
|
JSObject *p;
|
|
JSGCRef proto_ref;
|
|
extra_size = (unsigned)(extra_size + JSW - 1) / JSW;
|
|
JS_PUSH_VALUE(ctx, proto);
|
|
p = js_malloc(ctx, offsetof(JSObject, u) + extra_size * JSW, JS_MTAG_OBJECT);
|
|
JS_POP_VALUE(ctx, proto);
|
|
if (!p)
|
|
return NULL;
|
|
p->class_id = class_id;
|
|
p->extra_size = extra_size;
|
|
p->proto = proto;
|
|
p->props = ctx->empty_props;
|
|
return p;
|
|
}
|
|
|
|
static JSValue JS_NewObjectProtoClass(JSContext *ctx, JSValue proto, int class_id, int extra_size)
|
|
{
|
|
JSObject *p;
|
|
p = JS_NewObjectProtoClass1(ctx, proto, class_id, extra_size);
|
|
if (!p)
|
|
return JS_EXCEPTION;
|
|
else
|
|
return JS_VALUE_FROM_PTR(p);
|
|
}
|
|
|
|
static JSValue JS_NewObjectClass(JSContext *ctx, int class_id, int extra_size)
|
|
{
|
|
return JS_NewObjectProtoClass(ctx, ctx->class_proto[class_id], class_id, extra_size);
|
|
}
|
|
|
|
JSValue JS_NewObjectClassUser(JSContext *ctx, int class_id)
|
|
{
|
|
JSObject *p;
|
|
assert(class_id >= JS_CLASS_USER);
|
|
p = JS_NewObjectProtoClass1(ctx, ctx->class_proto[class_id], class_id,
|
|
sizeof(JSObjectUserData));
|
|
if (!p)
|
|
return JS_EXCEPTION;
|
|
p->u.user.opaque = NULL;
|
|
return JS_VALUE_FROM_PTR(p);
|
|
}
|
|
|
|
JSValue JS_NewObject(JSContext *ctx)
|
|
{
|
|
return JS_NewObjectClass(ctx, JS_CLASS_OBJECT, 0);
|
|
}
|
|
|
|
/* same as JS_NewObject() but preallocate for 'n' properties */
|
|
JSValue JS_NewObjectPrealloc(JSContext *ctx, int n)
|
|
{
|
|
JSValue obj;
|
|
JSValueArray *arr;
|
|
JSObject *p;
|
|
JSGCRef obj_ref;
|
|
|
|
obj = JS_NewObjectClass(ctx, JS_CLASS_OBJECT, 0);
|
|
if (JS_IsException(obj) || n <= 0)
|
|
return obj;
|
|
JS_PUSH_VALUE(ctx, obj);
|
|
arr = js_alloc_props(ctx, n);
|
|
JS_POP_VALUE(ctx, obj);
|
|
if (!arr)
|
|
return JS_EXCEPTION;
|
|
p = JS_VALUE_TO_PTR(obj);
|
|
p->props = JS_VALUE_FROM_PTR(arr);
|
|
return obj;
|
|
}
|
|
|
|
JSValue JS_NewArray(JSContext *ctx, int initial_len)
|
|
{
|
|
JSObject *p;
|
|
JSValue val;
|
|
JSGCRef val_ref;
|
|
|
|
val = JS_NewObjectClass(ctx, JS_CLASS_ARRAY, sizeof(JSArrayData));
|
|
if (JS_IsException(val))
|
|
return val;
|
|
p = JS_VALUE_TO_PTR(val);
|
|
p->u.array.tab = JS_NULL;
|
|
p->u.array.len = 0;
|
|
if (initial_len > 0) {
|
|
JSValueArray *arr;
|
|
JS_PUSH_VALUE(ctx, val);
|
|
arr = js_alloc_value_array(ctx, 0, initial_len);
|
|
JS_POP_VALUE(ctx, val);
|
|
if (!arr)
|
|
return JS_EXCEPTION;
|
|
p = JS_VALUE_TO_PTR(val);
|
|
p->u.array.tab = JS_VALUE_FROM_PTR(arr);
|
|
p->u.array.len = initial_len;
|
|
}
|
|
return val;
|
|
}
|
|
|
|
static inline uint32_t hash_prop(JSValue prop)
|
|
{
|
|
return (prop / JSW) ^ (prop % JSW); /* XXX: improve */
|
|
}
|
|
|
|
/* return NULL if not found */
|
|
static force_inline JSProperty *find_own_property_inlined(JSContext *ctx,
|
|
JSObject *p, JSValue prop)
|
|
{
|
|
JSValueArray *arr;
|
|
JSProperty *pr;
|
|
uint32_t hash_mask, h, idx;
|
|
|
|
arr = JS_VALUE_TO_PTR(p->props);
|
|
hash_mask = JS_VALUE_GET_INT(arr->arr[1]);
|
|
h = hash_prop(prop) & hash_mask;
|
|
idx = arr->arr[2 + h]; /* JSValue, hence idx * 2 */
|
|
while (idx != 0) {
|
|
pr = (JSProperty *)((uint8_t *)arr->arr + idx * (sizeof(JSValue) / 2));
|
|
if (pr->key == prop)
|
|
return pr;
|
|
idx = pr->hash_next; /* JSValue, hence idx * 2 */
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static inline JSProperty *find_own_property(JSContext *ctx,
|
|
JSObject *p, JSValue prop)
|
|
{
|
|
return find_own_property_inlined(ctx, p, prop);
|
|
}
|
|
|
|
static JSValue get_special_prop(JSContext *ctx, JSValue val)
|
|
{
|
|
int idx;
|
|
/* 'prototype' or 'constructor' property in ROM */
|
|
idx = JS_VALUE_GET_INT(val);
|
|
if (idx >= 0)
|
|
return ctx->class_proto[idx];
|
|
else
|
|
return ctx->class_obj[-idx - 1];
|
|
}
|
|
|
|
/* return the value or:
|
|
- exception
|
|
- tail call : returned in case of getter and handle_getset =
|
|
true. The function is put on the stack
|
|
*/
|
|
static JSValue JS_GetPropertyInternal(JSContext *ctx, JSValue obj, JSValue prop,
|
|
BOOL allow_tail_call)
|
|
{
|
|
JSObject *p;
|
|
JSValue proto;
|
|
JSProperty *pr;
|
|
|
|
if (unlikely(!JS_IsPtr(obj))) {
|
|
if (JS_IsIntOrShortFloat(obj)) {
|
|
p = JS_VALUE_TO_PTR(ctx->class_proto[JS_CLASS_NUMBER]);
|
|
} else {
|
|
switch(JS_VALUE_GET_SPECIAL_TAG(obj)) {
|
|
case JS_TAG_BOOL:
|
|
p = JS_VALUE_TO_PTR(ctx->class_proto[JS_CLASS_BOOLEAN]);
|
|
break;
|
|
case JS_TAG_SHORT_FUNC:
|
|
p = JS_VALUE_TO_PTR(ctx->class_proto[JS_CLASS_CLOSURE]);
|
|
break;
|
|
case JS_TAG_STRING_CHAR:
|
|
goto string_proto;
|
|
case JS_TAG_NULL:
|
|
return JS_ThrowTypeError(ctx, "cannot read property '%"JSValue_PRI"' of null", prop);
|
|
case JS_TAG_UNDEFINED:
|
|
return JS_ThrowTypeError(ctx, "cannot read property '%"JSValue_PRI"' of undefined", prop);
|
|
default:
|
|
goto no_prop;
|
|
}
|
|
}
|
|
} else {
|
|
p = JS_VALUE_TO_PTR(obj);
|
|
}
|
|
if (unlikely(p->mtag != JS_MTAG_OBJECT)) {
|
|
switch(p->mtag) {
|
|
case JS_MTAG_FLOAT64:
|
|
p = JS_VALUE_TO_PTR(ctx->class_proto[JS_CLASS_NUMBER]);
|
|
break;
|
|
case JS_MTAG_STRING:
|
|
string_proto:
|
|
{
|
|
if (JS_IsInt(prop)) {
|
|
JSValue ret;
|
|
ret = js_string_charAt(ctx, &obj, 1, &prop, magic_internalAt);
|
|
if (!JS_IsUndefined(ret))
|
|
return ret;
|
|
}
|
|
p = JS_VALUE_TO_PTR(ctx->class_proto[JS_CLASS_STRING]);
|
|
}
|
|
break;
|
|
default:
|
|
no_prop:
|
|
return JS_ThrowTypeError(ctx, "cannot read property '%"JSValue_PRI"' of value", prop);
|
|
}
|
|
}
|
|
|
|
for(;;) {
|
|
if (p->class_id == JS_CLASS_ARRAY) {
|
|
if (JS_IsInt(prop)) {
|
|
uint32_t idx = JS_VALUE_GET_INT(prop);
|
|
if (idx < p->u.array.len) {
|
|
JSValueArray *arr = JS_VALUE_TO_PTR(p->u.array.tab);
|
|
return arr->arr[idx];
|
|
}
|
|
} else if (JS_IsNumericProperty(ctx, prop)) {
|
|
return JS_UNDEFINED;
|
|
}
|
|
} else if (p->class_id >= JS_CLASS_UINT8C_ARRAY &&
|
|
p->class_id <= JS_CLASS_FLOAT64_ARRAY) {
|
|
if (JS_IsInt(prop)) {
|
|
uint32_t idx = JS_VALUE_GET_INT(prop);
|
|
JSObject *pbuffer;
|
|
JSByteArray *arr;
|
|
if (idx < p->u.typed_array.len) {
|
|
idx += p->u.typed_array.offset;
|
|
pbuffer = JS_VALUE_TO_PTR(p->u.typed_array.buffer);
|
|
arr = JS_VALUE_TO_PTR(pbuffer->u.array_buffer.byte_buffer);
|
|
switch(p->class_id) {
|
|
default:
|
|
case JS_CLASS_UINT8C_ARRAY:
|
|
case JS_CLASS_UINT8_ARRAY:
|
|
return JS_NewShortInt(*((uint8_t *)arr->buf + idx));
|
|
case JS_CLASS_INT8_ARRAY:
|
|
return JS_NewShortInt(*((int8_t *)arr->buf + idx));
|
|
case JS_CLASS_INT16_ARRAY:
|
|
return JS_NewShortInt(*((int16_t *)arr->buf + idx));
|
|
case JS_CLASS_UINT16_ARRAY:
|
|
return JS_NewShortInt(*((uint16_t *)arr->buf + idx));
|
|
case JS_CLASS_INT32_ARRAY:
|
|
return JS_NewInt32(ctx, *((int32_t *)arr->buf + idx));
|
|
case JS_CLASS_UINT32_ARRAY:
|
|
return JS_NewUint32(ctx, *((uint32_t *)arr->buf + idx));
|
|
case JS_CLASS_FLOAT32_ARRAY:
|
|
return JS_NewFloat64(ctx, *((float *)arr->buf + idx));
|
|
case JS_CLASS_FLOAT64_ARRAY:
|
|
return JS_NewFloat64(ctx, *((double *)arr->buf + idx));
|
|
}
|
|
}
|
|
} else if (JS_IsNumericProperty(ctx, prop)) {
|
|
return JS_UNDEFINED;
|
|
}
|
|
}
|
|
|
|
pr = find_own_property(ctx, p, prop);
|
|
if (pr) {
|
|
if (likely(pr->prop_type == JS_PROP_NORMAL)) {
|
|
return pr->value;
|
|
} else if (pr->prop_type == JS_PROP_VARREF) {
|
|
JSVarRef *pv = JS_VALUE_TO_PTR(pr->value);
|
|
/* always detached */
|
|
return pv->u.value;
|
|
} else if (pr->prop_type == JS_PROP_SPECIAL) {
|
|
return get_special_prop(ctx, pr->value);
|
|
} else {
|
|
JSValueArray *arr = JS_VALUE_TO_PTR(pr->value);
|
|
JSValue getter = arr->arr[0];
|
|
if (getter == JS_UNDEFINED)
|
|
return JS_UNDEFINED;
|
|
if (allow_tail_call) {
|
|
/* It is assumed 'this_obj' is on the stack and
|
|
that the stack has some slack to add one element. */
|
|
ctx->sp[-1] = ctx->sp[0];
|
|
ctx->sp[0] = getter;
|
|
ctx->sp--;
|
|
return JS_NewTailCall(0);
|
|
} else {
|
|
JSGCRef getter_ref, obj_ref;
|
|
int err;
|
|
JS_PUSH_VALUE(ctx, getter);
|
|
JS_PUSH_VALUE(ctx, obj);
|
|
err = JS_StackCheck(ctx, 2);
|
|
JS_POP_VALUE(ctx, obj);
|
|
JS_POP_VALUE(ctx, getter);
|
|
if (err)
|
|
return JS_EXCEPTION;
|
|
JS_PushArg(ctx, getter);
|
|
JS_PushArg(ctx, obj);
|
|
return JS_Call(ctx, 0);
|
|
}
|
|
}
|
|
}
|
|
/* look in the prototype */
|
|
proto = p->proto;
|
|
if (proto == JS_NULL)
|
|
break;
|
|
p = JS_VALUE_TO_PTR(proto);
|
|
}
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
static JSValue JS_GetProperty(JSContext *ctx, JSValue obj, JSValue prop)
|
|
{
|
|
return JS_GetPropertyInternal(ctx, obj, prop, FALSE);
|
|
}
|
|
|
|
JSValue JS_GetPropertyStr(JSContext *ctx, JSValue this_obj, const char *str)
|
|
{
|
|
JSValue prop;
|
|
JSGCRef this_obj_ref;
|
|
|
|
JS_PUSH_VALUE(ctx, this_obj);
|
|
prop = JS_NewString(ctx, str);
|
|
if (!JS_IsException(prop)) {
|
|
prop = JS_ToPropertyKey(ctx, prop);
|
|
}
|
|
JS_POP_VALUE(ctx, this_obj);
|
|
if (JS_IsException(prop))
|
|
return prop;
|
|
return JS_GetProperty(ctx, this_obj, prop);
|
|
}
|
|
|
|
JSValue JS_GetPropertyUint32(JSContext *ctx, JSValue obj, uint32_t idx)
|
|
{
|
|
if (idx > JS_SHORTINT_MAX)
|
|
return JS_ThrowRangeError(ctx, "invalid array index");
|
|
return JS_GetProperty(ctx, obj, JS_NewInt32(ctx, idx));
|
|
}
|
|
|
|
static BOOL JS_HasProperty(JSContext *ctx, JSValue obj, JSValue prop)
|
|
{
|
|
JSObject *p;
|
|
JSProperty *pr;
|
|
|
|
if (!JS_IsPtr(obj))
|
|
return FALSE;
|
|
p = JS_VALUE_TO_PTR(obj);
|
|
if (p->mtag != JS_MTAG_OBJECT)
|
|
return FALSE;
|
|
for(;;) {
|
|
pr = find_own_property(ctx, p, prop);
|
|
if (pr)
|
|
return TRUE;
|
|
obj = p->proto;
|
|
if (obj == JS_NULL)
|
|
break;
|
|
p = JS_VALUE_TO_PTR(obj);
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static int get_prop_hash_size_log2(int prop_count)
|
|
{
|
|
/* XXX: adjust ? */
|
|
if (prop_count <= 1)
|
|
return 0;
|
|
else
|
|
return (32 - clz32(prop_count - 1)) - 1;
|
|
}
|
|
|
|
/* allocate 'n' properties, assuming n >= 1 */
|
|
static JSValueArray *js_alloc_props(JSContext *ctx, int n)
|
|
{
|
|
int hash_size_log2, hash_mask, size, i, first_free;
|
|
JSValueArray *arr;
|
|
JSProperty *pr;
|
|
|
|
hash_size_log2 = get_prop_hash_size_log2(n);
|
|
hash_mask = (1 << hash_size_log2) - 1;
|
|
first_free = 2 + hash_mask + 1;
|
|
size = first_free + 3 * n;
|
|
arr = js_alloc_value_array(ctx, 0, size);
|
|
if (!arr)
|
|
return NULL;
|
|
arr->arr[0] = JS_NewShortInt(0); /* no property is allocated yet */
|
|
arr->arr[1] = JS_NewShortInt(hash_mask);
|
|
for(i = 0; i <= hash_mask; i++)
|
|
arr->arr[2 + i] = 0;
|
|
pr = NULL; /* avoid warning */
|
|
for(i = 0; i < n; i++) {
|
|
pr = (JSProperty *)&arr->arr[2 + hash_mask + 1 + 3 * i];
|
|
pr->key = JS_UNINITIALIZED;
|
|
}
|
|
/* last property */
|
|
pr->hash_next = first_free << 1;
|
|
return arr;
|
|
}
|
|
|
|
static void js_rehash_props(JSContext *ctx, JSObject *p, BOOL gc_rehash)
|
|
{
|
|
JSValueArray *arr;
|
|
int prop_count, hash_mask, h, idx, i, j;
|
|
JSProperty *pr;
|
|
|
|
arr = JS_VALUE_TO_PTR(p->props);
|
|
if (JS_IS_ROM_PTR(ctx, arr))
|
|
return;
|
|
hash_mask = JS_VALUE_GET_INT(arr->arr[1]);
|
|
if (hash_mask == 0 && gc_rehash)
|
|
return; /* no need to rehash if single hash entry */
|
|
prop_count = JS_VALUE_GET_INT(arr->arr[0]);
|
|
for(i = 0; i <= hash_mask; i++) {
|
|
arr->arr[2 + i] = JS_NewShortInt(0);
|
|
}
|
|
for(i = 0, j = 0; j < prop_count; i++) {
|
|
idx = 2 + (hash_mask + 1) + 3 * i;
|
|
pr = (JSProperty *)&arr->arr[idx];
|
|
if (pr->key != JS_UNINITIALIZED) {
|
|
h = hash_prop(pr->key) & hash_mask;
|
|
pr->hash_next = arr->arr[2 + h];
|
|
arr->arr[2 + h] = JS_NewShortInt(idx);
|
|
j++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Compact the properties. No memory allocation is done */
|
|
static void js_compact_props(JSContext *ctx, JSObject *p)
|
|
{
|
|
JSValueArray *arr;
|
|
int prop_count, hash_mask, i, j, hash_size_log2;
|
|
int new_size, new_hash_mask;
|
|
JSProperty *pr, *pr1;
|
|
|
|
arr = JS_VALUE_TO_PTR(p->props);
|
|
prop_count = JS_VALUE_GET_INT(arr->arr[0]);
|
|
|
|
/* no property */
|
|
if (prop_count == 0) {
|
|
if (p->props != ctx->empty_props) {
|
|
//js_free(ctx, p->props);
|
|
p->props = ctx->empty_props;
|
|
}
|
|
return;
|
|
}
|
|
|
|
hash_mask = JS_VALUE_GET_INT(arr->arr[1]);
|
|
hash_size_log2 = get_prop_hash_size_log2(prop_count);
|
|
new_hash_mask = min_int(hash_mask, (1 << hash_size_log2) - 1);
|
|
new_size = 2 + new_hash_mask + 1 + 3 * prop_count;
|
|
if (new_size >= arr->size)
|
|
return; /* nothing to do */
|
|
// printf("compact_props: new_size=%d size=%d hash=%d\n", new_size, arr->size, new_hash_mask);
|
|
|
|
arr->arr[1] = JS_NewShortInt(new_hash_mask);
|
|
|
|
/* move the properties, skipping the deleted ones */
|
|
for(i = 0, j = 0; j < prop_count; i++) {
|
|
pr = (JSProperty *)&arr->arr[2 + (hash_mask + 1) + 3 * i];
|
|
if (pr->key != JS_UNINITIALIZED) {
|
|
pr1 = (JSProperty *)&arr->arr[2 + (new_hash_mask + 1) + 3 * j];
|
|
*pr1 = *pr;
|
|
j++;
|
|
}
|
|
}
|
|
|
|
js_shrink_value_array(ctx, &p->props, new_size);
|
|
|
|
js_rehash_props(ctx, p, FALSE);
|
|
}
|
|
|
|
/* if the existing properties are in ROM, copy them to RAM. Return non zero if error */
|
|
static int js_update_props(JSContext *ctx, JSValue obj)
|
|
{
|
|
JSObject *p;
|
|
JSValueArray *arr, *arr1;
|
|
JSGCRef obj_ref;
|
|
int i, idx, prop_count, hash_mask;
|
|
JSProperty *pr;
|
|
|
|
p = JS_VALUE_TO_PTR(obj);
|
|
arr = JS_VALUE_TO_PTR(p->props);
|
|
if (!JS_IS_ROM_PTR(ctx, arr))
|
|
return 0;
|
|
JS_PUSH_VALUE(ctx, obj);
|
|
arr1 = js_alloc_value_array(ctx, 0, arr->size);
|
|
JS_POP_VALUE(ctx, obj);
|
|
if (!arr1)
|
|
return -1;
|
|
/* no rehashing is needed because all the atoms are in ROM */
|
|
memcpy(arr1->arr, arr->arr, arr->size * sizeof(JSValue));
|
|
prop_count = JS_VALUE_GET_INT(arr1->arr[0]);
|
|
hash_mask = JS_VALUE_GET_INT(arr1->arr[1]);
|
|
/* no deleted properties in ROM */
|
|
assert(arr1->size == 2 + (hash_mask + 1) + 3 * prop_count);
|
|
/* convert JS_PROP_SPECIAL properties ("prototype" and "constructor") */
|
|
for(i = 0; i < prop_count; i++) {
|
|
idx = 2 + (hash_mask + 1) + 3 * i;
|
|
pr = (JSProperty *)&arr1->arr[idx];
|
|
if (pr->prop_type == JS_PROP_SPECIAL) {
|
|
pr->value = get_special_prop(ctx, pr->value);
|
|
pr->prop_type = JS_PROP_NORMAL;
|
|
}
|
|
}
|
|
|
|
p = JS_VALUE_TO_PTR(obj);
|
|
p->props = JS_VALUE_FROM_PTR(arr1);
|
|
return 0;
|
|
}
|
|
|
|
/* compute 'first_free' in a property list */
|
|
static int get_first_free(JSValueArray *arr)
|
|
{
|
|
JSProperty *pr1;
|
|
int first_free;
|
|
|
|
pr1 = (JSProperty *)&arr->arr[arr->size - 3];
|
|
if (pr1->key == JS_UNINITIALIZED)
|
|
first_free = pr1->hash_next >> 1;
|
|
else
|
|
first_free = arr->size;
|
|
return first_free;
|
|
}
|
|
|
|
/* It is assumed that the property does not already exists. */
|
|
static JSProperty *js_create_property(JSContext *ctx, JSValue obj,
|
|
JSValue prop)
|
|
{
|
|
JSObject *p;
|
|
JSValueArray *arr;
|
|
int prop_count, hash_mask, new_size, h, first_free, new_hash_mask;
|
|
JSProperty *pr, *pr1;
|
|
JSValue new_props;
|
|
JSGCRef obj_ref, prop_ref;
|
|
|
|
p = JS_VALUE_TO_PTR(obj);
|
|
arr = JS_VALUE_TO_PTR(p->props);
|
|
|
|
// JS_DumpValue(ctx, "create", prop);
|
|
prop_count = JS_VALUE_GET_INT(arr->arr[0]);
|
|
hash_mask = JS_VALUE_GET_INT(arr->arr[1]);
|
|
/* extend the array if no space left (this single test is valid
|
|
even if the property list is empty) */
|
|
pr1 = (JSProperty *)&arr->arr[arr->size - 3];
|
|
if (pr1->key != JS_UNINITIALIZED) {
|
|
if (p->props == ctx->empty_props) {
|
|
/* XXX: remove and move empty_props to ROM */
|
|
JS_PUSH_VALUE(ctx, obj);
|
|
JS_PUSH_VALUE(ctx, prop);
|
|
arr = js_alloc_props(ctx, 1);
|
|
JS_POP_VALUE(ctx, prop);
|
|
JS_POP_VALUE(ctx, obj);
|
|
if (!arr)
|
|
return NULL;
|
|
p = JS_VALUE_TO_PTR(obj);
|
|
p->props = JS_VALUE_FROM_PTR(arr);
|
|
first_free = 3;
|
|
} else {
|
|
first_free = arr->size;
|
|
new_size = first_free + 3;
|
|
new_hash_mask = hash_mask;
|
|
if ((prop_count + 1) > 2 * (hash_mask + 1)) {
|
|
/* resize the hash table if too many properties */
|
|
new_hash_mask = 2 * (hash_mask + 1) - 1;
|
|
new_size += new_hash_mask - hash_mask;
|
|
}
|
|
JS_PUSH_VALUE(ctx, obj);
|
|
JS_PUSH_VALUE(ctx, prop);
|
|
// printf("resize_props: new_size=%d hash=%d %d\n", new_size, new_hash_mask, hash_mask);
|
|
new_props = js_resize_value_array2(ctx, p->props, new_size, 2 + new_hash_mask + 1);
|
|
JS_POP_VALUE(ctx, prop);
|
|
JS_POP_VALUE(ctx, obj);
|
|
if (JS_IsException(new_props))
|
|
return NULL;
|
|
p = JS_VALUE_TO_PTR(obj);
|
|
p->props = new_props;
|
|
arr = JS_VALUE_TO_PTR(p->props);
|
|
if (new_hash_mask != hash_mask) {
|
|
/* rebuild the hash table */
|
|
memmove(&arr->arr[2 + (new_hash_mask + 1)],
|
|
&arr->arr[2 + (hash_mask + 1)],
|
|
(first_free - (2 + hash_mask + 1)) * sizeof(JSValue));
|
|
first_free += new_hash_mask - hash_mask;
|
|
hash_mask = new_hash_mask;
|
|
arr->arr[1] = JS_NewShortInt(hash_mask);
|
|
js_rehash_props(ctx, p, FALSE);
|
|
}
|
|
}
|
|
/* ensure the last element is marked as uninitialized to store 'first_free' */
|
|
pr1 = (JSProperty *)&arr->arr[arr->size - 3];
|
|
pr1->key = JS_UNINITIALIZED;
|
|
} else {
|
|
first_free = pr1->hash_next >> 1;
|
|
}
|
|
|
|
pr = (JSProperty *)&arr->arr[first_free];
|
|
pr->key = prop;
|
|
pr->value = JS_UNDEFINED;
|
|
pr->prop_type = JS_PROP_NORMAL;
|
|
h = hash_prop(prop) & hash_mask;
|
|
pr->hash_next = arr->arr[2 + h];
|
|
arr->arr[2 + h] = JS_NewShortInt(first_free);
|
|
arr->arr[0] = JS_NewShortInt(prop_count + 1);
|
|
/* update first_free */
|
|
first_free += 3;
|
|
if (first_free < arr->size) {
|
|
pr1 = (JSProperty *)&arr->arr[arr->size - 3];
|
|
pr1->hash_next = first_free << 1;
|
|
}
|
|
|
|
return pr;
|
|
}
|
|
|
|
/* don't do property lookup if not present */
|
|
#define JS_DEF_PROP_LOOKUP (1 << 0)
|
|
/* return the raw property value */
|
|
#define JS_DEF_PROP_RET_VAL (1 << 1)
|
|
#define JS_DEF_PROP_HAS_VALUE (1 << 2)
|
|
#define JS_DEF_PROP_HAS_GET (1 << 3)
|
|
#define JS_DEF_PROP_HAS_SET (1 << 4)
|
|
|
|
/* XXX: handle arrays and typed arrays */
|
|
static JSValue JS_DefinePropertyInternal(JSContext *ctx, JSValue obj,
|
|
JSValue prop, JSValue val,
|
|
JSValue setter, int flags)
|
|
{
|
|
JSProperty *pr;
|
|
JSValueArray *arr;
|
|
JSGCRef obj_ref, prop_ref, val_ref, setter_ref;
|
|
int ret, prop_type;
|
|
|
|
/* move to RAM if needed */
|
|
JS_PUSH_VALUE(ctx, obj);
|
|
JS_PUSH_VALUE(ctx, prop);
|
|
JS_PUSH_VALUE(ctx, val);
|
|
JS_PUSH_VALUE(ctx, setter);
|
|
ret = js_update_props(ctx, obj);
|
|
JS_POP_VALUE(ctx, setter);
|
|
JS_POP_VALUE(ctx, val);
|
|
JS_POP_VALUE(ctx, prop);
|
|
JS_POP_VALUE(ctx, obj);
|
|
if (ret)
|
|
return JS_EXCEPTION;
|
|
|
|
if (flags & JS_DEF_PROP_LOOKUP) {
|
|
pr = find_own_property(ctx, JS_VALUE_TO_PTR(obj), prop);
|
|
if (pr) {
|
|
if (flags & JS_DEF_PROP_HAS_VALUE) {
|
|
if (pr->prop_type == JS_PROP_NORMAL) {
|
|
pr->value = val;
|
|
} else if (pr->prop_type == JS_PROP_VARREF) {
|
|
JSVarRef *pv = JS_VALUE_TO_PTR(pr->value);
|
|
pv->u.value = val;
|
|
} else {
|
|
goto error_modify;
|
|
}
|
|
} else if (flags & (JS_DEF_PROP_HAS_GET | JS_DEF_PROP_HAS_SET)) {
|
|
if (pr->prop_type != JS_PROP_GETSET) {
|
|
error_modify:
|
|
return JS_ThrowTypeError(ctx, "cannot modify getter/setter/value kind");
|
|
}
|
|
arr = JS_VALUE_TO_PTR(pr->value);
|
|
if (unlikely(JS_IS_ROM_PTR(ctx, arr))) {
|
|
/* move to RAM */
|
|
JSValueArray *arr2;
|
|
JS_PUSH_VALUE(ctx, obj);
|
|
JS_PUSH_VALUE(ctx, prop);
|
|
JS_PUSH_VALUE(ctx, val);
|
|
JS_PUSH_VALUE(ctx, setter);
|
|
arr2 = js_alloc_value_array(ctx, 0, 2);
|
|
JS_POP_VALUE(ctx, setter);
|
|
JS_POP_VALUE(ctx, val);
|
|
JS_POP_VALUE(ctx, prop);
|
|
JS_POP_VALUE(ctx, obj);
|
|
if (!arr2)
|
|
return JS_EXCEPTION;
|
|
pr = find_own_property(ctx, JS_VALUE_TO_PTR(obj), prop);
|
|
arr = JS_VALUE_TO_PTR(pr->value);
|
|
arr2->arr[0] = arr->arr[0];
|
|
arr2->arr[1] = arr->arr[1];
|
|
pr->value = JS_VALUE_FROM_PTR(arr2);
|
|
arr = arr2;
|
|
}
|
|
if (flags & JS_DEF_PROP_HAS_GET)
|
|
arr->arr[0] = val;
|
|
if (flags & JS_DEF_PROP_HAS_SET)
|
|
arr->arr[1] = setter;
|
|
}
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
if (flags & (JS_DEF_PROP_HAS_GET | JS_DEF_PROP_HAS_SET)) {
|
|
prop_type = JS_PROP_GETSET;
|
|
JS_PUSH_VALUE(ctx, obj);
|
|
JS_PUSH_VALUE(ctx, prop);
|
|
JS_PUSH_VALUE(ctx, val);
|
|
JS_PUSH_VALUE(ctx, setter);
|
|
arr = js_alloc_value_array(ctx, 0, 2);
|
|
JS_POP_VALUE(ctx, setter);
|
|
JS_POP_VALUE(ctx, val);
|
|
JS_POP_VALUE(ctx, prop);
|
|
JS_POP_VALUE(ctx, obj);
|
|
if (!arr)
|
|
return JS_EXCEPTION;
|
|
arr->arr[0] = val;
|
|
arr->arr[1] = setter;
|
|
val = JS_VALUE_FROM_PTR(arr);
|
|
} else if (obj == ctx->global_obj) {
|
|
JSVarRef *pv;
|
|
|
|
prop_type = JS_PROP_VARREF;
|
|
JS_PUSH_VALUE(ctx, obj);
|
|
JS_PUSH_VALUE(ctx, prop);
|
|
JS_PUSH_VALUE(ctx, val);
|
|
pv = js_malloc(ctx, sizeof(JSVarRef) - sizeof(JSValue), JS_MTAG_VARREF);
|
|
JS_POP_VALUE(ctx, val);
|
|
JS_POP_VALUE(ctx, prop);
|
|
JS_POP_VALUE(ctx, obj);
|
|
if (!pv)
|
|
return JS_EXCEPTION;
|
|
pv->is_detached = TRUE;
|
|
pv->u.value = val;
|
|
val = JS_VALUE_FROM_PTR(pv);
|
|
} else {
|
|
prop_type = JS_PROP_NORMAL;
|
|
}
|
|
JS_PUSH_VALUE(ctx, val);
|
|
pr = js_create_property(ctx, obj, prop);
|
|
JS_POP_VALUE(ctx, val);
|
|
if (!pr)
|
|
return JS_EXCEPTION;
|
|
pr->prop_type = prop_type;
|
|
pr->value = val;
|
|
done:
|
|
if (flags & JS_DEF_PROP_RET_VAL) {
|
|
return pr->value;
|
|
} else {
|
|
return JS_UNDEFINED;
|
|
}
|
|
}
|
|
|
|
static JSValue JS_DefinePropertyValue(JSContext *ctx, JSValue obj,
|
|
JSValue prop, JSValue val)
|
|
{
|
|
return JS_DefinePropertyInternal(ctx, obj, prop, val, JS_NULL,
|
|
JS_DEF_PROP_LOOKUP | JS_DEF_PROP_HAS_VALUE);
|
|
}
|
|
|
|
static JSValue JS_DefinePropertyGetSet(JSContext *ctx, JSValue obj,
|
|
JSValue prop, JSValue getter,
|
|
JSValue setter, int flags)
|
|
{
|
|
return JS_DefinePropertyInternal(ctx, obj, prop, getter, setter,
|
|
JS_DEF_PROP_LOOKUP | flags);
|
|
}
|
|
|
|
/* return a JSVarRef or an exception. */
|
|
static JSValue add_global_var(JSContext *ctx, JSValue prop, BOOL define_flag)
|
|
{
|
|
JSObject *p;
|
|
JSProperty *pr;
|
|
|
|
p = JS_VALUE_TO_PTR(ctx->global_obj);
|
|
pr = find_own_property(ctx, p, prop);
|
|
if (pr) {
|
|
if (pr->prop_type != JS_PROP_VARREF)
|
|
return JS_ThrowReferenceError(ctx, "global variable '%"JSValue_PRI"' must be a reference", prop);
|
|
if (define_flag) {
|
|
JSVarRef *pv = JS_VALUE_TO_PTR(pr->value);
|
|
/* define the variable if needed */
|
|
if (pv->u.value == JS_UNINITIALIZED)
|
|
pv->u.value = JS_UNDEFINED;
|
|
}
|
|
return pr->value;
|
|
}
|
|
return JS_DefinePropertyInternal(ctx, ctx->global_obj, prop,
|
|
define_flag ? JS_UNDEFINED : JS_UNINITIALIZED, JS_NULL,
|
|
JS_DEF_PROP_RET_VAL | JS_DEF_PROP_HAS_VALUE);
|
|
}
|
|
|
|
/* return JS_UNDEFINED in the normal case. Otherwise:
|
|
- exception
|
|
- tail call : returned in case of getter and handle_getset =
|
|
true. The function is put on the stack
|
|
*/
|
|
static JSValue JS_SetPropertyInternal(JSContext *ctx, JSValue this_obj,
|
|
JSValue prop, JSValue val,
|
|
BOOL allow_tail_call)
|
|
{
|
|
JSValue proto;
|
|
JSObject *p;
|
|
JSProperty *pr;
|
|
BOOL is_obj;
|
|
|
|
if (unlikely(!JS_IsPtr(this_obj))) {
|
|
is_obj = FALSE;
|
|
if (JS_IsIntOrShortFloat(this_obj)) {
|
|
p = JS_VALUE_TO_PTR(ctx->class_proto[JS_CLASS_NUMBER]);
|
|
goto prototype_lookup;
|
|
} else {
|
|
switch(JS_VALUE_GET_SPECIAL_TAG(this_obj)) {
|
|
case JS_TAG_BOOL:
|
|
p = JS_VALUE_TO_PTR(ctx->class_proto[JS_CLASS_BOOLEAN]);
|
|
goto prototype_lookup;
|
|
case JS_TAG_SHORT_FUNC:
|
|
p = JS_VALUE_TO_PTR(ctx->class_proto[JS_CLASS_CLOSURE]);
|
|
goto prototype_lookup;
|
|
case JS_TAG_STRING_CHAR:
|
|
p = JS_VALUE_TO_PTR(ctx->class_proto[JS_CLASS_STRING]);
|
|
goto prototype_lookup;
|
|
case JS_TAG_NULL:
|
|
return JS_ThrowTypeError(ctx, "cannot set property '%"JSValue_PRI"' of null", prop);
|
|
case JS_TAG_UNDEFINED:
|
|
return JS_ThrowTypeError(ctx, "cannot set property '%"JSValue_PRI"' of undefined", prop);
|
|
default:
|
|
goto no_prop;
|
|
}
|
|
}
|
|
} else {
|
|
is_obj = TRUE;
|
|
p = JS_VALUE_TO_PTR(this_obj);
|
|
}
|
|
if (unlikely(p->mtag != JS_MTAG_OBJECT)) {
|
|
is_obj = FALSE;
|
|
switch(p->mtag) {
|
|
case JS_MTAG_FLOAT64:
|
|
p = JS_VALUE_TO_PTR(ctx->class_proto[JS_CLASS_NUMBER]);
|
|
goto prototype_lookup;
|
|
case JS_MTAG_STRING:
|
|
p = JS_VALUE_TO_PTR(ctx->class_proto[JS_CLASS_STRING]);
|
|
goto prototype_lookup;
|
|
default:
|
|
no_prop:
|
|
return JS_ThrowTypeError(ctx, "cannot set property '%"JSValue_PRI"' of value", prop);
|
|
}
|
|
}
|
|
|
|
/* search if the property is already present */
|
|
if (p->class_id == JS_CLASS_ARRAY) {
|
|
if (JS_IsInt(prop)) {
|
|
JSValueArray *arr;
|
|
uint32_t idx = JS_VALUE_GET_INT(prop);
|
|
/* not standard: we refuse to add properties to object
|
|
except at the last position */
|
|
if (idx < p->u.array.len) {
|
|
arr = JS_VALUE_TO_PTR(p->u.array.tab);
|
|
arr->arr[idx] = val;
|
|
return JS_UNDEFINED;
|
|
} else if (idx == p->u.array.len) {
|
|
JSValue new_tab;
|
|
JSGCRef this_obj_ref, val_ref;
|
|
|
|
JS_PUSH_VALUE(ctx, this_obj);
|
|
JS_PUSH_VALUE(ctx, val);
|
|
new_tab = js_resize_value_array(ctx, p->u.array.tab, idx + 1);
|
|
JS_POP_VALUE(ctx, val);
|
|
JS_POP_VALUE(ctx, this_obj);
|
|
if (JS_IsException(new_tab))
|
|
return JS_EXCEPTION;
|
|
p = JS_VALUE_TO_PTR(this_obj);
|
|
p->u.array.tab = new_tab;
|
|
arr = JS_VALUE_TO_PTR(p->u.array.tab);
|
|
arr->arr[idx] = val;
|
|
p->u.array.len++;
|
|
return JS_UNDEFINED;
|
|
} else {
|
|
goto invalid_array_subscript;
|
|
}
|
|
} else if (JS_IsNumericProperty(ctx, prop)) {
|
|
goto invalid_array_subscript;
|
|
}
|
|
} else if (p->class_id >= JS_CLASS_UINT8C_ARRAY &&
|
|
p->class_id <= JS_CLASS_FLOAT64_ARRAY) {
|
|
if (JS_IsInt(prop)) {
|
|
uint32_t idx = JS_VALUE_GET_INT(prop);
|
|
int v, conv_ret;
|
|
double d;
|
|
JSObject *pbuffer;
|
|
JSByteArray *arr;
|
|
JSGCRef val_ref, this_obj_ref;
|
|
|
|
JS_PUSH_VALUE(ctx, this_obj);
|
|
JS_PUSH_VALUE(ctx, val);
|
|
switch(p->class_id) {
|
|
case JS_CLASS_UINT8C_ARRAY:
|
|
conv_ret = JS_ToUint8Clamp(ctx, &v, val);
|
|
break;
|
|
case JS_CLASS_FLOAT32_ARRAY:
|
|
case JS_CLASS_FLOAT64_ARRAY:
|
|
conv_ret = JS_ToNumber(ctx, &d, val);
|
|
break;
|
|
default:
|
|
conv_ret = JS_ToInt32(ctx, &v, val);
|
|
break;
|
|
}
|
|
JS_POP_VALUE(ctx, val);
|
|
JS_POP_VALUE(ctx, this_obj);
|
|
if (conv_ret)
|
|
return JS_EXCEPTION;
|
|
|
|
p = JS_VALUE_TO_PTR(this_obj);
|
|
if (idx >= p->u.typed_array.len)
|
|
goto invalid_array_subscript;
|
|
idx += p->u.typed_array.offset;
|
|
pbuffer = JS_VALUE_TO_PTR(p->u.typed_array.buffer);
|
|
arr = JS_VALUE_TO_PTR(pbuffer->u.array_buffer.byte_buffer);
|
|
switch(p->class_id) {
|
|
default:
|
|
case JS_CLASS_UINT8C_ARRAY:
|
|
case JS_CLASS_INT8_ARRAY:
|
|
case JS_CLASS_UINT8_ARRAY:
|
|
*((uint8_t *)arr->buf + idx) = v;
|
|
break;
|
|
case JS_CLASS_INT16_ARRAY:
|
|
case JS_CLASS_UINT16_ARRAY:
|
|
*((uint16_t *)arr->buf + idx) = v;
|
|
break;
|
|
case JS_CLASS_INT32_ARRAY:
|
|
case JS_CLASS_UINT32_ARRAY:
|
|
*((uint32_t *)arr->buf + idx) = v;
|
|
break;
|
|
case JS_CLASS_FLOAT32_ARRAY:
|
|
*((float *)arr->buf + idx) = d;
|
|
break;
|
|
case JS_CLASS_FLOAT64_ARRAY:
|
|
*((double *)arr->buf + idx) = d;
|
|
break;
|
|
}
|
|
return JS_UNDEFINED;
|
|
} else if (JS_IsNumericProperty(ctx, prop)) {
|
|
invalid_array_subscript:
|
|
return JS_ThrowTypeError(ctx, "invalid array subscript");
|
|
}
|
|
}
|
|
|
|
redo:
|
|
pr = find_own_property(ctx, p, prop);
|
|
if (pr) {
|
|
if (likely(pr->prop_type == JS_PROP_NORMAL)) {
|
|
if (unlikely(JS_IS_ROM_PTR(ctx, pr)))
|
|
goto convert_to_ram;
|
|
pr->value = val;
|
|
return JS_UNDEFINED;
|
|
} else if (pr->prop_type == JS_PROP_VARREF) {
|
|
JSVarRef *pv = JS_VALUE_TO_PTR(pr->value);
|
|
/* always detached */
|
|
pv->u.value = val;
|
|
return JS_UNDEFINED;
|
|
} else if (pr->prop_type == JS_PROP_SPECIAL) {
|
|
JSGCRef val_ref, prop_ref, this_obj_ref;
|
|
int err;
|
|
convert_to_ram:
|
|
JS_PUSH_VALUE(ctx, this_obj);
|
|
JS_PUSH_VALUE(ctx, prop);
|
|
JS_PUSH_VALUE(ctx, val);
|
|
err = js_update_props(ctx, this_obj);
|
|
JS_POP_VALUE(ctx, val);
|
|
JS_POP_VALUE(ctx, prop);
|
|
JS_POP_VALUE(ctx, this_obj);
|
|
if (err)
|
|
return JS_EXCEPTION;
|
|
p = JS_VALUE_TO_PTR(this_obj);
|
|
goto redo;
|
|
} else {
|
|
goto getset;
|
|
}
|
|
}
|
|
|
|
/* search in the prototype chain (getter/setters) */
|
|
for(;;) {
|
|
proto = p->proto;
|
|
if (proto == JS_NULL)
|
|
break;
|
|
p = JS_VALUE_TO_PTR(proto);
|
|
prototype_lookup:
|
|
pr = find_own_property(ctx, p, prop);
|
|
if (pr) {
|
|
if (unlikely(pr->prop_type == JS_PROP_GETSET)) {
|
|
JSValueArray *arr;
|
|
JSValue setter;
|
|
getset:
|
|
arr = JS_VALUE_TO_PTR(pr->value);
|
|
setter = arr->arr[1];
|
|
if (allow_tail_call) {
|
|
/* It is assumed "this_obj" already is on the stack
|
|
and that the stack has some slack to add one
|
|
element. */
|
|
ctx->sp[-2] = ctx->sp[0];
|
|
ctx->sp[-1] = setter;
|
|
ctx->sp[0] = val;
|
|
ctx->sp -= 2;
|
|
return JS_NewTailCall(1 | FRAME_CF_POP_RET);
|
|
} else {
|
|
JSGCRef val_ref, setter_ref, this_obj_ref;
|
|
int err;
|
|
JS_PUSH_VALUE(ctx, val);
|
|
JS_PUSH_VALUE(ctx, setter);
|
|
JS_PUSH_VALUE(ctx, this_obj);
|
|
err = JS_StackCheck(ctx, 3);
|
|
JS_POP_VALUE(ctx, this_obj);
|
|
JS_POP_VALUE(ctx, setter);
|
|
JS_POP_VALUE(ctx, val);
|
|
if (err)
|
|
return JS_EXCEPTION;
|
|
JS_PushArg(ctx, val);
|
|
JS_PushArg(ctx, setter);
|
|
JS_PushArg(ctx, this_obj);
|
|
return JS_Call(ctx, 1);
|
|
}
|
|
} else {
|
|
/* stop prototype chain lookup */
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* add the property in the object */
|
|
if (!is_obj)
|
|
return JS_ThrowTypeErrorNotAnObject(ctx);
|
|
return JS_DefinePropertyInternal(ctx, this_obj, prop, val, JS_UNDEFINED,
|
|
JS_DEF_PROP_HAS_VALUE);
|
|
}
|
|
|
|
JSValue JS_SetPropertyStr(JSContext *ctx, JSValue this_obj,
|
|
const char *str, JSValue val)
|
|
{
|
|
JSValue prop;
|
|
JSGCRef this_obj_ref, val_ref;
|
|
|
|
JS_PUSH_VALUE(ctx, this_obj);
|
|
JS_PUSH_VALUE(ctx, val);
|
|
prop = JS_NewString(ctx, str);
|
|
if (!JS_IsException(prop)) {
|
|
prop = JS_ToPropertyKey(ctx, prop);
|
|
}
|
|
JS_POP_VALUE(ctx, val);
|
|
JS_POP_VALUE(ctx, this_obj);
|
|
if (JS_IsException(prop))
|
|
return prop;
|
|
return JS_SetPropertyInternal(ctx, this_obj, prop, val, FALSE);
|
|
}
|
|
|
|
JSValue JS_SetPropertyUint32(JSContext *ctx, JSValue this_obj,
|
|
uint32_t idx, JSValue val)
|
|
{
|
|
if (idx > JS_SHORTINT_MAX)
|
|
return JS_ThrowRangeError(ctx, "invalid array index");
|
|
return JS_SetPropertyInternal(ctx, this_obj, JS_NewShortInt(idx), val, FALSE);
|
|
}
|
|
|
|
/* return JS_FALSE, JS_TRUE or JS_EXCEPTION. Return false only if the
|
|
property is not configurable which is never the case here. */
|
|
static JSValue JS_DeleteProperty(JSContext *ctx, JSValue this_obj,
|
|
JSValue prop)
|
|
{
|
|
JSObject *p;
|
|
JSProperty *pr, *pr1;
|
|
JSValueArray *arr;
|
|
int h, idx, hash_mask, last_idx, prop_count, first_free;
|
|
JSGCRef this_obj_ref;
|
|
|
|
JS_PUSH_VALUE(ctx, this_obj);
|
|
prop = JS_ToPropertyKey(ctx, prop);
|
|
JS_POP_VALUE(ctx, this_obj);
|
|
if (JS_IsException(prop))
|
|
return prop;
|
|
|
|
/* XXX: check return value */
|
|
if (!JS_IsPtr(this_obj))
|
|
return JS_TRUE;
|
|
p = JS_VALUE_TO_PTR(this_obj);
|
|
if (p->mtag != JS_MTAG_OBJECT)
|
|
return JS_TRUE;
|
|
|
|
arr = JS_VALUE_TO_PTR(p->props);
|
|
hash_mask = JS_VALUE_GET_INT(arr->arr[1]);
|
|
h = hash_prop(prop) & hash_mask;
|
|
idx = JS_VALUE_GET_INT(arr->arr[2 + h]);
|
|
last_idx = -1;
|
|
while (idx != 0) {
|
|
pr = (JSProperty *)(arr->arr + idx);
|
|
if (pr->key == prop) {
|
|
if (JS_IS_ROM_PTR(ctx, arr)) {
|
|
JSGCRef this_obj_ref;
|
|
int ret;
|
|
JS_PUSH_VALUE(ctx, this_obj);
|
|
ret = js_update_props(ctx, this_obj);
|
|
JS_POP_VALUE(ctx, this_obj);
|
|
if (ret)
|
|
return JS_EXCEPTION;
|
|
p = JS_VALUE_TO_PTR(this_obj);
|
|
arr = JS_VALUE_TO_PTR(p->props);
|
|
pr = (JSProperty *)(arr->arr + idx);
|
|
}
|
|
/* found: remove it */
|
|
if (last_idx >= 0) {
|
|
JSProperty *lpr = (JSProperty *)(arr->arr + last_idx);
|
|
lpr->hash_next = pr->hash_next;
|
|
} else {
|
|
arr->arr[2 + h] = pr->hash_next;
|
|
}
|
|
first_free = get_first_free(arr);
|
|
|
|
prop_count = JS_VALUE_GET_INT(arr->arr[0]);
|
|
arr->arr[0] = JS_NewShortInt(prop_count - 1);
|
|
pr->prop_type = JS_PROP_NORMAL;
|
|
pr->key = JS_UNINITIALIZED;
|
|
pr->value = JS_UNDEFINED;
|
|
|
|
/* update first_free if needed */
|
|
while (first_free > 2 + hash_mask + 1) {
|
|
pr1 = (JSProperty *)&arr->arr[first_free - 3];
|
|
if (pr1->key != JS_UNINITIALIZED)
|
|
break;
|
|
first_free -= 3;
|
|
}
|
|
|
|
/* update first_free */
|
|
if (first_free < arr->size) {
|
|
pr1 = (JSProperty *)&arr->arr[arr->size - 3];
|
|
pr1->hash_next = first_free << 1;
|
|
}
|
|
|
|
/* compact the properties if needed */
|
|
if ((2 + hash_mask + 1 + 3 * prop_count) < arr->size / 2)
|
|
js_compact_props(ctx, p);
|
|
return JS_TRUE;
|
|
}
|
|
last_idx = idx;
|
|
idx = pr->hash_next >> 1;
|
|
}
|
|
/* not found */
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSValue stdlib_init_class(JSContext *ctx, const JSROMClass *class_def)
|
|
{
|
|
JSValue obj, proto, parent_class, parent_proto;
|
|
JSGCRef parent_class_ref;
|
|
JSObject *p;
|
|
int ctor_idx = class_def->ctor_idx;
|
|
|
|
if (ctor_idx >= 0) {
|
|
int class_id = ctx->c_function_table[ctor_idx].magic;
|
|
obj = ctx->class_obj[class_id];
|
|
if (!JS_IsNull(obj))
|
|
return obj; /* already defined */
|
|
|
|
/* initialize the parent class if necessary */
|
|
if (!JS_IsNull(class_def->parent_class)) {
|
|
JSROMClass *parent_class_def = JS_VALUE_TO_PTR(class_def->parent_class);
|
|
int parent_class_id;
|
|
parent_class = stdlib_init_class(ctx, parent_class_def);
|
|
parent_class_id = ctx->c_function_table[parent_class_def->ctor_idx].magic;
|
|
parent_proto = ctx->class_proto[parent_class_id];
|
|
} else {
|
|
parent_class = JS_NULL;
|
|
parent_proto = ctx->class_proto[JS_CLASS_OBJECT];
|
|
}
|
|
/* initialize the prototype before. It is already defined only
|
|
for Object and Function */
|
|
proto = ctx->class_proto[class_id];
|
|
if (JS_IsNull(proto)) {
|
|
JS_PUSH_VALUE(ctx, parent_class);
|
|
proto = JS_NewObjectProtoClass(ctx, parent_proto, JS_CLASS_OBJECT, 0);
|
|
JS_POP_VALUE(ctx, parent_class);
|
|
ctx->class_proto[class_id] = proto;
|
|
}
|
|
p = JS_VALUE_TO_PTR(proto);
|
|
if (!JS_IsNull(class_def->proto_props))
|
|
p->props = class_def->proto_props;
|
|
|
|
if (JS_IsNull(parent_class))
|
|
parent_class = ctx->class_proto[JS_CLASS_CLOSURE];
|
|
obj = js_new_c_function_proto(ctx, ctor_idx, parent_class, FALSE, JS_NULL);
|
|
ctx->class_obj[class_id] = obj;
|
|
} else {
|
|
/* normal object */
|
|
obj = JS_NewObject(ctx);
|
|
}
|
|
p = JS_VALUE_TO_PTR(obj);
|
|
if (!JS_IsNull(class_def->props)) {
|
|
/* set the properties from the ROM. They are copied to RAM
|
|
when modified */
|
|
p->props = class_def->props;
|
|
}
|
|
return obj;
|
|
}
|
|
|
|
static void stdlib_init(JSContext *ctx, const JSValueArray *arr)
|
|
{
|
|
JSValue name, val;
|
|
int i;
|
|
|
|
for(i = 0; i < arr->size; i += 2) {
|
|
name = arr->arr[i];
|
|
val = arr->arr[i + 1];
|
|
if (JS_IsObject(ctx, val)) {
|
|
val = stdlib_init_class(ctx, JS_VALUE_TO_PTR(val));
|
|
} else if (val == JS_NULL) {
|
|
val = ctx->global_obj;
|
|
}
|
|
JS_DefinePropertyInternal(ctx, ctx->global_obj, name,
|
|
val, JS_NULL,
|
|
JS_DEF_PROP_HAS_VALUE);
|
|
}
|
|
}
|
|
|
|
static void dummy_write_func(void *opaque, const void *buf, size_t buf_len)
|
|
{
|
|
// fwrite(buf, 1, buf_len, stdout);
|
|
}
|
|
|
|
/* if prepare_compilation is true, the context will be used to compile
|
|
to a binary file. It is not expected to be used in the embedded
|
|
version */
|
|
JSContext *JS_NewContext2(void *mem_start, size_t mem_size, const JSSTDLibraryDef *stdlib_def, BOOL prepare_compilation)
|
|
{
|
|
JSContext *ctx;
|
|
JSValueArray *arr;
|
|
int i, mem_align;
|
|
|
|
#ifdef JS_PTR64
|
|
mem_align = 8;
|
|
#else
|
|
mem_align = 4;
|
|
#endif
|
|
mem_size = mem_size & ~(mem_align - 1);
|
|
assert(mem_size >= 1024);
|
|
assert(((uintptr_t)mem_start & (mem_align - 1)) == 0);
|
|
|
|
ctx = mem_start;
|
|
memset(ctx, 0, sizeof(*ctx));
|
|
ctx->class_count = stdlib_def->class_count;
|
|
ctx->class_obj = ctx->class_proto + ctx->class_count;
|
|
ctx->heap_base = (void *)(ctx->class_proto + 2 * ctx->class_count);
|
|
ctx->heap_free = ctx->heap_base;
|
|
ctx->stack_top = mem_start + mem_size;
|
|
ctx->sp = (JSValue *)ctx->stack_top;
|
|
ctx->stack_bottom = ctx->sp;
|
|
ctx->fp = ctx->sp;
|
|
ctx->min_free_size = JS_MIN_FREE_SIZE;
|
|
#ifdef DEBUG_GC
|
|
ctx->dummy_block = JS_NULL;
|
|
ctx->unique_strings = JS_NULL;
|
|
#endif
|
|
ctx->random_state = 1;
|
|
ctx->write_func = dummy_write_func;
|
|
for(i = 0; i < JS_STRING_POS_CACHE_SIZE; i++)
|
|
ctx->string_pos_cache[i].str = JS_NULL;
|
|
|
|
if (prepare_compilation) {
|
|
int atom_table_len;
|
|
JSValueArray *arr, *arr1;
|
|
uint8_t *ptr;
|
|
|
|
/* for compilation, no stdlib is needed. Only the atoms
|
|
corresponding to JS_ATOM_x are needed and they are stored
|
|
in RAM. */
|
|
/* copy the atoms to a fixed location at the beginning of the
|
|
heap */
|
|
ctx->atom_table = (JSWord *)ctx->heap_free;
|
|
atom_table_len = stdlib_def->sorted_atoms_offset;
|
|
memcpy(ctx->heap_free, stdlib_def->stdlib_table,
|
|
atom_table_len * sizeof(JSWord));
|
|
ctx->heap_free += atom_table_len * sizeof(JSWord);
|
|
|
|
/* allocate the sorted atom table and populate it */
|
|
arr1 = (JSValueArray *)(stdlib_def->stdlib_table + atom_table_len);
|
|
arr = js_alloc_value_array(ctx, 0, arr1->size);
|
|
ctx->unique_strings = JS_VALUE_FROM_PTR(arr);
|
|
for(i = 0; i < arr1->size; i++) {
|
|
ptr = JS_VALUE_TO_PTR(arr1->arr[i]);
|
|
ptr = ptr - (uint8_t *)stdlib_def->stdlib_table +
|
|
(uint8_t *)ctx->atom_table;
|
|
arr->arr[i] = JS_VALUE_FROM_PTR(ptr);
|
|
}
|
|
ctx->unique_strings_len = arr1->size;
|
|
} else {
|
|
ctx->atom_table = stdlib_def->stdlib_table;
|
|
ctx->rom_atom_tables[0] = (JSValueArray *)(stdlib_def->stdlib_table +
|
|
stdlib_def->sorted_atoms_offset);
|
|
ctx->n_rom_atom_tables = 1;
|
|
ctx->c_function_table = stdlib_def->c_function_table;
|
|
ctx->c_finalizer_table = stdlib_def->c_finalizer_table;
|
|
ctx->unique_strings = JS_NULL;
|
|
ctx->unique_strings_len = 0;
|
|
}
|
|
|
|
|
|
ctx->current_exception = JS_UNDEFINED;
|
|
#ifdef DEBUG_GC
|
|
/* set the dummy block at the start of the memory */
|
|
{
|
|
JSByteArray *barr;
|
|
barr = js_alloc_byte_array(ctx, (min_int(mem_size / 2, 1 << 17)) & ~(JSW - 1));
|
|
ctx->dummy_block = JS_VALUE_FROM_PTR(barr);
|
|
}
|
|
#endif
|
|
|
|
arr = js_alloc_value_array(ctx, 0, 3);
|
|
arr->arr[0] = JS_NewShortInt(0); /* prop_count */
|
|
arr->arr[1] = JS_NewShortInt(0); /* hash_mark */
|
|
arr->arr[2] = JS_NewShortInt(0); /* hash_table[1] */
|
|
ctx->empty_props = JS_VALUE_FROM_PTR(arr);
|
|
for(i = 0; i < ctx->class_count; i++)
|
|
ctx->class_proto[i] = JS_NULL;
|
|
for(i = 0; i < ctx->class_count; i++)
|
|
ctx->class_obj[i] = JS_NULL;
|
|
/* must be done first so that the prototype of Object.prototype is
|
|
JS_NULL */
|
|
ctx->class_proto[JS_CLASS_OBJECT] = JS_NewObject(ctx);
|
|
/* must be done for proper function init */
|
|
ctx->class_proto[JS_CLASS_CLOSURE] = JS_NewObject(ctx);
|
|
|
|
ctx->global_obj = JS_NewObject(ctx);
|
|
ctx->minus_zero = js_alloc_float64(ctx, -0.0); /* XXX: use a ROM value instead */
|
|
|
|
if (!prepare_compilation) {
|
|
stdlib_init(ctx, (JSValueArray *)(stdlib_def->stdlib_table + stdlib_def->global_object_offset));
|
|
}
|
|
|
|
return ctx;
|
|
}
|
|
|
|
JSContext *JS_NewContext(void *mem_start, size_t mem_size, const JSSTDLibraryDef *stdlib_def)
|
|
{
|
|
return JS_NewContext2(mem_start, mem_size, stdlib_def, FALSE);
|
|
}
|
|
|
|
void JS_FreeContext(JSContext *ctx)
|
|
{
|
|
uint8_t *ptr;
|
|
int size;
|
|
JSObject *p;
|
|
|
|
/* call the user C finalizers */
|
|
/* XXX: could disable it when prepare_compilation = true */
|
|
ptr = ctx->heap_base;
|
|
while (ptr < ctx->heap_free) {
|
|
size = get_mblock_size(ptr);
|
|
p = (JSObject *)ptr;
|
|
if (p->mtag == JS_MTAG_OBJECT && p->class_id >= JS_CLASS_USER &&
|
|
ctx->c_finalizer_table[p->class_id - JS_CLASS_USER] != NULL) {
|
|
ctx->c_finalizer_table[p->class_id - JS_CLASS_USER](ctx, p->u.user.opaque);
|
|
}
|
|
ptr += size;
|
|
}
|
|
}
|
|
|
|
void JS_SetContextOpaque(JSContext *ctx, void *opaque)
|
|
{
|
|
ctx->opaque = opaque;
|
|
}
|
|
|
|
void JS_SetInterruptHandler(JSContext *ctx, JSInterruptHandler *interrupt_handler)
|
|
{
|
|
ctx->interrupt_handler = interrupt_handler;
|
|
}
|
|
|
|
void JS_SetLogFunc(JSContext *ctx, JSWriteFunc *write_func)
|
|
{
|
|
ctx->write_func = write_func;
|
|
}
|
|
|
|
void JS_SetRandomSeed(JSContext *ctx, uint64_t seed)
|
|
{
|
|
ctx->random_state = seed;
|
|
}
|
|
|
|
JSValue JS_GetGlobalObject(JSContext *ctx)
|
|
{
|
|
return ctx->global_obj;
|
|
}
|
|
|
|
static JSValue get_var_ref(JSContext *ctx, JSValue *pfirst_var_ref, JSValue *pval)
|
|
{
|
|
JSValue val;
|
|
JSVarRef *p;
|
|
|
|
val = *pfirst_var_ref;
|
|
for(;;) {
|
|
if (val == JS_NULL)
|
|
break;
|
|
p = JS_VALUE_TO_PTR(val);
|
|
assert(!p->is_detached);
|
|
if (p->u.pvalue == pval)
|
|
return val;
|
|
val = p->u.next;
|
|
}
|
|
|
|
p = js_malloc(ctx, sizeof(JSVarRef), JS_MTAG_VARREF);
|
|
if (!p)
|
|
return JS_EXCEPTION;
|
|
p->is_detached = FALSE;
|
|
p->u.pvalue = pval;
|
|
p->u.next = *pfirst_var_ref;
|
|
val = JS_VALUE_FROM_PTR(p);
|
|
*pfirst_var_ref = val;
|
|
return val;
|
|
}
|
|
|
|
#define FRAME_OFFSET_ARG0 4
|
|
#define FRAME_OFFSET_FUNC_OBJ 3
|
|
#define FRAME_OFFSET_THIS_OBJ 2
|
|
#define FRAME_OFFSET_CALL_FLAGS 1
|
|
#define FRAME_OFFSET_SAVED_FP 0
|
|
#define FRAME_OFFSET_CUR_PC (-1) /* current pc_offset */
|
|
#define FRAME_OFFSET_FIRST_VARREF (-2)
|
|
#define FRAME_OFFSET_VAR0 (-3)
|
|
|
|
/* stack layout:
|
|
|
|
padded_args (padded_argc - argc)
|
|
args (argc)
|
|
func_obj fp[3]
|
|
this_obj fp[2]
|
|
call_flags (int) fp[1]
|
|
saved_fp (int) fp[0]
|
|
cur_pc (int) fp[-1]
|
|
first_var_ref (val) fp[-2]
|
|
vars (var_count)
|
|
temp stack (pointed by sp)
|
|
*/
|
|
|
|
#define SP_TO_VALUE(ctx, fp) JS_NewShortInt((uint8_t *)(fp) - (uint8_t *)ctx)
|
|
#define VALUE_TO_SP(ctx, val) (void *)((uint8_t *)ctx + JS_VALUE_GET_INT(val))
|
|
|
|
/* buf_end points to the end of the buffer (after the final '\0') */
|
|
static __js_printf_like(3, 4) void cprintf(char **pp, char *buf_end, const char *fmt, ...)
|
|
{
|
|
char *p;
|
|
va_list ap;
|
|
|
|
p = *pp;
|
|
if ((p + 1) >= buf_end)
|
|
return;
|
|
va_start(ap, fmt);
|
|
js_vsnprintf(p, buf_end - p, fmt, ap);
|
|
va_end(ap);
|
|
p += strlen(p);
|
|
*pp = p;
|
|
}
|
|
|
|
static JSValue reloc_c_func_name(JSContext *ctx, JSValue val)
|
|
{
|
|
return val;
|
|
}
|
|
|
|
/* no memory allocation is done */
|
|
/* XXX: handle bound functions */
|
|
static JSValue js_function_get_length_name1(JSContext *ctx, JSValue *this_val,
|
|
int is_name, JSFunctionBytecode **pb)
|
|
{
|
|
int short_func_idx;
|
|
const JSCFunctionDef *fd;
|
|
JSValue ret;
|
|
|
|
if (!JS_IsPtr(*this_val)) {
|
|
if (JS_VALUE_GET_SPECIAL_TAG(*this_val) != JS_TAG_SHORT_FUNC)
|
|
goto fail;
|
|
short_func_idx = JS_VALUE_GET_SPECIAL_VALUE(*this_val);
|
|
goto short_func;
|
|
} else {
|
|
JSObject *p = JS_VALUE_TO_PTR(*this_val);
|
|
JSFunctionBytecode *b;
|
|
if (p->mtag != JS_MTAG_OBJECT)
|
|
goto fail;
|
|
if (p->class_id == JS_CLASS_CLOSURE) {
|
|
b = JS_VALUE_TO_PTR(p->u.closure.func_bytecode);
|
|
if (is_name) {
|
|
/* XXX: directly set func_name to the empty string ? */
|
|
if (b->func_name == JS_NULL)
|
|
ret = js_get_atom(ctx, JS_ATOM_empty);
|
|
else
|
|
ret = b->func_name;
|
|
} else {
|
|
ret = JS_NewShortInt(b->arg_count);
|
|
}
|
|
*pb = b;
|
|
return ret;
|
|
} else if (p->class_id == JS_CLASS_C_FUNCTION) {
|
|
short_func_idx = p->u.cfunc.idx;
|
|
short_func:
|
|
fd = &ctx->c_function_table[short_func_idx];
|
|
if (is_name) {
|
|
ret = reloc_c_func_name(ctx, fd->name);
|
|
} else {
|
|
ret = JS_NewShortInt(fd->arg_count);
|
|
}
|
|
*pb = NULL;
|
|
return ret;
|
|
} else {
|
|
fail:
|
|
*pb = NULL;
|
|
return JS_NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
static uint32_t get_bit(const uint8_t *buf, uint32_t index)
|
|
{
|
|
return (buf[index >> 3] >> (7 - (index & 7))) & 1;
|
|
}
|
|
|
|
static uint32_t get_bits_slow(const uint8_t *buf, uint32_t index, int n)
|
|
{
|
|
int i;
|
|
uint32_t val;
|
|
val = 0;
|
|
for(i = 0; i < n; i++)
|
|
val |= get_bit(buf, index + i) << (n - 1 - i);
|
|
return val;
|
|
}
|
|
|
|
static uint32_t get_bits(const uint8_t *buf, uint32_t buf_len,
|
|
uint32_t index, int n)
|
|
{
|
|
uint32_t val, pos;
|
|
|
|
pos = index >> 3;
|
|
if (unlikely(n > 25 || (pos + 3) >= buf_len)) {
|
|
/* slow case */
|
|
return get_bits_slow(buf, index, n);
|
|
} else {
|
|
/* fast case */
|
|
val = get_be32(buf + pos);
|
|
return (val >> (32 - (index & 7) - n)) & ((1 << n) - 1);
|
|
}
|
|
}
|
|
|
|
static uint32_t get_ugolomb(const uint8_t *buf, uint32_t buf_len,
|
|
uint32_t *pindex)
|
|
{
|
|
uint32_t index = *pindex;
|
|
int i;
|
|
uint32_t v;
|
|
|
|
i = 0;
|
|
for(;;) {
|
|
if (get_bit(buf, index++))
|
|
break;
|
|
i++;
|
|
if (i == 32) {
|
|
/* error */
|
|
*pindex = index;
|
|
return 0xffffffff;
|
|
}
|
|
}
|
|
if (i == 0) {
|
|
v = 0;
|
|
} else {
|
|
v = ((1 << i) | get_bits(buf, buf_len, index, i)) - 1;
|
|
index += i;
|
|
}
|
|
*pindex = index;
|
|
// printf("get_ugolomb: v=%d\n", v);
|
|
return v;
|
|
}
|
|
|
|
static int32_t get_sgolomb(const uint8_t *buf, uint32_t buf_len,
|
|
uint32_t *pindex)
|
|
{
|
|
uint32_t val;
|
|
val = get_ugolomb(buf, buf_len, pindex);
|
|
return (val >> 1) ^ -(val & 1);
|
|
}
|
|
|
|
static int get_pc2line_hoisted_code_len(const uint8_t *buf, size_t buf_len)
|
|
{
|
|
size_t i = buf_len;
|
|
int v = 0;
|
|
while (i > 0) {
|
|
i--;
|
|
v = (v << 7) | (buf[i] & 0x7f);
|
|
if ((buf[i] & 0x80) == 0)
|
|
break;
|
|
}
|
|
return v;
|
|
}
|
|
|
|
/* line_num, col_num and index are updated */
|
|
static void get_pc2line(int *pline_num, int *pcol_num, const uint8_t *buf,
|
|
uint32_t buf_len, uint32_t *pindex, BOOL has_column)
|
|
{
|
|
int line_delta, line_num, col_num, col_delta;
|
|
|
|
line_num = *pline_num;
|
|
col_num = *pcol_num;
|
|
|
|
line_delta = get_sgolomb(buf, buf_len, pindex);
|
|
line_num += line_delta;
|
|
if (has_column) {
|
|
if (line_delta == 0) {
|
|
col_delta = get_sgolomb(buf, buf_len, pindex);
|
|
col_num += col_delta;
|
|
} else {
|
|
col_num = get_ugolomb(buf, buf_len, pindex) + 1;
|
|
}
|
|
} else {
|
|
col_num = 0;
|
|
}
|
|
*pline_num = line_num;
|
|
*pcol_num = col_num;
|
|
}
|
|
|
|
/* return 0 if line/col number info */
|
|
static int find_line_col(int *pcol_num, JSFunctionBytecode *b, uint32_t pc)
|
|
{
|
|
JSByteArray *arr, *pc2line;
|
|
int pos, op, line_num, col_num;
|
|
uint32_t pc2line_pos;
|
|
|
|
if (b->pc2line == JS_NULL)
|
|
goto fail;
|
|
arr = JS_VALUE_TO_PTR(b->byte_code);
|
|
pc2line = JS_VALUE_TO_PTR(b->pc2line);
|
|
|
|
/* skip the hoisted code */
|
|
pos = get_pc2line_hoisted_code_len(pc2line->buf, pc2line->size);
|
|
if (pc < pos)
|
|
pc = pos;
|
|
pc2line_pos = 0;
|
|
line_num = 1;
|
|
col_num = 1;
|
|
while (pos < arr->size) {
|
|
get_pc2line(&line_num, &col_num, pc2line->buf, pc2line->size,
|
|
&pc2line_pos, b->has_column);
|
|
if (pos == pc) {
|
|
*pcol_num = col_num;
|
|
return line_num;
|
|
}
|
|
op = arr->buf[pos];
|
|
pos += opcode_info[op].size;
|
|
}
|
|
fail:
|
|
*pcol_num = 0;
|
|
return 0;
|
|
}
|
|
|
|
static const char *get_func_name(JSContext *ctx, JSValue func_obj,
|
|
JSCStringBuf *str_buf, JSFunctionBytecode **pb)
|
|
{
|
|
JSValue val;
|
|
val = js_function_get_length_name1(ctx, &func_obj, 1, pb);
|
|
if (JS_IsNull(val))
|
|
return NULL;
|
|
return JS_ToCString(ctx, val, str_buf);
|
|
}
|
|
|
|
static void build_backtrace(JSContext *ctx, JSValue error_obj,
|
|
const char *filename, int line_num, int col_num, int skip_level)
|
|
{
|
|
JSObject *p1;
|
|
char buf[128], *p, *buf_end, *line_start;
|
|
const char *str;
|
|
JSValue *fp, stack_str;
|
|
JSCStringBuf str_buf;
|
|
JSFunctionBytecode *b;
|
|
int level;
|
|
JSGCRef error_obj_ref;
|
|
|
|
if (!JS_IsError(ctx, error_obj))
|
|
return;
|
|
p = buf;
|
|
buf_end = buf + sizeof(buf);
|
|
p[0] = '\0';
|
|
if (filename) {
|
|
cprintf(&p, buf_end, " at %s:%d:%d\n", filename, line_num, col_num);
|
|
}
|
|
fp = ctx->fp;
|
|
level = 0;
|
|
while (fp != (JSValue *)ctx->stack_top && level < 10) {
|
|
if (skip_level != 0) {
|
|
skip_level--;
|
|
} else {
|
|
line_start = p;
|
|
str = get_func_name(ctx, fp[FRAME_OFFSET_FUNC_OBJ], &str_buf, &b);
|
|
if (!str)
|
|
str = "<anonymous>";
|
|
cprintf(&p, buf_end, " at %s", str);
|
|
if (b) {
|
|
int pc, line_num, col_num;
|
|
const char *filename;
|
|
filename = JS_ToCString(ctx, b->filename, &str_buf);
|
|
pc = JS_VALUE_GET_INT(fp[FRAME_OFFSET_CUR_PC]) - 1;
|
|
line_num = find_line_col(&col_num, b, pc);
|
|
cprintf(&p, buf_end, " (%s", filename);
|
|
if (line_num != 0) {
|
|
cprintf(&p, buf_end, ":%d", line_num);
|
|
if (col_num != 0)
|
|
cprintf(&p, buf_end, ":%d", col_num);
|
|
}
|
|
cprintf(&p, buf_end, ")");
|
|
} else {
|
|
cprintf(&p, buf_end, " (native)");
|
|
}
|
|
cprintf(&p, buf_end, "\n");
|
|
/* if truncated line, remove it and stop */
|
|
if ((p + 1) >= buf_end) {
|
|
*line_start = '\0';
|
|
break;
|
|
}
|
|
level++;
|
|
}
|
|
fp = VALUE_TO_SP(ctx, fp[FRAME_OFFSET_SAVED_FP]);
|
|
}
|
|
|
|
JS_PUSH_VALUE(ctx, error_obj);
|
|
stack_str = JS_NewString(ctx, buf);
|
|
JS_POP_VALUE(ctx, error_obj);
|
|
p1 = JS_VALUE_TO_PTR(error_obj);
|
|
p1->u.error.stack = stack_str;
|
|
}
|
|
|
|
#define HINT_STRING 0
|
|
#define HINT_NUMBER 1
|
|
#define HINT_NONE HINT_NUMBER
|
|
|
|
static JSValue JS_ToPrimitive(JSContext *ctx, JSValue val, int hint)
|
|
{
|
|
int i, atom;
|
|
JSValue method, ret;
|
|
JSGCRef val_ref, method_ref;
|
|
|
|
if (JS_IsPrimitive(ctx, val))
|
|
return val;
|
|
for(i = 0; i < 2; i++) {
|
|
if ((i ^ hint) == 0) {
|
|
atom = JS_ATOM_toString;
|
|
} else {
|
|
atom = JS_ATOM_valueOf;
|
|
}
|
|
JS_PUSH_VALUE(ctx, val);
|
|
method = JS_GetProperty(ctx, val, js_get_atom(ctx, atom));
|
|
JS_POP_VALUE(ctx, val);
|
|
if (JS_IsException(method))
|
|
return method;
|
|
if (JS_IsFunction(ctx, method)) {
|
|
int err;
|
|
JS_PUSH_VALUE(ctx, method);
|
|
JS_PUSH_VALUE(ctx, val);
|
|
err = JS_StackCheck(ctx, 2);
|
|
JS_POP_VALUE(ctx, val);
|
|
JS_POP_VALUE(ctx, method);
|
|
if (err)
|
|
return JS_EXCEPTION;
|
|
|
|
JS_PushArg(ctx, method);
|
|
JS_PushArg(ctx, val);
|
|
JS_PUSH_VALUE(ctx, val);
|
|
ret = JS_Call(ctx, 0);
|
|
JS_POP_VALUE(ctx, val);
|
|
if (JS_IsException(ret))
|
|
return ret;
|
|
if (!JS_IsObject(ctx, ret))
|
|
return ret;
|
|
}
|
|
}
|
|
return JS_ThrowTypeError(ctx, "toPrimitive");
|
|
}
|
|
|
|
/* return a string or an exception */
|
|
static JSValue js_dtoa2(JSContext *ctx, double d, int radix, int n_digits, int flags)
|
|
{
|
|
int len_max, len;
|
|
JSValue str;
|
|
JSGCRef str_ref;
|
|
JSByteArray *tmp_arr, *p;
|
|
|
|
len_max = js_dtoa_max_len(d, radix, n_digits, flags);
|
|
p = js_alloc_byte_array(ctx, len_max + 1);
|
|
if (!p)
|
|
return JS_EXCEPTION;
|
|
/* allocate the temporary buffer */
|
|
str = JS_VALUE_FROM_PTR(p);
|
|
JS_PUSH_VALUE(ctx, str);
|
|
tmp_arr = js_alloc_byte_array(ctx, sizeof(JSDTOATempMem));
|
|
JS_POP_VALUE(ctx, str);
|
|
if (!tmp_arr)
|
|
return JS_EXCEPTION;
|
|
p = JS_VALUE_TO_PTR(str);
|
|
/* Note: tmp_arr->buf is always 32 bit aligned */
|
|
len = js_dtoa((char *)p->buf, d, radix, n_digits, flags, (JSDTOATempMem *)tmp_arr->buf);
|
|
js_free(ctx, tmp_arr);
|
|
return js_byte_array_to_string(ctx, str, len, TRUE);
|
|
}
|
|
|
|
JSValue JS_ToString(JSContext *ctx, JSValue val)
|
|
{
|
|
char buf[128];
|
|
int atom;
|
|
const char *str;
|
|
|
|
redo:
|
|
if (JS_IsInt(val)) {
|
|
int len;
|
|
len = i32toa(buf, JS_VALUE_GET_INT(val));
|
|
buf[len] = '\0';
|
|
goto ret_buf;
|
|
} else
|
|
#ifdef JS_USE_SHORT_FLOAT
|
|
if (JS_IsShortFloat(val)) {
|
|
return js_dtoa2(ctx, js_get_short_float(val), 10, 0, JS_DTOA_FORMAT_FREE);
|
|
} else
|
|
#endif
|
|
if (JS_IsPtr(val)) {
|
|
void *ptr = JS_VALUE_TO_PTR(val);
|
|
int mtag = js_get_mtag(ptr);
|
|
switch(mtag) {
|
|
case JS_MTAG_OBJECT:
|
|
to_primitive:
|
|
val = JS_ToPrimitive(ctx, val, HINT_STRING);
|
|
if (JS_IsException(val))
|
|
return val;
|
|
goto redo;
|
|
case JS_MTAG_STRING:
|
|
return val;
|
|
case JS_MTAG_FLOAT64:
|
|
{
|
|
JSFloat64 *p = ptr;
|
|
return js_dtoa2(ctx, p->u.dval, 10, 0, JS_DTOA_FORMAT_FREE);
|
|
}
|
|
default:
|
|
js_snprintf(buf, sizeof(buf), "[mtag %d]", mtag);
|
|
goto ret_buf;
|
|
}
|
|
} else {
|
|
switch(JS_VALUE_GET_SPECIAL_TAG(val)) {
|
|
case JS_TAG_NULL:
|
|
atom = JS_ATOM_null;
|
|
goto ret_atom;
|
|
case JS_TAG_UNDEFINED:
|
|
atom = JS_ATOM_undefined;
|
|
goto ret_atom;
|
|
case JS_TAG_BOOL:
|
|
if (JS_VALUE_GET_SPECIAL_VALUE(val))
|
|
atom = JS_ATOM_true;
|
|
else
|
|
atom = JS_ATOM_false;
|
|
ret_atom:
|
|
return js_get_atom(ctx, atom);
|
|
case JS_TAG_STRING_CHAR:
|
|
return val;
|
|
case JS_TAG_SHORT_FUNC:
|
|
goto to_primitive;
|
|
default:
|
|
str = "?";
|
|
goto ret_str;
|
|
ret_buf:
|
|
str = buf;
|
|
ret_str:
|
|
return JS_NewString(ctx, str);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* return either a unique string or an integer. Strings representing
|
|
a short integer are converted to short integer */
|
|
static JSValue JS_ToPropertyKey(JSContext *ctx, JSValue val)
|
|
{
|
|
int32_t n;
|
|
if (JS_IsInt(val))
|
|
return val;
|
|
val = JS_ToString(ctx, val);
|
|
if (JS_IsException(val))
|
|
return val;
|
|
if (is_num_string(ctx, &n, val))
|
|
return JS_NewShortInt(n);
|
|
else
|
|
return JS_MakeUniqueString(ctx, val);
|
|
}
|
|
|
|
static int skip_spaces(const char *p1)
|
|
{
|
|
const char *p = p1;
|
|
int c;
|
|
for(;;) {
|
|
c = *p;
|
|
if (!((c >= 0x09 && c <= 0x0d) || (c == 0x20)))
|
|
break;
|
|
p++;
|
|
}
|
|
return p - p1;
|
|
}
|
|
|
|
/* JS_ToString() specific behaviors */
|
|
#define JS_ATOD_TOSTRING (1 << 8)
|
|
|
|
/* 'val' must be a string */
|
|
static int js_atod1(JSContext *ctx, double *pres, JSValue val,
|
|
int radix, int flags)
|
|
{
|
|
JSString *p;
|
|
JSByteArray *tmp_arr;
|
|
double d;
|
|
JSGCRef val_ref;
|
|
const char *p1;
|
|
|
|
if (JS_VALUE_GET_SPECIAL_TAG(val) == JS_TAG_STRING_CHAR) {
|
|
int c = JS_VALUE_GET_SPECIAL_VALUE(val);
|
|
if (c >= '0' && c <= '9') {
|
|
*pres = c - '0';
|
|
} else {
|
|
*pres = NAN;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
JS_PUSH_VALUE(ctx, val);
|
|
tmp_arr = js_alloc_byte_array(ctx, sizeof(JSATODTempMem));
|
|
JS_POP_VALUE(ctx, val);
|
|
if (!tmp_arr) {
|
|
*pres = NAN;
|
|
return -1;
|
|
}
|
|
p = JS_VALUE_TO_PTR(val);
|
|
p1 = (char *)p->buf;
|
|
p1 += skip_spaces(p1);
|
|
if ((p1 - (char *)p->buf) == p->len) {
|
|
if (flags & JS_ATOD_TOSTRING)
|
|
d = 0;
|
|
else
|
|
d = NAN;
|
|
goto done;
|
|
}
|
|
d = js_atod(p1, &p1, radix, flags, (JSATODTempMem *)tmp_arr->buf);
|
|
js_free(ctx, tmp_arr);
|
|
if (flags & JS_ATOD_TOSTRING) {
|
|
p1 += skip_spaces(p1);
|
|
if ((p1 - (char *)p->buf) < p->len)
|
|
d = NAN;
|
|
}
|
|
done:
|
|
*pres = d;
|
|
return 0;
|
|
}
|
|
|
|
/* Note: can fail due to memory allocation even if primitive type */
|
|
int JS_ToNumber(JSContext *ctx, double *pres, JSValue val)
|
|
{
|
|
redo:
|
|
if (JS_IsInt(val)) {
|
|
*pres = (double)JS_VALUE_GET_INT(val);
|
|
return 0;
|
|
} else
|
|
#ifdef JS_USE_SHORT_FLOAT
|
|
if (JS_IsShortFloat(val)) {
|
|
*pres = js_get_short_float(val);
|
|
return 0;
|
|
} else
|
|
#endif
|
|
if (JS_IsPtr(val)) {
|
|
void *ptr = JS_VALUE_TO_PTR(val);
|
|
switch(js_get_mtag(ptr)) {
|
|
case JS_MTAG_STRING:
|
|
goto atod;
|
|
case JS_MTAG_FLOAT64:
|
|
{
|
|
JSFloat64 *p = ptr;
|
|
*pres = p->u.dval;
|
|
return 0;
|
|
}
|
|
case JS_MTAG_OBJECT:
|
|
val = JS_ToPrimitive(ctx, val, HINT_NUMBER);
|
|
if (JS_IsException(val)) {
|
|
*pres = NAN;
|
|
return -1;
|
|
}
|
|
goto redo;
|
|
default:
|
|
*pres = NAN;
|
|
return 0;
|
|
}
|
|
} else {
|
|
switch(JS_VALUE_GET_SPECIAL_TAG(val)) {
|
|
case JS_TAG_NULL:
|
|
case JS_TAG_BOOL:
|
|
*pres = (double)JS_VALUE_GET_SPECIAL_VALUE(val);
|
|
return 0;
|
|
case JS_TAG_UNDEFINED:
|
|
*pres = NAN;
|
|
return 0;
|
|
case JS_TAG_STRING_CHAR:
|
|
atod:
|
|
return js_atod1(ctx, pres, val, 0,
|
|
JS_ATOD_ACCEPT_BIN_OCT | JS_ATOD_TOSTRING);
|
|
default:
|
|
*pres = NAN;
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int JS_ToInt32Internal(JSContext *ctx, int *pres, JSValue val, BOOL sat_flag)
|
|
{
|
|
int32_t ret;
|
|
double d;
|
|
|
|
if (JS_IsInt(val)) {
|
|
ret = JS_VALUE_GET_INT(val);
|
|
} else
|
|
#ifdef JS_USE_SHORT_FLOAT
|
|
if (JS_IsShortFloat(val)) {
|
|
d = js_get_short_float(val);
|
|
goto handle_float64;
|
|
} else
|
|
#endif
|
|
if (JS_IsPtr(val)) {
|
|
uint64_t u;
|
|
int e;
|
|
|
|
handle_number:
|
|
if (JS_ToNumber(ctx, &d, val)) {
|
|
*pres = 0;
|
|
return -1;
|
|
}
|
|
#ifdef JS_USE_SHORT_FLOAT
|
|
handle_float64:
|
|
#endif
|
|
u = float64_as_uint64(d);
|
|
e = (u >> 52) & 0x7ff;
|
|
if (likely(e <= (1023 + 30))) {
|
|
/* fast case */
|
|
ret = (int32_t)d;
|
|
} else if (!sat_flag) {
|
|
if (e <= (1023 + 30 + 53)) {
|
|
uint64_t v;
|
|
/* remainder modulo 2^32 */
|
|
v = (u & (((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 >> 63)
|
|
ret = -ret;
|
|
} else {
|
|
ret = 0; /* also handles NaN and +inf */
|
|
}
|
|
} else {
|
|
if (e == 2047 && (u & (((uint64_t)1 << 52) - 1)) != 0) {
|
|
/* nan */
|
|
ret = 0;
|
|
} else {
|
|
/* take the sign into account */
|
|
if (u >> 63)
|
|
ret = 0x80000000;
|
|
else
|
|
ret = 0x7fffffff;
|
|
}
|
|
}
|
|
} else {
|
|
switch(JS_VALUE_GET_SPECIAL_TAG(val)) {
|
|
case JS_TAG_BOOL:
|
|
case JS_TAG_NULL:
|
|
case JS_TAG_UNDEFINED:
|
|
ret = JS_VALUE_GET_SPECIAL_VALUE(val);
|
|
break;
|
|
default:
|
|
goto handle_number;
|
|
}
|
|
}
|
|
*pres = ret;
|
|
return 0;
|
|
}
|
|
|
|
int JS_ToInt32(JSContext *ctx, int *pres, JSValue val)
|
|
{
|
|
return JS_ToInt32Internal(ctx, pres, val, FALSE);
|
|
}
|
|
|
|
int JS_ToUint32(JSContext *ctx, uint32_t *pres, JSValue val)
|
|
{
|
|
return JS_ToInt32Internal(ctx, (int *)pres, val, FALSE);
|
|
}
|
|
|
|
int JS_ToInt32Sat(JSContext *ctx, int *pres, JSValue val)
|
|
{
|
|
return JS_ToInt32Internal(ctx, pres, val, TRUE);
|
|
}
|
|
|
|
static int JS_ToInt32Clamp(JSContext *ctx, int *pres, JSValue val,
|
|
int min, int max, int min_offset)
|
|
{
|
|
int res = JS_ToInt32Sat(ctx, pres, 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_ToUint8Clamp(JSContext *ctx, int *pres, JSValue val)
|
|
{
|
|
int32_t ret;
|
|
double d;
|
|
|
|
if (JS_IsInt(val)) {
|
|
ret = JS_VALUE_GET_INT(val);
|
|
if (ret < 0)
|
|
ret = 0;
|
|
else if (ret > 255)
|
|
ret = 255;
|
|
} else
|
|
#ifdef JS_USE_SHORT_FLOAT
|
|
if (JS_IsShortFloat(val)) {
|
|
d = js_get_short_float(val);
|
|
goto handle_float64;
|
|
} else
|
|
#endif
|
|
if (JS_IsPtr(val)) {
|
|
handle_number:
|
|
if (JS_ToNumber(ctx, &d, val)) {
|
|
*pres = 0;
|
|
return -1;
|
|
}
|
|
#ifdef JS_USE_SHORT_FLOAT
|
|
handle_float64:
|
|
#endif
|
|
if (d < 0 || isnan(d))
|
|
ret = 0;
|
|
else if (d > 255)
|
|
ret = 255;
|
|
else
|
|
ret = js_lrint(d);
|
|
} else {
|
|
switch(JS_VALUE_GET_SPECIAL_TAG(val)) {
|
|
case JS_TAG_BOOL:
|
|
case JS_TAG_NULL:
|
|
case JS_TAG_UNDEFINED:
|
|
ret = JS_VALUE_GET_SPECIAL_VALUE(val);
|
|
break;
|
|
default:
|
|
goto handle_number;
|
|
}
|
|
}
|
|
*pres = ret;
|
|
return 0;
|
|
}
|
|
|
|
static int js_get_length32(JSContext *ctx, uint32_t *pres, JSValue obj)
|
|
{
|
|
JSValue len_val;
|
|
len_val = JS_GetProperty(ctx, obj, js_get_atom(ctx, JS_ATOM_length));
|
|
if (JS_IsException(len_val)) {
|
|
*pres = 0;
|
|
return -1;
|
|
}
|
|
return JS_ToUint32(ctx, pres, len_val);
|
|
}
|
|
|
|
static no_inline JSValue js_add_slow(JSContext *ctx)
|
|
{
|
|
JSValue *op1, *op2;
|
|
|
|
op1 = &ctx->sp[1];
|
|
op2 = &ctx->sp[0];
|
|
*op1 = JS_ToPrimitive(ctx, *op1, HINT_NONE);
|
|
if (JS_IsException(*op1))
|
|
return JS_EXCEPTION;
|
|
*op2 = JS_ToPrimitive(ctx, *op2, HINT_NONE);
|
|
if (JS_IsException(*op2))
|
|
return JS_EXCEPTION;
|
|
if (JS_IsString(ctx, *op1) || JS_IsString(ctx, *op2)) {
|
|
*op1 = JS_ToString(ctx, *op1);
|
|
if (JS_IsException(*op1))
|
|
return JS_EXCEPTION;
|
|
*op2 = JS_ToString(ctx, *op2);
|
|
if (JS_IsException(*op2))
|
|
return JS_EXCEPTION;
|
|
return JS_ConcatString(ctx, *op1, *op2);
|
|
} else {
|
|
double d1, d2, r;
|
|
/* cannot fail */
|
|
if (JS_ToNumber(ctx, &d1, *op1))
|
|
return JS_EXCEPTION;
|
|
if (JS_ToNumber(ctx, &d2, *op2))
|
|
return JS_EXCEPTION;
|
|
r = d1 + d2;
|
|
return JS_NewFloat64(ctx, r);
|
|
}
|
|
}
|
|
|
|
static no_inline JSValue js_binary_arith_slow(JSContext *ctx, OPCodeEnum op)
|
|
{
|
|
double d1, d2, r;
|
|
|
|
if (JS_ToNumber(ctx, &d1, ctx->sp[1]))
|
|
return JS_EXCEPTION;
|
|
|
|
if (JS_ToNumber(ctx, &d2, ctx->sp[0]))
|
|
return JS_EXCEPTION;
|
|
|
|
switch(op) {
|
|
case OP_sub:
|
|
r = d1 - d2;
|
|
break;
|
|
case OP_mul:
|
|
r = d1 * d2;
|
|
break;
|
|
case OP_div:
|
|
r = d1 / d2;
|
|
break;
|
|
case OP_mod:
|
|
r = js_fmod(d1, d2);
|
|
break;
|
|
case OP_pow:
|
|
r = js_pow(d1, d2);
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
return JS_NewFloat64(ctx, r);
|
|
}
|
|
|
|
static no_inline JSValue js_unary_arith_slow(JSContext *ctx, OPCodeEnum op)
|
|
{
|
|
double d;
|
|
|
|
if (JS_ToNumber(ctx, &d, ctx->sp[0]))
|
|
return JS_EXCEPTION;
|
|
|
|
switch(op) {
|
|
case OP_inc:
|
|
d++;
|
|
break;
|
|
case OP_dec:
|
|
d--;
|
|
break;
|
|
case OP_plus:
|
|
break;
|
|
case OP_neg:
|
|
d = -d;
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
return JS_NewFloat64(ctx, d);
|
|
}
|
|
|
|
/* specific case necessary for correct return value semantics */
|
|
static no_inline JSValue js_post_inc_slow(JSContext *ctx, OPCodeEnum op)
|
|
{
|
|
JSValue val;
|
|
double d, r;
|
|
|
|
if (JS_ToNumber(ctx, &d, ctx->sp[0]))
|
|
return JS_EXCEPTION;
|
|
r = d + 2 * (op - OP_post_dec) - 1;
|
|
val = JS_NewFloat64(ctx, d);
|
|
if (JS_IsException(val))
|
|
return val;
|
|
ctx->sp[0] = val;
|
|
return JS_NewFloat64(ctx, r);
|
|
}
|
|
|
|
static no_inline JSValue js_binary_logic_slow(JSContext *ctx, OPCodeEnum op)
|
|
{
|
|
uint32_t v1, v2, r;
|
|
|
|
if (JS_ToUint32(ctx, &v1, ctx->sp[1]))
|
|
return JS_EXCEPTION;
|
|
if (JS_ToUint32(ctx, &v2, ctx->sp[0]))
|
|
return JS_EXCEPTION;
|
|
switch(op) {
|
|
case OP_shl:
|
|
r = v1 << (v2 & 0x1f);
|
|
break;
|
|
case OP_sar:
|
|
r = (int)v1 >> (v2 & 0x1f);
|
|
break;
|
|
case OP_shr:
|
|
r = v1 >> (v2 & 0x1f);
|
|
return JS_NewUint32(ctx, r);
|
|
case OP_and:
|
|
r = v1 & v2;
|
|
break;
|
|
case OP_or:
|
|
r = v1 | v2;
|
|
break;
|
|
case OP_xor:
|
|
r = v1 ^ v2;
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
return JS_NewInt32(ctx, r);
|
|
}
|
|
|
|
static no_inline JSValue js_not_slow(JSContext *ctx)
|
|
{
|
|
uint32_t r;
|
|
|
|
if (JS_ToUint32(ctx, &r, ctx->sp[0]))
|
|
return JS_EXCEPTION;
|
|
return JS_NewInt32(ctx, ~r);
|
|
}
|
|
|
|
static no_inline JSValue js_relational_slow(JSContext *ctx, OPCodeEnum op)
|
|
{
|
|
JSValue *op1, *op2;
|
|
int res;
|
|
double d1, d2;
|
|
|
|
op1 = &ctx->sp[1];
|
|
op2 = &ctx->sp[0];
|
|
*op1 = JS_ToPrimitive(ctx, *op1, HINT_NUMBER);
|
|
if (JS_IsException(*op1))
|
|
return JS_EXCEPTION;
|
|
*op2 = JS_ToPrimitive(ctx, *op2, HINT_NUMBER);
|
|
if (JS_IsException(*op2))
|
|
return JS_EXCEPTION;
|
|
if (JS_IsString(ctx, *op1) && JS_IsString(ctx, *op2)) {
|
|
res = js_string_compare(ctx, *op1, *op2);
|
|
switch(op) {
|
|
case OP_lt:
|
|
res = (res < 0);
|
|
break;
|
|
case OP_lte:
|
|
res = (res <= 0);
|
|
break;
|
|
case OP_gt:
|
|
res = (res > 0);
|
|
break;
|
|
default:
|
|
case OP_gte:
|
|
res = (res >= 0);
|
|
break;
|
|
}
|
|
} else {
|
|
if (JS_ToNumber(ctx, &d1, *op1))
|
|
return JS_EXCEPTION;
|
|
if (JS_ToNumber(ctx, &d2, *op2))
|
|
return JS_EXCEPTION;
|
|
switch(op) {
|
|
case OP_lt:
|
|
res = (d1 < d2); /* if NaN return false */
|
|
break;
|
|
case OP_lte:
|
|
res = (d1 <= d2); /* if NaN return false */
|
|
break;
|
|
case OP_gt:
|
|
res = (d1 > d2); /* if NaN return false */
|
|
break;
|
|
default:
|
|
case OP_gte:
|
|
res = (d1 >= d2); /* if NaN return false */
|
|
break;
|
|
}
|
|
}
|
|
return JS_NewBool(res);
|
|
}
|
|
|
|
static BOOL js_strict_eq(JSContext *ctx, JSValue op1, JSValue op2)
|
|
{
|
|
BOOL res;
|
|
|
|
if (JS_IsNumber(ctx, op1)) {
|
|
if (!JS_IsNumber(ctx, op2)) {
|
|
res = FALSE;
|
|
} else {
|
|
double d1, d2;
|
|
/* cannot fail */
|
|
JS_ToNumber(ctx, &d1, op1);
|
|
JS_ToNumber(ctx, &d2, op2);
|
|
res = (d1 == d2); /* if NaN return false */
|
|
}
|
|
} else if (JS_IsString(ctx, op1)) {
|
|
if (!JS_IsString(ctx, op2)) {
|
|
res = FALSE;
|
|
} else {
|
|
res = js_string_eq(ctx, op1, op2);
|
|
}
|
|
} else {
|
|
/* special value or object */
|
|
res = (op1 == op2);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
static JSValue js_strict_eq_slow(JSContext *ctx, BOOL is_neq)
|
|
{
|
|
BOOL res;
|
|
res = js_strict_eq(ctx, ctx->sp[1], ctx->sp[0]);
|
|
return JS_NewBool(res ^ is_neq);
|
|
}
|
|
|
|
enum {
|
|
/* special tags to simplify the comparison */
|
|
JS_ETAG_NUMBER = JS_TAG_SPECIAL | (8 << 2),
|
|
JS_ETAG_STRING = JS_TAG_SPECIAL | (9 << 2),
|
|
JS_ETAG_OBJECT = JS_TAG_SPECIAL | (10 << 2),
|
|
};
|
|
|
|
static int js_eq_get_type(JSContext *ctx, JSValue val)
|
|
{
|
|
if (JS_IsIntOrShortFloat(val)) {
|
|
return JS_ETAG_NUMBER;
|
|
} else if (JS_IsPtr(val)) {
|
|
void *ptr = JS_VALUE_TO_PTR(val);
|
|
switch(js_get_mtag(ptr)) {
|
|
case JS_MTAG_FLOAT64:
|
|
return JS_ETAG_NUMBER;
|
|
case JS_MTAG_STRING:
|
|
return JS_ETAG_STRING;
|
|
default:
|
|
case JS_MTAG_OBJECT:
|
|
return JS_ETAG_OBJECT;
|
|
}
|
|
} else {
|
|
int tag = JS_VALUE_GET_SPECIAL_TAG(val);
|
|
switch(tag) {
|
|
case JS_TAG_STRING_CHAR:
|
|
return JS_ETAG_STRING;
|
|
case JS_TAG_SHORT_FUNC:
|
|
return JS_ETAG_OBJECT;
|
|
default:
|
|
return tag;
|
|
}
|
|
}
|
|
}
|
|
|
|
static no_inline JSValue js_eq_slow(JSContext *ctx, BOOL is_neq)
|
|
{
|
|
JSValue op1, op2;
|
|
int tag1, tag2;
|
|
BOOL res;
|
|
|
|
redo:
|
|
op1 = ctx->sp[1];
|
|
op2 = ctx->sp[0];
|
|
tag1 = js_eq_get_type(ctx, op1);
|
|
tag2 = js_eq_get_type(ctx, op2);
|
|
if (tag1 == tag2) {
|
|
res = js_strict_eq(ctx, op1, op2);
|
|
} else if ((tag1 == JS_TAG_NULL && tag2 == JS_TAG_UNDEFINED) ||
|
|
(tag2 == JS_TAG_NULL && tag1 == JS_TAG_UNDEFINED)) {
|
|
res = TRUE;
|
|
} else if ((tag1 == JS_ETAG_STRING && tag2 == JS_ETAG_NUMBER) ||
|
|
(tag2 == JS_ETAG_STRING && tag1 == JS_ETAG_NUMBER)) {
|
|
double d1;
|
|
double d2;
|
|
if (JS_ToNumber(ctx, &d1, ctx->sp[1]))
|
|
return JS_EXCEPTION;
|
|
if (JS_ToNumber(ctx, &d2, ctx->sp[0]))
|
|
return JS_EXCEPTION;
|
|
res = (d1 == d2);
|
|
} else if (tag1 == JS_TAG_BOOL) {
|
|
ctx->sp[1] = JS_NewShortInt(JS_VALUE_GET_SPECIAL_VALUE(op1));
|
|
goto redo;
|
|
} else if (tag2 == JS_TAG_BOOL) {
|
|
ctx->sp[0] = JS_NewShortInt(JS_VALUE_GET_SPECIAL_VALUE(op2));
|
|
goto redo;
|
|
} else if (tag1 == JS_ETAG_OBJECT &&
|
|
(tag2 == JS_ETAG_NUMBER || tag2 == JS_ETAG_STRING)) {
|
|
ctx->sp[1] = JS_ToPrimitive(ctx, op1, HINT_NONE);
|
|
if (JS_IsException(ctx->sp[1]))
|
|
return JS_EXCEPTION;
|
|
goto redo;
|
|
} else if (tag2 == JS_ETAG_OBJECT &&
|
|
(tag1 == JS_ETAG_NUMBER || tag1 == JS_ETAG_STRING)) {
|
|
ctx->sp[0] = JS_ToPrimitive(ctx, op2, HINT_NONE);
|
|
if (JS_IsException(ctx->sp[0]))
|
|
return JS_EXCEPTION;
|
|
goto redo;
|
|
} else {
|
|
res = FALSE;
|
|
}
|
|
return JS_NewBool(res ^ is_neq);
|
|
}
|
|
|
|
static JSValue js_operator_in(JSContext *ctx)
|
|
{
|
|
JSValue prop;
|
|
int res;
|
|
|
|
if (js_eq_get_type(ctx, ctx->sp[0]) != JS_ETAG_OBJECT)
|
|
return JS_ThrowTypeError(ctx, "invalid 'in' operand");
|
|
prop = JS_ToPropertyKey(ctx, ctx->sp[1]);
|
|
if (JS_IsException(prop))
|
|
return prop;
|
|
res = JS_HasProperty(ctx, ctx->sp[0], prop);
|
|
return JS_NewBool(res);
|
|
}
|
|
|
|
static JSValue js_operator_instanceof(JSContext *ctx)
|
|
{
|
|
JSValue op1, op2, proto;
|
|
JSObject *p;
|
|
|
|
op1 = ctx->sp[1];
|
|
op2 = ctx->sp[0];
|
|
if (!JS_IsFunctionObject(ctx, op2))
|
|
return JS_ThrowTypeError(ctx, "invalid 'instanceof' right operand");
|
|
proto = JS_GetProperty(ctx, op2, js_get_atom(ctx, JS_ATOM_prototype));
|
|
if (JS_IsException(proto))
|
|
return proto;
|
|
if (!JS_IsObject(ctx, op1))
|
|
return JS_NewBool(FALSE);
|
|
p = JS_VALUE_TO_PTR(op1);
|
|
for(;;) {
|
|
if (p->proto == JS_NULL)
|
|
return JS_NewBool(FALSE);
|
|
if (p->proto == proto)
|
|
return JS_NewBool(TRUE);
|
|
p = JS_VALUE_TO_PTR(p->proto);
|
|
}
|
|
return JS_NewBool(FALSE);
|
|
}
|
|
|
|
static JSValue js_operator_typeof(JSContext *ctx, JSValue val)
|
|
{
|
|
int tag, atom;
|
|
tag = js_eq_get_type(ctx, val);
|
|
switch(tag) {
|
|
case JS_ETAG_NUMBER:
|
|
atom = JS_ATOM_number;
|
|
break;
|
|
case JS_ETAG_STRING:
|
|
atom = JS_ATOM_string;
|
|
break;
|
|
case JS_TAG_BOOL:
|
|
atom = JS_ATOM_boolean;
|
|
break;
|
|
case JS_ETAG_OBJECT:
|
|
if (JS_IsFunction(ctx, val))
|
|
atom = JS_ATOM_function;
|
|
else
|
|
atom = JS_ATOM_object;
|
|
break;
|
|
case JS_TAG_NULL:
|
|
atom = JS_ATOM_object;
|
|
break;
|
|
default:
|
|
case JS_TAG_UNDEFINED:
|
|
atom = JS_ATOM_undefined;
|
|
break;
|
|
}
|
|
return js_get_atom(ctx, atom);
|
|
}
|
|
|
|
static void js_reverse_val(JSValue *tab, int n)
|
|
{
|
|
int i;
|
|
JSValue tmp;
|
|
|
|
for(i = 0; i < n / 2; i++) {
|
|
tmp = tab[i];
|
|
tab[i] = tab[n - 1 - i];
|
|
tab[n - 1 - i] = tmp;
|
|
}
|
|
}
|
|
|
|
static JSValue js_closure(JSContext *ctx, JSValue bfunc, JSValue *fp)
|
|
{
|
|
JSFunctionBytecode *b;
|
|
JSObject *p;
|
|
JSGCRef bfunc_ref, closure_ref;
|
|
JSValueArray *ext_vars;
|
|
JSValue closure;
|
|
int ext_vars_len;
|
|
|
|
b = JS_VALUE_TO_PTR(bfunc);
|
|
if (b->ext_vars != JS_NULL) {
|
|
ext_vars = JS_VALUE_TO_PTR(b->ext_vars);
|
|
ext_vars_len = ext_vars->size / 2;
|
|
} else {
|
|
ext_vars_len = 0;
|
|
}
|
|
|
|
JS_PUSH_VALUE(ctx, bfunc);
|
|
closure = JS_NewObjectProtoClass(ctx, ctx->class_proto[JS_CLASS_CLOSURE], JS_CLASS_CLOSURE,
|
|
sizeof(JSClosureData) + ext_vars_len * sizeof(JSValue));
|
|
JS_POP_VALUE(ctx, bfunc);
|
|
if (JS_IsException(closure))
|
|
return JS_EXCEPTION;
|
|
p = JS_VALUE_TO_PTR(closure);
|
|
p->u.closure.func_bytecode = bfunc;
|
|
|
|
if (ext_vars_len > 0) {
|
|
JSValue *pfirst_var_ref, val;
|
|
int i, var_idx, var_kind, decl;
|
|
|
|
/* initialize the var_refs in case of exception */
|
|
memset(p->u.closure.var_refs, 0, sizeof(JSValue) * ext_vars_len);
|
|
if (fp) {
|
|
pfirst_var_ref = &fp[FRAME_OFFSET_FIRST_VARREF];
|
|
} else {
|
|
pfirst_var_ref = NULL; /* not used */
|
|
}
|
|
for(i = 0; i < ext_vars_len; i++) {
|
|
b = JS_VALUE_TO_PTR(bfunc);
|
|
ext_vars = JS_VALUE_TO_PTR(b->ext_vars);
|
|
decl = JS_VALUE_GET_INT(ext_vars->arr[2 * i + 1]);
|
|
var_kind = decl >> 16;
|
|
var_idx = decl & 0xffff;
|
|
JS_PUSH_VALUE(ctx, bfunc);
|
|
JS_PUSH_VALUE(ctx, closure);
|
|
switch(var_kind) {
|
|
case JS_VARREF_KIND_ARG:
|
|
val = get_var_ref(ctx, pfirst_var_ref,
|
|
&fp[FRAME_OFFSET_ARG0 + var_idx]);
|
|
break;
|
|
case JS_VARREF_KIND_VAR:
|
|
val = get_var_ref(ctx, pfirst_var_ref,
|
|
&fp[FRAME_OFFSET_VAR0 - var_idx]);
|
|
break;
|
|
case JS_VARREF_KIND_VAR_REF:
|
|
{
|
|
JSObject *p;
|
|
p = JS_VALUE_TO_PTR(fp[FRAME_OFFSET_FUNC_OBJ]);
|
|
val = p->u.closure.var_refs[var_idx];
|
|
}
|
|
break;
|
|
case JS_VARREF_KIND_GLOBAL:
|
|
/* only for eval code */
|
|
val = add_global_var(ctx, ext_vars->arr[2 * i], (var_idx != 0));
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
JS_POP_VALUE(ctx, closure);
|
|
JS_POP_VALUE(ctx, bfunc);
|
|
if (JS_IsException(val))
|
|
return val;
|
|
p = JS_VALUE_TO_PTR(closure);
|
|
p->u.closure.var_refs[i] = val;
|
|
}
|
|
}
|
|
return closure;
|
|
}
|
|
|
|
static JSValue js_for_of_start(JSContext *ctx, BOOL is_for_in)
|
|
{
|
|
JSValueArray *arr;
|
|
|
|
if (is_for_in) {
|
|
/* XXX: not spec compliant and slow. We return only the own
|
|
object keys. */
|
|
ctx->sp[0] = js_object_keys(ctx, NULL, 1, &ctx->sp[0]);
|
|
if (JS_IsException(ctx->sp[0]))
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
if (!js_get_object_class(ctx, ctx->sp[0], JS_CLASS_ARRAY))
|
|
return JS_ThrowTypeError(ctx, "unsupported type in for...of");
|
|
|
|
arr = js_alloc_value_array(ctx, 0, 2);
|
|
if (!arr)
|
|
return JS_EXCEPTION;
|
|
arr->arr[0] = ctx->sp[0];
|
|
arr->arr[1] = JS_NewShortInt(0);
|
|
return JS_VALUE_FROM_PTR(arr);
|
|
}
|
|
|
|
static JSValue js_for_of_next(JSContext *ctx)
|
|
{
|
|
JSValueArray *arr, *arr1;
|
|
JSObject *p;
|
|
int pos;
|
|
|
|
arr = JS_VALUE_TO_PTR(ctx->sp[0]);
|
|
pos = JS_VALUE_GET_INT(arr->arr[1]);
|
|
p = JS_VALUE_TO_PTR(arr->arr[0]);
|
|
if (pos >= p->u.array.len) {
|
|
ctx->sp[-2] = JS_TRUE;
|
|
ctx->sp[-1] = JS_UNDEFINED;
|
|
} else {
|
|
ctx->sp[-2] = JS_FALSE;
|
|
arr1 = JS_VALUE_TO_PTR(p->u.array.tab);
|
|
ctx->sp[-1] = arr1->arr[pos];
|
|
arr->arr[1] = JS_NewShortInt(pos + 1);
|
|
}
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
static JSValue js_new_c_function_proto(JSContext *ctx, int func_idx, JSValue proto, BOOL has_params,
|
|
JSValue params)
|
|
{
|
|
JSObject *p;
|
|
JSGCRef params_ref;
|
|
|
|
JS_PUSH_VALUE(ctx, params);
|
|
p = JS_NewObjectProtoClass1(ctx, proto, JS_CLASS_C_FUNCTION,
|
|
sizeof(JSCFunctionData) - (!has_params ? sizeof(JSValue) : 0));
|
|
JS_POP_VALUE(ctx, params);
|
|
if (!p)
|
|
return JS_EXCEPTION;
|
|
p->u.cfunc.idx = func_idx;
|
|
if (has_params)
|
|
p->u.cfunc.params = params;
|
|
return JS_VALUE_FROM_PTR(p);
|
|
}
|
|
|
|
JSValue JS_NewCFunctionParams(JSContext *ctx, int func_idx, JSValue params)
|
|
{
|
|
return js_new_c_function_proto(ctx, func_idx, ctx->class_proto[JS_CLASS_CLOSURE], TRUE, params);
|
|
}
|
|
|
|
static JSValue js_call_constructor_start(JSContext *ctx, JSValue func)
|
|
{
|
|
JSValue proto;
|
|
proto = JS_GetProperty(ctx, func, js_get_atom(ctx, JS_ATOM_prototype));
|
|
if (JS_IsException(proto))
|
|
return proto;
|
|
if (!JS_IsObject(ctx, proto))
|
|
proto = ctx->class_proto[JS_CLASS_OBJECT];
|
|
return JS_NewObjectProtoClass(ctx, proto, JS_CLASS_OBJECT, 0);
|
|
}
|
|
|
|
#define SAVE() do { \
|
|
fp[FRAME_OFFSET_CUR_PC] = JS_NewShortInt(pc - ((JSByteArray *)JS_VALUE_TO_PTR(b->byte_code))->buf); \
|
|
ctx->sp = sp; \
|
|
ctx->fp = fp; \
|
|
} while (0)
|
|
|
|
/* only need to restore PC */
|
|
#define RESTORE() do { \
|
|
b = JS_VALUE_TO_PTR(((JSObject *)JS_VALUE_TO_PTR(fp[FRAME_OFFSET_FUNC_OBJ]))->u.closure.func_bytecode); \
|
|
pc = ((JSByteArray *)JS_VALUE_TO_PTR(b->byte_code))->buf + JS_VALUE_GET_INT(fp[FRAME_OFFSET_CUR_PC]); \
|
|
} while (0)
|
|
|
|
static JSValue __js_poll_interrupt(JSContext *ctx)
|
|
{
|
|
ctx->interrupt_counter = JS_INTERRUPT_COUNTER_INIT;
|
|
if (ctx->interrupt_handler && ctx->interrupt_handler(ctx, ctx->opaque)) {
|
|
JS_ThrowInternalError(ctx, "interrupted");
|
|
ctx->current_exception_is_uncatchable = TRUE;
|
|
return JS_EXCEPTION;
|
|
}
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
/* handle user interruption */
|
|
#define POLL_INTERRUPT() do { \
|
|
if (unlikely(--ctx->interrupt_counter <= 0)) { \
|
|
SAVE(); \
|
|
val = __js_poll_interrupt(ctx); \
|
|
RESTORE(); \
|
|
if (JS_IsException(val)) \
|
|
goto exception; \
|
|
} \
|
|
} while(0)
|
|
|
|
/* must use JS_StackCheck() before using it */
|
|
void JS_PushArg(JSContext *ctx, JSValue val)
|
|
{
|
|
#ifdef DEBUG_GC
|
|
assert((ctx->sp - 1) >= ctx->stack_bottom);
|
|
#endif
|
|
*--ctx->sp = val;
|
|
}
|
|
|
|
/* Usage:
|
|
if (JS_StackCheck(ctx, n + 2)) ...
|
|
JS_PushArg(ctx, arg[n - 1]);
|
|
...
|
|
JS_PushArg(ctx, arg[0]);
|
|
JS_PushArg(ctx, func);
|
|
JS_PushArg(ctx, this_obj);
|
|
res = JS_Call(ctx, n);
|
|
*/
|
|
JSValue JS_Call(JSContext *ctx, int call_flags)
|
|
{
|
|
JSValue *fp, *sp, val = JS_UNDEFINED, *initial_fp;
|
|
uint8_t *pc;
|
|
/* temporary variables */
|
|
int opcode = OP_invalid, i;
|
|
JSFunctionBytecode *b;
|
|
#ifdef JS_USE_SHORT_FLOAT
|
|
double dr;
|
|
#endif
|
|
|
|
if (ctx->js_call_rec_count >= JS_MAX_CALL_RECURSE)
|
|
return JS_ThrowInternalError(ctx, "C stack overflow");
|
|
ctx->js_call_rec_count++;
|
|
|
|
sp = ctx->sp;
|
|
fp = ctx->fp;
|
|
initial_fp = fp;
|
|
b = NULL;
|
|
pc = NULL;
|
|
goto function_call;
|
|
|
|
#define CASE(op) case op
|
|
#define DEFAULT default
|
|
#define BREAK break
|
|
|
|
for(;;) {
|
|
opcode = *pc++;
|
|
#ifdef DUMP_EXEC
|
|
{
|
|
JSByteArray *arr;
|
|
arr = JS_VALUE_TO_PTR(b->byte_code);
|
|
js_printf(ctx, " sp=%d\n", (int)(sp - fp));
|
|
js_printf(ctx, "%4d: %s\n", (int)(pc - arr->buf - 1),
|
|
opcode_info[opcode].name);
|
|
}
|
|
#endif
|
|
switch(opcode) {
|
|
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_NewShortInt(opcode - OP_push_0);
|
|
BREAK;
|
|
CASE(OP_push_i8):
|
|
*--sp = JS_NewShortInt(get_i8(pc));
|
|
pc += 1;
|
|
BREAK;
|
|
CASE(OP_push_i16):
|
|
*--sp = JS_NewShortInt(get_i16(pc));
|
|
pc += 2;
|
|
BREAK;
|
|
CASE(OP_push_value):
|
|
*--sp = get_u32(pc);
|
|
pc += 4;
|
|
BREAK;
|
|
CASE(OP_push_const):
|
|
{
|
|
JSValueArray *cpool = JS_VALUE_TO_PTR(b->cpool);
|
|
*--sp = cpool->arr[get_u16(pc)];
|
|
pc += 2;
|
|
}
|
|
BREAK;
|
|
CASE(OP_undefined):
|
|
*--sp = JS_UNDEFINED;
|
|
BREAK;
|
|
CASE(OP_null):
|
|
*--sp = JS_NULL;
|
|
BREAK;
|
|
CASE(OP_push_this):
|
|
*--sp = fp[FRAME_OFFSET_THIS_OBJ];
|
|
BREAK;
|
|
CASE(OP_push_false):
|
|
*--sp = JS_FALSE;
|
|
BREAK;
|
|
CASE(OP_push_true):
|
|
*--sp = JS_TRUE;
|
|
BREAK;
|
|
CASE(OP_object):
|
|
{
|
|
int n = get_u16(pc);
|
|
SAVE();
|
|
val = JS_NewObjectPrealloc(ctx, n);
|
|
RESTORE();
|
|
if (JS_IsException(val))
|
|
goto exception;
|
|
*--sp = val;
|
|
pc += 2;
|
|
}
|
|
BREAK;
|
|
CASE(OP_regexp):
|
|
{
|
|
JSObject *p;
|
|
SAVE();
|
|
val = JS_NewObjectClass(ctx, JS_CLASS_REGEXP, sizeof(JSRegExp));
|
|
RESTORE();
|
|
if (JS_IsException(val))
|
|
goto exception;
|
|
p = JS_VALUE_TO_PTR(val);
|
|
p->u.regexp.source = sp[1];
|
|
p->u.regexp.byte_code = sp[0];
|
|
p->u.regexp.last_index = 0;
|
|
sp[1] = val;
|
|
sp++;
|
|
}
|
|
BREAK;
|
|
CASE(OP_array_from):
|
|
{
|
|
JSObject *p;
|
|
JSValueArray *arr;
|
|
int i, argc;
|
|
|
|
argc = get_u16(pc);
|
|
SAVE();
|
|
val = JS_NewArray(ctx, argc);
|
|
RESTORE();
|
|
if (JS_IsException(val))
|
|
goto exception;
|
|
pc += 2;
|
|
p = JS_VALUE_TO_PTR(val);
|
|
arr = JS_VALUE_TO_PTR(p->u.array.tab);
|
|
for(i = 0; i < argc; i++) {
|
|
arr->arr[i] = sp[argc - 1 - i];
|
|
}
|
|
sp += argc;
|
|
*--sp = val;
|
|
}
|
|
BREAK;
|
|
CASE(OP_this_func):
|
|
*--sp = fp[FRAME_OFFSET_FUNC_OBJ];
|
|
BREAK;
|
|
CASE(OP_arguments):
|
|
{
|
|
JSObject *p;
|
|
JSValueArray *arr;
|
|
int i, argc;
|
|
|
|
argc = JS_VALUE_GET_INT(fp[FRAME_OFFSET_CALL_FLAGS]) & FRAME_CF_ARGC_MASK;
|
|
SAVE();
|
|
val = JS_NewArray(ctx, argc);
|
|
RESTORE();
|
|
if (JS_IsException(val))
|
|
goto exception;
|
|
p = JS_VALUE_TO_PTR(val);
|
|
arr = JS_VALUE_TO_PTR(p->u.array.tab);
|
|
for(i = 0; i < argc; i++) {
|
|
arr->arr[i] = fp[FRAME_OFFSET_ARG0 + i];
|
|
}
|
|
*--sp = val;
|
|
}
|
|
BREAK;
|
|
CASE(OP_new_target):
|
|
call_flags = JS_VALUE_GET_INT(fp[FRAME_OFFSET_CALL_FLAGS]);
|
|
if (call_flags & FRAME_CF_CTOR) {
|
|
*--sp = fp[FRAME_OFFSET_FUNC_OBJ];
|
|
} else {
|
|
*--sp = JS_UNDEFINED;
|
|
}
|
|
BREAK;
|
|
CASE(OP_drop):
|
|
sp++;
|
|
BREAK;
|
|
CASE(OP_nip):
|
|
sp[1] = sp[0];
|
|
sp++;
|
|
BREAK;
|
|
CASE(OP_dup):
|
|
sp--;
|
|
sp[0] = sp[1];
|
|
BREAK;
|
|
CASE(OP_dup2):
|
|
sp -= 2;
|
|
sp[0] = sp[2];
|
|
sp[1] = sp[3];
|
|
BREAK;
|
|
CASE(OP_insert2):
|
|
sp[-1] = sp[0];
|
|
sp[0] = sp[1];
|
|
sp[1] = sp[-1];
|
|
sp--;
|
|
BREAK;
|
|
CASE(OP_insert3):
|
|
sp[-1] = sp[0];
|
|
sp[0] = sp[1];
|
|
sp[1] = sp[2];
|
|
sp[2] = sp[-1];
|
|
sp--;
|
|
BREAK;
|
|
CASE(OP_perm3): /* obj a b -> a obj b (213) */
|
|
{
|
|
JSValue tmp;
|
|
tmp = sp[1];
|
|
sp[1] = sp[2];
|
|
sp[2] = tmp;
|
|
}
|
|
BREAK;
|
|
CASE(OP_rot3l): /* x a b -> a b x (231) */
|
|
{
|
|
JSValue tmp;
|
|
tmp = sp[2];
|
|
sp[2] = sp[1];
|
|
sp[1] = sp[0];
|
|
sp[0] = tmp;
|
|
}
|
|
BREAK;
|
|
CASE(OP_perm4): /* obj prop a b -> a obj prop b */
|
|
{
|
|
JSValue tmp;
|
|
tmp = sp[1];
|
|
sp[1] = sp[2];
|
|
sp[2] = sp[3];
|
|
sp[3] = tmp;
|
|
}
|
|
BREAK;
|
|
CASE(OP_swap): /* a b -> b a */
|
|
{
|
|
JSValue tmp;
|
|
tmp = sp[1];
|
|
sp[1] = sp[0];
|
|
sp[0] = tmp;
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_fclosure):
|
|
{
|
|
int idx;
|
|
JSValueArray *cpool = JS_VALUE_TO_PTR(b->cpool);
|
|
idx = get_u16(pc);
|
|
SAVE();
|
|
val = js_closure(ctx, cpool->arr[idx], fp);
|
|
RESTORE();
|
|
if (unlikely(JS_IsException(val)))
|
|
goto exception;
|
|
pc += 2;
|
|
*--sp = val;
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_call_constructor):
|
|
call_flags = get_u16(pc) | FRAME_CF_CTOR;
|
|
goto global_function_call;
|
|
CASE(OP_call):
|
|
call_flags = get_u16(pc);
|
|
global_function_call:
|
|
js_reverse_val(sp, (call_flags & FRAME_CF_ARGC_MASK) + 1);
|
|
*--sp = JS_UNDEFINED;
|
|
goto generic_function_call;
|
|
CASE(OP_call_method):
|
|
{
|
|
int n, argc, short_func_idx;
|
|
JSValue func_obj;
|
|
JSObject *p;
|
|
JSByteArray *byte_code;
|
|
|
|
call_flags = get_u16(pc);
|
|
|
|
n = (call_flags & FRAME_CF_ARGC_MASK) + 2;
|
|
js_reverse_val(sp, n);
|
|
|
|
generic_function_call:
|
|
POLL_INTERRUPT();
|
|
byte_code = JS_VALUE_TO_PTR(b->byte_code);
|
|
/* save pc + 1 of the current call */
|
|
fp[FRAME_OFFSET_CUR_PC] = JS_NewShortInt(pc - byte_code->buf);
|
|
function_call:
|
|
*--sp = JS_NewShortInt(call_flags);
|
|
*--sp = SP_TO_VALUE(ctx, fp);
|
|
|
|
func_obj = sp[FRAME_OFFSET_FUNC_OBJ];
|
|
#if defined(DUMP_EXEC)
|
|
JS_DumpValue(ctx, "calling", func_obj);
|
|
#endif
|
|
if (!JS_IsPtr(func_obj)) {
|
|
if (JS_VALUE_GET_SPECIAL_TAG(func_obj) != JS_TAG_SHORT_FUNC)
|
|
goto not_a_function;
|
|
short_func_idx = JS_VALUE_GET_SPECIAL_VALUE(func_obj);
|
|
p = NULL;
|
|
goto c_function;
|
|
} else {
|
|
p = JS_VALUE_TO_PTR(func_obj);
|
|
if (p->mtag != JS_MTAG_OBJECT)
|
|
goto not_a_function;
|
|
if (p->class_id == JS_CLASS_C_FUNCTION) {
|
|
const JSCFunctionDef *fd;
|
|
int pushed_argc;
|
|
short_func_idx = p->u.cfunc.idx;
|
|
c_function:
|
|
fd = &ctx->c_function_table[short_func_idx];
|
|
/* add undefined arguments if the caller did not
|
|
provide enough arguments */
|
|
call_flags = JS_VALUE_GET_INT(sp[FRAME_OFFSET_CALL_FLAGS]);
|
|
if ((call_flags & FRAME_CF_CTOR) &&
|
|
(fd->def_type != JS_CFUNC_constructor &&
|
|
fd->def_type != JS_CFUNC_constructor_magic)) {
|
|
sp += 2; /* go back to the caller frame */
|
|
ctx->sp = sp;
|
|
ctx->fp = fp;
|
|
val = JS_ThrowTypeError(ctx, "not a constructor");
|
|
goto call_exception;
|
|
}
|
|
|
|
argc = call_flags & FRAME_CF_ARGC_MASK;
|
|
/* JS_StackCheck may trigger a gc */
|
|
ctx->sp = sp;
|
|
ctx->fp = fp;
|
|
n = JS_StackCheck(ctx, max_int(fd->arg_count - argc, 0));
|
|
if (n) {
|
|
sp += 2; /* go back to the caller frame */
|
|
val = JS_EXCEPTION;
|
|
goto call_exception;
|
|
}
|
|
pushed_argc = argc;
|
|
if (fd->arg_count > argc) {
|
|
n = fd->arg_count - argc;
|
|
sp -= n;
|
|
for(i = 0; i < FRAME_OFFSET_ARG0 + argc; i++)
|
|
sp[i] = sp[i + n];
|
|
for(i = 0; i < n; i++)
|
|
sp[FRAME_OFFSET_ARG0 + argc + i] = JS_UNDEFINED;
|
|
pushed_argc = fd->arg_count;
|
|
}
|
|
fp = sp;
|
|
ctx->sp = sp;
|
|
ctx->fp = fp;
|
|
switch(fd->def_type) {
|
|
case JS_CFUNC_generic:
|
|
case JS_CFUNC_constructor:
|
|
val = fd->func.generic(ctx, &fp[FRAME_OFFSET_THIS_OBJ],
|
|
call_flags & (FRAME_CF_CTOR | FRAME_CF_ARGC_MASK),
|
|
fp + FRAME_OFFSET_ARG0);
|
|
break;
|
|
case JS_CFUNC_generic_magic:
|
|
case JS_CFUNC_constructor_magic:
|
|
val = fd->func.generic_magic(ctx, &fp[FRAME_OFFSET_THIS_OBJ],
|
|
call_flags & (FRAME_CF_CTOR | FRAME_CF_ARGC_MASK),
|
|
fp + FRAME_OFFSET_ARG0, fd->magic);
|
|
break;
|
|
case JS_CFUNC_generic_params:
|
|
p = JS_VALUE_TO_PTR(fp[FRAME_OFFSET_FUNC_OBJ]);
|
|
val = fd->func.generic_params(ctx, &fp[FRAME_OFFSET_THIS_OBJ],
|
|
call_flags & (FRAME_CF_CTOR | FRAME_CF_ARGC_MASK),
|
|
fp + FRAME_OFFSET_ARG0, p->u.cfunc.params);
|
|
break;
|
|
case JS_CFUNC_f_f:
|
|
{
|
|
double d;
|
|
if (JS_ToNumber(ctx, &d, fp[FRAME_OFFSET_ARG0])) {
|
|
val = JS_EXCEPTION;
|
|
} else {
|
|
d = fd->func.f_f(d);
|
|
}
|
|
val = JS_NewFloat64(ctx, d);
|
|
}
|
|
break;
|
|
default:
|
|
assert(0);
|
|
}
|
|
if (JS_IsExceptionOrTailCall(val) &&
|
|
JS_VALUE_GET_SPECIAL_VALUE(val) >= JS_EX_CALL) {
|
|
JSValue *fp1, *sp1;
|
|
/* tail call: equivalent to calling the
|
|
function after the C function */
|
|
/* XXX: handle the call flags of the caller ? */
|
|
call_flags = JS_VALUE_GET_SPECIAL_VALUE(val) - JS_EX_CALL;
|
|
sp = ctx->sp;
|
|
/* pop the frame */
|
|
fp1 = VALUE_TO_SP(ctx, fp[FRAME_OFFSET_SAVED_FP]);
|
|
/* move the new arguments at the correct stack position */
|
|
argc = (call_flags & FRAME_CF_ARGC_MASK) + 2;
|
|
sp1 = fp + FRAME_OFFSET_ARG0 + pushed_argc - argc;
|
|
memmove(sp1, sp, sizeof(*sp) * (argc));
|
|
sp = sp1;
|
|
fp = fp1;
|
|
goto function_call;
|
|
} else {
|
|
sp = fp + FRAME_OFFSET_ARG0 + pushed_argc;
|
|
goto return_call;
|
|
}
|
|
} else if (p->class_id == JS_CLASS_CLOSURE) {
|
|
int n_vars;
|
|
call_flags = JS_VALUE_GET_INT(sp[FRAME_OFFSET_CALL_FLAGS]);
|
|
if (call_flags & FRAME_CF_CTOR) {
|
|
ctx->sp = sp;
|
|
ctx->fp = fp;
|
|
/* Note: can recurse at this point */
|
|
val = js_call_constructor_start(ctx, func_obj);
|
|
if (JS_IsException(val))
|
|
goto call_exception;
|
|
sp[FRAME_OFFSET_THIS_OBJ] = val;
|
|
func_obj = sp[FRAME_OFFSET_FUNC_OBJ];
|
|
p = JS_VALUE_TO_PTR(func_obj);
|
|
}
|
|
b = JS_VALUE_TO_PTR(p->u.closure.func_bytecode);
|
|
if (b->vars != JS_NULL) {
|
|
JSValueArray *vars = JS_VALUE_TO_PTR(b->vars);
|
|
n_vars = vars->size - b->arg_count;
|
|
} else {
|
|
n_vars = 0;
|
|
}
|
|
argc = call_flags & FRAME_CF_ARGC_MASK;
|
|
/* JS_StackCheck may trigger a gc */
|
|
ctx->sp = sp;
|
|
ctx->fp = fp;
|
|
n = JS_StackCheck(ctx, max_int(b->arg_count - argc, 0) + 2 + n_vars +
|
|
b->stack_size);
|
|
if (n) {
|
|
val = JS_EXCEPTION;
|
|
goto call_exception;
|
|
}
|
|
func_obj = sp[FRAME_OFFSET_FUNC_OBJ];
|
|
p = JS_VALUE_TO_PTR(func_obj);
|
|
b = JS_VALUE_TO_PTR(p->u.closure.func_bytecode);
|
|
/* add undefined arguments if the caller did not
|
|
provide enough arguments */
|
|
if (unlikely(b->arg_count > argc)) {
|
|
n = b->arg_count - argc;
|
|
sp -= n;
|
|
for(i = 0; i < FRAME_OFFSET_ARG0 + argc; i++)
|
|
sp[i] = sp[i + n];
|
|
for(i = 0; i < n; i++)
|
|
sp[FRAME_OFFSET_ARG0 + argc + i] = JS_UNDEFINED;
|
|
}
|
|
fp = sp;
|
|
*--sp = JS_NewShortInt(0); /* FRAME_OFFSET_CUR_PC */
|
|
*--sp = JS_NULL; /* FRAME_OFFSET_FIRST_VARREF */
|
|
sp -= n_vars;
|
|
for(i = 0; i < n_vars; i++)
|
|
sp[i] = JS_UNDEFINED;
|
|
byte_code = JS_VALUE_TO_PTR(b->byte_code);
|
|
pc = byte_code->buf;
|
|
} else {
|
|
not_a_function:
|
|
sp += 2; /* go back to the caller frame */
|
|
ctx->sp = sp;
|
|
ctx->fp = fp;
|
|
val = JS_ThrowTypeError(ctx, "not a function");
|
|
call_exception:
|
|
if (!pc) {
|
|
goto done;
|
|
} else {
|
|
RESTORE();
|
|
goto exception;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
BREAK;
|
|
|
|
exception:
|
|
/* 'val' must contain the exception */
|
|
{
|
|
JSValue *stack_top, val2;
|
|
JSValueArray *vars;
|
|
int v;
|
|
/* exception before entering in the first function ?
|
|
(XXX: remove this test) */
|
|
if (!pc)
|
|
goto done;
|
|
v = JS_VALUE_GET_SPECIAL_VALUE(val);
|
|
if (v >= JS_EX_CALL) {
|
|
/* tail call */
|
|
call_flags = JS_VALUE_GET_SPECIAL_VALUE(val) - JS_EX_CALL;
|
|
/* the opcode has only one byte, hence the PC must
|
|
be updated accordingly after the function
|
|
returns */
|
|
if (opcode == OP_get_length ||
|
|
opcode == OP_get_length2 ||
|
|
opcode == OP_get_array_el ||
|
|
opcode == OP_get_array_el2 ||
|
|
opcode == OP_put_array_el) {
|
|
call_flags |= FRAME_CF_PC_ADD1;
|
|
}
|
|
// js_printf(ctx, "tail call: 0x%x\n", call_flags);
|
|
goto generic_function_call;
|
|
}
|
|
/* XXX: start gc in case of JS_EXCEPTION_MEM */
|
|
stack_top = fp + FRAME_OFFSET_VAR0 + 1;
|
|
if (b->vars != JS_NULL) {
|
|
vars = JS_VALUE_TO_PTR(b->vars);
|
|
stack_top -= (vars->size - b->arg_count);
|
|
}
|
|
if (ctx->current_exception_is_uncatchable) {
|
|
sp = stack_top;
|
|
} else {
|
|
while (sp < stack_top) {
|
|
val2 = *sp++;
|
|
if (JS_VALUE_GET_SPECIAL_TAG(val2) == JS_TAG_CATCH_OFFSET) {
|
|
JSByteArray *byte_code;
|
|
/* exception caught by a 'catch' in the
|
|
current function */
|
|
*--sp = ctx->current_exception;
|
|
ctx->current_exception = JS_NULL;
|
|
byte_code = JS_VALUE_TO_PTR(b->byte_code);
|
|
pc = byte_code->buf + JS_VALUE_GET_SPECIAL_VALUE(val2);
|
|
goto restart;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
goto generic_return;
|
|
|
|
CASE(OP_return_undef):
|
|
val = JS_UNDEFINED;
|
|
goto generic_return;
|
|
|
|
CASE(OP_return):
|
|
val = sp[0];
|
|
generic_return:
|
|
{
|
|
JSObject *p;
|
|
int argc, pc_offset;
|
|
JSValue val2;
|
|
JSVarRef *pv;
|
|
JSByteArray *byte_code;
|
|
|
|
/* detach the variable references */
|
|
val2 = fp[FRAME_OFFSET_FIRST_VARREF];
|
|
while (val2 != JS_NULL) {
|
|
pv = JS_VALUE_TO_PTR(val2);
|
|
val2 = pv->u.next;
|
|
assert(!pv->is_detached);
|
|
pv->u.value = *pv->u.pvalue;
|
|
pv->is_detached = TRUE;
|
|
/* shrink 'pv' */
|
|
set_free_block((uint8_t *)pv + sizeof(JSVarRef) - sizeof(JSValue), sizeof(JSValue));
|
|
}
|
|
|
|
call_flags = JS_VALUE_GET_INT(fp[FRAME_OFFSET_CALL_FLAGS]);
|
|
if (unlikely(call_flags & FRAME_CF_CTOR)) {
|
|
if (!JS_IsException(val) && !JS_IsObject(ctx, val)) {
|
|
val = fp[FRAME_OFFSET_THIS_OBJ];
|
|
}
|
|
}
|
|
argc = call_flags & FRAME_CF_ARGC_MASK;
|
|
argc = max_int(argc, b->arg_count);
|
|
sp = fp + FRAME_OFFSET_ARG0 + argc;
|
|
return_call:
|
|
call_flags = JS_VALUE_GET_INT(fp[FRAME_OFFSET_CALL_FLAGS]);
|
|
/* XXX: restore stack_bottom to reduce memory usage */
|
|
fp = VALUE_TO_SP(ctx, fp[FRAME_OFFSET_SAVED_FP]);
|
|
if (fp == initial_fp)
|
|
goto done;
|
|
pc_offset = JS_VALUE_GET_INT(fp[FRAME_OFFSET_CUR_PC]);
|
|
p = JS_VALUE_TO_PTR(fp[FRAME_OFFSET_FUNC_OBJ]);
|
|
b = JS_VALUE_TO_PTR(p->u.closure.func_bytecode);
|
|
byte_code = JS_VALUE_TO_PTR(b->byte_code);
|
|
pc = byte_code->buf + pc_offset;
|
|
/* now we are in the calling function */
|
|
if (JS_IsException(val))
|
|
goto exception;
|
|
if (!(call_flags & FRAME_CF_POP_RET))
|
|
*--sp = val;
|
|
/* Note: if variable size call, can add a flag in call_flags */
|
|
if (!(call_flags & FRAME_CF_PC_ADD1))
|
|
pc += 2; /* skip the call arg or get_field/put_field arg */
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_catch):
|
|
{
|
|
int32_t diff;
|
|
JSByteArray *byte_code = JS_VALUE_TO_PTR(b->byte_code);
|
|
diff = get_u32(pc);
|
|
*--sp = JS_VALUE_MAKE_SPECIAL(JS_TAG_CATCH_OFFSET, pc + diff - byte_code->buf);
|
|
pc += 4;
|
|
}
|
|
BREAK;
|
|
CASE(OP_throw):
|
|
val = *sp++;
|
|
SAVE();
|
|
val = JS_Throw(ctx, val);
|
|
RESTORE();
|
|
goto exception;
|
|
CASE(OP_gosub):
|
|
{
|
|
int32_t diff;
|
|
JSByteArray *byte_code = JS_VALUE_TO_PTR(b->byte_code);
|
|
diff = get_u32(pc);
|
|
*--sp = JS_NewShortInt(pc + 4 - byte_code->buf);
|
|
pc += diff;
|
|
}
|
|
BREAK;
|
|
CASE(OP_ret):
|
|
{
|
|
JSByteArray *byte_code = JS_VALUE_TO_PTR(b->byte_code);
|
|
uint32_t pos;
|
|
if (unlikely(!JS_IsInt(sp[0])))
|
|
goto ret_fail;
|
|
pos = JS_VALUE_GET_INT(sp[0]);
|
|
if (unlikely(pos >= byte_code->size)) {
|
|
ret_fail:
|
|
SAVE();
|
|
val = JS_ThrowInternalError(ctx, "invalid ret value");
|
|
RESTORE();
|
|
goto exception;
|
|
}
|
|
sp++;
|
|
pc = byte_code->buf + pos;
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_get_loc):
|
|
{
|
|
int idx;
|
|
idx = get_u16(pc);
|
|
pc += 2;
|
|
*--sp = fp[FRAME_OFFSET_VAR0 - idx];
|
|
}
|
|
BREAK;
|
|
CASE(OP_put_loc):
|
|
{
|
|
int idx;
|
|
idx = get_u16(pc);
|
|
pc += 2;
|
|
fp[FRAME_OFFSET_VAR0 - idx] = sp[0];
|
|
sp++;
|
|
}
|
|
BREAK;
|
|
CASE(OP_get_arg):
|
|
{
|
|
int idx;
|
|
idx = get_u16(pc);
|
|
pc += 2;
|
|
*--sp = fp[FRAME_OFFSET_ARG0 + idx];
|
|
}
|
|
BREAK;
|
|
CASE(OP_put_arg):
|
|
{
|
|
int idx;
|
|
idx = get_u16(pc);
|
|
pc += 2;
|
|
fp[FRAME_OFFSET_ARG0 + idx] = sp[0];
|
|
sp++;
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_get_loc0): *--sp = fp[FRAME_OFFSET_VAR0 - 0]; BREAK;
|
|
CASE(OP_get_loc1): *--sp = fp[FRAME_OFFSET_VAR0 - 1]; BREAK;
|
|
CASE(OP_get_loc2): *--sp = fp[FRAME_OFFSET_VAR0 - 2]; BREAK;
|
|
CASE(OP_get_loc3): *--sp = fp[FRAME_OFFSET_VAR0 - 3]; BREAK;
|
|
CASE(OP_get_loc8): *--sp = fp[FRAME_OFFSET_VAR0 - *pc++]; BREAK;
|
|
|
|
CASE(OP_put_loc0): fp[FRAME_OFFSET_VAR0 - 0] = *sp++; BREAK;
|
|
CASE(OP_put_loc1): fp[FRAME_OFFSET_VAR0 - 1] = *sp++; BREAK;
|
|
CASE(OP_put_loc2): fp[FRAME_OFFSET_VAR0 - 2] = *sp++; BREAK;
|
|
CASE(OP_put_loc3): fp[FRAME_OFFSET_VAR0 - 3] = *sp++; BREAK;
|
|
CASE(OP_put_loc8): fp[FRAME_OFFSET_VAR0 - *pc++] = *sp++; BREAK;
|
|
|
|
CASE(OP_get_arg0): *--sp = fp[FRAME_OFFSET_ARG0 + 0]; BREAK;
|
|
CASE(OP_get_arg1): *--sp = fp[FRAME_OFFSET_ARG0 + 1]; BREAK;
|
|
CASE(OP_get_arg2): *--sp = fp[FRAME_OFFSET_ARG0 + 2]; BREAK;
|
|
CASE(OP_get_arg3): *--sp = fp[FRAME_OFFSET_ARG0 + 3]; BREAK;
|
|
|
|
CASE(OP_put_arg0): fp[FRAME_OFFSET_ARG0 + 0] = *sp++; BREAK;
|
|
CASE(OP_put_arg1): fp[FRAME_OFFSET_ARG0 + 1] = *sp++; BREAK;
|
|
CASE(OP_put_arg2): fp[FRAME_OFFSET_ARG0 + 2] = *sp++; BREAK;
|
|
CASE(OP_put_arg3): fp[FRAME_OFFSET_ARG0 + 3] = *sp++; BREAK;
|
|
|
|
CASE(OP_get_var_ref):
|
|
CASE(OP_get_var_ref_nocheck):
|
|
{
|
|
int idx;
|
|
JSObject *p;
|
|
JSVarRef *pv;
|
|
idx = get_u16(pc);
|
|
p = JS_VALUE_TO_PTR(fp[FRAME_OFFSET_FUNC_OBJ]);
|
|
pv = JS_VALUE_TO_PTR(p->u.closure.var_refs[idx]);
|
|
if (pv->is_detached)
|
|
val = pv->u.value;
|
|
else
|
|
val = *pv->u.pvalue;
|
|
if (unlikely(val == JS_TAG_UNINITIALIZED) &&
|
|
opcode == OP_get_var_ref) {
|
|
JSValueArray *ext_vars = JS_VALUE_TO_PTR(b->ext_vars);
|
|
SAVE();
|
|
val = JS_ThrowReferenceError(ctx, "variable '%"JSValue_PRI"' is not defined", ext_vars->arr[2 * idx]);
|
|
RESTORE();
|
|
goto exception;
|
|
}
|
|
pc += 2;
|
|
*--sp = val;
|
|
}
|
|
BREAK;
|
|
CASE(OP_put_var_ref):
|
|
CASE(OP_put_var_ref_nocheck):
|
|
{
|
|
int idx;
|
|
JSObject *p;
|
|
JSVarRef *pv;
|
|
JSValue *pval;
|
|
idx = get_u16(pc);
|
|
p = JS_VALUE_TO_PTR(fp[FRAME_OFFSET_FUNC_OBJ]);
|
|
pv = JS_VALUE_TO_PTR(p->u.closure.var_refs[idx]);
|
|
if (pv->is_detached)
|
|
pval = &pv->u.value;
|
|
else
|
|
pval = pv->u.pvalue;
|
|
if (unlikely(*pval == JS_TAG_UNINITIALIZED) &&
|
|
opcode == OP_put_var_ref) {
|
|
JSValueArray *ext_vars = JS_VALUE_TO_PTR(b->ext_vars);
|
|
SAVE();
|
|
val = JS_ThrowReferenceError(ctx, "variable '%"JSValue_PRI"' is not defined", ext_vars->arr[2 * idx]);
|
|
RESTORE();
|
|
goto exception;
|
|
}
|
|
*pval = *sp++;
|
|
pc += 2;
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_goto):
|
|
pc += (int32_t)get_u32(pc);
|
|
POLL_INTERRUPT();
|
|
BREAK;
|
|
CASE(OP_if_false):
|
|
CASE(OP_if_true):
|
|
{
|
|
int res;
|
|
|
|
pc += 4;
|
|
|
|
res = JS_ToBool(ctx, *sp++);
|
|
if (res ^ (OP_if_true - opcode)) {
|
|
pc += (int32_t)get_u32(pc - 4) - 4;
|
|
}
|
|
POLL_INTERRUPT();
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_lnot):
|
|
{
|
|
int res;
|
|
res = JS_ToBool(ctx, sp[0]);
|
|
sp[0] = JS_NewBool(!res);
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_get_field2):
|
|
sp--;
|
|
sp[0] = sp[1];
|
|
goto get_field_common;
|
|
CASE(OP_get_field):
|
|
get_field_common:
|
|
{
|
|
int idx;
|
|
JSValue prop, obj;
|
|
JSValueArray *cpool = JS_VALUE_TO_PTR(b->cpool);
|
|
idx = get_u16(pc);
|
|
prop = cpool->arr[idx];
|
|
obj = sp[0];
|
|
if (likely(JS_IsPtr(obj))) {
|
|
/* fast case */
|
|
JSObject *p = JS_VALUE_TO_PTR(obj);
|
|
JSProperty *pr;
|
|
if (unlikely(p->mtag != JS_MTAG_OBJECT))
|
|
goto get_field_slow;
|
|
for(;;) {
|
|
/* no array check is necessary because 'prop' is
|
|
guaranteed not to be a numeric property */
|
|
/* XXX: slow due to short ints */
|
|
pr = find_own_property_inlined(ctx, p, prop);
|
|
if (pr) {
|
|
if (unlikely(pr->prop_type != JS_PROP_NORMAL)) {
|
|
/* sp[0] is this_obj, obj is the current
|
|
object */
|
|
goto get_field_slow;
|
|
} else {
|
|
val = pr->value;
|
|
break;
|
|
}
|
|
}
|
|
obj = p->proto;
|
|
if (obj == JS_NULL) {
|
|
val = JS_UNDEFINED;
|
|
break;
|
|
}
|
|
p = JS_VALUE_TO_PTR(obj);
|
|
}
|
|
} else {
|
|
get_field_slow:
|
|
SAVE();
|
|
val = JS_GetPropertyInternal(ctx, obj, prop, TRUE);
|
|
RESTORE();
|
|
if (unlikely(JS_IsExceptionOrTailCall(val))) {
|
|
sp = ctx->sp;
|
|
goto exception;
|
|
}
|
|
}
|
|
pc += 2;
|
|
sp[0] = val;
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_get_length2):
|
|
sp--;
|
|
sp[0] = sp[1];
|
|
goto get_length_common;
|
|
|
|
CASE(OP_get_length):
|
|
get_length_common:
|
|
{
|
|
JSValue obj;
|
|
obj = sp[0];
|
|
if (likely(JS_IsPtr(obj))) {
|
|
/* fast case */
|
|
JSObject *p = JS_VALUE_TO_PTR(obj);
|
|
if (p->mtag == JS_MTAG_OBJECT) {
|
|
if (p->class_id == JS_CLASS_ARRAY) {
|
|
if (unlikely(p->proto != ctx->class_proto[JS_CLASS_ARRAY] ||
|
|
p->props != ctx->empty_props))
|
|
goto get_length_slow;
|
|
val = JS_NewShortInt(p->u.array.len);
|
|
} else {
|
|
goto get_length_slow;
|
|
}
|
|
} else if (p->mtag == JS_MTAG_STRING) {
|
|
JSString *ps = (JSString *)p;
|
|
if (likely(ps->is_ascii))
|
|
val = JS_NewShortInt(ps->len);
|
|
else
|
|
val = JS_NewShortInt(js_string_utf8_to_utf16_pos(ctx, obj, ps->len * 2));
|
|
} else {
|
|
goto get_length_slow;
|
|
}
|
|
} else if (JS_VALUE_GET_SPECIAL_TAG(val) == JS_TAG_STRING_CHAR) {
|
|
val = JS_NewShortInt(JS_VALUE_GET_SPECIAL_VALUE(val) >= 0x10000 ? 2 : 1);
|
|
} else {
|
|
get_length_slow:
|
|
SAVE();
|
|
val = JS_GetPropertyInternal(ctx, obj, js_get_atom(ctx, JS_ATOM_length), TRUE);
|
|
RESTORE();
|
|
if (unlikely(JS_IsExceptionOrTailCall(val))) {
|
|
sp = ctx->sp;
|
|
goto exception;
|
|
}
|
|
}
|
|
sp[0] = val;
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_put_field):
|
|
{
|
|
int idx;
|
|
JSValue prop, obj;
|
|
JSValueArray *cpool = JS_VALUE_TO_PTR(b->cpool);
|
|
|
|
idx = get_u16(pc);
|
|
prop = cpool->arr[idx];
|
|
obj = sp[1];
|
|
if (likely(JS_IsPtr(obj))) {
|
|
/* fast case */
|
|
JSObject *p = JS_VALUE_TO_PTR(obj);
|
|
JSProperty *pr;
|
|
if (unlikely(p->mtag != JS_MTAG_OBJECT))
|
|
goto put_field_slow;
|
|
/* no array check is necessary because 'prop' is
|
|
guaranteed not to be a numeric property */
|
|
/* XXX: slow due to short ints */
|
|
pr = find_own_property_inlined(ctx, p, prop);
|
|
if (unlikely(!pr))
|
|
goto put_field_slow;
|
|
if (unlikely(pr->prop_type != JS_PROP_NORMAL))
|
|
goto put_field_slow;
|
|
/* XXX: slow */
|
|
if (unlikely(JS_IS_ROM_PTR(ctx, pr)))
|
|
goto put_field_slow;
|
|
pr->value = sp[0];
|
|
sp += 2;
|
|
} else {
|
|
put_field_slow:
|
|
val = *sp++;
|
|
SAVE();
|
|
val = JS_SetPropertyInternal(ctx, sp[0], prop, val, TRUE);
|
|
RESTORE();
|
|
if (unlikely(JS_IsExceptionOrTailCall(val))) {
|
|
sp = ctx->sp;
|
|
goto exception;
|
|
}
|
|
sp++;
|
|
}
|
|
pc += 2;
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_get_array_el2):
|
|
val = sp[0];
|
|
sp[0] = sp[1];
|
|
goto get_array_el_common;
|
|
CASE(OP_get_array_el):
|
|
val = sp[0];
|
|
sp++;
|
|
get_array_el_common:
|
|
{
|
|
JSValue prop = val, obj;
|
|
obj = sp[0];
|
|
if (JS_IsPtr(obj) && JS_IsInt(prop)) {
|
|
/* fast case with array */
|
|
/* XXX: optimize typed arrays too ? */
|
|
JSObject *p = JS_VALUE_TO_PTR(obj);
|
|
uint32_t idx;
|
|
JSValueArray *arr;
|
|
if (unlikely(p->mtag != JS_MTAG_OBJECT))
|
|
goto get_array_el_slow;
|
|
if (unlikely(p->class_id != JS_CLASS_ARRAY))
|
|
goto get_array_el_slow;
|
|
idx = JS_VALUE_GET_INT(prop);
|
|
if (unlikely(idx >= p->u.array.len))
|
|
goto get_array_el_slow;
|
|
|
|
arr = JS_VALUE_TO_PTR(p->u.array.tab);
|
|
val = arr->arr[idx];
|
|
} else {
|
|
get_array_el_slow:
|
|
SAVE();
|
|
prop = JS_ToPropertyKey(ctx, prop);
|
|
RESTORE();
|
|
if (JS_IsException(prop)) {
|
|
val = prop;
|
|
goto exception;
|
|
}
|
|
SAVE();
|
|
val = JS_GetPropertyInternal(ctx, sp[0], prop, TRUE);
|
|
RESTORE();
|
|
if (unlikely(JS_IsExceptionOrTailCall(val))) {
|
|
sp = ctx->sp;
|
|
goto exception;
|
|
}
|
|
}
|
|
sp[0] = val;
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_put_array_el):
|
|
{
|
|
JSValue prop, obj;
|
|
obj = sp[2];
|
|
prop = sp[1];
|
|
if (JS_IsPtr(obj) && JS_IsInt(prop)) {
|
|
/* fast case with array */
|
|
/* XXX: optimize typed arrays too ? */
|
|
JSObject *p = JS_VALUE_TO_PTR(obj);
|
|
uint32_t idx;
|
|
JSValueArray *arr;
|
|
if (unlikely(p->mtag != JS_MTAG_OBJECT))
|
|
goto put_array_el_slow;
|
|
if (unlikely(p->class_id != JS_CLASS_ARRAY))
|
|
goto put_array_el_slow;
|
|
idx = JS_VALUE_GET_INT(prop);
|
|
arr = JS_VALUE_TO_PTR(p->u.array.tab);
|
|
if (unlikely(idx >= p->u.array.len)) {
|
|
if (idx == p->u.array.len &&
|
|
p->u.array.tab != JS_NULL &&
|
|
idx < arr->size) {
|
|
arr->arr[idx] = sp[0];
|
|
p->u.array.len = idx + 1;
|
|
} else {
|
|
goto put_array_el_slow;
|
|
}
|
|
} else {
|
|
arr->arr[idx] = sp[0];
|
|
}
|
|
sp += 3;
|
|
} else {
|
|
put_array_el_slow:
|
|
SAVE();
|
|
sp[1] = JS_ToPropertyKey(ctx, sp[1]);
|
|
RESTORE();
|
|
if (JS_IsException(sp[1])) {
|
|
val = sp[1];
|
|
goto exception;
|
|
}
|
|
val = *sp++;
|
|
prop = *sp++;
|
|
SAVE();
|
|
val = JS_SetPropertyInternal(ctx, sp[0], prop, val, TRUE);
|
|
RESTORE();
|
|
if (unlikely(JS_IsExceptionOrTailCall(val))) {
|
|
sp = ctx->sp;
|
|
goto exception;
|
|
}
|
|
sp++;
|
|
}
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_define_field):
|
|
CASE(OP_define_getter):
|
|
CASE(OP_define_setter):
|
|
{
|
|
int idx;
|
|
JSValue prop;
|
|
JSValueArray *cpool = JS_VALUE_TO_PTR(b->cpool);
|
|
|
|
idx = get_u16(pc);
|
|
prop = cpool->arr[idx];
|
|
|
|
SAVE();
|
|
if (opcode == OP_define_field) {
|
|
val = JS_DefinePropertyValue(ctx, sp[1], prop, sp[0]);
|
|
} else if (opcode == OP_define_getter)
|
|
val = JS_DefinePropertyGetSet(ctx, sp[1], prop, sp[0], JS_UNDEFINED, JS_DEF_PROP_HAS_GET);
|
|
else
|
|
val = JS_DefinePropertyGetSet(ctx, sp[1], prop, JS_UNDEFINED, sp[0], JS_DEF_PROP_HAS_SET);
|
|
RESTORE();
|
|
if (unlikely(JS_IsException(val)))
|
|
goto exception;
|
|
pc += 2;
|
|
sp++;
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_set_proto):
|
|
{
|
|
if (JS_IsObject(ctx, sp[0]) || JS_IsNull(sp[0])) {
|
|
SAVE();
|
|
val = js_set_prototype_internal(ctx, sp[1], sp[0]);
|
|
RESTORE();
|
|
if (unlikely(JS_IsException(val)))
|
|
goto exception;
|
|
}
|
|
sp++;
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_add):
|
|
{
|
|
JSValue op1, op2;
|
|
op1 = sp[1];
|
|
op2 = sp[0];
|
|
if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) {
|
|
int r;
|
|
if (unlikely(__builtin_add_overflow((int)op1, (int)op2, &r)))
|
|
goto add_slow;
|
|
sp[1] = (uint32_t)r;
|
|
} else
|
|
#ifdef JS_USE_SHORT_FLOAT
|
|
if (JS_VALUE_IS_BOTH_SHORT_FLOAT(op1, op2)) {
|
|
double d1, d2;
|
|
d1 = js_get_short_float(op1);
|
|
d2 = js_get_short_float(op2);
|
|
dr = d1 + d2;
|
|
sp++;
|
|
goto float_result;
|
|
} else
|
|
#endif
|
|
{
|
|
add_slow:
|
|
SAVE();
|
|
val = js_add_slow(ctx);
|
|
RESTORE();
|
|
if (JS_IsException(val))
|
|
goto exception;
|
|
sp[1] = val;
|
|
}
|
|
sp++;
|
|
}
|
|
BREAK;
|
|
CASE(OP_sub):
|
|
{
|
|
JSValue op1, op2;
|
|
op1 = sp[1];
|
|
op2 = sp[0];
|
|
if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) {
|
|
int r;
|
|
if (unlikely(__builtin_sub_overflow((int)op1, (int)op2, &r)))
|
|
goto binary_arith_slow;
|
|
sp[1] = (uint32_t)r;
|
|
} else
|
|
#ifdef JS_USE_SHORT_FLOAT
|
|
if (JS_VALUE_IS_BOTH_SHORT_FLOAT(op1, op2)) {
|
|
double d1, d2;
|
|
d1 = js_get_short_float(op1);
|
|
d2 = js_get_short_float(op2);
|
|
dr = d1 - d2;
|
|
sp++;
|
|
goto float_result;
|
|
} else
|
|
#endif
|
|
{
|
|
goto binary_arith_slow;
|
|
}
|
|
sp++;
|
|
}
|
|
BREAK;
|
|
CASE(OP_mul):
|
|
{
|
|
JSValue op1, op2;
|
|
op1 = sp[1];
|
|
op2 = sp[0];
|
|
if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) {
|
|
int v1, v2;
|
|
int64_t r;
|
|
v1 = (int)op1;
|
|
v2 = (int)op2 >> 1;
|
|
r = (int64_t)v1 * (int64_t)v2;
|
|
if (unlikely(r != (int)r)) {
|
|
#if defined(JS_USE_SHORT_FLOAT)
|
|
dr = (double)(r >> 1);
|
|
sp++;
|
|
goto float_result;
|
|
#else
|
|
goto binary_arith_slow;
|
|
#endif
|
|
}
|
|
/* -0 case */
|
|
if (unlikely(r == 0 && (v1 | v2) < 0)) {
|
|
sp[1] = ctx->minus_zero;
|
|
} else {
|
|
sp[1] = (uint32_t)r;
|
|
}
|
|
} else
|
|
#ifdef JS_USE_SHORT_FLOAT
|
|
if (JS_VALUE_IS_BOTH_SHORT_FLOAT(op1, op2)) {
|
|
double d1, d2;
|
|
d1 = js_get_short_float(op1);
|
|
d2 = js_get_short_float(op2);
|
|
dr = d1 * d2;
|
|
sp++;
|
|
goto float_result;
|
|
} else
|
|
#endif
|
|
{
|
|
goto binary_arith_slow;
|
|
}
|
|
sp++;
|
|
}
|
|
BREAK;
|
|
CASE(OP_div):
|
|
{
|
|
JSValue op1, op2;
|
|
op1 = sp[1];
|
|
op2 = sp[0];
|
|
if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) {
|
|
int v1, v2;
|
|
v1 = JS_VALUE_GET_INT(op1);
|
|
v2 = JS_VALUE_GET_INT(op2);
|
|
SAVE();
|
|
val = JS_NewFloat64(ctx, (double)v1 / (double)v2);
|
|
RESTORE();
|
|
if (JS_IsException(val))
|
|
goto exception;
|
|
sp[1] = val;
|
|
sp++;
|
|
} else {
|
|
goto binary_arith_slow;
|
|
}
|
|
}
|
|
BREAK;
|
|
CASE(OP_mod):
|
|
{
|
|
JSValue op1, op2;
|
|
op1 = sp[1];
|
|
op2 = sp[0];
|
|
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);
|
|
if (unlikely(v1 < 0 || v2 <= 0))
|
|
goto binary_arith_slow;
|
|
r = v1 % v2;
|
|
sp[1] = JS_NewShortInt(r);
|
|
sp++;
|
|
} else {
|
|
goto binary_arith_slow;
|
|
}
|
|
}
|
|
BREAK;
|
|
CASE(OP_pow):
|
|
binary_arith_slow:
|
|
SAVE();
|
|
val = js_binary_arith_slow(ctx, opcode);
|
|
RESTORE();
|
|
if (JS_IsException(val))
|
|
goto exception;
|
|
sp[1] = val;
|
|
sp++;
|
|
BREAK;
|
|
CASE(OP_plus):
|
|
{
|
|
JSValue op1;
|
|
op1 = sp[0];
|
|
if (JS_IsIntOrShortFloat(op1) ||
|
|
(JS_IsPtr(op1) && js_get_mtag(JS_VALUE_TO_PTR(op1)) == JS_MTAG_FLOAT64)) {
|
|
} else {
|
|
goto unary_arith_slow;
|
|
}
|
|
}
|
|
BREAK;
|
|
CASE(OP_neg):
|
|
{
|
|
JSValue op1;
|
|
int v1;
|
|
op1 = sp[0];
|
|
if (JS_IsInt(op1)) {
|
|
v1 = op1;
|
|
if (v1 == 0) {
|
|
sp[0] = ctx->minus_zero;
|
|
} else if (v1 == INT32_MIN) {
|
|
#if defined(JS_USE_SHORT_FLOAT)
|
|
dr = -(double)JS_SHORTINT_MIN;
|
|
goto float_result;
|
|
#else
|
|
goto unary_arith_slow;
|
|
#endif
|
|
} else {
|
|
sp[0] = -v1;
|
|
}
|
|
} else
|
|
#if defined(JS_USE_SHORT_FLOAT)
|
|
if (JS_IsShortFloat(op1)) {
|
|
dr = -js_get_short_float(op1);
|
|
float_result:
|
|
/* for efficiency, we don't try to store it as a short integer */
|
|
if (likely(fabs(dr) >= 0x1p-127 && fabs(dr) <= 0x1p+128)) {
|
|
val = js_to_short_float(dr);
|
|
} else if (dr == 0.0) {
|
|
if (float64_as_uint64(dr) != 0) {
|
|
/* minus zero often happens, so it is worth having a constant
|
|
value */
|
|
val = ctx->minus_zero;
|
|
} else {
|
|
/* XXX: could have a short float
|
|
representation for zero and minus zero
|
|
so that the float fast case is still
|
|
used when they happen */
|
|
val = JS_NewShortInt(0);
|
|
}
|
|
} else {
|
|
/* slow case: need to allocate it */
|
|
SAVE();
|
|
val = js_alloc_float64(ctx, dr);
|
|
RESTORE();
|
|
if (JS_IsException(val))
|
|
goto exception;
|
|
}
|
|
sp[0] = val;
|
|
} else
|
|
#endif
|
|
{
|
|
goto unary_arith_slow;
|
|
}
|
|
}
|
|
BREAK;
|
|
CASE(OP_inc):
|
|
{
|
|
JSValue op1;
|
|
int v1;
|
|
op1 = sp[0];
|
|
if (JS_IsInt(op1)) {
|
|
v1 = JS_VALUE_GET_INT(op1);
|
|
if (unlikely(v1 == JS_SHORTINT_MAX))
|
|
goto unary_arith_slow;
|
|
sp[0] = JS_NewShortInt(v1 + 1);
|
|
} else {
|
|
goto unary_arith_slow;
|
|
}
|
|
}
|
|
BREAK;
|
|
CASE(OP_dec):
|
|
{
|
|
JSValue op1;
|
|
int v1;
|
|
op1 = sp[0];
|
|
if (JS_IsInt(op1)) {
|
|
v1 = JS_VALUE_GET_INT(op1);
|
|
if (unlikely(v1 == JS_SHORTINT_MIN))
|
|
goto unary_arith_slow;
|
|
sp[0] = JS_NewShortInt(v1 - 1);
|
|
} else {
|
|
unary_arith_slow:
|
|
SAVE();
|
|
val = js_unary_arith_slow(ctx, opcode);
|
|
RESTORE();
|
|
if (JS_IsException(val))
|
|
goto exception;
|
|
sp[0] = val;
|
|
}
|
|
}
|
|
BREAK;
|
|
CASE(OP_post_inc):
|
|
CASE(OP_post_dec):
|
|
{
|
|
JSValue op1;
|
|
int v1;
|
|
op1 = sp[0];
|
|
if (JS_IsInt(op1)) {
|
|
v1 = JS_VALUE_GET_INT(op1) + 2 * (opcode - OP_post_dec) - 1;
|
|
if (v1 < JS_SHORTINT_MIN || v1 > JS_SHORTINT_MAX)
|
|
goto slow_post_inc_dec;
|
|
val = JS_NewShortInt(v1);
|
|
} else {
|
|
slow_post_inc_dec:
|
|
SAVE();
|
|
val = js_post_inc_slow(ctx, opcode);
|
|
RESTORE();
|
|
if (JS_IsException(val))
|
|
goto exception;
|
|
}
|
|
*--sp = val;
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_not):
|
|
{
|
|
JSValue op1;
|
|
op1 = sp[0];
|
|
if (JS_IsInt(op1)) {
|
|
sp[0] = (~op1) & (~1);
|
|
} else {
|
|
SAVE();
|
|
val = js_not_slow(ctx);
|
|
RESTORE();
|
|
if (JS_IsException(val))
|
|
goto exception;
|
|
sp[0] = val;
|
|
}
|
|
}
|
|
BREAK;
|
|
|
|
CASE(OP_shl):
|
|
{
|
|
JSValue op1, op2;
|
|
op1 = sp[1];
|
|
op2 = sp[0];
|
|
if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) {
|
|
int32_t r;
|
|
r = JS_VALUE_GET_INT(op1) << (JS_VALUE_GET_INT(op2) & 0x1f);
|
|
if (unlikely(r < JS_SHORTINT_MIN || r > JS_SHORTINT_MAX)) {
|
|
#if defined(JS_USE_SHORT_FLOAT)
|
|
dr = (double)r;
|
|
sp++;
|
|
goto float_result;
|
|
#else
|
|
goto binary_logic_slow;
|
|
#endif
|
|
}
|
|
sp[1] = JS_NewShortInt(r);
|
|
sp++;
|
|
} else {
|
|
goto binary_logic_slow;
|
|
}
|
|
}
|
|
BREAK;
|
|
CASE(OP_shr):
|
|
{
|
|
JSValue op1, op2;
|
|
op1 = sp[1];
|
|
op2 = sp[0];
|
|
if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) {
|
|
uint32_t r;
|
|
r = (uint32_t)JS_VALUE_GET_INT(op1) >>
|
|
((uint32_t)JS_VALUE_GET_INT(op2) & 0x1f);
|
|
if (unlikely(r > JS_SHORTINT_MAX)) {
|
|
#if defined(JS_USE_SHORT_FLOAT)
|
|
dr = (double)r;
|
|
sp++;
|
|
goto float_result;
|
|
#else
|
|
goto binary_logic_slow;
|
|
#endif
|
|
}
|
|
sp[1] = JS_NewShortInt(r);
|
|
sp++;
|
|
} else {
|
|
goto binary_logic_slow;
|
|
}
|
|
}
|
|
BREAK;
|
|
CASE(OP_sar):
|
|
{
|
|
JSValue op1, op2;
|
|
op1 = sp[1];
|
|
op2 = sp[0];
|
|
if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) {
|
|
sp[1] = ((int)op1 >> ((uint32_t)JS_VALUE_GET_INT(op2) & 0x1f)) & ~1;
|
|
sp++;
|
|
} else {
|
|
goto binary_logic_slow;
|
|
}
|
|
}
|
|
BREAK;
|
|
CASE(OP_and):
|
|
{
|
|
JSValue op1, op2;
|
|
op1 = sp[1];
|
|
op2 = sp[0];
|
|
if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) {
|
|
sp[1] = op1 & op2;
|
|
sp++;
|
|
} else {
|
|
goto binary_logic_slow;
|
|
}
|
|
}
|
|
BREAK;
|
|
CASE(OP_or):
|
|
{
|
|
JSValue op1, op2;
|
|
op1 = sp[1];
|
|
op2 = sp[0];
|
|
if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) {
|
|
sp[1] = op1 | op2;
|
|
sp++;
|
|
} else {
|
|
goto binary_logic_slow;
|
|
}
|
|
}
|
|
BREAK;
|
|
CASE(OP_xor):
|
|
{
|
|
JSValue op1, op2;
|
|
op1 = sp[1];
|
|
op2 = sp[0];
|
|
if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) {
|
|
sp[1] = op1 ^ op2;
|
|
sp++;
|
|
} else {
|
|
binary_logic_slow:
|
|
SAVE();
|
|
val = js_binary_logic_slow(ctx, opcode);
|
|
RESTORE();
|
|
if (JS_IsException(val))
|
|
goto exception;
|
|
sp[1] = val;
|
|
sp++;
|
|
}
|
|
}
|
|
BREAK;
|
|
|
|
|
|
#define OP_CMP(opcode, binary_op, slow_call) \
|
|
CASE(opcode): \
|
|
{ \
|
|
JSValue op1, op2; \
|
|
op1 = sp[1]; \
|
|
op2 = sp[0]; \
|
|
if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { \
|
|
sp[1] = JS_NewBool(JS_VALUE_GET_INT(op1) binary_op JS_VALUE_GET_INT(op2)); \
|
|
sp++; \
|
|
} else { \
|
|
SAVE(); \
|
|
val = slow_call; \
|
|
RESTORE(); \
|
|
if (JS_IsException(val)) \
|
|
goto exception; \
|
|
sp[1] = val; \
|
|
sp++; \
|
|
} \
|
|
} \
|
|
BREAK;
|
|
|
|
OP_CMP(OP_lt, <, js_relational_slow(ctx, opcode));
|
|
OP_CMP(OP_lte, <=, js_relational_slow(ctx, opcode));
|
|
OP_CMP(OP_gt, >, js_relational_slow(ctx, opcode));
|
|
OP_CMP(OP_gte, >=, js_relational_slow(ctx, opcode));
|
|
OP_CMP(OP_eq, ==, js_eq_slow(ctx, 0));
|
|
OP_CMP(OP_neq, !=, js_eq_slow(ctx, 1));
|
|
OP_CMP(OP_strict_eq, ==, js_strict_eq_slow(ctx, 0));
|
|
OP_CMP(OP_strict_neq, !=, js_strict_eq_slow(ctx, 1));
|
|
CASE(OP_in):
|
|
SAVE();
|
|
val = js_operator_in(ctx);
|
|
RESTORE();
|
|
if (unlikely(JS_IsException(val)))
|
|
goto exception;
|
|
sp[1] = val;
|
|
sp++;
|
|
BREAK;
|
|
CASE(OP_instanceof):
|
|
SAVE();
|
|
val = js_operator_instanceof(ctx);
|
|
RESTORE();
|
|
if (unlikely(JS_IsException(val)))
|
|
goto exception;
|
|
sp[1] = val;
|
|
sp++;
|
|
BREAK;
|
|
CASE(OP_typeof):
|
|
SAVE();
|
|
val = js_operator_typeof(ctx, sp[0]);
|
|
RESTORE();
|
|
if (unlikely(JS_IsException(val)))
|
|
goto exception;
|
|
sp[0] = val;
|
|
BREAK;
|
|
CASE(OP_delete):
|
|
SAVE();
|
|
val = JS_DeleteProperty(ctx, sp[1], sp[0]);
|
|
RESTORE();
|
|
if (unlikely(JS_IsException(val)))
|
|
goto exception;
|
|
sp[1] = val;
|
|
sp++;
|
|
BREAK;
|
|
CASE(OP_for_in_start):
|
|
CASE(OP_for_of_start):
|
|
SAVE();
|
|
val = js_for_of_start(ctx, (opcode == OP_for_in_start));
|
|
RESTORE();
|
|
if (unlikely(JS_IsException(val)))
|
|
goto exception;
|
|
sp[0] = val;
|
|
BREAK;
|
|
CASE(OP_for_of_next):
|
|
SAVE();
|
|
val = js_for_of_next(ctx);
|
|
RESTORE();
|
|
if (unlikely(JS_IsException(val)))
|
|
goto exception;
|
|
sp -= 2;
|
|
BREAK;
|
|
default:
|
|
{
|
|
JSByteArray *byte_code = JS_VALUE_TO_PTR(b->byte_code);
|
|
SAVE();
|
|
val = JS_ThrowInternalError(ctx, "invalid opcode: pc=%u opcode=0x%02x",
|
|
(int)(pc - byte_code->buf - 1), opcode);
|
|
RESTORE();
|
|
}
|
|
goto exception;
|
|
}
|
|
restart: ;
|
|
} /* switch */
|
|
done:
|
|
ctx->sp = sp;
|
|
ctx->fp = fp;
|
|
ctx->js_call_rec_count--;
|
|
return val;
|
|
}
|
|
|
|
#undef SAVE
|
|
#undef RESTORE
|
|
|
|
static inline int is_ident_first(int c)
|
|
{
|
|
return (c >= 'a' && c <= 'z') ||
|
|
(c >= 'A' && c <= 'Z') ||
|
|
c == '_' || c == '$';
|
|
}
|
|
|
|
static inline int is_ident_next(int c)
|
|
{
|
|
return is_ident_first(c) || is_num(c);
|
|
}
|
|
|
|
/**********************************************************************/
|
|
/* dump utilities */
|
|
|
|
#ifdef JS_DUMP
|
|
|
|
static void js_dump_array(JSContext *ctx, JSValueArray *arr, int len)
|
|
{
|
|
int i;
|
|
|
|
js_printf(ctx, "[ ");
|
|
for(i = 0; i < len; i++) {
|
|
if (i != 0)
|
|
js_printf(ctx, ", ");
|
|
JS_PrintValue(ctx, arr->arr[i]);
|
|
}
|
|
js_printf(ctx, " ]");
|
|
}
|
|
|
|
/* put constructors into a separate table */
|
|
/* XXX: improve by using a table */
|
|
static JSValue js_find_class_name(JSContext *ctx, int class_id)
|
|
{
|
|
const JSCFunctionDef *fd;
|
|
fd = ctx->c_function_table;
|
|
while ((fd->def_type != JS_CFUNC_constructor_magic &&
|
|
fd->def_type != JS_CFUNC_constructor) ||
|
|
fd->magic != class_id) {
|
|
fd++;
|
|
}
|
|
return reloc_c_func_name(ctx, fd->name);
|
|
}
|
|
|
|
static void js_dump_float64(JSContext *ctx, double d)
|
|
{
|
|
char buf[32];
|
|
JSDTOATempMem tmp_mem; /* XXX: potentially large stack size */
|
|
js_dtoa(buf, d, 10, 0, JS_DTOA_FORMAT_FREE | JS_DTOA_MINUS_ZERO, &tmp_mem);
|
|
js_printf(ctx, "%s", buf);
|
|
}
|
|
|
|
static void dump_regexp(JSContext *ctx, JSObject *p);
|
|
|
|
static void js_dump_error(JSContext *ctx, JSObject *p)
|
|
{
|
|
JSObject *p1;
|
|
JSProperty *pr;
|
|
JSValue name;
|
|
|
|
/* find the error name without side effect */
|
|
p1 = p;
|
|
if (p->proto != JS_NULL)
|
|
p1 = JS_VALUE_TO_PTR(p->proto);
|
|
pr = find_own_property(ctx, p1, js_get_atom(ctx, JS_ATOM_name));
|
|
if (!pr || !JS_IsString(ctx, pr->value))
|
|
name = js_get_atom(ctx, JS_ATOM_Error);
|
|
else
|
|
name = pr->value;
|
|
js_printf(ctx, "%" JSValue_PRI, name);
|
|
if (p->u.error.message != JS_NULL) {
|
|
js_printf(ctx, ": %" JSValue_PRI, p->u.error.message);
|
|
}
|
|
if (p->u.error.stack != JS_NULL) {
|
|
/* remove the trailing '\n' if any */
|
|
js_printf(ctx, "\n%#" JSValue_PRI, p->u.error.stack);
|
|
}
|
|
}
|
|
|
|
static void js_dump_object(JSContext *ctx, JSObject *p, int flags)
|
|
{
|
|
if (flags & JS_DUMP_LONG) {
|
|
switch(p->class_id) {
|
|
case JS_CLASS_CLOSURE:
|
|
{
|
|
JSFunctionBytecode *b = JS_VALUE_TO_PTR(p->u.closure.func_bytecode);
|
|
js_printf(ctx, "function ");
|
|
JS_PrintValueF(ctx, b->func_name, JS_DUMP_NOQUOTE);
|
|
js_printf(ctx, "()");
|
|
}
|
|
break;
|
|
case JS_CLASS_C_FUNCTION:
|
|
js_printf(ctx, "function ");
|
|
JS_PrintValueF(ctx, reloc_c_func_name(ctx, ctx->c_function_table[p->u.cfunc.idx].name), JS_DUMP_NOQUOTE);
|
|
js_printf(ctx, "()");
|
|
break;
|
|
case JS_CLASS_ERROR:
|
|
js_dump_error(ctx, p);
|
|
break;
|
|
case JS_CLASS_REGEXP:
|
|
dump_regexp(ctx, p);
|
|
break;
|
|
default:
|
|
case JS_CLASS_ARRAY:
|
|
case JS_CLASS_OBJECT:
|
|
if (p->class_id >= JS_CLASS_UINT8C_ARRAY &&
|
|
p->class_id <= JS_CLASS_FLOAT64_ARRAY) {
|
|
int i, idx;
|
|
uint32_t v;
|
|
double d;
|
|
JSObject *pbuffer;
|
|
JSByteArray *arr;
|
|
JS_PrintValueF(ctx, js_find_class_name(ctx, p->class_id),
|
|
JS_DUMP_NOQUOTE);
|
|
js_printf(ctx, "([ ");
|
|
pbuffer = JS_VALUE_TO_PTR(p->u.typed_array.buffer);
|
|
arr = JS_VALUE_TO_PTR(pbuffer->u.array_buffer.byte_buffer);
|
|
for(i = 0; i < p->u.typed_array.len; i++) {
|
|
if (i != 0)
|
|
js_printf(ctx, ", ");
|
|
idx = i + p->u.typed_array.offset;
|
|
switch(p->class_id) {
|
|
default:
|
|
case JS_CLASS_UINT8C_ARRAY:
|
|
case JS_CLASS_UINT8_ARRAY:
|
|
v = *((uint8_t *)arr->buf + idx);
|
|
goto ta_i32;
|
|
case JS_CLASS_INT8_ARRAY:
|
|
v = *((int8_t *)arr->buf + idx);
|
|
goto ta_i32;
|
|
case JS_CLASS_INT16_ARRAY:
|
|
v = *((int16_t *)arr->buf + idx);
|
|
goto ta_i32;
|
|
case JS_CLASS_UINT16_ARRAY:
|
|
v = *((uint16_t *)arr->buf + idx);
|
|
goto ta_i32;
|
|
case JS_CLASS_INT32_ARRAY:
|
|
v = *((int32_t *)arr->buf + idx);
|
|
ta_i32:
|
|
js_printf(ctx, "%d", v);
|
|
break;
|
|
case JS_CLASS_UINT32_ARRAY:
|
|
v = *((uint32_t *)arr->buf + idx);
|
|
js_printf(ctx, "%u", v);
|
|
break;
|
|
case JS_CLASS_FLOAT32_ARRAY:
|
|
d = *((float *)arr->buf + idx);
|
|
goto ta_d;
|
|
case JS_CLASS_FLOAT64_ARRAY:
|
|
d = *((double *)arr->buf + idx);
|
|
ta_d:
|
|
js_dump_float64(ctx, d);
|
|
break;
|
|
}
|
|
}
|
|
js_printf(ctx, " ])");
|
|
} else {
|
|
int i, j, prop_count, hash_mask;
|
|
JSProperty *pr;
|
|
JSValueArray *arr;
|
|
BOOL is_first = TRUE;
|
|
|
|
arr = JS_VALUE_TO_PTR(p->props);
|
|
prop_count = JS_VALUE_GET_INT(arr->arr[0]);
|
|
hash_mask = JS_VALUE_GET_INT(arr->arr[1]);
|
|
if (p->class_id == JS_CLASS_ARRAY) {
|
|
JSValueArray *tab = JS_VALUE_TO_PTR(p->u.array.tab);
|
|
js_printf(ctx, "[ ");
|
|
for(i = 0; i < p->u.array.len; i++) {
|
|
if (!is_first)
|
|
js_printf(ctx, ", ");
|
|
JS_PrintValue(ctx, tab->arr[i]);
|
|
is_first = FALSE;
|
|
}
|
|
} else {
|
|
if (p->class_id != JS_CLASS_OBJECT) {
|
|
JSValue class_name = js_find_class_name(ctx, p->class_id);
|
|
if (!JS_IsNull(class_name))
|
|
JS_PrintValueF(ctx, class_name, JS_DUMP_NOQUOTE);
|
|
js_putchar(ctx, ' ');
|
|
}
|
|
js_printf(ctx, "{ ");
|
|
}
|
|
for(i = 0, j = 0; j < prop_count; i++) {
|
|
pr = (JSProperty *)&arr->arr[2 + (hash_mask + 1) + 3 * i];
|
|
if (pr->key != JS_UNINITIALIZED) {
|
|
if (!is_first)
|
|
js_printf(ctx, ", ");
|
|
JS_PrintValueF(ctx, pr->key, JS_DUMP_NOQUOTE);
|
|
js_printf(ctx, ": ");
|
|
if (!(flags & JS_DUMP_RAW) && pr->prop_type == JS_PROP_SPECIAL) {
|
|
JS_PrintValue(ctx, get_special_prop(ctx, pr->value));
|
|
} else {
|
|
JS_PrintValue(ctx, pr->value);
|
|
}
|
|
is_first = FALSE;
|
|
j++;
|
|
}
|
|
}
|
|
js_printf(ctx, " %c",
|
|
p->class_id == JS_CLASS_ARRAY ? ']' : '}');
|
|
}
|
|
break;
|
|
}
|
|
} else {
|
|
const char *str;
|
|
if (p->class_id == JS_CLASS_ARRAY)
|
|
str = "Array";
|
|
else if (p->class_id == JS_CLASS_ERROR)
|
|
str = "Error";
|
|
else if (p->class_id == JS_CLASS_CLOSURE ||
|
|
p->class_id == JS_CLASS_C_FUNCTION) {
|
|
str = "Function";
|
|
} else {
|
|
str = "Object";
|
|
}
|
|
js_printf(ctx, "[object %s]", str);
|
|
}
|
|
}
|
|
|
|
static void dump_string(JSContext *ctx, int sep, const uint8_t *buf, size_t len,
|
|
int flags)
|
|
{
|
|
BOOL use_quote;
|
|
const uint8_t *p, *p_end;
|
|
size_t i, clen;
|
|
int c;
|
|
|
|
use_quote = TRUE;
|
|
if (flags & JS_DUMP_NOQUOTE) {
|
|
if (len >= 1 && is_ident_first(buf[0])) {
|
|
for(i = 1; i < len; i++) {
|
|
if (!is_ident_next(buf[i]))
|
|
goto need_quote;
|
|
}
|
|
use_quote = FALSE;
|
|
}
|
|
need_quote: ;
|
|
}
|
|
|
|
if (!(flags & JS_DUMP_RAW))
|
|
sep = '"';
|
|
if (use_quote)
|
|
js_putchar(ctx, sep);
|
|
p = buf;
|
|
p_end = buf + len;
|
|
while (p < p_end) {
|
|
c = utf8_get(p, &clen);
|
|
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:
|
|
js_putchar(ctx, '\\');
|
|
js_putchar(ctx, c);
|
|
break;
|
|
default:
|
|
if (c < 32 || (c >= 0xd800 && c < 0xe000)) {
|
|
js_printf(ctx, "\\u%04x", c);
|
|
} else {
|
|
ctx->write_func(ctx->opaque, p, clen);
|
|
}
|
|
break;
|
|
}
|
|
p += clen;
|
|
}
|
|
if (use_quote)
|
|
js_putchar(ctx, sep);
|
|
}
|
|
|
|
void JS_PrintValueF(JSContext *ctx, JSValue val, int flags)
|
|
{
|
|
if (JS_IsInt(val)) {
|
|
js_printf(ctx, "%d", JS_VALUE_GET_INT(val));
|
|
} else
|
|
#ifdef JS_USE_SHORT_FLOAT
|
|
if (JS_IsShortFloat(val)) {
|
|
js_dump_float64(ctx, js_get_short_float(val));
|
|
} else
|
|
#endif
|
|
if (!JS_IsPtr(val)) {
|
|
switch(JS_VALUE_GET_SPECIAL_TAG(val)) {
|
|
case JS_TAG_NULL:
|
|
case JS_TAG_UNDEFINED:
|
|
case JS_TAG_UNINITIALIZED:
|
|
case JS_TAG_BOOL:
|
|
js_printf(ctx, "%"JSValue_PRI"", val);
|
|
break;
|
|
case JS_TAG_EXCEPTION:
|
|
js_printf(ctx, "[exception %d]", JS_VALUE_GET_SPECIAL_VALUE(val));
|
|
break;
|
|
case JS_TAG_CATCH_OFFSET:
|
|
js_printf(ctx, "[catch_offset %d]", JS_VALUE_GET_SPECIAL_VALUE(val));
|
|
break;
|
|
case JS_TAG_SHORT_FUNC:
|
|
{
|
|
int idx = JS_VALUE_GET_SPECIAL_VALUE(val);
|
|
js_printf(ctx, "function ");
|
|
JS_PrintValueF(ctx, reloc_c_func_name(ctx, ctx->c_function_table[idx].name), JS_DUMP_NOQUOTE);
|
|
js_printf(ctx, "()");
|
|
}
|
|
break;
|
|
case JS_TAG_STRING_CHAR:
|
|
{
|
|
uint8_t buf[UTF8_CHAR_LEN_MAX + 1];
|
|
int len;
|
|
len = get_short_string(buf, val);
|
|
dump_string(ctx, '`', buf, len, flags);
|
|
}
|
|
break;
|
|
default:
|
|
js_printf(ctx, "[tag %d]", (int)JS_VALUE_GET_SPECIAL_TAG(val));
|
|
break;
|
|
}
|
|
} else {
|
|
void *ptr = JS_VALUE_TO_PTR(val);
|
|
int mtag = ((JSMemBlockHeader *)ptr)->mtag;
|
|
switch(mtag) {
|
|
case JS_MTAG_FLOAT64:
|
|
{
|
|
JSFloat64 *p = ptr;
|
|
js_dump_float64(ctx, p->u.dval);
|
|
}
|
|
break;
|
|
case JS_MTAG_OBJECT:
|
|
js_dump_object(ctx, ptr, flags);
|
|
break;
|
|
case JS_MTAG_STRING:
|
|
{
|
|
JSString *p = ptr;
|
|
int sep;
|
|
sep = p->is_unique ? '\'' : '\"';
|
|
dump_string(ctx, sep, p->buf, p->len, flags);
|
|
}
|
|
break;
|
|
case JS_MTAG_VALUE_ARRAY:
|
|
{
|
|
JSValueArray *arr = ptr;
|
|
js_dump_array(ctx, arr, arr->size);
|
|
}
|
|
break;
|
|
case JS_MTAG_BYTE_ARRAY:
|
|
{
|
|
JSByteArray *arr = ptr;
|
|
js_printf(ctx, "byte_array(%" PRIu64 ")", (uint64_t)arr->size);
|
|
}
|
|
break;
|
|
case JS_MTAG_FUNCTION_BYTECODE:
|
|
{
|
|
JSFunctionBytecode *b = ptr;
|
|
js_printf(ctx, "bytecode_function ");
|
|
JS_PrintValueF(ctx, b->func_name, JS_DUMP_NOQUOTE);
|
|
js_printf(ctx, "()");
|
|
}
|
|
break;
|
|
case JS_MTAG_VARREF:
|
|
{
|
|
JSVarRef *pv = ptr;
|
|
js_printf(ctx, "var_ref(");
|
|
if (pv->is_detached)
|
|
JS_PrintValue(ctx, pv->u.value);
|
|
else
|
|
JS_PrintValue(ctx, *pv->u.pvalue);
|
|
js_printf(ctx, ")");
|
|
}
|
|
break;
|
|
default:
|
|
js_printf(ctx, "[mtag %d]", mtag);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void JS_PrintValue(JSContext *ctx, JSValue val)
|
|
{
|
|
return JS_PrintValueF(ctx, val, 0);
|
|
}
|
|
|
|
static const char *get_mtag_name(unsigned int mtag)
|
|
{
|
|
if (mtag >= countof(js_mtag_name))
|
|
return "?";
|
|
else
|
|
return js_mtag_name[mtag];
|
|
}
|
|
|
|
static uint32_t val_to_offset(JSContext *ctx, JSValue val)
|
|
{
|
|
if (!JS_IsPtr(val))
|
|
return 0;
|
|
else
|
|
return (uint8_t *)JS_VALUE_TO_PTR(val) - ctx->heap_base;
|
|
}
|
|
|
|
void JS_DumpMemory(JSContext *ctx, BOOL is_long)
|
|
{
|
|
uint8_t *ptr;
|
|
uint32_t mtag_mem_size[JS_MTAG_COUNT];
|
|
uint32_t mtag_count[JS_MTAG_COUNT];
|
|
uint32_t tot_size, i;
|
|
if (is_long) {
|
|
js_printf(ctx, "%10s %s %8s %15s %10s %10s %s\n", "OFFSET", "M", "SIZE", "TAG", "PROTO", "PROPS", "EXTRA");
|
|
}
|
|
for(i = 0; i < JS_MTAG_COUNT; i++) {
|
|
mtag_mem_size[i] = 0;
|
|
mtag_count[i] = 0;
|
|
}
|
|
tot_size = 0;
|
|
ptr = ctx->heap_base;
|
|
while (ptr < ctx->heap_free) {
|
|
int mtag, size, gc_mark;
|
|
mtag = ((JSMemBlockHeader *)ptr)->mtag;
|
|
gc_mark = ((JSMemBlockHeader *)ptr)->gc_mark;
|
|
size = get_mblock_size(ptr);
|
|
mtag_mem_size[mtag] += size;
|
|
mtag_count[mtag]++;
|
|
tot_size += size;
|
|
if (is_long) {
|
|
js_printf(ctx, "0x%08x %c %8u %15s",
|
|
(unsigned int)((uint8_t *)ptr - ctx->heap_base),
|
|
gc_mark ? '*' : ' ',
|
|
size,
|
|
get_mtag_name(mtag));
|
|
if (mtag != JS_MTAG_FREE) {
|
|
if (mtag == JS_MTAG_OBJECT) {
|
|
JSObject *p = (JSObject *)ptr;
|
|
js_printf(ctx, " 0x%08x 0x%08x",
|
|
val_to_offset(ctx, p->proto), val_to_offset(ctx, p->props));
|
|
} else {
|
|
js_printf(ctx, " %10s %10s", "", "");
|
|
}
|
|
js_printf(ctx, " ");
|
|
JS_PrintValueF(ctx, JS_VALUE_FROM_PTR(ptr), JS_DUMP_RAW);
|
|
}
|
|
js_printf(ctx, "\n");
|
|
}
|
|
ptr += size;
|
|
}
|
|
|
|
js_printf(ctx, "%15s %8s %8s %8s %8s\n", "TAG", "COUNT", "AVG_SIZE", "SIZE", "RATIO");
|
|
for(i = 0; i < JS_MTAG_COUNT; i++) {
|
|
if (mtag_count[i] != 0) {
|
|
js_printf(ctx, "%15s %8u %8d %8u %7d%%\n",
|
|
get_mtag_name(i),
|
|
(unsigned int)mtag_count[i],
|
|
(int)js_lrint((double)mtag_mem_size[i] / (double)mtag_count[i]),
|
|
(unsigned int)mtag_mem_size[i],
|
|
(int)js_lrint((double)mtag_mem_size[i] / (double)tot_size * 100.0));
|
|
}
|
|
}
|
|
js_printf(ctx, "heap size=%u/%u stack_size=%u\n",
|
|
(unsigned int)(ctx->heap_free - ctx->heap_base),
|
|
(unsigned int)(ctx->stack_top - ctx->heap_base),
|
|
(unsigned int)(ctx->stack_top - (uint8_t *)ctx->sp));
|
|
}
|
|
|
|
static __maybe_unused void JS_DumpUniqueStrings(JSContext *ctx)
|
|
{
|
|
int i;
|
|
JSValueArray *arr;
|
|
|
|
arr = JS_VALUE_TO_PTR( ctx->unique_strings);
|
|
js_printf(ctx, "%5s %s\n", "N", "UNIQUE_STRING");
|
|
for(i = 0; i < ctx->unique_strings_len; i++) {
|
|
js_printf(ctx, "%5d ", i);
|
|
JS_PrintValue(ctx, arr->arr[i]);
|
|
js_printf(ctx, "\n");
|
|
}
|
|
}
|
|
#else
|
|
void JS_PrintValueF(JSContext *ctx, JSValue val, int flags)
|
|
{
|
|
}
|
|
void JS_PrintValue(JSContext *ctx, JSValue val)
|
|
{
|
|
return JS_PrintValueF(ctx, val, 0);
|
|
}
|
|
void JS_DumpMemory(JSContext *ctx, BOOL is_long)
|
|
{
|
|
}
|
|
static __maybe_unused void JS_DumpUniqueStrings(JSContext *ctx)
|
|
{
|
|
}
|
|
#endif
|
|
|
|
void JS_DumpValueF(JSContext *ctx, const char *str,
|
|
JSValue val, int flags)
|
|
{
|
|
js_printf(ctx, "%s=", str);
|
|
JS_PrintValueF(ctx, val, flags);
|
|
js_printf(ctx, "\n");
|
|
}
|
|
|
|
void JS_DumpValue(JSContext *ctx, const char *str,
|
|
JSValue val)
|
|
{
|
|
JS_DumpValueF(ctx, str, val, 0);
|
|
}
|
|
|
|
|
|
/**************************************************/
|
|
/* JS parser */
|
|
|
|
enum {
|
|
TOK_NUMBER = 128,
|
|
TOK_STRING,
|
|
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_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_EOF,
|
|
/* keywords */
|
|
TOK_FIRST_KEYWORD,
|
|
TOK_NULL = TOK_FIRST_KEYWORD + JS_ATOM_null,
|
|
TOK_FALSE = TOK_FIRST_KEYWORD + JS_ATOM_false,
|
|
TOK_TRUE = TOK_FIRST_KEYWORD + JS_ATOM_true,
|
|
TOK_IF = TOK_FIRST_KEYWORD + JS_ATOM_if,
|
|
TOK_ELSE = TOK_FIRST_KEYWORD + JS_ATOM_else,
|
|
TOK_RETURN = TOK_FIRST_KEYWORD + JS_ATOM_return,
|
|
TOK_VAR = TOK_FIRST_KEYWORD + JS_ATOM_var,
|
|
TOK_THIS = TOK_FIRST_KEYWORD + JS_ATOM_this,
|
|
TOK_DELETE = TOK_FIRST_KEYWORD + JS_ATOM_delete,
|
|
TOK_VOID = TOK_FIRST_KEYWORD + JS_ATOM_void,
|
|
TOK_TYPEOF = TOK_FIRST_KEYWORD + JS_ATOM_typeof,
|
|
TOK_NEW = TOK_FIRST_KEYWORD + JS_ATOM_new,
|
|
TOK_IN = TOK_FIRST_KEYWORD + JS_ATOM_in,
|
|
TOK_INSTANCEOF = TOK_FIRST_KEYWORD + JS_ATOM_instanceof,
|
|
TOK_DO = TOK_FIRST_KEYWORD + JS_ATOM_do,
|
|
TOK_WHILE = TOK_FIRST_KEYWORD + JS_ATOM_while,
|
|
TOK_FOR = TOK_FIRST_KEYWORD + JS_ATOM_for,
|
|
TOK_BREAK = TOK_FIRST_KEYWORD + JS_ATOM_break,
|
|
TOK_CONTINUE = TOK_FIRST_KEYWORD + JS_ATOM_continue,
|
|
TOK_SWITCH = TOK_FIRST_KEYWORD + JS_ATOM_switch,
|
|
TOK_CASE = TOK_FIRST_KEYWORD + JS_ATOM_case,
|
|
TOK_DEFAULT = TOK_FIRST_KEYWORD + JS_ATOM_default,
|
|
TOK_THROW = TOK_FIRST_KEYWORD + JS_ATOM_throw,
|
|
TOK_TRY = TOK_FIRST_KEYWORD + JS_ATOM_try,
|
|
TOK_CATCH = TOK_FIRST_KEYWORD + JS_ATOM_catch,
|
|
TOK_FINALLY = TOK_FIRST_KEYWORD + JS_ATOM_finally,
|
|
TOK_FUNCTION = TOK_FIRST_KEYWORD + JS_ATOM_function,
|
|
TOK_DEBUGGER = TOK_FIRST_KEYWORD + JS_ATOM_debugger,
|
|
TOK_WITH = TOK_FIRST_KEYWORD + JS_ATOM_with,
|
|
TOK_CLASS = TOK_FIRST_KEYWORD + JS_ATOM_class,
|
|
TOK_CONST = TOK_FIRST_KEYWORD + JS_ATOM_const,
|
|
TOK_ENUM = TOK_FIRST_KEYWORD + JS_ATOM_enum,
|
|
TOK_EXPORT = TOK_FIRST_KEYWORD + JS_ATOM_export,
|
|
TOK_EXTENDS = TOK_FIRST_KEYWORD + JS_ATOM_extends,
|
|
TOK_IMPORT = TOK_FIRST_KEYWORD + JS_ATOM_import,
|
|
TOK_SUPER = TOK_FIRST_KEYWORD + JS_ATOM_super,
|
|
TOK_IMPLEMENTS = TOK_FIRST_KEYWORD + JS_ATOM_implements,
|
|
TOK_INTERFACE = TOK_FIRST_KEYWORD + JS_ATOM_interface,
|
|
TOK_LET = TOK_FIRST_KEYWORD + JS_ATOM_let,
|
|
TOK_PACKAGE = TOK_FIRST_KEYWORD + JS_ATOM_package,
|
|
TOK_PRIVATE = TOK_FIRST_KEYWORD + JS_ATOM_private,
|
|
TOK_PROTECTED = TOK_FIRST_KEYWORD + JS_ATOM_protected,
|
|
TOK_PUBLIC = TOK_FIRST_KEYWORD + JS_ATOM_public,
|
|
TOK_STATIC = TOK_FIRST_KEYWORD + JS_ATOM_static,
|
|
TOK_YIELD = TOK_FIRST_KEYWORD + JS_ATOM_yield,
|
|
};
|
|
|
|
/* this structure is pushed on the JS stack, so all members must be JSValue */
|
|
typedef struct BlockEnv {
|
|
JSValue prev; /* JS_NULL or stack index */
|
|
JSValue label_name; /* JS_NULL if none */
|
|
JSValue label_break;
|
|
JSValue label_cont;
|
|
JSValue label_finally;
|
|
JSValue drop_count; /* (int) number of stack elements to drop */
|
|
} BlockEnv;
|
|
|
|
typedef uint32_t JSSourcePos;
|
|
|
|
typedef struct JSToken {
|
|
int val;
|
|
JSSourcePos source_pos; /* position in source */
|
|
union {
|
|
double d; /* TOK_NUMBER */
|
|
struct {
|
|
uint32_t re_flags; /* regular expression flags */
|
|
uint32_t re_end_pos; /* at the final '/' */
|
|
} regexp;
|
|
} u;
|
|
JSValue value; /* associated value: string for TOK_STRING, TOK_REGEXP;
|
|
identifier for TOK_IDENT or keyword */
|
|
} JSToken;
|
|
|
|
typedef struct JSParseState {
|
|
JSContext *ctx;
|
|
JSToken token;
|
|
|
|
BOOL got_lf : 8; /* true if got line feed before the current token */
|
|
/* global eval: variables are defined as global */
|
|
BOOL is_eval : 8;
|
|
/* if true, return the last value. */
|
|
BOOL has_retval : 8;
|
|
/* if true, implicitly define global variables in an
|
|
assignment. */
|
|
BOOL is_repl : 8;
|
|
BOOL has_column : 8; /* column debug info is present */
|
|
/* TRUE if the expression result has been dropped (see PF_DROP) */
|
|
BOOL dropped_result : 8;
|
|
JSValue source_str; /* source string or JS_NULL */
|
|
JSValue filename_str; /* 'filename' converted to string */
|
|
/* zero terminated source buffer. Automatically updated by the GC
|
|
if source_str is a string */
|
|
const uint8_t *source_buf;
|
|
uint32_t buf_pos;
|
|
uint32_t buf_len;
|
|
|
|
/* current function */
|
|
JSValue cur_func;
|
|
JSValue byte_code;
|
|
uint32_t byte_code_len;
|
|
int last_opcode_pos; /* -1 if no last opcode */
|
|
int last_pc2line_pos; /* pc2line pos for the last opcode */
|
|
JSSourcePos last_pc2line_source_pos;
|
|
|
|
uint32_t pc2line_bit_len;
|
|
JSSourcePos pc2line_source_pos; /* last generated source pos */
|
|
|
|
uint16_t cpool_len;
|
|
/* size of the byte code necessary to define the hoisted functions */
|
|
uint32_t hoisted_code_len;
|
|
|
|
/* argument + defined local variable count */
|
|
uint16_t local_vars_len;
|
|
|
|
int eval_ret_idx; /* variable index for the eval return value, -1
|
|
if no return value */
|
|
JSValue top_break; /* JS_NULL or SP_TO_VALUE(BlockEnv *) */
|
|
|
|
/* regexp parsing only */
|
|
uint8_t capture_count;
|
|
uint8_t re_in_js: 1;
|
|
uint8_t multi_line : 1;
|
|
uint8_t dotall : 1;
|
|
uint8_t ignore_case : 1;
|
|
uint8_t is_unicode : 1;
|
|
|
|
/* error handling */
|
|
jmp_buf jmp_env;
|
|
char error_msg[64];
|
|
} JSParseState;
|
|
|
|
static int js_parse_json_value(JSParseState *s, int state, int dummy_param);
|
|
static JSValue js_parse_regexp(JSParseState *s, int eval_flags);
|
|
static size_t js_parse_regexp_flags(int *pre_flags, const uint8_t *buf);
|
|
static int re_parse_alternative(JSParseState *s, int state, int dummy_param);
|
|
static int re_parse_disjunction(JSParseState *s, int state, int dummy_param);
|
|
|
|
#ifdef DUMP_BYTECODE
|
|
static __maybe_unused void dump_byte_code(JSContext *ctx, JSFunctionBytecode *b)
|
|
{
|
|
JSByteArray *arr, *pc2line;
|
|
JSValueArray *cpool, *vars, *ext_vars;
|
|
const JSOpCode *oi;
|
|
int pos, op, size, addr, idx, arg_count, len, i, line_num, col_num;
|
|
int line_num1, col_num1, hoisted_code_len;
|
|
uint8_t *tab;
|
|
uint32_t pc2line_pos;
|
|
|
|
arr = JS_VALUE_TO_PTR(b->byte_code);
|
|
if (b->cpool != JS_NULL)
|
|
cpool = JS_VALUE_TO_PTR(b->cpool);
|
|
else
|
|
cpool = NULL;
|
|
if (b->vars != JS_NULL)
|
|
vars = JS_VALUE_TO_PTR(b->vars);
|
|
else
|
|
vars = NULL;
|
|
if (b->ext_vars != JS_NULL)
|
|
ext_vars = JS_VALUE_TO_PTR(b->ext_vars);
|
|
else
|
|
ext_vars = NULL;
|
|
if (b->pc2line != JS_NULL)
|
|
pc2line = JS_VALUE_TO_PTR(b->pc2line);
|
|
else
|
|
pc2line = NULL;
|
|
|
|
arg_count = b->arg_count;
|
|
|
|
JS_PrintValueF(ctx, b->filename, JS_DUMP_NOQUOTE);
|
|
js_printf(ctx, ": function ");
|
|
JS_PrintValueF(ctx, b->func_name, JS_DUMP_NOQUOTE);
|
|
js_printf(ctx, ":\n");
|
|
|
|
if (b->arg_count && vars) {
|
|
js_printf(ctx, " args:");
|
|
for(i = 0; i < b->arg_count; i++) {
|
|
js_printf(ctx, " ");
|
|
JS_PrintValue(ctx, vars->arr[i]);
|
|
}
|
|
js_printf(ctx, "\n");
|
|
}
|
|
if (vars) {
|
|
js_printf(ctx, " locals:");
|
|
for(i = 0; i < vars->size - b->arg_count; i++) {
|
|
js_printf(ctx, " ");
|
|
JS_PrintValue(ctx, vars->arr[i + b->arg_count]);
|
|
}
|
|
js_printf(ctx, "\n");
|
|
}
|
|
if (ext_vars) {
|
|
js_printf(ctx, " refs:");
|
|
for(i = 0; i < b->ext_vars_len; i++) {
|
|
int var_kind, var_idx, decl;
|
|
static const char *var_kind_str[] = { "arg", "var", "ref", "global" };
|
|
js_printf(ctx, " ");
|
|
JS_PrintValue(ctx, ext_vars->arr[2 * i]);
|
|
decl = JS_VALUE_GET_INT(ext_vars->arr[2 * i + 1]);
|
|
var_kind = decl >> 16;
|
|
var_idx = decl & 0xffff;
|
|
js_printf(ctx, " (%s:%d)", var_kind_str[var_kind], var_idx);
|
|
}
|
|
js_printf(ctx, "\n");
|
|
}
|
|
|
|
js_printf(ctx, " cpool_size: %d\n", cpool ? (int)cpool->size : 0);
|
|
js_printf(ctx, " stack_size: %d\n", b->stack_size);
|
|
js_printf(ctx, " opcodes:\n");
|
|
tab = arr->buf;
|
|
len = arr->size;
|
|
pos = 0;
|
|
pc2line_pos = 0;
|
|
hoisted_code_len = 0;
|
|
if (pc2line)
|
|
hoisted_code_len = get_pc2line_hoisted_code_len(pc2line->buf, pc2line->size);
|
|
line_num = 1;
|
|
col_num = 1;
|
|
line_num1 = 0;
|
|
col_num1 = 0;
|
|
while (pos < len) {
|
|
/* extract the debug info */
|
|
if (pc2line && pos >= hoisted_code_len) {
|
|
get_pc2line(&line_num, &col_num, pc2line->buf, pc2line->size,
|
|
&pc2line_pos, b->has_column);
|
|
if (line_num != line_num1 || col_num != col_num1) {
|
|
js_printf(ctx, " # %d", line_num);
|
|
if (b->has_column)
|
|
js_printf(ctx, ", %d", col_num);
|
|
js_printf(ctx, "\n");
|
|
line_num1 = line_num;
|
|
col_num1 = col_num;
|
|
}
|
|
}
|
|
op = tab[pos];
|
|
js_printf(ctx, "%5d: ", pos);
|
|
if (op >= OP_COUNT) {
|
|
js_printf(ctx, "invalid opcode (0x%02x)\n", op);
|
|
pos++;
|
|
continue;
|
|
}
|
|
oi = &opcode_info[op];
|
|
size = oi->size;
|
|
if ((pos + size) > len) {
|
|
js_printf(ctx, "truncated opcode (0x%02x)\n", op);
|
|
break;
|
|
}
|
|
js_printf(ctx, "%s", oi->name);
|
|
pos++;
|
|
switch(oi->fmt) {
|
|
case OP_FMT_u8:
|
|
js_printf(ctx, " %u", (int)get_u8(tab + pos));
|
|
break;
|
|
case OP_FMT_i8:
|
|
js_printf(ctx, " %d", (int)get_i8(tab + pos));
|
|
break;
|
|
case OP_FMT_u16:
|
|
case OP_FMT_npop:
|
|
js_printf(ctx, " %u", (int)get_u16(tab + pos));
|
|
break;
|
|
case OP_FMT_i16:
|
|
js_printf(ctx, " %d", (int)get_i16(tab + pos));
|
|
break;
|
|
case OP_FMT_i32:
|
|
js_printf(ctx, " %d", (int)get_i32(tab + pos));
|
|
break;
|
|
case OP_FMT_u32:
|
|
js_printf(ctx, " %u", (int)get_u32(tab + pos));
|
|
break;
|
|
case OP_FMT_none_int:
|
|
js_printf(ctx, " %d", op - OP_push_0);
|
|
break;
|
|
#if 0
|
|
case OP_FMT_npopx:
|
|
js_printf(ctx, " %d", op - OP_call0);
|
|
break;
|
|
#endif
|
|
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:
|
|
addr = get_u32(tab + pos);
|
|
goto has_addr1;
|
|
has_addr1:
|
|
js_printf(ctx, " %u", addr + pos);
|
|
break;
|
|
case OP_FMT_const8:
|
|
idx = get_u8(tab + pos);
|
|
goto has_pool_idx;
|
|
case OP_FMT_const16:
|
|
idx = get_u16(tab + pos);
|
|
goto has_pool_idx;
|
|
has_pool_idx:
|
|
js_printf(ctx, " %u: ", idx);
|
|
if (idx < cpool->size) {
|
|
JS_PrintValue(ctx, cpool->arr[idx]);
|
|
}
|
|
break;
|
|
case OP_FMT_none_loc:
|
|
idx = (op - OP_get_loc0) % 4;
|
|
goto has_loc;
|
|
case OP_FMT_loc8:
|
|
idx = get_u8(tab + pos);
|
|
goto has_loc;
|
|
case OP_FMT_loc:
|
|
idx = get_u16(tab + pos);
|
|
has_loc:
|
|
js_printf(ctx, " %d: ", idx);
|
|
idx += arg_count;
|
|
if (idx < vars->size) {
|
|
JS_PrintValue(ctx, vars->arr[idx]);
|
|
}
|
|
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:
|
|
js_printf(ctx, " %d: ", idx);
|
|
if (idx < vars->size) {
|
|
JS_PrintValue(ctx, vars->arr[idx]);
|
|
}
|
|
break;
|
|
#if 0
|
|
case OP_FMT_none_var_ref:
|
|
idx = (op - OP_get_var_ref0) % 4;
|
|
goto has_var_ref;
|
|
#endif
|
|
case OP_FMT_var_ref:
|
|
idx = get_u16(tab + pos);
|
|
// has_var_ref:
|
|
js_printf(ctx, " %d: ", idx);
|
|
if (2 * idx < ext_vars->size) {
|
|
JS_PrintValue(ctx, ext_vars->arr[2 * idx]);
|
|
}
|
|
break;
|
|
case OP_FMT_value:
|
|
js_printf(ctx, " ");
|
|
idx = get_u32(tab + pos);
|
|
JS_PrintValue(ctx, idx);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
js_printf(ctx, "\n");
|
|
pos += oi->size - 1;
|
|
}
|
|
}
|
|
#endif /* DUMP_BYTECODE */
|
|
|
|
static void next_token(JSParseState *s);
|
|
|
|
static void __attribute((unused)) dump_token(JSParseState *s,
|
|
const JSToken *token)
|
|
{
|
|
JSContext *ctx = s->ctx;
|
|
switch(token->val) {
|
|
case TOK_NUMBER:
|
|
/* XXX: TODO */
|
|
js_printf(ctx, "number: %d\n", (int)token->u.d);
|
|
break;
|
|
case TOK_IDENT:
|
|
{
|
|
js_printf(ctx, "ident: ");
|
|
JS_PrintValue(s->ctx, token->value);
|
|
js_printf(ctx, "\n");
|
|
}
|
|
break;
|
|
case TOK_STRING:
|
|
{
|
|
js_printf(ctx, "string: ");
|
|
JS_PrintValue(s->ctx, token->value);
|
|
js_printf(ctx, "\n");
|
|
}
|
|
break;
|
|
case TOK_REGEXP:
|
|
{
|
|
js_printf(ctx, "regexp: ");
|
|
JS_PrintValue(s->ctx, token->value);
|
|
js_printf(ctx, "\n");
|
|
}
|
|
break;
|
|
case TOK_EOF:
|
|
js_printf(ctx, "eof\n");
|
|
break;
|
|
default:
|
|
if (s->token.val >= TOK_FIRST_KEYWORD) {
|
|
js_printf(ctx, "token: ");
|
|
JS_PrintValue(s->ctx, token->value);
|
|
js_printf(ctx, "\n");
|
|
} else if (s->token.val >= 128) {
|
|
js_printf(ctx, "token: %d\n", token->val);
|
|
} else {
|
|
js_printf(ctx, "token: '%c'\n", token->val);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* return the zero based line and column number in the source. */
|
|
static int get_line_col(int *pcol_num, const uint8_t *buf, size_t len)
|
|
{
|
|
int line_num, col_num, c;
|
|
size_t i;
|
|
|
|
line_num = 0;
|
|
col_num = 0;
|
|
for(i = 0; i < len; i++) {
|
|
c = buf[i];
|
|
if (c == '\n') {
|
|
line_num++;
|
|
col_num = 0;
|
|
} else if (c < 0x80 || c >= 0xc0) {
|
|
col_num++;
|
|
}
|
|
}
|
|
*pcol_num = col_num;
|
|
return line_num;
|
|
}
|
|
|
|
static void __attribute__((format(printf, 2, 3), noreturn)) js_parse_error(JSParseState *s, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
js_vsnprintf(s->error_msg, sizeof(s->error_msg), fmt, ap);
|
|
va_end(ap);
|
|
longjmp(s->jmp_env, 1);
|
|
}
|
|
|
|
static void js_parse_error_mem(JSParseState *s)
|
|
{
|
|
return js_parse_error(s, "not enough memory");
|
|
}
|
|
|
|
static void js_parse_error_stack_overflow(JSParseState *s)
|
|
{
|
|
return js_parse_error(s, "stack overflow");
|
|
}
|
|
|
|
static void js_parse_expect1(JSParseState *s, int ch)
|
|
{
|
|
if (s->token.val != ch)
|
|
js_parse_error(s, "expecting '%c'", ch);
|
|
}
|
|
|
|
static void js_parse_expect(JSParseState *s, int ch)
|
|
{
|
|
js_parse_expect1(s, ch);
|
|
next_token(s);
|
|
}
|
|
|
|
static void 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;
|
|
}
|
|
js_parse_error(s, "expecting '%c'", ';');
|
|
}
|
|
next_token(s);
|
|
}
|
|
|
|
#define SKIP_HAS_ARGUMENTS (1 << 0)
|
|
#define SKIP_HAS_FUNC_NAME (1 << 1)
|
|
#define SKIP_HAS_SEMI (1 << 2) /* semicolon found inside the first level */
|
|
|
|
/* Skip parenthesis or blocks. The current token should be '(', '[' or
|
|
'{'. 'func_name' can be JS_NULL. */
|
|
static int js_skip_parens(JSParseState *s, JSValue *pfunc_name)
|
|
{
|
|
uint8_t state[128];
|
|
int level, c, bits = 0;
|
|
|
|
/* protect from underflow */
|
|
level = 0;
|
|
state[level++] = 0;
|
|
for (;;) {
|
|
switch(s->token.val) {
|
|
case '(':
|
|
c = ')';
|
|
goto add_level;
|
|
case '[':
|
|
c = ']';
|
|
goto add_level;
|
|
case '{':
|
|
c = '}';
|
|
add_level:
|
|
if (level >= sizeof(state)) {
|
|
js_parse_error(s, "too many nested blocks");
|
|
}
|
|
state[level++] = c;
|
|
break;
|
|
case ')':
|
|
case ']':
|
|
case '}':
|
|
c = state[--level];
|
|
if (s->token.val != c)
|
|
js_parse_error(s, "expecting '%c'", c);
|
|
break;
|
|
case TOK_EOF:
|
|
js_parse_error(s, "expecting '%c'", state[level - 1]);
|
|
case TOK_IDENT:
|
|
if (s->token.value == js_get_atom(s->ctx, JS_ATOM_arguments))
|
|
bits |= SKIP_HAS_ARGUMENTS;
|
|
if (pfunc_name && s->token.value == *pfunc_name)
|
|
bits |= SKIP_HAS_FUNC_NAME;
|
|
break;
|
|
case ';':
|
|
if (level == 2)
|
|
bits |= SKIP_HAS_SEMI;
|
|
break;
|
|
}
|
|
next_token(s);
|
|
if (level <= 1)
|
|
break;
|
|
}
|
|
return bits;
|
|
}
|
|
|
|
/* skip an expression until ')' */
|
|
static void js_skip_expr(JSParseState *s)
|
|
{
|
|
for(;;) {
|
|
switch(s->token.val) {
|
|
case ')':
|
|
return;
|
|
case ';':
|
|
case TOK_EOF:
|
|
js_parse_error(s, "expecting '%c'", ')');
|
|
case '(':
|
|
case '[':
|
|
case '{':
|
|
js_skip_parens(s, NULL);
|
|
break;
|
|
default:
|
|
next_token(s);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
typedef struct JSParsePos {
|
|
BOOL got_lf : 8;
|
|
BOOL regexp_allowed : 8;
|
|
uint32_t source_pos;
|
|
} JSParsePos;
|
|
|
|
/* 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 TOK_IF:
|
|
case TOK_WHILE:
|
|
case TOK_FOR:
|
|
case TOK_DO:
|
|
case TOK_CASE:
|
|
case TOK_CATCH:
|
|
case ')':
|
|
case ']':
|
|
case TOK_IDENT:
|
|
return FALSE;
|
|
default:
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
static void js_parse_get_pos(JSParseState *s, JSParsePos *sp)
|
|
{
|
|
sp->source_pos = s->token.source_pos;
|
|
sp->got_lf = s->got_lf;
|
|
sp->regexp_allowed = is_regexp_allowed(s->token.val);
|
|
}
|
|
|
|
static void js_parse_seek_token(JSParseState *s, const JSParsePos *sp)
|
|
{
|
|
s->buf_pos = sp->source_pos;
|
|
s->got_lf = sp->got_lf;
|
|
/* the previous token value is only needed so that
|
|
is_regexp_allowed() returns the correct value */
|
|
s->token.val = sp->regexp_allowed ? ' ' : ')';
|
|
next_token(s);
|
|
}
|
|
|
|
/* same as js_skip_parens but go back to the current token */
|
|
static int js_parse_skip_parens_token(JSParseState *s)
|
|
{
|
|
JSParsePos pos;
|
|
int bits;
|
|
|
|
js_parse_get_pos(s, &pos);
|
|
bits = js_skip_parens(s, NULL);
|
|
js_parse_seek_token(s, &pos);
|
|
return bits;
|
|
}
|
|
|
|
/* return the escape value or -1 */
|
|
static int js_parse_escape(const uint8_t *buf, size_t *plen)
|
|
{
|
|
int c;
|
|
const uint8_t *p = buf;
|
|
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 'v':
|
|
c = '\v';
|
|
break;
|
|
case '\'':
|
|
case '\"':
|
|
case '\\':
|
|
break;
|
|
case 'x':
|
|
{
|
|
int h0, h1;
|
|
|
|
h0 = from_hex(*p++);
|
|
if (h0 < 0)
|
|
return -1;
|
|
h1 = from_hex(*p++);
|
|
if (h1 < 0)
|
|
return -1;
|
|
c = (h0 << 4) | h1;
|
|
}
|
|
break;
|
|
case 'u':
|
|
{
|
|
int h, i;
|
|
|
|
if (*p == '{') {
|
|
p++;
|
|
c = 0;
|
|
for(;;) {
|
|
h = from_hex(*p++);
|
|
if (h < 0)
|
|
return -1;
|
|
c = (c << 4) | h;
|
|
if (c > 0x10FFFF)
|
|
return -1;
|
|
if (*p == '}')
|
|
break;
|
|
}
|
|
p++;
|
|
} else {
|
|
c = 0;
|
|
for(i = 0; i < 4; i++) {
|
|
h = from_hex(*p++);
|
|
if (h < 0) {
|
|
return -1;
|
|
}
|
|
c = (c << 4) | h;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case '0':
|
|
c -= '0';
|
|
if (c != 0 || is_num(*p))
|
|
return -1;
|
|
break;
|
|
default:
|
|
return -2;
|
|
}
|
|
*plen = p - buf;
|
|
return c;
|
|
}
|
|
|
|
static JSValue js_parse_string(JSParseState *s, uint32_t *ppos, int sep)
|
|
{
|
|
JSContext *ctx = s->ctx;
|
|
JSValue res;
|
|
const uint8_t *buf;
|
|
uint32_t pos;
|
|
uint32_t c;
|
|
size_t escape_len = 0; /* avoid warning */
|
|
StringBuffer b_s, *b = &b_s;
|
|
|
|
if (string_buffer_push(ctx, b, 16))
|
|
js_parse_error_mem(s);
|
|
buf = s->source_buf;
|
|
/* string */
|
|
pos = *ppos;
|
|
for(;;) {
|
|
c = buf[pos];
|
|
if (c == '\0' || c == '\n' || c == '\r') {
|
|
js_parse_error(s, "unexpected end of string");
|
|
}
|
|
pos++;
|
|
if (c == sep)
|
|
break;
|
|
if (c == '\\') {
|
|
if (buf[pos] == '\n') {
|
|
/* ignore escaped newline sequence */
|
|
pos++;
|
|
continue;
|
|
}
|
|
c = js_parse_escape(buf + pos, &escape_len);
|
|
if (c == -1) {
|
|
js_parse_error(s, "invalid escape sequence");
|
|
} else if (c == -2) {
|
|
/* ignore invalid escapes */
|
|
continue;
|
|
}
|
|
pos += escape_len;
|
|
} else if (c >= 0x80) {
|
|
size_t clen;
|
|
pos--;
|
|
c = unicode_from_utf8(buf + pos, UTF8_CHAR_LEN_MAX, &clen);
|
|
pos += clen;
|
|
if (c == -1) {
|
|
js_parse_error(s, "invalid UTF-8 sequence");
|
|
}
|
|
}
|
|
if (string_buffer_putc(ctx, b, c))
|
|
break;
|
|
buf = s->source_buf; /* may be reallocated */
|
|
}
|
|
*ppos = pos;
|
|
res = string_buffer_pop(ctx, b);
|
|
if (JS_IsException(res))
|
|
js_parse_error_mem(s);
|
|
return res;
|
|
}
|
|
|
|
static void js_parse_ident(JSParseState *s, JSToken *token,
|
|
uint32_t *ppos, int c)
|
|
{
|
|
JSContext *ctx = s->ctx;
|
|
uint32_t pos;
|
|
JSValue val, val2;
|
|
JSGCRef val2_ref;
|
|
const uint8_t *buf;
|
|
StringBuffer b_s, *b = &b_s;
|
|
|
|
if (string_buffer_push(ctx, b, 16))
|
|
js_parse_error_mem(s);
|
|
string_buffer_putc(ctx, b, c); /* no allocation */
|
|
buf = s->source_buf;
|
|
pos = *ppos;
|
|
while (pos < s->buf_len) {
|
|
c = buf[pos];
|
|
if (!is_ident_next(c))
|
|
break;
|
|
pos++;
|
|
if (string_buffer_putc(ctx, b, c))
|
|
break;
|
|
buf = s->source_buf; /* may be reallocated */
|
|
}
|
|
/* convert to token if necessary */
|
|
token->val = TOK_IDENT;
|
|
val2 = string_buffer_pop(ctx, b);
|
|
JS_PUSH_VALUE(ctx, val2);
|
|
val = JS_MakeUniqueString(ctx, val2);
|
|
JS_POP_VALUE(ctx, val2);
|
|
if (JS_IsException(val))
|
|
js_parse_error_mem(s);
|
|
if (val != val2)
|
|
js_free(ctx, JS_VALUE_TO_PTR(val2));
|
|
token->value = val;
|
|
if (JS_IsPtr(val)) {
|
|
const JSWord *atom_start, *atom_last, *ptr;
|
|
atom_start = ctx->atom_table;
|
|
atom_last = atom_start + JS_ATOM_yield;
|
|
ptr = JS_VALUE_TO_PTR(val);
|
|
if (ptr >= atom_start && ptr <= atom_last) {
|
|
token->val = TOK_NULL + (ptr - atom_start);
|
|
}
|
|
}
|
|
*ppos = pos;
|
|
}
|
|
|
|
static void js_parse_regexp_token(JSParseState *s, uint32_t *ppos)
|
|
{
|
|
JSContext *ctx = s->ctx;
|
|
uint32_t pos;
|
|
uint32_t c;
|
|
BOOL in_class;
|
|
size_t clen;
|
|
int re_flags, end_pos, start_pos;
|
|
JSString *p;
|
|
|
|
in_class = FALSE;
|
|
pos = *ppos;
|
|
start_pos = pos;
|
|
for(;;) {
|
|
c = unicode_from_utf8(s->source_buf + pos, UTF8_CHAR_LEN_MAX, &clen);
|
|
if (c == -1)
|
|
js_parse_error(s, "invalid UTF-8 sequence");
|
|
pos += clen;
|
|
if (c == '\0' || c == '\n' || c == '\r') {
|
|
goto invalid_char;
|
|
} else if (c == '/') {
|
|
if (!in_class)
|
|
break;
|
|
} else if (c == '[') {
|
|
in_class = TRUE;
|
|
} else if (c == ']') {
|
|
in_class = FALSE;
|
|
} else if (c == '\\') {
|
|
c = unicode_from_utf8(s->source_buf + pos, UTF8_CHAR_LEN_MAX, &clen);
|
|
if (c == -1)
|
|
js_parse_error(s, "invalid UTF-8 sequence");
|
|
if (c == '\0' || c == '\n' || c == '\r') {
|
|
invalid_char:
|
|
js_parse_error(s, "unexpected line terminator in regexp");
|
|
}
|
|
pos += clen;
|
|
}
|
|
}
|
|
end_pos = pos - 1;
|
|
|
|
clen = js_parse_regexp_flags(&re_flags, s->source_buf + pos);
|
|
pos += clen;
|
|
if (is_ident_next(s->source_buf[pos]))
|
|
js_parse_error(s, "invalid regular expression flags");
|
|
|
|
/* XXX: single char string is not optimized */
|
|
p = js_alloc_string(ctx, end_pos - start_pos);
|
|
if (!p)
|
|
js_parse_error_mem(s);
|
|
p->is_ascii = is_ascii_string((char *)(s->source_buf + start_pos), end_pos - start_pos);
|
|
memcpy(p->buf, s->source_buf + start_pos, end_pos - start_pos);
|
|
|
|
*ppos = pos;
|
|
s->token.val = TOK_REGEXP;
|
|
s->token.value = JS_VALUE_FROM_PTR(p);
|
|
s->token.u.regexp.re_flags = re_flags;
|
|
s->token.u.regexp.re_end_pos = end_pos;
|
|
}
|
|
|
|
static void next_token(JSParseState *s)
|
|
{
|
|
uint32_t pos;
|
|
const uint8_t *p;
|
|
int c;
|
|
|
|
pos = s->buf_pos;
|
|
s->got_lf = FALSE;
|
|
s->token.value = JS_NULL;
|
|
p = s->source_buf + s->buf_pos;
|
|
redo:
|
|
s->token.source_pos = p - s->source_buf;
|
|
c = *p;
|
|
switch(c) {
|
|
case 0:
|
|
s->token.val = TOK_EOF;
|
|
break;
|
|
case '\"':
|
|
case '\'':
|
|
p++;
|
|
pos = p - s->source_buf;
|
|
s->token.value = js_parse_string(s, &pos, c);
|
|
s->token.val = TOK_STRING;
|
|
p = s->source_buf + pos;
|
|
break;
|
|
case '\n':
|
|
s->got_lf = TRUE;
|
|
p++;
|
|
goto redo;
|
|
case ' ':
|
|
case '\t':
|
|
case '\f':
|
|
case '\v':
|
|
case '\r':
|
|
p++;
|
|
goto redo;
|
|
case '/':
|
|
if (p[1] == '*') {
|
|
/* comment */
|
|
p += 2;
|
|
for(;;) {
|
|
if (*p == '\0')
|
|
js_parse_error(s, "unexpected end of comment");
|
|
if (p[0] == '*' && p[1] == '/') {
|
|
p += 2;
|
|
break;
|
|
}
|
|
p++;
|
|
}
|
|
goto redo;
|
|
} else if (p[1] == '/') {
|
|
/* line comment */
|
|
p += 2;
|
|
for(;;) {
|
|
if (*p == '\0' || *p == '\n')
|
|
break;
|
|
p++;
|
|
}
|
|
goto redo;
|
|
} else if (is_regexp_allowed(s->token.val)) {
|
|
/* Note: we recognize regexps in the lexer. It does not
|
|
handle all the cases e.g. "({x:1} / 2)" or "a.void / 2" but
|
|
is consistent when we tokenize the input without
|
|
parsing it. */
|
|
p++;
|
|
pos = p - s->source_buf;
|
|
js_parse_regexp_token(s, &pos);
|
|
p = s->source_buf + pos;
|
|
} else if (p[1] == '=') {
|
|
p += 2;
|
|
s->token.val = TOK_DIV_ASSIGN;
|
|
} else {
|
|
p++;
|
|
s->token.val = c;
|
|
}
|
|
break;
|
|
case 'a' ... 'z':
|
|
case 'A' ... 'Z':
|
|
case '_':
|
|
case '$':
|
|
p++;
|
|
pos = p - s->source_buf;
|
|
js_parse_ident(s, &s->token, &pos, c);
|
|
p = s->source_buf + pos;
|
|
break;
|
|
case '.':
|
|
if (is_digit(p[1]))
|
|
goto parse_number;
|
|
else
|
|
goto def_token;
|
|
case '0':
|
|
/* in strict mode, octal literals are not accepted */
|
|
if (is_digit(p[1]))
|
|
goto invalid_number;
|
|
goto parse_number;
|
|
case '1': case '2': case '3': case '4':
|
|
case '5': case '6': case '7': case '8':
|
|
case '9':
|
|
/* number */
|
|
parse_number:
|
|
{
|
|
double d;
|
|
JSByteArray *tmp_arr;
|
|
pos = p - s->source_buf;
|
|
tmp_arr = js_alloc_byte_array(s->ctx, sizeof(JSATODTempMem));
|
|
if (!tmp_arr)
|
|
js_parse_error_mem(s);
|
|
p = s->source_buf + pos;
|
|
d = js_atod((const char *)p, (const char **)&p, 0,
|
|
JS_ATOD_ACCEPT_BIN_OCT | JS_ATOD_ACCEPT_UNDERSCORES,
|
|
(JSATODTempMem *)tmp_arr->buf);
|
|
js_free(s->ctx, tmp_arr);
|
|
if (isnan(d)) {
|
|
invalid_number:
|
|
js_parse_error(s, "invalid number literal");
|
|
}
|
|
s->token.val = TOK_NUMBER;
|
|
s->token.u.d = d;
|
|
}
|
|
break;
|
|
case '*':
|
|
if (p[1] == '=') {
|
|
p += 2;
|
|
s->token.val = TOK_MUL_ASSIGN;
|
|
} else if (p[1] == '*') {
|
|
if (p[2] == '=') {
|
|
p += 3;
|
|
s->token.val = TOK_POW_ASSIGN;
|
|
} else {
|
|
p += 2;
|
|
s->token.val = TOK_POW;
|
|
}
|
|
} else {
|
|
goto def_token;
|
|
}
|
|
break;
|
|
case '%':
|
|
if (p[1] == '=') {
|
|
p += 2;
|
|
s->token.val = TOK_MOD_ASSIGN;
|
|
} else {
|
|
goto def_token;
|
|
}
|
|
break;
|
|
case '+':
|
|
if (p[1] == '=') {
|
|
p += 2;
|
|
s->token.val = TOK_PLUS_ASSIGN;
|
|
} else if (p[1] == '+') {
|
|
p += 2;
|
|
s->token.val = TOK_INC;
|
|
} else {
|
|
goto def_token;
|
|
}
|
|
break;
|
|
case '-':
|
|
if (p[1] == '=') {
|
|
p += 2;
|
|
s->token.val = TOK_MINUS_ASSIGN;
|
|
} else if (p[1] == '-') {
|
|
p += 2;
|
|
s->token.val = TOK_DEC;
|
|
} else {
|
|
goto def_token;
|
|
}
|
|
break;
|
|
case '<':
|
|
if (p[1] == '=') {
|
|
p += 2;
|
|
s->token.val = TOK_LTE;
|
|
} else if (p[1] == '<') {
|
|
if (p[2] == '=') {
|
|
p += 3;
|
|
s->token.val = TOK_SHL_ASSIGN;
|
|
} else {
|
|
p += 2;
|
|
s->token.val = TOK_SHL;
|
|
}
|
|
} else {
|
|
goto def_token;
|
|
}
|
|
break;
|
|
case '>':
|
|
if (p[1] == '=') {
|
|
p += 2;
|
|
s->token.val = TOK_GTE;
|
|
} else if (p[1] == '>') {
|
|
if (p[2] == '>') {
|
|
if (p[3] == '=') {
|
|
p += 4;
|
|
s->token.val = TOK_SHR_ASSIGN;
|
|
} else {
|
|
p += 3;
|
|
s->token.val = TOK_SHR;
|
|
}
|
|
} else if (p[2] == '=') {
|
|
p += 3;
|
|
s->token.val = TOK_SAR_ASSIGN;
|
|
} else {
|
|
p += 2;
|
|
s->token.val = TOK_SAR;
|
|
}
|
|
} else {
|
|
goto def_token;
|
|
}
|
|
break;
|
|
case '=':
|
|
if (p[1] == '=') {
|
|
if (p[2] == '=') {
|
|
p += 3;
|
|
s->token.val = TOK_STRICT_EQ;
|
|
} else {
|
|
p += 2;
|
|
s->token.val = TOK_EQ;
|
|
}
|
|
} 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] == '&') {
|
|
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] == '|') {
|
|
p += 2;
|
|
s->token.val = TOK_LOR;
|
|
} else {
|
|
goto def_token;
|
|
}
|
|
break;
|
|
default:
|
|
if (c >= 128) {
|
|
js_parse_error(s, "unexpected character");
|
|
}
|
|
def_token:
|
|
s->token.val = c;
|
|
p++;
|
|
break;
|
|
}
|
|
s->buf_pos = p - s->source_buf;
|
|
#if defined(DUMP_TOKEN)
|
|
dump_token(s, &s->token);
|
|
#endif
|
|
}
|
|
|
|
/* test if the current token is a label. XXX: we assume there is no
|
|
space between the identifier and the ':' to avoid having to push
|
|
back a token */
|
|
static BOOL is_label(JSParseState *s)
|
|
{
|
|
return (s->token.val == TOK_IDENT && s->source_buf[s->buf_pos] == ':');
|
|
}
|
|
|
|
static inline uint8_t *get_byte_code(JSParseState *s)
|
|
{
|
|
JSByteArray *arr;
|
|
arr = JS_VALUE_TO_PTR(s->byte_code);
|
|
return arr->buf;
|
|
}
|
|
|
|
static void emit_claim_size(JSParseState *s, int n)
|
|
{
|
|
JSValue val;
|
|
val = js_resize_byte_array(s->ctx, s->byte_code, s->byte_code_len + n);
|
|
if (JS_IsException(val))
|
|
js_parse_error_mem(s);
|
|
s->byte_code = val;
|
|
}
|
|
|
|
static void emit_u8(JSParseState *s, uint8_t val)
|
|
{
|
|
JSByteArray *arr;
|
|
emit_claim_size(s, 1);
|
|
arr = JS_VALUE_TO_PTR(s->byte_code);
|
|
arr->buf[s->byte_code_len++] = val;
|
|
}
|
|
|
|
static void emit_u16(JSParseState *s, uint16_t val)
|
|
{
|
|
JSByteArray *arr;
|
|
emit_claim_size(s, 2);
|
|
arr = JS_VALUE_TO_PTR(s->byte_code);
|
|
put_u16(arr->buf + s->byte_code_len, val);
|
|
s->byte_code_len += 2;
|
|
}
|
|
|
|
static void emit_u32(JSParseState *s, uint32_t val)
|
|
{
|
|
JSByteArray *arr;
|
|
emit_claim_size(s, 4);
|
|
arr = JS_VALUE_TO_PTR(s->byte_code);
|
|
put_u32(arr->buf + s->byte_code_len, val);
|
|
s->byte_code_len += 4;
|
|
}
|
|
|
|
/* precondition: 1 <= n <= 25. */
|
|
static void pc2line_put_bits_short(JSParseState *s, int n, uint32_t bits)
|
|
{
|
|
JSFunctionBytecode *b;
|
|
JSValue val1;
|
|
JSByteArray *arr;
|
|
uint32_t index, pos;
|
|
unsigned int val;
|
|
int shift;
|
|
uint8_t *p;
|
|
|
|
index = s->pc2line_bit_len;
|
|
pos = index >> 3;
|
|
|
|
/* resize the array if needed */
|
|
b = JS_VALUE_TO_PTR(s->cur_func);
|
|
val1 = js_resize_byte_array(s->ctx, b->pc2line, pos + 4);
|
|
if (JS_IsException(val1))
|
|
js_parse_error_mem(s);
|
|
b = JS_VALUE_TO_PTR(s->cur_func);
|
|
b->pc2line = val1;
|
|
|
|
arr = JS_VALUE_TO_PTR(val1);
|
|
p = arr->buf + pos;
|
|
val = get_be32(p);
|
|
shift = (32 - (index & 7) - n);
|
|
val &= ~(((1U << n) - 1) << shift); /* reset the bits */
|
|
val |= bits << shift;
|
|
put_be32(p, val);
|
|
s->pc2line_bit_len = index + n;
|
|
}
|
|
|
|
/* precondition: 1 <= n <= 32 */
|
|
static void pc2line_put_bits(JSParseState *s, int n, uint32_t bits)
|
|
{
|
|
int n_max = 25;
|
|
if (unlikely(n > n_max)) {
|
|
pc2line_put_bits_short(s, n - n_max, bits >> n_max);
|
|
bits &= (1 << n_max) - 1;
|
|
n = n_max;
|
|
}
|
|
pc2line_put_bits_short(s, n, bits);
|
|
}
|
|
|
|
/* 0 <= v < 2^32-1 */
|
|
static void put_ugolomb(JSParseState *s, uint32_t v)
|
|
{
|
|
int n;
|
|
// printf("put_ugolomb: %u\n", v);
|
|
v++;
|
|
n = 32 - clz32(v);
|
|
if (n > 1)
|
|
pc2line_put_bits(s, n - 1, 0);
|
|
pc2line_put_bits(s, n, v);
|
|
}
|
|
|
|
/* v != -2^31 */
|
|
static void put_sgolomb(JSParseState *s, int32_t v1)
|
|
{
|
|
uint32_t v = v1;
|
|
put_ugolomb(s, (2 * v) ^ -(v >> 31));
|
|
}
|
|
|
|
//#define DUMP_PC2LINE_STATS
|
|
|
|
#ifdef DUMP_PC2LINE_STATS
|
|
static int pc2line_freq[256];
|
|
static int pc2line_freq_tot;
|
|
#endif
|
|
|
|
/* return the difference between the line numbers from 'pos1' to
|
|
'pos2'. If the difference is zero, '*pcol_num' contains the
|
|
difference between the column numbers. Otherwise it contains the
|
|
zero based absolute column number.
|
|
*/
|
|
static int get_line_col_delta(int *pcol_num, const uint8_t *buf,
|
|
int pos1, int pos2)
|
|
{
|
|
int line_num, col_num, c, i;
|
|
line_num = 0;
|
|
col_num = 0;
|
|
if (pos2 >= pos1) {
|
|
line_num = get_line_col(&col_num, buf + pos1, pos2 - pos1);
|
|
} else {
|
|
line_num = get_line_col(&col_num, buf + pos2, pos1 - pos2);
|
|
line_num = -line_num;
|
|
col_num = -col_num;
|
|
if (line_num != 0) {
|
|
/* find the absolute column position */
|
|
col_num = 0;
|
|
for(i = pos2 - 1; i >= 0; i--) {
|
|
c = buf[i];
|
|
if (c == '\n') {
|
|
break;
|
|
} else if (c < 0x80 || c >= 0xc0) {
|
|
col_num++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
*pcol_num = col_num;
|
|
return line_num;
|
|
}
|
|
|
|
static void emit_pc2line(JSParseState *s, JSSourcePos pos)
|
|
{
|
|
int line_delta, col_delta;
|
|
|
|
line_delta = get_line_col_delta(&col_delta, s->source_buf,
|
|
s->pc2line_source_pos, pos);
|
|
put_sgolomb(s, line_delta);
|
|
if (s->has_column) {
|
|
if (line_delta == 0) {
|
|
#ifdef DUMP_PC2LINE_STATS
|
|
pc2line_freq[min_int(max_int(col_delta + 128, 0), 255)]++;
|
|
pc2line_freq_tot++;
|
|
#endif
|
|
put_sgolomb(s, col_delta);
|
|
} else {
|
|
put_ugolomb(s, col_delta);
|
|
}
|
|
}
|
|
s->pc2line_source_pos = pos;
|
|
}
|
|
|
|
#ifdef DUMP_PC2LINE_STATS
|
|
void dump_pc2line(void)
|
|
{
|
|
int i;
|
|
for(i = 0; i < 256; i++) {
|
|
if (pc2line_freq[i] != 0) {
|
|
printf("%d: %d %0.2f\n",
|
|
i - 128, pc2line_freq[i],
|
|
-log2((double)pc2line_freq[i] / pc2line_freq_tot));
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* warning: pc2line info must be associated to each generated opcode */
|
|
static void emit_op_pos(JSParseState *s, uint8_t op, JSSourcePos source_pos)
|
|
{
|
|
s->last_opcode_pos = s->byte_code_len;
|
|
s->last_pc2line_pos = s->pc2line_bit_len;
|
|
s->last_pc2line_source_pos = s->pc2line_source_pos;
|
|
|
|
emit_pc2line(s, source_pos);
|
|
emit_u8(s, op);
|
|
}
|
|
|
|
static void emit_op(JSParseState *s, uint8_t op)
|
|
{
|
|
emit_op_pos(s, op, s->pc2line_source_pos);
|
|
}
|
|
|
|
static void emit_op_param(JSParseState *s, uint8_t op, uint32_t param,
|
|
JSSourcePos source_pos)
|
|
{
|
|
const JSOpCode *oi;
|
|
|
|
emit_op_pos(s, op, source_pos);
|
|
oi = &opcode_info[op];
|
|
switch(oi->fmt) {
|
|
case OP_FMT_none:
|
|
break;
|
|
case OP_FMT_npop:
|
|
emit_u16(s, param);
|
|
break;
|
|
default:
|
|
assert(0);
|
|
}
|
|
}
|
|
|
|
/* insert 'n' bytes at position pos */
|
|
static void emit_insert(JSParseState *s, int pos, int n)
|
|
{
|
|
JSByteArray *arr;
|
|
emit_claim_size(s, n);
|
|
arr = JS_VALUE_TO_PTR(s->byte_code);
|
|
memmove(arr->buf + pos + n, arr->buf + pos, s->byte_code_len - pos);
|
|
s->byte_code_len += n;
|
|
}
|
|
|
|
static inline int get_prev_opcode(JSParseState *s)
|
|
{
|
|
if (s->last_opcode_pos < 0) {
|
|
return OP_invalid;
|
|
} else {
|
|
uint8_t *byte_code = get_byte_code(s);
|
|
return byte_code[s->last_opcode_pos];
|
|
}
|
|
}
|
|
|
|
static BOOL js_is_live_code(JSParseState *s) {
|
|
switch (get_prev_opcode(s)) {
|
|
case OP_return:
|
|
case OP_return_undef:
|
|
case OP_throw:
|
|
case OP_goto:
|
|
case OP_ret:
|
|
return FALSE;
|
|
default:
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
static void remove_last_op(JSParseState *s)
|
|
{
|
|
s->byte_code_len = s->last_opcode_pos;
|
|
s->pc2line_bit_len = s->last_pc2line_pos;
|
|
s->pc2line_source_pos = s->last_pc2line_source_pos;
|
|
s->last_opcode_pos = -1;
|
|
}
|
|
|
|
static void emit_push_short_int(JSParseState *s, int val)
|
|
{
|
|
if (val >= -1 && val <= 7) {
|
|
emit_op(s, OP_push_0 + val);
|
|
} else if (val == (int8_t)val) {
|
|
emit_op(s, OP_push_i8);
|
|
emit_u8(s, val);
|
|
} else if (val == (int16_t)val) {
|
|
emit_op(s, OP_push_i16);
|
|
emit_u16(s, val);
|
|
} else {
|
|
emit_op(s, OP_push_value);
|
|
emit_u32(s, JS_NewShortInt(val));
|
|
}
|
|
}
|
|
|
|
static void emit_var(JSParseState *s, int opcode, int var_idx,
|
|
JSSourcePos source_pos)
|
|
{
|
|
switch(opcode) {
|
|
case OP_get_loc:
|
|
if (var_idx < 4) {
|
|
emit_op_pos(s, OP_get_loc0 + var_idx, source_pos);
|
|
return;
|
|
} else if (var_idx < 256) {
|
|
emit_op_pos(s, OP_get_loc8, source_pos);
|
|
emit_u8(s, var_idx);
|
|
return;
|
|
}
|
|
break;
|
|
case OP_put_loc:
|
|
if (var_idx < 4) {
|
|
emit_op_pos(s, OP_put_loc0 + var_idx, source_pos);
|
|
return;
|
|
} else if (var_idx < 256) {
|
|
emit_op_pos(s, OP_put_loc8, source_pos);
|
|
emit_u8(s, var_idx);
|
|
return;
|
|
}
|
|
break;
|
|
case OP_get_arg:
|
|
if (var_idx < 4) {
|
|
emit_op_pos(s, OP_get_arg0 + var_idx, source_pos);
|
|
return;
|
|
}
|
|
break;
|
|
case OP_put_arg:
|
|
if (var_idx < 4) {
|
|
emit_op_pos(s, OP_put_arg0 + var_idx, source_pos);
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
emit_op_pos(s, opcode, source_pos);
|
|
emit_u16(s, var_idx);
|
|
}
|
|
|
|
|
|
typedef enum {
|
|
JS_PARSE_FUNC_STATEMENT,
|
|
JS_PARSE_FUNC_EXPR,
|
|
JS_PARSE_FUNC_METHOD,
|
|
} JSParseFunctionEnum;
|
|
|
|
static void js_parse_function_decl(JSParseState *s,
|
|
JSParseFunctionEnum func_type, JSValue func_name);
|
|
|
|
/* labels are short integers so they can be used as JSValue. -1 is not
|
|
a valid label. */
|
|
#define LABEL_RESOLVED_FLAG (1 << 29)
|
|
#define LABEL_OFFSET_MASK ((1 << 29) - 1)
|
|
|
|
#define LABEL_NONE JS_NewShortInt(-1)
|
|
|
|
static BOOL label_is_none(JSValue label)
|
|
{
|
|
return JS_VALUE_GET_INT(label) < 0;
|
|
}
|
|
|
|
static JSValue new_label(JSParseState *s)
|
|
{
|
|
return JS_NewShortInt(LABEL_OFFSET_MASK);
|
|
}
|
|
|
|
static void emit_label_pos(JSParseState *s, JSValue *plabel, int pos)
|
|
{
|
|
int label;
|
|
JSByteArray *arr;
|
|
int next;
|
|
|
|
label = JS_VALUE_GET_INT(*plabel);
|
|
assert(!(label & LABEL_RESOLVED_FLAG));
|
|
arr = JS_VALUE_TO_PTR(s->byte_code);
|
|
while (label != LABEL_OFFSET_MASK) {
|
|
next = get_u32(arr->buf + label);
|
|
put_u32(arr->buf + label, pos - label);
|
|
label = next;
|
|
}
|
|
*plabel = JS_NewShortInt(pos | LABEL_RESOLVED_FLAG);
|
|
}
|
|
|
|
static void emit_label(JSParseState *s, JSValue *plabel)
|
|
{
|
|
emit_label_pos(s, plabel, s->byte_code_len);
|
|
/* prevent get_lvalue from using the last expression as an
|
|
lvalue. */
|
|
s->last_opcode_pos = -1;
|
|
}
|
|
|
|
static void emit_goto(JSParseState *s, int opcode, JSValue *plabel)
|
|
{
|
|
int label;
|
|
/* XXX: generate smaller gotos when possible */
|
|
emit_op(s, opcode);
|
|
label = JS_VALUE_GET_INT(*plabel);
|
|
if (label & LABEL_RESOLVED_FLAG) {
|
|
emit_u32(s, (label & LABEL_OFFSET_MASK) - s->byte_code_len);
|
|
} else {
|
|
emit_u32(s, label);
|
|
*plabel = JS_NewShortInt(s->byte_code_len - 4);
|
|
}
|
|
}
|
|
|
|
/* return the constant pool index. 'val' is not duplicated. */
|
|
static int cpool_add(JSParseState *s, JSValue val)
|
|
{
|
|
JSFunctionBytecode *b;
|
|
JSValueArray *arr;
|
|
int i;
|
|
JSValue new_cpool;
|
|
JSGCRef val_ref;
|
|
|
|
b = JS_VALUE_TO_PTR(s->cur_func);
|
|
arr = JS_VALUE_TO_PTR(b->cpool);
|
|
/* check if the value is already present */
|
|
for(i = 0; i < s->cpool_len; i++) {
|
|
if (arr->arr[i] == val)
|
|
return i;
|
|
}
|
|
|
|
if (s->cpool_len > 65535)
|
|
js_parse_error(s, "too many constants");
|
|
JS_PUSH_VALUE(s->ctx, val);
|
|
new_cpool = js_resize_value_array(s->ctx, b->cpool, max_int(s->cpool_len + 1, 4));
|
|
JS_POP_VALUE(s->ctx, val);
|
|
if (JS_IsException(new_cpool))
|
|
js_parse_error_mem(s);
|
|
b = JS_VALUE_TO_PTR(s->cur_func);
|
|
b->cpool = new_cpool;
|
|
arr = JS_VALUE_TO_PTR(b->cpool);
|
|
arr->arr[s->cpool_len++] = val;
|
|
return s->cpool_len - 1;
|
|
}
|
|
|
|
static void js_emit_push_const(JSParseState *s, JSValue val)
|
|
{
|
|
int idx;
|
|
|
|
if (JS_IsPtr(val)
|
|
#ifdef JS_USE_SHORT_FLOAT
|
|
|| JS_IsShortFloat(val)
|
|
#endif
|
|
) {
|
|
/* We use a constant pool to avoid scanning the bytecode
|
|
during the GC. XXX: is it a good choice ? */
|
|
idx = cpool_add(s, val);
|
|
emit_op(s, OP_push_const);
|
|
emit_u16(s, idx);
|
|
} else {
|
|
/* no GC mark */
|
|
emit_op(s, OP_push_value);
|
|
emit_u32(s, val);
|
|
}
|
|
}
|
|
|
|
/* return the local variable index or -1 if not found */
|
|
static int find_func_var(JSContext *ctx, JSValue func, JSValue name)
|
|
{
|
|
JSFunctionBytecode *b;
|
|
JSValueArray *arr;
|
|
int i;
|
|
|
|
b = JS_VALUE_TO_PTR(func);
|
|
if (b->vars == JS_NULL)
|
|
return -1;
|
|
arr = JS_VALUE_TO_PTR(b->vars);
|
|
for(i = 0; i < arr->size; i++) {
|
|
if (arr->arr[i] == name)
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static int find_var(JSParseState *s, JSValue name)
|
|
{
|
|
JSFunctionBytecode *b;
|
|
JSValueArray *arr;
|
|
int i;
|
|
|
|
b = JS_VALUE_TO_PTR(s->cur_func);
|
|
arr = JS_VALUE_TO_PTR(b->vars);
|
|
for(i = 0; i < s->local_vars_len; i++) {
|
|
if (arr->arr[i] == name)
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static JSValue get_ext_var_name(JSParseState *s, int var_idx)
|
|
{
|
|
JSFunctionBytecode *b;
|
|
JSValueArray *arr;
|
|
|
|
b = JS_VALUE_TO_PTR(s->cur_func);
|
|
arr = JS_VALUE_TO_PTR(b->ext_vars);
|
|
return arr->arr[2 * var_idx];
|
|
}
|
|
|
|
static int find_func_ext_var(JSParseState *s, JSValue func, JSValue name)
|
|
{
|
|
JSFunctionBytecode *b;
|
|
JSValueArray *arr;
|
|
int i;
|
|
|
|
b = JS_VALUE_TO_PTR(func);
|
|
arr = JS_VALUE_TO_PTR(b->ext_vars);
|
|
for(i = 0; i < b->ext_vars_len; i++) {
|
|
if (arr->arr[2 * i] == name)
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/* return the external variable index or -1 if not found */
|
|
static int find_ext_var(JSParseState *s, JSValue name)
|
|
{
|
|
return find_func_ext_var(s, s->cur_func, name);
|
|
}
|
|
|
|
/* return the external variable index */
|
|
static int add_func_ext_var(JSParseState *s, JSValue func, JSValue name, int decl)
|
|
{
|
|
JSFunctionBytecode *b;
|
|
JSValueArray *arr;
|
|
JSValue new_ext_vars;
|
|
JSGCRef name_ref, func_ref;
|
|
|
|
b = JS_VALUE_TO_PTR(func);
|
|
if (b->ext_vars_len >= JS_MAX_LOCAL_VARS)
|
|
js_parse_error(s, "too many variable references");
|
|
JS_PUSH_VALUE(s->ctx, func);
|
|
JS_PUSH_VALUE(s->ctx, name);
|
|
new_ext_vars = js_resize_value_array(s->ctx, b->ext_vars, max_int(b->ext_vars_len + 1, 2) * 2);
|
|
JS_POP_VALUE(s->ctx, name);
|
|
JS_POP_VALUE(s->ctx, func);
|
|
if (JS_IsException(new_ext_vars))
|
|
js_parse_error_mem(s);
|
|
b = JS_VALUE_TO_PTR(func);
|
|
b->ext_vars = new_ext_vars;
|
|
arr = JS_VALUE_TO_PTR(b->ext_vars);
|
|
arr->arr[2 * b->ext_vars_len] = name;
|
|
arr->arr[2 * b->ext_vars_len + 1] = JS_NewShortInt(decl);
|
|
b->ext_vars_len++;
|
|
return b->ext_vars_len - 1;
|
|
}
|
|
|
|
/* return the external variable index */
|
|
static int add_ext_var(JSParseState *s, JSValue name, int decl)
|
|
{
|
|
return add_func_ext_var(s, s->cur_func, name, decl);
|
|
}
|
|
|
|
/* return the local variable index */
|
|
static int add_var(JSParseState *s, JSValue name)
|
|
{
|
|
JSFunctionBytecode *b;
|
|
JSValueArray *arr;
|
|
JSValue new_vars;
|
|
JSGCRef name_ref;
|
|
|
|
b = JS_VALUE_TO_PTR(s->cur_func);
|
|
if (s->local_vars_len >= JS_MAX_LOCAL_VARS)
|
|
js_parse_error(s, "too many local variables");
|
|
JS_PUSH_VALUE(s->ctx, name);
|
|
new_vars = js_resize_value_array(s->ctx, b->vars, max_int(s->local_vars_len + 1, 4));
|
|
JS_POP_VALUE(s->ctx, name);
|
|
if (JS_IsException(new_vars))
|
|
js_parse_error_mem(s);
|
|
b = JS_VALUE_TO_PTR(s->cur_func);
|
|
b->vars = new_vars;
|
|
arr = JS_VALUE_TO_PTR(b->vars);
|
|
arr->arr[s->local_vars_len++] = name;
|
|
return s->local_vars_len - 1;
|
|
}
|
|
|
|
static void get_lvalue(JSParseState *s, int *popcode,
|
|
int *pvar_idx, JSSourcePos *psource_pos, BOOL keep)
|
|
{
|
|
int opcode, var_idx;
|
|
JSSourcePos source_pos;
|
|
|
|
/* we check the last opcode to get the lvalue type */
|
|
opcode = get_prev_opcode(s);
|
|
switch(opcode) {
|
|
case OP_get_loc0:
|
|
case OP_get_loc1:
|
|
case OP_get_loc2:
|
|
case OP_get_loc3:
|
|
var_idx = opcode - OP_get_loc0;
|
|
opcode = OP_get_loc;
|
|
break;
|
|
case OP_get_arg0:
|
|
case OP_get_arg1:
|
|
case OP_get_arg2:
|
|
case OP_get_arg3:
|
|
var_idx = opcode - OP_get_arg0;
|
|
opcode = OP_get_arg;
|
|
break;
|
|
case OP_get_loc8:
|
|
var_idx = get_u8(get_byte_code(s) + s->last_opcode_pos + 1);
|
|
opcode = OP_get_loc;
|
|
break;
|
|
case OP_get_loc:
|
|
case OP_get_arg:
|
|
case OP_get_var_ref:
|
|
case OP_get_field:
|
|
var_idx = get_u16(get_byte_code(s) + s->last_opcode_pos + 1);
|
|
break;
|
|
case OP_get_array_el:
|
|
case OP_get_length:
|
|
var_idx = -1;
|
|
break;
|
|
default:
|
|
js_parse_error(s, "invalid lvalue");
|
|
}
|
|
source_pos = s->pc2line_source_pos;
|
|
|
|
/* remove the last opcode */
|
|
remove_last_op(s);
|
|
|
|
if (keep) {
|
|
/* get the value but keep the object/fields on the stack */
|
|
switch(opcode) {
|
|
case OP_get_loc:
|
|
case OP_get_arg:
|
|
case OP_get_var_ref:
|
|
emit_var(s, opcode, var_idx, source_pos);
|
|
break;
|
|
case OP_get_field:
|
|
emit_op_pos(s, OP_get_field2, source_pos);
|
|
emit_u16(s, var_idx);
|
|
break;
|
|
case OP_get_length:
|
|
emit_op_pos(s, OP_get_length2, source_pos);
|
|
break;
|
|
case OP_get_array_el:
|
|
emit_op(s, OP_dup2);
|
|
emit_op_pos(s, OP_get_array_el, source_pos); /* XXX: add OP_get_array_el3 but need to modify tail call */
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
}
|
|
|
|
*popcode = opcode;
|
|
*pvar_idx = var_idx;
|
|
*psource_pos = source_pos;
|
|
}
|
|
|
|
typedef enum {
|
|
PUT_LVALUE_KEEP_TOP, /* [depth] v -> v */
|
|
PUT_LVALUE_NOKEEP_TOP, /* [depth] v -> */
|
|
PUT_LVALUE_KEEP_SECOND, /* [depth] v0 v -> v0 */
|
|
PUT_LVALUE_NOKEEP_BOTTOM, /* v [depth] -> */
|
|
} PutLValueEnum;
|
|
|
|
static void put_lvalue(JSParseState *s, int opcode,
|
|
int var_idx, JSSourcePos source_pos,
|
|
PutLValueEnum special)
|
|
{
|
|
switch(opcode) {
|
|
case OP_get_loc:
|
|
case OP_get_arg:
|
|
case OP_get_var_ref:
|
|
if (special == PUT_LVALUE_KEEP_TOP)
|
|
emit_op(s, OP_dup);
|
|
if (opcode == OP_get_var_ref && s->is_repl)
|
|
opcode = OP_put_var_ref_nocheck; /* an assignment defines the variable in the REPL */
|
|
else
|
|
opcode++;
|
|
emit_var(s, opcode, var_idx, source_pos);
|
|
break;
|
|
case OP_get_field:
|
|
case OP_get_length:
|
|
switch(special) {
|
|
case PUT_LVALUE_KEEP_TOP:
|
|
emit_op(s, OP_insert2); /* obj a -> a obj a */
|
|
break;
|
|
case PUT_LVALUE_NOKEEP_TOP:
|
|
break;
|
|
case PUT_LVALUE_NOKEEP_BOTTOM:
|
|
emit_op(s, OP_swap); /* a obj -> obj a */
|
|
break;
|
|
default:
|
|
case PUT_LVALUE_KEEP_SECOND:
|
|
emit_op(s, OP_perm3); /* obj a b -> a obj b */
|
|
break;
|
|
}
|
|
emit_op_pos(s, OP_put_field, source_pos);
|
|
if (opcode == OP_get_length) {
|
|
emit_u16(s, cpool_add(s, js_get_atom(s->ctx, JS_ATOM_length)));
|
|
} else {
|
|
emit_u16(s, var_idx);
|
|
}
|
|
break;
|
|
case OP_get_array_el:
|
|
switch(special) {
|
|
case PUT_LVALUE_KEEP_TOP:
|
|
emit_op(s, OP_insert3); /* obj prop a -> a obj prop a */
|
|
break;
|
|
case PUT_LVALUE_NOKEEP_TOP:
|
|
break;
|
|
case PUT_LVALUE_NOKEEP_BOTTOM: /* a obj prop -> obj prop a */
|
|
emit_op(s, OP_rot3l); /* obj prop a b -> a obj prop b */
|
|
break;
|
|
default:
|
|
case PUT_LVALUE_KEEP_SECOND:
|
|
emit_op(s, OP_perm4); /* obj prop a b -> a obj prop b */
|
|
break;
|
|
}
|
|
emit_op_pos(s, OP_put_array_el, source_pos);
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
}
|
|
|
|
enum {
|
|
PARSE_PROP_FIELD,
|
|
PARSE_PROP_GET,
|
|
PARSE_PROP_SET,
|
|
PARSE_PROP_METHOD,
|
|
};
|
|
|
|
static int js_parse_property_name(JSParseState *s, JSValue *pname)
|
|
{
|
|
JSContext *ctx = s->ctx;
|
|
JSValue name;
|
|
JSGCRef name_ref;
|
|
int prop_type;
|
|
|
|
prop_type = PARSE_PROP_FIELD;
|
|
|
|
if (s->token.val == TOK_IDENT) {
|
|
int is_set;
|
|
if (s->token.value == js_get_atom(ctx, JS_ATOM_get))
|
|
is_set = 0;
|
|
else if (s->token.value == js_get_atom(ctx, JS_ATOM_set))
|
|
is_set = 1;
|
|
else
|
|
is_set = -1;
|
|
if (is_set >= 0) {
|
|
next_token(s);
|
|
if (s->token.val == ':' || s->token.val == ',' ||
|
|
s->token.val == '}' || s->token.val == '(') {
|
|
/* not a get set */
|
|
name = js_get_atom(ctx, is_set ? JS_ATOM_set : JS_ATOM_get);
|
|
goto done;
|
|
}
|
|
prop_type = PARSE_PROP_GET + is_set;
|
|
}
|
|
}
|
|
|
|
if (s->token.val == TOK_IDENT || s->token.val >= TOK_FIRST_KEYWORD) {
|
|
name = s->token.value;
|
|
} else if (s->token.val == TOK_STRING) {
|
|
name = s->token.value;
|
|
} else if (s->token.val == TOK_NUMBER) {
|
|
name = JS_NewFloat64(s->ctx, s->token.u.d);
|
|
if (JS_IsException(name))
|
|
js_parse_error_mem(s);
|
|
} else {
|
|
js_parse_error(s, "invalid property name");
|
|
}
|
|
name = JS_ToPropertyKey(s->ctx, name);
|
|
if (JS_IsException(name))
|
|
js_parse_error_mem(s);
|
|
JS_PUSH_VALUE(ctx, name);
|
|
next_token(s);
|
|
JS_POP_VALUE(ctx, name);
|
|
done:
|
|
if (prop_type == PARSE_PROP_FIELD && s->token.val == '(')
|
|
prop_type = PARSE_PROP_METHOD;
|
|
*pname = name;
|
|
return prop_type;
|
|
}
|
|
|
|
/* recursion free parser definitions */
|
|
|
|
#define PF_NO_IN (1 << 0) /* the 'in' operator is not accepted*/
|
|
#define PF_DROP (1 << 1) /* drop result */
|
|
#define PF_ACCEPT_LPAREN (1 << 2) /* js_parse_postfix_expr only */
|
|
#define PF_LEVEL_SHIFT 4 /* optional level parameter */
|
|
#define PF_LEVEL_MASK (0xf << PF_LEVEL_SHIFT)
|
|
|
|
typedef enum {
|
|
PARSE_FUNC_js_parse_expr_comma,
|
|
PARSE_FUNC_js_parse_assign_expr,
|
|
PARSE_FUNC_js_parse_cond_expr,
|
|
PARSE_FUNC_js_parse_logical_and_or,
|
|
PARSE_FUNC_js_parse_expr_binary,
|
|
PARSE_FUNC_js_parse_unary,
|
|
PARSE_FUNC_js_parse_postfix_expr,
|
|
PARSE_FUNC_js_parse_statement,
|
|
PARSE_FUNC_js_parse_block,
|
|
PARSE_FUNC_js_parse_json_value,
|
|
PARSE_FUNC_re_parse_alternative,
|
|
PARSE_FUNC_re_parse_disjunction,
|
|
} ParseExprFuncEnum;
|
|
|
|
typedef int JSParseFunc(JSParseState *s, int state, int param);
|
|
|
|
#define PARSE_STATE_INIT 0xfe
|
|
#define PARSE_STATE_RET 0xff
|
|
|
|
/* may trigger a gc */
|
|
static JSValue parse_stack_alloc(JSParseState *s, JSValue val)
|
|
{
|
|
JSGCRef val_ref;
|
|
|
|
JS_PUSH_VALUE(s->ctx, val);
|
|
if (JS_StackCheck(s->ctx, 1))
|
|
js_parse_error_stack_overflow(s);
|
|
JS_POP_VALUE(s->ctx, val);
|
|
return val;
|
|
}
|
|
|
|
/* WARNING: 'val' may be modified after this val if it is a pointer */
|
|
static void js_parse_push_val(JSParseState *s, JSValue val)
|
|
{
|
|
JSContext *ctx = s->ctx;
|
|
if (unlikely(ctx->sp <= ctx->stack_bottom)) {
|
|
val = parse_stack_alloc(s, val);
|
|
}
|
|
*--(ctx->sp) = val;
|
|
}
|
|
|
|
/* update the stack bottom when there is a large stack space */
|
|
static JSValue js_parse_pop_val(JSParseState *s)
|
|
{
|
|
JSContext *ctx = s->ctx;
|
|
JSValue val;
|
|
val = *(ctx->sp)++;
|
|
if (unlikely(ctx->sp - JS_STACK_SLACK > ctx->stack_bottom))
|
|
ctx->stack_bottom = ctx->sp - JS_STACK_SLACK;
|
|
return val;
|
|
}
|
|
|
|
#define PARSE_PUSH_VAL(s, v) js_parse_push_val(s, v)
|
|
#define PARSE_POP_VAL(s, v) v = js_parse_pop_val(s)
|
|
|
|
#define PARSE_PUSH_INT(s, v) js_parse_push_val(s, JS_NewShortInt(v))
|
|
#define PARSE_POP_INT(s, v) v = JS_VALUE_GET_INT(js_parse_pop_val(s))
|
|
|
|
#define PARSE_START1() \
|
|
switch(state) {\
|
|
case PARSE_STATE_INIT: break;\
|
|
default: abort();\
|
|
case 0: goto parse_state0;\
|
|
}
|
|
|
|
#define PARSE_START2() \
|
|
switch(state) {\
|
|
case PARSE_STATE_INIT: break;\
|
|
default: abort();\
|
|
case 0: goto parse_state0;\
|
|
case 1: goto parse_state1;\
|
|
}
|
|
|
|
#define PARSE_START3() \
|
|
switch(state) {\
|
|
case PARSE_STATE_INIT: break;\
|
|
default: abort();\
|
|
case 0: goto parse_state0;\
|
|
case 1: goto parse_state1;\
|
|
case 2: goto parse_state2;\
|
|
}
|
|
|
|
#define PARSE_START7() \
|
|
switch(state) {\
|
|
case PARSE_STATE_INIT: break;\
|
|
default: abort();\
|
|
case 0: goto parse_state0;\
|
|
case 1: goto parse_state1;\
|
|
case 2: goto parse_state2;\
|
|
case 3: goto parse_state3; \
|
|
case 4: goto parse_state4;\
|
|
case 5: goto parse_state5;\
|
|
case 6: goto parse_state6;\
|
|
}
|
|
|
|
#define PARSE_START12() \
|
|
switch(state) {\
|
|
case PARSE_STATE_INIT: break;\
|
|
default: abort();\
|
|
case 0: goto parse_state0;\
|
|
case 1: goto parse_state1;\
|
|
case 2: goto parse_state2;\
|
|
case 3: goto parse_state3; \
|
|
case 4: goto parse_state4;\
|
|
case 5: goto parse_state5;\
|
|
case 6: goto parse_state6;\
|
|
case 7: goto parse_state7; \
|
|
case 8: goto parse_state8;\
|
|
case 9: goto parse_state9;\
|
|
case 10: goto parse_state10;\
|
|
case 11: goto parse_state11;\
|
|
}
|
|
|
|
/* WARNING: local variables are not preserved across PARSE_CALL(). So
|
|
they must be explicitly saved and restored */
|
|
#define PARSE_CALL(s, cur_state, func, param) return (cur_state | (PARSE_FUNC_ ## func << 8) | ((param) << 16)); parse_state ## cur_state : ;
|
|
|
|
/* preserve var1, ... across the call */
|
|
#define PARSE_CALL_SAVE1(s, cur_state, func, param, var1) \
|
|
PARSE_PUSH_INT(s, var1); \
|
|
PARSE_CALL(s, cur_state, func, param); \
|
|
PARSE_POP_INT(s, var1);
|
|
|
|
#define PARSE_CALL_SAVE2(s, cur_state, func, param, var1, var2) \
|
|
PARSE_PUSH_INT(s, var1); \
|
|
PARSE_PUSH_INT(s, var2); \
|
|
PARSE_CALL(s, cur_state, func, param); \
|
|
PARSE_POP_INT(s, var2); \
|
|
PARSE_POP_INT(s, var1);
|
|
|
|
#define PARSE_CALL_SAVE3(s, cur_state, func, param, var1, var2, var3) \
|
|
PARSE_PUSH_INT(s, var1); \
|
|
PARSE_PUSH_INT(s, var2); \
|
|
PARSE_PUSH_INT(s, var3); \
|
|
PARSE_CALL(s, cur_state, func, param); \
|
|
PARSE_POP_INT(s, var3); \
|
|
PARSE_POP_INT(s, var2); \
|
|
PARSE_POP_INT(s, var1);
|
|
|
|
#define PARSE_CALL_SAVE4(s, cur_state, func, param, var1, var2, var3, var4) \
|
|
PARSE_PUSH_INT(s, var1); \
|
|
PARSE_PUSH_INT(s, var2); \
|
|
PARSE_PUSH_INT(s, var3); \
|
|
PARSE_PUSH_INT(s, var4); \
|
|
PARSE_CALL(s, cur_state, func, param); \
|
|
PARSE_POP_INT(s, var4); \
|
|
PARSE_POP_INT(s, var3); \
|
|
PARSE_POP_INT(s, var2); \
|
|
PARSE_POP_INT(s, var1);
|
|
|
|
#define PARSE_CALL_SAVE5(s, cur_state, func, param, var1, var2, var3, var4, var5) \
|
|
PARSE_PUSH_INT(s, var1); \
|
|
PARSE_PUSH_INT(s, var2); \
|
|
PARSE_PUSH_INT(s, var3); \
|
|
PARSE_PUSH_INT(s, var4); \
|
|
PARSE_PUSH_INT(s, var5); \
|
|
PARSE_CALL(s, cur_state, func, param); \
|
|
PARSE_POP_INT(s, var5); \
|
|
PARSE_POP_INT(s, var4); \
|
|
PARSE_POP_INT(s, var3); \
|
|
PARSE_POP_INT(s, var2); \
|
|
PARSE_POP_INT(s, var1);
|
|
|
|
#define PARSE_CALL_SAVE6(s, cur_state, func, param, var1, var2, var3, var4, var5, var6) \
|
|
PARSE_PUSH_INT(s, var1); \
|
|
PARSE_PUSH_INT(s, var2); \
|
|
PARSE_PUSH_INT(s, var3); \
|
|
PARSE_PUSH_INT(s, var4); \
|
|
PARSE_PUSH_INT(s, var5); \
|
|
PARSE_PUSH_INT(s, var6); \
|
|
PARSE_CALL(s, cur_state, func, param); \
|
|
PARSE_POP_INT(s, var6); \
|
|
PARSE_POP_INT(s, var5); \
|
|
PARSE_POP_INT(s, var4); \
|
|
PARSE_POP_INT(s, var3); \
|
|
PARSE_POP_INT(s, var2); \
|
|
PARSE_POP_INT(s, var1);
|
|
|
|
static JSParseFunc *parse_func_table[];
|
|
|
|
static void js_parse_call(JSParseState *s, ParseExprFuncEnum func_idx,
|
|
int param)
|
|
{
|
|
JSContext *ctx = s->ctx;
|
|
int ret, state;
|
|
JSValue *stack_top;
|
|
|
|
stack_top = ctx->sp;
|
|
state = PARSE_STATE_INIT;
|
|
for(;;) {
|
|
ret = parse_func_table[func_idx](s, state, param);
|
|
state = ret & 0xff;
|
|
if (state == PARSE_STATE_RET) {
|
|
/* the function terminated: go back to the calling
|
|
function if any */
|
|
if (ctx->sp == stack_top)
|
|
break;
|
|
PARSE_POP_INT(s, ret);
|
|
state = ret & 0xff;
|
|
func_idx = (ret >> 8) & 0xff;
|
|
param = -1; /* the parameter is not saved */
|
|
} else {
|
|
/* push the call position and call another function */
|
|
PARSE_PUSH_INT(s, state | (func_idx << 8));
|
|
state = PARSE_STATE_INIT;
|
|
func_idx = (ret >> 8) & 0xff;
|
|
param = (ret >> 16);
|
|
}
|
|
}
|
|
}
|
|
|
|
static BOOL may_drop_result(JSParseState *s, int parse_flags)
|
|
{
|
|
return ((parse_flags & PF_DROP) &&
|
|
(s->token.val == ';' || s->token.val == ')' ||
|
|
s->token.val == ','));
|
|
}
|
|
|
|
static void js_emit_push_number(JSParseState *s, double d)
|
|
{
|
|
JSValue val;
|
|
|
|
val = JS_NewFloat64(s->ctx, d);
|
|
if (JS_IsException(val))
|
|
js_parse_error_mem(s);
|
|
if (JS_IsInt(val)) {
|
|
emit_push_short_int(s, JS_VALUE_GET_INT(val));
|
|
} else {
|
|
js_emit_push_const(s, val);
|
|
}
|
|
}
|
|
|
|
static int js_parse_postfix_expr(JSParseState *s, int state, int parse_flags)
|
|
{
|
|
BOOL is_new = FALSE;
|
|
|
|
PARSE_START7();
|
|
switch(s->token.val) {
|
|
case TOK_NUMBER:
|
|
js_emit_push_number(s, s->token.u.d);
|
|
next_token(s);
|
|
break;
|
|
case TOK_STRING:
|
|
{
|
|
js_emit_push_const(s, s->token.value);
|
|
next_token(s);
|
|
}
|
|
break;
|
|
case TOK_REGEXP:
|
|
{
|
|
uint32_t saved_buf_pos, saved_buf_len;
|
|
uint32_t saved_byte_code_len;
|
|
JSValue byte_code;
|
|
JSFunctionBytecode *b;
|
|
|
|
js_emit_push_const(s, s->token.value); /* regexp source */
|
|
|
|
saved_buf_pos = s->buf_pos;
|
|
saved_buf_len = s->buf_len;
|
|
/* save the current bytecode back to the function */
|
|
b = JS_VALUE_TO_PTR(s->cur_func);
|
|
b->byte_code = s->byte_code;
|
|
saved_byte_code_len = s->byte_code_len;
|
|
|
|
/* modify the parser to parse the regexp. This way we
|
|
avoid instantiating a new JSParseState */
|
|
/* XXX: find a better way as it relies on the regexp
|
|
parser to correctly handle the end of regexp */
|
|
s->buf_pos = s->token.source_pos + 1;
|
|
s->buf_len = s->token.u.regexp.re_end_pos;
|
|
byte_code = js_parse_regexp(s, s->token.u.regexp.re_flags);
|
|
|
|
s->buf_pos = saved_buf_pos;
|
|
s->buf_len = saved_buf_len;
|
|
b = JS_VALUE_TO_PTR(s->cur_func);
|
|
s->byte_code = b->byte_code;
|
|
s->byte_code_len = saved_byte_code_len;
|
|
|
|
js_emit_push_const(s, byte_code);
|
|
emit_op(s, OP_regexp);
|
|
next_token(s);
|
|
}
|
|
break;
|
|
case '(':
|
|
next_token(s);
|
|
PARSE_CALL_SAVE1(s, 0, js_parse_expr_comma, 0, parse_flags);
|
|
js_parse_expect(s, ')');
|
|
break;
|
|
case TOK_FUNCTION:
|
|
js_parse_function_decl(s, JS_PARSE_FUNC_EXPR, JS_NULL);
|
|
break;
|
|
case TOK_NULL:
|
|
emit_op(s, OP_null);
|
|
next_token(s);
|
|
break;
|
|
case TOK_THIS:
|
|
emit_op(s, OP_push_this);
|
|
next_token(s);
|
|
break;
|
|
case TOK_FALSE:
|
|
case TOK_TRUE:
|
|
emit_op(s, OP_push_false + (s->token.val == TOK_TRUE));
|
|
next_token(s);
|
|
break;
|
|
case TOK_IDENT:
|
|
{
|
|
JSFunctionBytecode *b;
|
|
JSValue name;
|
|
int var_idx, arg_count, opcode;
|
|
|
|
b = JS_VALUE_TO_PTR(s->cur_func);
|
|
arg_count = b->arg_count;
|
|
|
|
name = s->token.value;
|
|
|
|
var_idx = find_var(s, name);
|
|
if (var_idx >= 0) {
|
|
if (var_idx < arg_count) {
|
|
opcode = OP_get_arg;
|
|
} else {
|
|
opcode = OP_get_loc;
|
|
var_idx -= arg_count;
|
|
}
|
|
} else {
|
|
var_idx = find_ext_var(s, name);
|
|
if (var_idx < 0) {
|
|
var_idx = add_ext_var(s, name, (JS_VARREF_KIND_GLOBAL << 16) | 0);
|
|
}
|
|
opcode = OP_get_var_ref;
|
|
}
|
|
emit_var(s, opcode, var_idx, s->token.source_pos);
|
|
next_token(s);
|
|
}
|
|
break;
|
|
case '{':
|
|
{
|
|
JSValue name;
|
|
int prop_idx, prop_type, count_pos;
|
|
BOOL has_proto;
|
|
|
|
next_token(s);
|
|
emit_op(s, OP_object);
|
|
count_pos = s->byte_code_len;
|
|
emit_u16(s, 0);
|
|
|
|
has_proto = FALSE;
|
|
while (s->token.val != '}') {
|
|
prop_type = js_parse_property_name(s, &name);
|
|
if (prop_type == PARSE_PROP_FIELD &&
|
|
name == js_get_atom(s->ctx, JS_ATOM___proto__)) {
|
|
if (has_proto)
|
|
js_parse_error(s, "duplicate __proto__ property name");
|
|
has_proto = TRUE;
|
|
prop_idx = -1;
|
|
} else {
|
|
uint8_t *byte_code;
|
|
int count;
|
|
prop_idx = cpool_add(s, name);
|
|
/* increment the count */
|
|
byte_code = get_byte_code(s);
|
|
count = get_u16(byte_code + count_pos);
|
|
put_u16(byte_code + count_pos, min_int(count + 1, 0xffff));
|
|
}
|
|
if (prop_type == PARSE_PROP_FIELD) {
|
|
js_parse_expect(s, ':');
|
|
PARSE_CALL_SAVE4(s, 1, js_parse_assign_expr, 0, prop_idx, parse_flags, has_proto, count_pos);
|
|
if (prop_idx >= 0) {
|
|
emit_op(s, OP_define_field);
|
|
emit_u16(s, prop_idx);
|
|
} else {
|
|
emit_op(s, OP_set_proto);
|
|
}
|
|
} else {
|
|
/* getter/setter/method */
|
|
js_parse_function_decl(s, JS_PARSE_FUNC_METHOD, name);
|
|
if (prop_type == PARSE_PROP_METHOD)
|
|
emit_op(s, OP_define_field);
|
|
else if (prop_type == PARSE_PROP_GET)
|
|
emit_op(s, OP_define_getter);
|
|
else
|
|
emit_op(s, OP_define_setter);
|
|
emit_u16(s, prop_idx);
|
|
}
|
|
if (s->token.val != ',')
|
|
break;
|
|
next_token(s);
|
|
}
|
|
js_parse_expect(s, '}');
|
|
}
|
|
break;
|
|
case '[':
|
|
{
|
|
uint32_t idx;
|
|
|
|
next_token(s);
|
|
/* small regular arrays are created on the stack */
|
|
idx = 0;
|
|
while (s->token.val != ']' && idx < 32) {
|
|
/* SPEC: we don't accept empty elements */
|
|
PARSE_CALL_SAVE2(s, 2, js_parse_assign_expr, 0, idx, parse_flags);
|
|
idx++;
|
|
/* accept trailing comma */
|
|
if (s->token.val == ',') {
|
|
next_token(s);
|
|
} else if (s->token.val != ']') {
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
emit_op_param(s, OP_array_from, idx, s->pc2line_source_pos);
|
|
|
|
while (s->token.val != ']') {
|
|
if (idx >= JS_SHORTINT_MAX)
|
|
js_parse_error(s, "too many elements");
|
|
emit_op(s, OP_dup);
|
|
emit_push_short_int(s, idx);
|
|
PARSE_CALL_SAVE2(s, 3, js_parse_assign_expr, 0, idx, parse_flags);
|
|
emit_op(s, OP_put_array_el);
|
|
idx++;
|
|
/* accept trailing comma */
|
|
if (s->token.val == ',') {
|
|
next_token(s);
|
|
}
|
|
}
|
|
done:
|
|
js_parse_expect(s, ']');
|
|
}
|
|
break;
|
|
case TOK_NEW:
|
|
next_token(s);
|
|
if (s->token.val == '.') {
|
|
next_token(s);
|
|
if (s->token.val != TOK_IDENT ||
|
|
s->token.value != js_get_atom(s->ctx, JS_ATOM_target)) {
|
|
js_parse_error(s, "expecting target");
|
|
}
|
|
next_token(s);
|
|
emit_op(s, OP_new_target);
|
|
} else {
|
|
PARSE_CALL_SAVE1(s, 4, js_parse_postfix_expr, 0, parse_flags);
|
|
if (s->token.val != '(') {
|
|
/* new operator on an object */
|
|
emit_op_param(s, OP_call_constructor, 0, s->token.source_pos);
|
|
} else {
|
|
is_new = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
js_parse_error(s, "unexpected character in expression");
|
|
}
|
|
|
|
for(;;) {
|
|
if (s->token.val == '(' && (parse_flags & PF_ACCEPT_LPAREN)) {
|
|
int opcode, arg_count;
|
|
uint8_t *byte_code;
|
|
JSSourcePos op_source_pos;
|
|
|
|
/* function call */
|
|
op_source_pos = s->token.source_pos;
|
|
next_token(s);
|
|
|
|
if (!is_new) {
|
|
opcode = get_prev_opcode(s);
|
|
byte_code = get_byte_code(s);
|
|
switch(opcode) {
|
|
case OP_get_field:
|
|
byte_code[s->last_opcode_pos] = OP_get_field2;
|
|
break;
|
|
case OP_get_length:
|
|
byte_code[s->last_opcode_pos] = OP_get_length2;
|
|
break;
|
|
case OP_get_array_el:
|
|
byte_code[s->last_opcode_pos] = OP_get_array_el2;
|
|
break;
|
|
case OP_get_var_ref:
|
|
{
|
|
int var_idx = get_u16(byte_code + s->last_opcode_pos + 1);
|
|
if (get_ext_var_name(s, var_idx) == js_get_atom(s->ctx, JS_ATOM_eval)) {
|
|
js_parse_error(s, "direct eval is not supported. Use (1,eval) instead for indirect eval");
|
|
}
|
|
}
|
|
/* fall thru */
|
|
default:
|
|
opcode = OP_invalid;
|
|
break;
|
|
}
|
|
} else {
|
|
opcode = OP_invalid;
|
|
}
|
|
|
|
arg_count = 0;
|
|
if (s->token.val != ')') {
|
|
for(;;) {
|
|
if (arg_count >= JS_MAX_ARGC)
|
|
js_parse_error(s, "too many call arguments");
|
|
arg_count++;
|
|
PARSE_CALL_SAVE5(s, 5, js_parse_assign_expr, 0,
|
|
parse_flags, arg_count, opcode, is_new, op_source_pos);
|
|
if (s->token.val == ')')
|
|
break;
|
|
js_parse_expect(s, ',');
|
|
}
|
|
}
|
|
next_token(s);
|
|
if (opcode == OP_get_field ||
|
|
opcode == OP_get_length ||
|
|
opcode == OP_get_array_el) {
|
|
emit_op_param(s, OP_call_method, arg_count, op_source_pos);
|
|
} else {
|
|
if (is_new) {
|
|
emit_op_param(s, OP_call_constructor, arg_count, op_source_pos);
|
|
} else {
|
|
emit_op_param(s, OP_call, arg_count, op_source_pos);
|
|
}
|
|
}
|
|
is_new = FALSE;
|
|
} else if (s->token.val == '.') {
|
|
JSSourcePos op_source_pos;
|
|
int prop_idx;
|
|
|
|
op_source_pos = s->token.source_pos;
|
|
next_token(s);
|
|
if (!(s->token.val == TOK_IDENT || s->token.val >= TOK_FIRST_KEYWORD)) {
|
|
js_parse_error(s, "expecting field name");
|
|
}
|
|
/* we ensure that no numeric property is used with
|
|
OP_get_field to enable some optimizations. The only
|
|
possible identifiers are NaN and Infinity */
|
|
if (s->token.value == js_get_atom(s->ctx, JS_ATOM_NaN) ||
|
|
s->token.value == js_get_atom(s->ctx, JS_ATOM_Infinity)) {
|
|
js_emit_push_const(s, s->token.value);
|
|
emit_op_pos(s, OP_get_array_el, op_source_pos);
|
|
} else if (s->token.value == js_get_atom(s->ctx, JS_ATOM_length)) {
|
|
emit_op_pos(s, OP_get_length, op_source_pos);
|
|
} else {
|
|
prop_idx = cpool_add(s, s->token.value);
|
|
emit_op_pos(s, OP_get_field, op_source_pos);
|
|
emit_u16(s, prop_idx);
|
|
}
|
|
next_token(s);
|
|
} else if (s->token.val == '[') {
|
|
JSSourcePos op_source_pos;
|
|
op_source_pos = s->token.source_pos;
|
|
next_token(s);
|
|
PARSE_CALL_SAVE3(s, 6, js_parse_expr_comma, 0,
|
|
parse_flags, is_new, op_source_pos);
|
|
js_parse_expect(s, ']');
|
|
emit_op_pos(s, OP_get_array_el, op_source_pos);
|
|
} else if (!s->got_lf && (s->token.val == TOK_DEC || s->token.val == TOK_INC)) {
|
|
int opcode, op, var_idx;
|
|
JSSourcePos op_source_pos, source_pos;
|
|
|
|
op = s->token.val;
|
|
op_source_pos = s->token.source_pos;
|
|
next_token(s);
|
|
get_lvalue(s, &opcode, &var_idx, &source_pos, TRUE);
|
|
if (may_drop_result(s, parse_flags)) {
|
|
s->dropped_result = TRUE;
|
|
emit_op_pos(s, OP_dec + op - TOK_DEC, op_source_pos);
|
|
put_lvalue(s, opcode, var_idx, source_pos, PUT_LVALUE_NOKEEP_TOP);
|
|
} else {
|
|
emit_op_pos(s, OP_post_dec + op - TOK_DEC, op_source_pos);
|
|
put_lvalue(s, opcode, var_idx, source_pos, PUT_LVALUE_KEEP_SECOND);
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
return PARSE_STATE_RET;
|
|
}
|
|
|
|
static void js_emit_delete(JSParseState *s)
|
|
{
|
|
int opcode;
|
|
|
|
opcode = get_prev_opcode(s);
|
|
switch(opcode) {
|
|
case OP_get_field:
|
|
{
|
|
JSByteArray *byte_code;
|
|
int prop_idx;
|
|
byte_code = JS_VALUE_TO_PTR(s->byte_code);
|
|
prop_idx = get_u16(byte_code->buf + s->last_opcode_pos + 1);
|
|
remove_last_op(s);
|
|
emit_op(s, OP_push_const);
|
|
emit_u16(s, prop_idx);
|
|
}
|
|
break;
|
|
case OP_get_length:
|
|
remove_last_op(s);
|
|
js_emit_push_const(s, js_get_atom(s->ctx, JS_ATOM_length));
|
|
break;
|
|
case OP_get_array_el:
|
|
remove_last_op(s);
|
|
break;
|
|
default:
|
|
js_parse_error(s, "invalid lvalue for delete");
|
|
}
|
|
emit_op(s, OP_delete);
|
|
}
|
|
|
|
static int js_parse_unary(JSParseState *s, int state, int parse_flags)
|
|
{
|
|
PARSE_START7();
|
|
|
|
switch(s->token.val) {
|
|
case '+':
|
|
case '-':
|
|
case '!':
|
|
case '~':
|
|
{
|
|
int op;
|
|
JSSourcePos op_source_pos;
|
|
|
|
op = s->token.val;
|
|
op_source_pos = s->token.source_pos;
|
|
next_token(s);
|
|
|
|
/* XXX: could handle more cases */
|
|
if (s->token.val == TOK_NUMBER && (op == '-' || op == '+')) {
|
|
double d = s->token.u.d;
|
|
if (op == '-')
|
|
d = -d;
|
|
js_emit_push_number(s, d);
|
|
next_token(s);
|
|
} else {
|
|
PARSE_CALL_SAVE2(s, 0, js_parse_unary, 0, op, op_source_pos);
|
|
switch(op) {
|
|
case '-':
|
|
emit_op_pos(s, OP_neg, op_source_pos);
|
|
break;
|
|
case '+':
|
|
emit_op_pos(s, OP_plus, op_source_pos);
|
|
break;
|
|
case '!':
|
|
emit_op_pos(s, OP_lnot, op_source_pos);
|
|
break;
|
|
case '~':
|
|
emit_op_pos(s, OP_not, op_source_pos);
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case TOK_VOID:
|
|
next_token(s);
|
|
PARSE_CALL(s, 1, js_parse_unary, 0);
|
|
emit_op(s, OP_drop);
|
|
emit_op(s, OP_undefined);
|
|
break;
|
|
case TOK_DEC:
|
|
case TOK_INC:
|
|
{
|
|
int opcode, op, var_idx;
|
|
PutLValueEnum special;
|
|
JSSourcePos op_source_pos, source_pos;
|
|
|
|
op = s->token.val;
|
|
op_source_pos = s->token.source_pos;
|
|
next_token(s);
|
|
PARSE_CALL_SAVE3(s, 2, js_parse_unary, 0, op, parse_flags, op_source_pos);
|
|
get_lvalue(s, &opcode, &var_idx, &source_pos, TRUE);
|
|
emit_op_pos(s, OP_dec + op - TOK_DEC, op_source_pos);
|
|
|
|
if (may_drop_result(s, parse_flags)) {
|
|
special = PUT_LVALUE_NOKEEP_TOP;
|
|
s->dropped_result = TRUE;
|
|
} else {
|
|
special = PUT_LVALUE_KEEP_TOP;
|
|
}
|
|
put_lvalue(s, opcode, var_idx, source_pos, special);
|
|
}
|
|
break;
|
|
case TOK_TYPEOF:
|
|
{
|
|
next_token(s);
|
|
PARSE_CALL(s, 3, js_parse_unary, 0);
|
|
/* access to undefined variable should not return an
|
|
exception, so we patch the get_var */
|
|
if (get_prev_opcode(s) == OP_get_var_ref) {
|
|
uint8_t *byte_code = get_byte_code(s);
|
|
byte_code[s->last_opcode_pos] = OP_get_var_ref_nocheck;
|
|
}
|
|
emit_op(s, OP_typeof);
|
|
}
|
|
break;
|
|
case TOK_DELETE:
|
|
next_token(s);
|
|
PARSE_CALL(s, 4, js_parse_unary, 0);
|
|
js_emit_delete(s);
|
|
break;
|
|
default:
|
|
PARSE_CALL(s, 5, js_parse_postfix_expr, parse_flags | PF_ACCEPT_LPAREN);
|
|
/* XXX: we do not follow the ES7 grammar in order to have a
|
|
* more natural expression */
|
|
if (s->token.val == TOK_POW) {
|
|
JSSourcePos op_source_pos;
|
|
op_source_pos = s->token.source_pos;
|
|
next_token(s);
|
|
PARSE_CALL_SAVE1(s, 6, js_parse_unary, 0, op_source_pos);
|
|
emit_op_pos(s, OP_pow, op_source_pos);
|
|
}
|
|
break;
|
|
}
|
|
return PARSE_STATE_RET;
|
|
}
|
|
|
|
static int js_parse_expr_binary(JSParseState *s, int state, int parse_flags)
|
|
{
|
|
int op, opcode, level;
|
|
JSSourcePos op_source_pos;
|
|
|
|
PARSE_START3();
|
|
level = (parse_flags & PF_LEVEL_MASK) >> PF_LEVEL_SHIFT;
|
|
if (level == 0) {
|
|
PARSE_CALL(s, 0, js_parse_unary, parse_flags);
|
|
return PARSE_STATE_RET;
|
|
}
|
|
PARSE_CALL_SAVE1(s, 1, js_parse_expr_binary, parse_flags - (1 << PF_LEVEL_SHIFT), parse_flags);
|
|
parse_flags &= ~PF_DROP;
|
|
for(;;) {
|
|
op = s->token.val;
|
|
op_source_pos = s->token.source_pos;
|
|
level = (parse_flags & PF_LEVEL_MASK) >> PF_LEVEL_SHIFT;
|
|
switch(level) {
|
|
case 1:
|
|
switch(op) {
|
|
case '*':
|
|
opcode = OP_mul;
|
|
break;
|
|
case '/':
|
|
opcode = OP_div;
|
|
break;
|
|
case '%':
|
|
opcode = OP_mod;
|
|
break;
|
|
default:
|
|
return PARSE_STATE_RET;
|
|
}
|
|
break;
|
|
case 2:
|
|
switch(op) {
|
|
case '+':
|
|
opcode = OP_add;
|
|
break;
|
|
case '-':
|
|
opcode = OP_sub;
|
|
break;
|
|
default:
|
|
return PARSE_STATE_RET;
|
|
}
|
|
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 PARSE_STATE_RET;
|
|
}
|
|
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_NO_IN)) {
|
|
opcode = OP_in;
|
|
} else {
|
|
return PARSE_STATE_RET;
|
|
}
|
|
break;
|
|
default:
|
|
return PARSE_STATE_RET;
|
|
}
|
|
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 PARSE_STATE_RET;
|
|
}
|
|
break;
|
|
case 6:
|
|
switch(op) {
|
|
case '&':
|
|
opcode = OP_and;
|
|
break;
|
|
default:
|
|
return PARSE_STATE_RET;
|
|
}
|
|
break;
|
|
case 7:
|
|
switch(op) {
|
|
case '^':
|
|
opcode = OP_xor;
|
|
break;
|
|
default:
|
|
return PARSE_STATE_RET;
|
|
}
|
|
break;
|
|
case 8:
|
|
switch(op) {
|
|
case '|':
|
|
opcode = OP_or;
|
|
break;
|
|
default:
|
|
return PARSE_STATE_RET;
|
|
}
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
next_token(s);
|
|
PARSE_CALL_SAVE3(s, 2, js_parse_expr_binary, parse_flags - (1 << PF_LEVEL_SHIFT), parse_flags, opcode, op_source_pos);
|
|
emit_op_pos(s, opcode, op_source_pos);
|
|
}
|
|
return PARSE_STATE_RET;
|
|
}
|
|
|
|
static int js_parse_logical_and_or(JSParseState *s, int state, int parse_flags)
|
|
{
|
|
JSValue label1;
|
|
int level, op;
|
|
|
|
PARSE_START3();
|
|
level = (parse_flags & PF_LEVEL_MASK) >> PF_LEVEL_SHIFT;
|
|
if (level == 0) {
|
|
PARSE_CALL(s, 0, js_parse_expr_binary, (parse_flags & ~PF_LEVEL_MASK) | (8 << PF_LEVEL_SHIFT));
|
|
return PARSE_STATE_RET;
|
|
}
|
|
|
|
PARSE_CALL_SAVE1(s, 1, js_parse_logical_and_or, parse_flags - (1 << PF_LEVEL_SHIFT), parse_flags);
|
|
|
|
level = (parse_flags & PF_LEVEL_MASK) >> PF_LEVEL_SHIFT;
|
|
if (level == 1)
|
|
op = TOK_LAND;
|
|
else
|
|
op = TOK_LOR;
|
|
parse_flags &= ~PF_DROP;
|
|
if (s->token.val == op) {
|
|
label1 = new_label(s);
|
|
|
|
for(;;) {
|
|
next_token(s);
|
|
emit_op(s, OP_dup);
|
|
emit_goto(s, op == TOK_LAND ? OP_if_false : OP_if_true, &label1);
|
|
emit_op(s, OP_drop);
|
|
|
|
PARSE_PUSH_VAL(s, label1);
|
|
PARSE_CALL_SAVE1(s, 2, js_parse_logical_and_or, parse_flags - (1 << PF_LEVEL_SHIFT), parse_flags);
|
|
PARSE_POP_VAL(s, label1);
|
|
|
|
level = (parse_flags & PF_LEVEL_MASK) >> PF_LEVEL_SHIFT;
|
|
if (level == 1)
|
|
op = TOK_LAND;
|
|
else
|
|
op = TOK_LOR;
|
|
|
|
if (s->token.val != op)
|
|
break;
|
|
}
|
|
|
|
emit_label(s, &label1);
|
|
}
|
|
return PARSE_STATE_RET;
|
|
}
|
|
|
|
static int js_parse_cond_expr(JSParseState *s, int state, int parse_flags)
|
|
{
|
|
JSValue label1, label2;
|
|
|
|
PARSE_START3();
|
|
|
|
PARSE_CALL_SAVE1(s, 2, js_parse_logical_and_or, parse_flags | (2 << PF_LEVEL_SHIFT), parse_flags);
|
|
|
|
parse_flags &= ~PF_DROP;
|
|
if (s->token.val == '?') {
|
|
next_token(s);
|
|
label1 = new_label(s);
|
|
emit_goto(s, OP_if_false, &label1);
|
|
|
|
PARSE_PUSH_VAL(s, label1);
|
|
PARSE_CALL_SAVE1(s, 0, js_parse_assign_expr, parse_flags,
|
|
parse_flags);
|
|
PARSE_POP_VAL(s, label1);
|
|
|
|
label2 = new_label(s);
|
|
emit_goto(s, OP_goto, &label2);
|
|
|
|
js_parse_expect(s, ':');
|
|
|
|
emit_label(s, &label1);
|
|
|
|
PARSE_PUSH_VAL(s, label2);
|
|
PARSE_CALL_SAVE1(s, 1, js_parse_assign_expr, parse_flags,
|
|
parse_flags);
|
|
PARSE_POP_VAL(s, label2);
|
|
|
|
emit_label(s, &label2);
|
|
}
|
|
return PARSE_STATE_RET;
|
|
}
|
|
|
|
static int js_parse_assign_expr(JSParseState *s, int state, int parse_flags)
|
|
{
|
|
int opcode, op, var_idx;
|
|
PutLValueEnum special;
|
|
JSSourcePos op_source_pos, source_pos;
|
|
|
|
PARSE_START2();
|
|
|
|
PARSE_CALL_SAVE1(s, 1, js_parse_cond_expr, parse_flags, parse_flags);
|
|
|
|
op = s->token.val;
|
|
if (op == '=' || (op >= TOK_MUL_ASSIGN && op <= TOK_OR_ASSIGN)) {
|
|
op_source_pos = s->token.source_pos;
|
|
next_token(s);
|
|
get_lvalue(s, &opcode, &var_idx, &source_pos, (op != '='));
|
|
|
|
PARSE_CALL_SAVE6(s, 0, js_parse_assign_expr, parse_flags & ~PF_DROP,
|
|
op, opcode, var_idx, parse_flags,
|
|
op_source_pos, source_pos);
|
|
|
|
if (op != '=') {
|
|
static const uint8_t assign_opcodes[] = {
|
|
OP_mul, OP_div, OP_mod, OP_add, OP_sub,
|
|
OP_shl, OP_sar, OP_shr, OP_and, OP_xor, OP_or,
|
|
OP_pow,
|
|
};
|
|
emit_op_pos(s, assign_opcodes[op - TOK_MUL_ASSIGN], op_source_pos);
|
|
}
|
|
|
|
if (may_drop_result(s, parse_flags)) {
|
|
special = PUT_LVALUE_NOKEEP_TOP;
|
|
s->dropped_result = TRUE;
|
|
} else {
|
|
special = PUT_LVALUE_KEEP_TOP;
|
|
}
|
|
put_lvalue(s, opcode, var_idx, source_pos, special);
|
|
}
|
|
return PARSE_STATE_RET;
|
|
}
|
|
|
|
static int js_parse_expr_comma(JSParseState *s, int state, int parse_flags)
|
|
{
|
|
BOOL comma = FALSE;
|
|
|
|
PARSE_START1();
|
|
|
|
for(;;) {
|
|
s->dropped_result = FALSE;
|
|
PARSE_CALL_SAVE2(s, 0, js_parse_assign_expr, parse_flags,
|
|
comma, parse_flags);
|
|
if (comma) {
|
|
/* prevent get_lvalue from using the last expression as an
|
|
lvalue. */
|
|
s->last_opcode_pos = -1;
|
|
}
|
|
if (s->token.val != ',')
|
|
break;
|
|
comma = TRUE;
|
|
if (!s->dropped_result)
|
|
emit_op(s, OP_drop);
|
|
next_token(s);
|
|
}
|
|
if ((parse_flags & PF_DROP) && !s->dropped_result) {
|
|
emit_op(s, OP_drop);
|
|
}
|
|
return PARSE_STATE_RET;
|
|
}
|
|
|
|
static void js_parse_assign_expr2(JSParseState *s, int parse_flags)
|
|
{
|
|
js_parse_call(s, PARSE_FUNC_js_parse_assign_expr, parse_flags);
|
|
}
|
|
|
|
static void js_parse_expr2(JSParseState *s, int parse_flags)
|
|
{
|
|
js_parse_call(s, PARSE_FUNC_js_parse_expr_comma, parse_flags);
|
|
}
|
|
|
|
static void js_parse_expr(JSParseState *s)
|
|
{
|
|
js_parse_expr2(s, 0);
|
|
}
|
|
|
|
static void js_parse_expr_paren(JSParseState *s)
|
|
{
|
|
js_parse_expect(s, '(');
|
|
js_parse_expr(s);
|
|
js_parse_expect(s, ')');
|
|
}
|
|
|
|
static BlockEnv *push_break_entry(JSParseState *s, JSValue label_name,
|
|
JSValue label_break, JSValue label_cont,
|
|
int drop_count)
|
|
{
|
|
JSContext *ctx = s->ctx;
|
|
JSGCRef label_name_ref;
|
|
int ret, block_env_len;
|
|
BlockEnv *be;
|
|
|
|
block_env_len = sizeof(BlockEnv) / sizeof(JSValue);
|
|
JS_PUSH_VALUE(ctx, label_name);
|
|
ret = JS_StackCheck(ctx, block_env_len);
|
|
JS_POP_VALUE(ctx, label_name);
|
|
if (ret)
|
|
js_parse_error_stack_overflow(s);
|
|
ctx->sp -= block_env_len;
|
|
be = (BlockEnv *)ctx->sp;
|
|
be->prev = s->top_break;
|
|
s->top_break = SP_TO_VALUE(ctx, be);
|
|
be->label_name = label_name;
|
|
be->label_break = label_break;
|
|
be->label_cont = label_cont;
|
|
be->label_finally = LABEL_NONE;
|
|
be->drop_count = JS_NewShortInt(drop_count);
|
|
return be;
|
|
}
|
|
|
|
static void pop_break_entry(JSParseState *s)
|
|
{
|
|
JSContext *ctx = s->ctx;
|
|
BlockEnv *be;
|
|
|
|
be = VALUE_TO_SP(ctx, s->top_break);
|
|
s->top_break = be->prev;
|
|
ctx->sp += sizeof(BlockEnv) / sizeof(JSValue);
|
|
ctx->stack_bottom = ctx->sp;
|
|
}
|
|
|
|
static void emit_return(JSParseState *s, BOOL hasval, JSSourcePos source_pos)
|
|
{
|
|
JSValue top_val;
|
|
BlockEnv *top;
|
|
int i, drop_count;
|
|
|
|
drop_count = 0;
|
|
top_val = s->top_break;
|
|
while (!JS_IsNull(top_val)) {
|
|
top = VALUE_TO_SP(s->ctx, top_val);
|
|
/* no need to drop if no "finally" */
|
|
drop_count += JS_VALUE_GET_INT(top->drop_count);
|
|
|
|
if (!label_is_none(top->label_finally)) {
|
|
if (!hasval) {
|
|
emit_op(s, OP_undefined);
|
|
hasval = TRUE;
|
|
}
|
|
for(i = 0; i < drop_count; i++)
|
|
emit_op(s, OP_nip); /* must keep the stack stop */
|
|
drop_count = 0;
|
|
/* execute the "finally" block */
|
|
emit_goto(s, OP_gosub, &top->label_finally);
|
|
}
|
|
top_val = top->prev;
|
|
}
|
|
emit_op_pos(s, hasval ? OP_return : OP_return_undef, source_pos);
|
|
}
|
|
|
|
static void emit_break(JSParseState *s, JSValue label_name, int is_cont)
|
|
{
|
|
JSValue top_val;
|
|
BlockEnv *top;
|
|
int i;
|
|
JSValue *plabel;
|
|
JSGCRef label_name_ref;
|
|
BOOL is_labelled_stmt;
|
|
|
|
top_val = s->top_break;
|
|
while (!JS_IsNull(top_val)) {
|
|
top = VALUE_TO_SP(s->ctx, top_val);
|
|
is_labelled_stmt = (top->label_cont == LABEL_NONE &&
|
|
JS_VALUE_GET_INT(top->drop_count) == 0);
|
|
if ((label_name == JS_NULL && !is_labelled_stmt) ||
|
|
top->label_name == label_name) {
|
|
if (is_cont)
|
|
plabel = &top->label_cont;
|
|
else
|
|
plabel = &top->label_break;
|
|
if (!label_is_none(*plabel)) {
|
|
emit_goto(s, OP_goto, plabel);
|
|
return;
|
|
}
|
|
}
|
|
JS_PUSH_VALUE(s->ctx, label_name);
|
|
for(i = 0; i < JS_VALUE_GET_INT(top->drop_count); i++)
|
|
emit_op(s, OP_drop);
|
|
if (!label_is_none(top->label_finally)) {
|
|
/* 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);
|
|
}
|
|
JS_POP_VALUE(s->ctx, label_name);
|
|
top_val = top->prev;
|
|
}
|
|
if (label_name == JS_NULL) {
|
|
if (is_cont)
|
|
js_parse_error(s, "continue must be inside loop");
|
|
else
|
|
js_parse_error(s, "break must be inside loop or switch");
|
|
} else {
|
|
js_parse_error(s, "break/continue label not found");
|
|
}
|
|
}
|
|
|
|
static int define_var(JSParseState *s, JSVarRefKindEnum *pvar_kind, JSValue name)
|
|
{
|
|
JSVarRefKindEnum var_kind;
|
|
int var_idx;
|
|
|
|
if (s->is_eval) {
|
|
var_idx = find_ext_var(s, name);
|
|
if (var_idx < 0) {
|
|
var_idx = add_ext_var(s, name, (JS_VARREF_KIND_GLOBAL << 16) | 1);
|
|
} else {
|
|
JSFunctionBytecode *b = JS_VALUE_TO_PTR(s->cur_func);
|
|
JSValueArray *arr = JS_VALUE_TO_PTR(b->ext_vars);
|
|
arr->arr[2 * var_idx + 1] = JS_NewShortInt((JS_VARREF_KIND_GLOBAL << 16) | 1);
|
|
}
|
|
var_kind = JS_VARREF_KIND_VAR_REF;
|
|
} else {
|
|
JSFunctionBytecode *b;
|
|
int arg_count;
|
|
|
|
b = JS_VALUE_TO_PTR(s->cur_func);
|
|
arg_count = b->arg_count;
|
|
|
|
var_idx = find_var(s, name);
|
|
if (var_idx >= 0) {
|
|
if (var_idx < arg_count) {
|
|
var_kind = JS_VARREF_KIND_ARG;
|
|
} else {
|
|
var_kind = JS_VARREF_KIND_VAR;
|
|
var_idx -= arg_count;
|
|
}
|
|
} else {
|
|
var_idx = add_var(s, name);
|
|
var_kind = JS_VARREF_KIND_VAR;
|
|
var_idx -= arg_count;
|
|
}
|
|
}
|
|
*pvar_kind = var_kind;
|
|
return var_idx;
|
|
}
|
|
|
|
static void put_var(JSParseState *s, JSVarRefKindEnum var_kind, int var_idx, JSSourcePos source_pos)
|
|
{
|
|
int opcode;
|
|
if (var_kind == JS_VARREF_KIND_ARG)
|
|
opcode = OP_put_arg;
|
|
else if (var_kind == JS_VARREF_KIND_VAR)
|
|
opcode = OP_put_loc;
|
|
else
|
|
opcode = OP_put_var_ref_nocheck;
|
|
emit_var(s, opcode, var_idx, source_pos);
|
|
}
|
|
|
|
static void js_parse_var(JSParseState *s, BOOL in_accepted)
|
|
{
|
|
JSVarRefKindEnum var_kind;
|
|
int var_idx;
|
|
JSSourcePos ident_source_pos;
|
|
|
|
for(;;) {
|
|
ident_source_pos = s->token.source_pos;
|
|
if (s->token.val != TOK_IDENT)
|
|
js_parse_error(s, "variable name expected");
|
|
if (s->token.value == js_get_atom(s->ctx, JS_ATOM_arguments))
|
|
js_parse_error(s, "invalid variable name");
|
|
var_idx = define_var(s, &var_kind, s->token.value);
|
|
next_token(s);
|
|
if (s->token.val == '=') {
|
|
next_token(s);
|
|
js_parse_assign_expr2(s, in_accepted ? 0 : PF_NO_IN);
|
|
put_var(s, var_kind, var_idx, ident_source_pos);
|
|
}
|
|
if (s->token.val != ',')
|
|
break;
|
|
next_token(s);
|
|
}
|
|
}
|
|
|
|
static void set_eval_ret_undefined(JSParseState *s)
|
|
{
|
|
if (s->eval_ret_idx >= 0) {
|
|
emit_op(s, OP_undefined);
|
|
emit_var(s, OP_put_loc, s->eval_ret_idx, s->pc2line_source_pos);
|
|
}
|
|
}
|
|
|
|
static int js_parse_block(JSParseState *s, int state, int dummy_param)
|
|
{
|
|
PARSE_START1();
|
|
js_parse_expect(s, '{');
|
|
if (s->token.val != '}') {
|
|
for(;;) {
|
|
PARSE_CALL(s, 0, js_parse_statement, 0);
|
|
if (s->token.val == '}')
|
|
break;
|
|
}
|
|
}
|
|
next_token(s);
|
|
return PARSE_STATE_RET;
|
|
}
|
|
|
|
/* The statement parser assumes that the stack contains the result of
|
|
the last statement. Note: if not in eval code, the return value of
|
|
a statement does not matter */
|
|
static int js_parse_statement(JSParseState *s, int state, int dummy_param)
|
|
{
|
|
JSValue label_name;
|
|
JSGCRef label_name_ref;
|
|
|
|
PARSE_START12();
|
|
|
|
/* specific label handling */
|
|
if (is_label(s)) {
|
|
JSValue top_val;
|
|
BlockEnv *top;
|
|
|
|
label_name = s->token.value;
|
|
JS_PUSH_VALUE(s->ctx, label_name);
|
|
next_token(s);
|
|
js_parse_expect(s, ':');
|
|
JS_POP_VALUE(s->ctx, label_name);
|
|
|
|
for(top_val = s->top_break; !JS_IsNull(top_val); top_val = top->prev) {
|
|
top = VALUE_TO_SP(s->ctx, top_val);
|
|
if (top->label_name == label_name)
|
|
js_parse_error(s, "duplicate label name");
|
|
}
|
|
|
|
if (s->token.val != TOK_FOR &&
|
|
s->token.val != TOK_DO &&
|
|
s->token.val != TOK_WHILE) {
|
|
/* labelled regular statement */
|
|
BlockEnv *be;
|
|
push_break_entry(s, label_name, new_label(s), LABEL_NONE, 0);
|
|
|
|
PARSE_CALL(s, 11, js_parse_statement, 0);
|
|
|
|
be = VALUE_TO_SP(s->ctx, s->top_break);
|
|
emit_label(s, &be->label_break);
|
|
pop_break_entry(s);
|
|
goto done;
|
|
}
|
|
} else {
|
|
label_name = JS_NULL;
|
|
}
|
|
|
|
switch(s->token.val) {
|
|
case '{':
|
|
PARSE_CALL(s, 0, js_parse_block, 0);
|
|
break;
|
|
case TOK_RETURN:
|
|
{
|
|
BOOL has_val;
|
|
JSSourcePos op_source_pos;
|
|
if (s->is_eval)
|
|
js_parse_error(s, "return not in a function");
|
|
op_source_pos = s->token.source_pos;
|
|
next_token(s);
|
|
if (s->token.val != ';' && s->token.val != '}' && !s->got_lf) {
|
|
js_parse_expr(s);
|
|
has_val = TRUE;
|
|
} else {
|
|
has_val = FALSE;
|
|
}
|
|
emit_return(s, has_val, op_source_pos);
|
|
js_parse_expect_semi(s);
|
|
}
|
|
break;
|
|
case TOK_THROW:
|
|
{
|
|
JSSourcePos op_source_pos;
|
|
op_source_pos = s->token.source_pos;
|
|
next_token(s);
|
|
if (s->got_lf)
|
|
js_parse_error(s, "line terminator not allowed after throw");
|
|
js_parse_expr(s);
|
|
emit_op_pos(s, OP_throw, op_source_pos);
|
|
js_parse_expect_semi(s);
|
|
}
|
|
break;
|
|
case TOK_VAR:
|
|
next_token(s);
|
|
js_parse_var(s, TRUE);
|
|
js_parse_expect_semi(s);
|
|
break;
|
|
case TOK_IF:
|
|
{
|
|
JSValue label1, label2;
|
|
next_token(s);
|
|
set_eval_ret_undefined(s);
|
|
js_parse_expr_paren(s);
|
|
label1 = new_label(s);
|
|
emit_goto(s, OP_if_false, &label1);
|
|
|
|
PARSE_PUSH_VAL(s, label1);
|
|
PARSE_CALL(s, 1, js_parse_statement, 0);
|
|
PARSE_POP_VAL(s, label1);
|
|
|
|
if (s->token.val == TOK_ELSE) {
|
|
next_token(s);
|
|
|
|
label2 = new_label(s);
|
|
emit_goto(s, OP_goto, &label2);
|
|
|
|
emit_label(s, &label1);
|
|
|
|
PARSE_PUSH_VAL(s, label2);
|
|
PARSE_CALL(s, 2, js_parse_statement, 0);
|
|
PARSE_POP_VAL(s, label2);
|
|
|
|
label1 = label2;
|
|
}
|
|
emit_label(s, &label1);
|
|
}
|
|
break;
|
|
case TOK_WHILE:
|
|
{
|
|
BlockEnv *be;
|
|
|
|
be = push_break_entry(s, label_name, new_label(s), new_label(s), 0);
|
|
next_token(s);
|
|
|
|
set_eval_ret_undefined(s);
|
|
|
|
emit_label(s, &be->label_cont);
|
|
js_parse_expr_paren(s);
|
|
emit_goto(s, OP_if_false, &be->label_break);
|
|
|
|
PARSE_CALL(s, 3, js_parse_statement, 0);
|
|
|
|
be = VALUE_TO_SP(s->ctx, s->top_break);
|
|
emit_goto(s, OP_goto, &be->label_cont);
|
|
|
|
emit_label(s, &be->label_break);
|
|
|
|
pop_break_entry(s);
|
|
}
|
|
break;
|
|
case TOK_DO:
|
|
{
|
|
JSValue label1;
|
|
BlockEnv *be;
|
|
|
|
be = push_break_entry(s, label_name, new_label(s), new_label(s), 0);
|
|
|
|
label1 = new_label(s);
|
|
|
|
next_token(s);
|
|
set_eval_ret_undefined(s);
|
|
|
|
emit_label(s, &label1);
|
|
|
|
PARSE_PUSH_VAL(s, label1);
|
|
PARSE_CALL(s, 4, js_parse_statement, 0);
|
|
PARSE_POP_VAL(s, label1);
|
|
|
|
be = VALUE_TO_SP(s->ctx, s->top_break);
|
|
emit_label(s, &be->label_cont);
|
|
js_parse_expect(s, TOK_WHILE);
|
|
js_parse_expr_paren(s);
|
|
/* Insert semicolon if missing */
|
|
if (s->token.val == ';') {
|
|
next_token(s);
|
|
}
|
|
emit_goto(s, OP_if_true, &label1);
|
|
|
|
emit_label(s, &be->label_break);
|
|
|
|
pop_break_entry(s);
|
|
}
|
|
break;
|
|
case TOK_FOR:
|
|
{
|
|
int bits;
|
|
BlockEnv *be;
|
|
|
|
be = push_break_entry(s, label_name, new_label(s), new_label(s), 0);
|
|
|
|
next_token(s);
|
|
set_eval_ret_undefined(s);
|
|
|
|
js_parse_expect1(s, '(');
|
|
bits = js_parse_skip_parens_token(s);
|
|
next_token(s);
|
|
|
|
if (!(bits & SKIP_HAS_SEMI)) {
|
|
JSValue label_expr, label_body, label_next;
|
|
int opcode, var_idx;
|
|
|
|
be->drop_count = JS_NewShortInt(1);
|
|
|
|
label_expr = new_label(s);
|
|
label_body = new_label(s);
|
|
label_next = new_label(s);
|
|
|
|
emit_goto(s, OP_goto, &label_expr);
|
|
|
|
emit_label(s, &label_next);
|
|
|
|
if (s->token.val == TOK_VAR) {
|
|
JSVarRefKindEnum var_kind;
|
|
next_token(s);
|
|
var_idx = define_var(s, &var_kind, s->token.value);
|
|
put_var(s, var_kind, var_idx, s->pc2line_source_pos);
|
|
|
|
next_token(s);
|
|
} else {
|
|
JSSourcePos source_pos;
|
|
|
|
/* XXX: js_parse_left_hand_side_expr */
|
|
js_parse_assign_expr2(s, PF_NO_IN);
|
|
|
|
get_lvalue(s, &opcode, &var_idx, &source_pos, FALSE);
|
|
put_lvalue(s, opcode, var_idx, source_pos,
|
|
PUT_LVALUE_NOKEEP_BOTTOM);
|
|
}
|
|
|
|
emit_goto(s, OP_goto, &label_body);
|
|
|
|
if (s->token.val == TOK_IN) {
|
|
opcode = OP_for_in_start;
|
|
} else if (s->token.val == TOK_IDENT &&
|
|
s->token.value == js_get_atom(s->ctx, JS_ATOM_of)) {
|
|
opcode = OP_for_of_start;
|
|
} else {
|
|
js_parse_error(s, "expected 'of' or 'in' in for control expression");
|
|
}
|
|
|
|
next_token(s);
|
|
|
|
emit_label(s, &label_expr);
|
|
js_parse_expr(s);
|
|
emit_op(s, opcode);
|
|
|
|
emit_goto(s, OP_goto, &be->label_cont);
|
|
|
|
js_parse_expect(s, ')');
|
|
|
|
emit_label(s, &label_body);
|
|
|
|
PARSE_PUSH_VAL(s, label_next);
|
|
PARSE_CALL(s, 5, js_parse_statement, 0);
|
|
PARSE_POP_VAL(s, label_next);
|
|
|
|
be = VALUE_TO_SP(s->ctx, s->top_break);
|
|
emit_label(s, &be->label_cont);
|
|
emit_op(s, OP_for_of_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, &be->label_break);
|
|
emit_op(s, OP_drop);
|
|
} else {
|
|
JSValue label_test;
|
|
JSParsePos expr3_pos;
|
|
int tmp_val;
|
|
|
|
/* initial expression */
|
|
if (s->token.val != ';') {
|
|
if (s->token.val == TOK_VAR) {
|
|
next_token(s);
|
|
js_parse_var(s, FALSE);
|
|
} else {
|
|
js_parse_expr2(s, PF_NO_IN | PF_DROP);
|
|
}
|
|
}
|
|
js_parse_expect(s, ';');
|
|
|
|
label_test = new_label(s);
|
|
|
|
/* test expression */
|
|
emit_label(s, &label_test);
|
|
if (s->token.val != ';') {
|
|
js_parse_expr(s);
|
|
emit_goto(s, OP_if_false, &be->label_break);
|
|
}
|
|
js_parse_expect(s, ';');
|
|
|
|
if (s->token.val != ')') {
|
|
/* skip the third expression if present */
|
|
js_parse_get_pos(s, &expr3_pos);
|
|
js_skip_expr(s);
|
|
} else {
|
|
expr3_pos.source_pos = -1;
|
|
expr3_pos.got_lf = 0; /* avoid warning */
|
|
expr3_pos.regexp_allowed = 0; /* avoid warning */
|
|
}
|
|
js_parse_expect(s, ')');
|
|
|
|
PARSE_PUSH_VAL(s, label_test);
|
|
PARSE_PUSH_INT(s, expr3_pos.got_lf | (expr3_pos.regexp_allowed << 1));
|
|
PARSE_PUSH_INT(s, expr3_pos.source_pos);
|
|
PARSE_CALL(s, 6, js_parse_statement, 0);
|
|
PARSE_POP_INT(s, expr3_pos.source_pos);
|
|
PARSE_POP_INT(s, tmp_val);
|
|
expr3_pos.got_lf = tmp_val & 1;
|
|
expr3_pos.regexp_allowed = tmp_val >> 1;
|
|
PARSE_POP_VAL(s, label_test);
|
|
|
|
be = VALUE_TO_SP(s->ctx, s->top_break);
|
|
emit_label(s, &be->label_cont);
|
|
|
|
/* parse the third expression, if present, after the
|
|
statement */
|
|
if (expr3_pos.source_pos != -1) {
|
|
JSParsePos end_pos;
|
|
js_parse_get_pos(s, &end_pos);
|
|
js_parse_seek_token(s, &expr3_pos);
|
|
js_parse_expr2(s, PF_DROP);
|
|
js_parse_seek_token(s, &end_pos);
|
|
}
|
|
|
|
emit_goto(s, OP_goto, &label_test);
|
|
|
|
be = VALUE_TO_SP(s->ctx, s->top_break);
|
|
emit_label(s, &be->label_break);
|
|
}
|
|
pop_break_entry(s);
|
|
}
|
|
break;
|
|
case TOK_BREAK:
|
|
case TOK_CONTINUE:
|
|
{
|
|
int is_cont = (s->token.val == TOK_CONTINUE);
|
|
JSValue label_name;
|
|
|
|
next_token(s);
|
|
if (!s->got_lf && s->token.val == TOK_IDENT)
|
|
label_name = s->token.value;
|
|
else
|
|
label_name = JS_NULL;
|
|
emit_break(s, label_name, is_cont);
|
|
if (label_name != JS_NULL) {
|
|
next_token(s);
|
|
}
|
|
js_parse_expect_semi(s);
|
|
}
|
|
break;
|
|
case TOK_SWITCH:
|
|
{
|
|
JSValue label_case;
|
|
int default_label_pos;
|
|
BlockEnv *be;
|
|
|
|
be = push_break_entry(s, label_name, new_label(s), LABEL_NONE, 1);
|
|
|
|
next_token(s);
|
|
set_eval_ret_undefined(s);
|
|
|
|
js_parse_expr_paren(s);
|
|
|
|
js_parse_expect(s, '{');
|
|
default_label_pos = -1;
|
|
label_case = LABEL_NONE; /* label to the next case */
|
|
while (s->token.val != '}') {
|
|
if (s->token.val == TOK_CASE) {
|
|
JSValue label1 = LABEL_NONE;
|
|
if (!label_is_none(label_case)) {
|
|
/* skip the case if needed */
|
|
label1 = new_label(s);
|
|
emit_goto(s, OP_goto, &label1);
|
|
emit_label(s, &label_case);
|
|
label_case = LABEL_NONE;
|
|
}
|
|
for (;;) {
|
|
/* parse a sequence of case clauses */
|
|
next_token(s);
|
|
emit_op(s, OP_dup);
|
|
js_parse_expr(s);
|
|
js_parse_expect(s, ':');
|
|
emit_op(s, OP_strict_eq);
|
|
if (s->token.val == TOK_CASE) {
|
|
if (label_is_none(label1))
|
|
label1 = new_label(s);
|
|
emit_goto(s, OP_if_true, &label1);
|
|
} else {
|
|
label_case = new_label(s);
|
|
emit_goto(s, OP_if_false, &label_case);
|
|
if (!label_is_none(label1))
|
|
emit_label(s, &label1);
|
|
break;
|
|
}
|
|
}
|
|
} else if (s->token.val == TOK_DEFAULT) {
|
|
next_token(s);
|
|
js_parse_expect(s, ':');
|
|
if (default_label_pos >= 0)
|
|
js_parse_error(s, "duplicate default");
|
|
if (label_is_none(label_case)) {
|
|
/* falling thru direct from switch expression */
|
|
label_case = new_label(s);
|
|
emit_goto(s, OP_goto, &label_case);
|
|
}
|
|
default_label_pos = s->byte_code_len;
|
|
} else {
|
|
if (label_is_none(label_case))
|
|
js_parse_error(s, "invalid switch statement");
|
|
PARSE_PUSH_VAL(s, label_case);
|
|
PARSE_CALL_SAVE1(s, 7, js_parse_statement, 0,
|
|
default_label_pos);
|
|
PARSE_POP_VAL(s, label_case);
|
|
}
|
|
}
|
|
js_parse_expect(s, '}');
|
|
if (default_label_pos >= 0) {
|
|
/* patch the default label */
|
|
emit_label_pos(s, &label_case, default_label_pos);
|
|
} else if (!label_is_none(label_case)) {
|
|
emit_label(s, &label_case);
|
|
}
|
|
be = VALUE_TO_SP(s->ctx, s->top_break);
|
|
emit_label(s, &be->label_break);
|
|
emit_op(s, OP_drop); /* drop the switch expression */
|
|
|
|
pop_break_entry(s);
|
|
}
|
|
break;
|
|
case TOK_TRY:
|
|
{
|
|
JSValue label_catch, label_finally, label_end;
|
|
BlockEnv *be;
|
|
|
|
set_eval_ret_undefined(s);
|
|
next_token(s);
|
|
label_catch = new_label(s);
|
|
label_finally = new_label(s);
|
|
|
|
emit_goto(s, OP_catch, &label_catch);
|
|
|
|
be = push_break_entry(s, JS_NULL, LABEL_NONE, LABEL_NONE, 1);
|
|
be->label_finally = label_finally;
|
|
|
|
PARSE_PUSH_VAL(s, label_catch);
|
|
PARSE_CALL(s, 8, js_parse_block, 0);
|
|
PARSE_POP_VAL(s, label_catch);
|
|
|
|
be = VALUE_TO_SP(s->ctx, s->top_break);
|
|
label_finally = be->label_finally;
|
|
pop_break_entry(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);
|
|
|
|
label_end = new_label(s);
|
|
emit_goto(s, OP_goto, &label_end);
|
|
|
|
if (s->token.val == TOK_CATCH) {
|
|
JSValue label_catch2;
|
|
int var_idx;
|
|
JSValue name;
|
|
|
|
label_catch2 = new_label(s);
|
|
|
|
next_token(s);
|
|
js_parse_expect(s, '(');
|
|
if (s->token.val != TOK_IDENT)
|
|
js_parse_error(s, "identifier expected");
|
|
name = s->token.value;
|
|
/* XXX: the local scope is not implemented, so we add
|
|
a normal variable */
|
|
if (find_var(s, name) >= 0 || find_ext_var(s, name) >= 0) {
|
|
js_parse_error(s, "catch variable already exists");
|
|
}
|
|
var_idx = add_var(s, name);
|
|
next_token(s);
|
|
js_parse_expect(s, ')');
|
|
|
|
/* store the exception value in the variable */
|
|
emit_label(s, &label_catch);
|
|
{
|
|
JSFunctionBytecode *b = JS_VALUE_TO_PTR(s->cur_func);
|
|
emit_var(s, OP_put_loc, var_idx - b->arg_count, s->pc2line_source_pos);
|
|
}
|
|
|
|
emit_goto(s, OP_catch, &label_catch2);
|
|
|
|
be = push_break_entry(s, JS_NULL, LABEL_NONE, LABEL_NONE, 1);
|
|
be->label_finally = label_finally;
|
|
|
|
PARSE_PUSH_VAL(s, label_end);
|
|
PARSE_PUSH_VAL(s, label_catch2);
|
|
PARSE_CALL(s, 9, js_parse_block, 0);
|
|
PARSE_POP_VAL(s, label_catch2);
|
|
PARSE_POP_VAL(s, label_end);
|
|
|
|
be = VALUE_TO_SP(s->ctx, s->top_break);
|
|
label_finally = be->label_finally;
|
|
pop_break_entry(s);
|
|
|
|
/* drop the catch2 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);
|
|
|
|
/* 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");
|
|
}
|
|
|
|
emit_label(s, &label_finally);
|
|
if (s->token.val == TOK_FINALLY) {
|
|
next_token(s);
|
|
/* XXX: we don't return the correct value in eval() */
|
|
/* on the stack: ret_value gosub_ret_value */
|
|
push_break_entry(s, JS_NULL, LABEL_NONE, LABEL_NONE, 2);
|
|
|
|
PARSE_PUSH_VAL(s, label_end);
|
|
PARSE_CALL(s, 10, js_parse_block, 0);
|
|
PARSE_POP_VAL(s, label_end);
|
|
|
|
pop_break_entry(s);
|
|
}
|
|
emit_op(s, OP_ret);
|
|
emit_label(s, &label_end);
|
|
}
|
|
break;
|
|
case ';':
|
|
/* empty statement */
|
|
next_token(s);
|
|
break;
|
|
default:
|
|
if (s->eval_ret_idx >= 0) {
|
|
/* store the expression value so that it can be returned
|
|
by eval() */
|
|
js_parse_expr(s);
|
|
emit_var(s, OP_put_loc, s->eval_ret_idx, s->pc2line_source_pos);
|
|
} else {
|
|
js_parse_expr2(s, PF_DROP);
|
|
}
|
|
js_parse_expect_semi(s);
|
|
break;
|
|
}
|
|
done:
|
|
return PARSE_STATE_RET;
|
|
}
|
|
|
|
static JSParseFunc *parse_func_table[] = {
|
|
js_parse_expr_comma,
|
|
js_parse_assign_expr,
|
|
js_parse_cond_expr,
|
|
js_parse_logical_and_or,
|
|
js_parse_expr_binary,
|
|
js_parse_unary,
|
|
js_parse_postfix_expr,
|
|
js_parse_statement,
|
|
js_parse_block,
|
|
js_parse_json_value,
|
|
re_parse_alternative,
|
|
re_parse_disjunction,
|
|
};
|
|
|
|
static void js_parse_source_element(JSParseState *s)
|
|
{
|
|
if (s->token.val == TOK_FUNCTION) {
|
|
js_parse_function_decl(s, JS_PARSE_FUNC_STATEMENT, JS_NULL);
|
|
} else {
|
|
js_parse_call(s, PARSE_FUNC_js_parse_statement, 0);
|
|
}
|
|
}
|
|
|
|
static JSFunctionBytecode *js_alloc_function_bytecode(JSContext *ctx)
|
|
{
|
|
JSFunctionBytecode *b;
|
|
b = js_mallocz(ctx, sizeof(JSFunctionBytecode), JS_MTAG_FUNCTION_BYTECODE);
|
|
if (!b)
|
|
return NULL;
|
|
b->func_name = JS_NULL;
|
|
b->byte_code = JS_NULL;
|
|
b->cpool = JS_NULL;
|
|
b->vars = JS_NULL;
|
|
b->ext_vars = JS_NULL;
|
|
b->filename = JS_NULL;
|
|
b->pc2line = JS_NULL;
|
|
return b;
|
|
}
|
|
|
|
/* the current token must be TOK_FUNCTION for JS_PARSE_FUNC_STATEMENT
|
|
or JS_PARSE_FUNC_EXPR. Otherwise it is '('. */
|
|
static void js_parse_function_decl(JSParseState *s,
|
|
JSParseFunctionEnum func_type, JSValue func_name)
|
|
{
|
|
JSContext *ctx = s->ctx;
|
|
BOOL is_expr;
|
|
JSFunctionBytecode *b;
|
|
int idx, skip_bits;
|
|
JSVarRefKindEnum var_kind;
|
|
JSValue bfunc;
|
|
JSGCRef func_name_ref, bfunc_ref;
|
|
|
|
is_expr = (func_type != JS_PARSE_FUNC_STATEMENT);
|
|
|
|
if (func_type == JS_PARSE_FUNC_STATEMENT ||
|
|
func_type == JS_PARSE_FUNC_EXPR) {
|
|
next_token(s);
|
|
if (s->token.val != TOK_IDENT && !is_expr)
|
|
js_parse_error(s, "function name expected");
|
|
if (s->token.val == TOK_IDENT) {
|
|
func_name = s->token.value;
|
|
JS_PUSH_VALUE(ctx, func_name);
|
|
next_token(s);
|
|
JS_POP_VALUE(ctx, func_name);
|
|
}
|
|
}
|
|
|
|
JS_PUSH_VALUE(ctx, func_name);
|
|
b = js_alloc_function_bytecode(s->ctx);
|
|
if (!b)
|
|
js_parse_error_mem(s);
|
|
bfunc = JS_VALUE_FROM_PTR(b);
|
|
JS_PUSH_VALUE(ctx, bfunc);
|
|
|
|
b->filename = s->filename_str;
|
|
b->func_name = func_name_ref.val;
|
|
b->source_pos = s->token.source_pos;
|
|
b->has_column = s->has_column;
|
|
|
|
js_parse_expect1(s, '(');
|
|
/* skip the arguments */
|
|
js_skip_parens(s, NULL);
|
|
|
|
js_parse_expect1(s, '{');
|
|
|
|
/* skip the code */
|
|
skip_bits = js_skip_parens(s, is_expr ? &func_name_ref.val : NULL);
|
|
|
|
b = JS_VALUE_TO_PTR(bfunc_ref.val);
|
|
b->has_arguments = ((skip_bits & SKIP_HAS_ARGUMENTS) != 0);
|
|
b->has_local_func_name = ((skip_bits & SKIP_HAS_FUNC_NAME) != 0);
|
|
|
|
idx = cpool_add(s, bfunc_ref.val);
|
|
if (is_expr) {
|
|
/* create the function object */
|
|
emit_op(s, OP_fclosure);
|
|
emit_u16(s, idx);
|
|
} else {
|
|
idx = define_var(s, &var_kind, func_name_ref.val);
|
|
/* size of hoisted for OP_fclosure + OP_put_loc/OP_put_arg/OP_put_ref */
|
|
s->hoisted_code_len += 3 + 3;
|
|
if (var_kind == JS_VARREF_KIND_VAR) {
|
|
b = JS_VALUE_TO_PTR(s->cur_func);
|
|
idx += b->arg_count;
|
|
}
|
|
b = JS_VALUE_TO_PTR(bfunc_ref.val);
|
|
/* hoisted function definition: save the variable index to
|
|
define it at the start of the function */
|
|
b->arg_count = idx + 1;
|
|
}
|
|
JS_POP_VALUE(ctx, bfunc);
|
|
JS_POP_VALUE(ctx, func_name);
|
|
}
|
|
|
|
static void define_hoisted_functions(JSParseState *s, BOOL is_eval)
|
|
{
|
|
JSValueArray *cpool;
|
|
JSValue val;
|
|
JSFunctionBytecode *b;
|
|
int idx, saved_byte_code_len, arg_count, i, op;
|
|
|
|
/* add pc2line info */
|
|
b = JS_VALUE_TO_PTR(s->cur_func);
|
|
if (b->pc2line != JS_NULL) {
|
|
int h, n;
|
|
|
|
/* byte align */
|
|
n = (-s->pc2line_bit_len) & 7;
|
|
if (n != 0)
|
|
pc2line_put_bits(s, n, 0);
|
|
|
|
n = s->hoisted_code_len;
|
|
h = 0;
|
|
for(;;) {
|
|
pc2line_put_bits(s, 8, (n & 0x7f) | h);
|
|
n >>= 7;
|
|
if (n == 0)
|
|
break;
|
|
h |= 0x80;
|
|
}
|
|
}
|
|
|
|
if (s->hoisted_code_len == 0)
|
|
return;
|
|
emit_insert(s, 0, s->hoisted_code_len);
|
|
|
|
b = JS_VALUE_TO_PTR(s->cur_func);
|
|
arg_count = b->arg_count;
|
|
|
|
saved_byte_code_len = s->byte_code_len;
|
|
s->byte_code_len = 0;
|
|
cpool = JS_VALUE_TO_PTR(b->cpool);
|
|
for(i = 0; i < s->cpool_len; i++) {
|
|
val = cpool->arr[i];
|
|
if (JS_IsPtr(val)) {
|
|
b = JS_VALUE_TO_PTR(val);
|
|
if (b->mtag == JS_MTAG_FUNCTION_BYTECODE &&
|
|
b->arg_count != 0) {
|
|
idx = b->arg_count - 1;
|
|
/* XXX: could use smaller opcodes */
|
|
if (is_eval) {
|
|
op = OP_put_var_ref_nocheck;
|
|
} else if (idx < arg_count) {
|
|
op = OP_put_arg;
|
|
} else {
|
|
idx -= arg_count;
|
|
op = OP_put_loc;
|
|
}
|
|
/* no realloc possible here */
|
|
emit_u8(s, OP_fclosure);
|
|
emit_u16(s, i);
|
|
|
|
emit_u8(s, op);
|
|
emit_u16(s, idx);
|
|
}
|
|
}
|
|
}
|
|
s->byte_code_len = saved_byte_code_len;
|
|
}
|
|
|
|
static void js_parse_function(JSParseState *s)
|
|
{
|
|
JSFunctionBytecode *b;
|
|
int arg_count;
|
|
|
|
next_token(s);
|
|
|
|
js_parse_expect(s, '(');
|
|
|
|
while (s->token.val != ')') {
|
|
JSValue name;
|
|
/* XXX: gc */
|
|
if (s->token.val != TOK_IDENT)
|
|
js_parse_error(s, "missing formal parameter");
|
|
name = s->token.value;
|
|
if (name == js_get_atom(s->ctx, JS_ATOM_eval) ||
|
|
name == js_get_atom(s->ctx, JS_ATOM_arguments)) {
|
|
js_parse_error(s, "invalid argument name");
|
|
}
|
|
if (find_var(s, name) >= 0)
|
|
js_parse_error(s, "duplicate argument name");
|
|
add_var(s, name);
|
|
next_token(s);
|
|
if (s->token.val == ')')
|
|
break;
|
|
js_parse_expect(s, ',');
|
|
}
|
|
b = JS_VALUE_TO_PTR(s->cur_func);
|
|
arg_count = b->arg_count = s->local_vars_len;
|
|
|
|
next_token(s);
|
|
|
|
js_parse_expect(s, '{');
|
|
|
|
/* initialize the arguments */
|
|
b = JS_VALUE_TO_PTR(s->cur_func);
|
|
if (b->has_arguments) {
|
|
int var_idx;
|
|
var_idx = add_var(s, js_get_atom(s->ctx, JS_ATOM_arguments));
|
|
emit_op(s, OP_arguments);
|
|
put_var(s, JS_VARREF_KIND_VAR, var_idx - arg_count, s->pc2line_source_pos);
|
|
}
|
|
|
|
/* XXX: initialize the function name */
|
|
b = JS_VALUE_TO_PTR(s->cur_func);
|
|
if (b->has_local_func_name) {
|
|
int var_idx;
|
|
/* XXX: */
|
|
var_idx = add_var(s, b->func_name);
|
|
emit_op(s, OP_this_func);
|
|
put_var(s, JS_VARREF_KIND_VAR, var_idx - arg_count, s->pc2line_source_pos);
|
|
}
|
|
|
|
while (s->token.val != '}') {
|
|
js_parse_source_element(s);
|
|
}
|
|
|
|
if (js_is_live_code(s))
|
|
emit_op(s, OP_return_undef);
|
|
|
|
next_token(s);
|
|
|
|
define_hoisted_functions(s, FALSE);
|
|
|
|
/* save the bytecode to the function */
|
|
b = JS_VALUE_TO_PTR(s->cur_func);
|
|
b->byte_code = s->byte_code;
|
|
}
|
|
|
|
static void js_parse_program(JSParseState *s)
|
|
{
|
|
JSFunctionBytecode *b;
|
|
|
|
next_token(s);
|
|
|
|
/* hidden variable for the return value */
|
|
if (s->has_retval) {
|
|
s->eval_ret_idx = add_var(s, js_get_atom(s->ctx, JS_ATOM__ret_));
|
|
}
|
|
|
|
while (s->token.val != TOK_EOF) {
|
|
js_parse_source_element(s);
|
|
}
|
|
|
|
if (s->eval_ret_idx >= 0) {
|
|
emit_var(s, OP_get_loc, s->eval_ret_idx, s->pc2line_source_pos);
|
|
emit_op(s, OP_return);
|
|
} else {
|
|
emit_op(s, OP_return_undef);
|
|
}
|
|
|
|
define_hoisted_functions(s, TRUE);
|
|
|
|
/* save the bytecode to the function */
|
|
b = JS_VALUE_TO_PTR(s->cur_func);
|
|
b->byte_code = s->byte_code;
|
|
}
|
|
|
|
#define CVT_VAR_SIZE_MAX 16
|
|
|
|
typedef struct {
|
|
uint16_t new_var_idx; /* new local var index */
|
|
uint8_t is_local;
|
|
} ConvertVarEntry;
|
|
|
|
static void convert_ext_vars_to_local_vars_bytecode(JSParseState *s,
|
|
uint8_t *byte_code, int byte_code_len,
|
|
int var_start, const ConvertVarEntry *cvt_tab,
|
|
int tab_len)
|
|
{
|
|
int pos, var_end, j, op, var_idx;
|
|
const JSOpCode *oi;
|
|
|
|
var_end = var_start + tab_len;
|
|
pos = 0;
|
|
while (pos < byte_code_len) {
|
|
op = byte_code[pos];
|
|
oi = &opcode_info[op];
|
|
switch(op) {
|
|
case OP_get_var_ref:
|
|
case OP_put_var_ref:
|
|
case OP_get_var_ref_nocheck:
|
|
case OP_put_var_ref_nocheck:
|
|
var_idx = get_u16(byte_code + pos + 1);
|
|
if (var_idx >= var_start && var_idx < var_end) {
|
|
j = var_idx - var_start;
|
|
put_u16(byte_code + pos + 1, cvt_tab[j].new_var_idx);
|
|
if (cvt_tab[j].is_local) {
|
|
if (op == OP_get_var_ref || op == OP_get_var_ref_nocheck) {
|
|
byte_code[pos] = OP_get_loc;
|
|
} else {
|
|
byte_code[pos] = OP_put_loc;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
pos += oi->size;
|
|
}
|
|
}
|
|
|
|
/* no allocation */
|
|
static void convert_ext_vars_to_local_vars(JSParseState *s)
|
|
{
|
|
JSValueArray *ext_vars;
|
|
JSFunctionBytecode *b;
|
|
JSByteArray *bc_arr;
|
|
JSValue var_name, decl;
|
|
int i0, i, j, var_idx, l;
|
|
ConvertVarEntry cvt_tab[CVT_VAR_SIZE_MAX];
|
|
|
|
b = JS_VALUE_TO_PTR(s->cur_func);
|
|
if (s->local_vars_len == 0 || b->ext_vars_len == 0)
|
|
return;
|
|
bc_arr = JS_VALUE_TO_PTR(b->byte_code);
|
|
ext_vars = JS_VALUE_TO_PTR(b->ext_vars);
|
|
|
|
/* do it by parts to save memory */
|
|
j = 0;
|
|
for(i0 = 0; i0 < b->ext_vars_len; i0 += CVT_VAR_SIZE_MAX) {
|
|
l = min_int(b->ext_vars_len - i0, CVT_VAR_SIZE_MAX);
|
|
for(i = 0; i < l; i++) {
|
|
var_name = ext_vars->arr[2 * (i0 + i)];
|
|
decl = ext_vars->arr[2 * (i0 + i) + 1];
|
|
var_idx = find_var(s, var_name);
|
|
/* fail safe: we avoid arguments even if they cannot appear */
|
|
if (var_idx >= b->arg_count) {
|
|
cvt_tab[i].new_var_idx = var_idx - b->arg_count;
|
|
cvt_tab[i].is_local = TRUE;
|
|
} else {
|
|
cvt_tab[i].new_var_idx = j;
|
|
cvt_tab[i].is_local = FALSE;
|
|
ext_vars->arr[2 * j] = var_name;
|
|
ext_vars->arr[2 * j + 1] = decl;
|
|
j++;
|
|
}
|
|
}
|
|
if (j != (i0 + l)) {
|
|
convert_ext_vars_to_local_vars_bytecode(s, bc_arr->buf, s->byte_code_len,
|
|
i0, cvt_tab, l);
|
|
}
|
|
}
|
|
b->ext_vars_len = j;
|
|
}
|
|
|
|
/* prepare the analysis of the code starting at position 'pos' */
|
|
static void compute_stack_size_push(JSParseState *s,
|
|
JSByteArray *arr,
|
|
uint8_t *explore_tab,
|
|
uint32_t pos, int stack_len)
|
|
{
|
|
int short_stack_len;
|
|
|
|
#if 0
|
|
js_printf(s->ctx, "%5d: %d\n", pos, stack_len);
|
|
#endif
|
|
if (pos >= (uint32_t)arr->size)
|
|
js_parse_error(s, "bytecode buffer overflow (pc=%d)", pos);
|
|
/* XXX: could avoid the division */
|
|
short_stack_len = 1 + ((unsigned)stack_len % 255);
|
|
if (explore_tab[pos] != 0) {
|
|
/* already explored: check that the stack size is consistent */
|
|
if (explore_tab[pos] != short_stack_len) {
|
|
js_parse_error(s, "inconsistent stack size: %d %d (pc=%d)", explore_tab[pos] - 1, short_stack_len - 1, (int)pos);
|
|
}
|
|
} else {
|
|
explore_tab[pos] = short_stack_len;
|
|
/* may initiate a GC */
|
|
PARSE_PUSH_INT(s, pos);
|
|
PARSE_PUSH_INT(s, stack_len);
|
|
}
|
|
}
|
|
|
|
static void compute_stack_size(JSParseState *s, JSValue *pfunc)
|
|
{
|
|
JSContext *ctx = s->ctx;
|
|
JSByteArray *explore_arr, *arr;
|
|
JSFunctionBytecode *b;
|
|
uint8_t *explore_tab;
|
|
JSValue *stack_top, explore_arr_val;
|
|
uint32_t pos;
|
|
int op, op_len, pos1, n_pop, stack_len;
|
|
const JSOpCode *oi;
|
|
JSGCRef explore_arr_val_ref;
|
|
|
|
b = JS_VALUE_TO_PTR(*pfunc);
|
|
arr = JS_VALUE_TO_PTR(b->byte_code);
|
|
|
|
explore_arr = js_alloc_byte_array(s->ctx, arr->size);
|
|
if (!explore_arr)
|
|
js_parse_error_mem(s);
|
|
|
|
b = JS_VALUE_TO_PTR(*pfunc);
|
|
arr = JS_VALUE_TO_PTR(b->byte_code);
|
|
|
|
explore_arr_val = JS_VALUE_FROM_PTR(explore_arr);
|
|
explore_tab = explore_arr->buf;
|
|
memset(explore_tab, 0, arr->size);
|
|
|
|
JS_PUSH_VALUE(ctx, explore_arr_val);
|
|
|
|
stack_top = ctx->sp;
|
|
|
|
compute_stack_size_push(s, arr, explore_tab, 0, 0);
|
|
|
|
while (ctx->sp < stack_top) {
|
|
PARSE_POP_INT(s, stack_len);
|
|
PARSE_POP_INT(s, pos);
|
|
|
|
/* compute_stack_size_push may have initiated a GC */
|
|
b = JS_VALUE_TO_PTR(*pfunc);
|
|
arr = JS_VALUE_TO_PTR(b->byte_code);
|
|
explore_arr = JS_VALUE_TO_PTR(explore_arr_val_ref.val);
|
|
explore_tab = explore_arr->buf;
|
|
|
|
op = arr->buf[pos++];
|
|
if (op == OP_invalid || op >= OP_COUNT)
|
|
js_parse_error(s, "invalid opcode (pc=%d)", (int)(pos - 1));
|
|
oi = &opcode_info[op];
|
|
op_len = oi->size;
|
|
if ((pos + op_len - 1) > arr->size) {
|
|
js_parse_error(s, "bytecode buffer overflow (pc=%d)", (int)(pos - 1));
|
|
}
|
|
n_pop = oi->n_pop;
|
|
if (oi->fmt == OP_FMT_npop)
|
|
n_pop += get_u16(arr->buf + pos);
|
|
|
|
if (stack_len < n_pop) {
|
|
js_parse_error(s, "stack underflow (pc=%d)", (int)(pos - 1));
|
|
}
|
|
stack_len += oi->n_push - n_pop;
|
|
if (stack_len > b->stack_size) {
|
|
if (stack_len > JS_MAX_FUNC_STACK_SIZE)
|
|
js_parse_error(s, "stack overflow (pc=%d)", (int)(pos - 1));
|
|
b->stack_size = stack_len;
|
|
}
|
|
switch(op) {
|
|
case OP_return:
|
|
case OP_return_undef:
|
|
case OP_throw:
|
|
case OP_ret:
|
|
goto done; /* no code after */
|
|
case OP_goto:
|
|
pos += get_u32(arr->buf + pos);
|
|
break;
|
|
case OP_if_true:
|
|
case OP_if_false:
|
|
pos1 = pos + get_u32(arr->buf + pos);
|
|
compute_stack_size_push(s, arr, explore_tab, pos1, stack_len);
|
|
pos += op_len - 1;
|
|
break;
|
|
case OP_gosub:
|
|
pos1 = pos + get_u32(arr->buf + pos);
|
|
compute_stack_size_push(s, arr, explore_tab, pos1, stack_len + 1);
|
|
pos += op_len - 1;
|
|
break;
|
|
default:
|
|
pos += op_len - 1;
|
|
break;
|
|
}
|
|
compute_stack_size_push(s, arr, explore_tab, pos, stack_len);
|
|
done: ;
|
|
}
|
|
|
|
JS_POP_VALUE(ctx, explore_arr_val);
|
|
explore_arr = JS_VALUE_TO_PTR(explore_arr_val);
|
|
js_free(s->ctx, explore_arr);
|
|
}
|
|
|
|
static void resolve_var_refs(JSParseState *s, JSValue *pfunc, JSValue *pparent_func)
|
|
{
|
|
JSContext *ctx = s->ctx;
|
|
int i, decl, var_idx, arg_count, ext_vars_len;
|
|
JSValueArray *ext_vars;
|
|
JSValue var_name;
|
|
JSFunctionBytecode *b1, *b;
|
|
|
|
b = JS_VALUE_TO_PTR(*pfunc);
|
|
if (b->ext_vars_len == 0)
|
|
return;
|
|
b1 = JS_VALUE_TO_PTR(*pparent_func);
|
|
arg_count = b1->arg_count;
|
|
|
|
ext_vars = JS_VALUE_TO_PTR(b->ext_vars);
|
|
ext_vars_len = b->ext_vars_len;
|
|
|
|
for(i = 0; i < ext_vars_len; i++) {
|
|
b = JS_VALUE_TO_PTR(*pfunc);
|
|
ext_vars = JS_VALUE_TO_PTR(b->ext_vars);
|
|
var_name = ext_vars->arr[2 * i];
|
|
var_idx = find_func_var(ctx, *pparent_func, var_name);
|
|
if (var_idx >= 0) {
|
|
if (var_idx < arg_count) {
|
|
decl = (JS_VARREF_KIND_ARG << 16) | var_idx;
|
|
} else {
|
|
decl = (JS_VARREF_KIND_VAR << 16) | (var_idx - arg_count);
|
|
}
|
|
} else {
|
|
var_idx = find_func_ext_var(s, *pparent_func, var_name);
|
|
if (var_idx < 0) {
|
|
/* the global type may be patched later */
|
|
var_idx = add_func_ext_var(s, *pparent_func, var_name,
|
|
(JS_VARREF_KIND_GLOBAL << 16));
|
|
}
|
|
decl = (JS_VARREF_KIND_VAR_REF << 16) | var_idx;
|
|
}
|
|
b = JS_VALUE_TO_PTR(*pfunc);
|
|
ext_vars = JS_VALUE_TO_PTR(b->ext_vars);
|
|
ext_vars->arr[2 * i + 1] = JS_NewShortInt(decl);
|
|
}
|
|
}
|
|
|
|
static void reset_parse_state(JSParseState *s, uint32_t input_pos,
|
|
JSValue cur_func)
|
|
{
|
|
s->buf_pos = input_pos;
|
|
s->token.val = ' ';
|
|
|
|
s->cur_func = cur_func;
|
|
s->byte_code = JS_NULL;
|
|
s->byte_code_len = 0;
|
|
s->last_opcode_pos = -1;
|
|
|
|
s->pc2line_bit_len = 0;
|
|
s->pc2line_source_pos = 0;
|
|
|
|
s->cpool_len = 0;
|
|
s->hoisted_code_len = 0;
|
|
|
|
s->local_vars_len = 0;
|
|
|
|
s->eval_ret_idx = -1;
|
|
}
|
|
|
|
static void js_parse_local_functions(JSParseState *s, JSValue *pfunc)
|
|
{
|
|
JSContext *ctx = s->ctx;
|
|
JSValue *pparent_func;
|
|
JSValueArray *cpool;
|
|
int err, cpool_pos;
|
|
JSValue func;
|
|
JSFunctionBytecode *b, *b1;
|
|
JSGCRef func_ref;
|
|
JSValue *stack_top;
|
|
|
|
err = JS_StackCheck(ctx, 3);
|
|
if (err)
|
|
js_parse_error_stack_overflow(s);
|
|
stack_top = ctx->sp;
|
|
|
|
*--ctx->sp = JS_NULL; /* parent_func */
|
|
*--ctx->sp = *pfunc; /* func */
|
|
*--ctx->sp = JS_NewShortInt(0); /* cpool_pos */
|
|
|
|
while (ctx->sp < stack_top) {
|
|
pparent_func = &ctx->sp[2];
|
|
pfunc = &ctx->sp[1];
|
|
cpool_pos = JS_VALUE_GET_INT(ctx->sp[0]);
|
|
#if 0
|
|
JS_DumpValue(ctx, "func", *pfunc);
|
|
JS_DumpValue(ctx, "parent", *pparent_func);
|
|
JS_DumpValue(ctx, "cpool_pos", ctx->sp[0]);
|
|
#endif
|
|
if (cpool_pos == 0) {
|
|
b = JS_VALUE_TO_PTR(*pfunc);
|
|
|
|
convert_ext_vars_to_local_vars(s);
|
|
|
|
js_shrink_byte_array(ctx, &b->byte_code, s->byte_code_len);
|
|
js_shrink_value_array(ctx, &b->cpool, s->cpool_len);
|
|
js_shrink_value_array(ctx, &b->vars, s->local_vars_len);
|
|
js_shrink_byte_array(ctx, &b->pc2line, (s->pc2line_bit_len + 7) / 8);
|
|
|
|
compute_stack_size(s, pfunc);
|
|
}
|
|
|
|
b = JS_VALUE_TO_PTR(*pfunc);
|
|
if (b->cpool != JS_NULL) {
|
|
int cpool_size;
|
|
cpool = JS_VALUE_TO_PTR(b->cpool);
|
|
cpool_size = cpool->size;
|
|
for(; cpool_pos < cpool_size; cpool_pos++) {
|
|
b = JS_VALUE_TO_PTR(*pfunc);
|
|
cpool = JS_VALUE_TO_PTR(b->cpool);
|
|
func = cpool->arr[cpool_pos];
|
|
if (!JS_IsPtr(func))
|
|
continue;
|
|
b1 = JS_VALUE_TO_PTR(func);
|
|
if (b1->mtag != JS_MTAG_FUNCTION_BYTECODE)
|
|
continue;
|
|
|
|
reset_parse_state(s, b1->source_pos, func);
|
|
|
|
s->is_eval = FALSE;
|
|
s->is_repl = FALSE;
|
|
s->has_retval = FALSE;
|
|
|
|
JS_PUSH_VALUE(ctx, func);
|
|
js_parse_function(s);
|
|
|
|
/* parse a local function */
|
|
err = JS_StackCheck(ctx, 3);
|
|
JS_POP_VALUE(ctx, func);
|
|
if (err)
|
|
js_parse_error_stack_overflow(s);
|
|
/* set the next cpool position */
|
|
*ctx->sp = JS_NewShortInt(cpool_pos + 1);
|
|
|
|
*--ctx->sp = *pfunc; /* parent_func */
|
|
*--ctx->sp = func; /* func */
|
|
*--ctx->sp = JS_NewShortInt(0); /* cpool_pos */
|
|
goto next;
|
|
}
|
|
}
|
|
|
|
if (*pparent_func != JS_NULL) {
|
|
resolve_var_refs(s, pfunc, pparent_func);
|
|
}
|
|
/* now we can shrink the external vars */
|
|
b = JS_VALUE_TO_PTR(*pfunc);
|
|
js_shrink_value_array(ctx, &b->ext_vars, 2 * b->ext_vars_len);
|
|
#ifdef DUMP_FUNC_BYTECODE
|
|
dump_byte_code(ctx, b);
|
|
#endif
|
|
/* remove the stack entry */
|
|
ctx->sp += 3;
|
|
ctx->stack_bottom = ctx->sp;
|
|
next: ;
|
|
}
|
|
}
|
|
|
|
/* return the parsed value in s->token.value */
|
|
/* XXX: use exact JSON white space definition */
|
|
static int js_parse_json_value(JSParseState *s, int state, int dummy_param)
|
|
{
|
|
JSContext *ctx = s->ctx;
|
|
const uint8_t *p;
|
|
JSValue val;
|
|
|
|
PARSE_START2();
|
|
|
|
p = s->source_buf + s->buf_pos;
|
|
p += skip_spaces((const char *)p);
|
|
s->buf_pos = p - s->source_buf;
|
|
if ((*p >= '0' && *p <= '9') || *p == '-') {
|
|
double d;
|
|
JSByteArray *tmp_arr;
|
|
tmp_arr = js_alloc_byte_array(s->ctx, sizeof(JSATODTempMem));
|
|
if (!tmp_arr)
|
|
js_parse_error_mem(s);
|
|
p = s->source_buf + s->buf_pos;
|
|
d = js_atod((const char *)p, (const char **)&p, 10, 0,
|
|
(JSATODTempMem *)tmp_arr->buf);
|
|
js_free(s->ctx, tmp_arr);
|
|
if (isnan(d))
|
|
js_parse_error(s, "invalid number literal");
|
|
val = JS_NewFloat64(s->ctx, d);
|
|
} else if (*p == 't' &&
|
|
p[1] == 'r' && p[2] == 'u' && p[3] == 'e') {
|
|
p += 4;
|
|
val = JS_TRUE;
|
|
} else if (*p == 'f' &&
|
|
p[1] == 'a' && p[2] == 'l' && p[3] == 's' && p[4] == 'e') {
|
|
p += 5;
|
|
val = JS_FALSE;
|
|
} else if (*p == 'n' &&
|
|
p[1] == 'u' && p[2] == 'l' && p[3] == 'l') {
|
|
p += 4;
|
|
val = JS_NULL;
|
|
} else if (*p == '\"') {
|
|
uint32_t pos;
|
|
pos = p + 1 - s->source_buf;
|
|
val = js_parse_string(s, &pos, '\"');
|
|
p = s->source_buf + pos;
|
|
} else if (*p == '[') {
|
|
JSValue val2;
|
|
uint32_t idx;
|
|
|
|
val = JS_NewArray(ctx, 0);
|
|
if (JS_IsException(val))
|
|
js_parse_error_mem(s);
|
|
PARSE_PUSH_VAL(s, val); /* 'val' is not usable after this call */
|
|
p = s->source_buf + s->buf_pos + 1;
|
|
p += skip_spaces((const char *)p);
|
|
if (*p != ']') {
|
|
idx = 0;
|
|
for(;;) {
|
|
s->buf_pos = p - s->source_buf;
|
|
PARSE_PUSH_INT(s, idx);
|
|
PARSE_CALL(s, 0, js_parse_json_value, 0);
|
|
PARSE_POP_INT(s, idx);
|
|
val2 = s->token.value;
|
|
val2 = JS_SetPropertyUint32(ctx, *ctx->sp, idx, val2);
|
|
if (JS_IsException(val2))
|
|
js_parse_error_mem(s);
|
|
idx++;
|
|
p = s->source_buf + s->buf_pos;
|
|
p += skip_spaces((const char *)p);
|
|
if (*p != ',')
|
|
break;
|
|
p++;
|
|
}
|
|
}
|
|
if (*p != ']')
|
|
js_parse_error(s, "expecting ']'");
|
|
p++;
|
|
PARSE_POP_VAL(s, val);
|
|
} else if (*p == '{') {
|
|
JSValue val2, prop;
|
|
uint32_t pos;
|
|
|
|
val = JS_NewObject(ctx);
|
|
if (JS_IsException(val))
|
|
js_parse_error_mem(s);
|
|
PARSE_PUSH_VAL(s, val); /* 'val' is not usable after this call */
|
|
p = s->source_buf + s->buf_pos + 1;
|
|
p += skip_spaces((const char *)p);
|
|
if (*p != '}') {
|
|
for(;;) {
|
|
p += skip_spaces((const char *)p);
|
|
s->buf_pos = p - s->source_buf;
|
|
if (*p != '\"')
|
|
js_parse_error(s, "expecting '\"'");
|
|
pos = p + 1 - s->source_buf;
|
|
prop = js_parse_string(s, &pos, '\"');
|
|
prop = JS_ToPropertyKey(ctx, prop);
|
|
if (JS_IsException(prop))
|
|
js_parse_error_mem(s);
|
|
p = s->source_buf + pos;
|
|
p += skip_spaces((const char *)p);
|
|
if (*p != ':')
|
|
js_parse_error(s, "expecting ':'");
|
|
p++;
|
|
s->buf_pos = p - s->source_buf;
|
|
PARSE_PUSH_VAL(s, prop);
|
|
PARSE_CALL(s, 1, js_parse_json_value, 0);
|
|
val2 = s->token.value;
|
|
PARSE_POP_VAL(s, prop);
|
|
val2 = JS_DefinePropertyValue(ctx, *ctx->sp, prop, val2);
|
|
if (JS_IsException(val2))
|
|
js_parse_error_mem(s);
|
|
p = s->source_buf + s->buf_pos;
|
|
p += skip_spaces((const char *)p);
|
|
if (*p != ',')
|
|
break;
|
|
p++;
|
|
}
|
|
}
|
|
if (*p != '}')
|
|
js_parse_error(s, "expecting '}'");
|
|
p++;
|
|
PARSE_POP_VAL(s, val);
|
|
} else {
|
|
js_parse_error(s, "unexpected character");
|
|
}
|
|
s->buf_pos = p - s->source_buf;
|
|
s->token.value = val;
|
|
return PARSE_STATE_RET;
|
|
}
|
|
|
|
static JSValue js_parse_json(JSParseState *s)
|
|
{
|
|
s->buf_pos = 0;
|
|
js_parse_call(s, PARSE_FUNC_js_parse_json_value, 0);
|
|
s->buf_pos += skip_spaces((const char *)(s->source_buf + s->buf_pos));
|
|
if (s->buf_pos != s->buf_len) {
|
|
js_parse_error(s, "unexpected character");
|
|
}
|
|
return s->token.value;
|
|
}
|
|
|
|
/* source_str must be a string or JS_NULL. (input, input_len) is
|
|
meaningful only if source_str is JS_NULL. */
|
|
static JSValue JS_Parse2(JSContext *ctx, JSValue source_str,
|
|
const char *input, size_t input_len,
|
|
const char *filename, int eval_flags)
|
|
{
|
|
JSParseState parse_state, *s;
|
|
JSFunctionBytecode *b;
|
|
JSValue top_func, *saved_sp;
|
|
JSGCRef top_func_ref, *saved_top_gc_ref;
|
|
uint8_t str_buf[5];
|
|
|
|
/* XXX: start gc at the start of parsing ? */
|
|
/* XXX: if the parse state is too large, move it to JSContext */
|
|
s = &parse_state;
|
|
memset(s, 0, sizeof(*s));
|
|
|
|
s->ctx = ctx;
|
|
ctx->parse_state = s;
|
|
s->source_str = JS_NULL;
|
|
s->filename_str = JS_NULL;
|
|
s->has_column = ((eval_flags & JS_EVAL_STRIP_COL) == 0);
|
|
|
|
if (JS_IsPtr(source_str)) {
|
|
JSString *p = JS_VALUE_TO_PTR(source_str);
|
|
s->source_str = source_str;
|
|
s->buf_len = p->len;
|
|
s->source_buf = p->buf;
|
|
} else if (JS_VALUE_GET_SPECIAL_TAG(source_str) == JS_TAG_STRING_CHAR) {
|
|
s->buf_len = get_short_string(str_buf, source_str);
|
|
s->source_buf = str_buf;
|
|
} else {
|
|
s->buf_len = input_len;
|
|
s->source_buf = (const uint8_t *)input;
|
|
}
|
|
s->top_break = JS_NULL;
|
|
saved_top_gc_ref = ctx->top_gc_ref;
|
|
saved_sp = ctx->sp;
|
|
|
|
if (setjmp(s->jmp_env)) {
|
|
int line_num, col_num;
|
|
JSValue val;
|
|
|
|
ctx->parse_state = NULL;
|
|
ctx->top_gc_ref = saved_top_gc_ref;
|
|
ctx->sp = saved_sp;
|
|
ctx->stack_bottom = ctx->sp;
|
|
|
|
line_num = get_line_col(&col_num, s->source_buf,
|
|
(eval_flags & (JS_EVAL_JSON | JS_EVAL_REGEXP)) ?
|
|
s->buf_pos : s->token.source_pos);
|
|
val = JS_ThrowError(ctx, JS_CLASS_SYNTAX_ERROR, "%s", s->error_msg);
|
|
build_backtrace(ctx, ctx->current_exception, filename, line_num + 1, col_num + 1, 0);
|
|
return val;
|
|
}
|
|
|
|
if (eval_flags & JS_EVAL_JSON) {
|
|
top_func = js_parse_json(s);
|
|
} else if (eval_flags & JS_EVAL_REGEXP) {
|
|
top_func = js_parse_regexp(s, eval_flags >> JS_EVAL_REGEXP_FLAGS_SHIFT);
|
|
} else {
|
|
s->filename_str = JS_NewString(ctx, filename);
|
|
if (JS_IsException(s->filename_str))
|
|
js_parse_error_mem(s);
|
|
|
|
b = js_alloc_function_bytecode(ctx);
|
|
if (!b)
|
|
js_parse_error_mem(s);
|
|
b->filename = s->filename_str;
|
|
b->func_name = js_get_atom(ctx, JS_ATOM__eval_);
|
|
b->has_column = s->has_column;
|
|
top_func = JS_VALUE_FROM_PTR(b);
|
|
|
|
reset_parse_state(s, 0, top_func);
|
|
|
|
s->is_eval = TRUE;
|
|
s->has_retval = ((eval_flags & JS_EVAL_RETVAL) != 0);
|
|
s->is_repl = ((eval_flags & JS_EVAL_REPL) != 0);
|
|
|
|
JS_PUSH_VALUE(ctx, top_func);
|
|
|
|
js_parse_program(s);
|
|
|
|
js_parse_local_functions(s, &top_func_ref.val);
|
|
|
|
JS_POP_VALUE(ctx, top_func);
|
|
}
|
|
ctx->parse_state = NULL;
|
|
return top_func;
|
|
}
|
|
|
|
/* warning: it is assumed that input[input_len] = '\0' */
|
|
JSValue JS_Parse(JSContext *ctx, const char *input, size_t input_len,
|
|
const char *filename, int eval_flags)
|
|
{
|
|
return JS_Parse2(ctx, JS_NULL, input, input_len, filename, eval_flags);
|
|
}
|
|
|
|
JSValue JS_Run(JSContext *ctx, JSValue val)
|
|
{
|
|
JSFunctionBytecode *b;
|
|
JSGCRef val_ref;
|
|
int err;
|
|
|
|
if (!JS_IsPtr(val))
|
|
goto fail;
|
|
b = JS_VALUE_TO_PTR(val);
|
|
if (b->mtag != JS_MTAG_FUNCTION_BYTECODE) {
|
|
fail:
|
|
return JS_ThrowTypeError(ctx, "bytecode function expected");
|
|
}
|
|
|
|
val = js_closure(ctx, val, NULL);
|
|
if (JS_IsException(val))
|
|
return val;
|
|
JS_PUSH_VALUE(ctx, val);
|
|
err = JS_StackCheck(ctx, 2);
|
|
JS_POP_VALUE(ctx, val);
|
|
if (err)
|
|
return JS_EXCEPTION;
|
|
JS_PushArg(ctx, val);
|
|
JS_PushArg(ctx, JS_NULL);
|
|
val = JS_Call(ctx, 0);
|
|
return val;
|
|
}
|
|
|
|
/* warning: it is assumed that input[input_len] = '\0' */
|
|
JSValue JS_Eval(JSContext *ctx, const char *input, size_t input_len,
|
|
const char *filename, int eval_flags)
|
|
{
|
|
JSValue val;
|
|
val = JS_Parse(ctx, input, input_len, filename, eval_flags);
|
|
if (JS_IsException(val))
|
|
return val;
|
|
return JS_Run(ctx, val);
|
|
}
|
|
|
|
/**********************************************************************/
|
|
/* garbage collector */
|
|
|
|
/* return the size in bytes */
|
|
static int get_mblock_size(const void *ptr)
|
|
{
|
|
int mtag = ((JSMemBlockHeader *)ptr)->mtag;
|
|
int size;
|
|
switch(mtag) {
|
|
case JS_MTAG_OBJECT:
|
|
{
|
|
const JSObject *p = ptr;
|
|
size = offsetof(JSObject, u) + p->extra_size * JSW;
|
|
}
|
|
break;
|
|
case JS_MTAG_FLOAT64:
|
|
size = sizeof(JSFloat64);
|
|
break;
|
|
case JS_MTAG_STRING:
|
|
{
|
|
const JSString *p = ptr;
|
|
size = sizeof(JSString) + ((p->len + JSW) & ~(JSW - 1));
|
|
}
|
|
break;
|
|
case JS_MTAG_BYTE_ARRAY:
|
|
{
|
|
const JSByteArray *p = ptr;
|
|
size = sizeof(JSByteArray) + ((p->size + JSW - 1) & ~(JSW - 1));
|
|
}
|
|
break;
|
|
case JS_MTAG_VALUE_ARRAY:
|
|
{
|
|
const JSValueArray *p = ptr;
|
|
size = sizeof(JSValueArray) + p->size * sizeof(p->arr[0]);
|
|
}
|
|
break;
|
|
case JS_MTAG_FREE:
|
|
{
|
|
const JSFreeBlock *p = ptr;
|
|
size = sizeof(JSFreeBlock) + p->size * sizeof(JSWord);
|
|
}
|
|
break;
|
|
case JS_MTAG_VARREF:
|
|
{
|
|
const JSVarRef *p = ptr;
|
|
size = sizeof(JSVarRef);
|
|
if (p->is_detached)
|
|
size -= sizeof(JSValue);
|
|
}
|
|
break;
|
|
case JS_MTAG_FUNCTION_BYTECODE:
|
|
size = sizeof(JSFunctionBytecode);
|
|
break;
|
|
default:
|
|
size = 0;
|
|
assert(0);
|
|
}
|
|
return size;
|
|
}
|
|
|
|
/* gc mark pass */
|
|
|
|
typedef struct {
|
|
JSContext *ctx;
|
|
JSValue *gsp;
|
|
JSValue *gs_bottom;
|
|
JSValue *gs_top;
|
|
BOOL overflow;
|
|
} GCMarkState;
|
|
|
|
static BOOL mtag_has_references(int mtag)
|
|
{
|
|
return (mtag == JS_MTAG_OBJECT ||
|
|
mtag == JS_MTAG_VALUE_ARRAY ||
|
|
mtag == JS_MTAG_VARREF ||
|
|
mtag == JS_MTAG_FUNCTION_BYTECODE);
|
|
}
|
|
|
|
static void gc_mark(GCMarkState *s, JSValue val)
|
|
{
|
|
JSContext *ctx = s->ctx;
|
|
void *ptr;
|
|
JSMemBlockHeader *mb;
|
|
|
|
if (!JS_IsPtr(val))
|
|
return;
|
|
ptr = JS_VALUE_TO_PTR(val);
|
|
if (JS_IS_ROM_PTR(ctx, ptr))
|
|
return;
|
|
mb = ptr;
|
|
if (mb->gc_mark)
|
|
return;
|
|
mb->gc_mark = 1;
|
|
if (mtag_has_references(mb->mtag)) {
|
|
if (mb->mtag == JS_MTAG_VALUE_ARRAY) {
|
|
/* value array are handled specifically to save stack space */
|
|
if ((s->gsp - s->gs_bottom) < 2) {
|
|
s->overflow = TRUE;
|
|
} else {
|
|
*--s->gsp = 0;
|
|
*--s->gsp = val;
|
|
}
|
|
} else {
|
|
if ((s->gsp - s->gs_bottom) < 1) {
|
|
s->overflow = TRUE;
|
|
} else {
|
|
*--s->gsp = val;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* flush the GC mark stack */
|
|
static void gc_mark_flush(GCMarkState *s)
|
|
{
|
|
void *ptr;
|
|
JSMemBlockHeader *mb;
|
|
JSValue val;
|
|
|
|
while (s->gsp < s->gs_top) {
|
|
val = *s->gsp++;
|
|
ptr = JS_VALUE_TO_PTR(val);
|
|
mb = ptr;
|
|
|
|
switch(mb->mtag) {
|
|
case JS_MTAG_OBJECT:
|
|
{
|
|
const JSObject *p = ptr;
|
|
gc_mark(s, p->proto);
|
|
gc_mark(s, p->props);
|
|
switch(p->class_id) {
|
|
case JS_CLASS_CLOSURE:
|
|
{
|
|
int i;
|
|
gc_mark(s, p->u.closure.func_bytecode);
|
|
for(i = 0; i < p->extra_size - 1; i++)
|
|
gc_mark(s, p->u.closure.var_refs[i]);
|
|
}
|
|
break;
|
|
case JS_CLASS_C_FUNCTION:
|
|
if (p->extra_size > 1)
|
|
gc_mark(s, p->u.cfunc.params);
|
|
break;
|
|
case JS_CLASS_ARRAY:
|
|
gc_mark(s, p->u.array.tab);
|
|
break;
|
|
case JS_CLASS_ERROR:
|
|
gc_mark(s, p->u.error.message);
|
|
gc_mark(s, p->u.error.stack);
|
|
break;
|
|
case JS_CLASS_ARRAY_BUFFER:
|
|
gc_mark(s, p->u.array_buffer.byte_buffer);
|
|
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_FLOAT32_ARRAY:
|
|
case JS_CLASS_FLOAT64_ARRAY:
|
|
gc_mark(s, p->u.typed_array.buffer);
|
|
break;
|
|
case JS_CLASS_REGEXP:
|
|
gc_mark(s, p->u.regexp.source);
|
|
gc_mark(s, p->u.regexp.byte_code);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case JS_MTAG_VALUE_ARRAY:
|
|
{
|
|
const JSValueArray *p = ptr;
|
|
int pos;
|
|
|
|
pos = *s->gsp++;
|
|
|
|
/* fast path to skip non objects */
|
|
while (pos < p->size && !JS_IsPtr(p->arr[pos]))
|
|
pos++;
|
|
|
|
if (pos < p->size) {
|
|
if ((pos + 1) < p->size) {
|
|
/* the next element needs to be scanned */
|
|
*--s->gsp = pos + 1;
|
|
*--s->gsp = val;
|
|
}
|
|
/* mark the current element */
|
|
gc_mark(s, p->arr[pos]);
|
|
}
|
|
}
|
|
break;
|
|
case JS_MTAG_VARREF:
|
|
{
|
|
const JSVarRef *p = ptr;
|
|
gc_mark(s, p->u.value);
|
|
}
|
|
break;
|
|
case JS_MTAG_FUNCTION_BYTECODE:
|
|
{
|
|
const JSFunctionBytecode *b = ptr;
|
|
gc_mark(s, b->func_name);
|
|
gc_mark(s, b->byte_code);
|
|
gc_mark(s, b->cpool);
|
|
gc_mark(s, b->vars);
|
|
gc_mark(s, b->ext_vars);
|
|
gc_mark(s, b->filename);
|
|
gc_mark(s, b->pc2line);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void gc_mark_root(GCMarkState *s, JSValue val)
|
|
{
|
|
gc_mark(s, val);
|
|
gc_mark_flush(s);
|
|
}
|
|
|
|
/* return true if the memory block is marked i.e. it won't be freed by the GC */
|
|
static BOOL gc_mb_is_marked(JSValue val)
|
|
{
|
|
JSFreeBlock *b;
|
|
if (!JS_IsPtr(val))
|
|
return FALSE;
|
|
b = (JSFreeBlock *)JS_VALUE_TO_PTR(val);
|
|
return b->gc_mark;
|
|
}
|
|
|
|
static void gc_mark_all(JSContext *ctx, BOOL keep_atoms)
|
|
{
|
|
GCMarkState s_s, *s = &s_s;
|
|
JSValue *sp, *sp_end;
|
|
|
|
s->ctx = ctx;
|
|
/* initialize the GC stack */
|
|
s->overflow = FALSE;
|
|
s->gs_top = ctx->sp;
|
|
s->gsp = s->gs_top;
|
|
#if 1
|
|
s->gs_bottom = (JSValue *)ctx->heap_free;
|
|
#else
|
|
s->gs_bottom = s->gs_top - 3; /* TEST small stack space */
|
|
#endif
|
|
|
|
/* keep the atoms if they are in RAM (only used when compiling to file) */
|
|
if ((uint8_t *)ctx->atom_table == ctx->heap_base &&
|
|
keep_atoms) {
|
|
uint8_t *ptr;
|
|
for(ptr = (uint8_t *)ctx->atom_table;
|
|
ptr < (uint8_t *)(ctx->atom_table + JS_ATOM_END);
|
|
ptr += get_mblock_size(ptr)) {
|
|
gc_mark_root(s, JS_VALUE_FROM_PTR(ptr));
|
|
}
|
|
}
|
|
|
|
/* mark all the memory blocks */
|
|
sp_end = ctx->class_proto + 2 * ctx->class_count;
|
|
for(sp = &ctx->current_exception; sp < sp_end; sp++) {
|
|
gc_mark_root(s, *sp);
|
|
}
|
|
|
|
for(sp = ctx->sp; sp < (JSValue *)ctx->stack_top; sp++) {
|
|
gc_mark_root(s, *sp);
|
|
}
|
|
|
|
{
|
|
JSGCRef *ref;
|
|
for(ref = ctx->top_gc_ref; ref != NULL; ref = ref->prev) {
|
|
gc_mark_root(s, ref->val);
|
|
}
|
|
for(ref = ctx->last_gc_ref; ref != NULL; ref = ref->prev) {
|
|
gc_mark_root(s, ref->val);
|
|
}
|
|
}
|
|
if (ctx->parse_state) {
|
|
JSParseState *ps = ctx->parse_state;
|
|
|
|
gc_mark_root(s, ps->source_str);
|
|
gc_mark_root(s, ps->filename_str);
|
|
gc_mark_root(s, ps->token.value);
|
|
gc_mark_root(s, ps->cur_func);
|
|
gc_mark_root(s, ps->byte_code);
|
|
}
|
|
|
|
/* if the mark stack overflowed, need to scan the heap */
|
|
while (s->overflow) {
|
|
uint8_t *ptr;
|
|
int size;
|
|
JSMemBlockHeader *mb;
|
|
|
|
s->overflow = FALSE;
|
|
|
|
ptr = ctx->heap_base;
|
|
while (ptr < ctx->heap_free) {
|
|
size = get_mblock_size(ptr);
|
|
mb = (JSMemBlockHeader *)ptr;
|
|
if (mb->gc_mark && mtag_has_references(mb->mtag)) {
|
|
if (mb->mtag == JS_MTAG_VALUE_ARRAY)
|
|
*--s->gsp = 0;
|
|
*--s->gsp = JS_VALUE_FROM_PTR(ptr);
|
|
gc_mark_flush(s);
|
|
}
|
|
ptr += size;
|
|
}
|
|
}
|
|
|
|
/* update the unique string table (its elements are considered as
|
|
weak string references) */
|
|
if (!JS_IsNull(ctx->unique_strings)) {
|
|
JSValueArray *arr = JS_VALUE_TO_PTR(ctx->unique_strings);
|
|
int i, j;
|
|
|
|
j = 0;
|
|
for(i = 0; i < arr->size; i++) {
|
|
if (gc_mb_is_marked(arr->arr[i])) {
|
|
arr->arr[j++] = arr->arr[i];
|
|
}
|
|
}
|
|
ctx->unique_strings_len = j;
|
|
if (j > 0) {
|
|
arr->gc_mark = 1;
|
|
if (j < arr->size) {
|
|
/* shrink the array */
|
|
set_free_block(&arr->arr[j], (arr->size - j) * sizeof(JSValue));
|
|
arr->size = j;
|
|
}
|
|
} else {
|
|
arr->gc_mark = 0;
|
|
ctx->unique_strings = JS_NULL;
|
|
}
|
|
}
|
|
|
|
/* update the weak references in the string position cache */
|
|
{
|
|
int i;
|
|
JSStringPosCacheEntry *ce;
|
|
for(i = 0; i < JS_STRING_POS_CACHE_SIZE; i++) {
|
|
ce = &ctx->string_pos_cache[i];
|
|
if (!gc_mb_is_marked(ce->str))
|
|
ce->str = JS_NULL;
|
|
}
|
|
}
|
|
|
|
/* reset the gc marks and mark the free blocks as free */
|
|
{
|
|
uint8_t *ptr, *ptr1;
|
|
int size;
|
|
JSFreeBlock *b;
|
|
|
|
ptr = ctx->heap_base;
|
|
while (ptr < ctx->heap_free) {
|
|
size = get_mblock_size(ptr);
|
|
b = (JSFreeBlock *)ptr;
|
|
if (b->gc_mark) {
|
|
b->gc_mark = 0;
|
|
} else {
|
|
JSObject *p = (void *)ptr;
|
|
/* call the user finalizer if needed */
|
|
if (p->mtag == JS_MTAG_OBJECT && p->class_id >= JS_CLASS_USER &&
|
|
ctx->c_finalizer_table[p->class_id - JS_CLASS_USER] != NULL) {
|
|
ctx->c_finalizer_table[p->class_id - JS_CLASS_USER](ctx, p->u.user.opaque);
|
|
}
|
|
/* merge all the consecutive free blocks */
|
|
ptr1 = ptr + size;
|
|
while (ptr1 < ctx->heap_free && ((JSFreeBlock *)ptr1)->gc_mark == 0) {
|
|
ptr1 += get_mblock_size(ptr1);
|
|
}
|
|
size = ptr1 - ptr;
|
|
set_free_block(b, size);
|
|
}
|
|
ptr += size;
|
|
}
|
|
}
|
|
}
|
|
|
|
static JSValue js_value_from_pval(JSContext *ctx, JSValue *pval)
|
|
{
|
|
return JS_VALUE_FROM_PTR(pval);
|
|
}
|
|
|
|
static JSValue *js_value_to_pval(JSContext *ctx, JSValue val)
|
|
{
|
|
return JS_VALUE_TO_PTR(val);
|
|
}
|
|
|
|
static void gc_thread_pointer(JSContext *ctx, JSValue *pval)
|
|
{
|
|
JSValue val;
|
|
JSValue *ptr;
|
|
|
|
val = *pval;
|
|
if (!JS_IsPtr(val))
|
|
return;
|
|
ptr = JS_VALUE_TO_PTR(val);
|
|
if (JS_IS_ROM_PTR(ctx, ptr))
|
|
return;
|
|
/* gc_mark = 0 indicates a normal memory block header, gc_mark = 1
|
|
indicates a pointer to another element */
|
|
*pval = *ptr;
|
|
*ptr = js_value_from_pval(ctx, pval);
|
|
}
|
|
|
|
static void gc_update_threaded_pointers(JSContext *ctx,
|
|
void *ptr, void *new_ptr)
|
|
{
|
|
JSValue val, *pv;
|
|
|
|
val = *(JSValue *)ptr;
|
|
if (JS_IsPtr(val)) {
|
|
/* update the threaded pointers to the node 'ptr' and
|
|
unthread it. */
|
|
for(;;) {
|
|
pv = js_value_to_pval(ctx, val);
|
|
val = *pv;
|
|
*pv = JS_VALUE_FROM_PTR(new_ptr);
|
|
if (!JS_IsPtr(val))
|
|
break;
|
|
}
|
|
*(JSValue *)ptr = val;
|
|
}
|
|
}
|
|
|
|
static void gc_thread_block(JSContext *ctx, void *ptr)
|
|
{
|
|
int mtag;
|
|
|
|
mtag = ((JSMemBlockHeader *)ptr)->mtag;
|
|
switch(mtag) {
|
|
case JS_MTAG_OBJECT:
|
|
{
|
|
JSObject *p = ptr;
|
|
gc_thread_pointer(ctx, &p->proto);
|
|
gc_thread_pointer(ctx, &p->props);
|
|
switch(p->class_id) {
|
|
case JS_CLASS_CLOSURE:
|
|
{
|
|
int i;
|
|
gc_thread_pointer(ctx, &p->u.closure.func_bytecode);
|
|
for(i = 0; i < p->extra_size - 1; i++)
|
|
gc_thread_pointer(ctx, &p->u.closure.var_refs[i]);
|
|
}
|
|
break;
|
|
case JS_CLASS_C_FUNCTION:
|
|
if (p->extra_size > 1)
|
|
gc_thread_pointer(ctx, &p->u.cfunc.params);
|
|
break;
|
|
case JS_CLASS_ARRAY:
|
|
gc_thread_pointer(ctx, &p->u.array.tab);
|
|
break;
|
|
case JS_CLASS_ERROR:
|
|
gc_thread_pointer(ctx, &p->u.error.message);
|
|
gc_thread_pointer(ctx, &p->u.error.stack);
|
|
break;
|
|
case JS_CLASS_ARRAY_BUFFER:
|
|
gc_thread_pointer(ctx, &p->u.array_buffer.byte_buffer);
|
|
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_FLOAT32_ARRAY:
|
|
case JS_CLASS_FLOAT64_ARRAY:
|
|
gc_thread_pointer(ctx, &p->u.typed_array.buffer);
|
|
break;
|
|
case JS_CLASS_REGEXP:
|
|
gc_thread_pointer(ctx, &p->u.regexp.source);
|
|
gc_thread_pointer(ctx, &p->u.regexp.byte_code);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case JS_MTAG_VALUE_ARRAY:
|
|
{
|
|
JSValueArray *p = ptr;
|
|
int i;
|
|
for(i = 0; i < p->size; i++) {
|
|
gc_thread_pointer(ctx, &p->arr[i]);
|
|
}
|
|
}
|
|
break;
|
|
case JS_MTAG_VARREF:
|
|
{
|
|
JSVarRef *p = ptr;
|
|
gc_thread_pointer(ctx, &p->u.value);
|
|
}
|
|
break;
|
|
case JS_MTAG_FUNCTION_BYTECODE:
|
|
{
|
|
JSFunctionBytecode *b = ptr;
|
|
gc_thread_pointer(ctx, &b->func_name);
|
|
gc_thread_pointer(ctx, &b->byte_code);
|
|
gc_thread_pointer(ctx, &b->cpool);
|
|
gc_thread_pointer(ctx, &b->vars);
|
|
gc_thread_pointer(ctx, &b->ext_vars);
|
|
gc_thread_pointer(ctx, &b->filename);
|
|
gc_thread_pointer(ctx, &b->pc2line);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Heap compaction using Jonkers algorithm */
|
|
static void gc_compact_heap(JSContext *ctx)
|
|
{
|
|
uint8_t *ptr, *new_ptr;
|
|
int size;
|
|
JSValue *sp, *sp_end;
|
|
|
|
/* thread all the external pointers */
|
|
sp_end = ctx->class_proto + 2 * ctx->class_count;
|
|
for(sp = &ctx->unique_strings; sp < sp_end; sp++) {
|
|
gc_thread_pointer(ctx, sp);
|
|
}
|
|
{
|
|
int i;
|
|
JSStringPosCacheEntry *ce;
|
|
for(i = 0; i < JS_STRING_POS_CACHE_SIZE; i++) {
|
|
ce = &ctx->string_pos_cache[i];
|
|
gc_thread_pointer(ctx, &ce->str);
|
|
}
|
|
}
|
|
|
|
for(sp = ctx->sp; sp < (JSValue *)ctx->stack_top; sp++) {
|
|
gc_thread_pointer(ctx, sp);
|
|
}
|
|
|
|
{
|
|
JSGCRef *ref;
|
|
for(ref = ctx->top_gc_ref; ref != NULL; ref = ref->prev) {
|
|
gc_thread_pointer(ctx, &ref->val);
|
|
}
|
|
for(ref = ctx->last_gc_ref; ref != NULL; ref = ref->prev) {
|
|
gc_thread_pointer(ctx, &ref->val);
|
|
}
|
|
}
|
|
|
|
if (ctx->parse_state) {
|
|
JSParseState *ps = ctx->parse_state;
|
|
|
|
gc_thread_pointer(ctx, &ps->source_str);
|
|
gc_thread_pointer(ctx, &ps->filename_str);
|
|
gc_thread_pointer(ctx, &ps->token.value);
|
|
gc_thread_pointer(ctx, &ps->cur_func);
|
|
gc_thread_pointer(ctx, &ps->byte_code);
|
|
}
|
|
|
|
/* pass 1: thread the pointers and update the previous ones */
|
|
new_ptr = ctx->heap_base;
|
|
ptr = ctx->heap_base;
|
|
while (ptr < ctx->heap_free) {
|
|
gc_update_threaded_pointers(ctx, ptr, new_ptr);
|
|
size = get_mblock_size(ptr);
|
|
if (js_get_mtag(ptr) != JS_MTAG_FREE) {
|
|
gc_thread_block(ctx, ptr);
|
|
new_ptr += size;
|
|
}
|
|
ptr += size;
|
|
}
|
|
|
|
/* pass 2: update the threaded pointers and move the block to its
|
|
final position */
|
|
new_ptr = ctx->heap_base;
|
|
ptr = ctx->heap_base;
|
|
while (ptr < ctx->heap_free) {
|
|
gc_update_threaded_pointers(ctx, ptr, new_ptr);
|
|
size = get_mblock_size(ptr);
|
|
if (js_get_mtag(ptr) != JS_MTAG_FREE) {
|
|
if (new_ptr != ptr) {
|
|
memmove(new_ptr, ptr, size);
|
|
}
|
|
new_ptr += size;
|
|
}
|
|
ptr += size;
|
|
}
|
|
ctx->heap_free = new_ptr;
|
|
|
|
/* update the source pointer in the parser */
|
|
if (ctx->parse_state) {
|
|
JSParseState *ps = ctx->parse_state;
|
|
if (JS_IsPtr(ps->source_str)) {
|
|
JSString *p = JS_VALUE_TO_PTR(ps->source_str);
|
|
ps->source_buf = p->buf;
|
|
}
|
|
}
|
|
|
|
/* rehash the object properties */
|
|
/* XXX: try to do it in the previous pass (add a specific tag ?) */
|
|
ptr = ctx->heap_base;
|
|
while (ptr < ctx->heap_free) {
|
|
size = get_mblock_size(ptr);
|
|
if (js_get_mtag(ptr) == JS_MTAG_OBJECT) {
|
|
js_rehash_props(ctx, (JSObject *)ptr, TRUE);
|
|
}
|
|
ptr += size;
|
|
}
|
|
}
|
|
|
|
static void JS_GC2(JSContext *ctx, BOOL keep_atoms)
|
|
{
|
|
#ifdef DUMP_GC
|
|
js_printf(ctx, "GC : heap size=%u/%u stack_size=%u\n",
|
|
(uint32_t)(ctx->heap_free - ctx->heap_base),
|
|
(uint32_t)(ctx->stack_top - ctx->heap_base),
|
|
(uint32_t)(ctx->stack_top - (uint8_t *)ctx->sp));
|
|
#endif
|
|
#if defined(DEBUG_GC)
|
|
/* reduce the dummy block size at each GC to change the addresses
|
|
after compaction */
|
|
/* XXX: only works a finite number of times */
|
|
{
|
|
JSByteArray *arr;
|
|
if (JS_IsPtr(ctx->dummy_block)) {
|
|
arr = JS_VALUE_TO_PTR(ctx->dummy_block);
|
|
if (arr->size >= 8) {
|
|
js_shrink_byte_array(ctx, &ctx->dummy_block, arr->size - 4);
|
|
if (arr->size == 4) {
|
|
js_printf(ctx, "WARNING: debug GC: no longer modifying the addresses\n");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
gc_mark_all(ctx, keep_atoms);
|
|
gc_compact_heap(ctx);
|
|
#ifdef DUMP_GC
|
|
js_printf(ctx, "AFTER: heap size=%u/%u stack_size=%u\n",
|
|
(uint32_t)(ctx->heap_free - ctx->heap_base),
|
|
(uint32_t)(ctx->stack_top - ctx->heap_base),
|
|
(uint32_t)(ctx->stack_top - (uint8_t *)ctx->sp));
|
|
#endif
|
|
}
|
|
|
|
void JS_GC(JSContext *ctx)
|
|
{
|
|
JS_GC2(ctx, TRUE);
|
|
}
|
|
|
|
/* bytecode saving and loading */
|
|
|
|
#define JS_BYTECODE_VERSION_32 0x0001
|
|
/* bit 15 of bytecode version is a 64-bit indicator */
|
|
#define JS_BYTECODE_VERSION (JS_BYTECODE_VERSION_32 | ((JSW & 8) << 12))
|
|
|
|
void JS_PrepareBytecode(JSContext *ctx,
|
|
JSBytecodeHeader *hdr,
|
|
const uint8_t **pdata_buf, uint32_t *pdata_len,
|
|
JSValue eval_code)
|
|
{
|
|
JSGCRef eval_code_ref;
|
|
int i;
|
|
|
|
/* remove all the objects except the compiled code */
|
|
ctx->empty_props = JS_NULL;
|
|
for(i = 0; i < ctx->class_count; i++) {
|
|
ctx->class_proto[i] = JS_NULL;
|
|
ctx->class_obj[i] = JS_NULL;
|
|
}
|
|
ctx->global_obj = JS_NULL;
|
|
#ifdef DEBUG_GC
|
|
ctx->dummy_block = JS_NULL;
|
|
#endif
|
|
|
|
JS_PUSH_VALUE(ctx, eval_code);
|
|
JS_GC2(ctx, FALSE);
|
|
JS_POP_VALUE(ctx, eval_code);
|
|
|
|
hdr->magic = JS_BYTECODE_MAGIC;
|
|
hdr->version = JS_BYTECODE_VERSION;
|
|
hdr->base_addr = (uintptr_t)ctx->heap_base;
|
|
hdr->unique_strings = ctx->unique_strings;
|
|
hdr->main_func = eval_code;
|
|
|
|
*pdata_buf = ctx->heap_base;
|
|
*pdata_len = ctx->heap_free - ctx->heap_base;
|
|
}
|
|
|
|
#if JSW == 8
|
|
|
|
typedef uint32_t JSValue_32;
|
|
typedef uint32_t JSWord_32;
|
|
|
|
#define JS_MB_HEADER_32 \
|
|
JSWord_32 gc_mark: 1; \
|
|
JSWord_32 mtag: (JS_MTAG_BITS - 1)
|
|
|
|
#define JS_MB_PAD_32(n) (32 - (n))
|
|
|
|
typedef struct {
|
|
JS_MB_HEADER_32;
|
|
JSWord_32 dummy: JS_MB_PAD_32(JS_MTAG_BITS);
|
|
} JSMemBlockHeader_32;
|
|
|
|
typedef struct {
|
|
JS_MB_HEADER_32;
|
|
JSWord_32 size: JS_MB_PAD_32(JS_MTAG_BITS);
|
|
JSValue_32 arr[];
|
|
} JSValueArray_32;
|
|
|
|
typedef struct {
|
|
JS_MB_HEADER_32;
|
|
JSWord_32 size: JS_MB_PAD_32(JS_MTAG_BITS);
|
|
uint8_t buf[];
|
|
} JSByteArray_32;
|
|
|
|
typedef struct {
|
|
JS_MB_HEADER_32;
|
|
JSWord_32 dummy: JS_MB_PAD_32(JS_MTAG_BITS);
|
|
/* unaligned 64 bit access in 32-bit mode */
|
|
struct __attribute__((packed)) {
|
|
double dval;
|
|
} u;
|
|
} JSFloat64_32;
|
|
|
|
#define JS_STRING_LEN_MAX_32 ((1 << (32 - JS_MTAG_BITS - 3)) - 1)
|
|
|
|
typedef struct {
|
|
JS_MB_HEADER_32;
|
|
JSWord_32 is_unique: 1;
|
|
JSWord_32 is_ascii: 1;
|
|
/* true if the string content represents a number, only meaningful
|
|
is is_unique = true */
|
|
JSWord_32 is_numeric: 1;
|
|
JSWord_32 len: JS_MB_PAD_32(JS_MTAG_BITS + 3);
|
|
uint8_t buf[];
|
|
} JSString_32;
|
|
|
|
typedef struct {
|
|
JS_MB_HEADER_32;
|
|
JSWord_32 has_arguments : 1; /* only used during parsing */
|
|
JSWord_32 has_local_func_name : 1; /* only used during parsing */
|
|
JSWord_32 has_column : 1; /* column debug info is present */
|
|
JSWord_32 arg_count : 16;
|
|
JSWord_32 dummy: JS_MB_PAD_32(JS_MTAG_BITS + 3 + 16);
|
|
|
|
JSValue_32 func_name; /* JS_NULL if anonymous function */
|
|
JSValue_32 byte_code; /* JS_NULL if the function is not parsed yet */
|
|
JSValue_32 cpool; /* constant pool */
|
|
JSValue_32 vars; /* only for debug */
|
|
JSValue_32 ext_vars; /* records of (var_name, var_kind (2 bits) var_idx (16 bits)) */
|
|
uint16_t stack_size; /* maximum stack size */
|
|
uint16_t ext_vars_len; /* XXX: only used during parsing */
|
|
JSValue_32 filename; /* filename in which the function is defined */
|
|
JSValue_32 pc2line; /* JSByteArray or JS_NULL if not initialized */
|
|
uint32_t source_pos; /* only used during parsing (XXX: shrink) */
|
|
} JSFunctionBytecode_32;
|
|
|
|
/* warning: ptr1 and ptr may overlap. However there is always: ptr1 <= ptr. Return 0 if OK. */
|
|
static int convert_mblock_64to32(void *ptr1, const void *ptr)
|
|
{
|
|
int mtag, i;
|
|
|
|
mtag = ((JSMemBlockHeader*)ptr)->mtag;
|
|
switch(mtag) {
|
|
case JS_MTAG_FUNCTION_BYTECODE:
|
|
{
|
|
const JSFunctionBytecode *b = ptr;
|
|
JSFunctionBytecode_32 *b1 = ptr1;
|
|
b1->gc_mark = b->gc_mark;
|
|
b1->mtag = b->mtag;
|
|
b1->has_arguments = b->has_arguments;
|
|
b1->has_local_func_name = b->has_local_func_name;
|
|
b1->has_column = b->has_column;
|
|
b1->arg_count = b->arg_count;
|
|
b1->dummy = 0;
|
|
b1->func_name = b->func_name;
|
|
b1->byte_code = b->byte_code;
|
|
b1->cpool = b->cpool;
|
|
b1->vars = b->vars;
|
|
b1->ext_vars = b->ext_vars;
|
|
b1->stack_size = b->stack_size;
|
|
b1->ext_vars_len = b->ext_vars_len;
|
|
b1->filename = b->filename;
|
|
b1->pc2line = b->pc2line;
|
|
b1->source_pos = b->source_pos;
|
|
}
|
|
break;
|
|
case JS_MTAG_FLOAT64:
|
|
{
|
|
const JSFloat64 *b = ptr;
|
|
JSFloat64_32 *b1 = ptr1;
|
|
|
|
b1->gc_mark = b->gc_mark;
|
|
b1->mtag = b->mtag;
|
|
b1->dummy = 0;
|
|
b1->u.dval = b->u.dval;
|
|
}
|
|
break;
|
|
case JS_MTAG_VALUE_ARRAY:
|
|
{
|
|
const JSValueArray *b = ptr;
|
|
JSValueArray_32 *b1 = ptr1;
|
|
|
|
b1->gc_mark = b->gc_mark;
|
|
b1->mtag = b->mtag;
|
|
b1->size = b->size; /* no test needed as long as JS_VALUE_ARRAY_SIZE_MAX is identical */
|
|
for(i = 0; i < b1->size; i++)
|
|
b1->arr[i] = b->arr[i];
|
|
}
|
|
break;
|
|
case JS_MTAG_BYTE_ARRAY:
|
|
{
|
|
const JSByteArray *b = ptr;
|
|
JSByteArray_32 *b1 = ptr1;
|
|
|
|
b1->gc_mark = b->gc_mark;
|
|
b1->mtag = b->mtag;
|
|
b1->size = b->size; /* no test needed as long as JS_BYTE_ARRAY_SIZE_MAX is identical */
|
|
memmove(b1->buf, b->buf, b1->size);
|
|
}
|
|
break;
|
|
case JS_MTAG_STRING:
|
|
{
|
|
const JSString *b = ptr;
|
|
JSString_32 *b1 = ptr1;
|
|
|
|
if (b->len > JS_STRING_LEN_MAX_32)
|
|
return -1;
|
|
b1->gc_mark = b->gc_mark;
|
|
b1->mtag = b->mtag;
|
|
b1->is_unique = b->is_unique;
|
|
b1->is_ascii = b->is_ascii;
|
|
b1->is_numeric = b->is_numeric;
|
|
b1->len = b->len;
|
|
memmove(b1->buf, b->buf, b1->len + 1);
|
|
}
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* return the size in bytes */
|
|
static int get_mblock_size_32(const void *ptr)
|
|
{
|
|
int mtag = ((JSMemBlockHeader_32 *)ptr)->mtag;
|
|
int size;
|
|
switch(mtag) {
|
|
case JS_MTAG_FLOAT64:
|
|
size = sizeof(JSFloat64_32);
|
|
break;
|
|
case JS_MTAG_STRING:
|
|
{
|
|
const JSString_32 *p = ptr;
|
|
size = sizeof(JSString_32) + ((p->len + 4) & ~(4 - 1));
|
|
}
|
|
break;
|
|
case JS_MTAG_BYTE_ARRAY:
|
|
{
|
|
const JSByteArray_32 *p = ptr;
|
|
size = sizeof(JSByteArray_32) + ((p->size + 4 - 1) & ~(4 - 1));
|
|
}
|
|
break;
|
|
case JS_MTAG_VALUE_ARRAY:
|
|
{
|
|
const JSValueArray_32 *p = ptr;
|
|
size = sizeof(JSValueArray_32) + p->size * sizeof(p->arr[0]);
|
|
}
|
|
break;
|
|
case JS_MTAG_FUNCTION_BYTECODE:
|
|
size = sizeof(JSFunctionBytecode_32);
|
|
break;
|
|
default:
|
|
size = 0;
|
|
assert(0);
|
|
}
|
|
return size;
|
|
}
|
|
|
|
/* Compact and convert a 64 bit heap to a 32 bit heap at offset
|
|
0. Only used for code compilation. Return 0 if OK. */
|
|
static int gc_compact_heap_64to32(JSContext *ctx)
|
|
{
|
|
uint8_t *ptr;
|
|
int size, size_32;
|
|
uintptr_t new_offset;
|
|
|
|
gc_thread_pointer(ctx, &ctx->unique_strings);
|
|
|
|
/* thread all the external pointers */
|
|
{
|
|
JSGCRef *ref;
|
|
/* necessary because JS_PUSH_VAL() is called before
|
|
gc_compact_heap_64to32() */
|
|
for(ref = ctx->top_gc_ref; ref != NULL; ref = ref->prev) {
|
|
gc_thread_pointer(ctx, &ref->val);
|
|
}
|
|
}
|
|
|
|
/* pass 1: thread the pointers and update the previous ones */
|
|
new_offset = 0;
|
|
ptr = ctx->heap_base;
|
|
while (ptr < ctx->heap_free) {
|
|
gc_update_threaded_pointers(ctx, ptr, (uint8_t *)new_offset);
|
|
size = get_mblock_size(ptr);
|
|
if (js_get_mtag(ptr) != JS_MTAG_FREE) {
|
|
gc_thread_block(ctx, ptr);
|
|
size_32 = get_mblock_size_32(ptr);
|
|
new_offset += size_32;
|
|
}
|
|
ptr += size;
|
|
}
|
|
|
|
/* pass 2: update the threaded pointers and move the block to its
|
|
final position */
|
|
new_offset = 0;
|
|
ptr = ctx->heap_base;
|
|
while (ptr < ctx->heap_free) {
|
|
gc_update_threaded_pointers(ctx, ptr, (uint8_t *)new_offset);
|
|
size = get_mblock_size(ptr);
|
|
if (js_get_mtag(ptr) != JS_MTAG_FREE) {
|
|
size_32 = get_mblock_size_32(ptr);
|
|
if (convert_mblock_64to32(ctx->heap_base + new_offset, ptr))
|
|
return -1;
|
|
new_offset += size_32;
|
|
}
|
|
ptr += size;
|
|
}
|
|
ctx->heap_free = ctx->heap_base + new_offset;
|
|
return 0;
|
|
}
|
|
|
|
#ifdef JS_USE_SHORT_FLOAT
|
|
|
|
static int expand_short_float(JSContext *ctx, JSValue *pval)
|
|
{
|
|
JSFloat64 *f;
|
|
if (JS_IsShortFloat(*pval)) {
|
|
f = js_malloc(ctx, sizeof(JSFloat64), JS_MTAG_FLOAT64);
|
|
if (!f)
|
|
return -1;
|
|
f->u.dval = js_get_short_float(*pval);
|
|
*pval = JS_VALUE_FROM_PTR(f);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Expand all the short floats to JSFloat64 structures. Return < 0 if
|
|
not enough memory. */
|
|
static int expand_short_floats(JSContext *ctx)
|
|
{
|
|
uint8_t *ptr, *p_end;
|
|
int mtag, size;
|
|
|
|
ptr = ctx->heap_base;
|
|
p_end = ctx->heap_free;
|
|
while (ptr < p_end) {
|
|
size = get_mblock_size(ptr);
|
|
mtag = ((JSMemBlockHeader *)ptr)->mtag;
|
|
switch(mtag) {
|
|
case JS_MTAG_FUNCTION_BYTECODE:
|
|
/* we assume no short floats here */
|
|
break;
|
|
case JS_MTAG_VALUE_ARRAY:
|
|
{
|
|
JSValueArray *p = (JSValueArray *)ptr;
|
|
int i;
|
|
for(i = 0; i < p->size; i++) {
|
|
if (expand_short_float(ctx, &p->arr[i]))
|
|
return -1;
|
|
}
|
|
}
|
|
break;
|
|
case JS_MTAG_STRING:
|
|
case JS_MTAG_FLOAT64:
|
|
case JS_MTAG_BYTE_ARRAY:
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
ptr += size;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#endif /* JS_USE_SHORT_FLOAT */
|
|
|
|
int JS_PrepareBytecode64to32(JSContext *ctx,
|
|
JSBytecodeHeader32 *hdr,
|
|
const uint8_t **pdata_buf, uint32_t *pdata_len,
|
|
JSValue eval_code)
|
|
{
|
|
JSGCRef eval_code_ref;
|
|
int i;
|
|
|
|
/* remove all the objects except the compiled code */
|
|
ctx->empty_props = JS_NULL;
|
|
for(i = 0; i < ctx->class_count; i++) {
|
|
ctx->class_proto[i] = JS_NULL;
|
|
ctx->class_obj[i] = JS_NULL;
|
|
}
|
|
ctx->global_obj = JS_NULL;
|
|
#ifdef DEBUG_GC
|
|
ctx->dummy_block = JS_NULL;
|
|
#endif
|
|
|
|
JS_PUSH_VALUE(ctx, eval_code);
|
|
#ifdef JS_USE_SHORT_FLOAT
|
|
JS_GC2(ctx, FALSE);
|
|
if (expand_short_floats(ctx))
|
|
return -1;
|
|
#else
|
|
gc_mark_all(ctx, FALSE);
|
|
#endif
|
|
if (gc_compact_heap_64to32(ctx))
|
|
return -1;
|
|
JS_POP_VALUE(ctx, eval_code);
|
|
|
|
hdr->magic = JS_BYTECODE_MAGIC;
|
|
hdr->version = JS_BYTECODE_VERSION_32;
|
|
hdr->base_addr = 0;
|
|
hdr->unique_strings = ctx->unique_strings;
|
|
hdr->main_func = eval_code;
|
|
|
|
*pdata_buf = ctx->heap_base;
|
|
*pdata_len = ctx->heap_free - ctx->heap_base;
|
|
/* ensure that JS_FreeContext() will do nothing */
|
|
ctx->heap_free = ctx->heap_base;
|
|
return 0;
|
|
}
|
|
#endif /* JSW == 8 */
|
|
|
|
BOOL JS_IsBytecode(const uint8_t *buf, size_t buf_len)
|
|
{
|
|
const JSBytecodeHeader *hdr = (const JSBytecodeHeader *)buf;
|
|
return (buf_len >= sizeof(*hdr) && hdr->magic == JS_BYTECODE_MAGIC);
|
|
}
|
|
|
|
typedef struct {
|
|
JSContext *ctx;
|
|
uintptr_t offset;
|
|
BOOL update_atoms;
|
|
} BCRelocState;
|
|
|
|
static void bc_reloc_value(BCRelocState *s, JSValue *pval)
|
|
{
|
|
JSContext *ctx = s->ctx;
|
|
JSString *p;
|
|
JSValue val, str;
|
|
|
|
val = *pval;
|
|
if (JS_IsPtr(val)) {
|
|
val += s->offset;
|
|
|
|
/* unique strings must be unique, so modify the unique string
|
|
value if it already exists in the context */
|
|
if (s->update_atoms) {
|
|
p = JS_VALUE_TO_PTR(val);
|
|
if (p->mtag == JS_MTAG_STRING && p->is_unique) {
|
|
const JSValueArray *arr1;
|
|
int a, i;
|
|
for(i = 0; i < ctx->n_rom_atom_tables; i++) {
|
|
arr1 = ctx->rom_atom_tables[i];
|
|
str = find_atom(ctx, &a, arr1, arr1->size, val);
|
|
if (!JS_IsNull(str)) {
|
|
val = str;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
*pval = val;
|
|
}
|
|
}
|
|
|
|
int JS_RelocateBytecode2(JSContext *ctx, JSBytecodeHeader *hdr,
|
|
uint8_t *buf, uint32_t buf_len,
|
|
uintptr_t new_base_addr, BOOL update_atoms)
|
|
{
|
|
uint8_t *ptr, *p_end;
|
|
int size, mtag;
|
|
BCRelocState ss, *s = &ss;
|
|
|
|
if (hdr->magic != JS_BYTECODE_MAGIC)
|
|
return -1;
|
|
if (hdr->version != JS_BYTECODE_VERSION)
|
|
return -1;
|
|
|
|
/* XXX: add atom checksum to avoid problems if the stdlib is
|
|
modified */
|
|
s->ctx = ctx;
|
|
s->offset = new_base_addr - hdr->base_addr;
|
|
s->update_atoms = update_atoms;
|
|
|
|
bc_reloc_value(s, &hdr->unique_strings);
|
|
bc_reloc_value(s, &hdr->main_func);
|
|
|
|
ptr = buf;
|
|
p_end = buf + buf_len;
|
|
while (ptr < p_end) {
|
|
size = get_mblock_size(ptr);
|
|
mtag = ((JSMemBlockHeader *)ptr)->mtag;
|
|
switch(mtag) {
|
|
case JS_MTAG_FUNCTION_BYTECODE:
|
|
{
|
|
JSFunctionBytecode *b = (JSFunctionBytecode *)ptr;
|
|
bc_reloc_value(s, &b->func_name);
|
|
bc_reloc_value(s, &b->byte_code);
|
|
bc_reloc_value(s, &b->cpool);
|
|
bc_reloc_value(s, &b->vars);
|
|
bc_reloc_value(s, &b->ext_vars);
|
|
bc_reloc_value(s, &b->filename);
|
|
bc_reloc_value(s, &b->pc2line);
|
|
}
|
|
break;
|
|
case JS_MTAG_VALUE_ARRAY:
|
|
{
|
|
JSValueArray *p = (JSValueArray *)ptr;
|
|
int i;
|
|
for(i = 0; i < p->size; i++) {
|
|
bc_reloc_value(s, &p->arr[i]);
|
|
}
|
|
}
|
|
break;
|
|
case JS_MTAG_STRING:
|
|
case JS_MTAG_FLOAT64:
|
|
case JS_MTAG_BYTE_ARRAY:
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
ptr += size;
|
|
}
|
|
hdr->base_addr = new_base_addr;
|
|
return 0;
|
|
}
|
|
|
|
/* Relocate the bytecode in 'buf' so that it can be executed
|
|
later. Return 0 if OK, != 0 if error */
|
|
int JS_RelocateBytecode(JSContext *ctx,
|
|
uint8_t *buf, uint32_t buf_len)
|
|
{
|
|
uint8_t *data_ptr;
|
|
|
|
if (buf_len < sizeof(JSBytecodeHeader))
|
|
return -1;
|
|
data_ptr = buf + sizeof(JSBytecodeHeader);
|
|
return JS_RelocateBytecode2(ctx, (JSBytecodeHeader *)buf,
|
|
data_ptr,
|
|
buf_len - sizeof(JSBytecodeHeader),
|
|
(uintptr_t)data_ptr, TRUE);
|
|
}
|
|
|
|
/* Load the precompiled bytecode from 'buf'. 'buf' must be allocated
|
|
as long as the JSContext exists. Use JS_Run() to execute
|
|
it. warning: the bytecode is not checked so it should come from a
|
|
trusted source. */
|
|
JSValue JS_LoadBytecode(JSContext *ctx, const uint8_t *buf)
|
|
{
|
|
const JSBytecodeHeader *hdr = (const JSBytecodeHeader *)buf;
|
|
|
|
if (ctx->unique_strings_len != 0)
|
|
return JS_ThrowInternalError(ctx, "no atom must be defined in RAM");
|
|
/* XXX: could stack atom_tables */
|
|
if (ctx->n_rom_atom_tables >= N_ROM_ATOM_TABLES_MAX)
|
|
return JS_ThrowInternalError(ctx, "too many rom atom tables");
|
|
if (hdr->magic != JS_BYTECODE_MAGIC)
|
|
return JS_ThrowInternalError(ctx, "invalid bytecode magic");
|
|
if ((hdr->version & 0x8000) != (JS_BYTECODE_VERSION & 0x8000))
|
|
return JS_ThrowInternalError(ctx, "bytecode not saved for %d-bit", JSW * 8);
|
|
if (hdr->version != JS_BYTECODE_VERSION)
|
|
return JS_ThrowInternalError(ctx, "invalid bytecode version");
|
|
if (hdr->base_addr != (uintptr_t)(hdr + 1))
|
|
return JS_ThrowInternalError(ctx, "bytecode not relocated");
|
|
ctx->rom_atom_tables[ctx->n_rom_atom_tables++] = (JSValueArray *)JS_VALUE_TO_PTR(hdr->unique_strings);
|
|
return hdr->main_func;
|
|
}
|
|
|
|
/**********************************************************************/
|
|
/* runtime */
|
|
|
|
JSValue js_function_constructor(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
StringBuffer b_s, *b = &b_s;
|
|
JSValue val;
|
|
int i, n;
|
|
|
|
argc &= ~FRAME_CF_CTOR;
|
|
string_buffer_push(ctx, b, 0);
|
|
string_buffer_puts(ctx, b, "(function anonymous(");
|
|
n = argc - 1;
|
|
for(i = 0; i < n; i++) {
|
|
if (i != 0) {
|
|
string_buffer_putc(ctx, b, ',');
|
|
}
|
|
if (string_buffer_concat(ctx, b, argv[i]))
|
|
goto done;
|
|
}
|
|
string_buffer_puts(ctx, b, "\n) {\n");
|
|
if (n >= 0) {
|
|
if (string_buffer_concat(ctx, b, argv[n]))
|
|
goto done;
|
|
}
|
|
string_buffer_puts(ctx, b, "\n})");
|
|
done:
|
|
val = string_buffer_pop(ctx, b);
|
|
if (JS_IsException(val))
|
|
return val;
|
|
val = JS_Parse2(ctx, val, NULL, 0, "<input>", JS_EVAL_RETVAL);
|
|
if (JS_IsException(val))
|
|
return val;
|
|
return JS_Run(ctx, val);
|
|
}
|
|
|
|
JSValue js_function_get_prototype(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue obj;
|
|
JSGCRef obj_ref;
|
|
|
|
if (!JS_IsPtr(*this_val)) {
|
|
if (JS_VALUE_GET_SPECIAL_TAG(*this_val) != JS_TAG_SHORT_FUNC)
|
|
goto fail;
|
|
return JS_UNDEFINED;
|
|
} else {
|
|
JSObject *p = JS_VALUE_TO_PTR(*this_val);
|
|
if (p->mtag != JS_MTAG_OBJECT)
|
|
goto fail;
|
|
if (p->class_id == JS_CLASS_CLOSURE) {
|
|
obj = JS_NewObject(ctx);
|
|
if (JS_IsException(obj))
|
|
return obj;
|
|
} else if (p->class_id == JS_CLASS_C_FUNCTION) {
|
|
/* for C constructors, the prototype property is already present */
|
|
return JS_UNDEFINED;
|
|
} else {
|
|
fail:
|
|
return JS_ThrowTypeError(ctx, "not a function");
|
|
}
|
|
JS_PUSH_VALUE(ctx, obj);
|
|
JS_DefinePropertyValue(ctx, obj, js_get_atom(ctx, JS_ATOM_constructor),
|
|
*this_val);
|
|
JS_POP_VALUE(ctx, obj);
|
|
JS_PUSH_VALUE(ctx, obj);
|
|
JS_DefinePropertyValue(ctx, *this_val, js_get_atom(ctx, JS_ATOM_prototype),
|
|
obj);
|
|
JS_POP_VALUE(ctx, obj);
|
|
}
|
|
return obj;
|
|
}
|
|
|
|
JSValue js_function_set_prototype(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
if (!JS_IsFunctionObject(ctx, *this_val))
|
|
return JS_ThrowTypeError(ctx, "not a function");
|
|
|
|
JS_DefinePropertyValue(ctx, *this_val, js_get_atom(ctx, JS_ATOM_prototype),
|
|
argv[0]);
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
JSValue js_function_get_length_name(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv, int is_name)
|
|
{
|
|
JSFunctionBytecode *b;
|
|
JSValue ret = js_function_get_length_name1(ctx, this_val, is_name, &b);
|
|
if (JS_IsNull(ret))
|
|
return JS_ThrowTypeError(ctx, "not a function");
|
|
return ret;
|
|
}
|
|
|
|
JSValue js_function_toString(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue str, val;
|
|
JSGCRef str_ref;
|
|
|
|
str = js_function_get_length_name(ctx, this_val, 0, NULL, 1);
|
|
if (JS_IsException(str))
|
|
return str;
|
|
JS_PUSH_VALUE(ctx, str);
|
|
val = JS_NewString(ctx, "function ");
|
|
JS_POP_VALUE(ctx, str);
|
|
str = JS_ConcatString(ctx, val, str);
|
|
JS_PUSH_VALUE(ctx, str);
|
|
val = JS_NewString(ctx, "() {\n [native code]\n}");
|
|
JS_POP_VALUE(ctx, str);
|
|
return JS_ConcatString(ctx, str, val);
|
|
}
|
|
|
|
JSValue js_function_call(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
int i;
|
|
argc = max_int(argc, 1);
|
|
if (JS_StackCheck(ctx, argc + 1))
|
|
return JS_EXCEPTION;
|
|
for(i = 0; i < argc - 1; i++)
|
|
JS_PushArg(ctx, argv[argc - 1 - i]);
|
|
JS_PushArg(ctx, *this_val);
|
|
JS_PushArg(ctx, argv[0]);
|
|
/* we avoid recursing on the C stack */
|
|
return JS_NewTailCall(argc - 1);
|
|
}
|
|
|
|
JSValue js_function_apply(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValueArray *arr;
|
|
JSObject *p;
|
|
int len, i;
|
|
p = js_get_object_class(ctx, argv[1], JS_CLASS_ARRAY);
|
|
if (!p)
|
|
return JS_ThrowTypeError(ctx, "not an array");
|
|
arr = JS_VALUE_TO_PTR(p->u.array.tab);
|
|
len = p->u.array.len;
|
|
if (len > JS_MAX_ARGC)
|
|
return JS_ThrowTypeError(ctx, "too many call arguments");
|
|
if (JS_StackCheck(ctx, len + 2))
|
|
return JS_EXCEPTION;
|
|
p = JS_VALUE_TO_PTR(argv[1]);
|
|
arr = JS_VALUE_TO_PTR(p->u.array.tab);
|
|
for(i = 0; i < len; i++)
|
|
JS_PushArg(ctx, arr->arr[len - 1 - i]);
|
|
JS_PushArg(ctx, *this_val);
|
|
JS_PushArg(ctx, argv[0]);
|
|
/* we avoid recursing on the C stack */
|
|
return JS_NewTailCall(len);
|
|
}
|
|
|
|
JSValue js_function_bind(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
int arg_count;
|
|
JSValueArray *arr;
|
|
int i;
|
|
|
|
arg_count = max_int(argc - 1, 0);
|
|
arr = js_alloc_value_array(ctx, 0, 2 + arg_count);
|
|
if (!arr)
|
|
return JS_EXCEPTION;
|
|
/* arr[0] = func, arr[1] = this */
|
|
arr->arr[0] = *this_val;
|
|
for(i = 0; i < arg_count + 1; i++)
|
|
arr->arr[1 + i] = argv[i];
|
|
return JS_NewCFunctionParams(ctx, JS_CFUNCTION_bound, JS_VALUE_FROM_PTR(arr));
|
|
}
|
|
|
|
/* XXX: handle constructor case */
|
|
JSValue js_function_bound(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv, JSValue params)
|
|
{
|
|
JSValueArray *arr;
|
|
JSGCRef params_ref;
|
|
int i, err, size, argc2;
|
|
|
|
arr = JS_VALUE_TO_PTR(params);
|
|
size = arr->size;
|
|
JS_PUSH_VALUE(ctx, params);
|
|
err = JS_StackCheck(ctx, size + argc);
|
|
JS_POP_VALUE(ctx, params);
|
|
if (err)
|
|
return JS_EXCEPTION;
|
|
argc2 = size - 2 + argc;
|
|
if (argc2 > JS_MAX_ARGC)
|
|
return JS_ThrowTypeError(ctx, "too many call arguments");
|
|
arr = JS_VALUE_TO_PTR(params);
|
|
for(i = argc - 1; i >= 0; i--)
|
|
JS_PushArg(ctx, argv[i]);
|
|
for(i = size - 1; i >= 2; i--) {
|
|
JS_PushArg(ctx, arr->arr[i]);
|
|
}
|
|
JS_PushArg(ctx, arr->arr[0]); /* func */
|
|
JS_PushArg(ctx, arr->arr[1]); /* this_val */
|
|
/* we avoid recursing on the C stack */
|
|
return JS_NewTailCall(argc2);
|
|
}
|
|
|
|
/**********************************************************************/
|
|
|
|
JSValue js_number_constructor(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
double d;
|
|
if (argc & FRAME_CF_CTOR)
|
|
return JS_ThrowTypeError(ctx, "number constructor not supported");
|
|
if (argc == 0) {
|
|
return JS_NewShortInt(0);
|
|
} else {
|
|
if (JS_ToNumber(ctx, &d, argv[0]))
|
|
return JS_EXCEPTION;
|
|
return JS_NewFloat64(ctx, d);
|
|
}
|
|
}
|
|
|
|
static int js_thisNumberValue(JSContext *ctx, double *pres, JSValue val)
|
|
{
|
|
if (!JS_IsNumber(ctx, val)) {
|
|
JS_ThrowTypeError(ctx, "not a number");
|
|
return -1;
|
|
}
|
|
return JS_ToNumber(ctx, pres, val);
|
|
}
|
|
|
|
JSValue js_number_toString(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
int radix, flags;
|
|
double d;
|
|
|
|
if (js_thisNumberValue(ctx, &d, *this_val))
|
|
return JS_EXCEPTION;
|
|
if (JS_IsUndefined(argv[0])) {
|
|
radix = 10;
|
|
} else {
|
|
if (JS_ToInt32Sat(ctx, &radix, argv[0]))
|
|
return JS_EXCEPTION;
|
|
if (radix < 2 || radix > 36)
|
|
return JS_ThrowRangeError(ctx, "radix must be between 2 and 36");
|
|
}
|
|
/* cannot fail */
|
|
flags = JS_DTOA_FORMAT_FREE;
|
|
if (radix != 10)
|
|
flags |= JS_DTOA_EXP_DISABLED;
|
|
return js_dtoa2(ctx, d, radix, 0, flags);
|
|
}
|
|
|
|
JSValue js_number_toFixed(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
int f, flags;
|
|
double d;
|
|
|
|
if (js_thisNumberValue(ctx, &d, *this_val))
|
|
return JS_EXCEPTION;
|
|
if (JS_ToInt32Sat(ctx, &f, argv[0]))
|
|
return JS_EXCEPTION;
|
|
if (f < 0 || f > 100)
|
|
return JS_ThrowRangeError(ctx, "invalid number of digits");
|
|
if (fabs(d) >= 1e21) {
|
|
flags = JS_DTOA_FORMAT_FREE;
|
|
} else {
|
|
flags = JS_DTOA_FORMAT_FRAC;
|
|
}
|
|
return js_dtoa2(ctx, d, 10, f, flags);
|
|
}
|
|
|
|
JSValue js_number_toExponential(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
int f, flags;
|
|
double d;
|
|
|
|
if (js_thisNumberValue(ctx, &d, *this_val))
|
|
return JS_EXCEPTION;
|
|
if (JS_ToInt32Sat(ctx, &f, argv[0]))
|
|
return JS_EXCEPTION;
|
|
if (JS_IsUndefined(argv[0]) || !isfinite(d)) {
|
|
f = 0;
|
|
flags = JS_DTOA_FORMAT_FREE;
|
|
} else {
|
|
if (f < 0 || f > 100)
|
|
return JS_ThrowRangeError(ctx, "invalid number of digits");
|
|
f++;
|
|
flags = JS_DTOA_FORMAT_FIXED;
|
|
}
|
|
return js_dtoa2(ctx, d, 10, f, flags | JS_DTOA_EXP_ENABLED);
|
|
}
|
|
|
|
JSValue js_number_toPrecision(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
int p, flags;
|
|
double d;
|
|
|
|
if (js_thisNumberValue(ctx, &d, *this_val))
|
|
return JS_EXCEPTION;
|
|
if (JS_IsUndefined(argv[0])) {
|
|
flags = JS_DTOA_FORMAT_FREE;
|
|
p = 0;
|
|
} else {
|
|
if (JS_ToInt32Sat(ctx, &p, argv[0]))
|
|
return JS_EXCEPTION;
|
|
if (!isfinite(d)) {
|
|
flags = JS_DTOA_FORMAT_FREE;
|
|
} else {
|
|
if (p < 1 || p > 100)
|
|
return JS_ThrowRangeError(ctx, "invalid number of digits");
|
|
flags = JS_DTOA_FORMAT_FIXED;
|
|
}
|
|
}
|
|
return js_dtoa2(ctx, d, 10, p, flags);
|
|
}
|
|
|
|
JSValue js_number_parseInt(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
int radix;
|
|
double d;
|
|
|
|
argv[0] = JS_ToString(ctx, argv[0]);
|
|
if (JS_IsException(argv[0]))
|
|
return JS_EXCEPTION;
|
|
if (JS_ToInt32(ctx, &radix, argv[1]))
|
|
return JS_EXCEPTION;
|
|
if (radix != 0 && (radix < 2 || radix > 36)) {
|
|
d = NAN;
|
|
} else {
|
|
if (js_atod1(ctx, &d, argv[0], radix, JS_ATOD_INT_ONLY))
|
|
return JS_EXCEPTION;
|
|
}
|
|
return JS_NewFloat64(ctx, d);
|
|
}
|
|
|
|
JSValue js_number_parseFloat(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
double d;
|
|
|
|
argv[0] = JS_ToString(ctx, argv[0]);
|
|
if (JS_IsException(argv[0]))
|
|
return JS_EXCEPTION;
|
|
if (js_atod1(ctx, &d, argv[0], 10, 0))
|
|
return JS_EXCEPTION;
|
|
return JS_NewFloat64(ctx, d);
|
|
}
|
|
|
|
/**********************************************************************/
|
|
|
|
JSValue js_boolean_constructor(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
if (argc & FRAME_CF_CTOR)
|
|
return JS_ThrowTypeError(ctx, "Boolean constructor not supported");
|
|
return JS_NewBool(JS_ToBool(ctx, argv[0]));
|
|
}
|
|
|
|
/**********************************************************************/
|
|
|
|
JSValue js_string_get_length(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
int len;
|
|
|
|
if (!JS_IsString(ctx, *this_val))
|
|
return JS_ThrowTypeError(ctx, "not a string");
|
|
len = js_string_len(ctx, *this_val);
|
|
return JS_NewShortInt(len);
|
|
}
|
|
|
|
JSValue js_string_set_length(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
return JS_UNDEFINED; /* ignored */
|
|
}
|
|
|
|
JSValue js_string_slice(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
int len, start, end;
|
|
|
|
*this_val = JS_ToStringCheckObject(ctx, *this_val);
|
|
if (JS_IsException(*this_val))
|
|
return JS_EXCEPTION;
|
|
len = js_string_len(ctx, *this_val);
|
|
if (JS_ToInt32Clamp(ctx, &start, argv[0], 0, len, len))
|
|
return JS_EXCEPTION;
|
|
end = len;
|
|
if (!JS_IsUndefined(argv[1])) {
|
|
if (JS_ToInt32Clamp(ctx, &end, argv[1], 0, len, len))
|
|
return JS_EXCEPTION;
|
|
}
|
|
return js_sub_string(ctx, *this_val, start, max_int(end, start));
|
|
}
|
|
|
|
JSValue js_string_substring(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
int a, b, start, end, len;
|
|
|
|
*this_val = JS_ToStringCheckObject(ctx, *this_val);
|
|
if (JS_IsException(*this_val))
|
|
return JS_EXCEPTION;
|
|
len = js_string_len(ctx, *this_val);
|
|
if (JS_ToInt32Clamp(ctx, &a, argv[0], 0, len, 0))
|
|
return JS_EXCEPTION;
|
|
b = len;
|
|
if (!JS_IsUndefined(argv[1])) {
|
|
if (JS_ToInt32Clamp(ctx, &b, argv[1], 0, len, 0))
|
|
return JS_EXCEPTION;
|
|
}
|
|
if (a < b) {
|
|
start = a;
|
|
end = b;
|
|
} else {
|
|
start = b;
|
|
end = a;
|
|
}
|
|
return js_sub_string(ctx, *this_val, start, end);
|
|
}
|
|
|
|
JSValue js_string_charAt(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv, int magic)
|
|
{
|
|
JSValue ret;
|
|
int idx, c;
|
|
|
|
*this_val = JS_ToStringCheckObject(ctx, *this_val);
|
|
if (JS_IsException(*this_val))
|
|
return JS_EXCEPTION;
|
|
if (JS_ToInt32Sat(ctx, &idx, argv[0]))
|
|
return JS_EXCEPTION;
|
|
if (idx < 0)
|
|
goto ret_undef;
|
|
c = string_getcp(ctx, *this_val, idx, (magic == magic_codePointAt));
|
|
if (c == -1) {
|
|
ret_undef:
|
|
if (magic == magic_charCodeAt)
|
|
ret = JS_NewFloat64(ctx, NAN);
|
|
else if (magic == magic_charAt)
|
|
ret = js_get_atom(ctx, JS_ATOM_empty);
|
|
else
|
|
ret = JS_UNDEFINED;
|
|
} else {
|
|
if (magic == magic_charCodeAt || magic == magic_codePointAt)
|
|
ret = JS_NewShortInt(c);
|
|
else
|
|
ret = JS_NewStringChar(c);
|
|
}
|
|
// dump_string_pos_cache(ctx);
|
|
return ret;
|
|
}
|
|
|
|
JSValue js_string_constructor(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
if (argc & FRAME_CF_CTOR)
|
|
return JS_ThrowTypeError(ctx, "string constructor not supported");
|
|
if (argc <= 0) {
|
|
return js_get_atom(ctx, JS_ATOM_empty);
|
|
} else {
|
|
return JS_ToString(ctx, argv[0]);
|
|
}
|
|
}
|
|
|
|
JSValue js_string_fromCharCode(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv, int is_fromCodePoint)
|
|
{
|
|
int i;
|
|
StringBuffer b_s, *b = &b_s;
|
|
|
|
string_buffer_push(ctx, b, 0);
|
|
for(i = 0; i < argc; i++) {
|
|
int c;
|
|
if (JS_ToInt32(ctx, &c, argv[i]))
|
|
goto fail;
|
|
if (is_fromCodePoint) {
|
|
if (c < 0 || c > 0x10ffff) {
|
|
JS_ThrowRangeError(ctx, "invalid code point");
|
|
goto fail;
|
|
}
|
|
} else {
|
|
c &= 0xffff;
|
|
}
|
|
if (string_buffer_putc(ctx, b, c))
|
|
break;
|
|
}
|
|
return string_buffer_pop(ctx, b);
|
|
fail:
|
|
string_buffer_pop(ctx, b);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
JSValue js_string_concat(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
int i;
|
|
StringBuffer b_s, *b = &b_s;
|
|
JSValue r;
|
|
|
|
r = JS_ToStringCheckObject(ctx, *this_val);
|
|
if (JS_IsException(r))
|
|
return JS_EXCEPTION;
|
|
string_buffer_push(ctx, b, 0);
|
|
if (string_buffer_concat(ctx, b, r))
|
|
goto done;
|
|
|
|
for (i = 0; i < argc; i++) {
|
|
if (string_buffer_concat(ctx, b, argv[i]))
|
|
goto done;
|
|
}
|
|
done:
|
|
return string_buffer_pop(ctx, b);
|
|
}
|
|
|
|
JSValue js_string_indexOf(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv, int lastIndexOf)
|
|
{
|
|
int i, len, v_len, pos, start, stop, ret, inc, j;
|
|
|
|
*this_val = JS_ToStringCheckObject(ctx, *this_val);
|
|
if (JS_IsException(*this_val))
|
|
return JS_EXCEPTION;
|
|
argv[0] = JS_ToString(ctx, argv[0]);
|
|
if (JS_IsException(argv[0]))
|
|
return JS_EXCEPTION;
|
|
len = js_string_len(ctx, *this_val);
|
|
v_len = js_string_len(ctx, argv[0]);
|
|
if (lastIndexOf) {
|
|
pos = len - v_len;
|
|
if (argc > 1) {
|
|
double d;
|
|
if (JS_ToNumber(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) {
|
|
for(j = 0; j < v_len; j++) {
|
|
if (string_getc(ctx, *this_val, i + j) != string_getc(ctx, argv[0], j)) {
|
|
goto next;
|
|
}
|
|
}
|
|
ret = i;
|
|
break;
|
|
next:
|
|
if (i == stop)
|
|
break;
|
|
}
|
|
}
|
|
return JS_NewShortInt(ret);
|
|
|
|
fail:
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static int js_string_indexof(JSContext *ctx, JSValue str, JSValue needle,
|
|
int start, int str_len, int needle_len)
|
|
{
|
|
int i, j;
|
|
for(i = start; i <= str_len - needle_len; i++) {
|
|
for(j = 0; j < needle_len; j++) {
|
|
if (string_getc(ctx, str, i + j) !=
|
|
string_getc(ctx, needle, j)) {
|
|
goto next;
|
|
}
|
|
|
|
}
|
|
return i;
|
|
next: ;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/* Note: ascii only */
|
|
JSValue js_string_toLowerCase(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv, int to_lower)
|
|
{
|
|
StringBuffer b_s, *b = &b_s;
|
|
int i, c, len;
|
|
|
|
*this_val = JS_ToStringCheckObject(ctx, *this_val);
|
|
if (JS_IsException(*this_val))
|
|
return *this_val;
|
|
len = js_string_len(ctx, *this_val);
|
|
if (string_buffer_push(ctx, b, len))
|
|
return JS_EXCEPTION;
|
|
for(i = 0; i < len; i++) {
|
|
c = string_getc(ctx, *this_val, i);
|
|
if (to_lower) {
|
|
if (c >= 'A' && c <= 'Z')
|
|
c += 'a' - 'A';
|
|
} else {
|
|
if (c >= 'a' && c <= 'z')
|
|
c += 'A' - 'a';
|
|
}
|
|
string_buffer_putc(ctx, b, c);
|
|
}
|
|
return string_buffer_pop(ctx, b);
|
|
}
|
|
|
|
/* c < 128 */
|
|
static force_inline BOOL unicode_is_space_ascii(uint32_t c)
|
|
{
|
|
return (c >= 0x0009 && c <= 0x000D) || (c == 0x0020);
|
|
}
|
|
|
|
static BOOL unicode_is_space_non_ascii(uint32_t c)
|
|
{
|
|
return (c == 0x00A0 ||
|
|
c == 0x1680 ||
|
|
(c >= 0x2000 && c <= 0x200A) ||
|
|
(c >= 0x2028 && c <= 0x2029) ||
|
|
c == 0x202F ||
|
|
c == 0x205F ||
|
|
c == 0x3000 ||
|
|
c == 0xFEFF);
|
|
}
|
|
|
|
static force_inline BOOL unicode_is_space(uint32_t c)
|
|
{
|
|
if (likely(c < 128)) {
|
|
return unicode_is_space_ascii(c);
|
|
} else {
|
|
return unicode_is_space_non_ascii(c);
|
|
}
|
|
}
|
|
|
|
JSValue js_string_trim(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv, int magic)
|
|
{
|
|
int a, b, len;
|
|
|
|
*this_val = JS_ToStringCheckObject(ctx, *this_val);
|
|
if (JS_IsException(*this_val))
|
|
return *this_val;
|
|
len = js_string_len(ctx, *this_val);
|
|
a = 0;
|
|
b = len;
|
|
if (magic & 1) {
|
|
while (a < len && unicode_is_space(string_getc(ctx, *this_val, a)))
|
|
a++;
|
|
}
|
|
if (magic & 2) {
|
|
while (b > a && unicode_is_space(string_getc(ctx, *this_val, b - 1)))
|
|
b--;
|
|
}
|
|
return js_sub_string(ctx, *this_val, a, b);
|
|
}
|
|
|
|
JSValue js_string_toString(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
if (!JS_IsString(ctx, *this_val))
|
|
return JS_ThrowTypeError(ctx, "not a string");
|
|
return *this_val;
|
|
}
|
|
|
|
JSValue js_string_repeat(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
StringBuffer b_s, *b = &b_s;
|
|
JSStringCharBuf buf;
|
|
JSString *p;
|
|
int n;
|
|
int64_t len;
|
|
|
|
if (!JS_IsString(ctx, *this_val))
|
|
return JS_ThrowTypeError(ctx, "not a string");
|
|
if (JS_ToInt32Sat(ctx, &n, argv[0]))
|
|
return -1;
|
|
p = get_string_ptr(ctx, &buf, *this_val);
|
|
if (n < 0 || (len = (int64_t)n * p->len) > JS_STRING_LEN_MAX)
|
|
return JS_ThrowRangeError(ctx, "invalid repeat count");
|
|
if (p->len == 0 || n == 1)
|
|
return *this_val;
|
|
if (string_buffer_push(ctx, b, len))
|
|
return JS_EXCEPTION;
|
|
while (n-- > 0) {
|
|
string_buffer_concat_str(ctx, b, *this_val);
|
|
}
|
|
return string_buffer_pop(ctx, b);
|
|
}
|
|
|
|
/**********************************************************************/
|
|
|
|
JSValue js_object_constructor(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
/* XXX: incomplete */
|
|
argc &= ~FRAME_CF_CTOR;
|
|
if (argc <= 0) {
|
|
return JS_NewObject(ctx);
|
|
} else {
|
|
return argv[0];
|
|
}
|
|
}
|
|
|
|
JSValue js_object_defineProperty(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue *pobj, *pprop, *pdesc;
|
|
JSValue val, getter, setter;
|
|
JSGCRef val_ref, getter_ref;
|
|
int flags;
|
|
|
|
pobj = &argv[0];
|
|
pprop = &argv[1];
|
|
pdesc = &argv[2];
|
|
|
|
if (!JS_IsObject(ctx, *pobj))
|
|
return JS_ThrowTypeErrorNotAnObject(ctx);
|
|
*pprop = JS_ToPropertyKey(ctx, *pprop);
|
|
if (JS_IsException(*pprop))
|
|
return JS_EXCEPTION;
|
|
val = JS_UNDEFINED;
|
|
getter = JS_UNDEFINED;
|
|
setter = JS_UNDEFINED;
|
|
flags = 0;
|
|
if (JS_HasProperty(ctx, *pdesc, js_get_atom(ctx, JS_ATOM_value))) {
|
|
flags |= JS_DEF_PROP_HAS_VALUE;
|
|
val = JS_GetProperty(ctx, *pdesc, js_get_atom(ctx, JS_ATOM_value));
|
|
if (JS_IsException(val))
|
|
return JS_EXCEPTION;
|
|
}
|
|
if (JS_HasProperty(ctx, *pdesc, js_get_atom(ctx, JS_ATOM_get))) {
|
|
flags |= JS_DEF_PROP_HAS_GET;
|
|
JS_PUSH_VALUE(ctx, val);
|
|
getter = JS_GetProperty(ctx, *pdesc, js_get_atom(ctx, JS_ATOM_get));
|
|
JS_POP_VALUE(ctx, val);
|
|
if (JS_IsException(getter))
|
|
return JS_EXCEPTION;
|
|
if (!JS_IsUndefined(getter) && !JS_IsFunction(ctx, getter))
|
|
goto bad_getset;
|
|
}
|
|
if (JS_HasProperty(ctx, *pdesc, js_get_atom(ctx, JS_ATOM_set))) {
|
|
flags |= JS_DEF_PROP_HAS_SET;
|
|
JS_PUSH_VALUE(ctx, val);
|
|
JS_PUSH_VALUE(ctx, getter);
|
|
setter = JS_GetProperty(ctx, *pdesc, js_get_atom(ctx, JS_ATOM_set));
|
|
JS_POP_VALUE(ctx, getter);
|
|
JS_POP_VALUE(ctx, val);
|
|
if (JS_IsException(setter))
|
|
return JS_EXCEPTION;
|
|
if (!JS_IsUndefined(setter) && !JS_IsFunction(ctx, setter)) {
|
|
bad_getset:
|
|
return JS_ThrowTypeError(ctx, "invalid getter or setter");
|
|
}
|
|
}
|
|
if (flags & (JS_DEF_PROP_HAS_GET | JS_DEF_PROP_HAS_SET)) {
|
|
if (flags & JS_DEF_PROP_HAS_VALUE)
|
|
return JS_ThrowTypeError(ctx, "cannot have both value and get/set");
|
|
val = getter;
|
|
}
|
|
val = JS_DefinePropertyInternal(ctx, *pobj, *pprop, val, setter,
|
|
flags | JS_DEF_PROP_LOOKUP);
|
|
if (JS_IsException(val))
|
|
return val;
|
|
return *pobj;
|
|
}
|
|
|
|
JSValue js_object_getPrototypeOf(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSObject *p;
|
|
if (!JS_IsObject(ctx, argv[0]))
|
|
return JS_ThrowTypeErrorNotAnObject(ctx);
|
|
p = JS_VALUE_TO_PTR(argv[0]);
|
|
return p->proto;
|
|
}
|
|
|
|
/* 'obj' must be an object. 'proto' must be JS_NULL or an object */
|
|
static JSValue js_set_prototype_internal(JSContext *ctx, JSValue obj, JSValue proto)
|
|
{
|
|
JSObject *p, *p1;
|
|
|
|
p = JS_VALUE_TO_PTR(obj);
|
|
if (p->proto != proto) {
|
|
if (proto != JS_NULL) {
|
|
/* check if there is a cycle */
|
|
p1 = JS_VALUE_TO_PTR(proto);
|
|
for(;;) {
|
|
if (p1 == p)
|
|
return JS_ThrowTypeError(ctx, "circular prototype chain");
|
|
if (p1->proto == JS_NULL)
|
|
break;
|
|
p1 = JS_VALUE_TO_PTR(p1->proto);
|
|
}
|
|
}
|
|
|
|
p->proto = proto;
|
|
}
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
JSValue js_object_setPrototypeOf(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue proto;
|
|
|
|
if (!JS_IsObject(ctx, argv[0]))
|
|
return JS_ThrowTypeErrorNotAnObject(ctx);
|
|
proto = argv[1];
|
|
if (proto != JS_NULL && !JS_IsObject(ctx, proto))
|
|
return JS_ThrowTypeError(ctx, "not a prototype");
|
|
if (JS_IsException(js_set_prototype_internal(ctx, argv[0], proto)))
|
|
return JS_EXCEPTION;
|
|
return argv[0];
|
|
}
|
|
|
|
JSValue js_object_create(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue proto;
|
|
proto = argv[0];
|
|
if (proto != JS_NULL && !JS_IsObject(ctx, proto))
|
|
return JS_ThrowTypeError(ctx, "not a prototype");
|
|
if (argc >= 2)
|
|
return JS_ThrowTypeError(ctx, "unsupported additional properties");
|
|
return JS_NewObjectProtoClass(ctx, proto, JS_CLASS_OBJECT, 0);
|
|
}
|
|
|
|
JSValue js_object_keys(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSObject *p, *pret;
|
|
JSValue ret, str;
|
|
JSValueArray *arr, *ret_arr;
|
|
int array_len, prop_count, hash_mask, alloc_size, i, j, pos;
|
|
JSGCRef ret_ref;
|
|
|
|
if (!JS_IsObject(ctx, argv[0]))
|
|
return JS_ThrowTypeErrorNotAnObject(ctx);
|
|
p = JS_VALUE_TO_PTR(argv[0]);
|
|
|
|
if (p->class_id == JS_CLASS_ARRAY) {
|
|
array_len = p->u.array.len;
|
|
} else if (p->class_id >= JS_CLASS_UINT8C_ARRAY && p->class_id <= JS_CLASS_FLOAT64_ARRAY) {
|
|
array_len = p->u.typed_array.len;
|
|
} else {
|
|
array_len = 0;
|
|
}
|
|
|
|
arr = JS_VALUE_TO_PTR(p->props);
|
|
prop_count = JS_VALUE_GET_INT(arr->arr[0]);
|
|
hash_mask = JS_VALUE_GET_INT(arr->arr[1]);
|
|
|
|
alloc_size = array_len + prop_count;
|
|
|
|
ret = JS_NewArray(ctx, alloc_size);
|
|
if (JS_IsException(ret))
|
|
return ret;
|
|
|
|
pos = 0;
|
|
for(i = 0; i < array_len; i++) {
|
|
JS_PUSH_VALUE(ctx, ret);
|
|
str = JS_ToString(ctx, JS_NewShortInt(i));
|
|
JS_POP_VALUE(ctx, ret);
|
|
if (JS_IsException(str))
|
|
return str;
|
|
pret = JS_VALUE_TO_PTR(ret);
|
|
ret_arr = JS_VALUE_TO_PTR(pret->u.array.tab);
|
|
ret_arr->arr[pos++] = str;
|
|
}
|
|
|
|
for(i = 0, j = 0; j < prop_count; i++) {
|
|
JSProperty *pr;
|
|
p = JS_VALUE_TO_PTR(argv[0]);
|
|
arr = JS_VALUE_TO_PTR(p->props);
|
|
pr = (JSProperty *)&arr->arr[2 + hash_mask + 1 + 3 * i];
|
|
/* exclude deleted properties */
|
|
if (pr->key != JS_UNINITIALIZED) {
|
|
JS_PUSH_VALUE(ctx, ret);
|
|
str = JS_ToString(ctx, pr->key);
|
|
JS_POP_VALUE(ctx, ret);
|
|
if (JS_IsException(str))
|
|
return str;
|
|
pret = JS_VALUE_TO_PTR(ret);
|
|
ret_arr = JS_VALUE_TO_PTR(pret->u.array.tab);
|
|
ret_arr->arr[pos++] = str;
|
|
j++;
|
|
}
|
|
}
|
|
pret = JS_VALUE_TO_PTR(ret);
|
|
pret->u.array.len = pos;
|
|
return ret;
|
|
}
|
|
|
|
JSValue js_object_hasOwnProperty(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSObject *p;
|
|
JSValue prop;
|
|
int array_len, idx;
|
|
|
|
if (JS_IsNull(*this_val) || JS_IsUndefined(*this_val))
|
|
return JS_ThrowTypeError(ctx, "cannot convert to object");
|
|
if (!JS_IsObject(ctx, *this_val))
|
|
return JS_FALSE; /* XXX: could improve for strings */
|
|
prop = JS_ToPropertyKey(ctx, argv[0]);
|
|
p = JS_VALUE_TO_PTR(*this_val);
|
|
if (p->class_id == JS_CLASS_ARRAY) {
|
|
array_len = p->u.array.len;
|
|
goto check_array;
|
|
} else if (p->class_id >= JS_CLASS_UINT8C_ARRAY && p->class_id <= JS_CLASS_FLOAT64_ARRAY) {
|
|
array_len = p->u.typed_array.len;
|
|
check_array:
|
|
if (JS_IsInt(prop)) {
|
|
idx = JS_VALUE_GET_INT(prop);
|
|
return JS_NewBool((idx >= 0 && idx < array_len));
|
|
}
|
|
}
|
|
return JS_NewBool((find_own_property(ctx, p, prop) != NULL));
|
|
}
|
|
|
|
JSValue js_object_toString(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
const char *str;
|
|
char buf[64];
|
|
/* XXX: not fully compliant */
|
|
if (JS_IsIntOrShortFloat(*this_val)) {
|
|
goto number;
|
|
} else if (!JS_IsPtr(*this_val)) {
|
|
switch(JS_VALUE_GET_SPECIAL_TAG(*this_val)) {
|
|
case JS_TAG_NULL:
|
|
str = "Null";
|
|
break;
|
|
case JS_TAG_UNDEFINED:
|
|
str = "Undefined";
|
|
break;
|
|
case JS_TAG_SHORT_FUNC:
|
|
str = "Function";
|
|
break;
|
|
case JS_TAG_BOOL:
|
|
str = "Boolean";
|
|
break;
|
|
case JS_TAG_STRING_CHAR:
|
|
goto string;
|
|
default:
|
|
goto object;
|
|
}
|
|
} else {
|
|
JSObject *p = JS_VALUE_TO_PTR(*this_val);
|
|
switch(p->mtag) {
|
|
case JS_MTAG_OBJECT:
|
|
switch(p->class_id) {
|
|
case JS_CLASS_ARRAY:
|
|
str = "Array";
|
|
break;
|
|
case JS_CLASS_ERROR:
|
|
str = "Error";
|
|
break;
|
|
case JS_CLASS_CLOSURE:
|
|
case JS_CLASS_C_FUNCTION:
|
|
str = "Function";
|
|
break;
|
|
default:
|
|
object:
|
|
str = "Object";
|
|
break;
|
|
}
|
|
break;
|
|
case JS_MTAG_STRING:
|
|
string:
|
|
str = "String";
|
|
break;
|
|
case JS_MTAG_FLOAT64:
|
|
number:
|
|
str = "Number";
|
|
break;
|
|
default:
|
|
goto object;
|
|
}
|
|
}
|
|
js_snprintf(buf, sizeof(buf), "[object %s]", str);
|
|
return JS_NewString(ctx, buf);
|
|
}
|
|
|
|
/**********************************************************************/
|
|
|
|
JSValue js_error_constructor(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv, int magic)
|
|
{
|
|
JSValue obj, msg;
|
|
JSObject *p;
|
|
JSGCRef obj_ref;
|
|
|
|
argc &= ~FRAME_CF_CTOR;
|
|
|
|
obj = JS_NewObjectProtoClass(ctx, ctx->class_proto[magic], JS_CLASS_ERROR,
|
|
sizeof(JSErrorData));
|
|
if (JS_IsException(obj))
|
|
return obj;
|
|
p = JS_VALUE_TO_PTR(obj);
|
|
p->u.error.message = JS_NULL;
|
|
p->u.error.stack = JS_NULL;
|
|
|
|
if (!JS_IsUndefined(argv[0])) {
|
|
JS_PUSH_VALUE(ctx, obj);
|
|
msg = JS_ToString(ctx, argv[0]);
|
|
JS_POP_VALUE(ctx, obj);
|
|
if (JS_IsException(msg))
|
|
return msg;
|
|
p = JS_VALUE_TO_PTR(obj);
|
|
p->u.error.message = msg;
|
|
} else {
|
|
p = JS_VALUE_TO_PTR(obj);
|
|
p->u.error.message = js_get_atom(ctx, JS_ATOM_empty);
|
|
}
|
|
JS_PUSH_VALUE(ctx, obj);
|
|
build_backtrace(ctx, obj, NULL, 0, 0, 1);
|
|
JS_POP_VALUE(ctx, obj);
|
|
return obj;
|
|
}
|
|
|
|
JSValue js_error_toString(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSObject *p;
|
|
JSValue name;
|
|
StringBuffer b_s, *b = &b_s;
|
|
|
|
if (!JS_IsError(ctx, *this_val))
|
|
return JS_ThrowTypeError(ctx, "not an Error object");
|
|
name = JS_GetProperty(ctx, *this_val, js_get_atom(ctx, JS_ATOM_name));
|
|
if (JS_IsException(name))
|
|
return name;
|
|
if (JS_IsUndefined(name))
|
|
name = js_get_atom(ctx, JS_ATOM_Error);
|
|
else
|
|
name = JS_ToString(ctx, name);
|
|
if (JS_IsException(name))
|
|
return name;
|
|
string_buffer_push(ctx, b, 0);
|
|
string_buffer_concat(ctx, b, name);
|
|
p = JS_VALUE_TO_PTR(*this_val);
|
|
if (p->u.error.message != JS_NULL) {
|
|
string_buffer_puts(ctx, b, ": ");
|
|
p = JS_VALUE_TO_PTR(*this_val);
|
|
string_buffer_concat(ctx, b, p->u.error.message);
|
|
}
|
|
return string_buffer_pop(ctx, b);
|
|
}
|
|
|
|
JSValue js_error_get_message(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv, int magic)
|
|
{
|
|
JSObject *p;
|
|
if (!JS_IsError(ctx, *this_val))
|
|
return JS_ThrowTypeError(ctx, "not an Error object");
|
|
p = JS_VALUE_TO_PTR(*this_val);
|
|
if (magic == 0)
|
|
return p->u.error.message;
|
|
else
|
|
return p->u.error.stack;
|
|
}
|
|
|
|
/**********************************************************************/
|
|
|
|
static JSObject *js_get_array(JSContext *ctx, JSValue obj)
|
|
{
|
|
JSObject *p;
|
|
p = js_get_object_class(ctx, obj, JS_CLASS_ARRAY);
|
|
if (!p) {
|
|
JS_ThrowTypeError(ctx, "not an array");
|
|
return NULL;
|
|
}
|
|
return p;
|
|
}
|
|
|
|
JSValue js_array_get_length(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSObject *p;
|
|
p = js_get_array(ctx, *this_val);
|
|
if (!p)
|
|
return JS_EXCEPTION;
|
|
return JS_NewShortInt(p->u.array.len);
|
|
}
|
|
|
|
static int js_array_resize(JSContext *ctx, JSValue *this_val, int new_len)
|
|
{
|
|
JSObject *p;
|
|
int i;
|
|
|
|
if (new_len < 0 || new_len > JS_SHORTINT_MAX) {
|
|
JS_ThrowTypeError(ctx, "invalid array length");
|
|
return -1;
|
|
}
|
|
p = JS_VALUE_TO_PTR(*this_val);
|
|
if (new_len < p->u.array.len) {
|
|
JSValueArray *arr = JS_VALUE_TO_PTR(p->u.array.tab);
|
|
/* shrink the array if the new size is small enough */
|
|
if (new_len < (arr->size / 2) && arr->size >= 4) {
|
|
js_shrink_value_array(ctx, &p->u.array.tab, new_len);
|
|
p = JS_VALUE_TO_PTR(*this_val);
|
|
} else {
|
|
for(i = new_len; i < p->u.array.len; i++)
|
|
arr->arr[i] = JS_UNDEFINED;
|
|
}
|
|
} else if (new_len > p->u.array.len) {
|
|
JSValueArray *arr;
|
|
JSValue new_tab;
|
|
new_tab = js_resize_value_array(ctx, p->u.array.tab, new_len);
|
|
if (JS_IsException(new_tab))
|
|
return -1;
|
|
p = JS_VALUE_TO_PTR(*this_val);
|
|
p->u.array.tab = new_tab;
|
|
arr = JS_VALUE_TO_PTR(p->u.array.tab);
|
|
for(i = p->u.array.len; i < new_len; i++)
|
|
arr->arr[i] = JS_UNDEFINED;
|
|
}
|
|
p->u.array.len = new_len;
|
|
return 0;
|
|
}
|
|
|
|
JSValue js_array_set_length(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
int new_len;
|
|
|
|
if (!js_get_array(ctx, *this_val))
|
|
return JS_EXCEPTION;
|
|
if (JS_ToInt32(ctx, &new_len, argv[0]))
|
|
return JS_EXCEPTION;
|
|
if (js_array_resize(ctx, this_val, new_len))
|
|
return JS_EXCEPTION;
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
JSValue js_array_constructor(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue obj;
|
|
JSObject *p;
|
|
int len, i;
|
|
BOOL has_init;
|
|
|
|
argc &= ~FRAME_CF_CTOR;
|
|
|
|
if (argc == 1 && JS_IsNumber(ctx, argv[0])) {
|
|
/* XXX: we create undefined properties instead of just setting the length */
|
|
if (JS_ToInt32(ctx, &len, argv[0]))
|
|
return JS_EXCEPTION;
|
|
has_init = FALSE;
|
|
} else {
|
|
len = argc;
|
|
has_init = TRUE;
|
|
}
|
|
|
|
if (len < 0 || len > JS_SHORTINT_MAX)
|
|
return JS_ThrowRangeError(ctx, "invalid array length");
|
|
obj = JS_NewArray(ctx, len);
|
|
if (JS_IsException(obj))
|
|
return obj;
|
|
p = JS_VALUE_TO_PTR(obj);
|
|
p->u.array.len = len;
|
|
|
|
if (has_init) {
|
|
JSValueArray *arr = JS_VALUE_TO_PTR(p->u.array.tab);
|
|
for(i = 0; i < argc; i++) {
|
|
arr->arr[i] = argv[i];
|
|
}
|
|
}
|
|
return obj;
|
|
}
|
|
|
|
JSValue js_array_push(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv, int is_unshift)
|
|
{
|
|
JSObject *p;
|
|
int new_len, i, from;
|
|
JSValueArray *arr;
|
|
JSValue new_tab;
|
|
|
|
p = js_get_array(ctx, *this_val);
|
|
if (!p)
|
|
return JS_EXCEPTION;
|
|
from = p->u.array.len;
|
|
new_len = from + argc;
|
|
if (new_len > JS_SHORTINT_MAX)
|
|
return JS_ThrowRangeError(ctx, "invalid array length");
|
|
new_tab = js_resize_value_array(ctx, p->u.array.tab, new_len);
|
|
if (JS_IsException(new_tab))
|
|
return JS_EXCEPTION;
|
|
p = JS_VALUE_TO_PTR(*this_val);
|
|
p->u.array.tab = new_tab;
|
|
p->u.array.len = new_len;
|
|
arr = JS_VALUE_TO_PTR(p->u.array.tab);
|
|
if (is_unshift && argc > 0) {
|
|
memmove(arr->arr + argc, arr->arr, from * sizeof(JSValue));
|
|
from = 0;
|
|
}
|
|
for(i = 0; i < argc; i++) {
|
|
arr->arr[from + i] = argv[i];
|
|
}
|
|
return JS_NewShortInt(new_len);
|
|
}
|
|
|
|
JSValue js_array_pop(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSObject *p;
|
|
JSValue ret;
|
|
|
|
p = js_get_array(ctx, *this_val);
|
|
if (!p)
|
|
return JS_EXCEPTION;
|
|
if (p->u.array.len > 0) {
|
|
JSValueArray *arr = JS_VALUE_TO_PTR(p->u.array.tab);
|
|
ret = arr->arr[--p->u.array.len];
|
|
} else {
|
|
ret = JS_UNDEFINED;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
JSValue js_array_shift(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSObject *p;
|
|
JSValue ret;
|
|
|
|
p = js_get_array(ctx, *this_val);
|
|
if (!p)
|
|
return JS_EXCEPTION;
|
|
if (p->u.array.len > 0) {
|
|
JSValueArray *arr = JS_VALUE_TO_PTR(p->u.array.tab);
|
|
ret = arr->arr[0];
|
|
p->u.array.len--;
|
|
memmove(arr->arr, arr->arr + 1, p->u.array.len * sizeof(JSValue));
|
|
} else {
|
|
ret = JS_UNDEFINED;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
JSValue js_array_join(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
uint32_t i, len;
|
|
BOOL is_array;
|
|
JSValue sep, val;
|
|
JSGCRef sep_ref;
|
|
JSObject *p;
|
|
JSValueArray *arr;
|
|
StringBuffer b_s, *b = &b_s;
|
|
|
|
if (!JS_IsObject(ctx, *this_val))
|
|
return JS_ThrowTypeErrorNotAnObject(ctx);
|
|
p = JS_VALUE_TO_PTR(*this_val);
|
|
is_array = (p->class_id == JS_CLASS_ARRAY);
|
|
if (is_array) {
|
|
len = p->u.array.len;
|
|
} else {
|
|
if (js_get_length32(ctx, &len, *this_val))
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
if (argc > 0 && !JS_IsUndefined(argv[0])) {
|
|
sep = JS_ToString(ctx, argv[0]);
|
|
if (JS_IsException(sep))
|
|
return sep;
|
|
} else {
|
|
sep = JS_NewStringChar(',');
|
|
}
|
|
JS_PUSH_VALUE(ctx, sep);
|
|
|
|
string_buffer_push(ctx, b, 0);
|
|
for(i = 0; i < len; i++) {
|
|
if (i > 0) {
|
|
if (string_buffer_concat(ctx, b, sep_ref.val))
|
|
goto exception;
|
|
}
|
|
if (is_array) {
|
|
p = JS_VALUE_TO_PTR(*this_val);
|
|
arr = JS_VALUE_TO_PTR(p->u.array.tab);
|
|
if (i < p->u.array.len)
|
|
val = arr->arr[i];
|
|
else
|
|
val = JS_UNDEFINED;
|
|
} else {
|
|
val = JS_GetPropertyUint32(ctx, *this_val, i);
|
|
if (JS_IsException(val))
|
|
goto exception;
|
|
}
|
|
if (!JS_IsUndefined(val) && !JS_IsNull(val)) {
|
|
if (string_buffer_concat(ctx, b, val))
|
|
goto exception;
|
|
}
|
|
}
|
|
val = string_buffer_pop(ctx, b);
|
|
JS_POP_VALUE(ctx, sep);
|
|
return val;
|
|
|
|
exception:
|
|
string_buffer_pop(ctx, b);
|
|
JS_POP_VALUE(ctx, sep);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
JSValue js_array_toString(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
return js_array_join(ctx, this_val, 0, NULL);
|
|
}
|
|
|
|
JSValue js_array_isArray(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSObject *p;
|
|
p = js_get_object_class(ctx, argv[0], JS_CLASS_ARRAY);
|
|
return JS_NewBool(p != NULL);
|
|
}
|
|
|
|
JSValue js_array_reverse(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
int len;
|
|
JSObject *p;
|
|
JSValueArray *arr;
|
|
|
|
p = js_get_array(ctx, *this_val);
|
|
if (!p)
|
|
return JS_EXCEPTION;
|
|
len = p->u.array.len;
|
|
arr = JS_VALUE_TO_PTR(p->u.array.tab);
|
|
js_reverse_val(arr->arr, len);
|
|
return *this_val;
|
|
}
|
|
|
|
JSValue js_array_concat(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSObject *p;
|
|
int len, i, j, pos;
|
|
int64_t len64;
|
|
JSValue obj, val;
|
|
JSValueArray *arr, *arr1;
|
|
|
|
p = js_get_array(ctx, *this_val);
|
|
if (!p)
|
|
return JS_EXCEPTION;
|
|
/* do a first pass to estimate the length */
|
|
len64 = p->u.array.len;
|
|
for(i = 0; i < argc; i++) {
|
|
p = js_get_object_class(ctx, argv[i], JS_CLASS_ARRAY);
|
|
if (p) {
|
|
len64 += p->u.array.len;
|
|
} else {
|
|
len64++;
|
|
}
|
|
}
|
|
if (len64 > JS_SHORTINT_MAX)
|
|
return JS_ThrowTypeError(ctx, "Array loo long");
|
|
len = len64;
|
|
|
|
obj = JS_NewArray(ctx, len);
|
|
if (JS_IsException(obj))
|
|
return obj;
|
|
p = JS_VALUE_TO_PTR(obj);
|
|
arr = JS_VALUE_TO_PTR(p->u.array.tab);
|
|
|
|
pos = 0;
|
|
for(i = -1; i < argc; i++) {
|
|
val = i == -1 ? *this_val : argv[i];
|
|
p = js_get_object_class(ctx, val, JS_CLASS_ARRAY);
|
|
if (p) {
|
|
arr1 = JS_VALUE_TO_PTR(p->u.array.tab);
|
|
for(j = 0; j < p->u.array.len; j++)
|
|
arr->arr[pos + j] = arr1->arr[j];
|
|
pos += p->u.array.len;
|
|
} else {
|
|
arr->arr[pos++] = val;
|
|
}
|
|
}
|
|
return obj;
|
|
}
|
|
|
|
JSValue js_array_indexOf(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv, int is_lastIndexOf)
|
|
{
|
|
JSObject *p;
|
|
int len, n, res;
|
|
JSValueArray *arr;
|
|
|
|
p = js_get_array(ctx, *this_val);
|
|
if (!p)
|
|
return JS_EXCEPTION;
|
|
len = p->u.array.len;
|
|
if (is_lastIndexOf) {
|
|
n = len - 1;
|
|
} else {
|
|
n = 0;
|
|
}
|
|
if (argc > 1) {
|
|
if (JS_ToInt32Clamp(ctx, &n, argv[1],
|
|
-is_lastIndexOf, len - is_lastIndexOf, len))
|
|
return JS_EXCEPTION;
|
|
}
|
|
/* the array may be modified */
|
|
p = JS_VALUE_TO_PTR(*this_val);
|
|
len = p->u.array.len; /* the length may be modified */
|
|
arr = JS_VALUE_TO_PTR(p->u.array.tab);
|
|
res = -1;
|
|
if (is_lastIndexOf) {
|
|
n = min_int(n, len - 1);
|
|
for(;n >= 0; n--) {
|
|
if (js_strict_eq(ctx, argv[0], arr->arr[n])) {
|
|
res = n;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
for(;n < len; n++) {
|
|
if (js_strict_eq(ctx, argv[0], arr->arr[n])) {
|
|
res = n;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return JS_NewShortInt(res);
|
|
}
|
|
|
|
JSValue js_array_slice(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSObject *p, *p1;
|
|
int len, start, final, k;
|
|
JSValueArray *arr, *arr1;
|
|
JSValue obj;
|
|
|
|
p = js_get_array(ctx, *this_val);
|
|
if (!p)
|
|
return JS_EXCEPTION;
|
|
len = p->u.array.len;
|
|
|
|
if (JS_ToInt32Clamp(ctx, &start, argv[0], 0, len, len))
|
|
return JS_EXCEPTION;
|
|
final = len;
|
|
if (!JS_IsUndefined(argv[1])) {
|
|
if (JS_ToInt32Clamp(ctx, &final, argv[1], 0, len, len))
|
|
return JS_EXCEPTION;
|
|
}
|
|
/* the array may have been modified */
|
|
p = JS_VALUE_TO_PTR(*this_val);
|
|
len = p->u.array.len; /* the length may be modified */
|
|
final = min_int(final, len);
|
|
|
|
obj = JS_NewArray(ctx, max_int(final - start, 0));
|
|
if (JS_IsException(obj))
|
|
return obj;
|
|
p = JS_VALUE_TO_PTR(*this_val);
|
|
arr = JS_VALUE_TO_PTR(p->u.array.tab);
|
|
p1 = JS_VALUE_TO_PTR(obj);
|
|
arr1 = JS_VALUE_TO_PTR(p1->u.array.tab);
|
|
for(k = start; k < final; k++) {
|
|
arr1->arr[k - start] = arr->arr[k];
|
|
}
|
|
return obj;
|
|
}
|
|
|
|
JSValue js_array_splice(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSObject *p, *p1;
|
|
int start, len, item_count, del_count, new_len, i, ret;
|
|
JSValueArray *arr, *arr1;
|
|
JSValue obj;
|
|
JSGCRef obj_ref;
|
|
|
|
p = js_get_array(ctx, *this_val);
|
|
if (!p)
|
|
return JS_EXCEPTION;
|
|
len = p->u.array.len;
|
|
|
|
if (JS_ToInt32Clamp(ctx, &start, argv[0], 0, len, len))
|
|
return JS_EXCEPTION;
|
|
|
|
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_ToInt32Clamp(ctx, &del_count, argv[1], 0, len - start, 0))
|
|
return JS_EXCEPTION;
|
|
}
|
|
new_len = len + item_count - del_count;
|
|
|
|
obj = JS_NewArray(ctx, del_count);
|
|
if (JS_IsException(obj))
|
|
return obj;
|
|
p = JS_VALUE_TO_PTR(*this_val);
|
|
/* handling this case has no practical use */
|
|
if (p->u.array.len != len)
|
|
return JS_ThrowTypeError(ctx, "array length was modified");
|
|
arr = JS_VALUE_TO_PTR(p->u.array.tab);
|
|
p1 = JS_VALUE_TO_PTR(obj);
|
|
arr1 = JS_VALUE_TO_PTR(p1->u.array.tab);
|
|
|
|
for(i = 0; i < del_count; i++) {
|
|
arr1->arr[i] = arr->arr[start + i];
|
|
}
|
|
|
|
if (item_count != del_count) {
|
|
/* resize */
|
|
if (del_count > item_count) {
|
|
memmove(arr->arr + start + item_count,
|
|
arr->arr + start + del_count,
|
|
(len - (start + del_count)) * sizeof(JSValue));
|
|
}
|
|
JS_PUSH_VALUE(ctx, obj);
|
|
ret = js_array_resize(ctx, this_val, new_len);
|
|
JS_POP_VALUE(ctx, obj);
|
|
if (ret)
|
|
return JS_EXCEPTION;
|
|
p = JS_VALUE_TO_PTR(*this_val);
|
|
arr = JS_VALUE_TO_PTR(p->u.array.tab);
|
|
if (del_count < item_count) {
|
|
memmove(arr->arr + start + item_count,
|
|
arr->arr + start + del_count,
|
|
(len - (start + del_count)) * sizeof(JSValue));
|
|
}
|
|
}
|
|
|
|
for(i = 0; i < item_count; i++)
|
|
arr->arr[start + i] = argv[2 + i];
|
|
|
|
return obj;
|
|
}
|
|
|
|
JSValue js_array_every(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv, int special)
|
|
{
|
|
JSObject *p;
|
|
JSValueArray *arr;
|
|
JSValue res, ret, val;
|
|
JSValue *pfunc, *pthis_arg;
|
|
JSGCRef val_ref, ret_ref;
|
|
int len, k, n;
|
|
|
|
p = js_get_array(ctx, *this_val);
|
|
if (!p)
|
|
return JS_EXCEPTION;
|
|
len = p->u.array.len;
|
|
|
|
pfunc = &argv[0];
|
|
pthis_arg = NULL;
|
|
if (argc > 1)
|
|
pthis_arg = &argv[1];
|
|
|
|
if (!JS_IsFunction(ctx, *pfunc))
|
|
return JS_ThrowTypeError(ctx, "not a function");
|
|
|
|
switch (special) {
|
|
case js_special_every:
|
|
ret = JS_TRUE;
|
|
break;
|
|
case js_special_some:
|
|
ret = JS_FALSE;
|
|
break;
|
|
case js_special_map:
|
|
ret = JS_NewArray(ctx, len);
|
|
if (JS_IsException(ret))
|
|
return JS_EXCEPTION;
|
|
break;
|
|
case js_special_filter:
|
|
ret = JS_NewArray(ctx, 0);
|
|
if (JS_IsException(ret))
|
|
return JS_EXCEPTION;
|
|
break;
|
|
case js_special_forEach:
|
|
default:
|
|
ret = JS_UNDEFINED;
|
|
break;
|
|
}
|
|
n = 0;
|
|
|
|
JS_PUSH_VALUE(ctx, ret);
|
|
for(k = 0; k < len; k++) {
|
|
if (JS_StackCheck(ctx, 5))
|
|
goto exception;
|
|
|
|
p = JS_VALUE_TO_PTR(*this_val);
|
|
arr = JS_VALUE_TO_PTR(p->u.array.tab);
|
|
/* the array length may have been modified by the function call*/
|
|
if (k >= p->u.array.len)
|
|
break;
|
|
val = arr->arr[k];
|
|
|
|
JS_PushArg(ctx, *this_val);
|
|
JS_PushArg(ctx, JS_NewShortInt(k));
|
|
JS_PushArg(ctx, val); /* arg0 */
|
|
JS_PushArg(ctx, *pfunc); /* func */
|
|
JS_PushArg(ctx, pthis_arg ? *pthis_arg : JS_UNDEFINED); /* this */
|
|
JS_PUSH_VALUE(ctx, val);
|
|
res = JS_Call(ctx, 3);
|
|
JS_POP_VALUE(ctx, val);
|
|
if (JS_IsException(res))
|
|
goto exception;
|
|
|
|
switch (special) {
|
|
case js_special_every:
|
|
if (!JS_ToBool(ctx, res)) {
|
|
ret_ref.val = JS_FALSE;
|
|
goto done;
|
|
}
|
|
break;
|
|
case js_special_some:
|
|
if (JS_ToBool(ctx, res)) {
|
|
ret_ref.val = JS_TRUE;
|
|
goto done;
|
|
}
|
|
break;
|
|
case js_special_map:
|
|
/* Note: same as defineProperty for arrays */
|
|
res = JS_SetPropertyUint32(ctx, ret_ref.val, k, res);
|
|
if (JS_IsException(res))
|
|
goto exception;
|
|
break;
|
|
case js_special_filter:
|
|
if (JS_ToBool(ctx, res)) {
|
|
res = JS_SetPropertyUint32(ctx, ret_ref.val, n++, val);
|
|
if (JS_IsException(res))
|
|
goto exception;
|
|
}
|
|
break;
|
|
case js_special_forEach:
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
done:
|
|
JS_POP_VALUE(ctx, ret);
|
|
return ret;
|
|
exception:
|
|
ret_ref.val = JS_EXCEPTION;
|
|
goto done;
|
|
}
|
|
|
|
JSValue js_array_reduce(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv, int special)
|
|
{
|
|
JSObject *p;
|
|
JSValueArray *arr;
|
|
JSValue acc, *pfunc;
|
|
JSGCRef acc_ref;
|
|
int len, k, k1, ret;
|
|
|
|
p = js_get_array(ctx, *this_val);
|
|
if (!p)
|
|
return JS_EXCEPTION;
|
|
len = p->u.array.len;
|
|
pfunc = &argv[0];
|
|
|
|
if (!JS_IsFunction(ctx, *pfunc))
|
|
return JS_ThrowTypeError(ctx, "not a function");
|
|
|
|
k = 0;
|
|
if (argc > 1) {
|
|
acc = argv[1];
|
|
} else {
|
|
if (len == 0)
|
|
return JS_ThrowTypeError(ctx, "empty array");
|
|
k1 = (special == js_special_reduceRight) ? len - k - 1 : k;
|
|
arr = JS_VALUE_TO_PTR(p->u.array.tab);
|
|
acc = arr->arr[k1];
|
|
k++;
|
|
}
|
|
for (; k < len; k++) {
|
|
JS_PUSH_VALUE(ctx, acc);
|
|
ret = JS_StackCheck(ctx, 6);
|
|
JS_POP_VALUE(ctx, acc);
|
|
if (ret)
|
|
return JS_EXCEPTION;
|
|
|
|
k1 = (special == js_special_reduceRight) ? len - k - 1 : k;
|
|
p = JS_VALUE_TO_PTR(*this_val);
|
|
arr = JS_VALUE_TO_PTR(p->u.array.tab);
|
|
/* Note: the array length may have been modified, hence the check */
|
|
if (k1 >= p->u.array.len)
|
|
break;
|
|
|
|
JS_PushArg(ctx, *this_val);
|
|
JS_PushArg(ctx, JS_NewShortInt(k1));
|
|
JS_PushArg(ctx, arr->arr[k1]);
|
|
JS_PushArg(ctx, acc); /* arg0 */
|
|
JS_PushArg(ctx, *pfunc); /* func */
|
|
JS_PushArg(ctx, JS_UNDEFINED); /* this */
|
|
acc = JS_Call(ctx, 4);
|
|
if (JS_IsException(acc))
|
|
return JS_EXCEPTION;
|
|
}
|
|
return acc;
|
|
}
|
|
|
|
/* heapsort algorithm */
|
|
static void rqsort_idx(size_t nmemb,
|
|
int (*cmp)(size_t, size_t, void *),
|
|
void (*swap)(size_t, size_t, void *),
|
|
void *opaque)
|
|
{
|
|
size_t i, n, c, r, size;
|
|
|
|
size = 1;
|
|
if (nmemb > 1) {
|
|
i = (nmemb / 2) * size;
|
|
n = nmemb * size;
|
|
|
|
while (i > 0) {
|
|
i -= size;
|
|
for (r = i; (c = r * 2 + size) < n; r = c) {
|
|
if (c < n - size && cmp(c, c + size, opaque) <= 0)
|
|
c += size;
|
|
if (cmp(r, c, opaque) > 0)
|
|
break;
|
|
swap(r, c, opaque);
|
|
}
|
|
}
|
|
for (i = n - size; i > 0; i -= size) {
|
|
swap(0, i, opaque);
|
|
|
|
for (r = 0; (c = r * 2 + size) < i; r = c) {
|
|
if (c < i - size && cmp(c, c + size, opaque) <= 0)
|
|
c += size;
|
|
if (cmp(r, c, opaque) > 0)
|
|
break;
|
|
swap(r, c, opaque);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
typedef struct {
|
|
JSContext *ctx;
|
|
BOOL exception;
|
|
JSValue *parr;
|
|
JSValue *pfunc;
|
|
} JSArraySortContext;
|
|
|
|
/* return -1, 0, 1 */
|
|
static int js_array_sort_cmp(size_t i1, size_t i2, void *opaque)
|
|
{
|
|
JSArraySortContext *s = opaque;
|
|
JSContext *ctx = s->ctx;
|
|
JSValueArray *arr;
|
|
int cmp, j1, j2;
|
|
|
|
if (s->exception)
|
|
return 0;
|
|
|
|
arr = JS_VALUE_TO_PTR(*s->parr);
|
|
if (s->pfunc) {
|
|
JSValue res;
|
|
/* custom sort function is specified as returning 0 for identical
|
|
* objects: avoid method call overhead.
|
|
*/
|
|
if (arr->arr[2 * i1] == arr->arr[2 * i2])
|
|
goto cmp_same;
|
|
if (JS_StackCheck(ctx, 4))
|
|
goto exception;
|
|
arr = JS_VALUE_TO_PTR(*s->parr);
|
|
|
|
JS_PushArg(ctx, arr->arr[2 * i2]);
|
|
JS_PushArg(ctx, arr->arr[2 * i1]); /* arg0 */
|
|
JS_PushArg(ctx, *s->pfunc); /* func */
|
|
JS_PushArg(ctx, JS_UNDEFINED); /* this */
|
|
res = JS_Call(ctx, 2);
|
|
if (JS_IsException(res))
|
|
return JS_EXCEPTION;
|
|
if (JS_IsInt(res)) {
|
|
int val = JS_VALUE_GET_INT(res);
|
|
cmp = (val > 0) - (val < 0);
|
|
} else {
|
|
double val;
|
|
if (JS_ToNumber(ctx, &val, res))
|
|
goto exception;
|
|
cmp = (val > 0) - (val < 0);
|
|
}
|
|
} else {
|
|
JSValue str1, str2;
|
|
JSGCRef str1_ref;
|
|
|
|
str1 = arr->arr[2 * i1];
|
|
if (!JS_IsString(ctx, str1)) {
|
|
str1 = JS_ToString(ctx, str1);
|
|
if (JS_IsException(str1))
|
|
goto exception;
|
|
arr = JS_VALUE_TO_PTR(*s->parr);
|
|
}
|
|
str2 = arr->arr[2 * i2];
|
|
if (!JS_IsString(ctx, str2)) {
|
|
JS_PUSH_VALUE(ctx, str1);
|
|
str2 = JS_ToString(ctx, str2);
|
|
JS_POP_VALUE(ctx, str1);
|
|
if (JS_IsException(str2))
|
|
goto exception;
|
|
}
|
|
cmp = js_string_compare(ctx, str1, str2);
|
|
}
|
|
if (cmp != 0)
|
|
return cmp;
|
|
cmp_same:
|
|
/* make sort stable: compare array offsets */
|
|
arr = JS_VALUE_TO_PTR(*s->parr);
|
|
j1 = JS_VALUE_GET_INT(arr->arr[2 * i1 + 1]);
|
|
j2 = JS_VALUE_GET_INT(arr->arr[2 * i2 + 1]);
|
|
return (j1 > j2) - (j1 < j2);
|
|
|
|
exception:
|
|
s->exception = TRUE;
|
|
return 0;
|
|
}
|
|
|
|
static void js_array_sort_swap(size_t i1, size_t i2, void *opaque)
|
|
{
|
|
JSArraySortContext *s = opaque;
|
|
JSValueArray *arr;
|
|
JSValue tmp, *tab;
|
|
|
|
arr = JS_VALUE_TO_PTR(*s->parr);
|
|
tab = arr->arr;
|
|
tmp = tab[2 * i1];
|
|
tab[2 * i1] = tab[2 * i2];
|
|
tab[2 * i2] = tmp;
|
|
|
|
tmp = tab[2 * i1 + 1];
|
|
tab[2 * i1 + 1] = tab[2 * i2 + 1];
|
|
tab[2 * i2 + 1] = tmp;
|
|
}
|
|
|
|
JSValue js_array_sort(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue *pfunc = &argv[0];
|
|
JSObject *p;
|
|
JSValue tab_val;
|
|
JSGCRef tab_val_ref;
|
|
JSValueArray *tab, *arr;
|
|
int i, len, n;
|
|
JSArraySortContext ss, *s = &ss;
|
|
|
|
if (!JS_IsUndefined(*pfunc)) {
|
|
if (!JS_IsFunction(ctx, *pfunc))
|
|
return JS_ThrowTypeError(ctx, "not a function");
|
|
} else {
|
|
pfunc = NULL;
|
|
}
|
|
p = js_get_array(ctx, *this_val);
|
|
if (!p)
|
|
return JS_EXCEPTION;
|
|
|
|
/* create a temporary array for sorting */
|
|
len = p->u.array.len;
|
|
tab = js_alloc_value_array(ctx, 0, len * 2);
|
|
if (!tab)
|
|
return JS_EXCEPTION;
|
|
|
|
p = JS_VALUE_TO_PTR(*this_val);
|
|
arr = JS_VALUE_TO_PTR(p->u.array.tab);
|
|
n = 0;
|
|
for(i = 0; i < len; i++) {
|
|
if (!JS_IsUndefined(arr->arr[i])) {
|
|
tab->arr[2 * n] = arr->arr[i];
|
|
tab->arr[2 * n + 1] = JS_NewShortInt(i);
|
|
n++;
|
|
}
|
|
}
|
|
/* the end of 'tab' is already filled with JS_UNDEFINED */
|
|
tab_val = JS_VALUE_FROM_PTR(tab);
|
|
|
|
JS_PUSH_VALUE(ctx, tab_val);
|
|
s->ctx = ctx;
|
|
s->exception = FALSE;
|
|
s->parr = &tab_val_ref.val;
|
|
s->pfunc = pfunc;
|
|
rqsort_idx(n, js_array_sort_cmp, js_array_sort_swap, s);
|
|
JS_POP_VALUE(ctx, tab_val);
|
|
tab = JS_VALUE_TO_PTR(tab_val);
|
|
if (s->exception) {
|
|
js_free(ctx, tab);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
p = JS_VALUE_TO_PTR(*this_val);
|
|
arr = JS_VALUE_TO_PTR(p->u.array.tab);
|
|
/* XXX: could resize the array in case it was shrank by the compare function */
|
|
len = min_int(len, p->u.array.len);
|
|
for(i = 0; i < len; i++) {
|
|
arr->arr[i] = tab->arr[2 * i];
|
|
}
|
|
js_free(ctx, tab);
|
|
return *this_val;
|
|
}
|
|
|
|
/**********************************************************************/
|
|
|
|
/* precondition: a and b are not NaN */
|
|
static double js_fmin(double a, double b)
|
|
{
|
|
if (a == 0 && b == 0) {
|
|
return uint64_as_float64(float64_as_uint64(a) | float64_as_uint64(b));
|
|
} else if (a <= b) {
|
|
return a;
|
|
} else {
|
|
return b;
|
|
}
|
|
}
|
|
|
|
/* precondition: a and b are not NaN */
|
|
static double js_fmax(double a, double b)
|
|
{
|
|
if (a == 0 && b == 0) {
|
|
return uint64_as_float64(float64_as_uint64(a) & float64_as_uint64(b));
|
|
} else if (a >= b) {
|
|
return a;
|
|
} else {
|
|
return b;
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
if (unlikely(argc == 0)) {
|
|
return __JS_NewFloat64(ctx, is_max ? -1.0 / 0.0 : 1.0 / 0.0);
|
|
}
|
|
|
|
if (JS_IsInt(argv[0])) {
|
|
int a1, r1 = JS_VALUE_GET_INT(argv[0]);
|
|
for(i = 1; i < argc; i++) {
|
|
if (!JS_IsInt(argv[i])) {
|
|
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_NewShortInt(r1);
|
|
} else {
|
|
if (JS_ToNumber(ctx, &r, argv[0]))
|
|
return JS_EXCEPTION;
|
|
i = 1;
|
|
generic_case:
|
|
while (i < argc) {
|
|
if (JS_ToNumber(ctx, &a, argv[i]))
|
|
return JS_EXCEPTION;
|
|
if (!isnan(r)) {
|
|
if (isnan(a)) {
|
|
r = a;
|
|
} else {
|
|
if (is_max)
|
|
r = js_fmax(r, a);
|
|
else
|
|
r = js_fmin(r, a);
|
|
}
|
|
}
|
|
i++;
|
|
}
|
|
return JS_NewFloat64(ctx, r);
|
|
}
|
|
}
|
|
|
|
double js_math_sign(double a)
|
|
{
|
|
if (isnan(a) || a == 0.0)
|
|
return a;
|
|
if (a < 0)
|
|
return -1;
|
|
else
|
|
return 1;
|
|
}
|
|
|
|
double js_math_fround(double a)
|
|
{
|
|
return (float)a;
|
|
}
|
|
|
|
JSValue js_math_imul(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
int a, b;
|
|
|
|
if (JS_ToInt32(ctx, &a, argv[0]))
|
|
return JS_EXCEPTION;
|
|
if (JS_ToInt32(ctx, &b, argv[1]))
|
|
return JS_EXCEPTION;
|
|
/* purposely ignoring overflow */
|
|
return JS_NewInt32(ctx, (uint32_t)a * (uint32_t)b);
|
|
}
|
|
|
|
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_NewInt32(ctx, r);
|
|
}
|
|
|
|
JSValue js_math_atan2(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
double y, x;
|
|
|
|
if (JS_ToNumber(ctx, &y, argv[0]))
|
|
return JS_EXCEPTION;
|
|
if (JS_ToNumber(ctx, &x, argv[1]))
|
|
return JS_EXCEPTION;
|
|
return JS_NewFloat64(ctx, js_atan2(y, x));
|
|
}
|
|
|
|
JSValue js_math_pow(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
double y, x;
|
|
|
|
if (JS_ToNumber(ctx, &x, argv[0]))
|
|
return JS_EXCEPTION;
|
|
if (JS_ToNumber(ctx, &y, argv[1]))
|
|
return JS_EXCEPTION;
|
|
return JS_NewFloat64(ctx, js_pow(x, y));
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
JSValue js_math_random(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
double d;
|
|
uint64_t v;
|
|
|
|
v = xorshift64star(&ctx->random_state);
|
|
/* 1.0 <= u.d < 2 */
|
|
d = uint64_as_float64(((uint64_t)0x3ff << 52) | (v >> 12));
|
|
return __JS_NewFloat64(ctx, d - 1.0);
|
|
}
|
|
|
|
/* typed array */
|
|
|
|
#define JS_TYPED_ARRAY_COUNT (JS_CLASS_FLOAT64_ARRAY - JS_CLASS_UINT8C_ARRAY + 1)
|
|
|
|
static uint8_t typed_array_size_log2[JS_TYPED_ARRAY_COUNT] = {
|
|
0, 0, 0, 1, 1, 2, 2, 2, 3
|
|
};
|
|
|
|
static int JS_ToIndex(JSContext *ctx, uint64_t *plen, JSValue val)
|
|
{
|
|
int v;
|
|
/* XXX: should support 53 bit inteers */
|
|
if (JS_ToInt32Sat(ctx, &v, val))
|
|
return -1;
|
|
if (v < 0 || v > JS_SHORTINT_MAX) {
|
|
JS_ThrowRangeError(ctx, "invalid array index");
|
|
return -1;
|
|
}
|
|
*plen = v;
|
|
return 0;
|
|
}
|
|
|
|
JSValue js_array_buffer_alloc(JSContext *ctx, uint64_t len)
|
|
{
|
|
JSByteArray *arr;
|
|
JSValue buffer, obj;
|
|
JSGCRef buffer_ref;
|
|
JSObject *p;
|
|
|
|
if (len > JS_SHORTINT_MAX)
|
|
return JS_ThrowRangeError(ctx, "invalid array buffer length");
|
|
arr = js_alloc_byte_array(ctx, len);
|
|
if (!arr)
|
|
return JS_EXCEPTION;
|
|
memset(arr->buf, 0, len);
|
|
buffer = JS_VALUE_FROM_PTR(arr);
|
|
JS_PUSH_VALUE(ctx, buffer);
|
|
obj = JS_NewObjectClass(ctx, JS_CLASS_ARRAY_BUFFER, sizeof(JSArrayBuffer));
|
|
JS_POP_VALUE(ctx, buffer);
|
|
if (JS_IsException(obj))
|
|
return obj;
|
|
p = JS_VALUE_TO_PTR(obj);
|
|
p->u.array_buffer.byte_buffer = buffer;
|
|
return obj;
|
|
}
|
|
|
|
JSValue js_array_buffer_constructor(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
uint64_t len;
|
|
if (!(argc & FRAME_CF_CTOR))
|
|
return JS_ThrowTypeError(ctx, "must be called with new");
|
|
if (JS_ToIndex(ctx, &len, argv[0]))
|
|
return JS_EXCEPTION;
|
|
return js_array_buffer_alloc(ctx, len);
|
|
}
|
|
|
|
JSValue js_array_buffer_get_byteLength(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSObject *p = js_get_object_class(ctx, *this_val, JS_CLASS_ARRAY_BUFFER);
|
|
JSByteArray *arr;
|
|
if (!p)
|
|
return JS_ThrowTypeError(ctx, "expected an ArrayBuffer");
|
|
arr = JS_VALUE_TO_PTR(p->u.array_buffer.byte_buffer);
|
|
return JS_NewShortInt(arr->size);
|
|
}
|
|
|
|
JSValue js_typed_array_base_constructor(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
return JS_ThrowTypeError(ctx, "cannot be called");
|
|
}
|
|
|
|
static JSValue js_typed_array_constructor_obj(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv, int magic)
|
|
{
|
|
int i, len;
|
|
JSValue val, obj;
|
|
JSGCRef obj_ref;
|
|
JSObject *p;
|
|
|
|
p = JS_VALUE_TO_PTR(argv[0]);
|
|
if (p->class_id == JS_CLASS_ARRAY) {
|
|
len = p->u.array.len;
|
|
} else if (p->class_id >= JS_CLASS_UINT8C_ARRAY &&
|
|
p->class_id <= JS_CLASS_FLOAT64_ARRAY) {
|
|
len = p->u.typed_array.len;
|
|
} else {
|
|
return JS_ThrowTypeError(ctx, "unsupported object class");
|
|
}
|
|
val = JS_NewShortInt(len);
|
|
obj = js_typed_array_constructor(ctx, NULL, 1 | FRAME_CF_CTOR, &val, magic);
|
|
if (JS_IsException(obj))
|
|
return obj;
|
|
|
|
for(i = 0; i < len; i++) {
|
|
JS_PUSH_VALUE(ctx, obj);
|
|
val = JS_GetProperty(ctx, argv[0], JS_NewShortInt(i));
|
|
JS_POP_VALUE(ctx, obj);
|
|
if (JS_IsException(val))
|
|
return val;
|
|
JS_PUSH_VALUE(ctx, obj);
|
|
val = JS_SetPropertyInternal(ctx, obj, JS_NewShortInt(i), val, FALSE);
|
|
JS_POP_VALUE(ctx, obj);
|
|
if (JS_IsException(val))
|
|
return val;
|
|
}
|
|
return obj;
|
|
}
|
|
|
|
JSValue js_typed_array_constructor(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv, int magic)
|
|
{
|
|
int size_log2;
|
|
uint64_t len, offset, byte_length;
|
|
JSObject *p;
|
|
JSByteArray *arr;
|
|
JSValue buffer, obj;
|
|
JSGCRef buffer_ref;
|
|
|
|
if (!(argc & FRAME_CF_CTOR))
|
|
return JS_ThrowTypeError(ctx, "must be called with new");
|
|
size_log2 = typed_array_size_log2[magic - JS_CLASS_UINT8C_ARRAY];
|
|
if (!JS_IsObject(ctx, argv[0])) {
|
|
if (JS_ToIndex(ctx, &len, argv[0]))
|
|
return JS_EXCEPTION;
|
|
buffer = js_array_buffer_alloc(ctx, len << size_log2);
|
|
if (JS_IsException(buffer))
|
|
return JS_EXCEPTION;
|
|
offset = 0;
|
|
} else {
|
|
p = JS_VALUE_TO_PTR(argv[0]);
|
|
if (p->class_id == JS_CLASS_ARRAY_BUFFER) {
|
|
arr = JS_VALUE_TO_PTR(p->u.array_buffer.byte_buffer);
|
|
byte_length = arr->size;
|
|
if (JS_ToIndex(ctx, &offset, argv[1]))
|
|
return JS_EXCEPTION;
|
|
if ((offset & ((1 << size_log2) - 1)) != 0 ||
|
|
offset > byte_length)
|
|
return JS_ThrowRangeError(ctx, "invalid offset");
|
|
if (JS_IsUndefined(argv[2])) {
|
|
if ((byte_length & ((1 << size_log2) - 1)) != 0)
|
|
goto invalid_length;
|
|
len = (byte_length - offset) >> size_log2;
|
|
} else {
|
|
if (JS_ToIndex(ctx, &len, argv[2]))
|
|
return JS_EXCEPTION;
|
|
if ((offset + (len << size_log2)) > byte_length) {
|
|
invalid_length:
|
|
return JS_ThrowRangeError(ctx, "invalid length");
|
|
}
|
|
}
|
|
buffer = argv[0];
|
|
offset >>= size_log2;
|
|
} else {
|
|
return js_typed_array_constructor_obj(ctx, this_val,
|
|
argc, argv, magic);
|
|
}
|
|
}
|
|
|
|
JS_PUSH_VALUE(ctx, buffer);
|
|
obj = JS_NewObjectClass(ctx, magic, sizeof(JSTypedArray));
|
|
JS_POP_VALUE(ctx, buffer);
|
|
if (JS_IsException(obj))
|
|
return obj;
|
|
p = JS_VALUE_TO_PTR(obj);
|
|
p->u.typed_array.buffer = buffer;
|
|
p->u.typed_array.offset = offset;
|
|
p->u.typed_array.len = len;
|
|
return obj;
|
|
}
|
|
|
|
static JSObject *get_typed_array(JSContext *ctx, JSValue val)
|
|
{
|
|
JSObject *p;
|
|
if (!JS_IsObject(ctx, val))
|
|
goto fail;
|
|
p = JS_VALUE_TO_PTR(val);
|
|
if (!(p->class_id >= JS_CLASS_UINT8C_ARRAY && p->class_id <= JS_CLASS_FLOAT64_ARRAY)) {
|
|
fail:
|
|
JS_ThrowTypeError(ctx, "not a TypedArray");
|
|
return NULL;
|
|
}
|
|
return p;
|
|
}
|
|
|
|
JSValue js_typed_array_get_length(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv, int magic)
|
|
{
|
|
JSObject *p;
|
|
int size_log2;
|
|
|
|
p = get_typed_array(ctx, *this_val);
|
|
if (!p)
|
|
return JS_EXCEPTION;
|
|
size_log2 = typed_array_size_log2[p->class_id - JS_CLASS_UINT8C_ARRAY];
|
|
switch(magic) {
|
|
default:
|
|
case 0:
|
|
return JS_NewShortInt(p->u.typed_array.len);
|
|
case 1:
|
|
return JS_NewShortInt(p->u.typed_array.len << size_log2);
|
|
case 2:
|
|
return JS_NewShortInt(p->u.typed_array.offset << size_log2);
|
|
case 3:
|
|
return p->u.typed_array.buffer;
|
|
}
|
|
}
|
|
|
|
JSValue js_typed_array_subarray(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSObject *p, *p1;
|
|
JSByteArray *arr;
|
|
int start, final, len;
|
|
uint32_t offset, count;
|
|
JSValue obj;
|
|
|
|
p = get_typed_array(ctx, *this_val);
|
|
if (!p)
|
|
return JS_EXCEPTION;
|
|
len = p->u.typed_array.len;
|
|
if (JS_ToInt32Clamp(ctx, &start, argv[0], 0, len, len))
|
|
return JS_EXCEPTION;
|
|
if (JS_IsUndefined(argv[1])) {
|
|
final = len;
|
|
} else {
|
|
if (JS_ToInt32Clamp(ctx, &final, argv[1], 0, len, len))
|
|
return JS_EXCEPTION;
|
|
}
|
|
p = JS_VALUE_TO_PTR(*this_val);
|
|
offset = p->u.typed_array.offset + start;
|
|
count = max_int(final - start, 0);
|
|
|
|
/* check offset and count */
|
|
p1 = JS_VALUE_TO_PTR(p->u.typed_array.buffer);
|
|
arr = JS_VALUE_TO_PTR(p1->u.array_buffer.byte_buffer);
|
|
if (offset + count > arr->size)
|
|
return JS_ThrowRangeError(ctx, "invalid length");
|
|
|
|
obj = JS_NewObjectClass(ctx, p->class_id, sizeof(JSTypedArray));
|
|
if (JS_IsException(obj))
|
|
return JS_EXCEPTION;
|
|
p = JS_VALUE_TO_PTR(*this_val);
|
|
p1 = JS_VALUE_TO_PTR(obj);
|
|
p1->u.typed_array.buffer = p->u.typed_array.buffer;
|
|
p1->u.typed_array.offset = offset;
|
|
p1->u.typed_array.len = count;
|
|
return obj;
|
|
}
|
|
|
|
JSValue js_typed_array_set(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSObject *p, *p1;
|
|
uint32_t dst_len, src_len, i;
|
|
int offset;
|
|
|
|
p = get_typed_array(ctx, *this_val);
|
|
if (!p)
|
|
return JS_EXCEPTION;
|
|
if (argc > 1) {
|
|
if (JS_ToInt32Sat(ctx, &offset, argv[1]))
|
|
return JS_EXCEPTION;
|
|
} else {
|
|
offset = 0;
|
|
}
|
|
if (offset < 0)
|
|
goto range_error;
|
|
if (!JS_IsObject(ctx, argv[0]))
|
|
return JS_ThrowTypeErrorNotAnObject(ctx);
|
|
p = JS_VALUE_TO_PTR(*this_val);
|
|
dst_len = p->u.typed_array.len;
|
|
p1 = JS_VALUE_TO_PTR(argv[0]);
|
|
if (p1->class_id >= JS_CLASS_UINT8C_ARRAY &&
|
|
p1->class_id <= JS_CLASS_FLOAT64_ARRAY) {
|
|
src_len = p1->u.typed_array.len;
|
|
if (src_len > dst_len || offset > dst_len - src_len)
|
|
goto range_error;
|
|
if (p1->class_id == p->class_id) {
|
|
JSObject *src_buffer, *dst_buffer;
|
|
JSByteArray *src_arr, *dst_arr;
|
|
int shift = typed_array_size_log2[p->class_id - JS_CLASS_UINT8C_ARRAY];
|
|
dst_buffer = JS_VALUE_TO_PTR(p->u.typed_array.buffer);
|
|
dst_arr = JS_VALUE_TO_PTR(dst_buffer->u.array_buffer.byte_buffer);
|
|
src_buffer = JS_VALUE_TO_PTR(p1->u.typed_array.buffer);
|
|
src_arr = JS_VALUE_TO_PTR(src_buffer->u.array_buffer.byte_buffer);
|
|
/* same type: must copy to preserve float bits */
|
|
memmove(dst_arr->buf + ((p->u.typed_array.offset + offset) << shift),
|
|
src_arr->buf + (p1->u.typed_array.offset << shift),
|
|
src_len << shift);
|
|
goto done;
|
|
}
|
|
} else {
|
|
if (js_get_length32(ctx, (uint32_t *)&src_len, argv[0]))
|
|
return JS_EXCEPTION;
|
|
if (src_len > dst_len || offset > dst_len - src_len) {
|
|
range_error:
|
|
return JS_ThrowRangeError(ctx, "invalid array length");
|
|
}
|
|
}
|
|
for(i = 0; i < src_len; i++) {
|
|
JSValue val;
|
|
val = JS_GetPropertyUint32(ctx, argv[0], i);
|
|
if (JS_IsException(val))
|
|
return JS_EXCEPTION;
|
|
val = JS_SetPropertyUint32(ctx, *this_val, offset + i, val);
|
|
if (JS_IsException(val))
|
|
return JS_EXCEPTION;
|
|
}
|
|
done:
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
/* Date */
|
|
|
|
JSValue js_date_constructor(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
return JS_ThrowTypeError(ctx, "only Date.now() is supported");
|
|
}
|
|
|
|
/* global */
|
|
|
|
JSValue js_global_eval(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue val;
|
|
|
|
if (!JS_IsString(ctx, argv[0]))
|
|
return argv[0];
|
|
val = JS_Parse2(ctx, argv[0], NULL, 0, "<input>", JS_EVAL_RETVAL);
|
|
if (JS_IsException(val))
|
|
return val;
|
|
return JS_Run(ctx, val);
|
|
}
|
|
|
|
JSValue js_global_isNaN(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
double d;
|
|
if (unlikely(JS_ToNumber(ctx, &d, argv[0])))
|
|
return JS_EXCEPTION;
|
|
return JS_NewBool(isnan(d));
|
|
}
|
|
|
|
JSValue js_global_isFinite(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
double d;
|
|
if (unlikely(JS_ToNumber(ctx, &d, argv[0])))
|
|
return JS_EXCEPTION;
|
|
return JS_NewBool(isfinite(d));
|
|
}
|
|
|
|
/* JSON */
|
|
|
|
JSValue js_json_parse(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue val;
|
|
|
|
val = JS_ToString(ctx, argv[0]);
|
|
if (JS_IsException(val))
|
|
return val;
|
|
return JS_Parse2(ctx, val, NULL, 0, "<input>", JS_EVAL_JSON);
|
|
}
|
|
|
|
static int js_to_quoted_string(JSContext *ctx, StringBuffer *b, JSValue str)
|
|
{
|
|
int i, c;
|
|
JSStringCharBuf buf;
|
|
JSString *p;
|
|
JSGCRef str_ref;
|
|
size_t clen;
|
|
|
|
JS_PUSH_VALUE(ctx, str);
|
|
string_buffer_putc(ctx, b, '\"');
|
|
|
|
i = 0;
|
|
for(;;) {
|
|
/* XXX: inefficient */
|
|
p = get_string_ptr(ctx, &buf, str_ref.val);
|
|
if (i >= p->len)
|
|
break;
|
|
c = utf8_get(p->buf + i, &clen);
|
|
i += clen;
|
|
|
|
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:
|
|
string_buffer_putc(ctx, b, '\\');
|
|
string_buffer_putc(ctx, b, c);
|
|
break;
|
|
default:
|
|
if (c < 32 || (c >= 0xd800 && c < 0xe000)) {
|
|
char buf[7];
|
|
js_snprintf(buf, sizeof(buf), "\\u%04x", c);
|
|
string_buffer_puts(ctx, b, buf);
|
|
} else {
|
|
string_buffer_putc(ctx, b, c);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
string_buffer_putc(ctx, b, '\"');
|
|
JS_POP_VALUE(ctx, str);
|
|
return 0;
|
|
}
|
|
|
|
#define JSON_REC_SIZE 3
|
|
|
|
static int check_circular_ref(JSContext *ctx, JSValue *stack_top, JSValue val)
|
|
{
|
|
JSValue *sp;
|
|
for(sp = ctx->sp; sp < stack_top; sp += JSON_REC_SIZE) {
|
|
if (sp[0] == val) {
|
|
JS_ThrowTypeError(ctx, "circular reference");
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* XXX: no space nor replacer */
|
|
JSValue js_json_stringify(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue obj, *stack_top;
|
|
StringBuffer b_s, *b = &b_s;
|
|
int idx, ret;
|
|
|
|
#if 0
|
|
if (JS_IsNumber(ctx, *pspace)) {
|
|
int n;
|
|
if (JS_ToInt32Clamp(ctx, &n, *pspace, 0, 10, 0))
|
|
return JS_EXCEPTION;
|
|
*pspace = JS_NewStringLen(ctx, " ", n);
|
|
} else if (JS_IsString(ctx, *pspace)) {
|
|
*pspace = js_sub_string(ctx, *pspace, 0, 10);
|
|
} else {
|
|
*pspace = js_get_atom(ctx, JS_ATOM_empty);
|
|
}
|
|
#endif
|
|
string_buffer_push(ctx, b, 0);
|
|
stack_top = ctx->sp;
|
|
|
|
ret = JS_StackCheck(ctx, JSON_REC_SIZE);
|
|
if (ret)
|
|
goto fail;
|
|
*--ctx->sp = JS_NULL; /* keys */
|
|
*--ctx->sp = JS_NewShortInt(0); /* prop index */
|
|
*--ctx->sp = argv[0]; /* object */
|
|
|
|
while (ctx->sp < stack_top) {
|
|
obj = ctx->sp[0];
|
|
if (JS_IsFunction(ctx, obj)) {
|
|
goto output_null;
|
|
} else if (JS_IsObject(ctx, obj)) {
|
|
JSObject *p = JS_VALUE_TO_PTR(obj);
|
|
idx = JS_VALUE_GET_INT(ctx->sp[1]);
|
|
if (p->class_id == JS_CLASS_ARRAY) {
|
|
JSValueArray *arr;
|
|
JSValue val;
|
|
|
|
/* array */
|
|
if (idx == 0)
|
|
string_buffer_putc(ctx, b, '[');
|
|
p = JS_VALUE_TO_PTR(ctx->sp[0]);
|
|
if (idx >= p->u.array.len) {
|
|
/* end of array */
|
|
string_buffer_putc(ctx, b, ']');
|
|
ctx->sp += JSON_REC_SIZE;
|
|
} else {
|
|
if (idx != 0)
|
|
string_buffer_putc(ctx, b, ',');
|
|
ctx->sp[1] = JS_NewShortInt(idx + 1);
|
|
ret = JS_StackCheck(ctx, JSON_REC_SIZE);
|
|
if (ret)
|
|
goto fail;
|
|
p = JS_VALUE_TO_PTR(ctx->sp[0]);
|
|
arr = JS_VALUE_TO_PTR(p->u.array.tab);
|
|
val = arr->arr[idx];
|
|
if (check_circular_ref(ctx, stack_top, val))
|
|
goto fail;
|
|
*--ctx->sp = JS_NULL;
|
|
*--ctx->sp = JS_NewShortInt(0);
|
|
*--ctx->sp = val;
|
|
}
|
|
} else {
|
|
JSValueArray *arr;
|
|
JSValue val, prop;
|
|
JSGCRef val_ref;
|
|
int saved_idx;
|
|
|
|
/* object */
|
|
if (idx == 0) {
|
|
string_buffer_putc(ctx, b, '{');
|
|
ctx->sp[2] = js_object_keys(ctx, NULL, 1, &ctx->sp[0]);
|
|
if (JS_IsException(ctx->sp[2]))
|
|
goto fail;
|
|
}
|
|
saved_idx = idx;
|
|
for(;;) {
|
|
p = JS_VALUE_TO_PTR(ctx->sp[2]); /* keys */
|
|
if (idx >= p->u.array.len) {
|
|
/* end of object */
|
|
string_buffer_putc(ctx, b, '}');
|
|
ctx->sp += JSON_REC_SIZE;
|
|
goto end_obj;
|
|
} else {
|
|
arr = JS_VALUE_TO_PTR(p->u.array.tab);
|
|
prop = JS_ToPropertyKey(ctx, arr->arr[idx]);
|
|
val = JS_GetProperty(ctx, ctx->sp[0], prop);
|
|
if (JS_IsException(val))
|
|
goto fail;
|
|
/* skip undefined properties */
|
|
if (!JS_IsUndefined(val))
|
|
break;
|
|
idx++;
|
|
}
|
|
}
|
|
JS_PUSH_VALUE(ctx, val);
|
|
if (saved_idx != 0)
|
|
string_buffer_putc(ctx, b, ',');
|
|
ctx->sp[1] = JS_NewShortInt(idx + 1);
|
|
p = JS_VALUE_TO_PTR(ctx->sp[2]);
|
|
arr = JS_VALUE_TO_PTR(p->u.array.tab);
|
|
ret = js_to_quoted_string(ctx, b, arr->arr[idx]);
|
|
string_buffer_putc(ctx, b, ':');
|
|
ret |= JS_StackCheck(ctx, JSON_REC_SIZE);
|
|
JS_POP_VALUE(ctx, val);
|
|
if (ret)
|
|
goto fail;
|
|
if (check_circular_ref(ctx, stack_top, val))
|
|
goto fail;
|
|
*--ctx->sp = JS_NULL;
|
|
*--ctx->sp = JS_NewShortInt(0);
|
|
*--ctx->sp = val;
|
|
end_obj: ;
|
|
}
|
|
} else if (JS_IsNumber(ctx, obj)) {
|
|
double d;
|
|
ret = JS_ToNumber(ctx, &d, obj);
|
|
if (ret)
|
|
goto fail;
|
|
if (!isfinite(d))
|
|
goto output_null;
|
|
goto to_string;
|
|
} else if (JS_IsBool(obj)) {
|
|
to_string:
|
|
if (string_buffer_concat(ctx, b, obj))
|
|
goto fail;
|
|
ctx->sp += JSON_REC_SIZE;
|
|
} else if (JS_IsString(ctx, obj)) {
|
|
if (js_to_quoted_string(ctx, b, obj))
|
|
goto fail;
|
|
ctx->sp += JSON_REC_SIZE;
|
|
} else {
|
|
output_null:
|
|
string_buffer_concat(ctx, b, js_get_atom(ctx, JS_ATOM_null));
|
|
ctx->sp += JSON_REC_SIZE;
|
|
}
|
|
}
|
|
return string_buffer_pop(ctx, b);
|
|
|
|
fail:
|
|
ctx->sp = stack_top;
|
|
string_buffer_pop(ctx, b);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
/**********************************************************************/
|
|
/* regexp */
|
|
|
|
typedef enum {
|
|
#define REDEF(id, size) REOP_ ## id,
|
|
#include "mquickjs_opcode.h"
|
|
#undef REDEF
|
|
REOP_COUNT,
|
|
} REOPCodeEnum;
|
|
|
|
#define CAPTURE_COUNT_MAX 255
|
|
#define REGISTER_COUNT_MAX 255
|
|
|
|
typedef struct {
|
|
#ifdef DUMP_REOP
|
|
const char *name;
|
|
#endif
|
|
uint8_t size;
|
|
} REOpCode;
|
|
|
|
static const REOpCode reopcode_info[REOP_COUNT] = {
|
|
#ifdef DUMP_REOP
|
|
#define REDEF(id, size) { #id, size },
|
|
#else
|
|
#define REDEF(id, size) { size },
|
|
#endif
|
|
#include "mquickjs_opcode.h"
|
|
#undef REDEF
|
|
};
|
|
|
|
#define LRE_FLAG_GLOBAL (1 << 0)
|
|
#define LRE_FLAG_IGNORECASE (1 << 1)
|
|
#define LRE_FLAG_MULTILINE (1 << 2)
|
|
#define LRE_FLAG_DOTALL (1 << 3)
|
|
#define LRE_FLAG_UNICODE (1 << 4)
|
|
#define LRE_FLAG_STICKY (1 << 5)
|
|
|
|
#define RE_HEADER_FLAGS 0
|
|
#define RE_HEADER_CAPTURE_COUNT 2
|
|
#define RE_HEADER_REGISTER_COUNT 3
|
|
|
|
#define RE_HEADER_LEN 4
|
|
|
|
#define CLASS_RANGE_BASE 0x40000000
|
|
|
|
typedef enum {
|
|
CHAR_RANGE_d,
|
|
CHAR_RANGE_D,
|
|
CHAR_RANGE_s,
|
|
CHAR_RANGE_S,
|
|
CHAR_RANGE_w,
|
|
CHAR_RANGE_W,
|
|
} CharRangeEnum;
|
|
|
|
static int lre_get_capture_count(const uint8_t *bc_buf)
|
|
{
|
|
return bc_buf[RE_HEADER_CAPTURE_COUNT];
|
|
}
|
|
|
|
static int lre_get_alloc_count(const uint8_t *bc_buf)
|
|
{
|
|
return bc_buf[RE_HEADER_CAPTURE_COUNT] * 2 + bc_buf[RE_HEADER_REGISTER_COUNT];
|
|
}
|
|
|
|
static int lre_get_flags(const uint8_t *bc_buf)
|
|
{
|
|
return get_u16(bc_buf + RE_HEADER_FLAGS);
|
|
}
|
|
|
|
#ifdef DUMP_REOP
|
|
static __maybe_unused void lre_dump_bytecode(const uint8_t *buf,
|
|
int buf_len)
|
|
{
|
|
int pos, len, opcode, bc_len, re_flags;
|
|
uint32_t val, val2;
|
|
|
|
assert(buf_len >= RE_HEADER_LEN);
|
|
re_flags = lre_get_flags(buf);
|
|
bc_len = buf_len - RE_HEADER_LEN;
|
|
|
|
printf("flags: 0x%x capture_count=%d reg_count=%d bytecode_len=%d\n",
|
|
re_flags, buf[RE_HEADER_CAPTURE_COUNT], buf[RE_HEADER_REGISTER_COUNT],
|
|
bc_len);
|
|
|
|
buf += RE_HEADER_LEN;
|
|
|
|
pos = 0;
|
|
while (pos < bc_len) {
|
|
printf("%5u: ", pos);
|
|
opcode = buf[pos];
|
|
len = reopcode_info[opcode].size;
|
|
if (opcode >= REOP_COUNT) {
|
|
printf(" invalid opcode=0x%02x\n", opcode);
|
|
break;
|
|
}
|
|
if ((pos + len) > bc_len) {
|
|
printf(" buffer overflow (opcode=0x%02x)\n", opcode);
|
|
break;
|
|
}
|
|
printf("%s", reopcode_info[opcode].name);
|
|
switch(opcode) {
|
|
case REOP_char1:
|
|
case REOP_char2:
|
|
case REOP_char3:
|
|
case REOP_char4:
|
|
{
|
|
int i, n;
|
|
n = opcode - REOP_char1 + 1;
|
|
for(i = 0; i < n; i++) {
|
|
val = buf[pos + 1 + i];
|
|
if (val >= ' ' && val <= 126)
|
|
printf(" '%c'", val);
|
|
else
|
|
printf(" 0x%2x", val);
|
|
}
|
|
}
|
|
break;
|
|
case REOP_goto:
|
|
case REOP_split_goto_first:
|
|
case REOP_split_next_first:
|
|
case REOP_lookahead:
|
|
case REOP_negative_lookahead:
|
|
val = get_u32(buf + pos + 1);
|
|
val += (pos + 5);
|
|
printf(" %u", val);
|
|
break;
|
|
case REOP_loop:
|
|
val2 = buf[pos + 1];
|
|
val = get_u32(buf + pos + 2);
|
|
val += (pos + 6);
|
|
printf(" r%u, %u", val2, val);
|
|
break;
|
|
case REOP_loop_split_goto_first:
|
|
case REOP_loop_split_next_first:
|
|
case REOP_loop_check_adv_split_goto_first:
|
|
case REOP_loop_check_adv_split_next_first:
|
|
{
|
|
uint32_t limit;
|
|
val2 = buf[pos + 1];
|
|
limit = get_u32(buf + pos + 2);
|
|
val = get_u32(buf + pos + 6);
|
|
val += (pos + 10);
|
|
printf(" r%u, %u, %u", val2, limit, val);
|
|
}
|
|
break;
|
|
case REOP_save_start:
|
|
case REOP_save_end:
|
|
case REOP_back_reference:
|
|
case REOP_back_reference_i:
|
|
printf(" %u", buf[pos + 1]);
|
|
break;
|
|
case REOP_save_reset:
|
|
printf(" %u %u", buf[pos + 1], buf[pos + 2]);
|
|
break;
|
|
case REOP_set_i32:
|
|
val = buf[pos + 1];
|
|
val2 = get_u32(buf + pos + 2);
|
|
printf(" r%u, %d", val, val2);
|
|
break;
|
|
case REOP_set_char_pos:
|
|
case REOP_check_advance:
|
|
val = buf[pos + 1];
|
|
printf(" r%u", val);
|
|
break;
|
|
case REOP_range8:
|
|
{
|
|
int n, i;
|
|
n = buf[pos + 1];
|
|
len += n * 2;
|
|
for(i = 0; i < n * 2; i++) {
|
|
val = buf[pos + 2 + i];
|
|
printf(" 0x%02x", val);
|
|
}
|
|
}
|
|
break;
|
|
case REOP_range:
|
|
{
|
|
int n, i;
|
|
n = get_u16(buf + pos + 1);
|
|
len += n * 8;
|
|
for(i = 0; i < n * 2; i++) {
|
|
val = get_u32(buf + pos + 3 + i * 4);
|
|
printf(" 0x%05x", val);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
printf("\n");
|
|
pos += len;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static void re_emit_op(JSParseState *s, int op)
|
|
{
|
|
emit_u8(s, op);
|
|
}
|
|
|
|
static void re_emit_op_u8(JSParseState *s, int op, uint32_t val)
|
|
{
|
|
emit_u8(s, op);
|
|
emit_u8(s, val);
|
|
}
|
|
|
|
static void re_emit_op_u16(JSParseState *s, int op, uint32_t val)
|
|
{
|
|
emit_u8(s, op);
|
|
emit_u16(s, val);
|
|
}
|
|
|
|
/* return the offset of the u32 value */
|
|
static int re_emit_op_u32(JSParseState *s, int op, uint32_t val)
|
|
{
|
|
int pos;
|
|
emit_u8(s, op);
|
|
pos = s->byte_code_len;
|
|
emit_u32(s, val);
|
|
return pos;
|
|
}
|
|
|
|
static int re_emit_goto(JSParseState *s, int op, uint32_t val)
|
|
{
|
|
int pos;
|
|
emit_u8(s, op);
|
|
pos = s->byte_code_len;
|
|
emit_u32(s, val - (pos + 4));
|
|
return pos;
|
|
}
|
|
|
|
static int re_emit_goto_u8(JSParseState *s, int op, uint32_t arg, uint32_t val)
|
|
{
|
|
int pos;
|
|
emit_u8(s, op);
|
|
emit_u8(s, arg);
|
|
pos = s->byte_code_len;
|
|
emit_u32(s, val - (pos + 4));
|
|
return pos;
|
|
}
|
|
|
|
static int re_emit_goto_u8_u32(JSParseState *s, int op, uint32_t arg0, uint32_t arg1, uint32_t val)
|
|
{
|
|
int pos;
|
|
emit_u8(s, op);
|
|
emit_u8(s, arg0);
|
|
emit_u32(s, arg1);
|
|
pos = s->byte_code_len;
|
|
emit_u32(s, val - (pos + 4));
|
|
return pos;
|
|
}
|
|
|
|
static void re_emit_char(JSParseState *s, int c)
|
|
{
|
|
uint8_t buf[4];
|
|
size_t n, i;
|
|
n = unicode_to_utf8(buf, c);
|
|
re_emit_op(s, REOP_char1 + n - 1);
|
|
for(i = 0; i < n; i++)
|
|
emit_u8(s, buf[i]);
|
|
}
|
|
|
|
static void re_parse_expect(JSParseState *s, int c)
|
|
{
|
|
if (s->source_buf[s->buf_pos] != c)
|
|
return js_parse_error(s, "expecting '%c'", c);
|
|
s->buf_pos++;
|
|
}
|
|
|
|
/* return JS_SHORTINT_MAX in case of overflow */
|
|
static int parse_digits(const uint8_t **pp)
|
|
{
|
|
const uint8_t *p;
|
|
uint64_t v;
|
|
int c;
|
|
|
|
p = *pp;
|
|
v = 0;
|
|
for(;;) {
|
|
c = *p;
|
|
if (c < '0' || c > '9')
|
|
break;
|
|
v = v * 10 + c - '0';
|
|
if (v >= JS_SHORTINT_MAX)
|
|
v = JS_SHORTINT_MAX;
|
|
p++;
|
|
}
|
|
*pp = p;
|
|
return v;
|
|
}
|
|
|
|
/* need_check_adv: false if the opcodes always advance the char pointer
|
|
need_capture_init: true if all the captures in the atom are not set
|
|
*/
|
|
static BOOL re_need_check_adv_and_capture_init(BOOL *pneed_capture_init,
|
|
const uint8_t *bc_buf, int bc_buf_len)
|
|
{
|
|
int pos, opcode, len;
|
|
uint32_t val;
|
|
BOOL need_check_adv, need_capture_init;
|
|
|
|
need_check_adv = TRUE;
|
|
need_capture_init = FALSE;
|
|
pos = 0;
|
|
while (pos < bc_buf_len) {
|
|
opcode = bc_buf[pos];
|
|
len = reopcode_info[opcode].size;
|
|
switch(opcode) {
|
|
case REOP_range8:
|
|
val = bc_buf[pos + 1];
|
|
len += val * 2;
|
|
need_check_adv = FALSE;
|
|
break;
|
|
case REOP_range:
|
|
val = get_u16(bc_buf + pos + 1);
|
|
len += val * 8;
|
|
need_check_adv = FALSE;
|
|
break;
|
|
case REOP_char1:
|
|
case REOP_char2:
|
|
case REOP_char3:
|
|
case REOP_char4:
|
|
case REOP_dot:
|
|
case REOP_any:
|
|
case REOP_space:
|
|
case REOP_not_space:
|
|
need_check_adv = FALSE;
|
|
break;
|
|
case REOP_line_start:
|
|
case REOP_line_start_m:
|
|
case REOP_line_end:
|
|
case REOP_line_end_m:
|
|
case REOP_set_i32:
|
|
case REOP_set_char_pos:
|
|
case REOP_word_boundary:
|
|
case REOP_not_word_boundary:
|
|
/* no effect */
|
|
break;
|
|
case REOP_save_start:
|
|
case REOP_save_end:
|
|
case REOP_save_reset:
|
|
break;
|
|
default:
|
|
/* safe behavior: we cannot predict the outcome */
|
|
need_capture_init = TRUE;
|
|
goto done;
|
|
}
|
|
pos += len;
|
|
}
|
|
done:
|
|
*pneed_capture_init = need_capture_init;
|
|
return need_check_adv;
|
|
}
|
|
|
|
/* return the character or a class range (>= CLASS_RANGE_BASE) if inclass
|
|
= TRUE */
|
|
static int get_class_atom(JSParseState *s, BOOL inclass)
|
|
{
|
|
const uint8_t *p;
|
|
uint32_t c;
|
|
int ret;
|
|
size_t len;
|
|
|
|
p = s->source_buf + s->buf_pos;
|
|
c = *p;
|
|
switch(c) {
|
|
case '\\':
|
|
p++;
|
|
c = *p++;
|
|
switch(c) {
|
|
case 'd':
|
|
c = CHAR_RANGE_d;
|
|
goto class_range;
|
|
case 'D':
|
|
c = CHAR_RANGE_D;
|
|
goto class_range;
|
|
case 's':
|
|
c = CHAR_RANGE_s;
|
|
goto class_range;
|
|
case 'S':
|
|
c = CHAR_RANGE_S;
|
|
goto class_range;
|
|
case 'w':
|
|
c = CHAR_RANGE_w;
|
|
goto class_range;
|
|
case 'W':
|
|
c = CHAR_RANGE_W;
|
|
class_range:
|
|
c += CLASS_RANGE_BASE;
|
|
break;
|
|
case 'c':
|
|
c = *p;
|
|
if ((c >= 'a' && c <= 'z') ||
|
|
(c >= 'A' && c <= 'Z') ||
|
|
(((c >= '0' && c <= '9') || c == '_') &&
|
|
inclass && !s->is_unicode)) { /* Annex B.1.4 */
|
|
c &= 0x1f;
|
|
p++;
|
|
} else if (s->is_unicode) {
|
|
goto invalid_escape;
|
|
} else {
|
|
/* otherwise return '\' and 'c' */
|
|
p--;
|
|
c = '\\';
|
|
}
|
|
break;
|
|
case '-':
|
|
if (!inclass && s->is_unicode)
|
|
goto invalid_escape;
|
|
break;
|
|
case '^':
|
|
case '$':
|
|
case '\\':
|
|
case '.':
|
|
case '*':
|
|
case '+':
|
|
case '?':
|
|
case '(':
|
|
case ')':
|
|
case '[':
|
|
case ']':
|
|
case '{':
|
|
case '}':
|
|
case '|':
|
|
case '/':
|
|
/* always valid to escape these characters */
|
|
break;
|
|
default:
|
|
p--;
|
|
ret = js_parse_escape(p, &len);
|
|
if (ret < 0) {
|
|
if (s->is_unicode) {
|
|
invalid_escape:
|
|
s->buf_pos = p - s->source_buf;
|
|
js_parse_error(s, "invalid escape sequence in regular expression");
|
|
} else {
|
|
goto normal_char;
|
|
}
|
|
}
|
|
p += len;
|
|
c = ret;
|
|
break;
|
|
}
|
|
break;
|
|
case '\0':
|
|
case '/': /* safety for end of regexp in JS parser */
|
|
if ((p - s->source_buf) >= s->buf_len)
|
|
js_parse_error(s, "unexpected end");
|
|
goto normal_char;
|
|
default:
|
|
normal_char:
|
|
/* normal char */
|
|
ret = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &len);
|
|
/* Note: should not fail with normal JS strings */
|
|
if (ret < 0)
|
|
js_parse_error(s, "malformed unicode char");
|
|
p += len;
|
|
c = ret;
|
|
break;
|
|
}
|
|
s->buf_pos = p - s->source_buf;
|
|
return c;
|
|
}
|
|
|
|
/* code point ranges for Zs,Zl or Zp property */
|
|
static const uint16_t char_range_s[] = {
|
|
0x0009, 0x000D + 1,
|
|
0x0020, 0x0020 + 1,
|
|
0x00A0, 0x00A0 + 1,
|
|
0x1680, 0x1680 + 1,
|
|
0x2000, 0x200A + 1,
|
|
/* 2028;LINE SEPARATOR;Zl;0;WS;;;;;N;;;;; */
|
|
/* 2029;PARAGRAPH SEPARATOR;Zp;0;B;;;;;N;;;;; */
|
|
0x2028, 0x2029 + 1,
|
|
0x202F, 0x202F + 1,
|
|
0x205F, 0x205F + 1,
|
|
0x3000, 0x3000 + 1,
|
|
/* FEFF;ZERO WIDTH NO-BREAK SPACE;Cf;0;BN;;;;;N;BYTE ORDER MARK;;;; */
|
|
0xFEFF, 0xFEFF + 1,
|
|
};
|
|
|
|
static const uint16_t char_range_w[] = {
|
|
0x0030, 0x0039 + 1,
|
|
0x0041, 0x005A + 1,
|
|
0x005F, 0x005F + 1,
|
|
0x0061, 0x007A + 1,
|
|
};
|
|
|
|
static void re_emit_range_base1(JSParseState *s, const uint16_t *tab, int n)
|
|
{
|
|
int i;
|
|
for(i = 0; i < n; i++)
|
|
emit_u32(s, tab[i]);
|
|
}
|
|
|
|
static void re_emit_range_base(JSParseState *s, int c)
|
|
{
|
|
BOOL invert;
|
|
invert = c & 1;
|
|
if (invert)
|
|
emit_u32(s, 0);
|
|
switch(c & ~1) {
|
|
case CHAR_RANGE_d:
|
|
emit_u32(s, 0x30);
|
|
emit_u32(s, 0x39 + 1);
|
|
break;
|
|
case CHAR_RANGE_s:
|
|
re_emit_range_base1(s, char_range_s, countof(char_range_s));
|
|
break;
|
|
case CHAR_RANGE_w:
|
|
re_emit_range_base1(s, char_range_w, countof(char_range_w));
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
if (invert)
|
|
emit_u32(s, 0x110000);
|
|
}
|
|
|
|
static int range_sort_cmp(size_t i1, size_t i2, void *opaque)
|
|
{
|
|
uint8_t *tab = opaque;
|
|
return get_u32(&tab[8 * i1]) - get_u32(&tab[8 * i2]);
|
|
}
|
|
|
|
static void range_sort_swap(size_t i1, size_t i2, void *opaque)
|
|
{
|
|
uint8_t *tab = opaque;
|
|
uint64_t tmp;
|
|
tmp = get_u64(&tab[8 * i1]);
|
|
put_u64(&tab[8 * i1], get_u64(&tab[8 * i2]));
|
|
put_u64(&tab[8 * i2], tmp);
|
|
}
|
|
|
|
/* merge consecutive intervals, remove empty intervals and handle overlapping intervals */
|
|
static int range_compress(uint8_t *tab, int len)
|
|
{
|
|
int i, j;
|
|
uint32_t start, end, start2, end2;
|
|
|
|
i = 0;
|
|
j = 0;
|
|
while (i < len) {
|
|
start = get_u32(&tab[8 * i]);
|
|
end = get_u32(&tab[8 * i + 4]);
|
|
if (start == end) {
|
|
/* empty interval : remove */
|
|
} else if ((i + 1) < len) {
|
|
start2 = get_u32(&tab[8 * i + 8]);
|
|
end2 = get_u32(&tab[8 * i + 12]);
|
|
if (end < start2) {
|
|
goto copy;
|
|
} else {
|
|
/* union of the intervals */
|
|
put_u32(&tab[8 * i + 8], start);
|
|
put_u32(&tab[8 * i + 12], max_uint32(end, end2));
|
|
}
|
|
} else {
|
|
copy:
|
|
put_u32(&tab[8 * j], start);
|
|
put_u32(&tab[8 * j + 4], end);
|
|
j++;
|
|
}
|
|
i++;
|
|
}
|
|
return j;
|
|
}
|
|
|
|
static void re_range_optimize(JSParseState *s, int range_start, BOOL invert)
|
|
{
|
|
int n, n1;
|
|
JSByteArray *arr;
|
|
|
|
n = (unsigned)(s->byte_code_len - range_start) / 8;
|
|
|
|
arr = JS_VALUE_TO_PTR(s->byte_code);
|
|
rqsort_idx(n, range_sort_cmp, range_sort_swap, arr->buf + range_start);
|
|
|
|
/* must compress before inverting */
|
|
n1 = range_compress(arr->buf + range_start, n);
|
|
s->byte_code_len -= (n - n1) * 8;
|
|
|
|
if (invert) {
|
|
emit_insert(s, range_start, 4);
|
|
arr = JS_VALUE_TO_PTR(s->byte_code);
|
|
put_u32(arr->buf + range_start, 0);
|
|
emit_u32(s, 0x110000);
|
|
arr = JS_VALUE_TO_PTR(s->byte_code);
|
|
n = n1 + 1;
|
|
n1 = range_compress(arr->buf + range_start, n);
|
|
s->byte_code_len -= (n - n1) * 8;
|
|
}
|
|
n = n1;
|
|
|
|
if (n > 65534)
|
|
js_parse_error(s, "range too big");
|
|
|
|
/* compress to 8 bit if possible */
|
|
/* XXX: adjust threshold */
|
|
if (n > 0 && n < 16) {
|
|
uint8_t *tab = arr->buf + range_start;
|
|
int c, i;
|
|
c = get_u32(&tab[8 * (n - 1) + 4]);
|
|
if (c < 254 || (c == 0x110000 &&
|
|
get_u32(&tab[8 * (n - 1)]) < 254)) {
|
|
s->byte_code_len = range_start - 3;
|
|
re_emit_op_u8(s, REOP_range8, n);
|
|
for(i = 0; i < 2 * n; i++) {
|
|
c = get_u32(&tab[4 * i]);
|
|
if (c == 0x110000)
|
|
c = 0xff;
|
|
emit_u8(s, c);
|
|
}
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
put_u16(arr->buf + range_start - 2, n);
|
|
done: ;
|
|
}
|
|
|
|
/* add the intersection of the two intervals and if offset != 0 the
|
|
translated interval */
|
|
static void add_interval_intersect(JSParseState *s,
|
|
uint32_t start, uint32_t end,
|
|
uint32_t start1, uint32_t end1,
|
|
int offset)
|
|
{
|
|
start = max_uint32(start, start1);
|
|
end = min_uint32(end, end1);
|
|
if (start < end) {
|
|
emit_u32(s, start);
|
|
emit_u32(s, end);
|
|
if (offset != 0) {
|
|
emit_u32(s, start + offset);
|
|
emit_u32(s, end + offset);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void re_parse_char_class(JSParseState *s)
|
|
{
|
|
uint32_t c1, c2;
|
|
BOOL invert;
|
|
int range_start;
|
|
|
|
s->buf_pos++; /* skip '[' */
|
|
|
|
invert = FALSE;
|
|
if (s->source_buf[s->buf_pos] == '^') {
|
|
s->buf_pos++;
|
|
invert = TRUE;
|
|
}
|
|
|
|
re_emit_op_u16(s, REOP_range, 0);
|
|
range_start = s->byte_code_len;
|
|
|
|
for(;;) {
|
|
if (s->source_buf[s->buf_pos] == ']')
|
|
break;
|
|
|
|
c1 = get_class_atom(s, TRUE);
|
|
if (s->source_buf[s->buf_pos] == '-' && s->source_buf[s->buf_pos + 1] != ']') {
|
|
s->buf_pos++;
|
|
if (c1 >= CLASS_RANGE_BASE)
|
|
goto invalid_class_range;
|
|
c2 = get_class_atom(s, TRUE);
|
|
if (c2 >= CLASS_RANGE_BASE)
|
|
goto invalid_class_range;
|
|
if (c2 < c1) {
|
|
invalid_class_range:
|
|
js_parse_error(s, "invalid class range");
|
|
}
|
|
goto add_range;
|
|
} else {
|
|
if (c1 >= CLASS_RANGE_BASE) {
|
|
re_emit_range_base(s, c1 - CLASS_RANGE_BASE);
|
|
} else {
|
|
c2 = c1;
|
|
add_range:
|
|
c2++;
|
|
if (s->ignore_case) {
|
|
/* add the intervals exclude the cased characters */
|
|
add_interval_intersect(s, c1, c2, 0, 'A', 0);
|
|
add_interval_intersect(s, c1, c2, 'Z' + 1, 'a', 0);
|
|
add_interval_intersect(s, c1, c2, 'z' + 1, INT32_MAX, 0);
|
|
/* include all the possible cases */
|
|
add_interval_intersect(s, c1, c2, 'A', 'Z' + 1, 32);
|
|
add_interval_intersect(s, c1, c2, 'a', 'z' + 1, -32);
|
|
} else {
|
|
emit_u32(s, c1);
|
|
emit_u32(s, c2);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
s->buf_pos++; /* skip ']' */
|
|
re_range_optimize(s, range_start, invert);
|
|
}
|
|
|
|
static void re_parse_quantifier(JSParseState *s, int last_atom_start, int last_capture_count)
|
|
{
|
|
int c, quant_min, quant_max;
|
|
JSByteArray *arr;
|
|
BOOL greedy;
|
|
const uint8_t *p;
|
|
|
|
p = s->source_buf + s->buf_pos;
|
|
c = *p;
|
|
switch(c) {
|
|
case '*':
|
|
p++;
|
|
quant_min = 0;
|
|
quant_max = JS_SHORTINT_MAX;
|
|
goto quantifier;
|
|
case '+':
|
|
p++;
|
|
quant_min = 1;
|
|
quant_max = JS_SHORTINT_MAX;
|
|
goto quantifier;
|
|
case '?':
|
|
p++;
|
|
quant_min = 0;
|
|
quant_max = 1;
|
|
goto quantifier;
|
|
case '{':
|
|
{
|
|
if (!is_digit(p[1]))
|
|
goto invalid_quant_count;
|
|
p++;
|
|
quant_min = parse_digits(&p);
|
|
quant_max = quant_min;
|
|
if (*p == ',') {
|
|
p++;
|
|
if (is_digit(*p)) {
|
|
quant_max = parse_digits(&p);
|
|
if (quant_max < quant_min) {
|
|
invalid_quant_count:
|
|
js_parse_error(s, "invalid repetition count");
|
|
}
|
|
} else {
|
|
quant_max = JS_SHORTINT_MAX; /* infinity */
|
|
}
|
|
}
|
|
s->buf_pos = p - s->source_buf;
|
|
re_parse_expect(s, '}');
|
|
p = s->source_buf + s->buf_pos;
|
|
}
|
|
quantifier:
|
|
greedy = TRUE;
|
|
|
|
if (*p == '?') {
|
|
p++;
|
|
greedy = FALSE;
|
|
}
|
|
s->buf_pos = p - s->source_buf;
|
|
|
|
if (last_atom_start < 0)
|
|
js_parse_error(s, "nothing to repeat");
|
|
{
|
|
BOOL need_capture_init, add_zero_advance_check;
|
|
int len, pos;
|
|
|
|
/* the spec tells that if there is no advance when
|
|
running the atom after the first quant_min times,
|
|
then there is no match. We remove this test when we
|
|
are sure the atom always advances the position. */
|
|
arr = JS_VALUE_TO_PTR(s->byte_code);
|
|
add_zero_advance_check =
|
|
re_need_check_adv_and_capture_init(&need_capture_init,
|
|
arr->buf + last_atom_start,
|
|
s->byte_code_len - last_atom_start);
|
|
|
|
/* general case: need to reset the capture at each
|
|
iteration. We don't do it if there are no captures
|
|
in the atom or if we are sure all captures are
|
|
initialized in the atom. If quant_min = 0, we still
|
|
need to reset once the captures in case the atom
|
|
does not match. */
|
|
if (need_capture_init && last_capture_count != s->capture_count) {
|
|
emit_insert(s, last_atom_start, 3);
|
|
int pos = last_atom_start;
|
|
arr = JS_VALUE_TO_PTR(s->byte_code);
|
|
arr->buf[pos++] = REOP_save_reset;
|
|
arr->buf[pos++] = last_capture_count;
|
|
arr->buf[pos++] = s->capture_count - 1;
|
|
}
|
|
|
|
len = s->byte_code_len - last_atom_start;
|
|
if (quant_min == 0) {
|
|
/* need to reset the capture in case the atom is
|
|
not executed */
|
|
if (!need_capture_init && last_capture_count != s->capture_count) {
|
|
emit_insert(s, last_atom_start, 3);
|
|
arr = JS_VALUE_TO_PTR(s->byte_code);
|
|
arr->buf[last_atom_start++] = REOP_save_reset;
|
|
arr->buf[last_atom_start++] = last_capture_count;
|
|
arr->buf[last_atom_start++] = s->capture_count - 1;
|
|
}
|
|
if (quant_max == 0) {
|
|
s->byte_code_len = last_atom_start;
|
|
} else if (quant_max == 1 || quant_max == JS_SHORTINT_MAX) {
|
|
BOOL has_goto = (quant_max == JS_SHORTINT_MAX);
|
|
emit_insert(s, last_atom_start, 5 + add_zero_advance_check * 2);
|
|
arr = JS_VALUE_TO_PTR(s->byte_code);
|
|
arr->buf[last_atom_start] = REOP_split_goto_first +
|
|
greedy;
|
|
put_u32(arr->buf + last_atom_start + 1,
|
|
len + 5 * has_goto + add_zero_advance_check * 2 * 2);
|
|
if (add_zero_advance_check) {
|
|
arr->buf[last_atom_start + 1 + 4] = REOP_set_char_pos;
|
|
arr->buf[last_atom_start + 1 + 4 + 1] = 0;
|
|
re_emit_op_u8(s, REOP_check_advance, 0);
|
|
}
|
|
if (has_goto)
|
|
re_emit_goto(s, REOP_goto, last_atom_start);
|
|
} else {
|
|
emit_insert(s, last_atom_start, 11 + add_zero_advance_check * 2);
|
|
pos = last_atom_start;
|
|
arr = JS_VALUE_TO_PTR(s->byte_code);
|
|
arr->buf[pos++] = REOP_split_goto_first + greedy;
|
|
put_u32(arr->buf + pos, 6 + add_zero_advance_check * 2 + len + 10);
|
|
pos += 4;
|
|
|
|
arr->buf[pos++] = REOP_set_i32;
|
|
arr->buf[pos++] = 0;
|
|
put_u32(arr->buf + pos, quant_max);
|
|
pos += 4;
|
|
last_atom_start = pos;
|
|
if (add_zero_advance_check) {
|
|
arr->buf[pos++] = REOP_set_char_pos;
|
|
arr->buf[pos++] = 0;
|
|
}
|
|
re_emit_goto_u8_u32(s, (add_zero_advance_check ? REOP_loop_check_adv_split_next_first : REOP_loop_split_next_first) - greedy, 0, quant_max, last_atom_start);
|
|
}
|
|
} else if (quant_min == 1 && quant_max == JS_SHORTINT_MAX &&
|
|
!add_zero_advance_check) {
|
|
re_emit_goto(s, REOP_split_next_first - greedy,
|
|
last_atom_start);
|
|
} else {
|
|
if (quant_min == quant_max)
|
|
add_zero_advance_check = FALSE;
|
|
emit_insert(s, last_atom_start, 6 + add_zero_advance_check * 2);
|
|
/* Note: we assume the string length is < JS_SHORTINT_MAX */
|
|
pos = last_atom_start;
|
|
arr = JS_VALUE_TO_PTR(s->byte_code);
|
|
arr->buf[pos++] = REOP_set_i32;
|
|
arr->buf[pos++] = 0;
|
|
put_u32(arr->buf + pos, quant_max);
|
|
pos += 4;
|
|
last_atom_start = pos;
|
|
if (add_zero_advance_check) {
|
|
arr->buf[pos++] = REOP_set_char_pos;
|
|
arr->buf[pos++] = 0;
|
|
}
|
|
if (quant_min == quant_max) {
|
|
/* a simple loop is enough */
|
|
re_emit_goto_u8(s, REOP_loop, 0, last_atom_start);
|
|
} else {
|
|
re_emit_goto_u8_u32(s, (add_zero_advance_check ? REOP_loop_check_adv_split_next_first : REOP_loop_split_next_first) - greedy, 0, quant_max - quant_min, last_atom_start);
|
|
}
|
|
}
|
|
last_atom_start = -1;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* return the number of bytes if char otherwise 0 */
|
|
static int re_is_char(const uint8_t *buf, int start, int end)
|
|
{
|
|
int n;
|
|
if (!(buf[start] >= REOP_char1 && buf[start] <= REOP_char4))
|
|
return 0;
|
|
n = buf[start] - REOP_char1 + 1;
|
|
if ((end - start) != (n + 1))
|
|
return 0;
|
|
return n;
|
|
}
|
|
|
|
static int re_parse_alternative(JSParseState *s, int state, int dummy_param)
|
|
{
|
|
int term_start, last_term_start, last_atom_start, last_capture_count, c, n1, n2, i;
|
|
JSByteArray *arr;
|
|
|
|
PARSE_START3();
|
|
|
|
last_term_start = -1;
|
|
for(;;) {
|
|
if (s->buf_pos >= s->buf_len)
|
|
break;
|
|
term_start = s->byte_code_len;
|
|
|
|
last_atom_start = -1;
|
|
last_capture_count = 0;
|
|
c = s->source_buf[s->buf_pos];
|
|
switch(c) {
|
|
case '|':
|
|
case ')':
|
|
goto done;
|
|
case '^':
|
|
s->buf_pos++;
|
|
re_emit_op(s, s->multi_line ? REOP_line_start_m : REOP_line_start);
|
|
break;
|
|
case '$':
|
|
s->buf_pos++;
|
|
re_emit_op(s, s->multi_line ? REOP_line_end_m : REOP_line_end);
|
|
break;
|
|
case '.':
|
|
s->buf_pos++;
|
|
last_atom_start = s->byte_code_len;
|
|
last_capture_count = s->capture_count;
|
|
re_emit_op(s, s->dotall ? REOP_any : REOP_dot);
|
|
break;
|
|
case '{':
|
|
/* As an extension (see ES6 annex B), we accept '{' not
|
|
followed by digits as a normal atom */
|
|
if (!s->is_unicode && !is_digit(s->source_buf[s->buf_pos + 1]))
|
|
goto parse_class_atom;
|
|
/* fall thru */
|
|
case '*':
|
|
case '+':
|
|
case '?':
|
|
js_parse_error(s, "nothing to repeat");
|
|
case '(':
|
|
if (s->source_buf[s->buf_pos + 1] == '?') {
|
|
c = s->source_buf[s->buf_pos + 2];
|
|
if (c == ':') {
|
|
s->buf_pos += 3;
|
|
last_atom_start = s->byte_code_len;
|
|
last_capture_count = s->capture_count;
|
|
PARSE_CALL_SAVE4(s, 0, re_parse_disjunction, 0,
|
|
last_term_start, term_start, last_atom_start, last_capture_count);
|
|
re_parse_expect(s, ')');
|
|
} else if ((c == '=' || c == '!')) {
|
|
int is_neg, pos;
|
|
is_neg = (c == '!');
|
|
s->buf_pos += 3;
|
|
/* lookahead */
|
|
pos = re_emit_op_u32(s, REOP_lookahead + is_neg, 0);
|
|
PARSE_CALL_SAVE6(s, 1, re_parse_disjunction, 0,
|
|
last_term_start, term_start, last_atom_start, last_capture_count,
|
|
is_neg, pos);
|
|
re_parse_expect(s, ')');
|
|
re_emit_op(s, REOP_lookahead_match + is_neg);
|
|
/* jump after the 'match' after the lookahead is successful */
|
|
arr = JS_VALUE_TO_PTR(s->byte_code);
|
|
put_u32(arr->buf + pos, s->byte_code_len - (pos + 4));
|
|
} else {
|
|
js_parse_error(s, "invalid group");
|
|
}
|
|
} else {
|
|
int capture_index;
|
|
s->buf_pos++;
|
|
/* capture without group name */
|
|
if (s->capture_count >= CAPTURE_COUNT_MAX)
|
|
js_parse_error(s, "too many captures");
|
|
last_atom_start = s->byte_code_len;
|
|
last_capture_count = s->capture_count;
|
|
capture_index = s->capture_count++;
|
|
re_emit_op_u8(s, REOP_save_start, capture_index);
|
|
|
|
PARSE_CALL_SAVE5(s, 2, re_parse_disjunction, 0,
|
|
last_term_start, term_start, last_atom_start, last_capture_count,
|
|
capture_index);
|
|
|
|
re_emit_op_u8(s, REOP_save_end, capture_index);
|
|
|
|
re_parse_expect(s, ')');
|
|
}
|
|
break;
|
|
case '\\':
|
|
switch(s->source_buf[s->buf_pos + 1]) {
|
|
case 'b':
|
|
case 'B':
|
|
if (s->source_buf[s->buf_pos + 1] != 'b') {
|
|
re_emit_op(s, REOP_not_word_boundary);
|
|
} else {
|
|
re_emit_op(s, REOP_word_boundary);
|
|
}
|
|
s->buf_pos += 2;
|
|
break;
|
|
case '0':
|
|
s->buf_pos += 2;
|
|
c = 0;
|
|
if (is_digit(s->source_buf[s->buf_pos]))
|
|
js_parse_error(s, "invalid decimal escape in regular expression");
|
|
goto normal_char;
|
|
case '1': case '2': case '3': case '4':
|
|
case '5': case '6': case '7': case '8':
|
|
case '9':
|
|
{
|
|
const uint8_t *p;
|
|
p = s->source_buf + s->buf_pos + 1;
|
|
c = parse_digits(&p);
|
|
s->buf_pos = p - s->source_buf;
|
|
if (c > CAPTURE_COUNT_MAX)
|
|
js_parse_error(s, "back reference is out of range");
|
|
/* the range is checked afterwards as we don't know the number of captures */
|
|
last_atom_start = s->byte_code_len;
|
|
last_capture_count = s->capture_count;
|
|
re_emit_op_u8(s, REOP_back_reference + s->ignore_case, c);
|
|
}
|
|
break;
|
|
default:
|
|
goto parse_class_atom;
|
|
}
|
|
break;
|
|
case '[':
|
|
last_atom_start = s->byte_code_len;
|
|
last_capture_count = s->capture_count;
|
|
re_parse_char_class(s);
|
|
break;
|
|
case ']':
|
|
case '}':
|
|
if (s->is_unicode)
|
|
js_parse_error(s, "syntax error");
|
|
goto parse_class_atom;
|
|
default:
|
|
parse_class_atom:
|
|
c = get_class_atom(s, FALSE);
|
|
normal_char:
|
|
last_atom_start = s->byte_code_len;
|
|
last_capture_count = s->capture_count;
|
|
if (c >= CLASS_RANGE_BASE) {
|
|
int range_start;
|
|
c -= CLASS_RANGE_BASE;
|
|
if (c == CHAR_RANGE_s || c == CHAR_RANGE_S) {
|
|
re_emit_op(s, REOP_space + c - CHAR_RANGE_s);
|
|
} else {
|
|
re_emit_op_u16(s, REOP_range, 0);
|
|
range_start = s->byte_code_len;
|
|
|
|
re_emit_range_base(s, c);
|
|
re_range_optimize(s, range_start, FALSE);
|
|
}
|
|
} else {
|
|
if (s->ignore_case &&
|
|
((c >= 'A' && c <= 'Z') ||
|
|
(c >= 'a' && c <= 'z'))) {
|
|
/* XXX: could add specific operation */
|
|
if (c >= 'a')
|
|
c -= 32;
|
|
re_emit_op_u8(s, REOP_range8, 2);
|
|
emit_u8(s, c);
|
|
emit_u8(s, c + 1);
|
|
emit_u8(s, c + 32);
|
|
emit_u8(s, c + 32 + 1);
|
|
} else {
|
|
re_emit_char(s, c);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* quantifier */
|
|
if (last_atom_start >= 0) {
|
|
re_parse_quantifier(s, last_atom_start, last_capture_count);
|
|
}
|
|
|
|
/* combine several characters when possible */
|
|
arr = JS_VALUE_TO_PTR(s->byte_code);
|
|
if (last_term_start >= 0 &&
|
|
(n1 = re_is_char(arr->buf, last_term_start, term_start)) > 0 &&
|
|
(n2 = re_is_char(arr->buf, term_start, s->byte_code_len)) > 0 &&
|
|
(n1 + n2) <= 4) {
|
|
n1 += n2;
|
|
arr->buf[last_term_start] = REOP_char1 + n1 - 1;
|
|
for(i = 0; i < n2; i++)
|
|
arr->buf[last_term_start + n1 + i] = arr->buf[last_term_start + n1 + i + 1];
|
|
s->byte_code_len--;
|
|
} else {
|
|
last_term_start = term_start;
|
|
}
|
|
}
|
|
done:
|
|
return PARSE_STATE_RET;
|
|
}
|
|
|
|
static int re_parse_disjunction(JSParseState *s, int state, int dummy_param)
|
|
{
|
|
int start, len, pos;
|
|
JSByteArray *arr;
|
|
|
|
PARSE_START2();
|
|
|
|
start = s->byte_code_len;
|
|
|
|
PARSE_CALL_SAVE1(s, 0, re_parse_alternative, 0, start);
|
|
while (s->source_buf[s->buf_pos] == '|') {
|
|
s->buf_pos++;
|
|
|
|
len = s->byte_code_len - start;
|
|
|
|
/* insert a split before the first alternative */
|
|
emit_insert(s, start, 5);
|
|
arr = JS_VALUE_TO_PTR(s->byte_code);
|
|
arr->buf[start] = REOP_split_next_first;
|
|
put_u32(arr->buf + start + 1, len + 5);
|
|
|
|
pos = re_emit_op_u32(s, REOP_goto, 0);
|
|
|
|
PARSE_CALL_SAVE2(s, 1, re_parse_alternative, 0, start, pos);
|
|
|
|
/* patch the goto */
|
|
len = s->byte_code_len - (pos + 4);
|
|
arr = JS_VALUE_TO_PTR(s->byte_code);
|
|
put_u32(arr->buf + pos, len);
|
|
}
|
|
return PARSE_STATE_RET;
|
|
}
|
|
|
|
/* Allocate the registers as a stack. The control flow is recursive so
|
|
the analysis can be linear. */
|
|
static int re_compute_register_count(JSParseState *s, uint8_t *bc_buf, int bc_buf_len)
|
|
{
|
|
int stack_size, stack_size_max, pos, opcode, len;
|
|
uint32_t val;
|
|
|
|
stack_size = 0;
|
|
stack_size_max = 0;
|
|
pos = 0;
|
|
while (pos < bc_buf_len) {
|
|
opcode = bc_buf[pos];
|
|
len = reopcode_info[opcode].size;
|
|
assert(opcode < REOP_COUNT);
|
|
assert((pos + len) <= bc_buf_len);
|
|
switch(opcode) {
|
|
case REOP_set_i32:
|
|
case REOP_set_char_pos:
|
|
bc_buf[pos + 1] = stack_size;
|
|
stack_size++;
|
|
if (stack_size > stack_size_max) {
|
|
if (stack_size > REGISTER_COUNT_MAX)
|
|
js_parse_error(s, "too many regexp registers");
|
|
stack_size_max = stack_size;
|
|
}
|
|
break;
|
|
case REOP_check_advance:
|
|
case REOP_loop:
|
|
case REOP_loop_split_goto_first:
|
|
case REOP_loop_split_next_first:
|
|
assert(stack_size > 0);
|
|
stack_size--;
|
|
bc_buf[pos + 1] = stack_size;
|
|
break;
|
|
case REOP_loop_check_adv_split_goto_first:
|
|
case REOP_loop_check_adv_split_next_first:
|
|
assert(stack_size >= 2);
|
|
stack_size -= 2;
|
|
bc_buf[pos + 1] = stack_size;
|
|
break;
|
|
case REOP_range8:
|
|
val = bc_buf[pos + 1];
|
|
len += val * 2;
|
|
break;
|
|
case REOP_range:
|
|
val = get_u16(bc_buf + pos + 1);
|
|
len += val * 8;
|
|
break;
|
|
case REOP_back_reference:
|
|
case REOP_back_reference_i:
|
|
/* validate back references */
|
|
if (bc_buf[pos + 1] >= s->capture_count)
|
|
js_parse_error(s, "back reference is out of range");
|
|
break;
|
|
}
|
|
pos += len;
|
|
}
|
|
return stack_size_max;
|
|
}
|
|
|
|
/* return a JSByteArray. 'source' must be a string */
|
|
static JSValue js_parse_regexp(JSParseState *s, int re_flags)
|
|
{
|
|
JSByteArray *arr;
|
|
int register_count;
|
|
|
|
s->multi_line = ((re_flags & LRE_FLAG_MULTILINE) != 0);
|
|
s->dotall = ((re_flags & LRE_FLAG_DOTALL) != 0);
|
|
s->ignore_case = ((re_flags & LRE_FLAG_IGNORECASE) != 0);
|
|
s->is_unicode = ((re_flags & LRE_FLAG_UNICODE) != 0);
|
|
s->byte_code = JS_NULL;
|
|
s->byte_code_len = 0;
|
|
s->capture_count = 1;
|
|
|
|
emit_u16(s, re_flags);
|
|
emit_u8(s, 0); /* number of captures */
|
|
emit_u8(s, 0); /* number of registers */
|
|
|
|
if (!(re_flags & LRE_FLAG_STICKY)) {
|
|
re_emit_op_u32(s, REOP_split_goto_first, 1 + 5);
|
|
re_emit_op(s, REOP_any);
|
|
re_emit_op_u32(s, REOP_goto, -(5 + 1 + 5));
|
|
}
|
|
re_emit_op_u8(s, REOP_save_start, 0);
|
|
|
|
js_parse_call(s, PARSE_FUNC_re_parse_disjunction, 0);
|
|
|
|
re_emit_op_u8(s, REOP_save_end, 0);
|
|
re_emit_op(s, REOP_match);
|
|
|
|
if (s->buf_pos != s->buf_len)
|
|
js_parse_error(s, "extraneous characters at the end");
|
|
|
|
arr = JS_VALUE_TO_PTR(s->byte_code);
|
|
arr->buf[RE_HEADER_CAPTURE_COUNT] = s->capture_count;
|
|
register_count =
|
|
re_compute_register_count(s, arr->buf + RE_HEADER_LEN,
|
|
s->byte_code_len - RE_HEADER_LEN);
|
|
arr->buf[RE_HEADER_REGISTER_COUNT] = register_count;
|
|
|
|
js_shrink_byte_array(s->ctx, &s->byte_code, s->byte_code_len);
|
|
|
|
#ifdef DUMP_REOP
|
|
arr = JS_VALUE_TO_PTR(s->byte_code);
|
|
lre_dump_bytecode(arr->buf, arr->size);
|
|
#endif
|
|
|
|
return s->byte_code;
|
|
}
|
|
|
|
/* regexp interpreter */
|
|
|
|
#define CP_LS 0x2028
|
|
#define CP_PS 0x2029
|
|
|
|
static BOOL is_line_terminator(uint32_t c)
|
|
{
|
|
return (c == '\n' || c == '\r' || c == CP_LS || c == CP_PS);
|
|
}
|
|
|
|
static BOOL is_word_char(uint32_t c)
|
|
{
|
|
return ((c >= '0' && c <= '9') ||
|
|
(c >= 'a' && c <= 'z') ||
|
|
(c >= 'A' && c <= 'Z') ||
|
|
(c == '_'));
|
|
}
|
|
|
|
/* Note: we canonicalize as in the unicode case, but only handle ASCII characters */
|
|
static int lre_canonicalize(uint32_t c)
|
|
{
|
|
if (c >= 'A' && c <= 'Z') {
|
|
c = c - 'A' + 'a';
|
|
}
|
|
return c;
|
|
}
|
|
|
|
#define GET_CHAR(c, cptr, cbuf_end) \
|
|
do { \
|
|
size_t clen; \
|
|
c = utf8_get(cptr, &clen); \
|
|
cptr += clen; \
|
|
} while (0)
|
|
|
|
#define PEEK_CHAR(c, cptr, cbuf_end) \
|
|
do { \
|
|
size_t clen; \
|
|
c = utf8_get(cptr, &clen); \
|
|
} while (0)
|
|
|
|
#define PEEK_PREV_CHAR(c, cptr, cbuf_start) \
|
|
do { \
|
|
const uint8_t *cptr1 = cptr - 1; \
|
|
size_t clen; \
|
|
while ((*cptr1 & 0xc0) == 0x80) \
|
|
cptr1--; \
|
|
c = utf8_get(cptr1, &clen); \
|
|
} while (0)
|
|
|
|
typedef enum {
|
|
RE_EXEC_STATE_SPLIT,
|
|
RE_EXEC_STATE_LOOKAHEAD,
|
|
RE_EXEC_STATE_NEGATIVE_LOOKAHEAD,
|
|
} REExecStateEnum;
|
|
|
|
//#define DUMP_REEXEC
|
|
|
|
/* return 1 if match, 0 if not match or < 0 if error. str must be a
|
|
JSString. capture_buf and byte_code are JSByteArray */
|
|
static int lre_exec(JSContext *ctx, JSValue capture_buf,
|
|
JSValue byte_code, JSValue str, int cindex)
|
|
{
|
|
const uint8_t *pc, *cptr, *cbuf;
|
|
uint32_t *capture;
|
|
int opcode, capture_count;
|
|
uint32_t val, c, idx;
|
|
const uint8_t *cbuf_end;
|
|
JSValue *sp, *bp, *initial_sp, *saved_stack_bottom;
|
|
JSByteArray *arr; /* temporary use */
|
|
JSString *ps; /* temporary use */
|
|
JSGCRef capture_buf_ref, byte_code_ref, str_ref;
|
|
|
|
arr = JS_VALUE_TO_PTR(byte_code);
|
|
pc = arr->buf;
|
|
arr = JS_VALUE_TO_PTR(capture_buf);
|
|
capture = (uint32_t *)arr->buf;
|
|
capture_count = lre_get_capture_count(pc);
|
|
pc += RE_HEADER_LEN;
|
|
ps = JS_VALUE_TO_PTR(str);
|
|
cbuf = ps->buf;
|
|
cbuf_end = cbuf + ps->len;
|
|
cptr = cbuf + cindex;
|
|
|
|
saved_stack_bottom = ctx->stack_bottom;
|
|
initial_sp = ctx->sp;
|
|
sp = initial_sp;
|
|
bp = initial_sp;
|
|
|
|
#define LRE_POLL_INTERRUPT() do { \
|
|
if (unlikely(--ctx->interrupt_counter <= 0)) { \
|
|
JSValue ret; \
|
|
int saved_pc, saved_cptr; \
|
|
arr = JS_VALUE_TO_PTR(byte_code); \
|
|
saved_pc = pc - arr->buf; \
|
|
saved_cptr = cptr - cbuf; \
|
|
JS_PUSH_VALUE(ctx, capture_buf); \
|
|
JS_PUSH_VALUE(ctx, byte_code); \
|
|
JS_PUSH_VALUE(ctx, str); \
|
|
ctx->sp = sp; \
|
|
ret = __js_poll_interrupt(ctx); \
|
|
JS_POP_VALUE(ctx, str); \
|
|
JS_POP_VALUE(ctx, byte_code); \
|
|
JS_POP_VALUE(ctx, capture_buf); \
|
|
if (JS_IsException(ret)) { \
|
|
ctx->sp = initial_sp; \
|
|
ctx->stack_bottom = saved_stack_bottom; \
|
|
return -1; \
|
|
} \
|
|
arr = JS_VALUE_TO_PTR(byte_code); \
|
|
pc = arr->buf + saved_pc; \
|
|
ps = JS_VALUE_TO_PTR(str); \
|
|
cbuf = ps->buf; \
|
|
cbuf_end = cbuf + ps->len; \
|
|
cptr = cbuf + saved_cptr; \
|
|
arr = JS_VALUE_TO_PTR(capture_buf); \
|
|
capture = (uint32_t *)arr->buf; \
|
|
} \
|
|
} while(0)
|
|
|
|
#define CHECK_STACK_SPACE(n) \
|
|
{ \
|
|
if (unlikely((sp - ctx->stack_bottom) < (n))) { \
|
|
int ret, saved_pc, saved_cptr; \
|
|
arr = JS_VALUE_TO_PTR(byte_code); \
|
|
saved_pc = pc - arr->buf; \
|
|
saved_cptr = cptr - cbuf; \
|
|
JS_PUSH_VALUE(ctx, capture_buf); \
|
|
JS_PUSH_VALUE(ctx, byte_code); \
|
|
JS_PUSH_VALUE(ctx, str); \
|
|
ctx->sp = sp; \
|
|
ret = JS_StackCheck(ctx, n); \
|
|
JS_POP_VALUE(ctx, str); \
|
|
JS_POP_VALUE(ctx, byte_code); \
|
|
JS_POP_VALUE(ctx, capture_buf); \
|
|
if (ret < 0) { \
|
|
ctx->sp = initial_sp; \
|
|
ctx->stack_bottom = saved_stack_bottom; \
|
|
return -1; \
|
|
} \
|
|
arr = JS_VALUE_TO_PTR(byte_code); \
|
|
pc = arr->buf + saved_pc; \
|
|
ps = JS_VALUE_TO_PTR(str); \
|
|
cbuf = ps->buf; \
|
|
cbuf_end = cbuf + ps->len; \
|
|
cptr = cbuf + saved_cptr; \
|
|
arr = JS_VALUE_TO_PTR(capture_buf); \
|
|
capture = (uint32_t *)arr->buf; \
|
|
} \
|
|
}
|
|
|
|
#define SAVE_CAPTURE(idx, value) \
|
|
{ \
|
|
int __v = (value); \
|
|
CHECK_STACK_SPACE(2); \
|
|
sp[-2] = JS_NewShortInt(idx); \
|
|
sp[-1] = JS_NewShortInt(capture[idx]); \
|
|
sp -= 2; \
|
|
capture[idx] = __v; \
|
|
}
|
|
|
|
/* avoid saving the previous value if already saved */
|
|
#define SAVE_CAPTURE_CHECK(idx, value) \
|
|
{ \
|
|
int __v = (value); \
|
|
JSValue *sp1; \
|
|
sp1 = sp; \
|
|
for(;;) { \
|
|
if (sp1 < bp) { \
|
|
if (JS_VALUE_GET_INT(sp1[0]) == (idx)) \
|
|
break; \
|
|
sp1 += 2; \
|
|
} else { \
|
|
CHECK_STACK_SPACE(2); \
|
|
sp[-2] = JS_NewShortInt(idx); \
|
|
sp[-1] = JS_NewShortInt(capture[idx]); \
|
|
sp -= 2; \
|
|
break; \
|
|
} \
|
|
} \
|
|
capture[idx] = __v; \
|
|
}
|
|
|
|
#define RE_PC_TYPE_TO_VALUE(pc, type) (((type) << 1) | (((pc) - ((JSByteArray *)JS_VALUE_TO_PTR(byte_code))->buf) << 3))
|
|
#define RE_VALUE_TO_PC(val) (((val) >> 3) + ((JSByteArray *)JS_VALUE_TO_PTR(byte_code))->buf)
|
|
#define RE_VALUE_TO_TYPE(val) (((val) >> 1) & 3)
|
|
|
|
#ifdef DUMP_REEXEC
|
|
printf("%5s %5s %5s %5s %s\n", "PC", "CP", "BP", "SP", "OPCODE");
|
|
#endif
|
|
for(;;) {
|
|
opcode = *pc++;
|
|
#ifdef DUMP_REEXEC
|
|
printf("%5ld %5ld %5ld %5ld %s\n",
|
|
pc - 1 - ((JSByteArray *)JS_VALUE_TO_PTR(byte_code))->buf - RE_HEADER_LEN,
|
|
cptr - cbuf,
|
|
bp - initial_sp,
|
|
sp - initial_sp,
|
|
reopcode_info[opcode].name);
|
|
#endif
|
|
switch(opcode) {
|
|
case REOP_match:
|
|
ctx->sp = initial_sp;
|
|
ctx->stack_bottom = saved_stack_bottom;
|
|
return 1;
|
|
no_match:
|
|
for(;;) {
|
|
REExecStateEnum type;
|
|
if (bp == initial_sp) {
|
|
ctx->sp = initial_sp;
|
|
ctx->stack_bottom = saved_stack_bottom;
|
|
return 0;
|
|
}
|
|
/* undo the modifications to capture[] and regs[] */
|
|
while (sp < bp) {
|
|
int idx2 = JS_VALUE_GET_INT(sp[0]);
|
|
capture[idx2] = JS_VALUE_GET_INT(sp[1]);
|
|
sp += 2;
|
|
}
|
|
|
|
pc = RE_VALUE_TO_PC(sp[0]);
|
|
type = RE_VALUE_TO_TYPE(sp[0]);
|
|
cptr = JS_VALUE_GET_INT(sp[1]) + cbuf;
|
|
bp = VALUE_TO_SP(ctx, sp[2]);
|
|
sp += 3;
|
|
if (type != RE_EXEC_STATE_LOOKAHEAD)
|
|
break;
|
|
}
|
|
LRE_POLL_INTERRUPT();
|
|
break;
|
|
case REOP_lookahead_match:
|
|
/* pop all the saved states until reaching the start of
|
|
the lookahead and keep the updated captures and
|
|
variables and the corresponding undo info. */
|
|
{
|
|
JSValue *sp1, *sp_start, *next_sp;
|
|
REExecStateEnum type;
|
|
|
|
sp_start = sp;
|
|
for(;;) {
|
|
sp1 = sp;
|
|
sp = bp;
|
|
pc = RE_VALUE_TO_PC(sp[0]);
|
|
type = RE_VALUE_TO_TYPE(sp[0]);
|
|
cptr = JS_VALUE_GET_INT(sp[1]) + cbuf;
|
|
bp = VALUE_TO_SP(ctx, sp[2]);
|
|
sp[2] = SP_TO_VALUE(ctx, sp1); /* save the next value for the copy step */
|
|
sp += 3;
|
|
if (type == RE_EXEC_STATE_LOOKAHEAD)
|
|
break;
|
|
}
|
|
if (sp != initial_sp) {
|
|
/* keep the undo info if there is a saved state */
|
|
sp1 = sp;
|
|
while (sp1 != sp_start) {
|
|
sp1 -= 3;
|
|
next_sp = VALUE_TO_SP(ctx, sp1[2]);
|
|
while (sp1 != next_sp) {
|
|
*--sp = *--sp1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case REOP_negative_lookahead_match:
|
|
/* pop all the saved states until reaching start of the negative lookahead */
|
|
for(;;) {
|
|
REExecStateEnum type;
|
|
type = RE_VALUE_TO_TYPE(bp[0]);
|
|
/* undo the modifications to capture[] and regs[] */
|
|
while (sp < bp) {
|
|
int idx2 = JS_VALUE_GET_INT(sp[0]);
|
|
capture[idx2] = JS_VALUE_GET_INT(sp[1]);
|
|
sp += 2;
|
|
}
|
|
pc = RE_VALUE_TO_PC(sp[0]);
|
|
type = RE_VALUE_TO_TYPE(sp[0]);
|
|
cptr = JS_VALUE_GET_INT(sp[1]) + cbuf;
|
|
bp = VALUE_TO_SP(ctx, sp[2]);
|
|
sp += 3;
|
|
if (type == RE_EXEC_STATE_NEGATIVE_LOOKAHEAD)
|
|
break;
|
|
}
|
|
goto no_match;
|
|
|
|
case REOP_char1:
|
|
if ((cbuf_end - cptr) < 1)
|
|
goto no_match;
|
|
if (pc[0] != cptr[0])
|
|
goto no_match;
|
|
pc++;
|
|
cptr++;
|
|
break;
|
|
case REOP_char2:
|
|
if ((cbuf_end - cptr) < 2)
|
|
goto no_match;
|
|
if (get_u16(pc) != get_u16(cptr))
|
|
goto no_match;
|
|
pc += 2;
|
|
cptr += 2;
|
|
break;
|
|
case REOP_char3:
|
|
if ((cbuf_end - cptr) < 3)
|
|
goto no_match;
|
|
if (get_u16(pc) != get_u16(cptr) || pc[2] != cptr[2])
|
|
goto no_match;
|
|
pc += 3;
|
|
cptr += 3;
|
|
break;
|
|
case REOP_char4:
|
|
if ((cbuf_end - cptr) < 4)
|
|
goto no_match;
|
|
if (get_u32(pc) != get_u32(cptr))
|
|
goto no_match;
|
|
pc += 4;
|
|
cptr += 4;
|
|
break;
|
|
case REOP_split_goto_first:
|
|
case REOP_split_next_first:
|
|
{
|
|
const uint8_t *pc1;
|
|
|
|
val = get_u32(pc);
|
|
pc += 4;
|
|
CHECK_STACK_SPACE(3);
|
|
if (opcode == REOP_split_next_first) {
|
|
pc1 = pc + (int)val;
|
|
} else {
|
|
pc1 = pc;
|
|
pc = pc + (int)val;
|
|
}
|
|
sp -= 3;
|
|
sp[0] = RE_PC_TYPE_TO_VALUE(pc1, RE_EXEC_STATE_SPLIT);
|
|
sp[1] = JS_NewShortInt(cptr - cbuf);
|
|
sp[2] = SP_TO_VALUE(ctx, bp);
|
|
bp = sp;
|
|
}
|
|
break;
|
|
case REOP_lookahead:
|
|
case REOP_negative_lookahead:
|
|
val = get_u32(pc);
|
|
pc += 4;
|
|
CHECK_STACK_SPACE(3);
|
|
sp -= 3;
|
|
sp[0] = RE_PC_TYPE_TO_VALUE(pc + (int)val,
|
|
RE_EXEC_STATE_LOOKAHEAD + opcode - REOP_lookahead);
|
|
sp[1] = JS_NewShortInt(cptr - cbuf);
|
|
sp[2] = SP_TO_VALUE(ctx, bp);
|
|
bp = sp;
|
|
break;
|
|
case REOP_goto:
|
|
val = get_u32(pc);
|
|
pc += 4 + (int)val;
|
|
LRE_POLL_INTERRUPT();
|
|
break;
|
|
case REOP_line_start:
|
|
case REOP_line_start_m:
|
|
if (cptr == cbuf)
|
|
break;
|
|
if (opcode == REOP_line_start)
|
|
goto no_match;
|
|
PEEK_PREV_CHAR(c, cptr, cbuf);
|
|
if (!is_line_terminator(c))
|
|
goto no_match;
|
|
break;
|
|
case REOP_line_end:
|
|
case REOP_line_end_m:
|
|
if (cptr == cbuf_end)
|
|
break;
|
|
if (opcode == REOP_line_end)
|
|
goto no_match;
|
|
PEEK_CHAR(c, cptr, cbuf_end);
|
|
if (!is_line_terminator(c))
|
|
goto no_match;
|
|
break;
|
|
case REOP_dot:
|
|
if (cptr == cbuf_end)
|
|
goto no_match;
|
|
GET_CHAR(c, cptr, cbuf_end);
|
|
if (is_line_terminator(c))
|
|
goto no_match;
|
|
break;
|
|
case REOP_any:
|
|
if (cptr == cbuf_end)
|
|
goto no_match;
|
|
GET_CHAR(c, cptr, cbuf_end);
|
|
break;
|
|
case REOP_space:
|
|
case REOP_not_space:
|
|
{
|
|
BOOL v1;
|
|
if (cptr == cbuf_end)
|
|
goto no_match;
|
|
c = cptr[0];
|
|
if (c < 128) {
|
|
cptr++;
|
|
v1 = unicode_is_space_ascii(c);
|
|
} else {
|
|
size_t clen;
|
|
c = __utf8_get(cptr, &clen);
|
|
cptr += clen;
|
|
v1 = unicode_is_space_non_ascii(c);
|
|
}
|
|
v1 ^= (opcode - REOP_space);
|
|
if (!v1)
|
|
goto no_match;
|
|
}
|
|
break;
|
|
case REOP_save_start:
|
|
case REOP_save_end:
|
|
val = *pc++;
|
|
assert(val < capture_count);
|
|
idx = 2 * val + opcode - REOP_save_start;
|
|
SAVE_CAPTURE(idx, cptr - cbuf);
|
|
break;
|
|
case REOP_save_reset:
|
|
{
|
|
uint32_t val2;
|
|
val = pc[0];
|
|
val2 = pc[1];
|
|
pc += 2;
|
|
assert(val2 < capture_count);
|
|
CHECK_STACK_SPACE(2 * (val2 - val + 1));
|
|
while (val <= val2) {
|
|
idx = 2 * val;
|
|
SAVE_CAPTURE(idx, 0);
|
|
idx = 2 * val + 1;
|
|
SAVE_CAPTURE(idx, 0);
|
|
val++;
|
|
}
|
|
}
|
|
break;
|
|
case REOP_set_i32:
|
|
idx = pc[0];
|
|
val = get_u32(pc + 1);
|
|
pc += 5;
|
|
SAVE_CAPTURE_CHECK(2 * capture_count + idx, val);
|
|
break;
|
|
case REOP_loop:
|
|
{
|
|
uint32_t val2;
|
|
idx = pc[0];
|
|
val = get_u32(pc + 1);
|
|
pc += 5;
|
|
|
|
val2 = capture[2 * capture_count + idx] - 1;
|
|
SAVE_CAPTURE_CHECK(2 * capture_count + idx, val2);
|
|
if (val2 != 0) {
|
|
pc += (int)val;
|
|
LRE_POLL_INTERRUPT();
|
|
}
|
|
}
|
|
break;
|
|
case REOP_loop_split_goto_first:
|
|
case REOP_loop_split_next_first:
|
|
case REOP_loop_check_adv_split_goto_first:
|
|
case REOP_loop_check_adv_split_next_first:
|
|
{
|
|
const uint8_t *pc1;
|
|
uint32_t val2, limit;
|
|
idx = pc[0];
|
|
limit = get_u32(pc + 1);
|
|
val = get_u32(pc + 5);
|
|
pc += 9;
|
|
|
|
/* decrement the counter */
|
|
val2 = capture[2 * capture_count + idx] - 1;
|
|
SAVE_CAPTURE_CHECK(2 * capture_count + idx, val2);
|
|
|
|
if (val2 > limit) {
|
|
/* normal loop if counter > limit */
|
|
pc += (int)val;
|
|
LRE_POLL_INTERRUPT();
|
|
} else {
|
|
/* check advance */
|
|
if ((opcode == REOP_loop_check_adv_split_goto_first ||
|
|
opcode == REOP_loop_check_adv_split_next_first) &&
|
|
capture[2 * capture_count + idx + 1] == (cptr - cbuf) &&
|
|
val2 != limit) {
|
|
goto no_match;
|
|
}
|
|
|
|
/* otherwise conditional split */
|
|
if (val2 != 0) {
|
|
CHECK_STACK_SPACE(3);
|
|
if (opcode == REOP_loop_split_next_first ||
|
|
opcode == REOP_loop_check_adv_split_next_first) {
|
|
pc1 = pc + (int)val;
|
|
} else {
|
|
pc1 = pc;
|
|
pc = pc + (int)val;
|
|
}
|
|
sp -= 3;
|
|
sp[0] = RE_PC_TYPE_TO_VALUE(pc1, RE_EXEC_STATE_SPLIT);
|
|
sp[1] = JS_NewShortInt(cptr - cbuf);
|
|
sp[2] = SP_TO_VALUE(ctx, bp);
|
|
bp = sp;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case REOP_set_char_pos:
|
|
idx = pc[0];
|
|
pc++;
|
|
SAVE_CAPTURE_CHECK(2 * capture_count + idx, cptr - cbuf);
|
|
break;
|
|
case REOP_check_advance:
|
|
idx = pc[0];
|
|
pc++;
|
|
if (capture[2 * capture_count + idx] == cptr - cbuf)
|
|
goto no_match;
|
|
break;
|
|
case REOP_word_boundary:
|
|
case REOP_not_word_boundary:
|
|
{
|
|
BOOL v1, v2;
|
|
BOOL is_boundary = (opcode == REOP_word_boundary);
|
|
/* char before */
|
|
if (cptr == cbuf) {
|
|
v1 = FALSE;
|
|
} else {
|
|
PEEK_PREV_CHAR(c, cptr, cbuf);
|
|
v1 = is_word_char(c);
|
|
}
|
|
/* current char */
|
|
if (cptr >= cbuf_end) {
|
|
v2 = FALSE;
|
|
} else {
|
|
PEEK_CHAR(c, cptr, cbuf_end);
|
|
v2 = is_word_char(c);
|
|
}
|
|
if (v1 ^ v2 ^ is_boundary)
|
|
goto no_match;
|
|
}
|
|
break;
|
|
/* assumption: 8 bit and small number of ranges */
|
|
case REOP_range8:
|
|
{
|
|
int n, i;
|
|
n = pc[0];
|
|
pc++;
|
|
if (cptr >= cbuf_end)
|
|
goto no_match;
|
|
GET_CHAR(c, cptr, cbuf_end);
|
|
for(i = 0; i < n - 1; i++) {
|
|
if (c >= pc[2 * i] && c < pc[2 * i + 1])
|
|
goto range8_match;
|
|
}
|
|
/* 0xff = max code point value */
|
|
if (c >= pc[2 * i] &&
|
|
(c < pc[2 * i + 1] || pc[2 * i + 1] == 0xff))
|
|
goto range8_match;
|
|
goto no_match;
|
|
range8_match:
|
|
pc += 2 * n;
|
|
}
|
|
break;
|
|
case REOP_range:
|
|
{
|
|
int n;
|
|
uint32_t low, high, idx_min, idx_max, idx;
|
|
|
|
n = get_u16(pc); /* n must be >= 1 */
|
|
pc += 2;
|
|
if (cptr >= cbuf_end || n == 0)
|
|
goto no_match;
|
|
GET_CHAR(c, cptr, cbuf_end);
|
|
idx_min = 0;
|
|
low = get_u32(pc + 0 * 8);
|
|
if (c < low)
|
|
goto no_match;
|
|
idx_max = n - 1;
|
|
high = get_u32(pc + idx_max * 8 + 4);
|
|
if (c >= high)
|
|
goto no_match;
|
|
while (idx_min <= idx_max) {
|
|
idx = (idx_min + idx_max) / 2;
|
|
low = get_u32(pc + idx * 8);
|
|
high = get_u32(pc + idx * 8 + 4);
|
|
if (c < low)
|
|
idx_max = idx - 1;
|
|
else if (c >= high)
|
|
idx_min = idx + 1;
|
|
else
|
|
goto range_match;
|
|
}
|
|
goto no_match;
|
|
range_match:
|
|
pc += 8 * n;
|
|
}
|
|
break;
|
|
case REOP_back_reference:
|
|
case REOP_back_reference_i:
|
|
val = pc[0];
|
|
pc++;
|
|
if (capture[2 * val] != -1 && capture[2 * val + 1] != -1) {
|
|
const uint8_t *cptr1, *cptr1_end;
|
|
int c1, c2;
|
|
|
|
cptr1 = cbuf + capture[2 * val];
|
|
cptr1_end = cbuf + capture[2 * val + 1];
|
|
while (cptr1 < cptr1_end) {
|
|
if (cptr >= cbuf_end)
|
|
goto no_match;
|
|
GET_CHAR(c1, cptr1, cptr1_end);
|
|
GET_CHAR(c2, cptr, cbuf_end);
|
|
if (opcode == REOP_back_reference_i) {
|
|
c1 = lre_canonicalize(c1);
|
|
c2 = lre_canonicalize(c2);
|
|
}
|
|
if (c1 != c2)
|
|
goto no_match;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
#ifdef DUMP_REEXEC
|
|
printf("unknown opcode pc=%ld\n", pc - 1 - ((JSByteArray *)JS_VALUE_TO_PTR(byte_code))->buf - RE_HEADER_LEN);
|
|
#endif
|
|
abort();
|
|
}
|
|
}
|
|
}
|
|
|
|
/* regexp js interface */
|
|
|
|
/* return the length */
|
|
static size_t js_parse_regexp_flags(int *pre_flags, const uint8_t *buf)
|
|
{
|
|
const uint8_t *p = buf;
|
|
int mask, re_flags;
|
|
re_flags = 0;
|
|
while (*p != '\0') {
|
|
switch(*p) {
|
|
#if 0
|
|
case 'd':
|
|
mask = LRE_FLAG_INDICES;
|
|
break;
|
|
#endif
|
|
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;
|
|
#if 0
|
|
case 'v':
|
|
mask = LRE_FLAG_UNICODE_SETS;
|
|
break;
|
|
#endif
|
|
case 'y':
|
|
mask = LRE_FLAG_STICKY;
|
|
break;
|
|
default:
|
|
goto done;
|
|
}
|
|
if ((re_flags & mask) != 0)
|
|
break;
|
|
re_flags |= mask;
|
|
p++;
|
|
}
|
|
done:
|
|
*pre_flags = re_flags;
|
|
return p - buf;
|
|
}
|
|
|
|
/* pattern and flags must be strings */
|
|
static JSValue js_compile_regexp(JSContext *ctx, JSValue pattern, JSValue flags)
|
|
{
|
|
int re_flags;
|
|
|
|
re_flags = 0;
|
|
if (!JS_IsUndefined(flags)) {
|
|
JSString *ps;
|
|
JSStringCharBuf buf;
|
|
size_t len;
|
|
ps = get_string_ptr(ctx, &buf, flags);
|
|
len = js_parse_regexp_flags(&re_flags, ps->buf);
|
|
if (len != ps->len)
|
|
return JS_ThrowSyntaxError(ctx, "invalid regular expression flags");
|
|
}
|
|
|
|
return JS_Parse2(ctx, pattern, NULL, 0, "<regexp>",
|
|
JS_EVAL_REGEXP | (re_flags << JS_EVAL_REGEXP_FLAGS_SHIFT));
|
|
}
|
|
|
|
static JSRegExp *js_get_regexp(JSContext *ctx, JSValue obj)
|
|
{
|
|
JSObject *p;
|
|
p = js_get_object_class(ctx, obj, JS_CLASS_REGEXP);
|
|
if (!p) {
|
|
JS_ThrowTypeError(ctx, "not a regular expression");
|
|
return NULL;
|
|
}
|
|
return &p->u.regexp;
|
|
}
|
|
|
|
JSValue js_regexp_get_lastIndex(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSRegExp *re = js_get_regexp(ctx, *this_val);
|
|
if (!re)
|
|
return JS_EXCEPTION;
|
|
return JS_NewInt32(ctx, re->last_index);
|
|
}
|
|
|
|
JSValue js_regexp_get_source(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSRegExp *re = js_get_regexp(ctx, *this_val);
|
|
if (!re)
|
|
return JS_EXCEPTION;
|
|
/* XXX: not complete */
|
|
return re->source;
|
|
}
|
|
|
|
JSValue js_regexp_set_lastIndex(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSRegExp *re;
|
|
int last_index;
|
|
if (JS_ToInt32(ctx, &last_index, argv[0]))
|
|
return JS_EXCEPTION;
|
|
re = js_get_regexp(ctx, *this_val);
|
|
if (!re)
|
|
return JS_EXCEPTION;
|
|
re->last_index = last_index;
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
#define RE_FLAG_COUNT 6
|
|
|
|
/* return the string length */
|
|
static size_t js_regexp_flags_str(char *buf, int re_flags)
|
|
{
|
|
static const char flag_char[RE_FLAG_COUNT] = { 'g', 'i', 'm', 's', 'u', 'y' };
|
|
char *p = buf;
|
|
int i;
|
|
|
|
for(i = 0; i < RE_FLAG_COUNT; i++) {
|
|
if ((re_flags >> i) & 1)
|
|
*p++ = flag_char[i];
|
|
}
|
|
*p = '\0';
|
|
return p - buf;
|
|
}
|
|
|
|
static void dump_regexp(JSContext *ctx, JSObject *p)
|
|
{
|
|
JSStringCharBuf buf;
|
|
JSString *ps;
|
|
char buf2[RE_FLAG_COUNT + 1];
|
|
JSByteArray *arr;
|
|
|
|
js_putchar(ctx, '/');
|
|
ps = get_string_ptr(ctx, &buf, p->u.regexp.source);
|
|
if (ps->len == 0) {
|
|
js_printf(ctx, "(?:)");
|
|
} else {
|
|
js_printf(ctx, "%" JSValue_PRI, p->u.regexp.source);
|
|
}
|
|
arr = JS_VALUE_TO_PTR(p->u.regexp.byte_code);
|
|
js_regexp_flags_str(buf2, lre_get_flags(arr->buf));
|
|
js_printf(ctx, "/%s", buf2);
|
|
}
|
|
|
|
JSValue js_regexp_get_flags(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSRegExp *re;
|
|
JSByteArray *arr;
|
|
size_t len;
|
|
char buf[RE_FLAG_COUNT + 1];
|
|
|
|
re = js_get_regexp(ctx, *this_val);
|
|
if (!re)
|
|
return JS_EXCEPTION;
|
|
arr = JS_VALUE_TO_PTR(re->byte_code);
|
|
len = js_regexp_flags_str(buf, lre_get_flags(arr->buf));
|
|
return JS_NewStringLen(ctx, buf, len);
|
|
}
|
|
|
|
JSValue js_regexp_constructor(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue obj, byte_code;
|
|
JSObject *p;
|
|
JSGCRef byte_code_ref;
|
|
|
|
argc &= ~FRAME_CF_CTOR;
|
|
|
|
argv[0] = JS_ToString(ctx, argv[0]);
|
|
if (JS_IsException(argv[0]))
|
|
return JS_EXCEPTION;
|
|
if (!JS_IsUndefined(argv[1])) {
|
|
argv[1] = JS_ToString(ctx, argv[1]);
|
|
if (JS_IsException(argv[1]))
|
|
return JS_EXCEPTION;
|
|
}
|
|
byte_code = js_compile_regexp(ctx, argv[0], argv[1]);
|
|
if (JS_IsException(byte_code))
|
|
return JS_EXCEPTION;
|
|
JS_PUSH_VALUE(ctx, byte_code);
|
|
obj = JS_NewObjectClass(ctx, JS_CLASS_REGEXP, sizeof(JSRegExp));
|
|
JS_POP_VALUE(ctx, byte_code);
|
|
if (JS_IsException(obj))
|
|
return obj;
|
|
p = JS_VALUE_TO_PTR(obj);
|
|
p->u.regexp.source = argv[0];
|
|
p->u.regexp.byte_code = byte_code;
|
|
p->u.regexp.last_index = 0;
|
|
return obj;
|
|
}
|
|
|
|
enum {
|
|
MAGIC_REGEXP_EXEC,
|
|
MAGIC_REGEXP_TEST,
|
|
MAGIC_REGEXP_SEARCH,
|
|
MAGIC_REGEXP_FORCE_GLOBAL, /* same as exec but force the global flag */
|
|
};
|
|
|
|
JSValue js_regexp_exec(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv, int magic)
|
|
{
|
|
JSObject *p;
|
|
JSRegExp *re;
|
|
JSValue obj, *capture_buf, res;
|
|
uint32_t *capture, last_index_utf8;
|
|
int rc, capture_count, i, re_flags, last_index;
|
|
JSByteArray *bc_arr, *carr;
|
|
JSGCRef capture_buf_ref, obj_ref;
|
|
JSString *str;
|
|
JSStringCharBuf str_buf;
|
|
|
|
re = js_get_regexp(ctx, *this_val);
|
|
if (!re)
|
|
return JS_EXCEPTION;
|
|
|
|
argv[0] = JS_ToString(ctx, argv[0]);
|
|
if (JS_IsException(argv[0]))
|
|
return JS_EXCEPTION;
|
|
|
|
p = JS_VALUE_TO_PTR(*this_val);
|
|
re = &p->u.regexp;
|
|
last_index = max_int(re->last_index, 0);
|
|
|
|
bc_arr = JS_VALUE_TO_PTR(re->byte_code);
|
|
re_flags = lre_get_flags(bc_arr->buf);
|
|
if (magic == MAGIC_REGEXP_FORCE_GLOBAL)
|
|
re_flags |= MAGIC_REGEXP_FORCE_GLOBAL;
|
|
if ((re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) == 0 ||
|
|
magic == MAGIC_REGEXP_SEARCH) {
|
|
last_index = 0;
|
|
}
|
|
capture_count = lre_get_capture_count(bc_arr->buf);
|
|
|
|
carr = js_alloc_byte_array(ctx, sizeof(uint32_t) * lre_get_alloc_count(bc_arr->buf));
|
|
if (!carr)
|
|
goto fail;
|
|
capture_buf = JS_PushGCRef(ctx, &capture_buf_ref);
|
|
*capture_buf = JS_VALUE_FROM_PTR(carr);
|
|
capture = (uint32_t *)carr->buf;
|
|
for(i = 0; i < 2 * capture_count; i++)
|
|
capture[i] = -1;
|
|
|
|
if (last_index <= 0)
|
|
last_index_utf8 = 0;
|
|
else
|
|
last_index_utf8 = js_string_utf16_to_utf8_pos(ctx, argv[0], last_index) / 2;
|
|
if (last_index_utf8 > js_string_byte_len(ctx, argv[0])) {
|
|
rc = 2;
|
|
} else {
|
|
p = JS_VALUE_TO_PTR(*this_val);
|
|
re = &p->u.regexp;
|
|
str = get_string_ptr(ctx, &str_buf, argv[0]);
|
|
/* JS_VALUE_FROM_PTR(str) is acceptable here because the
|
|
GC ignores pointers outside the heap */
|
|
rc = lre_exec(ctx, *capture_buf, re->byte_code, JS_VALUE_FROM_PTR(str),
|
|
last_index_utf8);
|
|
}
|
|
if (rc != 1) {
|
|
if (rc >= 0) {
|
|
if (re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) {
|
|
p = JS_VALUE_TO_PTR(*this_val);
|
|
re = &p->u.regexp;
|
|
re->last_index = 0;
|
|
}
|
|
if (magic == MAGIC_REGEXP_SEARCH)
|
|
obj = JS_NewShortInt(-1);
|
|
else if (magic == MAGIC_REGEXP_TEST)
|
|
obj = JS_FALSE;
|
|
else
|
|
obj = JS_NULL;
|
|
} else {
|
|
goto fail;
|
|
}
|
|
} else {
|
|
capture = (uint32_t *)((JSByteArray *)JS_VALUE_TO_PTR(*capture_buf))->buf;
|
|
if (magic == MAGIC_REGEXP_SEARCH) {
|
|
obj = JS_NewShortInt(js_string_utf8_to_utf16_pos(ctx, argv[0], capture[0] * 2));
|
|
goto done;
|
|
}
|
|
if (re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) {
|
|
p = JS_VALUE_TO_PTR(*this_val);
|
|
re = &p->u.regexp;
|
|
re->last_index = js_string_utf8_to_utf16_pos(ctx, argv[0], capture[1] * 2);
|
|
}
|
|
if (magic == MAGIC_REGEXP_TEST) {
|
|
obj = JS_TRUE;
|
|
} else {
|
|
obj = JS_NewArray(ctx, capture_count);
|
|
if (JS_IsException(obj))
|
|
goto fail;
|
|
|
|
JS_PUSH_VALUE(ctx, obj);
|
|
capture = (uint32_t *)((JSByteArray *)JS_VALUE_TO_PTR(*capture_buf))->buf;
|
|
res = JS_DefinePropertyValue(ctx, obj, js_get_atom(ctx, JS_ATOM_index),
|
|
JS_NewShortInt(js_string_utf8_to_utf16_pos(ctx, argv[0], capture[0] * 2)));
|
|
JS_POP_VALUE(ctx, obj);
|
|
if (JS_IsException(res))
|
|
goto fail;
|
|
|
|
JS_PUSH_VALUE(ctx, obj);
|
|
res = JS_DefinePropertyValue(ctx, obj, js_get_atom(ctx, JS_ATOM_input),
|
|
argv[0]);
|
|
JS_POP_VALUE(ctx, obj);
|
|
if (JS_IsException(res))
|
|
goto fail;
|
|
|
|
for(i = 0; i < capture_count; i++) {
|
|
int start, end;
|
|
JSValue val;
|
|
|
|
capture = (uint32_t *)((JSByteArray *)JS_VALUE_TO_PTR(*capture_buf))->buf;
|
|
start = capture[2 * i];
|
|
end = capture[2 * i + 1];
|
|
if (start != -1 && end != -1) {
|
|
JSValueArray *arr;
|
|
JS_PUSH_VALUE(ctx, obj);
|
|
val = js_sub_string_utf8(ctx, argv[0], 2 * start, 2 * end);
|
|
JS_POP_VALUE(ctx, obj);
|
|
if (JS_IsException(val))
|
|
goto fail;
|
|
p = JS_VALUE_TO_PTR(obj);
|
|
arr = JS_VALUE_TO_PTR(p->u.array.tab);
|
|
arr->arr[i] = val;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
done:
|
|
JS_PopGCRef(ctx, &capture_buf_ref);
|
|
return obj;
|
|
fail:
|
|
obj = JS_EXCEPTION;
|
|
goto done;
|
|
}
|
|
|
|
/* if regexp replace: capture_buf != NULL, needle = NULL
|
|
if string replace: capture_buf = NULL, captures_len = 1, needle != NULL
|
|
*/
|
|
static int js_string_concat_subst(JSContext *ctx, StringBuffer *b,
|
|
JSValue *str, JSValue *rep,
|
|
uint32_t pos, uint32_t end_of_match,
|
|
JSValue *capture_buf, uint32_t captures_len,
|
|
JSValue *needle)
|
|
{
|
|
JSStringCharBuf buf_rep;
|
|
JSString *p;
|
|
int rep_len, i, j, j0, c, k;
|
|
|
|
if (JS_IsFunction(ctx, *rep)) {
|
|
JSValue res, val;
|
|
JSGCRef val_ref;
|
|
int ret;
|
|
|
|
if (JS_StackCheck(ctx, 4 + captures_len))
|
|
return -1;
|
|
JS_PushArg(ctx, *str);
|
|
JS_PushArg(ctx, JS_NewShortInt(pos));
|
|
if (capture_buf) {
|
|
for(k = captures_len - 1; k >= 0; k--) {
|
|
uint32_t *captures = (uint32_t *)((JSByteArray *)JS_VALUE_TO_PTR(*capture_buf))->buf;
|
|
if (captures[2 * k] != -1 && captures[2 * k + 1] != -1) {
|
|
val = js_sub_string_utf8(ctx, *str, captures[2 * k] * 2, captures[2 * k + 1] * 2);
|
|
if (JS_IsException(val))
|
|
return -1;
|
|
JS_PUSH_VALUE(ctx, val);
|
|
ret = JS_StackCheck(ctx, 3 + k);
|
|
JS_POP_VALUE(ctx, val);
|
|
if (ret)
|
|
return -1;
|
|
} else {
|
|
val = JS_UNDEFINED;
|
|
}
|
|
JS_PushArg(ctx, val);
|
|
}
|
|
} else {
|
|
JS_PushArg(ctx, *needle);
|
|
}
|
|
JS_PushArg(ctx, *rep); /* function */
|
|
JS_PushArg(ctx, JS_UNDEFINED); /* this_val */
|
|
res = JS_Call(ctx, 2 + captures_len);
|
|
if (JS_IsException(res))
|
|
return -1;
|
|
return string_buffer_concat(ctx, b, res);
|
|
}
|
|
|
|
p = get_string_ptr(ctx, &buf_rep, *rep);
|
|
rep_len = p->len;
|
|
i = 0;
|
|
for(;;) {
|
|
p = get_string_ptr(ctx, &buf_rep, *rep);
|
|
j = i;
|
|
while (j < rep_len && p->buf[j] != '$')
|
|
j++;
|
|
if (j + 1 >= rep_len)
|
|
break;
|
|
j0 = j++; /* j0 = position of '$' */
|
|
c = p->buf[j++];
|
|
string_buffer_concat_utf8(ctx, b, *rep, 2 * i, 2 * j0);
|
|
if (c == '$') {
|
|
string_buffer_putc(ctx, b, '$');
|
|
} else if (c == '&') {
|
|
if (capture_buf) {
|
|
string_buffer_concat_utf16(ctx, b, *str, pos, end_of_match);
|
|
} else {
|
|
string_buffer_concat_str(ctx, b, *needle);
|
|
}
|
|
} else if (c == '`') {
|
|
string_buffer_concat_utf16(ctx, b, *str, 0, pos);
|
|
} else if (c == '\'') {
|
|
string_buffer_concat_utf16(ctx, b, *str, end_of_match, js_string_len(ctx, *str));
|
|
} else if (c >= '0' && c <= '9') {
|
|
k = c - '0';
|
|
if (j < rep_len) {
|
|
c = p->buf[j];
|
|
if (c >= '0' && c <= '9') {
|
|
k = k * 10 + c - '0';
|
|
j++;
|
|
}
|
|
}
|
|
if (k >= 1 && k < captures_len) {
|
|
uint32_t *captures = (uint32_t *)((JSByteArray *)JS_VALUE_TO_PTR(*capture_buf))->buf;
|
|
if (captures[2 * k] != -1 && captures[2 * k + 1] != -1) {
|
|
string_buffer_concat_utf8(ctx, b, *str,
|
|
captures[2 * k] * 2, captures[2 * k + 1] * 2);
|
|
}
|
|
} else {
|
|
goto no_rep;
|
|
}
|
|
} else {
|
|
no_rep:
|
|
string_buffer_concat_utf8(ctx, b, *rep, 2 * j0, 2 * j);
|
|
}
|
|
i = j;
|
|
}
|
|
return string_buffer_concat_utf8(ctx, b, *rep, 2 * i, 2 * rep_len);
|
|
}
|
|
|
|
JSValue js_string_replace(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv, int is_replaceAll)
|
|
{
|
|
StringBuffer b_s, *b = &b_s;
|
|
int pos, endOfLastMatch, needle_len, input_len;
|
|
BOOL is_first, is_regexp;
|
|
|
|
*this_val = JS_ToString(ctx, *this_val);
|
|
if (JS_IsException(*this_val))
|
|
return JS_EXCEPTION;
|
|
is_regexp = (JS_GetClassID(ctx, argv[0]) == JS_CLASS_REGEXP);
|
|
if (!is_regexp) {
|
|
argv[0] = JS_ToString(ctx, argv[0]);
|
|
if (JS_IsException(argv[0]))
|
|
return JS_EXCEPTION;
|
|
}
|
|
if (!JS_IsFunction(ctx, argv[1])) {
|
|
argv[1] = JS_ToString(ctx, argv[1]);
|
|
if (JS_IsException(argv[1]))
|
|
return JS_EXCEPTION;
|
|
}
|
|
input_len = js_string_len(ctx, *this_val);
|
|
endOfLastMatch = 0;
|
|
|
|
string_buffer_push(ctx, b, 0);
|
|
|
|
if (is_regexp) {
|
|
int start, end, last_index, ret, re_flags, i, capture_count;
|
|
JSObject *p;
|
|
JSByteArray *bc_arr, *carr;
|
|
JSValue *capture_buf;
|
|
uint32_t *capture;
|
|
JSGCRef capture_buf_ref;
|
|
|
|
p = JS_VALUE_TO_PTR(argv[0]);
|
|
bc_arr = JS_VALUE_TO_PTR(p->u.regexp.byte_code);
|
|
re_flags = lre_get_flags(bc_arr->buf);
|
|
capture_count = lre_get_capture_count(bc_arr->buf);
|
|
|
|
if (re_flags & LRE_FLAG_GLOBAL)
|
|
p->u.regexp.last_index = 0;
|
|
|
|
if ((re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) == 0) {
|
|
last_index = 0;
|
|
} else {
|
|
last_index = max_int(p->u.regexp.last_index, 0);
|
|
}
|
|
|
|
carr = js_alloc_byte_array(ctx, sizeof(uint32_t) * lre_get_alloc_count(bc_arr->buf));
|
|
if (!carr) {
|
|
string_buffer_pop(ctx, b);
|
|
return JS_EXCEPTION;
|
|
}
|
|
capture_buf = JS_PushGCRef(ctx, &capture_buf_ref);
|
|
*capture_buf = JS_VALUE_FROM_PTR(carr);
|
|
capture = (uint32_t *)carr->buf;
|
|
for(i = 0; i < 2 * capture_count; i++)
|
|
capture[i] = -1;
|
|
|
|
for(;;) {
|
|
if (last_index > input_len) {
|
|
ret = 0;
|
|
} else {
|
|
JSString *str;
|
|
JSStringCharBuf str_buf;
|
|
p = JS_VALUE_TO_PTR(argv[0]);
|
|
str = get_string_ptr(ctx, &str_buf, *this_val);
|
|
/* JS_VALUE_FROM_PTR(str) is acceptable here because the
|
|
GC ignores pointers outside the heap */
|
|
ret = lre_exec(ctx, *capture_buf, p->u.regexp.byte_code,
|
|
JS_VALUE_FROM_PTR(str),
|
|
js_string_utf16_to_utf8_pos(ctx, *this_val, last_index) / 2);
|
|
}
|
|
if (ret < 0) {
|
|
JS_PopGCRef(ctx, &capture_buf_ref);
|
|
string_buffer_pop(ctx, b);
|
|
return JS_EXCEPTION;
|
|
}
|
|
if (ret == 0) {
|
|
if (re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) {
|
|
p = JS_VALUE_TO_PTR(argv[0]);
|
|
p->u.regexp.last_index = 0;
|
|
}
|
|
break;
|
|
}
|
|
capture = (uint32_t *)((JSByteArray *)JS_VALUE_TO_PTR(*capture_buf))->buf;
|
|
start = js_string_utf8_to_utf16_pos(ctx, *this_val, capture[0] * 2);
|
|
end = js_string_utf8_to_utf16_pos(ctx, *this_val, capture[1] * 2);
|
|
string_buffer_concat_utf16(ctx, b, *this_val, endOfLastMatch, start);
|
|
js_string_concat_subst(ctx, b, this_val, &argv[1],
|
|
start, end, capture_buf, capture_count, NULL);
|
|
endOfLastMatch = end;
|
|
if (!(re_flags & LRE_FLAG_GLOBAL)) {
|
|
if (re_flags & LRE_FLAG_STICKY) {
|
|
p = JS_VALUE_TO_PTR(argv[0]);
|
|
p->u.regexp.last_index = end;
|
|
}
|
|
break;
|
|
}
|
|
if (end == start) {
|
|
int c = string_getcp(ctx, *this_val, end, TRUE);
|
|
/* since regexp are unicode by default, replace is also unicode by default */
|
|
end += 1 + (c >= 0x10000);
|
|
}
|
|
last_index = end;
|
|
}
|
|
JS_PopGCRef(ctx, &capture_buf_ref);
|
|
} else {
|
|
needle_len = js_string_len(ctx, argv[0]);
|
|
|
|
is_first = TRUE;
|
|
for(;;) {
|
|
if (unlikely(needle_len == 0)) {
|
|
if (is_first)
|
|
pos = 0;
|
|
else if (endOfLastMatch >= input_len)
|
|
pos = -1;
|
|
else
|
|
pos = endOfLastMatch + 1;
|
|
} else {
|
|
pos = js_string_indexof(ctx, *this_val, argv[0], endOfLastMatch,
|
|
input_len, needle_len);
|
|
}
|
|
if (pos < 0) {
|
|
if (is_first) {
|
|
string_buffer_pop(ctx, b);
|
|
return *this_val;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
string_buffer_concat_utf16(ctx, b, *this_val, endOfLastMatch, pos);
|
|
|
|
js_string_concat_subst(ctx, b, this_val, &argv[1],
|
|
pos, pos + needle_len, NULL, 1, &argv[0]);
|
|
|
|
endOfLastMatch = pos + needle_len;
|
|
is_first = FALSE;
|
|
if (!is_replaceAll)
|
|
break;
|
|
}
|
|
}
|
|
string_buffer_concat_utf16(ctx, b, *this_val, endOfLastMatch, input_len);
|
|
return string_buffer_pop(ctx, b);
|
|
}
|
|
|
|
// split(sep, limit)
|
|
JSValue js_string_split(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSValue *A, T, ret, *z;
|
|
uint32_t lim, lengthA;
|
|
int p, q, s, e;
|
|
BOOL undef_sep;
|
|
JSGCRef A_ref, z_ref;
|
|
BOOL is_regexp;
|
|
|
|
*this_val = JS_ToString(ctx, *this_val);
|
|
if (JS_IsException(*this_val))
|
|
return JS_EXCEPTION;
|
|
if (JS_IsUndefined(argv[1])) {
|
|
lim = 0xffffffff;
|
|
} else {
|
|
if (JS_ToUint32(ctx, &lim, argv[1]) < 0)
|
|
return JS_EXCEPTION;
|
|
}
|
|
is_regexp = (JS_GetClassID(ctx, argv[0]) == JS_CLASS_REGEXP);
|
|
if (!is_regexp) {
|
|
undef_sep = JS_IsUndefined(argv[0]);
|
|
argv[0] = JS_ToString(ctx, argv[0]);
|
|
if (JS_IsException(argv[0]))
|
|
return JS_EXCEPTION;
|
|
} else {
|
|
undef_sep = FALSE;
|
|
}
|
|
|
|
A = JS_PushGCRef(ctx, &A_ref);
|
|
z = JS_PushGCRef(ctx, &z_ref);
|
|
*A = JS_NewArray(ctx, 0);
|
|
if (JS_IsException(*A))
|
|
goto exception;
|
|
lengthA = 0;
|
|
|
|
s = js_string_len(ctx, *this_val);
|
|
p = 0;
|
|
if (lim == 0)
|
|
goto done;
|
|
if (undef_sep)
|
|
goto add_tail;
|
|
|
|
if (is_regexp) {
|
|
int numberOfCaptures, i, re_flags;
|
|
JSObject *p1;
|
|
JSValueArray *arr;
|
|
JSByteArray *bc_arr;
|
|
|
|
p1 = JS_VALUE_TO_PTR(argv[0]);
|
|
bc_arr = JS_VALUE_TO_PTR(p1->u.regexp.byte_code);
|
|
re_flags = lre_get_flags(bc_arr->buf);
|
|
|
|
if (s == 0) {
|
|
p1 = JS_VALUE_TO_PTR(argv[0]);
|
|
p1->u.regexp.last_index = 0;
|
|
*z = js_regexp_exec(ctx, &argv[0], 1, this_val, MAGIC_REGEXP_FORCE_GLOBAL);
|
|
if (JS_IsException(*z))
|
|
goto exception;
|
|
if (JS_IsNull(*z))
|
|
goto add_tail;
|
|
goto done;
|
|
}
|
|
q = 0;
|
|
while (q < s) {
|
|
p1 = JS_VALUE_TO_PTR(argv[0]);
|
|
p1->u.regexp.last_index = q;
|
|
/* XXX: need sticky behavior */
|
|
*z = js_regexp_exec(ctx, &argv[0], 1, this_val, MAGIC_REGEXP_FORCE_GLOBAL);
|
|
if (JS_IsException(*z))
|
|
goto exception;
|
|
if (JS_IsNull(*z)) {
|
|
if (!(re_flags & LRE_FLAG_STICKY)) {
|
|
break;
|
|
} else {
|
|
int c = string_getcp(ctx, *this_val, q, TRUE);
|
|
/* since regexp are unicode by default, split is also unicode by default */
|
|
q += 1 + (c >= 0x10000);
|
|
}
|
|
} else {
|
|
if (!(re_flags & LRE_FLAG_STICKY)) {
|
|
JSValue res;
|
|
res = JS_GetProperty(ctx, *z, js_get_atom(ctx, JS_ATOM_index));
|
|
if (JS_IsException(res))
|
|
goto exception;
|
|
q = JS_VALUE_GET_INT(res);
|
|
}
|
|
p1 = JS_VALUE_TO_PTR(argv[0]);
|
|
e = p1->u.regexp.last_index;
|
|
if (e > s)
|
|
e = s;
|
|
if (e == p) {
|
|
int c = string_getcp(ctx, *this_val, q, TRUE);
|
|
/* since regexp are unicode by default, split is also unicode by default */
|
|
q += 1 + (c >= 0x10000);
|
|
} else {
|
|
T = js_sub_string(ctx, *this_val, p, q);
|
|
if (JS_IsException(T))
|
|
goto exception;
|
|
ret = JS_SetPropertyUint32(ctx, *A, lengthA++, T);
|
|
if (JS_IsException(ret))
|
|
goto exception;
|
|
if (lengthA == lim)
|
|
goto done;
|
|
p1 = JS_VALUE_TO_PTR(*z);
|
|
numberOfCaptures = p1->u.array.len;
|
|
for(i = 1; i < numberOfCaptures; i++) {
|
|
p1 = JS_VALUE_TO_PTR(*z);
|
|
arr = JS_VALUE_TO_PTR(p1->u.array.tab);
|
|
T = arr->arr[i];
|
|
ret = JS_SetPropertyUint32(ctx, *A, lengthA++, T);
|
|
if (JS_IsException(ret))
|
|
goto exception;
|
|
}
|
|
q = p = e;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
int r = js_string_len(ctx, argv[0]);
|
|
if (s == 0) {
|
|
if (r != 0)
|
|
goto add_tail;
|
|
goto done;
|
|
}
|
|
|
|
for (q = 0; (q += !r) <= s - r - !r; q = p = e + r) {
|
|
|
|
e = js_string_indexof(ctx, *this_val, argv[0], q, s, r);
|
|
if (e < 0)
|
|
break;
|
|
T = js_sub_string(ctx, *this_val, p, e);
|
|
if (JS_IsException(T))
|
|
goto exception;
|
|
ret = JS_SetPropertyUint32(ctx, *A, lengthA++, T);
|
|
if (JS_IsException(ret))
|
|
goto exception;
|
|
if (lengthA == lim)
|
|
goto done;
|
|
}
|
|
}
|
|
add_tail:
|
|
T = js_sub_string(ctx, *this_val, p, s);
|
|
if (JS_IsException(T))
|
|
goto exception;
|
|
ret = JS_SetPropertyUint32(ctx, *A, lengthA++, T);
|
|
if (JS_IsException(ret))
|
|
goto exception;
|
|
|
|
done:
|
|
JS_PopGCRef(ctx, &z_ref);
|
|
return JS_PopGCRef(ctx, &A_ref);
|
|
|
|
exception:
|
|
JS_PopGCRef(ctx, &z_ref);
|
|
JS_PopGCRef(ctx, &A_ref);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
JSValue js_string_match(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
JSRegExp *re;
|
|
int global, n;
|
|
JSValue *A, *result, ret;
|
|
JSObject *p;
|
|
JSValueArray *arr;
|
|
JSByteArray *barr;
|
|
JSGCRef A_ref, result_ref;
|
|
|
|
re = js_get_regexp(ctx, argv[0]);
|
|
if (!re)
|
|
return JS_EXCEPTION;
|
|
barr = JS_VALUE_TO_PTR(re->byte_code);
|
|
global = lre_get_flags(barr->buf) & LRE_FLAG_GLOBAL;
|
|
if (!global)
|
|
return js_regexp_exec(ctx, &argv[0], 1, this_val, 0);
|
|
|
|
p = JS_VALUE_TO_PTR(argv[0]);
|
|
re = &p->u.regexp;
|
|
re->last_index = 0;
|
|
|
|
A = JS_PushGCRef(ctx, &A_ref);
|
|
result = JS_PushGCRef(ctx, &result_ref);
|
|
*A = JS_NULL;
|
|
n = 0;
|
|
for(;;) {
|
|
*result = js_regexp_exec(ctx, &argv[0], 1, this_val, 0);
|
|
if (JS_IsException(*result))
|
|
goto fail;
|
|
if (*result == JS_NULL)
|
|
break;
|
|
if (*A == JS_NULL) {
|
|
*A = JS_NewArray(ctx, 1);
|
|
if (JS_IsException(*A))
|
|
goto fail;
|
|
}
|
|
|
|
p = JS_VALUE_TO_PTR(*result);
|
|
arr = JS_VALUE_TO_PTR(p->u.array.tab);
|
|
|
|
ret = JS_SetPropertyUint32(ctx, *A, n++, arr->arr[0]);
|
|
if (JS_IsException(ret)) {
|
|
fail:
|
|
*A = JS_EXCEPTION;
|
|
break;
|
|
}
|
|
}
|
|
JS_PopGCRef(ctx, &result_ref);
|
|
return JS_PopGCRef(ctx, &A_ref);
|
|
}
|
|
|
|
JSValue js_string_search(JSContext *ctx, JSValue *this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
return js_regexp_exec(ctx, &argv[0], 1, this_val, MAGIC_REGEXP_SEARCH);
|
|
}
|