cosmopolitan/third_party/quickjs/err.c
2021-08-05 14:43:53 -07:00

418 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 "libc/fmt/fmt.h"
#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\"");
/* clang-format off */
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);
}