mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-01-31 19:43:32 +00:00
417 lines
13 KiB
C
417 lines
13 KiB
C
|
/*
|
||
|
* QuickJS Javascript Engine
|
||
|
*
|
||
|
* Copyright (c) 2017-2021 Fabrice Bellard
|
||
|
* Copyright (c) 2017-2021 Charlie Gordon
|
||
|
*
|
||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||
|
* of this software and associated documentation files (the "Software"), to deal
|
||
|
* in the Software without restriction, including without limitation the rights
|
||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||
|
* copies of the Software, and to permit persons to whom the Software is
|
||
|
* furnished to do so, subject to the following conditions:
|
||
|
*
|
||
|
* The above copyright notice and this permission notice shall be included in
|
||
|
* all copies or substantial portions of the Software.
|
||
|
*
|
||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||
|
* THE SOFTWARE.
|
||
|
*/
|
||
|
#include "third_party/quickjs/internal.h"
|
||
|
#include "third_party/quickjs/leb128.h"
|
||
|
#include "third_party/quickjs/libregexp.h"
|
||
|
#include "third_party/quickjs/quickjs.h"
|
||
|
|
||
|
asm(".ident\t\"\\n\\n\
|
||
|
QuickJS (MIT License)\\n\
|
||
|
Copyright (c) 2017-2021 Fabrice Bellard\\n\
|
||
|
Copyright (c) 2017-2021 Charlie Gordon\"");
|
||
|
asm(".include \"libc/disclaimer.inc\"");
|
||
|
|
||
|
int js_parse_error(JSParseState *s, const char *fmt, ...)
|
||
|
{
|
||
|
JSContext *ctx = s->ctx;
|
||
|
va_list ap;
|
||
|
int backtrace_flags;
|
||
|
va_start(ap, fmt);
|
||
|
JS_ThrowError2(ctx, JS_SYNTAX_ERROR, fmt, ap, FALSE);
|
||
|
va_end(ap);
|
||
|
backtrace_flags = 0;
|
||
|
if (s->cur_func && s->cur_func->backtrace_barrier)
|
||
|
backtrace_flags = JS_BACKTRACE_FLAG_SINGLE_LEVEL;
|
||
|
build_backtrace(ctx, ctx->rt->current_exception, s->filename, s->line_num,
|
||
|
backtrace_flags);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
JSValue JS_NewError(JSContext *ctx)
|
||
|
{
|
||
|
return JS_NewObjectClass(ctx, JS_CLASS_ERROR);
|
||
|
}
|
||
|
|
||
|
JSValue JS_ThrowError2(JSContext *ctx, JSErrorEnum error_num, const char *fmt, va_list ap, BOOL add_backtrace)
|
||
|
{
|
||
|
char buf[256];
|
||
|
JSValue obj, ret;
|
||
|
vsnprintf(buf, sizeof(buf), fmt, ap);
|
||
|
obj = JS_NewObjectProtoClass(ctx, ctx->native_error_proto[error_num],
|
||
|
JS_CLASS_ERROR);
|
||
|
if (UNLIKELY(JS_IsException(obj))) {
|
||
|
/* out of memory: throw JS_NULL to avoid recursing */
|
||
|
obj = JS_NULL;
|
||
|
} else {
|
||
|
JS_DefinePropertyValue(ctx, obj, JS_ATOM_message,
|
||
|
JS_NewString(ctx, buf),
|
||
|
JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
|
||
|
}
|
||
|
if (add_backtrace) {
|
||
|
build_backtrace(ctx, obj, NULL, 0, 0);
|
||
|
}
|
||
|
ret = JS_Throw(ctx, obj);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
JSValue JS_ThrowError(JSContext *ctx, JSErrorEnum error_num, const char *fmt, va_list ap)
|
||
|
{
|
||
|
JSRuntime *rt = ctx->rt;
|
||
|
JSStackFrame *sf;
|
||
|
BOOL add_backtrace;
|
||
|
/* the backtrace is added later if called from a bytecode function */
|
||
|
sf = rt->current_stack_frame;
|
||
|
add_backtrace = !rt->in_out_of_memory &&
|
||
|
(!sf || (JS_GetFunctionBytecode(sf->cur_func) == NULL));
|
||
|
return JS_ThrowError2(ctx, error_num, fmt, ap, add_backtrace);
|
||
|
}
|
||
|
|
||
|
JSValue JS_ThrowSyntaxError(JSContext *ctx, const char *fmt, ...)
|
||
|
{
|
||
|
JSValue val;
|
||
|
va_list ap;
|
||
|
va_start(ap, fmt);
|
||
|
val = JS_ThrowError(ctx, JS_SYNTAX_ERROR, fmt, ap);
|
||
|
va_end(ap);
|
||
|
return val;
|
||
|
}
|
||
|
|
||
|
JSValue JS_ThrowTypeError(JSContext *ctx, const char *fmt, ...)
|
||
|
{
|
||
|
JSValue val;
|
||
|
va_list ap;
|
||
|
va_start(ap, fmt);
|
||
|
val = JS_ThrowError(ctx, JS_TYPE_ERROR, fmt, ap);
|
||
|
va_end(ap);
|
||
|
return val;
|
||
|
}
|
||
|
|
||
|
int JS_ThrowTypeErrorOrFalse(JSContext *ctx, int flags, const char *fmt, ...)
|
||
|
{
|
||
|
va_list ap;
|
||
|
if ((flags & JS_PROP_THROW) ||
|
||
|
((flags & JS_PROP_THROW_STRICT) && is_strict_mode(ctx))) {
|
||
|
va_start(ap, fmt);
|
||
|
JS_ThrowError(ctx, JS_TYPE_ERROR, fmt, ap);
|
||
|
va_end(ap);
|
||
|
return -1;
|
||
|
} else {
|
||
|
return FALSE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* never use it directly */
|
||
|
JSValue __JS_ThrowTypeErrorAtom(JSContext *ctx, JSAtom atom, const char *fmt, ...)
|
||
|
{
|
||
|
char buf[ATOM_GET_STR_BUF_SIZE];
|
||
|
return JS_ThrowTypeError(ctx, fmt,
|
||
|
JS_AtomGetStr(ctx, buf, sizeof(buf), atom));
|
||
|
}
|
||
|
|
||
|
/* never use it directly */
|
||
|
JSValue __JS_ThrowSyntaxErrorAtom(JSContext *ctx, JSAtom atom, const char *fmt, ...)
|
||
|
{
|
||
|
char buf[ATOM_GET_STR_BUF_SIZE];
|
||
|
return JS_ThrowSyntaxError(ctx, fmt,
|
||
|
JS_AtomGetStr(ctx, buf, sizeof(buf), atom));
|
||
|
}
|
||
|
|
||
|
int JS_ThrowTypeErrorReadOnly(JSContext *ctx, int flags, JSAtom atom)
|
||
|
{
|
||
|
if ((flags & JS_PROP_THROW) ||
|
||
|
((flags & JS_PROP_THROW_STRICT) && is_strict_mode(ctx))) {
|
||
|
JS_ThrowTypeErrorAtom(ctx, "'%s' is read-only", atom);
|
||
|
return -1;
|
||
|
} else {
|
||
|
return FALSE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
JSValue JS_ThrowReferenceError(JSContext *ctx, const char *fmt, ...)
|
||
|
{
|
||
|
JSValue val;
|
||
|
va_list ap;
|
||
|
va_start(ap, fmt);
|
||
|
val = JS_ThrowError(ctx, JS_REFERENCE_ERROR, fmt, ap);
|
||
|
va_end(ap);
|
||
|
return val;
|
||
|
}
|
||
|
|
||
|
JSValue JS_ThrowRangeError(JSContext *ctx, const char *fmt, ...)
|
||
|
{
|
||
|
JSValue val;
|
||
|
va_list ap;
|
||
|
va_start(ap, fmt);
|
||
|
val = JS_ThrowError(ctx, JS_RANGE_ERROR, fmt, ap);
|
||
|
va_end(ap);
|
||
|
return val;
|
||
|
}
|
||
|
|
||
|
JSValue JS_ThrowInternalError(JSContext *ctx, const char *fmt, ...)
|
||
|
{
|
||
|
JSValue val;
|
||
|
va_list ap;
|
||
|
va_start(ap, fmt);
|
||
|
val = JS_ThrowError(ctx, JS_INTERNAL_ERROR, fmt, ap);
|
||
|
va_end(ap);
|
||
|
return val;
|
||
|
}
|
||
|
|
||
|
JSValue JS_ThrowOutOfMemory(JSContext *ctx)
|
||
|
{
|
||
|
JSRuntime *rt = ctx->rt;
|
||
|
if (!rt->in_out_of_memory) {
|
||
|
rt->in_out_of_memory = TRUE;
|
||
|
JS_ThrowInternalError(ctx, "out of memory");
|
||
|
rt->in_out_of_memory = FALSE;
|
||
|
}
|
||
|
return JS_EXCEPTION;
|
||
|
}
|
||
|
|
||
|
JSValue JS_ThrowStackOverflow(JSContext *ctx)
|
||
|
{
|
||
|
return JS_ThrowInternalError(ctx, "stack overflow");
|
||
|
}
|
||
|
|
||
|
JSValue JS_ThrowTypeErrorNotAnObject(JSContext *ctx)
|
||
|
{
|
||
|
return JS_ThrowTypeError(ctx, "not an object");
|
||
|
}
|
||
|
|
||
|
JSValue JS_ThrowTypeErrorNotASymbol(JSContext *ctx)
|
||
|
{
|
||
|
return JS_ThrowTypeError(ctx, "not a symbol");
|
||
|
}
|
||
|
|
||
|
int js_throw_URIError(JSContext *ctx, const char *fmt, ...)
|
||
|
{
|
||
|
va_list ap;
|
||
|
va_start(ap, fmt);
|
||
|
JS_ThrowError(ctx, JS_URI_ERROR, fmt, ap);
|
||
|
va_end(ap);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
JSValue JS_ThrowReferenceErrorNotDefined(JSContext *ctx, JSAtom name)
|
||
|
{
|
||
|
char buf[ATOM_GET_STR_BUF_SIZE];
|
||
|
return JS_ThrowReferenceError(ctx, "'%s' is not defined",
|
||
|
JS_AtomGetStr(ctx, buf, sizeof(buf), name));
|
||
|
}
|
||
|
|
||
|
JSValue JS_ThrowReferenceErrorUninitialized(JSContext *ctx, JSAtom name)
|
||
|
{
|
||
|
char buf[ATOM_GET_STR_BUF_SIZE];
|
||
|
return JS_ThrowReferenceError(ctx, "%s is not initialized",
|
||
|
name == JS_ATOM_NULL ? "lexical variable" :
|
||
|
JS_AtomGetStr(ctx, buf, sizeof(buf), name));
|
||
|
}
|
||
|
|
||
|
JSValue JS_ThrowReferenceErrorUninitialized2(JSContext *ctx,
|
||
|
JSFunctionBytecode *b,
|
||
|
int idx, BOOL is_ref)
|
||
|
{
|
||
|
JSAtom atom = JS_ATOM_NULL;
|
||
|
if (is_ref) {
|
||
|
atom = b->closure_var[idx].var_name;
|
||
|
} else {
|
||
|
/* not present if the function is stripped and contains no eval() */
|
||
|
if (b->vardefs)
|
||
|
atom = b->vardefs[b->arg_count + idx].var_name;
|
||
|
}
|
||
|
return JS_ThrowReferenceErrorUninitialized(ctx, atom);
|
||
|
}
|
||
|
|
||
|
JSValue JS_ThrowSyntaxErrorVarRedeclaration(JSContext *ctx, JSAtom prop)
|
||
|
{
|
||
|
return JS_ThrowSyntaxErrorAtom(ctx, "redeclaration of '%s'", prop);
|
||
|
}
|
||
|
|
||
|
JSValue JS_ThrowTypeErrorPrivateNotFound(JSContext *ctx, JSAtom atom)
|
||
|
{
|
||
|
return JS_ThrowTypeErrorAtom(ctx, "private class field '%s' does not exist",
|
||
|
atom);
|
||
|
}
|
||
|
|
||
|
JSValue JS_ThrowTypeErrorDetachedArrayBuffer(JSContext *ctx)
|
||
|
{
|
||
|
return JS_ThrowTypeError(ctx, "ArrayBuffer is detached");
|
||
|
}
|
||
|
|
||
|
/* in order to avoid executing arbitrary code during the stack trace
|
||
|
generation, we only look at simple 'name' properties containing a
|
||
|
string. */
|
||
|
static const char *get_func_name(JSContext *ctx, JSValueConst func)
|
||
|
{
|
||
|
JSProperty *pr;
|
||
|
JSShapeProperty *prs;
|
||
|
JSValueConst val;
|
||
|
if (JS_VALUE_GET_TAG(func) != JS_TAG_OBJECT)
|
||
|
return NULL;
|
||
|
prs = find_own_property(&pr, JS_VALUE_GET_OBJ(func), JS_ATOM_name);
|
||
|
if (!prs)
|
||
|
return NULL;
|
||
|
if ((prs->flags & JS_PROP_TMASK) != JS_PROP_NORMAL)
|
||
|
return NULL;
|
||
|
val = pr->u.value;
|
||
|
if (JS_VALUE_GET_TAG(val) != JS_TAG_STRING)
|
||
|
return NULL;
|
||
|
return JS_ToCString(ctx, val);
|
||
|
}
|
||
|
|
||
|
int find_line_num(JSContext *ctx, JSFunctionBytecode *b, uint32_t pc_value)
|
||
|
{
|
||
|
const uint8_t *p_end, *p;
|
||
|
int new_line_num, line_num, pc, v, ret;
|
||
|
unsigned int op;
|
||
|
if (!b->has_debug || !b->debug.pc2line_buf) {
|
||
|
/* function was stripped */
|
||
|
return -1;
|
||
|
}
|
||
|
p = b->debug.pc2line_buf;
|
||
|
p_end = p + b->debug.pc2line_len;
|
||
|
pc = 0;
|
||
|
line_num = b->debug.line_num;
|
||
|
while (p < p_end) {
|
||
|
op = *p++;
|
||
|
if (op == 0) {
|
||
|
uint32_t val;
|
||
|
ret = get_leb128(&val, p, p_end);
|
||
|
if (ret < 0)
|
||
|
goto fail;
|
||
|
pc += val;
|
||
|
p += ret;
|
||
|
ret = get_sleb128(&v, p, p_end);
|
||
|
if (ret < 0) {
|
||
|
fail:
|
||
|
/* should never happen */
|
||
|
return b->debug.line_num;
|
||
|
}
|
||
|
p += ret;
|
||
|
new_line_num = line_num + v;
|
||
|
} else {
|
||
|
op -= PC2LINE_OP_FIRST;
|
||
|
pc += (op / PC2LINE_RANGE);
|
||
|
new_line_num = line_num + (op % PC2LINE_RANGE) + PC2LINE_BASE;
|
||
|
}
|
||
|
if (pc_value < pc)
|
||
|
return line_num;
|
||
|
line_num = new_line_num;
|
||
|
}
|
||
|
return line_num;
|
||
|
}
|
||
|
|
||
|
/* if filename != NULL, an additional level is added with the filename
|
||
|
and line number information (used for parse error). */
|
||
|
void build_backtrace(JSContext *ctx, JSValueConst error_obj,
|
||
|
const char *filename, int line_num,
|
||
|
int backtrace_flags)
|
||
|
{
|
||
|
JSStackFrame *sf;
|
||
|
JSValue str;
|
||
|
DynBuf dbuf;
|
||
|
const char *func_name_str;
|
||
|
const char *str1;
|
||
|
JSObject *p;
|
||
|
BOOL backtrace_barrier;
|
||
|
js_dbuf_init(ctx, &dbuf);
|
||
|
if (filename) {
|
||
|
dbuf_printf(&dbuf, " at %s", filename);
|
||
|
if (line_num != -1)
|
||
|
dbuf_printf(&dbuf, ":%d", line_num);
|
||
|
dbuf_putc(&dbuf, '\n');
|
||
|
str = JS_NewString(ctx, filename);
|
||
|
JS_DefinePropertyValue(ctx, error_obj, JS_ATOM_fileName, str,
|
||
|
JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
|
||
|
JS_DefinePropertyValue(ctx, error_obj, JS_ATOM_lineNumber, JS_NewInt32(ctx, line_num),
|
||
|
JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
|
||
|
if (backtrace_flags & JS_BACKTRACE_FLAG_SINGLE_LEVEL)
|
||
|
goto done;
|
||
|
}
|
||
|
for(sf = ctx->rt->current_stack_frame; sf != NULL; sf = sf->prev_frame) {
|
||
|
if (backtrace_flags & JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL) {
|
||
|
backtrace_flags &= ~JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL;
|
||
|
continue;
|
||
|
}
|
||
|
func_name_str = get_func_name(ctx, sf->cur_func);
|
||
|
if (!func_name_str || func_name_str[0] == '\0')
|
||
|
str1 = "<anonymous>";
|
||
|
else
|
||
|
str1 = func_name_str;
|
||
|
dbuf_printf(&dbuf, " at %s", str1);
|
||
|
JS_FreeCString(ctx, func_name_str);
|
||
|
p = JS_VALUE_GET_OBJ(sf->cur_func);
|
||
|
backtrace_barrier = FALSE;
|
||
|
if (js_class_has_bytecode(p->class_id)) {
|
||
|
JSFunctionBytecode *b;
|
||
|
const char *atom_str;
|
||
|
int line_num1;
|
||
|
b = p->u.func.function_bytecode;
|
||
|
backtrace_barrier = b->backtrace_barrier;
|
||
|
if (b->has_debug) {
|
||
|
line_num1 = find_line_num(ctx, b,
|
||
|
sf->cur_pc - b->byte_code_buf - 1);
|
||
|
atom_str = JS_AtomToCString(ctx, b->debug.filename);
|
||
|
dbuf_printf(&dbuf, " (%s",
|
||
|
atom_str ? atom_str : "<null>");
|
||
|
JS_FreeCString(ctx, atom_str);
|
||
|
if (line_num1 != -1)
|
||
|
dbuf_printf(&dbuf, ":%d", line_num1);
|
||
|
dbuf_putc(&dbuf, ')');
|
||
|
}
|
||
|
} else {
|
||
|
dbuf_printf(&dbuf, " (native)");
|
||
|
}
|
||
|
dbuf_putc(&dbuf, '\n');
|
||
|
/* stop backtrace if JS_EVAL_FLAG_BACKTRACE_BARRIER was used */
|
||
|
if (backtrace_barrier)
|
||
|
break;
|
||
|
}
|
||
|
done:
|
||
|
dbuf_putc(&dbuf, '\0');
|
||
|
if (dbuf_error(&dbuf))
|
||
|
str = JS_NULL;
|
||
|
else
|
||
|
str = JS_NewString(ctx, (char *)dbuf.buf);
|
||
|
dbuf_free(&dbuf);
|
||
|
JS_DefinePropertyValue(ctx, error_obj, JS_ATOM_stack, str,
|
||
|
JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
|
||
|
}
|
||
|
|
||
|
JSValue throw_bf_exception(JSContext *ctx, int status)
|
||
|
{
|
||
|
const char *str;
|
||
|
if (status & BF_ST_MEM_ERROR)
|
||
|
return JS_ThrowOutOfMemory(ctx);
|
||
|
if (status & BF_ST_DIVIDE_ZERO) {
|
||
|
str = "division by zero";
|
||
|
} else if (status & BF_ST_INVALID_OP) {
|
||
|
str = "invalid operation";
|
||
|
} else {
|
||
|
str = "integer overflow";
|
||
|
}
|
||
|
return JS_ThrowRangeError(ctx, "%s", str);
|
||
|
}
|