diff --git a/TODO b/TODO index 7bf6e36..4448b0a 100644 --- a/TODO +++ b/TODO @@ -63,4 +63,4 @@ Test262o: 0/11262 errors, 463 excluded Test262o commit: 7da91bceb9ce7613f87db47ddd1292a2dda58b42 (es5-tests branch) Test262: -Result: 60/83436 errors, 3348 excluded, 6069 skipped +Result: 58/83574 errors, 3348 excluded, 6000 skipped diff --git a/quickjs-atom.h b/quickjs-atom.h index dc64f2f..13c1ccd 100644 --- a/quickjs-atom.h +++ b/quickjs-atom.h @@ -190,6 +190,10 @@ DEF(unicodeSets, "unicodeSets") DEF(not_equal, "not-equal") DEF(timed_out, "timed-out") DEF(ok, "ok") +DEF(toISOString, "toISOString") +DEF(alphabet, "alphabet") +DEF(lastChunkHandling, "lastChunkHandling") +DEF(omitPadding, "omitPadding") /* */ DEF(toJSON, "toJSON") DEF(maxByteLength, "maxByteLength") diff --git a/quickjs.c b/quickjs.c index dcd0fd6..766f8bd 100644 --- a/quickjs.c +++ b/quickjs.c @@ -1345,6 +1345,10 @@ static JSValue js_error_toString(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv); static JSVarRef *js_global_object_find_uninitialized_var(JSContext *ctx, JSObject *p, JSAtom atom, BOOL is_lexical); +static int typed_array_init(JSContext *ctx, JSValueConst obj, + JSValue buffer, uint64_t offset, uint64_t len, + BOOL track_rab); + static const JSClassExoticMethods js_arguments_exotic_methods; static const JSClassExoticMethods js_string_exotic_methods; @@ -55371,7 +55375,7 @@ static JSValue js_date_toJSON(JSContext *ctx, JSValueConst this_val, goto done; } } - method = JS_GetPropertyStr(ctx, obj, "toISOString"); + method = JS_GetProperty(ctx, obj, JS_ATOM_toISOString); if (JS_IsException(method)) goto exception; if (!JS_IsFunction(ctx, method)) { @@ -58247,6 +58251,797 @@ static JSValue js_typed_array_toSorted(JSContext *ctx, JSValueConst this_val, return ret; } +/* Uint8Array base64/hex (tc39 proposal-arraybuffer-base64) */ + +enum { + B64_ALPHABET_BASE64 = 0, + B64_ALPHABET_BASE64URL = 1, +}; + +enum { + B64_LAST_LOOSE = 0, + B64_LAST_STRICT = 1, + B64_LAST_STOP_BEFORE_PARTIAL = 2, +}; + +static const unsigned char b64_enc[64] = { + 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P', + 'Q','R','S','T','U','V','W','X','Y','Z', + 'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p', + 'q','r','s','t','u','v','w','x','y','z', + '0','1','2','3','4','5','6','7','8','9', + '+','/' +}; + +static const unsigned char b64url_enc[64] = { + 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P', + 'Q','R','S','T','U','V','W','X','Y','Z', + 'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p', + 'q','r','s','t','u','v','w','x','y','z', + '0','1','2','3','4','5','6','7','8','9', + '-','_' +}; + +#define K_WS 64 +#define K_ER 65 + +static const uint8_t b64_dec[256] = { + [ 0]=K_ER, [ 1]=K_ER, [ 2]=K_ER, [ 3]=K_ER, [ 4]=K_ER, [ 5]=K_ER, [ 6]=K_ER, [ 7]=K_ER, + [ 8]=K_ER, [ 9]=K_WS, [ 10]=K_WS, [ 11]=K_ER, [ 12]=K_WS, [ 13]=K_WS, [ 14]=K_ER, [ 15]=K_ER, + [ 16]=K_ER, [ 17]=K_ER, [ 18]=K_ER, [ 19]=K_ER, [ 20]=K_ER, [ 21]=K_ER, [ 22]=K_ER, [ 23]=K_ER, + [ 24]=K_ER, [ 25]=K_ER, [ 26]=K_ER, [ 27]=K_ER, [ 28]=K_ER, [ 29]=K_ER, [ 30]=K_ER, [ 31]=K_ER, + [' ']=K_WS, ['!']=K_ER, ['"']=K_ER, ['#']=K_ER, ['$']=K_ER, ['%']=K_ER, ['&']=K_ER, [ 39]=K_ER, + ['(']=K_ER, [')']=K_ER, ['*']=K_ER, ['+']= 62, [',']=K_ER, ['-']=K_ER, ['.']=K_ER, ['/']= 63, + ['0']= 52, ['1']= 53, ['2']= 54, ['3']= 55, ['4']= 56, ['5']= 57, ['6']= 58, ['7']= 59, + ['8']= 60, ['9']= 61, [':']=K_ER, [';']=K_ER, ['<']=K_ER, ['=']=K_ER, ['>']=K_ER, ['?']=K_ER, + ['@']=K_ER, ['A']= 0, ['B']= 1, ['C']= 2, ['D']= 3, ['E']= 4, ['F']= 5, ['G']= 6, + ['H']= 7, ['I']= 8, ['J']= 9, ['K']= 10, ['L']= 11, ['M']= 12, ['N']= 13, ['O']= 14, + ['P']= 15, ['Q']= 16, ['R']= 17, ['S']= 18, ['T']= 19, ['U']= 20, ['V']= 21, ['W']= 22, + ['X']= 23, ['Y']= 24, ['Z']= 25, ['[']=K_ER, [ 92]=K_ER, [']']=K_ER, ['^']=K_ER, ['_']=K_ER, + ['`']=K_ER, ['a']= 26, ['b']= 27, ['c']= 28, ['d']= 29, ['e']= 30, ['f']= 31, ['g']= 32, + ['h']= 33, ['i']= 34, ['j']= 35, ['k']= 36, ['l']= 37, ['m']= 38, ['n']= 39, ['o']= 40, + ['p']= 41, ['q']= 42, ['r']= 43, ['s']= 44, ['t']= 45, ['u']= 46, ['v']= 47, ['w']= 48, + ['x']= 49, ['y']= 50, ['z']= 51, ['{']=K_ER, ['|']=K_ER, ['}']=K_ER, ['~']=K_ER, [127]=K_ER, + [128]=K_ER, [129]=K_ER, [130]=K_ER, [131]=K_ER, [132]=K_ER, [133]=K_ER, [134]=K_ER, [135]=K_ER, + [136]=K_ER, [137]=K_ER, [138]=K_ER, [139]=K_ER, [140]=K_ER, [141]=K_ER, [142]=K_ER, [143]=K_ER, + [144]=K_ER, [145]=K_ER, [146]=K_ER, [147]=K_ER, [148]=K_ER, [149]=K_ER, [150]=K_ER, [151]=K_ER, + [152]=K_ER, [153]=K_ER, [154]=K_ER, [155]=K_ER, [156]=K_ER, [157]=K_ER, [158]=K_ER, [159]=K_ER, + [160]=K_ER, [161]=K_ER, [162]=K_ER, [163]=K_ER, [164]=K_ER, [165]=K_ER, [166]=K_ER, [167]=K_ER, + [168]=K_ER, [169]=K_ER, [170]=K_ER, [171]=K_ER, [172]=K_ER, [173]=K_ER, [174]=K_ER, [175]=K_ER, + [176]=K_ER, [177]=K_ER, [178]=K_ER, [179]=K_ER, [180]=K_ER, [181]=K_ER, [182]=K_ER, [183]=K_ER, + [184]=K_ER, [185]=K_ER, [186]=K_ER, [187]=K_ER, [188]=K_ER, [189]=K_ER, [190]=K_ER, [191]=K_ER, + [192]=K_ER, [193]=K_ER, [194]=K_ER, [195]=K_ER, [196]=K_ER, [197]=K_ER, [198]=K_ER, [199]=K_ER, + [200]=K_ER, [201]=K_ER, [202]=K_ER, [203]=K_ER, [204]=K_ER, [205]=K_ER, [206]=K_ER, [207]=K_ER, + [208]=K_ER, [209]=K_ER, [210]=K_ER, [211]=K_ER, [212]=K_ER, [213]=K_ER, [214]=K_ER, [215]=K_ER, + [216]=K_ER, [217]=K_ER, [218]=K_ER, [219]=K_ER, [220]=K_ER, [221]=K_ER, [222]=K_ER, [223]=K_ER, + [224]=K_ER, [225]=K_ER, [226]=K_ER, [227]=K_ER, [228]=K_ER, [229]=K_ER, [230]=K_ER, [231]=K_ER, + [232]=K_ER, [233]=K_ER, [234]=K_ER, [235]=K_ER, [236]=K_ER, [237]=K_ER, [238]=K_ER, [239]=K_ER, + [240]=K_ER, [241]=K_ER, [242]=K_ER, [243]=K_ER, [244]=K_ER, [245]=K_ER, [246]=K_ER, [247]=K_ER, + [248]=K_ER, [249]=K_ER, [250]=K_ER, [251]=K_ER, [252]=K_ER, [253]=K_ER, [254]=K_ER, [255]=K_ER, +}; + +static const uint8_t b64url_dec[256] = { + [ 0]=K_ER, [ 1]=K_ER, [ 2]=K_ER, [ 3]=K_ER, [ 4]=K_ER, [ 5]=K_ER, [ 6]=K_ER, [ 7]=K_ER, + [ 8]=K_ER, [ 9]=K_WS, [ 10]=K_WS, [ 11]=K_ER, [ 12]=K_WS, [ 13]=K_WS, [ 14]=K_ER, [ 15]=K_ER, + [ 16]=K_ER, [ 17]=K_ER, [ 18]=K_ER, [ 19]=K_ER, [ 20]=K_ER, [ 21]=K_ER, [ 22]=K_ER, [ 23]=K_ER, + [ 24]=K_ER, [ 25]=K_ER, [ 26]=K_ER, [ 27]=K_ER, [ 28]=K_ER, [ 29]=K_ER, [ 30]=K_ER, [ 31]=K_ER, + [' ']=K_WS, ['!']=K_ER, ['"']=K_ER, ['#']=K_ER, ['$']=K_ER, ['%']=K_ER, ['&']=K_ER, [ 39]=K_ER, + ['(']=K_ER, [')']=K_ER, ['*']=K_ER, ['+']=K_ER, [',']=K_ER, ['-']= 62, ['.']=K_ER, ['/']=K_ER, + ['0']= 52, ['1']= 53, ['2']= 54, ['3']= 55, ['4']= 56, ['5']= 57, ['6']= 58, ['7']= 59, + ['8']= 60, ['9']= 61, [':']=K_ER, [';']=K_ER, ['<']=K_ER, ['=']=K_ER, ['>']=K_ER, ['?']=K_ER, + ['@']=K_ER, ['A']= 0, ['B']= 1, ['C']= 2, ['D']= 3, ['E']= 4, ['F']= 5, ['G']= 6, + ['H']= 7, ['I']= 8, ['J']= 9, ['K']= 10, ['L']= 11, ['M']= 12, ['N']= 13, ['O']= 14, + ['P']= 15, ['Q']= 16, ['R']= 17, ['S']= 18, ['T']= 19, ['U']= 20, ['V']= 21, ['W']= 22, + ['X']= 23, ['Y']= 24, ['Z']= 25, ['[']=K_ER, [ 92]=K_ER, [']']=K_ER, ['^']=K_ER, ['_']= 63, + ['`']=K_ER, ['a']= 26, ['b']= 27, ['c']= 28, ['d']= 29, ['e']= 30, ['f']= 31, ['g']= 32, + ['h']= 33, ['i']= 34, ['j']= 35, ['k']= 36, ['l']= 37, ['m']= 38, ['n']= 39, ['o']= 40, + ['p']= 41, ['q']= 42, ['r']= 43, ['s']= 44, ['t']= 45, ['u']= 46, ['v']= 47, ['w']= 48, + ['x']= 49, ['y']= 50, ['z']= 51, ['{']=K_ER, ['|']=K_ER, ['}']=K_ER, ['~']=K_ER, [127]=K_ER, + [128]=K_ER, [129]=K_ER, [130]=K_ER, [131]=K_ER, [132]=K_ER, [133]=K_ER, [134]=K_ER, [135]=K_ER, + [136]=K_ER, [137]=K_ER, [138]=K_ER, [139]=K_ER, [140]=K_ER, [141]=K_ER, [142]=K_ER, [143]=K_ER, + [144]=K_ER, [145]=K_ER, [146]=K_ER, [147]=K_ER, [148]=K_ER, [149]=K_ER, [150]=K_ER, [151]=K_ER, + [152]=K_ER, [153]=K_ER, [154]=K_ER, [155]=K_ER, [156]=K_ER, [157]=K_ER, [158]=K_ER, [159]=K_ER, + [160]=K_ER, [161]=K_ER, [162]=K_ER, [163]=K_ER, [164]=K_ER, [165]=K_ER, [166]=K_ER, [167]=K_ER, + [168]=K_ER, [169]=K_ER, [170]=K_ER, [171]=K_ER, [172]=K_ER, [173]=K_ER, [174]=K_ER, [175]=K_ER, + [176]=K_ER, [177]=K_ER, [178]=K_ER, [179]=K_ER, [180]=K_ER, [181]=K_ER, [182]=K_ER, [183]=K_ER, + [184]=K_ER, [185]=K_ER, [186]=K_ER, [187]=K_ER, [188]=K_ER, [189]=K_ER, [190]=K_ER, [191]=K_ER, + [192]=K_ER, [193]=K_ER, [194]=K_ER, [195]=K_ER, [196]=K_ER, [197]=K_ER, [198]=K_ER, [199]=K_ER, + [200]=K_ER, [201]=K_ER, [202]=K_ER, [203]=K_ER, [204]=K_ER, [205]=K_ER, [206]=K_ER, [207]=K_ER, + [208]=K_ER, [209]=K_ER, [210]=K_ER, [211]=K_ER, [212]=K_ER, [213]=K_ER, [214]=K_ER, [215]=K_ER, + [216]=K_ER, [217]=K_ER, [218]=K_ER, [219]=K_ER, [220]=K_ER, [221]=K_ER, [222]=K_ER, [223]=K_ER, + [224]=K_ER, [225]=K_ER, [226]=K_ER, [227]=K_ER, [228]=K_ER, [229]=K_ER, [230]=K_ER, [231]=K_ER, + [232]=K_ER, [233]=K_ER, [234]=K_ER, [235]=K_ER, [236]=K_ER, [237]=K_ER, [238]=K_ER, [239]=K_ER, + [240]=K_ER, [241]=K_ER, [242]=K_ER, [243]=K_ER, [244]=K_ER, [245]=K_ER, [246]=K_ER, [247]=K_ER, + [248]=K_ER, [249]=K_ER, [250]=K_ER, [251]=K_ER, [252]=K_ER, [253]=K_ER, [254]=K_ER, [255]=K_ER, +}; + +static size_t b64_encode(const uint8_t *src, size_t len, char *dst, + const unsigned char *alpha) +{ + size_t i, j; + + for (i = 0, j = 0; i + 3 <= len; i += 3, j += 4) { + uint32_t v = 65536*src[i] + 256*src[i + 1] + src[i + 2]; + dst[j + 0] = alpha[(v >> 18) & 63]; + dst[j + 1] = alpha[(v >> 12) & 63]; + dst[j + 2] = alpha[(v >> 6) & 63]; + dst[j + 3] = alpha[v & 63]; + } + + size_t rem = len - i; + if (rem == 1) { + uint32_t v = 65536*src[i]; + dst[j++] = alpha[(v >> 18) & 63]; + dst[j++] = alpha[(v >> 12) & 63]; + dst[j++] = '='; + dst[j++] = '='; + } else if (rem == 2) { + uint32_t v = 65536*src[i] + 256*src[i + 1]; + dst[j++] = alpha[(v >> 18) & 63]; + dst[j++] = alpha[(v >> 12) & 63]; + dst[j++] = alpha[(v >> 6) & 63]; + dst[j++] = '='; + } + return j; +} + +static size_t b64_skip_ws(const char *src, size_t len, size_t index, + const uint8_t *dec_table) +{ + while (index < len && dec_table[(unsigned char)src[index]] == K_WS) + index++; + return index; +} + +/* Implements the FromBase64 abstract operation. + src/src_len: the input string (must be ASCII/latin1) + dst/max_len: output buffer + flags: b64_flags or b64_flags_url (selects valid characters) + last_chunk: B64_LAST_LOOSE, B64_LAST_STRICT, or B64_LAST_STOP_BEFORE_PARTIAL + *p_read: set to number of input characters consumed + *p_err: set to 1 on error, 0 on success + Returns: number of bytes written to dst */ +static size_t from_base64(const char *src, size_t src_len, + uint8_t *dst, size_t max_len, + const uint8_t *dec_table, int last_chunk, + size_t *p_read, int *p_err) +{ + size_t read = 0, written = 0; + uint32_t v, acc = 0; + int seen = 0; + size_t index = 0; + uint8_t ch; + + *p_err = 0; + + if (max_len == 0) { + *p_read = 0; + return 0; + } + + for (;;) { + if (seen == 0) { + /* Fast path: decode complete groups of 4 valid characters. + Breaks out on whitespace, padding, invalid chars, or capacity. */ + while (index + 4 <= src_len && written + 3 <= max_len) { + uint32_t v0, v1, v2, v3; + v0 = dec_table[(unsigned char)src[index]]; + v1 = dec_table[(unsigned char)src[index + 1]]; + v2 = dec_table[(unsigned char)src[index + 2]]; + v3 = dec_table[(unsigned char)src[index + 3]]; + if ((v0 | v1 | v2 | v3) >= 64) + break; + v = (v0 << 18) | (v1 << 12) | (v2 << 6) | v3; + dst[written] = (uint8_t)(v >> 16); + dst[written + 1] = (uint8_t)(v >> 8); + dst[written + 2] = (uint8_t)(v); + written += 3; + index += 4; + } + read = index; + + if (written >= max_len) { + *p_read = read; + return written; + } + } + + /* Slow path: handle whitespace, padding, partial groups, capacity. */ + index = b64_skip_ws(src, src_len, index, dec_table); + + if (index == src_len) { + if (seen > 0) { + if (last_chunk == B64_LAST_STOP_BEFORE_PARTIAL) { + *p_read = read; + return written; + } + if (last_chunk == B64_LAST_STRICT) { + *p_err = 1; + return 0; + } + /* loose */ + if (seen == 1) { + *p_err = 1; + return 0; + } + break; + } + *p_read = src_len; + return written; + } + + ch = src[index++]; + + if (ch == '=') { + if (seen < 2) { + *p_err = 1; + return 0; + } + index = b64_skip_ws(src, src_len, index, dec_table); + if (seen == 2) { + if (index == src_len) { + if (last_chunk == B64_LAST_STOP_BEFORE_PARTIAL) { + *p_read = read; + return written; + } + *p_err = 1; + return 0; + } + if (src[index] == '=') { + index++; + index = b64_skip_ws(src, src_len, index, dec_table); + } else { + *p_err = 1; + return 0; + } + } + /* After padding, only whitespace is allowed */ + if (index != src_len) { + *p_err = 1; + return 0; + } + if (last_chunk == B64_LAST_STRICT) { + uint32_t mask = (seen == 2) ? 0xF : 0x3; + if (acc & mask) { + *p_err = 1; + return 0; + } + } + break; + } + + v = dec_table[ch]; + if (v >= 64) { + *p_err = 1; + return 0; + } + + /* Check remaining capacity before committing to this group */ + { + size_t remaining = max_len - written; + if ((remaining == 1 && seen == 2) || + (remaining == 2 && seen == 3)) { + *p_read = read; + return written; + } + } + + acc = (acc << 6) | v; + seen++; + + if (seen == 4) { + dst[written] = (uint8_t)(acc >> 16); + dst[written + 1] = (uint8_t)(acc >> 8); + dst[written + 2] = (uint8_t)(acc); + written += 3; + acc = 0; + seen = 0; + read = index; + if (written >= max_len) { + *p_read = read; + return written; + } + } + } + + if (seen == 2) { + dst[written++] = (uint8_t)(acc >> 4); + } else if (seen == 3) { + dst[written] = (uint8_t)(acc >> 10); + dst[written + 1] = (uint8_t)(acc >> 2); + written += 2; + } + *p_read = src_len; + return written; +} + +/* Hex helpers */ +static const char u8a_hex_digits[] = "0123456789abcdef"; + +static size_t u8a_hex_encode(const uint8_t *src, size_t len, char *dst) +{ + for (size_t i = 0; i < len; i++) { + dst[i * 2] = u8a_hex_digits[src[i] >> 4]; + dst[i * 2 + 1] = u8a_hex_digits[src[i] & 0xF]; + } + return len * 2; +} + +/* Decode hex string to bytes. + Returns bytes written. Sets *p_read to chars consumed, *p_err on error. */ +static size_t u8a_hex_decode(const char *src, size_t src_len, + uint8_t *dst, size_t max_len, + size_t *p_read, int *p_err) +{ + size_t written = 0, i = 0; + *p_err = 0; + + if (src_len & 1) { + *p_err = 1; + return 0; + } + + while (i < src_len && written < max_len) { + int hi = from_hex(src[i]); + int lo = from_hex(src[i + 1]); + if (hi < 0 || lo < 0) { + *p_err = 1; + return 0; + } + dst[written++] = (uint8_t)((hi << 4) | lo); + i += 2; + } + + *p_read = i; + return written; +} + +static JSValue JS_NewUint8ArrayCopy(JSContext *ctx, const uint8_t *buf, size_t len) +{ + JSValue buffer, obj; + JSArrayBuffer *abuf; + + buffer = js_array_buffer_constructor3(ctx, JS_UNDEFINED, len, NULL, + JS_CLASS_ARRAY_BUFFER, + (uint8_t *)buf, + js_array_buffer_free, NULL, + TRUE); + if (JS_IsException(buffer)) + return JS_EXCEPTION; + obj = js_create_from_ctor(ctx, JS_UNDEFINED, JS_CLASS_UINT8_ARRAY); + if (JS_IsException(obj)) { + JS_FreeValue(ctx, buffer); + return JS_EXCEPTION; + } + abuf = js_get_array_buffer(ctx, buffer); + assert(abuf != NULL); + if (typed_array_init(ctx, obj, buffer, 0, abuf->byte_length, /*track_rab*/FALSE)) { + // 'buffer' is freed on error above. + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + return obj; +} + +/* Validate that this_val is a Uint8Array (type check only, no detach check). + Returns the JSObject pointer or NULL on error (throws). */ +static JSObject *check_uint8array(JSContext *ctx, JSValueConst this_val) +{ + JSObject *p; + + if (JS_VALUE_GET_TAG(this_val) != JS_TAG_OBJECT) + goto fail; + p = JS_VALUE_GET_OBJ(this_val); + if (p->class_id != JS_CLASS_UINT8_ARRAY) + goto fail; + return p; +fail: + JS_ThrowTypeError(ctx, "not a Uint8Array"); + return NULL; +} + +/* Get the data pointer and length of a Uint8Array, checking for detached + buffers. Must be called after options are read (per spec ordering). + Returns 0 on success, -1 on error (throws). */ +static int get_uint8array_bytes(JSContext *ctx, JSObject *p, + uint8_t **pdata, size_t *plen) +{ + if (typed_array_is_oob(p)) { + JS_ThrowTypeErrorArrayBufferOOB(ctx); + *pdata = NULL; /* fail safe */ + *plen = 0; + return -1; + } + *pdata = p->u.array.u.uint8_ptr; + *plen = p->u.array.count; + return 0; +} + +/* Validate options is undefined or an object (GetOptionsObject). + Returns 0 on success, -1 on error (throws). */ +static int check_options_object(JSContext *ctx, JSValueConst options) +{ + if (JS_IsUndefined(options)) + return 0; + if (!JS_IsObject(options)) { + JS_ThrowTypeError(ctx, "options must be an object"); + return -1; + } + return 0; +} + +/* Parse the 'alphabet' option from an options object. + Returns B64_ALPHABET_BASE64 or B64_ALPHABET_BASE64URL, or -1 on error. */ +static int parse_alphabet_option(JSContext *ctx, JSValueConst options) +{ + JSValue val; + const char *str; + int ret; + + if (JS_IsUndefined(options)) + return B64_ALPHABET_BASE64; + + val = JS_GetProperty(ctx, options, JS_ATOM_alphabet); + if (JS_IsException(val)) + return -1; + if (JS_IsUndefined(val)) + return B64_ALPHABET_BASE64; + if (!JS_IsString(val)) { + JS_FreeValue(ctx, val); + JS_ThrowTypeError(ctx, "expected string for alphabet"); + return -1; + } + + str = JS_ToCString(ctx, val); + JS_FreeValue(ctx, val); + if (!str) + return -1; + + if (!strcmp(str, "base64")) + ret = B64_ALPHABET_BASE64; + else if (!strcmp(str, "base64url")) + ret = B64_ALPHABET_BASE64URL; + else { + JS_ThrowTypeError(ctx, "invalid alphabet"); + ret = -1; + } + JS_FreeCString(ctx, str); + return ret; +} + +/* Parse the 'lastChunkHandling' option. Returns mode or -1 on error. */ +static int parse_last_chunk_option(JSContext *ctx, JSValueConst options) +{ + JSValue val; + const char *str; + int ret; + + if (JS_IsUndefined(options)) + return B64_LAST_LOOSE; + + val = JS_GetProperty(ctx, options, JS_ATOM_lastChunkHandling); + if (JS_IsException(val)) + return -1; + if (JS_IsUndefined(val)) + return B64_LAST_LOOSE; + if (!JS_IsString(val)) { + JS_FreeValue(ctx, val); + JS_ThrowTypeError(ctx, "expected string for lastChunkHandling"); + return -1; + } + + str = JS_ToCString(ctx, val); + JS_FreeValue(ctx, val); + if (!str) + return -1; + + if (!strcmp(str, "loose")) + ret = B64_LAST_LOOSE; + else if (!strcmp(str, "strict")) + ret = B64_LAST_STRICT; + else if (!strcmp(str, "stop-before-partial")) + ret = B64_LAST_STOP_BEFORE_PARTIAL; + else { + JS_ThrowTypeError(ctx, "invalid lastChunkHandling option"); + ret = -1; + } + JS_FreeCString(ctx, str); + return ret; +} + +/* Uint8Array.prototype.toBase64([options]) */ +static JSValue js_uint8array_to_base64(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + uint8_t *data; + size_t len; + JSValueConst options; + JSObject *p; + int alphabet, omit_padding; + size_t out_len, written; + JSString *ostr; + char *dst; + + p = check_uint8array(ctx, this_val); + if (!p) + return JS_EXCEPTION; + + options = argc > 0 ? argv[0] : JS_UNDEFINED; + if (check_options_object(ctx, options)) + return JS_EXCEPTION; + alphabet = parse_alphabet_option(ctx, options); + if (alphabet < 0) + return JS_EXCEPTION; + + omit_padding = 0; + if (!JS_IsUndefined(options)) { + JSValue op_val = JS_GetProperty(ctx, options, JS_ATOM_omitPadding); + if (JS_IsException(op_val)) + return JS_EXCEPTION; + omit_padding = JS_ToBool(ctx, op_val); + JS_FreeValue(ctx, op_val); + } + + if (get_uint8array_bytes(ctx, p, &data, &len)) + return JS_EXCEPTION; + + out_len = 4 * ((len + 2) / 3); + + if (unlikely(out_len > JS_STRING_LEN_MAX)) + return JS_ThrowRangeError(ctx, "output too large"); + + ostr = js_alloc_string(ctx, out_len, 0); + if (!ostr) + return JS_EXCEPTION; + + dst = (char *)ostr->u.str8; + written = b64_encode(data, len, dst, + alphabet == B64_ALPHABET_BASE64URL ? b64url_enc : b64_enc); + if (omit_padding) { + while (written > 0 && dst[written - 1] == '=') + written--; + } + dst[written] = '\0'; + + ostr->len = written; + return JS_MKPTR(JS_TAG_STRING, ostr); +} + +/* Uint8Array.prototype.toHex() */ +static JSValue js_uint8array_to_hex(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + uint8_t *data; + size_t len, out_len; + JSObject *p; + JSString *ostr; + + p = check_uint8array(ctx, this_val); + if (!p) + return JS_EXCEPTION; + if (get_uint8array_bytes(ctx, p, &data, &len)) + return JS_EXCEPTION; + + out_len = len * 2; + if (unlikely(out_len > JS_STRING_LEN_MAX)) + return JS_ThrowRangeError(ctx, "output too large"); + + ostr = js_alloc_string(ctx, out_len, 0); + if (!ostr) + return JS_EXCEPTION; + + u8a_hex_encode(data, len, (char *)ostr->u.str8); + ostr->u.str8[out_len] = '\0'; + return JS_MKPTR(JS_TAG_STRING, ostr); +} + +/* Uint8Array.fromBase64(string[, options]) */ +static JSValue js_uint8array_from_base64(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const char *str; + size_t str_len, read_pos, decoded_len, out_cap; + int alphabet, last_chunk, err; + uint8_t *buf; + JSValue result; + JSValueConst options; + + if (!JS_IsString(argv[0])) + return JS_ThrowTypeError(ctx, "expected string"); + + str = JS_ToCStringLen(ctx, &str_len, argv[0]); + if (!str) + return JS_EXCEPTION; + + options = argc > 1 ? argv[1] : JS_UNDEFINED; + if (check_options_object(ctx, options)) { + JS_FreeCString(ctx, str); + return JS_EXCEPTION; + } + alphabet = parse_alphabet_option(ctx, options); + if (alphabet < 0) { + JS_FreeCString(ctx, str); + return JS_EXCEPTION; + } + last_chunk = parse_last_chunk_option(ctx, options); + if (last_chunk < 0) { + JS_FreeCString(ctx, str); + return JS_EXCEPTION; + } + + out_cap = (str_len / 4) * 3 + 3; + buf = js_malloc(ctx, out_cap); + if (!buf) { + JS_FreeCString(ctx, str); + return JS_EXCEPTION; + } + + decoded_len = from_base64(str, str_len, buf, out_cap, + alphabet == B64_ALPHABET_BASE64URL + ? b64url_dec : b64_dec, + last_chunk, &read_pos, &err); + JS_FreeCString(ctx, str); + + if (err) { + js_free(ctx, buf); + return JS_ThrowSyntaxError(ctx, "invalid base64 string"); + } + + result = JS_NewUint8ArrayCopy(ctx, buf, decoded_len); + js_free(ctx, buf); + return result; +} + +/* Uint8Array.fromHex(string) */ +static JSValue js_uint8array_from_hex(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const char *str; + size_t str_len, read_pos, decoded_len, out_cap; + int err; + uint8_t *buf; + JSValue result; + + if (!JS_IsString(argv[0])) + return JS_ThrowTypeError(ctx, "expected string"); + + str = JS_ToCStringLen(ctx, &str_len, argv[0]); + if (!str) + return JS_EXCEPTION; + + out_cap = str_len / 2 + 1; + buf = js_malloc(ctx, out_cap); + if (!buf) { + JS_FreeCString(ctx, str); + return JS_EXCEPTION; + } + + decoded_len = u8a_hex_decode(str, str_len, buf, out_cap, &read_pos, &err); + JS_FreeCString(ctx, str); + + if (err) { + js_free(ctx, buf); + return JS_ThrowSyntaxError(ctx, "invalid hex string"); + } + + /* XXX: could avoid the copy */ + result = JS_NewUint8ArrayCopy(ctx, buf, decoded_len); + js_free(ctx, buf); + return result; +} + +/* Return a { read, written } result object */ +static JSValue js_make_read_written(JSContext *ctx, size_t read, size_t written) +{ + JSValue obj = JS_NewObject(ctx); + if (JS_IsException(obj)) + return JS_EXCEPTION; + if (JS_DefinePropertyValueStr(ctx, obj, "read", + JS_NewUint32(ctx, read), JS_PROP_C_W_E) < 0) + goto fail; + if (JS_DefinePropertyValueStr(ctx, obj, "written", + JS_NewUint32(ctx, written), JS_PROP_C_W_E) < 0) + goto fail; + return obj; +fail: + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +/* Uint8Array.prototype.setFromBase64(string[, options]) */ +static JSValue js_uint8array_set_from_base64(JSContext *ctx, + JSValueConst this_val, + int argc, JSValueConst *argv) +{ + uint8_t *data; + size_t len; + const char *str; + size_t str_len, read_pos, decoded_len; + JSObject *p; + int alphabet, last_chunk, err; + JSValueConst options; + + p = check_uint8array(ctx, this_val); + if (!p) + return JS_EXCEPTION; + + if (!JS_IsString(argv[0])) + return JS_ThrowTypeError(ctx, "expected string"); + + str = JS_ToCStringLen(ctx, &str_len, argv[0]); + if (!str) + return JS_EXCEPTION; + + options = argc > 1 ? argv[1] : JS_UNDEFINED; + if (check_options_object(ctx, options)) { + JS_FreeCString(ctx, str); + return JS_EXCEPTION; + } + alphabet = parse_alphabet_option(ctx, options); + if (alphabet < 0) { + JS_FreeCString(ctx, str); + return JS_EXCEPTION; + } + last_chunk = parse_last_chunk_option(ctx, options); + if (last_chunk < 0) { + JS_FreeCString(ctx, str); + return JS_EXCEPTION; + } + + if (get_uint8array_bytes(ctx, p, &data, &len)) { + JS_FreeCString(ctx, str); + return JS_EXCEPTION; + } + + decoded_len = from_base64(str, str_len, data, len, + alphabet == B64_ALPHABET_BASE64URL + ? b64url_dec : b64_dec, + last_chunk, &read_pos, &err); + JS_FreeCString(ctx, str); + + if (err) + return JS_ThrowSyntaxError(ctx, "invalid base64 string"); + + return js_make_read_written(ctx, read_pos, decoded_len); +} + +/* Uint8Array.prototype.setFromHex(string) */ +static JSValue js_uint8array_set_from_hex(JSContext *ctx, + JSValueConst this_val, + int argc, JSValueConst *argv) +{ + uint8_t *data; + size_t len; + const char *str; + size_t str_len, read_pos, decoded_len; + JSObject *p; + int err; + + p = check_uint8array(ctx, this_val); + if (!p) + return JS_EXCEPTION; + + if (!JS_IsString(argv[0])) + return JS_ThrowTypeError(ctx, "expected string"); + + str = JS_ToCStringLen(ctx, &str_len, argv[0]); + if (!str) + return JS_EXCEPTION; + + if (get_uint8array_bytes(ctx, p, &data, &len)) { + JS_FreeCString(ctx, str); + return JS_EXCEPTION; + } + + decoded_len = u8a_hex_decode(str, str_len, data, len, &read_pos, &err); + JS_FreeCString(ctx, str); + + if (err) + return JS_ThrowSyntaxError(ctx, "invalid hex string"); + + return js_make_read_written(ctx, read_pos, decoded_len); +} + static const JSCFunctionListEntry js_typed_array_base_funcs[] = { JS_CFUNC_DEF("from", 1, js_typed_array_from ), JS_CFUNC_DEF("of", 0, js_typed_array_of ), @@ -58300,6 +59095,20 @@ static const JSCFunctionListEntry js_typed_array_funcs[] = { JS_PROP_INT32_DEF("BYTES_PER_ELEMENT", 8, 0), }; +static const JSCFunctionListEntry js_uint8array_proto_funcs[] = { + JS_PROP_INT32_DEF("BYTES_PER_ELEMENT", 1, 0), + JS_CFUNC_DEF("toBase64", 0, js_uint8array_to_base64), + JS_CFUNC_DEF("toHex", 0, js_uint8array_to_hex), + JS_CFUNC_DEF("setFromBase64", 1, js_uint8array_set_from_base64), + JS_CFUNC_DEF("setFromHex", 1, js_uint8array_set_from_hex), +}; + +static const JSCFunctionListEntry js_uint8array_funcs[] = { + JS_PROP_INT32_DEF("BYTES_PER_ELEMENT", 1, 0), + JS_CFUNC_DEF("fromBase64", 1, js_uint8array_from_base64), + JS_CFUNC_DEF("fromHex", 1, js_uint8array_from_hex), +}; + static JSValue js_typed_array_base_constructor(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) @@ -59560,17 +60369,25 @@ int JS_AddIntrinsicTypedArrays(JSContext *ctx) for(i = JS_CLASS_UINT8C_ARRAY; i < JS_CLASS_UINT8C_ARRAY + JS_TYPED_ARRAY_COUNT; i++) { char buf[ATOM_GET_STR_BUF_SIZE]; const char *name; - const JSCFunctionListEntry *bpe; name = JS_AtomGetStr(ctx, buf, sizeof(buf), JS_ATOM_Uint8ClampedArray + i - JS_CLASS_UINT8C_ARRAY); - bpe = js_typed_array_funcs + typed_array_size_log2(i); - obj = JS_NewCConstructor(ctx, i, name, - ft.generic, 3, JS_CFUNC_constructor_magic, i, - typed_array_base_func, - bpe, 1, - bpe, 1, - 0); + if (i == JS_CLASS_UINT8_ARRAY) { + obj = JS_NewCConstructor(ctx, i, name, + ft.generic, 3, JS_CFUNC_constructor_magic, i, + typed_array_base_func, + js_uint8array_funcs, countof(js_uint8array_funcs), + js_uint8array_proto_funcs, countof(js_uint8array_proto_funcs), + 0); + } else { + const JSCFunctionListEntry *bpe = js_typed_array_funcs + typed_array_size_log2(i); + obj = JS_NewCConstructor(ctx, i, name, + ft.generic, 3, JS_CFUNC_constructor_magic, i, + typed_array_base_func, + bpe, 1, + bpe, 1, + 0); + } if (JS_IsException(obj)) { fail: JS_FreeValue(ctx, typed_array_base_func); diff --git a/test262.conf b/test262.conf index ab8b0a5..8fe86cc 100644 --- a/test262.conf +++ b/test262.conf @@ -236,7 +236,7 @@ u180e Uint16Array Uint32Array Uint8Array -uint8array-base64=skip +uint8array-base64 Uint8ClampedArray upsert WeakMap diff --git a/test262_errors.txt b/test262_errors.txt index 309e927..66e83f2 100644 --- a/test262_errors.txt +++ b/test262_errors.txt @@ -31,8 +31,6 @@ test262/test/staging/sm/Function/function-name-for.js:13: Test262Error: Expected test262/test/staging/sm/Function/implicit-this-in-parameter-expression.js:12: Test262Error: Expected SameValue(«[object Object]», «undefined») to be true test262/test/staging/sm/Function/invalid-parameter-list.js:13: Test262Error: Expected a SyntaxError to be thrown but no exception was thrown at all test262/test/staging/sm/Function/invalid-parameter-list.js:13: strict mode: Test262Error: Expected a SyntaxError to be thrown but no exception was thrown at all -test262/test/staging/sm/TypedArray/prototype-constructor-identity.js:17: Test262Error: Expected SameValue(«2», «6») to be true -test262/test/staging/sm/TypedArray/prototype-constructor-identity.js:17: strict mode: Test262Error: Expected SameValue(«2», «6») to be true test262/test/staging/sm/async-functions/async-contains-unicode-escape.js:11: Test262Error: Expected a SyntaxError to be thrown but no exception was thrown at all test262/test/staging/sm/async-functions/async-contains-unicode-escape.js:11: strict mode: Test262Error: Expected a SyntaxError to be thrown but no exception was thrown at all test262/test/staging/sm/async-functions/await-in-arrow-parameters.js:10: Test262Error: AsyncFunction:(a = (b = await/r/g) => {}) => {} Expected a SyntaxError to be thrown but no exception was thrown at all