/
noire-dev
/
SourceTech
Обзор
Документация
Войти
/
noire-dev
/
SourceTech
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
sourcetech
code/qcommon/duktape.c
101 351 строка
4 MB
noire-dev
Update: 2025-12-14 10:41 javascript
14 дек 2025, 01:41
14 дек 2025, 01:41
7d875a5
Код
Авторство
О чём код?
/* * Single source autogenerated distributable for Duktape 2.7.0. * * Git commit 03d4d728f8365021de6955c649e6dcd05dcca99f (03d4d72-dirty). * Git branch HEAD. * * See Duktape AUTHORS.rst and LICENSE.txt for copyright and * licensing information. */ /* LICENSE.txt */ /* * =============== * Duktape license * =============== * * (http://opensource.org/licenses/MIT) * * Copyright (c) 2013-present by Duktape authors (see AUTHORS.rst) * * 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. */ /* AUTHORS.rst */ /* * =============== * Duktape authors * =============== * * Copyright * ========= * * Duktape copyrights are held by its authors. Each author has a copyright * to their contribution, and agrees to irrevocably license the contribution * under the Duktape ``LICENSE.txt``. * * Authors * ======= * * Please include an e-mail address, a link to your GitHub profile, or something * similar to allow your contribution to be identified accurately. * * The following people have contributed code, website contents, or Wiki contents, * and agreed to irrevocably license their contributions under the Duktape * ``LICENSE.txt`` (in order of appearance): * * * Sami Vaarala <sami.vaarala@iki.fi> * * Niki Dobrev * * Andreas \u00d6man <andreas@lonelycoder.com> * * L\u00e1szl\u00f3 Lang\u00f3 <llango.u-szeged@partner.samsung.com> * * Legimet <legimet.calc@gmail.com> * * Karl Skomski <karl@skomski.com> * * Bruce Pascoe <fatcerberus1@gmail.com> * * Ren\u00e9 Hollander <rene@rene8888.at> * * Julien Hamaide (https://github.com/crazyjul) * * Sebastian G\u00f6tte (https://github.com/jaseg) * * Tomasz Magulski (https://github.com/magul) * * \D. Bohdan (https://github.com/dbohdan) * * Ond\u0159ej Jirman (https://github.com/megous) * * Sa\u00fal Ibarra Corretg\u00e9 <saghul@gmail.com> * * Jeremy HU <huxingyi@msn.com> * * Ole Andr\u00e9 Vadla Ravn\u00e5s (https://github.com/oleavr) * * Harold Brenes (https://github.com/harold-b) * * Oliver Crow (https://github.com/ocrow) * * Jakub Ch\u0142api\u0144ski (https://github.com/jchlapinski) * * Brett Vickers (https://github.com/beevik) * * Dominik Okwieka (https://github.com/okitec) * * Remko Tron\u00e7on (https://el-tramo.be) * * Romero Malaquias (rbsm@ic.ufal.br) * * Michael Drake <michael.drake@codethink.co.uk> * * Steven Don (https://github.com/shdon) * * Simon Stone (https://github.com/sstone1) * * \J. McC. (https://github.com/jmhmccr) * * Jakub Nowakowski (https://github.com/jimvonmoon) * * Tommy Nguyen (https://github.com/tn0502) * * Fabrice Fontaine (https://github.com/ffontaine) * * Christopher Hiller (https://github.com/boneskull) * * Gonzalo Diethelm (https://github.com/gonzus) * * Michal Kasperek (https://github.com/michalkas) * * Andrew Janke (https://github.com/apjanke) * * Steve Fan (https://github.com/stevefan1999) * * Edward Betts (https://github.com/edwardbetts) * * Ozhan Duz (https://github.com/webfolderio) * * Akos Kiss (https://github.com/akosthekiss) * * TheBrokenRail (https://github.com/TheBrokenRail) * * Jesse Doyle (https://github.com/jessedoyle) * * Gero Kuehn (https://github.com/dc6jgk) * * James Swift (https://github.com/phraemer) * * Luis de Bethencourt (https://github.com/luisbg) * * Ian Whyman (https://github.com/v00d00) * * Rick Sayre (https://github.com/whorfin) * * Craig Leres (https://github.com/leres) * * Maurici Abad (https://github.com/mauriciabad) * * Nancy Li (https://github.com/NancyLi1013) * * William Parks (https://github.com/WilliamParks) * * Sam Hellawell (https://github.com/samhellawell) * * Vladislavs Sokurenko (https://github.com/sokurenko) * * Other contributions * =================== * * The following people have contributed something other than code (e.g. reported * bugs, provided ideas, etc; roughly in order of appearance): * * * Greg Burns * * Anthony Rabine * * Carlos Costa * * Aur\u00e9lien Bouilland * * Preet Desai (Pris Matic) * * judofyr (http://www.reddit.com/user/judofyr) * * Jason Woofenden * * Micha\u0142 Przyby\u015b * * Anthony Howe * * Conrad Pankoff * * Jim Schimpf * * Rajaran Gaunker (https://github.com/zimbabao) * * Andreas \u00d6man * * Doug Sanden * * Josh Engebretson (https://github.com/JoshEngebretson) * * Remo Eichenberger (https://github.com/remoe) * * Mamod Mehyar (https://github.com/mamod) * * David Demelier (https://github.com/markand) * * Tim Caswell (https://github.com/creationix) * * Mitchell Blank Jr (https://github.com/mitchblank) * * https://github.com/yushli * * Seo Sanghyeon (https://github.com/sanxiyn) * * Han ChoongWoo (https://github.com/tunz) * * Joshua Peek (https://github.com/josh) * * Bruce E. Pascoe (https://github.com/fatcerberus) * * https://github.com/Kelledin * * https://github.com/sstruchtrup * * Michael Drake (https://github.com/tlsa) * * https://github.com/chris-y * * Laurent Zubiaur (https://github.com/lzubiaur) * * Neil Kolban (https://github.com/nkolban) * * Wilhelm Wanecek (https://github.com/wanecek) * * Andrew Janke (https://github.com/apjanke) * * Unamer (https://github.com/unamer) * * Karl Dahlke (eklhad@gmail.com) * * If you are accidentally missing from this list, send me an e-mail * (``sami.vaarala@iki.fi``) and I'll fix the omission. */ #line 1 "duk_replacements.c" /* * Replacements for missing platform functions. * * Unlike the originals, fpclassify() and signbit() replacements don't * work on any floating point types, only doubles. The C typing here * mimics the standard prototypes. */ /* #include duk_internal.h */ #line 1 "duk_internal.h" /* * Top-level include file to be used for all (internal) source files. * * Source files should not include individual header files, as they * have not been designed to be individually included. */ #if !defined(DUK_INTERNAL_H_INCLUDED) #define DUK_INTERNAL_H_INCLUDED /* * The 'duktape.h' header provides the public API, but also handles all * compiler and platform specific feature detection, Duktape feature * resolution, inclusion of system headers, etc. These have been merged * because the public API is also dependent on e.g. detecting appropriate * C types which is quite platform/compiler specific especially for a non-C99 * build. The public API is also dependent on the resolved feature set. * * Some actions taken by the merged header (such as including system headers) * are not appropriate for building a user application. The define * DUK_COMPILING_DUKTAPE allows the merged header to skip/include some * sections depending on what is being built. */ #define DUK_COMPILING_DUKTAPE #include "duktape.h" /* * Duktape includes (other than duk_features.h) * * The header files expect to be included in an order which satisfies header * dependencies correctly (the headers themselves don't include any other * includes). Forward declarations are used to break circular struct/typedef * dependencies. */ /* #include duk_dblunion.h */ #line 1 "duk_dblunion.h" /* * Union to access IEEE double memory representation, indexes for double * memory representation, and some macros for double manipulation. * * Also used by packed duk_tval. Use a union for bit manipulation to * minimize aliasing issues in practice. The C99 standard does not * guarantee that this should work, but it's a very widely supported * practice for low level manipulation. * * IEEE double format summary: * * seeeeeee eeeeffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff * A B C D E F G H * * s sign bit * eee... exponent field * fff... fraction * * See http://en.wikipedia.org/wiki/Double_precision_floating-point_format. * * NaNs are represented as exponent 0x7ff and mantissa != 0. The NaN is a * signaling NaN when the highest bit of the mantissa is zero, and a quiet * NaN when the highest bit is set. * * At least three memory layouts are relevant here: * * A B C D E F G H Big endian (e.g. 68k) DUK_USE_DOUBLE_BE * H G F E D C B A Little endian (e.g. x86) DUK_USE_DOUBLE_LE * D C B A H G F E Mixed endian (e.g. ARM FPA) DUK_USE_DOUBLE_ME * * Legacy ARM (FPA) is a special case: ARM double values are in mixed * endian format while ARM duk_uint64_t values are in standard little endian * format (H G F E D C B A). When a double is read as a duk_uint64_t * from memory, the register will contain the (logical) value * E F G H A B C D. This requires some special handling below. * See http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0056d/Bcfhgcgd.html. * * Indexes of various types (8-bit, 16-bit, 32-bit) in memory relative to * the logical (big endian) order: * * byte order duk_uint8_t duk_uint16_t duk_uint32_t * BE 01234567 0123 01 * LE 76543210 3210 10 * ME (ARM) 32107654 1032 01 * * Some processors may alter NaN values in a floating point load+store. * For instance, on X86 a FLD + FSTP may convert a signaling NaN to a * quiet one. This is catastrophic when NaN space is used in packed * duk_tval values. See: misc/clang_aliasing.c. */ #if !defined(DUK_DBLUNION_H_INCLUDED) #define DUK_DBLUNION_H_INCLUDED /* * Union for accessing double parts, also serves as packed duk_tval */ union duk_double_union { double d; float f[2]; #if defined(DUK_USE_64BIT_OPS) duk_uint64_t ull[1]; #endif duk_uint32_t ui[2]; duk_uint16_t us[4]; duk_uint8_t uc[8]; #if defined(DUK_USE_PACKED_TVAL) void *vp[2]; /* used by packed duk_tval, assumes sizeof(void *) == 4 */ #endif }; typedef union duk_double_union duk_double_union; /* * Indexes of various types with respect to big endian (logical) layout */ #if defined(DUK_USE_DOUBLE_LE) #if defined(DUK_USE_64BIT_OPS) #define DUK_DBL_IDX_ULL0 0 #endif #define DUK_DBL_IDX_UI0 1 #define DUK_DBL_IDX_UI1 0 #define DUK_DBL_IDX_US0 3 #define DUK_DBL_IDX_US1 2 #define DUK_DBL_IDX_US2 1 #define DUK_DBL_IDX_US3 0 #define DUK_DBL_IDX_UC0 7 #define DUK_DBL_IDX_UC1 6 #define DUK_DBL_IDX_UC2 5 #define DUK_DBL_IDX_UC3 4 #define DUK_DBL_IDX_UC4 3 #define DUK_DBL_IDX_UC5 2 #define DUK_DBL_IDX_UC6 1 #define DUK_DBL_IDX_UC7 0 #define DUK_DBL_IDX_VP0 DUK_DBL_IDX_UI0 /* packed tval */ #define DUK_DBL_IDX_VP1 DUK_DBL_IDX_UI1 /* packed tval */ #elif defined(DUK_USE_DOUBLE_BE) #if defined(DUK_USE_64BIT_OPS) #define DUK_DBL_IDX_ULL0 0 #endif #define DUK_DBL_IDX_UI0 0 #define DUK_DBL_IDX_UI1 1 #define DUK_DBL_IDX_US0 0 #define DUK_DBL_IDX_US1 1 #define DUK_DBL_IDX_US2 2 #define DUK_DBL_IDX_US3 3 #define DUK_DBL_IDX_UC0 0 #define DUK_DBL_IDX_UC1 1 #define DUK_DBL_IDX_UC2 2 #define DUK_DBL_IDX_UC3 3 #define DUK_DBL_IDX_UC4 4 #define DUK_DBL_IDX_UC5 5 #define DUK_DBL_IDX_UC6 6 #define DUK_DBL_IDX_UC7 7 #define DUK_DBL_IDX_VP0 DUK_DBL_IDX_UI0 /* packed tval */ #define DUK_DBL_IDX_VP1 DUK_DBL_IDX_UI1 /* packed tval */ #elif defined(DUK_USE_DOUBLE_ME) #if defined(DUK_USE_64BIT_OPS) #define DUK_DBL_IDX_ULL0 0 /* not directly applicable, byte order differs from a double */ #endif #define DUK_DBL_IDX_UI0 0 #define DUK_DBL_IDX_UI1 1 #define DUK_DBL_IDX_US0 1 #define DUK_DBL_IDX_US1 0 #define DUK_DBL_IDX_US2 3 #define DUK_DBL_IDX_US3 2 #define DUK_DBL_IDX_UC0 3 #define DUK_DBL_IDX_UC1 2 #define DUK_DBL_IDX_UC2 1 #define DUK_DBL_IDX_UC3 0 #define DUK_DBL_IDX_UC4 7 #define DUK_DBL_IDX_UC5 6 #define DUK_DBL_IDX_UC6 5 #define DUK_DBL_IDX_UC7 4 #define DUK_DBL_IDX_VP0 DUK_DBL_IDX_UI0 /* packed tval */ #define DUK_DBL_IDX_VP1 DUK_DBL_IDX_UI1 /* packed tval */ #else #error internal error #endif /* * Helper macros for reading/writing memory representation parts, used * by duk_numconv.c and duk_tval.h. */ #define DUK_DBLUNION_SET_DOUBLE(u, v) \ do { \ (u)->d = (v); \ } while (0) #define DUK_DBLUNION_SET_HIGH32(u, v) \ do { \ (u)->ui[DUK_DBL_IDX_UI0] = (duk_uint32_t) (v); \ } while (0) #if defined(DUK_USE_64BIT_OPS) #if defined(DUK_USE_DOUBLE_ME) #define DUK_DBLUNION_SET_HIGH32_ZERO_LOW32(u, v) \ do { \ (u)->ull[DUK_DBL_IDX_ULL0] = (duk_uint64_t) (v); \ } while (0) #else #define DUK_DBLUNION_SET_HIGH32_ZERO_LOW32(u, v) \ do { \ (u)->ull[DUK_DBL_IDX_ULL0] = ((duk_uint64_t) (v)) << 32; \ } while (0) #endif #else /* DUK_USE_64BIT_OPS */ #define DUK_DBLUNION_SET_HIGH32_ZERO_LOW32(u, v) \ do { \ (u)->ui[DUK_DBL_IDX_UI0] = (duk_uint32_t) (v); \ (u)->ui[DUK_DBL_IDX_UI1] = (duk_uint32_t) 0; \ } while (0) #endif /* DUK_USE_64BIT_OPS */ #define DUK_DBLUNION_SET_LOW32(u, v) \ do { \ (u)->ui[DUK_DBL_IDX_UI1] = (duk_uint32_t) (v); \ } while (0) #define DUK_DBLUNION_GET_DOUBLE(u) ((u)->d) #define DUK_DBLUNION_GET_HIGH32(u) ((u)->ui[DUK_DBL_IDX_UI0]) #define DUK_DBLUNION_GET_LOW32(u) ((u)->ui[DUK_DBL_IDX_UI1]) #if defined(DUK_USE_64BIT_OPS) #if defined(DUK_USE_DOUBLE_ME) #define DUK_DBLUNION_SET_UINT64(u, v) \ do { \ (u)->ui[DUK_DBL_IDX_UI0] = (duk_uint32_t) ((v) >> 32); \ (u)->ui[DUK_DBL_IDX_UI1] = (duk_uint32_t) (v); \ } while (0) #define DUK_DBLUNION_GET_UINT64(u) ((((duk_uint64_t) (u)->ui[DUK_DBL_IDX_UI0]) << 32) | ((duk_uint64_t) (u)->ui[DUK_DBL_IDX_UI1])) #else #define DUK_DBLUNION_SET_UINT64(u, v) \ do { \ (u)->ull[DUK_DBL_IDX_ULL0] = (duk_uint64_t) (v); \ } while (0) #define DUK_DBLUNION_GET_UINT64(u) ((u)->ull[DUK_DBL_IDX_ULL0]) #endif #define DUK_DBLUNION_SET_INT64(u, v) DUK_DBLUNION_SET_UINT64((u), (duk_uint64_t) (v)) #define DUK_DBLUNION_GET_INT64(u) ((duk_int64_t) DUK_DBLUNION_GET_UINT64((u))) #endif /* DUK_USE_64BIT_OPS */ /* * Double NaN manipulation macros related to NaN normalization needed when * using the packed duk_tval representation. NaN normalization is necessary * to keep double values compatible with the duk_tval format. * * When packed duk_tval is used, the NaN space is used to store pointers * and other tagged values in addition to NaNs. Actual NaNs are normalized * to a specific quiet NaN. The macros below are used by the implementation * to check and normalize NaN values when they might be created. The macros * are essentially NOPs when the non-packed duk_tval representation is used. * * A FULL check is exact and checks all bits. A NOTFULL check is used by * the packed duk_tval and works correctly for all NaNs except those that * begin with 0x7ff0. Since the 'normalized NaN' values used with packed * duk_tval begin with 0x7ff8, the partial check is reliable when packed * duk_tval is used. The 0x7ff8 prefix means the normalized NaN will be a * quiet NaN regardless of its remaining lower bits. * * The ME variant below is specifically for ARM byte order, which has the * feature that while doubles have a mixed byte order (32107654), unsigned * long long values has a little endian byte order (76543210). When writing * a logical double value through a ULL pointer, the 32-bit words need to be * swapped; hence the #if defined()s below for ULL writes with DUK_USE_DOUBLE_ME. * This is not full ARM support but suffices for some environments. */ #if defined(DUK_USE_64BIT_OPS) #if defined(DUK_USE_DOUBLE_ME) /* Macros for 64-bit ops + mixed endian doubles. */ #define DUK__DBLUNION_SET_NAN_FULL(u) \ do { \ (u)->ull[DUK_DBL_IDX_ULL0] = DUK_U64_CONSTANT(0x000000007ff80000); \ } while (0) #define DUK__DBLUNION_IS_NAN_FULL(u) \ ((((u)->ull[DUK_DBL_IDX_ULL0] & DUK_U64_CONSTANT(0x000000007ff00000)) == DUK_U64_CONSTANT(0x000000007ff00000)) && \ ((((u)->ull[DUK_DBL_IDX_ULL0]) & DUK_U64_CONSTANT(0xffffffff000fffff)) != 0)) #define DUK__DBLUNION_IS_NORMALIZED_NAN_FULL(u) ((u)->ull[DUK_DBL_IDX_ULL0] == DUK_U64_CONSTANT(0x000000007ff80000)) #define DUK__DBLUNION_IS_ANYINF(u) \ (((u)->ull[DUK_DBL_IDX_ULL0] & DUK_U64_CONSTANT(0xffffffff7fffffff)) == DUK_U64_CONSTANT(0x000000007ff00000)) #define DUK__DBLUNION_IS_POSINF(u) ((u)->ull[DUK_DBL_IDX_ULL0] == DUK_U64_CONSTANT(0x000000007ff00000)) #define DUK__DBLUNION_IS_NEGINF(u) ((u)->ull[DUK_DBL_IDX_ULL0] == DUK_U64_CONSTANT(0x00000000fff00000)) #define DUK__DBLUNION_IS_ANYZERO(u) \ (((u)->ull[DUK_DBL_IDX_ULL0] & DUK_U64_CONSTANT(0xffffffff7fffffff)) == DUK_U64_CONSTANT(0x0000000000000000)) #define DUK__DBLUNION_IS_POSZERO(u) ((u)->ull[DUK_DBL_IDX_ULL0] == DUK_U64_CONSTANT(0x0000000000000000)) #define DUK__DBLUNION_IS_NEGZERO(u) ((u)->ull[DUK_DBL_IDX_ULL0] == DUK_U64_CONSTANT(0x0000000080000000)) #else /* Macros for 64-bit ops + big/little endian doubles. */ #define DUK__DBLUNION_SET_NAN_FULL(u) \ do { \ (u)->ull[DUK_DBL_IDX_ULL0] = DUK_U64_CONSTANT(0x7ff8000000000000); \ } while (0) #define DUK__DBLUNION_IS_NAN_FULL(u) \ ((((u)->ull[DUK_DBL_IDX_ULL0] & DUK_U64_CONSTANT(0x7ff0000000000000)) == DUK_U64_CONSTANT(0x7ff0000000000000)) && \ ((((u)->ull[DUK_DBL_IDX_ULL0]) & DUK_U64_CONSTANT(0x000fffffffffffff)) != 0)) #define DUK__DBLUNION_IS_NORMALIZED_NAN_FULL(u) ((u)->ull[DUK_DBL_IDX_ULL0] == DUK_U64_CONSTANT(0x7ff8000000000000)) #define DUK__DBLUNION_IS_ANYINF(u) \ (((u)->ull[DUK_DBL_IDX_ULL0] & DUK_U64_CONSTANT(0x7fffffffffffffff)) == DUK_U64_CONSTANT(0x7ff0000000000000)) #define DUK__DBLUNION_IS_POSINF(u) ((u)->ull[DUK_DBL_IDX_ULL0] == DUK_U64_CONSTANT(0x7ff0000000000000)) #define DUK__DBLUNION_IS_NEGINF(u) ((u)->ull[DUK_DBL_IDX_ULL0] == DUK_U64_CONSTANT(0xfff0000000000000)) #define DUK__DBLUNION_IS_ANYZERO(u) \ (((u)->ull[DUK_DBL_IDX_ULL0] & DUK_U64_CONSTANT(0x7fffffffffffffff)) == DUK_U64_CONSTANT(0x0000000000000000)) #define DUK__DBLUNION_IS_POSZERO(u) ((u)->ull[DUK_DBL_IDX_ULL0] == DUK_U64_CONSTANT(0x0000000000000000)) #define DUK__DBLUNION_IS_NEGZERO(u) ((u)->ull[DUK_DBL_IDX_ULL0] == DUK_U64_CONSTANT(0x8000000000000000)) #endif #else /* DUK_USE_64BIT_OPS */ /* Macros for no 64-bit ops, any endianness. */ #define DUK__DBLUNION_SET_NAN_FULL(u) \ do { \ (u)->ui[DUK_DBL_IDX_UI0] = (duk_uint32_t) 0x7ff80000UL; \ (u)->ui[DUK_DBL_IDX_UI1] = (duk_uint32_t) 0x00000000UL; \ } while (0) #define DUK__DBLUNION_IS_NAN_FULL(u) \ ((((u)->ui[DUK_DBL_IDX_UI0] & 0x7ff00000UL) == 0x7ff00000UL) && \ (((u)->ui[DUK_DBL_IDX_UI0] & 0x000fffffUL) != 0 || (u)->ui[DUK_DBL_IDX_UI1] != 0)) #define DUK__DBLUNION_IS_NORMALIZED_NAN_FULL(u) \ (((u)->ui[DUK_DBL_IDX_UI0] == 0x7ff80000UL) && ((u)->ui[DUK_DBL_IDX_UI1] == 0x00000000UL)) #define DUK__DBLUNION_IS_ANYINF(u) \ ((((u)->ui[DUK_DBL_IDX_UI0] & 0x7fffffffUL) == 0x7ff00000UL) && ((u)->ui[DUK_DBL_IDX_UI1] == 0x00000000UL)) #define DUK__DBLUNION_IS_POSINF(u) (((u)->ui[DUK_DBL_IDX_UI0] == 0x7ff00000UL) && ((u)->ui[DUK_DBL_IDX_UI1] == 0x00000000UL)) #define DUK__DBLUNION_IS_NEGINF(u) (((u)->ui[DUK_DBL_IDX_UI0] == 0xfff00000UL) && ((u)->ui[DUK_DBL_IDX_UI1] == 0x00000000UL)) #define DUK__DBLUNION_IS_ANYZERO(u) \ ((((u)->ui[DUK_DBL_IDX_UI0] & 0x7fffffffUL) == 0x00000000UL) && ((u)->ui[DUK_DBL_IDX_UI1] == 0x00000000UL)) #define DUK__DBLUNION_IS_POSZERO(u) (((u)->ui[DUK_DBL_IDX_UI0] == 0x00000000UL) && ((u)->ui[DUK_DBL_IDX_UI1] == 0x00000000UL)) #define DUK__DBLUNION_IS_NEGZERO(u) (((u)->ui[DUK_DBL_IDX_UI0] == 0x80000000UL) && ((u)->ui[DUK_DBL_IDX_UI1] == 0x00000000UL)) #endif /* DUK_USE_64BIT_OPS */ #define DUK__DBLUNION_SET_NAN_NOTFULL(u) \ do { \ (u)->us[DUK_DBL_IDX_US0] = 0x7ff8UL; \ } while (0) #define DUK__DBLUNION_IS_NAN_NOTFULL(u) \ /* E == 0x7ff, topmost four bits of F != 0 => assume NaN */ \ ((((u)->us[DUK_DBL_IDX_US0] & 0x7ff0UL) == 0x7ff0UL) && (((u)->us[DUK_DBL_IDX_US0] & 0x000fUL) != 0x0000UL)) #define DUK__DBLUNION_IS_NORMALIZED_NAN_NOTFULL(u) \ /* E == 0x7ff, F == 8 => normalized NaN */ \ ((u)->us[DUK_DBL_IDX_US0] == 0x7ff8UL) #define DUK__DBLUNION_NORMALIZE_NAN_CHECK_FULL(u) \ do { \ if (DUK__DBLUNION_IS_NAN_FULL((u))) { \ DUK__DBLUNION_SET_NAN_FULL((u)); \ } \ } while (0) #define DUK__DBLUNION_NORMALIZE_NAN_CHECK_NOTFULL(u) \ do { \ /* Check must be full. */ \ if (DUK__DBLUNION_IS_NAN_FULL((u))) { \ DUK__DBLUNION_SET_NAN_NOTFULL((u)); \ } \ } while (0) /* Concrete macros for NaN handling used by the implementation internals. * Chosen so that they match the duk_tval representation: with a packed * duk_tval, ensure NaNs are properly normalized; with a non-packed duk_tval * these are essentially NOPs. */ #if defined(DUK_USE_PACKED_TVAL) #define DUK_DBLUNION_NORMALIZE_NAN_CHECK(u) DUK__DBLUNION_NORMALIZE_NAN_CHECK_FULL((u)) #define DUK_DBLUNION_IS_NAN(u) DUK__DBLUNION_IS_NAN_FULL((u)) #define DUK_DBLUNION_IS_NORMALIZED_NAN(u) DUK__DBLUNION_IS_NORMALIZED_NAN_FULL((u)) #define DUK_DBLUNION_SET_NAN(d) DUK__DBLUNION_SET_NAN_FULL((d)) #if 0 #define DUK_DBLUNION_NORMALIZE_NAN_CHECK(u) DUK__DBLUNION_NORMALIZE_NAN_CHECK_NOTFULL((u)) #define DUK_DBLUNION_IS_NAN(u) DUK__DBLUNION_IS_NAN_NOTFULL((u)) #define DUK_DBLUNION_IS_NORMALIZED_NAN(u) DUK__DBLUNION_IS_NORMALIZED_NAN_NOTFULL((u)) #define DUK_DBLUNION_SET_NAN(d) DUK__DBLUNION_SET_NAN_NOTFULL((d)) #endif #define DUK_DBLUNION_IS_NORMALIZED(u) \ (!DUK_DBLUNION_IS_NAN((u)) || /* either not a NaN */ \ DUK_DBLUNION_IS_NORMALIZED_NAN((u))) /* or is a normalized NaN */ #else /* DUK_USE_PACKED_TVAL */ #define DUK_DBLUNION_NORMALIZE_NAN_CHECK(u) /* nop: no need to normalize */ #define DUK_DBLUNION_IS_NAN(u) DUK__DBLUNION_IS_NAN_FULL((u)) /* (DUK_ISNAN((u)->d)) */ #define DUK_DBLUNION_IS_NORMALIZED_NAN(u) DUK__DBLUNION_IS_NAN_FULL((u)) /* (DUK_ISNAN((u)->d)) */ #define DUK_DBLUNION_IS_NORMALIZED(u) 1 /* all doubles are considered normalized */ #define DUK_DBLUNION_SET_NAN(u) \ do { \ /* in non-packed representation we don't care about which NaN is used */ \ (u)->d = DUK_DOUBLE_NAN; \ } while (0) #endif /* DUK_USE_PACKED_TVAL */ #define DUK_DBLUNION_IS_ANYINF(u) DUK__DBLUNION_IS_ANYINF((u)) #define DUK_DBLUNION_IS_POSINF(u) DUK__DBLUNION_IS_POSINF((u)) #define DUK_DBLUNION_IS_NEGINF(u) DUK__DBLUNION_IS_NEGINF((u)) #define DUK_DBLUNION_IS_ANYZERO(u) DUK__DBLUNION_IS_ANYZERO((u)) #define DUK_DBLUNION_IS_POSZERO(u) DUK__DBLUNION_IS_POSZERO((u)) #define DUK_DBLUNION_IS_NEGZERO(u) DUK__DBLUNION_IS_NEGZERO((u)) /* XXX: native 64-bit byteswaps when available */ /* 64-bit byteswap, same operation independent of target endianness. */ #define DUK_DBLUNION_BSWAP64(u) \ do { \ duk_uint32_t duk__bswaptmp1, duk__bswaptmp2; \ duk__bswaptmp1 = (u)->ui[0]; \ duk__bswaptmp2 = (u)->ui[1]; \ duk__bswaptmp1 = DUK_BSWAP32(duk__bswaptmp1); \ duk__bswaptmp2 = DUK_BSWAP32(duk__bswaptmp2); \ (u)->ui[0] = duk__bswaptmp2; \ (u)->ui[1] = duk__bswaptmp1; \ } while (0) /* Byteswap an IEEE double in the duk_double_union from host to network * order. For a big endian target this is a no-op. */ #if defined(DUK_USE_DOUBLE_LE) #define DUK_DBLUNION_DOUBLE_HTON(u) \ do { \ duk_uint32_t duk__bswaptmp1, duk__bswaptmp2; \ duk__bswaptmp1 = (u)->ui[0]; \ duk__bswaptmp2 = (u)->ui[1]; \ duk__bswaptmp1 = DUK_BSWAP32(duk__bswaptmp1); \ duk__bswaptmp2 = DUK_BSWAP32(duk__bswaptmp2); \ (u)->ui[0] = duk__bswaptmp2; \ (u)->ui[1] = duk__bswaptmp1; \ } while (0) #elif defined(DUK_USE_DOUBLE_ME) #define DUK_DBLUNION_DOUBLE_HTON(u) \ do { \ duk_uint32_t duk__bswaptmp1, duk__bswaptmp2; \ duk__bswaptmp1 = (u)->ui[0]; \ duk__bswaptmp2 = (u)->ui[1]; \ duk__bswaptmp1 = DUK_BSWAP32(duk__bswaptmp1); \ duk__bswaptmp2 = DUK_BSWAP32(duk__bswaptmp2); \ (u)->ui[0] = duk__bswaptmp1; \ (u)->ui[1] = duk__bswaptmp2; \ } while (0) #elif defined(DUK_USE_DOUBLE_BE) #define DUK_DBLUNION_DOUBLE_HTON(u) \ do { \ } while (0) #else #error internal error, double endianness insane #endif /* Reverse operation is the same. */ #define DUK_DBLUNION_DOUBLE_NTOH(u) DUK_DBLUNION_DOUBLE_HTON((u)) /* Some sign bit helpers. */ #if defined(DUK_USE_64BIT_OPS) #define DUK_DBLUNION_HAS_SIGNBIT(u) (((u)->ull[DUK_DBL_IDX_ULL0] & DUK_U64_CONSTANT(0x8000000000000000)) != 0) #define DUK_DBLUNION_GET_SIGNBIT(u) (((u)->ull[DUK_DBL_IDX_ULL0] >> 63U)) #else #define DUK_DBLUNION_HAS_SIGNBIT(u) (((u)->ui[DUK_DBL_IDX_UI0] & 0x80000000UL) != 0) #define DUK_DBLUNION_GET_SIGNBIT(u) (((u)->ui[DUK_DBL_IDX_UI0] >> 31U)) #endif #endif /* DUK_DBLUNION_H_INCLUDED */ /* #include duk_fltunion.h */ #line 1 "duk_fltunion.h" /* * Union to access IEEE float memory representation. */ #if !defined(DUK_FLTUNION_H_INCLUDED) #define DUK_FLTUNION_H_INCLUDED /* #include duk_internal.h -> already included */ union duk_float_union { float f; duk_uint32_t ui[1]; duk_uint16_t us[2]; duk_uint8_t uc[4]; }; typedef union duk_float_union duk_float_union; #if defined(DUK_USE_DOUBLE_LE) || defined(DUK_USE_DOUBLE_ME) #define DUK_FLT_IDX_UI0 0 #define DUK_FLT_IDX_US0 1 #define DUK_FLT_IDX_US1 0 #define DUK_FLT_IDX_UC0 3 #define DUK_FLT_IDX_UC1 2 #define DUK_FLT_IDX_UC2 1 #define DUK_FLT_IDX_UC3 0 #elif defined(DUK_USE_DOUBLE_BE) #define DUK_FLT_IDX_UI0 0 #define DUK_FLT_IDX_US0 0 #define DUK_FLT_IDX_US1 1 #define DUK_FLT_IDX_UC0 0 #define DUK_FLT_IDX_UC1 1 #define DUK_FLT_IDX_UC2 2 #define DUK_FLT_IDX_UC3 3 #else #error internal error #endif #endif /* DUK_FLTUNION_H_INCLUDED */ /* #include duk_replacements.h */ #line 1 "duk_replacements.h" #if !defined(DUK_REPLACEMENTS_H_INCLUDED) #define DUK_REPLACEMENTS_H_INCLUDED #if !defined(DUK_SINGLE_FILE) #if defined(DUK_USE_COMPUTED_INFINITY) DUK_INTERNAL_DECL double duk_computed_infinity; #endif #if defined(DUK_USE_COMPUTED_NAN) DUK_INTERNAL_DECL double duk_computed_nan; #endif #endif /* !DUK_SINGLE_FILE */ #if defined(DUK_USE_REPL_FPCLASSIFY) DUK_INTERNAL_DECL int duk_repl_fpclassify(double x); #endif #if defined(DUK_USE_REPL_SIGNBIT) DUK_INTERNAL_DECL int duk_repl_signbit(double x); #endif #if defined(DUK_USE_REPL_ISFINITE) DUK_INTERNAL_DECL int duk_repl_isfinite(double x); #endif #if defined(DUK_USE_REPL_ISNAN) DUK_INTERNAL_DECL int duk_repl_isnan(double x); #endif #if defined(DUK_USE_REPL_ISINF) DUK_INTERNAL_DECL int duk_repl_isinf(double x); #endif #endif /* DUK_REPLACEMENTS_H_INCLUDED */ /* #include duk_jmpbuf.h */ #line 1 "duk_jmpbuf.h" /* * Wrapper for jmp_buf. * * This is used because jmp_buf is an array type for backward compatibility. * Wrapping jmp_buf in a struct makes pointer references, sizeof, etc, * behave more intuitively. * * http://en.wikipedia.org/wiki/Setjmp.h#Member_types */ #if !defined(DUK_JMPBUF_H_INCLUDED) #define DUK_JMPBUF_H_INCLUDED #if defined(DUK_USE_CPP_EXCEPTIONS) struct duk_jmpbuf { duk_small_int_t dummy; /* unused */ }; #else struct duk_jmpbuf { DUK_JMPBUF_TYPE jb; }; #endif #endif /* DUK_JMPBUF_H_INCLUDED */ /* #include duk_exception.h */ #line 1 "duk_exception.h" /* * Exceptions for Duktape internal throws when C++ exceptions are used * for long control transfers. */ #if !defined(DUK_EXCEPTION_H_INCLUDED) #define DUK_EXCEPTION_H_INCLUDED #if defined(DUK_USE_CPP_EXCEPTIONS) /* Internal exception used as a setjmp-longjmp replacement. User code should * NEVER see or catch this exception, so it doesn't inherit from any base * class which should minimize the chance of user code accidentally catching * the exception. */ class duk_internal_exception { /* intentionally empty */ }; /* Fatal error, thrown as a specific C++ exception with C++ exceptions * enabled. It is unsafe to continue; doing so may cause crashes or memory * leaks. This is intended to be either uncaught, or caught by user code * aware of the "unsafe to continue" semantics. */ class duk_fatal_exception : public virtual std::runtime_error { public: duk_fatal_exception(const char *message) : std::runtime_error(message) { } }; #endif #endif /* DUK_EXCEPTION_H_INCLUDED */ /* #include duk_forwdecl.h */ #line 1 "duk_forwdecl.h" /* * Forward declarations for all Duktape structures. */ #if !defined(DUK_FORWDECL_H_INCLUDED) #define DUK_FORWDECL_H_INCLUDED /* * Forward declarations */ #if defined(DUK_USE_CPP_EXCEPTIONS) class duk_internal_exception; #else struct duk_jmpbuf; #endif /* duk_tval intentionally skipped */ struct duk_heaphdr; struct duk_heaphdr_string; struct duk_harray; struct duk_hstring; struct duk_hstring_external; struct duk_hobject; struct duk_hcompfunc; struct duk_hnatfunc; struct duk_hboundfunc; struct duk_hthread; struct duk_hbufobj; struct duk_hdecenv; struct duk_hobjenv; struct duk_hproxy; struct duk_hbuffer; struct duk_hbuffer_fixed; struct duk_hbuffer_dynamic; struct duk_hbuffer_external; struct duk_propaccessor; union duk_propvalue; struct duk_propdesc; struct duk_heap; struct duk_breakpoint; struct duk_activation; struct duk_catcher; struct duk_ljstate; struct duk_strcache_entry; struct duk_litcache_entry; struct duk_strtab_entry; #if defined(DUK_USE_DEBUG) struct duk_fixedbuffer; #endif struct duk_bitdecoder_ctx; struct duk_bitencoder_ctx; struct duk_bufwriter_ctx; struct duk_token; struct duk_re_token; struct duk_lexer_point; struct duk_lexer_ctx; struct duk_lexer_codepoint; struct duk_compiler_instr; struct duk_compiler_func; struct duk_compiler_ctx; struct duk_re_matcher_ctx; struct duk_re_compiler_ctx; #if defined(DUK_USE_CPP_EXCEPTIONS) /* no typedef */ #else typedef struct duk_jmpbuf duk_jmpbuf; #endif /* duk_tval intentionally skipped */ typedef struct duk_heaphdr duk_heaphdr; typedef struct duk_heaphdr_string duk_heaphdr_string; typedef struct duk_harray duk_harray; typedef struct duk_hstring duk_hstring; typedef struct duk_hstring_external duk_hstring_external; typedef struct duk_hobject duk_hobject; typedef struct duk_hcompfunc duk_hcompfunc; typedef struct duk_hnatfunc duk_hnatfunc; typedef struct duk_hboundfunc duk_hboundfunc; typedef struct duk_hthread duk_hthread; typedef struct duk_hbufobj duk_hbufobj; typedef struct duk_hdecenv duk_hdecenv; typedef struct duk_hobjenv duk_hobjenv; typedef struct duk_hproxy duk_hproxy; typedef struct duk_hbuffer duk_hbuffer; typedef struct duk_hbuffer_fixed duk_hbuffer_fixed; typedef struct duk_hbuffer_dynamic duk_hbuffer_dynamic; typedef struct duk_hbuffer_external duk_hbuffer_external; typedef struct duk_propaccessor duk_propaccessor; typedef union duk_propvalue duk_propvalue; typedef struct duk_propdesc duk_propdesc; typedef struct duk_heap duk_heap; typedef struct duk_breakpoint duk_breakpoint; typedef struct duk_activation duk_activation; typedef struct duk_catcher duk_catcher; typedef struct duk_ljstate duk_ljstate; typedef struct duk_strcache_entry duk_strcache_entry; typedef struct duk_litcache_entry duk_litcache_entry; typedef struct duk_strtab_entry duk_strtab_entry; #if defined(DUK_USE_DEBUG) typedef struct duk_fixedbuffer duk_fixedbuffer; #endif typedef struct duk_bitdecoder_ctx duk_bitdecoder_ctx; typedef struct duk_bitencoder_ctx duk_bitencoder_ctx; typedef struct duk_bufwriter_ctx duk_bufwriter_ctx; typedef struct duk_token duk_token; typedef struct duk_re_token duk_re_token; typedef struct duk_lexer_point duk_lexer_point; typedef struct duk_lexer_ctx duk_lexer_ctx; typedef struct duk_lexer_codepoint duk_lexer_codepoint; typedef struct duk_compiler_instr duk_compiler_instr; typedef struct duk_compiler_func duk_compiler_func; typedef struct duk_compiler_ctx duk_compiler_ctx; typedef struct duk_re_matcher_ctx duk_re_matcher_ctx; typedef struct duk_re_compiler_ctx duk_re_compiler_ctx; #endif /* DUK_FORWDECL_H_INCLUDED */ /* #include duk_tval.h */ #line 1 "duk_tval.h" /* * Tagged type definition (duk_tval) and accessor macros. * * Access all fields through the accessor macros, as the representation * is quite tricky. * * There are two packed type alternatives: an 8-byte representation * based on an IEEE double (preferred for compactness), and a 12-byte * representation (portability). The latter is needed also in e.g. * 64-bit environments (it usually pads to 16 bytes per value). * * Selecting the tagged type format involves many trade-offs (memory * use, size and performance of generated code, portability, etc). * * NB: because macro arguments are often expressions, macros should * avoid evaluating their argument more than once. */ #if !defined(DUK_TVAL_H_INCLUDED) #define DUK_TVAL_H_INCLUDED /* sanity */ #if !defined(DUK_USE_DOUBLE_LE) && !defined(DUK_USE_DOUBLE_ME) && !defined(DUK_USE_DOUBLE_BE) #error unsupported: cannot determine byte order variant #endif #if defined(DUK_USE_PACKED_TVAL) /* ======================================================================== */ /* * Packed 8-byte representation */ /* use duk_double_union as duk_tval directly */ typedef union duk_double_union duk_tval; typedef struct { duk_uint16_t a; duk_uint16_t b; duk_uint16_t c; duk_uint16_t d; } duk_tval_unused; /* tags */ #define DUK_TAG_NORMALIZED_NAN 0x7ff8UL /* the NaN variant we use */ /* avoid tag 0xfff0, no risk of confusion with negative infinity */ #define DUK_TAG_MIN 0xfff1UL #if defined(DUK_USE_FASTINT) #define DUK_TAG_FASTINT 0xfff1UL /* embed: integer value */ #endif #define DUK_TAG_UNUSED 0xfff2UL /* marker; not actual tagged value */ #define DUK_TAG_UNDEFINED 0xfff3UL /* embed: nothing */ #define DUK_TAG_NULL 0xfff4UL /* embed: nothing */ #define DUK_TAG_BOOLEAN 0xfff5UL /* embed: 0 or 1 (false or true) */ /* DUK_TAG_NUMBER would logically go here, but it has multiple 'tags' */ #define DUK_TAG_POINTER 0xfff6UL /* embed: void ptr */ #define DUK_TAG_LIGHTFUNC 0xfff7UL /* embed: func ptr */ #define DUK_TAG_STRING 0xfff8UL /* embed: duk_hstring ptr */ #define DUK_TAG_OBJECT 0xfff9UL /* embed: duk_hobject ptr */ #define DUK_TAG_BUFFER 0xfffaUL /* embed: duk_hbuffer ptr */ #define DUK_TAG_MAX 0xfffaUL /* for convenience */ #define DUK_XTAG_BOOLEAN_FALSE 0xfff50000UL #define DUK_XTAG_BOOLEAN_TRUE 0xfff50001UL #define DUK_TVAL_IS_VALID_TAG(tv) (DUK_TVAL_GET_TAG((tv)) - DUK_TAG_MIN <= DUK_TAG_MAX - DUK_TAG_MIN) /* DUK_TVAL_UNUSED initializer for duk_tval_unused, works for any endianness. */ #define DUK_TVAL_UNUSED_INITIALIZER() \ { DUK_TAG_UNUSED, DUK_TAG_UNUSED, DUK_TAG_UNUSED, DUK_TAG_UNUSED } /* two casts to avoid gcc warning: "warning: cast from pointer to integer of different size [-Wpointer-to-int-cast]" */ #if defined(DUK_USE_64BIT_OPS) #if defined(DUK_USE_DOUBLE_ME) #define DUK__TVAL_SET_TAGGEDPOINTER(tv, h, tag) \ do { \ (tv)->ull[DUK_DBL_IDX_ULL0] = (((duk_uint64_t) (tag)) << 16) | (((duk_uint64_t) (duk_uint32_t) (h)) << 32); \ } while (0) #else #define DUK__TVAL_SET_TAGGEDPOINTER(tv, h, tag) \ do { \ (tv)->ull[DUK_DBL_IDX_ULL0] = (((duk_uint64_t) (tag)) << 48) | ((duk_uint64_t) (duk_uint32_t) (h)); \ } while (0) #endif #else /* DUK_USE_64BIT_OPS */ #define DUK__TVAL_SET_TAGGEDPOINTER(tv, h, tag) \ do { \ duk_tval *duk__tv; \ duk__tv = (tv); \ duk__tv->ui[DUK_DBL_IDX_UI0] = ((duk_uint32_t) (tag)) << 16; \ duk__tv->ui[DUK_DBL_IDX_UI1] = (duk_uint32_t) (h); \ } while (0) #endif /* DUK_USE_64BIT_OPS */ #if defined(DUK_USE_64BIT_OPS) /* Double casting for pointer to avoid gcc warning (cast from pointer to integer of different size) */ #if defined(DUK_USE_DOUBLE_ME) #define DUK__TVAL_SET_LIGHTFUNC(tv, fp, flags) \ do { \ (tv)->ull[DUK_DBL_IDX_ULL0] = (((duk_uint64_t) DUK_TAG_LIGHTFUNC) << 16) | ((duk_uint64_t) (flags)) | \ (((duk_uint64_t) (duk_uint32_t) (fp)) << 32); \ } while (0) #else #define DUK__TVAL_SET_LIGHTFUNC(tv, fp, flags) \ do { \ (tv)->ull[DUK_DBL_IDX_ULL0] = (((duk_uint64_t) DUK_TAG_LIGHTFUNC) << 48) | (((duk_uint64_t) (flags)) << 32) | \ ((duk_uint64_t) (duk_uint32_t) (fp)); \ } while (0) #endif #else /* DUK_USE_64BIT_OPS */ #define DUK__TVAL_SET_LIGHTFUNC(tv, fp, flags) \ do { \ duk_tval *duk__tv; \ duk__tv = (tv); \ duk__tv->ui[DUK_DBL_IDX_UI0] = (((duk_uint32_t) DUK_TAG_LIGHTFUNC) << 16) | ((duk_uint32_t) (flags)); \ duk__tv->ui[DUK_DBL_IDX_UI1] = (duk_uint32_t) (fp); \ } while (0) #endif /* DUK_USE_64BIT_OPS */ #if defined(DUK_USE_FASTINT) /* Note: masking is done for 'i' to deal with negative numbers correctly */ #if defined(DUK_USE_DOUBLE_ME) #define DUK__TVAL_SET_I48(tv, i) \ do { \ duk_tval *duk__tv; \ duk__tv = (tv); \ duk__tv->ui[DUK_DBL_IDX_UI0] = \ ((duk_uint32_t) DUK_TAG_FASTINT) << 16 | (((duk_uint32_t) ((i) >> 32)) & 0x0000ffffUL); \ duk__tv->ui[DUK_DBL_IDX_UI1] = (duk_uint32_t) (i); \ } while (0) #define DUK__TVAL_SET_U32(tv, i) \ do { \ duk_tval *duk__tv; \ duk__tv = (tv); \ duk__tv->ui[DUK_DBL_IDX_UI0] = ((duk_uint32_t) DUK_TAG_FASTINT) << 16; \ duk__tv->ui[DUK_DBL_IDX_UI1] = (duk_uint32_t) (i); \ } while (0) #else #define DUK__TVAL_SET_I48(tv, i) \ do { \ (tv)->ull[DUK_DBL_IDX_ULL0] = \ (((duk_uint64_t) DUK_TAG_FASTINT) << 48) | (((duk_uint64_t) (i)) & DUK_U64_CONSTANT(0x0000ffffffffffff)); \ } while (0) #define DUK__TVAL_SET_U32(tv, i) \ do { \ (tv)->ull[DUK_DBL_IDX_ULL0] = (((duk_uint64_t) DUK_TAG_FASTINT) << 48) | (duk_uint64_t) (i); \ } while (0) #endif /* This needs to go through a cast because sign extension is needed. */ #define DUK__TVAL_SET_I32(tv, i) \ do { \ duk_int64_t duk__tmp = (duk_int64_t) (i); \ DUK_TVAL_SET_I48((tv), duk__tmp); \ } while (0) /* XXX: Clumsy sign extend and masking of 16 topmost bits. */ #if defined(DUK_USE_DOUBLE_ME) #define DUK__TVAL_GET_FASTINT(tv) \ (((duk_int64_t) ((((duk_uint64_t) (tv)->ui[DUK_DBL_IDX_UI0]) << 32) | ((duk_uint64_t) (tv)->ui[DUK_DBL_IDX_UI1]))) \ << 16 >> \ 16) #else #define DUK__TVAL_GET_FASTINT(tv) ((((duk_int64_t) (tv)->ull[DUK_DBL_IDX_ULL0]) << 16) >> 16) #endif #define DUK__TVAL_GET_FASTINT_U32(tv) ((tv)->ui[DUK_DBL_IDX_UI1]) #define DUK__TVAL_GET_FASTINT_I32(tv) ((duk_int32_t) (tv)->ui[DUK_DBL_IDX_UI1]) #endif /* DUK_USE_FASTINT */ #define DUK_TVAL_SET_UNDEFINED(tv) \ do { \ (tv)->us[DUK_DBL_IDX_US0] = (duk_uint16_t) DUK_TAG_UNDEFINED; \ } while (0) #define DUK_TVAL_SET_UNUSED(tv) \ do { \ (tv)->us[DUK_DBL_IDX_US0] = (duk_uint16_t) DUK_TAG_UNUSED; \ } while (0) #define DUK_TVAL_SET_NULL(tv) \ do { \ (tv)->us[DUK_DBL_IDX_US0] = (duk_uint16_t) DUK_TAG_NULL; \ } while (0) #define DUK_TVAL_SET_BOOLEAN(tv, val) \ DUK_DBLUNION_SET_HIGH32((tv), (((duk_uint32_t) DUK_TAG_BOOLEAN) << 16) | ((duk_uint32_t) (val))) #define DUK_TVAL_SET_NAN(tv) DUK_DBLUNION_SET_NAN_FULL((tv)) /* Assumes that caller has normalized NaNs, otherwise trouble ahead. */ #if defined(DUK_USE_FASTINT) #define DUK_TVAL_SET_DOUBLE(tv, d) \ do { \ duk_double_t duk__dblval; \ duk__dblval = (d); \ DUK_ASSERT_DOUBLE_IS_NORMALIZED(duk__dblval); \ DUK_DBLUNION_SET_DOUBLE((tv), duk__dblval); \ } while (0) #define DUK_TVAL_SET_I48(tv, i) DUK__TVAL_SET_I48((tv), (i)) #define DUK_TVAL_SET_I32(tv, i) DUK__TVAL_SET_I32((tv), (i)) #define DUK_TVAL_SET_U32(tv, i) DUK__TVAL_SET_U32((tv), (i)) #define DUK_TVAL_SET_NUMBER_CHKFAST_FAST(tv, d) duk_tval_set_number_chkfast_fast((tv), (d)) #define DUK_TVAL_SET_NUMBER_CHKFAST_SLOW(tv, d) duk_tval_set_number_chkfast_slow((tv), (d)) #define DUK_TVAL_SET_NUMBER(tv, d) DUK_TVAL_SET_DOUBLE((tv), (d)) #define DUK_TVAL_CHKFAST_INPLACE_FAST(tv) \ do { \ duk_tval *duk__tv; \ duk_double_t duk__d; \ duk__tv = (tv); \ if (DUK_TVAL_IS_DOUBLE(duk__tv)) { \ duk__d = DUK_TVAL_GET_DOUBLE(duk__tv); \ DUK_TVAL_SET_NUMBER_CHKFAST_FAST(duk__tv, duk__d); \ } \ } while (0) #define DUK_TVAL_CHKFAST_INPLACE_SLOW(tv) \ do { \ duk_tval *duk__tv; \ duk_double_t duk__d; \ duk__tv = (tv); \ if (DUK_TVAL_IS_DOUBLE(duk__tv)) { \ duk__d = DUK_TVAL_GET_DOUBLE(duk__tv); \ DUK_TVAL_SET_NUMBER_CHKFAST_SLOW(duk__tv, duk__d); \ } \ } while (0) #else /* DUK_USE_FASTINT */ #define DUK_TVAL_SET_DOUBLE(tv, d) \ do { \ duk_double_t duk__dblval; \ duk__dblval = (d); \ DUK_ASSERT_DOUBLE_IS_NORMALIZED(duk__dblval); \ DUK_DBLUNION_SET_DOUBLE((tv), duk__dblval); \ } while (0) #define DUK_TVAL_SET_I48(tv, i) DUK_TVAL_SET_DOUBLE((tv), (duk_double_t) (i)) /* XXX: fast int-to-double */ #define DUK_TVAL_SET_I32(tv, i) DUK_TVAL_SET_DOUBLE((tv), (duk_double_t) (i)) #define DUK_TVAL_SET_U32(tv, i) DUK_TVAL_SET_DOUBLE((tv), (duk_double_t) (i)) #define DUK_TVAL_SET_NUMBER_CHKFAST_FAST(tv, d) DUK_TVAL_SET_DOUBLE((tv), (d)) #define DUK_TVAL_SET_NUMBER_CHKFAST_SLOW(tv, d) DUK_TVAL_SET_DOUBLE((tv), (d)) #define DUK_TVAL_SET_NUMBER(tv, d) DUK_TVAL_SET_DOUBLE((tv), (d)) #define DUK_TVAL_CHKFAST_INPLACE_FAST(tv) \ do { \ } while (0) #define DUK_TVAL_CHKFAST_INPLACE_SLOW(tv) \ do { \ } while (0) #endif /* DUK_USE_FASTINT */ #define DUK_TVAL_SET_FASTINT(tv, i) DUK_TVAL_SET_I48((tv), (i)) /* alias */ #define DUK_TVAL_SET_LIGHTFUNC(tv, fp, flags) DUK__TVAL_SET_LIGHTFUNC((tv), (fp), (flags)) #define DUK_TVAL_SET_STRING(tv, h) DUK__TVAL_SET_TAGGEDPOINTER((tv), (h), DUK_TAG_STRING) #define DUK_TVAL_SET_OBJECT(tv, h) DUK__TVAL_SET_TAGGEDPOINTER((tv), (h), DUK_TAG_OBJECT) #define DUK_TVAL_SET_BUFFER(tv, h) DUK__TVAL_SET_TAGGEDPOINTER((tv), (h), DUK_TAG_BUFFER) #define DUK_TVAL_SET_POINTER(tv, p) DUK__TVAL_SET_TAGGEDPOINTER((tv), (p), DUK_TAG_POINTER) #define DUK_TVAL_SET_TVAL(tv, x) \ do { \ *(tv) = *(x); \ } while (0) /* getters */ #define DUK_TVAL_GET_BOOLEAN(tv) ((duk_small_uint_t) (tv)->us[DUK_DBL_IDX_US1]) #if defined(DUK_USE_FASTINT) #define DUK_TVAL_GET_DOUBLE(tv) ((tv)->d) #define DUK_TVAL_GET_FASTINT(tv) DUK__TVAL_GET_FASTINT((tv)) #define DUK_TVAL_GET_FASTINT_U32(tv) DUK__TVAL_GET_FASTINT_U32((tv)) #define DUK_TVAL_GET_FASTINT_I32(tv) DUK__TVAL_GET_FASTINT_I32((tv)) #define DUK_TVAL_GET_NUMBER(tv) duk_tval_get_number_packed((tv)) #else #define DUK_TVAL_GET_NUMBER(tv) ((tv)->d) #define DUK_TVAL_GET_DOUBLE(tv) ((tv)->d) #endif #define DUK_TVAL_GET_LIGHTFUNC(tv, out_fp, out_flags) \ do { \ (out_flags) = (tv)->ui[DUK_DBL_IDX_UI0] & 0xffffUL; \ (out_fp) = (duk_c_function) (tv)->ui[DUK_DBL_IDX_UI1]; \ } while (0) #define DUK_TVAL_GET_LIGHTFUNC_FUNCPTR(tv) ((duk_c_function) ((tv)->ui[DUK_DBL_IDX_UI1])) #define DUK_TVAL_GET_LIGHTFUNC_FLAGS(tv) (((duk_small_uint_t) (tv)->ui[DUK_DBL_IDX_UI0]) & 0xffffUL) #define DUK_TVAL_GET_STRING(tv) ((duk_hstring *) (tv)->vp[DUK_DBL_IDX_VP1]) #define DUK_TVAL_GET_OBJECT(tv) ((duk_hobject *) (tv)->vp[DUK_DBL_IDX_VP1]) #define DUK_TVAL_GET_BUFFER(tv) ((duk_hbuffer *) (tv)->vp[DUK_DBL_IDX_VP1]) #define DUK_TVAL_GET_POINTER(tv) ((void *) (tv)->vp[DUK_DBL_IDX_VP1]) #define DUK_TVAL_GET_HEAPHDR(tv) ((duk_heaphdr *) (tv)->vp[DUK_DBL_IDX_VP1]) /* decoding */ #define DUK_TVAL_GET_TAG(tv) ((duk_small_uint_t) (tv)->us[DUK_DBL_IDX_US0]) #define DUK_TVAL_IS_UNDEFINED(tv) (DUK_TVAL_GET_TAG((tv)) == DUK_TAG_UNDEFINED) #define DUK_TVAL_IS_UNUSED(tv) (DUK_TVAL_GET_TAG((tv)) == DUK_TAG_UNUSED) #define DUK_TVAL_IS_NULL(tv) (DUK_TVAL_GET_TAG((tv)) == DUK_TAG_NULL) #define DUK_TVAL_IS_BOOLEAN(tv) (DUK_TVAL_GET_TAG((tv)) == DUK_TAG_BOOLEAN) #define DUK_TVAL_IS_BOOLEAN_TRUE(tv) ((tv)->ui[DUK_DBL_IDX_UI0] == DUK_XTAG_BOOLEAN_TRUE) #define DUK_TVAL_IS_BOOLEAN_FALSE(tv) ((tv)->ui[DUK_DBL_IDX_UI0] == DUK_XTAG_BOOLEAN_FALSE) #define DUK_TVAL_IS_LIGHTFUNC(tv) (DUK_TVAL_GET_TAG((tv)) == DUK_TAG_LIGHTFUNC) #define DUK_TVAL_IS_STRING(tv) (DUK_TVAL_GET_TAG((tv)) == DUK_TAG_STRING) #define DUK_TVAL_IS_OBJECT(tv) (DUK_TVAL_GET_TAG((tv)) == DUK_TAG_OBJECT) #define DUK_TVAL_IS_BUFFER(tv) (DUK_TVAL_GET_TAG((tv)) == DUK_TAG_BUFFER) #define DUK_TVAL_IS_POINTER(tv) (DUK_TVAL_GET_TAG((tv)) == DUK_TAG_POINTER) #if defined(DUK_USE_FASTINT) /* 0xfff0 is -Infinity */ #define DUK_TVAL_IS_DOUBLE(tv) (DUK_TVAL_GET_TAG((tv)) <= 0xfff0UL) #define DUK_TVAL_IS_FASTINT(tv) (DUK_TVAL_GET_TAG((tv)) == DUK_TAG_FASTINT) #define DUK_TVAL_IS_NUMBER(tv) (DUK_TVAL_GET_TAG((tv)) <= 0xfff1UL) #else #define DUK_TVAL_IS_NUMBER(tv) (DUK_TVAL_GET_TAG((tv)) <= 0xfff0UL) #define DUK_TVAL_IS_DOUBLE(tv) DUK_TVAL_IS_NUMBER((tv)) #endif /* This is performance critical because it appears in every DECREF. */ #define DUK_TVAL_IS_HEAP_ALLOCATED(tv) (DUK_TVAL_GET_TAG((tv)) >= DUK_TAG_STRING) #if defined(DUK_USE_FASTINT) DUK_INTERNAL_DECL duk_double_t duk_tval_get_number_packed(duk_tval *tv); #endif #else /* DUK_USE_PACKED_TVAL */ /* ======================================================================== */ /* * Portable 12-byte representation */ /* Note: not initializing all bytes is normally not an issue: Duktape won't * read or use the uninitialized bytes so valgrind won't issue warnings. * In some special cases a harmless valgrind warning may be issued though. * For example, the DumpHeap debugger command writes out a compiled function's * 'data' area as is, including any uninitialized bytes, which causes a * valgrind warning. */ typedef struct duk_tval_struct duk_tval; struct duk_tval_struct { duk_small_uint_t t; duk_small_uint_t v_extra; union { duk_double_t d; duk_small_int_t i; #if defined(DUK_USE_FASTINT) duk_int64_t fi; /* if present, forces 16-byte duk_tval */ #endif void *voidptr; duk_hstring *hstring; duk_hobject *hobject; duk_hcompfunc *hcompfunc; duk_hnatfunc *hnatfunc; duk_hthread *hthread; duk_hbuffer *hbuffer; duk_heaphdr *heaphdr; duk_c_function lightfunc; } v; }; typedef struct { duk_small_uint_t t; duk_small_uint_t v_extra; /* The rest of the fields don't matter except for debug dumps and such * for which a partial initializer may trigger out-ot-bounds memory * reads. Include a double field which is usually as large or larger * than pointers (not always however). */ duk_double_t d; } duk_tval_unused; #define DUK_TVAL_UNUSED_INITIALIZER() \ { DUK_TAG_UNUSED, 0, 0.0 } #define DUK_TAG_MIN 0 #define DUK_TAG_NUMBER 0 /* DUK_TAG_NUMBER only defined for non-packed duk_tval */ #if defined(DUK_USE_FASTINT) #define DUK_TAG_FASTINT 1 #endif #define DUK_TAG_UNDEFINED 2 #define DUK_TAG_NULL 3 #define DUK_TAG_BOOLEAN 4 #define DUK_TAG_POINTER 5 #define DUK_TAG_LIGHTFUNC 6 #define DUK_TAG_UNUSED 7 /* marker; not actual tagged type */ #define DUK_TAG_STRING 8 /* first heap allocated, match bit boundary */ #define DUK_TAG_OBJECT 9 #define DUK_TAG_BUFFER 10 #define DUK_TAG_MAX 10 #define DUK_TVAL_IS_VALID_TAG(tv) (DUK_TVAL_GET_TAG((tv)) - DUK_TAG_MIN <= DUK_TAG_MAX - DUK_TAG_MIN) /* DUK_TAG_NUMBER is intentionally first, as it is the default clause in code * to support the 8-byte representation. Further, it is a non-heap-allocated * type so it should come before DUK_TAG_STRING. Finally, it should not break * the tag value ranges covered by case-clauses in a switch-case. */ /* setters */ #define DUK_TVAL_SET_UNDEFINED(tv) \ do { \ duk_tval *duk__tv; \ duk__tv = (tv); \ duk__tv->t = DUK_TAG_UNDEFINED; \ } while (0) #define DUK_TVAL_SET_UNUSED(tv) \ do { \ duk_tval *duk__tv; \ duk__tv = (tv); \ duk__tv->t = DUK_TAG_UNUSED; \ } while (0) #define DUK_TVAL_SET_NULL(tv) \ do { \ duk_tval *duk__tv; \ duk__tv = (tv); \ duk__tv->t = DUK_TAG_NULL; \ } while (0) #define DUK_TVAL_SET_BOOLEAN(tv, val) \ do { \ duk_tval *duk__tv; \ duk__tv = (tv); \ duk__tv->t = DUK_TAG_BOOLEAN; \ duk__tv->v.i = (duk_small_int_t) (val); \ } while (0) #if defined(DUK_USE_FASTINT) #define DUK_TVAL_SET_DOUBLE(tv, val) \ do { \ duk_tval *duk__tv; \ duk_double_t duk__dblval; \ duk__dblval = (val); \ DUK_ASSERT_DOUBLE_IS_NORMALIZED(duk__dblval); /* nop for unpacked duk_tval */ \ duk__tv = (tv); \ duk__tv->t = DUK_TAG_NUMBER; \ duk__tv->v.d = duk__dblval; \ } while (0) #define DUK_TVAL_SET_I48(tv, val) \ do { \ duk_tval *duk__tv; \ duk__tv = (tv); \ duk__tv->t = DUK_TAG_FASTINT; \ duk__tv->v.fi = (val); \ } while (0) #define DUK_TVAL_SET_U32(tv, val) \ do { \ duk_tval *duk__tv; \ duk__tv = (tv); \ duk__tv->t = DUK_TAG_FASTINT; \ duk__tv->v.fi = (duk_int64_t) (val); \ } while (0) #define DUK_TVAL_SET_I32(tv, val) \ do { \ duk_tval *duk__tv; \ duk__tv = (tv); \ duk__tv->t = DUK_TAG_FASTINT; \ duk__tv->v.fi = (duk_int64_t) (val); \ } while (0) #define DUK_TVAL_SET_NUMBER_CHKFAST_FAST(tv, d) duk_tval_set_number_chkfast_fast((tv), (d)) #define DUK_TVAL_SET_NUMBER_CHKFAST_SLOW(tv, d) duk_tval_set_number_chkfast_slow((tv), (d)) #define DUK_TVAL_SET_NUMBER(tv, val) DUK_TVAL_SET_DOUBLE((tv), (val)) #define DUK_TVAL_CHKFAST_INPLACE_FAST(tv) \ do { \ duk_tval *duk__tv; \ duk_double_t duk__d; \ duk__tv = (tv); \ if (DUK_TVAL_IS_DOUBLE(duk__tv)) { \ duk__d = DUK_TVAL_GET_DOUBLE(duk__tv); \ DUK_TVAL_SET_NUMBER_CHKFAST_FAST(duk__tv, duk__d); \ } \ } while (0) #define DUK_TVAL_CHKFAST_INPLACE_SLOW(tv) \ do { \ duk_tval *duk__tv; \ duk_double_t duk__d; \ duk__tv = (tv); \ if (DUK_TVAL_IS_DOUBLE(duk__tv)) { \ duk__d = DUK_TVAL_GET_DOUBLE(duk__tv); \ DUK_TVAL_SET_NUMBER_CHKFAST_SLOW(duk__tv, duk__d); \ } \ } while (0) #else /* DUK_USE_FASTINT */ #define DUK_TVAL_SET_DOUBLE(tv, d) DUK_TVAL_SET_NUMBER((tv), (d)) #define DUK_TVAL_SET_I48(tv, val) DUK_TVAL_SET_NUMBER((tv), (duk_double_t) (val)) /* XXX: fast int-to-double */ #define DUK_TVAL_SET_U32(tv, val) DUK_TVAL_SET_NUMBER((tv), (duk_double_t) (val)) #define DUK_TVAL_SET_I32(tv, val) DUK_TVAL_SET_NUMBER((tv), (duk_double_t) (val)) #define DUK_TVAL_SET_NUMBER(tv, val) \ do { \ duk_tval *duk__tv; \ duk_double_t duk__dblval; \ duk__dblval = (val); \ DUK_ASSERT_DOUBLE_IS_NORMALIZED(duk__dblval); /* nop for unpacked duk_tval */ \ duk__tv = (tv); \ duk__tv->t = DUK_TAG_NUMBER; \ duk__tv->v.d = duk__dblval; \ } while (0) #define DUK_TVAL_SET_NUMBER_CHKFAST_FAST(tv, d) DUK_TVAL_SET_NUMBER((tv), (d)) #define DUK_TVAL_SET_NUMBER_CHKFAST_SLOW(tv, d) DUK_TVAL_SET_NUMBER((tv), (d)) #define DUK_TVAL_CHKFAST_INPLACE_FAST(tv) \ do { \ } while (0) #define DUK_TVAL_CHKFAST_INPLACE_SLOW(tv) \ do { \ } while (0) #endif /* DUK_USE_FASTINT */ #define DUK_TVAL_SET_FASTINT(tv, i) DUK_TVAL_SET_I48((tv), (i)) /* alias */ #define DUK_TVAL_SET_POINTER(tv, hptr) \ do { \ duk_tval *duk__tv; \ duk__tv = (tv); \ duk__tv->t = DUK_TAG_POINTER; \ duk__tv->v.voidptr = (hptr); \ } while (0) #define DUK_TVAL_SET_LIGHTFUNC(tv, fp, flags) \ do { \ duk_tval *duk__tv; \ duk__tv = (tv); \ duk__tv->t = DUK_TAG_LIGHTFUNC; \ duk__tv->v_extra = (flags); \ duk__tv->v.lightfunc = (duk_c_function) (fp); \ } while (0) #define DUK_TVAL_SET_STRING(tv, hptr) \ do { \ duk_tval *duk__tv; \ duk__tv = (tv); \ duk__tv->t = DUK_TAG_STRING; \ duk__tv->v.hstring = (hptr); \ } while (0) #define DUK_TVAL_SET_OBJECT(tv, hptr) \ do { \ duk_tval *duk__tv; \ duk__tv = (tv); \ duk__tv->t = DUK_TAG_OBJECT; \ duk__tv->v.hobject = (hptr); \ } while (0) #define DUK_TVAL_SET_BUFFER(tv, hptr) \ do { \ duk_tval *duk__tv; \ duk__tv = (tv); \ duk__tv->t = DUK_TAG_BUFFER; \ duk__tv->v.hbuffer = (hptr); \ } while (0) #define DUK_TVAL_SET_NAN(tv) \ do { \ /* in non-packed representation we don't care about which NaN is used */ \ duk_tval *duk__tv; \ duk__tv = (tv); \ duk__tv->t = DUK_TAG_NUMBER; \ duk__tv->v.d = DUK_DOUBLE_NAN; \ } while (0) #define DUK_TVAL_SET_TVAL(tv, x) \ do { \ *(tv) = *(x); \ } while (0) /* getters */ #define DUK_TVAL_GET_BOOLEAN(tv) ((duk_small_uint_t) (tv)->v.i) #if defined(DUK_USE_FASTINT) #define DUK_TVAL_GET_DOUBLE(tv) ((tv)->v.d) #define DUK_TVAL_GET_FASTINT(tv) ((tv)->v.fi) #define DUK_TVAL_GET_FASTINT_U32(tv) ((duk_uint32_t) ((tv)->v.fi)) #define DUK_TVAL_GET_FASTINT_I32(tv) ((duk_int32_t) ((tv)->v.fi)) #if 0 #define DUK_TVAL_GET_NUMBER(tv) (DUK_TVAL_IS_FASTINT((tv)) ? (duk_double_t) DUK_TVAL_GET_FASTINT((tv)) : DUK_TVAL_GET_DOUBLE((tv))) #define DUK_TVAL_GET_NUMBER(tv) duk_tval_get_number_unpacked((tv)) #else /* This seems reasonable overall. */ #define DUK_TVAL_GET_NUMBER(tv) (DUK_TVAL_IS_FASTINT((tv)) ? duk_tval_get_number_unpacked_fastint((tv)) : DUK_TVAL_GET_DOUBLE((tv))) #endif #else #define DUK_TVAL_GET_NUMBER(tv) ((tv)->v.d) #define DUK_TVAL_GET_DOUBLE(tv) ((tv)->v.d) #endif /* DUK_USE_FASTINT */ #define DUK_TVAL_GET_POINTER(tv) ((tv)->v.voidptr) #define DUK_TVAL_GET_LIGHTFUNC(tv, out_fp, out_flags) \ do { \ (out_flags) = (duk_uint32_t) (tv)->v_extra; \ (out_fp) = (tv)->v.lightfunc; \ } while (0) #define DUK_TVAL_GET_LIGHTFUNC_FUNCPTR(tv) ((tv)->v.lightfunc) #define DUK_TVAL_GET_LIGHTFUNC_FLAGS(tv) ((duk_small_uint_t) ((tv)->v_extra)) #define DUK_TVAL_GET_STRING(tv) ((tv)->v.hstring) #define DUK_TVAL_GET_OBJECT(tv) ((tv)->v.hobject) #define DUK_TVAL_GET_BUFFER(tv) ((tv)->v.hbuffer) #define DUK_TVAL_GET_HEAPHDR(tv) ((tv)->v.heaphdr) /* decoding */ #define DUK_TVAL_GET_TAG(tv) ((tv)->t) #define DUK_TVAL_IS_UNDEFINED(tv) ((tv)->t == DUK_TAG_UNDEFINED) #define DUK_TVAL_IS_UNUSED(tv) ((tv)->t == DUK_TAG_UNUSED) #define DUK_TVAL_IS_NULL(tv) ((tv)->t == DUK_TAG_NULL) #define DUK_TVAL_IS_BOOLEAN(tv) ((tv)->t == DUK_TAG_BOOLEAN) #define DUK_TVAL_IS_BOOLEAN_TRUE(tv) (((tv)->t == DUK_TAG_BOOLEAN) && ((tv)->v.i != 0)) #define DUK_TVAL_IS_BOOLEAN_FALSE(tv) (((tv)->t == DUK_TAG_BOOLEAN) && ((tv)->v.i == 0)) #if defined(DUK_USE_FASTINT) #define DUK_TVAL_IS_DOUBLE(tv) ((tv)->t == DUK_TAG_NUMBER) #define DUK_TVAL_IS_FASTINT(tv) ((tv)->t == DUK_TAG_FASTINT) #define DUK_TVAL_IS_NUMBER(tv) ((tv)->t == DUK_TAG_NUMBER || (tv)->t == DUK_TAG_FASTINT) #else #define DUK_TVAL_IS_NUMBER(tv) ((tv)->t == DUK_TAG_NUMBER) #define DUK_TVAL_IS_DOUBLE(tv) DUK_TVAL_IS_NUMBER((tv)) #endif /* DUK_USE_FASTINT */ #define DUK_TVAL_IS_POINTER(tv) ((tv)->t == DUK_TAG_POINTER) #define DUK_TVAL_IS_LIGHTFUNC(tv) ((tv)->t == DUK_TAG_LIGHTFUNC) #define DUK_TVAL_IS_STRING(tv) ((tv)->t == DUK_TAG_STRING) #define DUK_TVAL_IS_OBJECT(tv) ((tv)->t == DUK_TAG_OBJECT) #define DUK_TVAL_IS_BUFFER(tv) ((tv)->t == DUK_TAG_BUFFER) /* This is performance critical because it's needed for every DECREF. * Take advantage of the fact that the first heap allocated tag is 8, * so that bit 3 is set for all heap allocated tags (and never set for * non-heap-allocated tags). */ #if 0 #define DUK_TVAL_IS_HEAP_ALLOCATED(tv) ((tv)->t >= DUK_TAG_STRING) #endif #define DUK_TVAL_IS_HEAP_ALLOCATED(tv) ((tv)->t & 0x08) #if defined(DUK_USE_FASTINT) #if 0 DUK_INTERNAL_DECL duk_double_t duk_tval_get_number_unpacked(duk_tval *tv); #endif DUK_INTERNAL_DECL duk_double_t duk_tval_get_number_unpacked_fastint(duk_tval *tv); #endif #endif /* DUK_USE_PACKED_TVAL */ /* * Convenience (independent of representation) */ #define DUK_TVAL_SET_BOOLEAN_TRUE(tv) DUK_TVAL_SET_BOOLEAN((tv), 1) #define DUK_TVAL_SET_BOOLEAN_FALSE(tv) DUK_TVAL_SET_BOOLEAN((tv), 0) #define DUK_TVAL_STRING_IS_SYMBOL(tv) DUK_HSTRING_HAS_SYMBOL(DUK_TVAL_GET_STRING((tv))) /* Lightfunc flags packing and unpacking. */ /* Sign extend: 0x0000##00 -> 0x##000000 -> sign extend to 0xssssss##. * Avoid signed shifts due to portability limitations. */ #define DUK_LFUNC_FLAGS_GET_MAGIC(lf_flags) ((duk_int32_t) (duk_int8_t) (((duk_uint16_t) (lf_flags)) >> 8)) #define DUK_LFUNC_FLAGS_GET_LENGTH(lf_flags) (((lf_flags) >> 4) & 0x0fU) #define DUK_LFUNC_FLAGS_GET_NARGS(lf_flags) ((lf_flags) &0x0fU) #define DUK_LFUNC_FLAGS_PACK(magic, length, nargs) ((((duk_small_uint_t) (magic)) & 0xffU) << 8) | ((length) << 4) | (nargs) #define DUK_LFUNC_NARGS_VARARGS 0x0f /* varargs marker */ #define DUK_LFUNC_NARGS_MIN 0x00 #define DUK_LFUNC_NARGS_MAX 0x0e /* max, excl. varargs marker */ #define DUK_LFUNC_LENGTH_MIN 0x00 #define DUK_LFUNC_LENGTH_MAX 0x0f #define DUK_LFUNC_MAGIC_MIN (-0x80) #define DUK_LFUNC_MAGIC_MAX 0x7f /* fastint constants etc */ #if defined(DUK_USE_FASTINT) #define DUK_FASTINT_MIN (DUK_I64_CONSTANT(-0x800000000000)) #define DUK_FASTINT_MAX (DUK_I64_CONSTANT(0x7fffffffffff)) #define DUK_FASTINT_BITS 48 DUK_INTERNAL_DECL void duk_tval_set_number_chkfast_fast(duk_tval *tv, duk_double_t x); DUK_INTERNAL_DECL void duk_tval_set_number_chkfast_slow(duk_tval *tv, duk_double_t x); #endif #if defined(DUK_USE_ASSERTIONS) DUK_INTERNAL_DECL void duk_tval_assert_valid(duk_tval *tv); #define DUK_TVAL_ASSERT_VALID(tv) \ do { \ duk_tval_assert_valid((tv)); \ } while (0) #else #define DUK_TVAL_ASSERT_VALID(tv) \ do { \ } while (0) #endif #endif /* DUK_TVAL_H_INCLUDED */ /* #include duk_builtins.h */ #line 1 "duk_builtins.h" /* * Automatically generated by genbuiltins.py, do not edit! */ #if !defined(DUK_BUILTINS_H_INCLUDED) #define DUK_BUILTINS_H_INCLUDED #if defined(DUK_USE_ROM_STRINGS) #error ROM support not enabled, rerun configure.py with --rom-support #else /* DUK_USE_ROM_STRINGS */ #define DUK_STRIDX_UC_UNDEFINED 0 /* 'Undefined' */ #define DUK_HEAP_STRING_UC_UNDEFINED(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_UNDEFINED) #define DUK_HTHREAD_STRING_UC_UNDEFINED(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_UNDEFINED) #define DUK_STRIDX_UC_NULL 1 /* 'Null' */ #define DUK_HEAP_STRING_UC_NULL(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_NULL) #define DUK_HTHREAD_STRING_UC_NULL(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_NULL) #define DUK_STRIDX_UC_SYMBOL 2 /* 'Symbol' */ #define DUK_HEAP_STRING_UC_SYMBOL(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_SYMBOL) #define DUK_HTHREAD_STRING_UC_SYMBOL(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_SYMBOL) #define DUK_STRIDX_UC_ARGUMENTS 3 /* 'Arguments' */ #define DUK_HEAP_STRING_UC_ARGUMENTS(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_ARGUMENTS) #define DUK_HTHREAD_STRING_UC_ARGUMENTS(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_ARGUMENTS) #define DUK_STRIDX_UC_OBJECT 4 /* 'Object' */ #define DUK_HEAP_STRING_UC_OBJECT(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_OBJECT) #define DUK_HTHREAD_STRING_UC_OBJECT(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_OBJECT) #define DUK_STRIDX_UC_FUNCTION 5 /* 'Function' */ #define DUK_HEAP_STRING_UC_FUNCTION(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_FUNCTION) #define DUK_HTHREAD_STRING_UC_FUNCTION(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_FUNCTION) #define DUK_STRIDX_UC_ARRAY 6 /* 'Array' */ #define DUK_HEAP_STRING_UC_ARRAY(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_ARRAY) #define DUK_HTHREAD_STRING_UC_ARRAY(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_ARRAY) #define DUK_STRIDX_UC_STRING 7 /* 'String' */ #define DUK_HEAP_STRING_UC_STRING(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_STRING) #define DUK_HTHREAD_STRING_UC_STRING(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_STRING) #define DUK_STRIDX_UC_BOOLEAN 8 /* 'Boolean' */ #define DUK_HEAP_STRING_UC_BOOLEAN(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_BOOLEAN) #define DUK_HTHREAD_STRING_UC_BOOLEAN(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_BOOLEAN) #define DUK_STRIDX_UC_NUMBER 9 /* 'Number' */ #define DUK_HEAP_STRING_UC_NUMBER(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_NUMBER) #define DUK_HTHREAD_STRING_UC_NUMBER(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_NUMBER) #define DUK_STRIDX_UC_DATE 10 /* 'Date' */ #define DUK_HEAP_STRING_UC_DATE(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_DATE) #define DUK_HTHREAD_STRING_UC_DATE(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_DATE) #define DUK_STRIDX_REG_EXP 11 /* 'RegExp' */ #define DUK_HEAP_STRING_REG_EXP(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_REG_EXP) #define DUK_HTHREAD_STRING_REG_EXP(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_REG_EXP) #define DUK_STRIDX_UC_ERROR 12 /* 'Error' */ #define DUK_HEAP_STRING_UC_ERROR(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_ERROR) #define DUK_HTHREAD_STRING_UC_ERROR(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_ERROR) #define DUK_STRIDX_MATH 13 /* 'Math' */ #define DUK_HEAP_STRING_MATH(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_MATH) #define DUK_HTHREAD_STRING_MATH(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_MATH) #define DUK_STRIDX_JSON 14 /* 'JSON' */ #define DUK_HEAP_STRING_JSON(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_JSON) #define DUK_HTHREAD_STRING_JSON(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_JSON) #define DUK_STRIDX_EMPTY_STRING 15 /* '' */ #define DUK_HEAP_STRING_EMPTY_STRING(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_EMPTY_STRING) #define DUK_HTHREAD_STRING_EMPTY_STRING(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_EMPTY_STRING) #define DUK_STRIDX_ARRAY_BUFFER 16 /* 'ArrayBuffer' */ #define DUK_HEAP_STRING_ARRAY_BUFFER(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ARRAY_BUFFER) #define DUK_HTHREAD_STRING_ARRAY_BUFFER(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ARRAY_BUFFER) #define DUK_STRIDX_DATA_VIEW 17 /* 'DataView' */ #define DUK_HEAP_STRING_DATA_VIEW(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DATA_VIEW) #define DUK_HTHREAD_STRING_DATA_VIEW(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DATA_VIEW) #define DUK_STRIDX_INT8_ARRAY 18 /* 'Int8Array' */ #define DUK_HEAP_STRING_INT8_ARRAY(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT8_ARRAY) #define DUK_HTHREAD_STRING_INT8_ARRAY(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT8_ARRAY) #define DUK_STRIDX_UINT8_ARRAY 19 /* 'Uint8Array' */ #define DUK_HEAP_STRING_UINT8_ARRAY(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UINT8_ARRAY) #define DUK_HTHREAD_STRING_UINT8_ARRAY(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UINT8_ARRAY) #define DUK_STRIDX_UINT8_CLAMPED_ARRAY 20 /* 'Uint8ClampedArray' */ #define DUK_HEAP_STRING_UINT8_CLAMPED_ARRAY(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UINT8_CLAMPED_ARRAY) #define DUK_HTHREAD_STRING_UINT8_CLAMPED_ARRAY(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UINT8_CLAMPED_ARRAY) #define DUK_STRIDX_INT16_ARRAY 21 /* 'Int16Array' */ #define DUK_HEAP_STRING_INT16_ARRAY(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT16_ARRAY) #define DUK_HTHREAD_STRING_INT16_ARRAY(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT16_ARRAY) #define DUK_STRIDX_UINT16_ARRAY 22 /* 'Uint16Array' */ #define DUK_HEAP_STRING_UINT16_ARRAY(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UINT16_ARRAY) #define DUK_HTHREAD_STRING_UINT16_ARRAY(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UINT16_ARRAY) #define DUK_STRIDX_INT32_ARRAY 23 /* 'Int32Array' */ #define DUK_HEAP_STRING_INT32_ARRAY(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT32_ARRAY) #define DUK_HTHREAD_STRING_INT32_ARRAY(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT32_ARRAY) #define DUK_STRIDX_UINT32_ARRAY 24 /* 'Uint32Array' */ #define DUK_HEAP_STRING_UINT32_ARRAY(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UINT32_ARRAY) #define DUK_HTHREAD_STRING_UINT32_ARRAY(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UINT32_ARRAY) #define DUK_STRIDX_FLOAT32_ARRAY 25 /* 'Float32Array' */ #define DUK_HEAP_STRING_FLOAT32_ARRAY(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_FLOAT32_ARRAY) #define DUK_HTHREAD_STRING_FLOAT32_ARRAY(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_FLOAT32_ARRAY) #define DUK_STRIDX_FLOAT64_ARRAY 26 /* 'Float64Array' */ #define DUK_HEAP_STRING_FLOAT64_ARRAY(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_FLOAT64_ARRAY) #define DUK_HTHREAD_STRING_FLOAT64_ARRAY(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_FLOAT64_ARRAY) #define DUK_STRIDX_GLOBAL 27 /* 'global' */ #define DUK_HEAP_STRING_GLOBAL(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GLOBAL) #define DUK_HTHREAD_STRING_GLOBAL(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GLOBAL) #define DUK_STRIDX_OBJ_ENV 28 /* 'ObjEnv' */ #define DUK_HEAP_STRING_OBJ_ENV(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_OBJ_ENV) #define DUK_HTHREAD_STRING_OBJ_ENV(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_OBJ_ENV) #define DUK_STRIDX_DEC_ENV 29 /* 'DecEnv' */ #define DUK_HEAP_STRING_DEC_ENV(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DEC_ENV) #define DUK_HTHREAD_STRING_DEC_ENV(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DEC_ENV) #define DUK_STRIDX_UC_BUFFER 30 /* 'Buffer' */ #define DUK_HEAP_STRING_UC_BUFFER(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_BUFFER) #define DUK_HTHREAD_STRING_UC_BUFFER(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_BUFFER) #define DUK_STRIDX_UC_POINTER 31 /* 'Pointer' */ #define DUK_HEAP_STRING_UC_POINTER(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_POINTER) #define DUK_HTHREAD_STRING_UC_POINTER(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_POINTER) #define DUK_STRIDX_UC_THREAD 32 /* 'Thread' */ #define DUK_HEAP_STRING_UC_THREAD(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_THREAD) #define DUK_HTHREAD_STRING_UC_THREAD(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_THREAD) #define DUK_STRIDX_EVAL 33 /* 'eval' */ #define DUK_HEAP_STRING_EVAL(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_EVAL) #define DUK_HTHREAD_STRING_EVAL(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_EVAL) #define DUK_STRIDX_VALUE 34 /* 'value' */ #define DUK_HEAP_STRING_VALUE(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_VALUE) #define DUK_HTHREAD_STRING_VALUE(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_VALUE) #define DUK_STRIDX_WRITABLE 35 /* 'writable' */ #define DUK_HEAP_STRING_WRITABLE(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_WRITABLE) #define DUK_HTHREAD_STRING_WRITABLE(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_WRITABLE) #define DUK_STRIDX_CONFIGURABLE 36 /* 'configurable' */ #define DUK_HEAP_STRING_CONFIGURABLE(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CONFIGURABLE) #define DUK_HTHREAD_STRING_CONFIGURABLE(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CONFIGURABLE) #define DUK_STRIDX_ENUMERABLE 37 /* 'enumerable' */ #define DUK_HEAP_STRING_ENUMERABLE(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ENUMERABLE) #define DUK_HTHREAD_STRING_ENUMERABLE(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ENUMERABLE) #define DUK_STRIDX_JOIN 38 /* 'join' */ #define DUK_HEAP_STRING_JOIN(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_JOIN) #define DUK_HTHREAD_STRING_JOIN(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_JOIN) #define DUK_STRIDX_TO_LOCALE_STRING 39 /* 'toLocaleString' */ #define DUK_HEAP_STRING_TO_LOCALE_STRING(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_LOCALE_STRING) #define DUK_HTHREAD_STRING_TO_LOCALE_STRING(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_LOCALE_STRING) #define DUK_STRIDX_VALUE_OF 40 /* 'valueOf' */ #define DUK_HEAP_STRING_VALUE_OF(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_VALUE_OF) #define DUK_HTHREAD_STRING_VALUE_OF(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_VALUE_OF) #define DUK_STRIDX_TO_UTC_STRING 41 /* 'toUTCString' */ #define DUK_HEAP_STRING_TO_UTC_STRING(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_UTC_STRING) #define DUK_HTHREAD_STRING_TO_UTC_STRING(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_UTC_STRING) #define DUK_STRIDX_TO_ISO_STRING 42 /* 'toISOString' */ #define DUK_HEAP_STRING_TO_ISO_STRING(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_ISO_STRING) #define DUK_HTHREAD_STRING_TO_ISO_STRING(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_ISO_STRING) #define DUK_STRIDX_TO_GMT_STRING 43 /* 'toGMTString' */ #define DUK_HEAP_STRING_TO_GMT_STRING(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_GMT_STRING) #define DUK_HTHREAD_STRING_TO_GMT_STRING(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_GMT_STRING) #define DUK_STRIDX_SOURCE 44 /* 'source' */ #define DUK_HEAP_STRING_SOURCE(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SOURCE) #define DUK_HTHREAD_STRING_SOURCE(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SOURCE) #define DUK_STRIDX_IGNORE_CASE 45 /* 'ignoreCase' */ #define DUK_HEAP_STRING_IGNORE_CASE(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_IGNORE_CASE) #define DUK_HTHREAD_STRING_IGNORE_CASE(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_IGNORE_CASE) #define DUK_STRIDX_MULTILINE 46 /* 'multiline' */ #define DUK_HEAP_STRING_MULTILINE(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_MULTILINE) #define DUK_HTHREAD_STRING_MULTILINE(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_MULTILINE) #define DUK_STRIDX_LAST_INDEX 47 /* 'lastIndex' */ #define DUK_HEAP_STRING_LAST_INDEX(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LAST_INDEX) #define DUK_HTHREAD_STRING_LAST_INDEX(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LAST_INDEX) #define DUK_STRIDX_FLAGS 48 /* 'flags' */ #define DUK_HEAP_STRING_FLAGS(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_FLAGS) #define DUK_HTHREAD_STRING_FLAGS(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_FLAGS) #define DUK_STRIDX_INDEX 49 /* 'index' */ #define DUK_HEAP_STRING_INDEX(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INDEX) #define DUK_HTHREAD_STRING_INDEX(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INDEX) #define DUK_STRIDX_PROTOTYPE 50 /* 'prototype' */ #define DUK_HEAP_STRING_PROTOTYPE(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PROTOTYPE) #define DUK_HTHREAD_STRING_PROTOTYPE(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PROTOTYPE) #define DUK_STRIDX_CONSTRUCTOR 51 /* 'constructor' */ #define DUK_HEAP_STRING_CONSTRUCTOR(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CONSTRUCTOR) #define DUK_HTHREAD_STRING_CONSTRUCTOR(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CONSTRUCTOR) #define DUK_STRIDX_MESSAGE 52 /* 'message' */ #define DUK_HEAP_STRING_MESSAGE(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_MESSAGE) #define DUK_HTHREAD_STRING_MESSAGE(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_MESSAGE) #define DUK_STRIDX_LC_BOOLEAN 53 /* 'boolean' */ #define DUK_HEAP_STRING_LC_BOOLEAN(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_BOOLEAN) #define DUK_HTHREAD_STRING_LC_BOOLEAN(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_BOOLEAN) #define DUK_STRIDX_LC_NUMBER 54 /* 'number' */ #define DUK_HEAP_STRING_LC_NUMBER(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_NUMBER) #define DUK_HTHREAD_STRING_LC_NUMBER(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_NUMBER) #define DUK_STRIDX_LC_STRING 55 /* 'string' */ #define DUK_HEAP_STRING_LC_STRING(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_STRING) #define DUK_HTHREAD_STRING_LC_STRING(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_STRING) #define DUK_STRIDX_LC_SYMBOL 56 /* 'symbol' */ #define DUK_HEAP_STRING_LC_SYMBOL(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_SYMBOL) #define DUK_HTHREAD_STRING_LC_SYMBOL(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_SYMBOL) #define DUK_STRIDX_LC_OBJECT 57 /* 'object' */ #define DUK_HEAP_STRING_LC_OBJECT(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_OBJECT) #define DUK_HTHREAD_STRING_LC_OBJECT(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_OBJECT) #define DUK_STRIDX_LC_UNDEFINED 58 /* 'undefined' */ #define DUK_HEAP_STRING_LC_UNDEFINED(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_UNDEFINED) #define DUK_HTHREAD_STRING_LC_UNDEFINED(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_UNDEFINED) #define DUK_STRIDX_NAN 59 /* 'NaN' */ #define DUK_HEAP_STRING_NAN(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_NAN) #define DUK_HTHREAD_STRING_NAN(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_NAN) #define DUK_STRIDX_INFINITY 60 /* 'Infinity' */ #define DUK_HEAP_STRING_INFINITY(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INFINITY) #define DUK_HTHREAD_STRING_INFINITY(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INFINITY) #define DUK_STRIDX_MINUS_INFINITY 61 /* '-Infinity' */ #define DUK_HEAP_STRING_MINUS_INFINITY(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_MINUS_INFINITY) #define DUK_HTHREAD_STRING_MINUS_INFINITY(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_MINUS_INFINITY) #define DUK_STRIDX_MINUS_ZERO 62 /* '-0' */ #define DUK_HEAP_STRING_MINUS_ZERO(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_MINUS_ZERO) #define DUK_HTHREAD_STRING_MINUS_ZERO(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_MINUS_ZERO) #define DUK_STRIDX_COMMA 63 /* ',' */ #define DUK_HEAP_STRING_COMMA(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_COMMA) #define DUK_HTHREAD_STRING_COMMA(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_COMMA) #define DUK_STRIDX_NEWLINE_4SPACE 64 /* '\n ' */ #define DUK_HEAP_STRING_NEWLINE_4SPACE(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_NEWLINE_4SPACE) #define DUK_HTHREAD_STRING_NEWLINE_4SPACE(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_NEWLINE_4SPACE) #define DUK_STRIDX_BRACKETED_ELLIPSIS 65 /* '[...]' */ #define DUK_HEAP_STRING_BRACKETED_ELLIPSIS(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_BRACKETED_ELLIPSIS) #define DUK_HTHREAD_STRING_BRACKETED_ELLIPSIS(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_BRACKETED_ELLIPSIS) #define DUK_STRIDX_INVALID_DATE 66 /* 'Invalid Date' */ #define DUK_HEAP_STRING_INVALID_DATE(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INVALID_DATE) #define DUK_HTHREAD_STRING_INVALID_DATE(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INVALID_DATE) #define DUK_STRIDX_LC_ARGUMENTS 67 /* 'arguments' */ #define DUK_HEAP_STRING_LC_ARGUMENTS(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_ARGUMENTS) #define DUK_HTHREAD_STRING_LC_ARGUMENTS(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_ARGUMENTS) #define DUK_STRIDX_CALLEE 68 /* 'callee' */ #define DUK_HEAP_STRING_CALLEE(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CALLEE) #define DUK_HTHREAD_STRING_CALLEE(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CALLEE) #define DUK_STRIDX_CALLER 69 /* 'caller' */ #define DUK_HEAP_STRING_CALLER(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CALLER) #define DUK_HTHREAD_STRING_CALLER(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CALLER) #define DUK_STRIDX_APPLY 70 /* 'apply' */ #define DUK_HEAP_STRING_APPLY(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_APPLY) #define DUK_HTHREAD_STRING_APPLY(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_APPLY) #define DUK_STRIDX_CONSTRUCT 71 /* 'construct' */ #define DUK_HEAP_STRING_CONSTRUCT(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CONSTRUCT) #define DUK_HTHREAD_STRING_CONSTRUCT(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CONSTRUCT) #define DUK_STRIDX_DELETE_PROPERTY 72 /* 'deleteProperty' */ #define DUK_HEAP_STRING_DELETE_PROPERTY(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DELETE_PROPERTY) #define DUK_HTHREAD_STRING_DELETE_PROPERTY(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DELETE_PROPERTY) #define DUK_STRIDX_GET 73 /* 'get' */ #define DUK_HEAP_STRING_GET(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET) #define DUK_HTHREAD_STRING_GET(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET) #define DUK_STRIDX_HAS 74 /* 'has' */ #define DUK_HEAP_STRING_HAS(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_HAS) #define DUK_HTHREAD_STRING_HAS(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_HAS) #define DUK_STRIDX_OWN_KEYS 75 /* 'ownKeys' */ #define DUK_HEAP_STRING_OWN_KEYS(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_OWN_KEYS) #define DUK_HTHREAD_STRING_OWN_KEYS(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_OWN_KEYS) #define DUK_STRIDX_WELLKNOWN_SYMBOL_TO_PRIMITIVE 76 /* '\x81Symbol.toPrimitive\xff' */ #define DUK_HEAP_STRING_WELLKNOWN_SYMBOL_TO_PRIMITIVE(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_WELLKNOWN_SYMBOL_TO_PRIMITIVE) #define DUK_HTHREAD_STRING_WELLKNOWN_SYMBOL_TO_PRIMITIVE(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_WELLKNOWN_SYMBOL_TO_PRIMITIVE) #define DUK_STRIDX_WELLKNOWN_SYMBOL_HAS_INSTANCE 77 /* '\x81Symbol.hasInstance\xff' */ #define DUK_HEAP_STRING_WELLKNOWN_SYMBOL_HAS_INSTANCE(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_WELLKNOWN_SYMBOL_HAS_INSTANCE) #define DUK_HTHREAD_STRING_WELLKNOWN_SYMBOL_HAS_INSTANCE(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_WELLKNOWN_SYMBOL_HAS_INSTANCE) #define DUK_STRIDX_WELLKNOWN_SYMBOL_TO_STRING_TAG 78 /* '\x81Symbol.toStringTag\xff' */ #define DUK_HEAP_STRING_WELLKNOWN_SYMBOL_TO_STRING_TAG(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_WELLKNOWN_SYMBOL_TO_STRING_TAG) #define DUK_HTHREAD_STRING_WELLKNOWN_SYMBOL_TO_STRING_TAG(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_WELLKNOWN_SYMBOL_TO_STRING_TAG) #define DUK_STRIDX_WELLKNOWN_SYMBOL_IS_CONCAT_SPREADABLE 79 /* '\x81Symbol.isConcatSpreadable\xff' */ #define DUK_HEAP_STRING_WELLKNOWN_SYMBOL_IS_CONCAT_SPREADABLE(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_WELLKNOWN_SYMBOL_IS_CONCAT_SPREADABLE) #define DUK_HTHREAD_STRING_WELLKNOWN_SYMBOL_IS_CONCAT_SPREADABLE(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_WELLKNOWN_SYMBOL_IS_CONCAT_SPREADABLE) #define DUK_STRIDX_SET_PROTOTYPE_OF 80 /* 'setPrototypeOf' */ #define DUK_HEAP_STRING_SET_PROTOTYPE_OF(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_PROTOTYPE_OF) #define DUK_HTHREAD_STRING_SET_PROTOTYPE_OF(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_PROTOTYPE_OF) #define DUK_STRIDX___PROTO__ 81 /* '__proto__' */ #define DUK_HEAP_STRING___PROTO__(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX___PROTO__) #define DUK_HTHREAD_STRING___PROTO__(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX___PROTO__) #define DUK_STRIDX_TO_STRING 82 /* 'toString' */ #define DUK_HEAP_STRING_TO_STRING(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_STRING) #define DUK_HTHREAD_STRING_TO_STRING(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_STRING) #define DUK_STRIDX_TO_JSON 83 /* 'toJSON' */ #define DUK_HEAP_STRING_TO_JSON(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_JSON) #define DUK_HTHREAD_STRING_TO_JSON(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_JSON) #define DUK_STRIDX_TYPE 84 /* 'type' */ #define DUK_HEAP_STRING_TYPE(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TYPE) #define DUK_HTHREAD_STRING_TYPE(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TYPE) #define DUK_STRIDX_DATA 85 /* 'data' */ #define DUK_HEAP_STRING_DATA(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DATA) #define DUK_HTHREAD_STRING_DATA(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DATA) #define DUK_STRIDX_LC_BUFFER 86 /* 'buffer' */ #define DUK_HEAP_STRING_LC_BUFFER(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_BUFFER) #define DUK_HTHREAD_STRING_LC_BUFFER(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_BUFFER) #define DUK_STRIDX_LENGTH 87 /* 'length' */ #define DUK_HEAP_STRING_LENGTH(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LENGTH) #define DUK_HTHREAD_STRING_LENGTH(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LENGTH) #define DUK_STRIDX_SET 88 /* 'set' */ #define DUK_HEAP_STRING_SET(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET) #define DUK_HTHREAD_STRING_SET(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET) #define DUK_STRIDX_STACK 89 /* 'stack' */ #define DUK_HEAP_STRING_STACK(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_STACK) #define DUK_HTHREAD_STRING_STACK(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_STACK) #define DUK_STRIDX_PC 90 /* 'pc' */ #define DUK_HEAP_STRING_PC(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PC) #define DUK_HTHREAD_STRING_PC(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PC) #define DUK_STRIDX_LINE_NUMBER 91 /* 'lineNumber' */ #define DUK_HEAP_STRING_LINE_NUMBER(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LINE_NUMBER) #define DUK_HTHREAD_STRING_LINE_NUMBER(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LINE_NUMBER) #define DUK_STRIDX_INT_TRACEDATA 92 /* '\x82Tracedata' */ #define DUK_HEAP_STRING_INT_TRACEDATA(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_TRACEDATA) #define DUK_HTHREAD_STRING_INT_TRACEDATA(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_TRACEDATA) #define DUK_STRIDX_NAME 93 /* 'name' */ #define DUK_HEAP_STRING_NAME(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_NAME) #define DUK_HTHREAD_STRING_NAME(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_NAME) #define DUK_STRIDX_FILE_NAME 94 /* 'fileName' */ #define DUK_HEAP_STRING_FILE_NAME(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_FILE_NAME) #define DUK_HTHREAD_STRING_FILE_NAME(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_FILE_NAME) #define DUK_STRIDX_LC_POINTER 95 /* 'pointer' */ #define DUK_HEAP_STRING_LC_POINTER(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_POINTER) #define DUK_HTHREAD_STRING_LC_POINTER(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_POINTER) #define DUK_STRIDX_INT_TARGET 96 /* '\x82Target' */ #define DUK_HEAP_STRING_INT_TARGET(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_TARGET) #define DUK_HTHREAD_STRING_INT_TARGET(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_TARGET) #define DUK_STRIDX_INT_NEXT 97 /* '\x82Next' */ #define DUK_HEAP_STRING_INT_NEXT(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_NEXT) #define DUK_HTHREAD_STRING_INT_NEXT(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_NEXT) #define DUK_STRIDX_INT_BYTECODE 98 /* '\x82Bytecode' */ #define DUK_HEAP_STRING_INT_BYTECODE(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_BYTECODE) #define DUK_HTHREAD_STRING_INT_BYTECODE(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_BYTECODE) #define DUK_STRIDX_INT_FORMALS 99 /* '\x82Formals' */ #define DUK_HEAP_STRING_INT_FORMALS(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_FORMALS) #define DUK_HTHREAD_STRING_INT_FORMALS(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_FORMALS) #define DUK_STRIDX_INT_VARMAP 100 /* '\x82Varmap' */ #define DUK_HEAP_STRING_INT_VARMAP(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_VARMAP) #define DUK_HTHREAD_STRING_INT_VARMAP(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_VARMAP) #define DUK_STRIDX_INT_SOURCE 101 /* '\x82Source' */ #define DUK_HEAP_STRING_INT_SOURCE(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_SOURCE) #define DUK_HTHREAD_STRING_INT_SOURCE(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_SOURCE) #define DUK_STRIDX_INT_PC2LINE 102 /* '\x82Pc2line' */ #define DUK_HEAP_STRING_INT_PC2LINE(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_PC2LINE) #define DUK_HTHREAD_STRING_INT_PC2LINE(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_PC2LINE) #define DUK_STRIDX_INT_MAP 103 /* '\x82Map' */ #define DUK_HEAP_STRING_INT_MAP(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_MAP) #define DUK_HTHREAD_STRING_INT_MAP(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_MAP) #define DUK_STRIDX_INT_VARENV 104 /* '\x82Varenv' */ #define DUK_HEAP_STRING_INT_VARENV(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_VARENV) #define DUK_HTHREAD_STRING_INT_VARENV(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_VARENV) #define DUK_STRIDX_INT_FINALIZER 105 /* '\x82Finalizer' */ #define DUK_HEAP_STRING_INT_FINALIZER(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_FINALIZER) #define DUK_HTHREAD_STRING_INT_FINALIZER(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_FINALIZER) #define DUK_STRIDX_INT_VALUE 106 /* '\x82Value' */ #define DUK_HEAP_STRING_INT_VALUE(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_VALUE) #define DUK_HTHREAD_STRING_INT_VALUE(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_VALUE) #define DUK_STRIDX_COMPILE 107 /* 'compile' */ #define DUK_HEAP_STRING_COMPILE(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_COMPILE) #define DUK_HTHREAD_STRING_COMPILE(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_COMPILE) #define DUK_STRIDX_INPUT 108 /* 'input' */ #define DUK_HEAP_STRING_INPUT(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INPUT) #define DUK_HTHREAD_STRING_INPUT(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INPUT) #define DUK_STRIDX_ERR_CREATE 109 /* 'errCreate' */ #define DUK_HEAP_STRING_ERR_CREATE(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ERR_CREATE) #define DUK_HTHREAD_STRING_ERR_CREATE(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ERR_CREATE) #define DUK_STRIDX_ERR_THROW 110 /* 'errThrow' */ #define DUK_HEAP_STRING_ERR_THROW(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ERR_THROW) #define DUK_HTHREAD_STRING_ERR_THROW(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ERR_THROW) #define DUK_STRIDX_ENV 111 /* 'env' */ #define DUK_HEAP_STRING_ENV(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ENV) #define DUK_HTHREAD_STRING_ENV(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ENV) #define DUK_STRIDX_HEX 112 /* 'hex' */ #define DUK_HEAP_STRING_HEX(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_HEX) #define DUK_HTHREAD_STRING_HEX(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_HEX) #define DUK_STRIDX_BASE64 113 /* 'base64' */ #define DUK_HEAP_STRING_BASE64(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_BASE64) #define DUK_HTHREAD_STRING_BASE64(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_BASE64) #define DUK_STRIDX_JX 114 /* 'jx' */ #define DUK_HEAP_STRING_JX(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_JX) #define DUK_HTHREAD_STRING_JX(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_JX) #define DUK_STRIDX_JC 115 /* 'jc' */ #define DUK_HEAP_STRING_JC(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_JC) #define DUK_HTHREAD_STRING_JC(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_JC) #define DUK_STRIDX_JSON_EXT_UNDEFINED 116 /* '{"_undef":true}' */ #define DUK_HEAP_STRING_JSON_EXT_UNDEFINED(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_JSON_EXT_UNDEFINED) #define DUK_HTHREAD_STRING_JSON_EXT_UNDEFINED(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_JSON_EXT_UNDEFINED) #define DUK_STRIDX_JSON_EXT_NAN 117 /* '{"_nan":true}' */ #define DUK_HEAP_STRING_JSON_EXT_NAN(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_JSON_EXT_NAN) #define DUK_HTHREAD_STRING_JSON_EXT_NAN(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_JSON_EXT_NAN) #define DUK_STRIDX_JSON_EXT_POSINF 118 /* '{"_inf":true}' */ #define DUK_HEAP_STRING_JSON_EXT_POSINF(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_JSON_EXT_POSINF) #define DUK_HTHREAD_STRING_JSON_EXT_POSINF(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_JSON_EXT_POSINF) #define DUK_STRIDX_JSON_EXT_NEGINF 119 /* '{"_ninf":true}' */ #define DUK_HEAP_STRING_JSON_EXT_NEGINF(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_JSON_EXT_NEGINF) #define DUK_HTHREAD_STRING_JSON_EXT_NEGINF(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_JSON_EXT_NEGINF) #define DUK_STRIDX_JSON_EXT_FUNCTION1 120 /* '{"_func":true}' */ #define DUK_HEAP_STRING_JSON_EXT_FUNCTION1(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_JSON_EXT_FUNCTION1) #define DUK_HTHREAD_STRING_JSON_EXT_FUNCTION1(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_JSON_EXT_FUNCTION1) #define DUK_STRIDX_JSON_EXT_FUNCTION2 121 /* '{_func:true}' */ #define DUK_HEAP_STRING_JSON_EXT_FUNCTION2(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_JSON_EXT_FUNCTION2) #define DUK_HTHREAD_STRING_JSON_EXT_FUNCTION2(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_JSON_EXT_FUNCTION2) #define DUK_STRIDX_BREAK 122 /* 'break' */ #define DUK_HEAP_STRING_BREAK(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_BREAK) #define DUK_HTHREAD_STRING_BREAK(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_BREAK) #define DUK_STRIDX_CASE 123 /* 'case' */ #define DUK_HEAP_STRING_CASE(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CASE) #define DUK_HTHREAD_STRING_CASE(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CASE) #define DUK_STRIDX_CATCH 124 /* 'catch' */ #define DUK_HEAP_STRING_CATCH(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CATCH) #define DUK_HTHREAD_STRING_CATCH(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CATCH) #define DUK_STRIDX_CONTINUE 125 /* 'continue' */ #define DUK_HEAP_STRING_CONTINUE(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CONTINUE) #define DUK_HTHREAD_STRING_CONTINUE(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CONTINUE) #define DUK_STRIDX_DEBUGGER 126 /* 'debugger' */ #define DUK_HEAP_STRING_DEBUGGER(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DEBUGGER) #define DUK_HTHREAD_STRING_DEBUGGER(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DEBUGGER) #define DUK_STRIDX_DEFAULT 127 /* 'default' */ #define DUK_HEAP_STRING_DEFAULT(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DEFAULT) #define DUK_HTHREAD_STRING_DEFAULT(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DEFAULT) #define DUK_STRIDX_DELETE 128 /* 'delete' */ #define DUK_HEAP_STRING_DELETE(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DELETE) #define DUK_HTHREAD_STRING_DELETE(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DELETE) #define DUK_STRIDX_DO 129 /* 'do' */ #define DUK_HEAP_STRING_DO(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DO) #define DUK_HTHREAD_STRING_DO(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DO) #define DUK_STRIDX_ELSE 130 /* 'else' */ #define DUK_HEAP_STRING_ELSE(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ELSE) #define DUK_HTHREAD_STRING_ELSE(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ELSE) #define DUK_STRIDX_FINALLY 131 /* 'finally' */ #define DUK_HEAP_STRING_FINALLY(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_FINALLY) #define DUK_HTHREAD_STRING_FINALLY(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_FINALLY) #define DUK_STRIDX_FOR 132 /* 'for' */ #define DUK_HEAP_STRING_FOR(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_FOR) #define DUK_HTHREAD_STRING_FOR(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_FOR) #define DUK_STRIDX_LC_FUNCTION 133 /* 'function' */ #define DUK_HEAP_STRING_LC_FUNCTION(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_FUNCTION) #define DUK_HTHREAD_STRING_LC_FUNCTION(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_FUNCTION) #define DUK_STRIDX_IF 134 /* 'if' */ #define DUK_HEAP_STRING_IF(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_IF) #define DUK_HTHREAD_STRING_IF(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_IF) #define DUK_STRIDX_IN 135 /* 'in' */ #define DUK_HEAP_STRING_IN(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_IN) #define DUK_HTHREAD_STRING_IN(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_IN) #define DUK_STRIDX_INSTANCEOF 136 /* 'instanceof' */ #define DUK_HEAP_STRING_INSTANCEOF(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INSTANCEOF) #define DUK_HTHREAD_STRING_INSTANCEOF(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INSTANCEOF) #define DUK_STRIDX_NEW 137 /* 'new' */ #define DUK_HEAP_STRING_NEW(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_NEW) #define DUK_HTHREAD_STRING_NEW(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_NEW) #define DUK_STRIDX_RETURN 138 /* 'return' */ #define DUK_HEAP_STRING_RETURN(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_RETURN) #define DUK_HTHREAD_STRING_RETURN(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_RETURN) #define DUK_STRIDX_SWITCH 139 /* 'switch' */ #define DUK_HEAP_STRING_SWITCH(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SWITCH) #define DUK_HTHREAD_STRING_SWITCH(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SWITCH) #define DUK_STRIDX_THIS 140 /* 'this' */ #define DUK_HEAP_STRING_THIS(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_THIS) #define DUK_HTHREAD_STRING_THIS(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_THIS) #define DUK_STRIDX_THROW 141 /* 'throw' */ #define DUK_HEAP_STRING_THROW(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_THROW) #define DUK_HTHREAD_STRING_THROW(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_THROW) #define DUK_STRIDX_TRY 142 /* 'try' */ #define DUK_HEAP_STRING_TRY(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TRY) #define DUK_HTHREAD_STRING_TRY(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TRY) #define DUK_STRIDX_TYPEOF 143 /* 'typeof' */ #define DUK_HEAP_STRING_TYPEOF(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TYPEOF) #define DUK_HTHREAD_STRING_TYPEOF(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TYPEOF) #define DUK_STRIDX_VAR 144 /* 'var' */ #define DUK_HEAP_STRING_VAR(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_VAR) #define DUK_HTHREAD_STRING_VAR(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_VAR) #define DUK_STRIDX_CONST 145 /* 'const' */ #define DUK_HEAP_STRING_CONST(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CONST) #define DUK_HTHREAD_STRING_CONST(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CONST) #define DUK_STRIDX_VOID 146 /* 'void' */ #define DUK_HEAP_STRING_VOID(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_VOID) #define DUK_HTHREAD_STRING_VOID(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_VOID) #define DUK_STRIDX_WHILE 147 /* 'while' */ #define DUK_HEAP_STRING_WHILE(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_WHILE) #define DUK_HTHREAD_STRING_WHILE(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_WHILE) #define DUK_STRIDX_WITH 148 /* 'with' */ #define DUK_HEAP_STRING_WITH(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_WITH) #define DUK_HTHREAD_STRING_WITH(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_WITH) #define DUK_STRIDX_CLASS 149 /* 'class' */ #define DUK_HEAP_STRING_CLASS(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CLASS) #define DUK_HTHREAD_STRING_CLASS(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CLASS) #define DUK_STRIDX_ENUM 150 /* 'enum' */ #define DUK_HEAP_STRING_ENUM(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ENUM) #define DUK_HTHREAD_STRING_ENUM(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ENUM) #define DUK_STRIDX_EXPORT 151 /* 'export' */ #define DUK_HEAP_STRING_EXPORT(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_EXPORT) #define DUK_HTHREAD_STRING_EXPORT(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_EXPORT) #define DUK_STRIDX_EXTENDS 152 /* 'extends' */ #define DUK_HEAP_STRING_EXTENDS(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_EXTENDS) #define DUK_HTHREAD_STRING_EXTENDS(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_EXTENDS) #define DUK_STRIDX_IMPORT 153 /* 'import' */ #define DUK_HEAP_STRING_IMPORT(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_IMPORT) #define DUK_HTHREAD_STRING_IMPORT(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_IMPORT) #define DUK_STRIDX_SUPER 154 /* 'super' */ #define DUK_HEAP_STRING_SUPER(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SUPER) #define DUK_HTHREAD_STRING_SUPER(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SUPER) #define DUK_STRIDX_LC_NULL 155 /* 'null' */ #define DUK_HEAP_STRING_LC_NULL(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_NULL) #define DUK_HTHREAD_STRING_LC_NULL(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_NULL) #define DUK_STRIDX_TRUE 156 /* 'true' */ #define DUK_HEAP_STRING_TRUE(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TRUE) #define DUK_HTHREAD_STRING_TRUE(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TRUE) #define DUK_STRIDX_FALSE 157 /* 'false' */ #define DUK_HEAP_STRING_FALSE(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_FALSE) #define DUK_HTHREAD_STRING_FALSE(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_FALSE) #define DUK_STRIDX_IMPLEMENTS 158 /* 'implements' */ #define DUK_HEAP_STRING_IMPLEMENTS(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_IMPLEMENTS) #define DUK_HTHREAD_STRING_IMPLEMENTS(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_IMPLEMENTS) #define DUK_STRIDX_INTERFACE 159 /* 'interface' */ #define DUK_HEAP_STRING_INTERFACE(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INTERFACE) #define DUK_HTHREAD_STRING_INTERFACE(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INTERFACE) #define DUK_STRIDX_LET 160 /* 'let' */ #define DUK_HEAP_STRING_LET(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LET) #define DUK_HTHREAD_STRING_LET(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LET) #define DUK_STRIDX_PACKAGE 161 /* 'package' */ #define DUK_HEAP_STRING_PACKAGE(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PACKAGE) #define DUK_HTHREAD_STRING_PACKAGE(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PACKAGE) #define DUK_STRIDX_PRIVATE 162 /* 'private' */ #define DUK_HEAP_STRING_PRIVATE(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PRIVATE) #define DUK_HTHREAD_STRING_PRIVATE(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PRIVATE) #define DUK_STRIDX_PROTECTED 163 /* 'protected' */ #define DUK_HEAP_STRING_PROTECTED(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PROTECTED) #define DUK_HTHREAD_STRING_PROTECTED(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PROTECTED) #define DUK_STRIDX_PUBLIC 164 /* 'public' */ #define DUK_HEAP_STRING_PUBLIC(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PUBLIC) #define DUK_HTHREAD_STRING_PUBLIC(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PUBLIC) #define DUK_STRIDX_STATIC 165 /* 'static' */ #define DUK_HEAP_STRING_STATIC(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_STATIC) #define DUK_HTHREAD_STRING_STATIC(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_STATIC) #define DUK_STRIDX_YIELD 166 /* 'yield' */ #define DUK_HEAP_STRING_YIELD(heap) DUK_HEAP_GET_STRING((heap),DUK_STRIDX_YIELD) #define DUK_HTHREAD_STRING_YIELD(thr) DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_YIELD) #define DUK_HEAP_NUM_STRINGS 167 #define DUK_STRIDX_START_RESERVED 122 #define DUK_STRIDX_START_STRICT_RESERVED 158 #define DUK_STRIDX_END_RESERVED 167 /* exclusive endpoint */ /* To convert a heap stridx to a token number, subtract * DUK_STRIDX_START_RESERVED and add DUK_TOK_START_RESERVED. */ #if !defined(DUK_SINGLE_FILE) DUK_INTERNAL_DECL const duk_uint8_t duk_strings_data[972]; #endif /* !DUK_SINGLE_FILE */ #define DUK_STRDATA_MAX_STRLEN 27 #define DUK_STRDATA_DATA_LENGTH 972 #endif /* DUK_USE_ROM_STRINGS */ #if defined(DUK_USE_ROM_OBJECTS) #error RAM support not enabled, rerun configure.py with --ram-support #else /* DUK_USE_ROM_OBJECTS */ DUK_INTERNAL_DECL duk_ret_t duk_bi_object_constructor(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_function_constructor(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_function_prototype(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_array_constructor(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_string_constructor(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_boolean_constructor(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_number_constructor(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_date_constructor(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_regexp_constructor(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_error_constructor_shared(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_type_error_thrower(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_global_object_parse_int(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_global_object_parse_float(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_thread_constructor(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_pointer_constructor(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_proxy_constructor(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_symbol_constructor_shared(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_arraybuffer_constructor(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_dataview_constructor(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_typedarray_constructor(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_nodejs_buffer_constructor(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_textencoder_constructor(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_textdecoder_constructor(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_global_object_eval(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_global_object_is_nan(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_global_object_is_finite(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_global_object_decode_uri(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_global_object_decode_uri_component(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_global_object_encode_uri(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_global_object_encode_uri_component(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_global_object_escape(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_global_object_unescape(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_object_getprototype_shared(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_object_setprototype_shared(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_object_constructor_get_own_property_descriptor(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_object_constructor_keys_shared(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_object_constructor_assign(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_object_constructor_create(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_object_constructor_define_property(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_object_constructor_define_properties(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_object_constructor_seal_freeze_shared(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_object_constructor_prevent_extensions(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_object_constructor_is_sealed_frozen_shared(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_object_constructor_is_extensible(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_object_constructor_is(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_object_prototype_to_string(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_object_prototype_to_locale_string(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_object_prototype_value_of(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_object_prototype_has_own_property(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_object_prototype_is_prototype_of(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_object_prototype_property_is_enumerable(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_object_prototype_defineaccessor(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_object_prototype_lookupaccessor(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_function_prototype_to_string(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_function_prototype_apply(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_function_prototype_call(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_function_prototype_bind(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_function_prototype_hasinstance(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_native_function_length(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_native_function_name(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_array_constructor_is_array(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_array_prototype_to_string(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_array_prototype_join_shared(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_array_prototype_concat(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_array_prototype_pop(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_array_prototype_push(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_array_prototype_reverse(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_array_prototype_shift(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_array_prototype_slice(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_array_prototype_sort(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_array_prototype_splice(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_array_prototype_unshift(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_array_prototype_indexof_shared(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_array_prototype_iter_shared(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_array_prototype_reduce_shared(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_string_constructor_from_char_code(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_string_constructor_from_code_point(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_string_prototype_to_string(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_string_prototype_char_at(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_string_prototype_char_code_at(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_string_prototype_concat(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_string_prototype_indexof_shared(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_string_prototype_locale_compare(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_string_prototype_match(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_string_prototype_replace(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_string_prototype_search(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_string_prototype_slice(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_string_prototype_split(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_string_prototype_substring(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_string_prototype_caseconv_shared(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_string_prototype_trim(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_string_prototype_repeat(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_string_prototype_startswith_endswith(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_string_prototype_includes(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_string_prototype_substr(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_boolean_prototype_tostring_shared(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_number_check_shared(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_number_prototype_to_string(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_number_prototype_to_locale_string(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_number_prototype_value_of(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_number_prototype_to_fixed(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_number_prototype_to_exponential(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_number_prototype_to_precision(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_date_constructor_parse(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_date_constructor_utc(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_date_constructor_now(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_date_prototype_tostring_shared(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_date_prototype_to_json(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_date_prototype_value_of(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_date_prototype_get_shared(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_date_prototype_get_timezone_offset(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_date_prototype_set_time(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_date_prototype_set_shared(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_date_prototype_toprimitive(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_regexp_prototype_exec(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_regexp_prototype_test(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_regexp_prototype_tostring(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_regexp_prototype_flags(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_regexp_prototype_shared_getter(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_error_prototype_stack_getter(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_error_prototype_stack_setter(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_error_prototype_filename_getter(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_error_prototype_filename_setter(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_error_prototype_linenumber_getter(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_error_prototype_linenumber_setter(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_error_prototype_to_string(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_math_object_onearg_shared(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_math_object_twoarg_shared(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_math_object_clz32(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_math_object_hypot(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_math_object_imul(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_math_object_max(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_math_object_min(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_math_object_random(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_math_object_sign(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_json_object_parse(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_json_object_stringify(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_duktape_object_info(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_duktape_object_act(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_duktape_object_gc(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_duktape_object_fin(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_duktape_object_enc(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_duktape_object_dec(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_duktape_object_compact(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_thread_yield(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_thread_resume(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_thread_current(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_pointer_prototype_tostring_shared(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_reflect_apply(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_reflect_construct(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_reflect_object_delete_property(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_reflect_object_get(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_reflect_object_has(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_reflect_object_set(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_symbol_key_for(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_symbol_tostring_shared(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_symbol_toprimitive(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_arraybuffer_isview(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_typedarray_bytelength_getter(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_buffer_slice_shared(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_typedarray_byteoffset_getter(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_typedarray_buffer_getter(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_buffer_readfield(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_buffer_writefield(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_typedarray_set(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_uint8array_allocplain(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_uint8array_plainof(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_nodejs_buffer_concat(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_nodejs_buffer_is_encoding(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_nodejs_buffer_is_buffer(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_nodejs_buffer_byte_length(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_buffer_compare_shared(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_nodejs_buffer_tostring(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_nodejs_buffer_tojson(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_nodejs_buffer_fill(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_nodejs_buffer_copy(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_nodejs_buffer_write(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_cbor_encode(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_cbor_decode(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_textencoder_prototype_encoding_getter(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_textencoder_prototype_encode(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_textdecoder_prototype_shared_getter(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_textdecoder_prototype_decode(duk_context *ctx); DUK_INTERNAL_DECL duk_ret_t duk_bi_performance_now(duk_context *ctx); #if !defined(DUK_SINGLE_FILE) DUK_INTERNAL_DECL const duk_c_function duk_bi_native_functions[185]; #endif /* !DUK_SINGLE_FILE */ #define DUK_BIDX_GLOBAL 0 #define DUK_BIDX_GLOBAL_ENV 1 #define DUK_BIDX_OBJECT_CONSTRUCTOR 2 #define DUK_BIDX_OBJECT_PROTOTYPE 3 #define DUK_BIDX_FUNCTION_CONSTRUCTOR 4 #define DUK_BIDX_FUNCTION_PROTOTYPE 5 #define DUK_BIDX_NATIVE_FUNCTION_PROTOTYPE 6 #define DUK_BIDX_ARRAY_CONSTRUCTOR 7 #define DUK_BIDX_ARRAY_PROTOTYPE 8 #define DUK_BIDX_STRING_CONSTRUCTOR 9 #define DUK_BIDX_STRING_PROTOTYPE 10 #define DUK_BIDX_BOOLEAN_CONSTRUCTOR 11 #define DUK_BIDX_BOOLEAN_PROTOTYPE 12 #define DUK_BIDX_NUMBER_CONSTRUCTOR 13 #define DUK_BIDX_NUMBER_PROTOTYPE 14 #define DUK_BIDX_DATE_CONSTRUCTOR 15 #define DUK_BIDX_DATE_PROTOTYPE 16 #define DUK_BIDX_REGEXP_CONSTRUCTOR 17 #define DUK_BIDX_REGEXP_PROTOTYPE 18 #define DUK_BIDX_ERROR_CONSTRUCTOR 19 #define DUK_BIDX_ERROR_PROTOTYPE 20 #define DUK_BIDX_EVAL_ERROR_CONSTRUCTOR 21 #define DUK_BIDX_EVAL_ERROR_PROTOTYPE 22 #define DUK_BIDX_RANGE_ERROR_CONSTRUCTOR 23 #define DUK_BIDX_RANGE_ERROR_PROTOTYPE 24 #define DUK_BIDX_REFERENCE_ERROR_CONSTRUCTOR 25 #define DUK_BIDX_REFERENCE_ERROR_PROTOTYPE 26 #define DUK_BIDX_SYNTAX_ERROR_CONSTRUCTOR 27 #define DUK_BIDX_SYNTAX_ERROR_PROTOTYPE 28 #define DUK_BIDX_TYPE_ERROR_CONSTRUCTOR 29 #define DUK_BIDX_TYPE_ERROR_PROTOTYPE 30 #define DUK_BIDX_URI_ERROR_CONSTRUCTOR 31 #define DUK_BIDX_URI_ERROR_PROTOTYPE 32 #define DUK_BIDX_TYPE_ERROR_THROWER 33 #define DUK_BIDX_DUKTAPE 34 #define DUK_BIDX_THREAD_PROTOTYPE 35 #define DUK_BIDX_POINTER_PROTOTYPE 36 #define DUK_BIDX_DOUBLE_ERROR 37 #define DUK_BIDX_SYMBOL_PROTOTYPE 38 #define DUK_BIDX_ARRAYBUFFER_PROTOTYPE 39 #define DUK_BIDX_DATAVIEW_PROTOTYPE 40 #define DUK_BIDX_INT8ARRAY_PROTOTYPE 41 #define DUK_BIDX_UINT8ARRAY_PROTOTYPE 42 #define DUK_BIDX_UINT8CLAMPEDARRAY_PROTOTYPE 43 #define DUK_BIDX_INT16ARRAY_PROTOTYPE 44 #define DUK_BIDX_UINT16ARRAY_PROTOTYPE 45 #define DUK_BIDX_INT32ARRAY_PROTOTYPE 46 #define DUK_BIDX_UINT32ARRAY_PROTOTYPE 47 #define DUK_BIDX_FLOAT32ARRAY_PROTOTYPE 48 #define DUK_BIDX_FLOAT64ARRAY_PROTOTYPE 49 #define DUK_BIDX_NODEJS_BUFFER_PROTOTYPE 50 #define DUK_NUM_BUILTINS 51 #define DUK_NUM_BIDX_BUILTINS 51 #define DUK_NUM_ALL_BUILTINS 80 #if defined(DUK_USE_DOUBLE_LE) #if !defined(DUK_SINGLE_FILE) DUK_INTERNAL_DECL const duk_uint8_t duk_builtins_data[4281]; #endif /* !DUK_SINGLE_FILE */ #define DUK_BUILTINS_DATA_LENGTH 4281 #elif defined(DUK_USE_DOUBLE_BE) #if !defined(DUK_SINGLE_FILE) DUK_INTERNAL_DECL const duk_uint8_t duk_builtins_data[4281]; #endif /* !DUK_SINGLE_FILE */ #define DUK_BUILTINS_DATA_LENGTH 4281 #elif defined(DUK_USE_DOUBLE_ME) #if !defined(DUK_SINGLE_FILE) DUK_INTERNAL_DECL const duk_uint8_t duk_builtins_data[4281]; #endif /* !DUK_SINGLE_FILE */ #define DUK_BUILTINS_DATA_LENGTH 4281 #else #error invalid endianness defines #endif #endif /* DUK_USE_ROM_OBJECTS */ #endif /* DUK_BUILTINS_H_INCLUDED */ #line 45 "duk_internal.h" /* #include duk_util.h */ #line 1 "duk_util.h" /* * Utilities */ #if !defined(DUK_UTIL_H_INCLUDED) #define DUK_UTIL_H_INCLUDED /* * Some useful constants */ #define DUK_DOUBLE_2TO32 4294967296.0 #define DUK_DOUBLE_2TO31 2147483648.0 #define DUK_DOUBLE_LOG2E 1.4426950408889634 #define DUK_DOUBLE_LOG10E 0.4342944819032518 /* * Endian conversion */ #if defined(DUK_USE_INTEGER_LE) #define DUK_HTON32(x) DUK_BSWAP32((x)) #define DUK_NTOH32(x) DUK_BSWAP32((x)) #define DUK_HTON16(x) DUK_BSWAP16((x)) #define DUK_NTOH16(x) DUK_BSWAP16((x)) #elif defined(DUK_USE_INTEGER_BE) #define DUK_HTON32(x) (x) #define DUK_NTOH32(x) (x) #define DUK_HTON16(x) (x) #define DUK_NTOH16(x) (x) #else #error internal error, endianness defines broken #endif /* * Bitstream decoder */ struct duk_bitdecoder_ctx { const duk_uint8_t *data; duk_size_t offset; duk_size_t length; duk_uint32_t currval; duk_small_int_t currbits; }; #define DUK_BD_BITPACKED_STRING_MAXLEN 256 /* * Bitstream encoder */ struct duk_bitencoder_ctx { duk_uint8_t *data; duk_size_t offset; duk_size_t length; duk_uint32_t currval; duk_small_int_t currbits; duk_small_int_t truncated; }; /* * Raw write/read macros for big endian, unaligned basic values. * Caller ensures there's enough space. The INC macro variants * update the pointer argument automatically. */ #define DUK_RAW_WRITE_U8(ptr, val) \ do { \ *(ptr) = (duk_uint8_t) (val); \ } while (0) #define DUK_RAW_WRITE_U16_BE(ptr, val) duk_raw_write_u16_be((ptr), (duk_uint16_t) (val)) #define DUK_RAW_WRITE_U32_BE(ptr, val) duk_raw_write_u32_be((ptr), (duk_uint32_t) (val)) #define DUK_RAW_WRITE_FLOAT_BE(ptr, val) duk_raw_write_float_be((ptr), (duk_float_t) (val)) #define DUK_RAW_WRITE_DOUBLE_BE(ptr, val) duk_raw_write_double_be((ptr), (duk_double_t) (val)) #define DUK_RAW_WRITE_XUTF8(ptr, val) duk_raw_write_xutf8((ptr), (duk_ucodepoint_t) (val)) #define DUK_RAW_WRITEINC_U8(ptr, val) \ do { \ *(ptr)++ = (duk_uint8_t) (val); \ } while (0) #define DUK_RAW_WRITEINC_U16_BE(ptr, val) duk_raw_writeinc_u16_be(&(ptr), (duk_uint16_t) (val)) #define DUK_RAW_WRITEINC_U32_BE(ptr, val) duk_raw_writeinc_u32_be(&(ptr), (duk_uint32_t) (val)) #define DUK_RAW_WRITEINC_FLOAT_BE(ptr, val) duk_raw_writeinc_float_be(&(ptr), (duk_float_t) (val)) #define DUK_RAW_WRITEINC_DOUBLE_BE(ptr, val) duk_raw_writeinc_double_be(&(ptr), (duk_double_t) (val)) #define DUK_RAW_WRITEINC_XUTF8(ptr, val) duk_raw_writeinc_xutf8(&(ptr), (duk_ucodepoint_t) (val)) #define DUK_RAW_WRITEINC_CESU8(ptr, val) duk_raw_writeinc_cesu8(&(ptr), (duk_ucodepoint_t) (val)) #define DUK_RAW_READ_U8(ptr) ((duk_uint8_t) (*(ptr))) #define DUK_RAW_READ_U16_BE(ptr) duk_raw_read_u16_be((ptr)); #define DUK_RAW_READ_U32_BE(ptr) duk_raw_read_u32_be((ptr)); #define DUK_RAW_READ_DOUBLE_BE(ptr) duk_raw_read_double_be((ptr)); #define DUK_RAW_READINC_U8(ptr) ((duk_uint8_t) (*(ptr)++)) #define DUK_RAW_READINC_U16_BE(ptr) duk_raw_readinc_u16_be(&(ptr)); #define DUK_RAW_READINC_U32_BE(ptr) duk_raw_readinc_u32_be(&(ptr)); #define DUK_RAW_READINC_DOUBLE_BE(ptr) duk_raw_readinc_double_be(&(ptr)); /* * Double and float byte order operations. */ DUK_INTERNAL_DECL void duk_dblunion_host_to_little(duk_double_union *u); DUK_INTERNAL_DECL void duk_dblunion_little_to_host(duk_double_union *u); DUK_INTERNAL_DECL void duk_dblunion_host_to_big(duk_double_union *u); DUK_INTERNAL_DECL void duk_dblunion_big_to_host(duk_double_union *u); DUK_INTERNAL_DECL void duk_fltunion_host_to_big(duk_float_union *u); DUK_INTERNAL_DECL void duk_fltunion_big_to_host(duk_float_union *u); /* * Buffer writer (dynamic buffer only) * * Helper for writing to a dynamic buffer with a concept of a "slack" area * to reduce resizes. You can ensure there is enough space beforehand and * then write for a while without further checks, relying on a stable data * pointer. Slack handling is automatic so call sites only indicate how * much data they need right now. * * There are several ways to write using bufwriter. The best approach * depends mainly on how much performance matters over code footprint. * The key issues are (1) ensuring there is space and (2) keeping the * pointers consistent. Fast code should ensure space for multiple writes * with one ensure call. Fastest inner loop code can temporarily borrow * the 'p' pointer but must write it back eventually. * * Be careful to ensure all macro arguments (other than static pointers like * 'thr' and 'bw_ctx') are evaluated exactly once, using temporaries if * necessary (if that's not possible, there should be a note near the macro). * Buffer write arguments often contain arithmetic etc so this is * particularly important here. */ /* XXX: Migrate bufwriter and other read/write helpers to its own header? */ struct duk_bufwriter_ctx { duk_uint8_t *p; duk_uint8_t *p_base; duk_uint8_t *p_limit; duk_hbuffer_dynamic *buf; }; #if defined(DUK_USE_PREFER_SIZE) #define DUK_BW_SLACK_ADD 64 #define DUK_BW_SLACK_SHIFT 4 /* 2^4 -> 1/16 = 6.25% slack */ #else #define DUK_BW_SLACK_ADD 64 #define DUK_BW_SLACK_SHIFT 2 /* 2^2 -> 1/4 = 25% slack */ #endif /* Initialization and finalization (compaction), converting to other types. */ #define DUK_BW_INIT_PUSHBUF(thr, bw_ctx, sz) \ do { \ duk_bw_init_pushbuf((thr), (bw_ctx), (sz)); \ } while (0) #define DUK_BW_INIT_WITHBUF(thr, bw_ctx, buf) \ do { \ duk_bw_init((thr), (bw_ctx), (buf)); \ } while (0) #define DUK_BW_COMPACT(thr, bw_ctx) \ do { \ /* Make underlying buffer compact to match DUK_BW_GET_SIZE(). */ \ duk_bw_compact((thr), (bw_ctx)); \ } while (0) #define DUK_BW_PUSH_AS_STRING(thr, bw_ctx) \ do { \ duk_push_lstring((thr), (const char *) (bw_ctx)->p_base, (duk_size_t) ((bw_ctx)->p - (bw_ctx)->p_base)); \ } while (0) /* Pointers may be NULL for a while when 'buf' size is zero and before any * ENSURE calls have been made. Once an ENSURE has been made, the pointers * are required to be non-NULL so that it's always valid to use memcpy() and * memmove(), even for zero size. */ #if defined(DUK_USE_ASSERTIONS) DUK_INTERNAL_DECL void duk_bw_assert_valid(duk_hthread *thr, duk_bufwriter_ctx *bw_ctx); #define DUK_BW_ASSERT_VALID_EXPR(thr, bw_ctx) (duk_bw_assert_valid((thr), (bw_ctx))) #define DUK_BW_ASSERT_VALID(thr, bw_ctx) \ do { \ duk_bw_assert_valid((thr), (bw_ctx)); \ } while (0) #else #define DUK_BW_ASSERT_VALID_EXPR(thr, bw_ctx) DUK_ASSERT_EXPR(1) #define DUK_BW_ASSERT_VALID(thr, bw_ctx) \ do { \ } while (0) #endif /* Working with the pointer and current size. */ #define DUK_BW_GET_PTR(thr, bw_ctx) ((bw_ctx)->p) #define DUK_BW_SET_PTR(thr, bw_ctx, ptr) \ do { \ (bw_ctx)->p = (ptr); \ } while (0) #define DUK_BW_ADD_PTR(thr, bw_ctx, delta) \ do { \ (bw_ctx)->p += (delta); \ } while (0) #define DUK_BW_GET_BASEPTR(thr, bw_ctx) ((bw_ctx)->p_base) #define DUK_BW_GET_LIMITPTR(thr, bw_ctx) ((bw_ctx)->p_limit) #define DUK_BW_GET_SIZE(thr, bw_ctx) ((duk_size_t) ((bw_ctx)->p - (bw_ctx)->p_base)) #define DUK_BW_SET_SIZE(thr, bw_ctx, sz) \ do { \ DUK_ASSERT((duk_size_t) (sz) <= (duk_size_t) ((bw_ctx)->p - (bw_ctx)->p_base)); \ (bw_ctx)->p = (bw_ctx)->p_base + (sz); \ } while (0) #define DUK_BW_RESET_SIZE(thr, bw_ctx) \ do { \ /* Reset to zero size, keep current limit. */ \ (bw_ctx)->p = (bw_ctx)->p_base; \ } while (0) #define DUK_BW_GET_BUFFER(thr, bw_ctx) ((bw_ctx)->buf) /* Ensuring (reserving) space. */ #define DUK_BW_ENSURE(thr, bw_ctx, sz) \ do { \ duk_size_t duk__sz, duk__space; \ DUK_BW_ASSERT_VALID((thr), (bw_ctx)); \ duk__sz = (sz); \ duk__space = (duk_size_t) ((bw_ctx)->p_limit - (bw_ctx)->p); \ if (duk__space < duk__sz) { \ (void) duk_bw_resize((thr), (bw_ctx), duk__sz); \ } \ } while (0) /* NOTE: Multiple evaluation of 'ptr' in this macro. */ /* XXX: Rework to use an always-inline function? */ #define DUK_BW_ENSURE_RAW(thr, bw_ctx, sz, ptr) \ (((duk_size_t) ((bw_ctx)->p_limit - (ptr)) >= (sz)) ? (ptr) : ((bw_ctx)->p = (ptr), duk_bw_resize((thr), (bw_ctx), (sz)))) #define DUK_BW_ENSURE_GETPTR(thr, bw_ctx, sz) DUK_BW_ENSURE_RAW((thr), (bw_ctx), (sz), (bw_ctx)->p) #define DUK_BW_ASSERT_SPACE_EXPR(thr, bw_ctx, sz) \ (DUK_BW_ASSERT_VALID_EXPR((thr), (bw_ctx)), \ DUK_ASSERT_EXPR((duk_size_t) ((bw_ctx)->p_limit - (bw_ctx)->p) >= (duk_size_t) (sz))) #define DUK_BW_ASSERT_SPACE(thr, bw_ctx, sz) \ do { \ DUK_BW_ASSERT_SPACE_EXPR((thr), (bw_ctx), (sz)); \ } while (0) /* Miscellaneous. */ #define DUK_BW_SETPTR_AND_COMPACT(thr, bw_ctx, ptr) \ do { \ (bw_ctx)->p = (ptr); \ duk_bw_compact((thr), (bw_ctx)); \ } while (0) /* Fast write calls which assume you control the slack beforehand. * Multibyte write variants exist and use a temporary write pointer * because byte writes alias with anything: with a stored pointer * explicit pointer load/stores get generated (e.g. gcc -Os). */ #define DUK_BW_WRITE_RAW_U8(thr, bw_ctx, val) \ do { \ DUK_BW_ASSERT_SPACE((thr), (bw_ctx), 1); \ *(bw_ctx)->p++ = (duk_uint8_t) (val); \ } while (0) #define DUK_BW_WRITE_RAW_U8_2(thr, bw_ctx, val1, val2) \ do { \ duk_uint8_t *duk__p; \ DUK_BW_ASSERT_SPACE((thr), (bw_ctx), 2); \ duk__p = (bw_ctx)->p; \ *duk__p++ = (duk_uint8_t) (val1); \ *duk__p++ = (duk_uint8_t) (val2); \ (bw_ctx)->p = duk__p; \ } while (0) #define DUK_BW_WRITE_RAW_U8_3(thr, bw_ctx, val1, val2, val3) \ do { \ duk_uint8_t *duk__p; \ DUK_BW_ASSERT_SPACE((thr), (bw_ctx), 3); \ duk__p = (bw_ctx)->p; \ *duk__p++ = (duk_uint8_t) (val1); \ *duk__p++ = (duk_uint8_t) (val2); \ *duk__p++ = (duk_uint8_t) (val3); \ (bw_ctx)->p = duk__p; \ } while (0) #define DUK_BW_WRITE_RAW_U8_4(thr, bw_ctx, val1, val2, val3, val4) \ do { \ duk_uint8_t *duk__p; \ DUK_BW_ASSERT_SPACE((thr), (bw_ctx), 4); \ duk__p = (bw_ctx)->p; \ *duk__p++ = (duk_uint8_t) (val1); \ *duk__p++ = (duk_uint8_t) (val2); \ *duk__p++ = (duk_uint8_t) (val3); \ *duk__p++ = (duk_uint8_t) (val4); \ (bw_ctx)->p = duk__p; \ } while (0) #define DUK_BW_WRITE_RAW_U8_5(thr, bw_ctx, val1, val2, val3, val4, val5) \ do { \ duk_uint8_t *duk__p; \ DUK_BW_ASSERT_SPACE((thr), (bw_ctx), 5); \ duk__p = (bw_ctx)->p; \ *duk__p++ = (duk_uint8_t) (val1); \ *duk__p++ = (duk_uint8_t) (val2); \ *duk__p++ = (duk_uint8_t) (val3); \ *duk__p++ = (duk_uint8_t) (val4); \ *duk__p++ = (duk_uint8_t) (val5); \ (bw_ctx)->p = duk__p; \ } while (0) #define DUK_BW_WRITE_RAW_U8_6(thr, bw_ctx, val1, val2, val3, val4, val5, val6) \ do { \ duk_uint8_t *duk__p; \ DUK_BW_ASSERT_SPACE((thr), (bw_ctx), 6); \ duk__p = (bw_ctx)->p; \ *duk__p++ = (duk_uint8_t) (val1); \ *duk__p++ = (duk_uint8_t) (val2); \ *duk__p++ = (duk_uint8_t) (val3); \ *duk__p++ = (duk_uint8_t) (val4); \ *duk__p++ = (duk_uint8_t) (val5); \ *duk__p++ = (duk_uint8_t) (val6); \ (bw_ctx)->p = duk__p; \ } while (0) #define DUK_BW_WRITE_RAW_XUTF8(thr, bw_ctx, cp) \ do { \ duk_ucodepoint_t duk__cp; \ duk_small_int_t duk__enc_len; \ duk__cp = (duk_ucodepoint_t) (cp); \ DUK_BW_ASSERT_SPACE((thr), (bw_ctx), duk_unicode_get_xutf8_length(duk__cp)); \ duk__enc_len = duk_unicode_encode_xutf8(duk__cp, (bw_ctx)->p); \ (bw_ctx)->p += duk__enc_len; \ } while (0) #define DUK_BW_WRITE_RAW_CESU8(thr, bw_ctx, cp) \ do { \ duk_ucodepoint_t duk__cp; \ duk_small_int_t duk__enc_len; \ duk__cp = (duk_ucodepoint_t) (cp); \ DUK_BW_ASSERT_SPACE((thr), (bw_ctx), duk_unicode_get_cesu8_length(duk__cp)); \ duk__enc_len = duk_unicode_encode_cesu8(duk__cp, (bw_ctx)->p); \ (bw_ctx)->p += duk__enc_len; \ } while (0) /* XXX: add temporary duk__p pointer here too; sharing */ /* XXX: avoid unsafe variants */ #define DUK_BW_WRITE_RAW_BYTES(thr, bw_ctx, valptr, valsz) \ do { \ const void *duk__valptr; \ duk_size_t duk__valsz; \ duk__valptr = (const void *) (valptr); \ duk__valsz = (duk_size_t) (valsz); \ duk_memcpy_unsafe((void *) ((bw_ctx)->p), duk__valptr, duk__valsz); \ (bw_ctx)->p += duk__valsz; \ } while (0) #define DUK_BW_WRITE_RAW_CSTRING(thr, bw_ctx, val) \ do { \ const duk_uint8_t *duk__val; \ duk_size_t duk__val_len; \ duk__val = (const duk_uint8_t *) (val); \ duk__val_len = DUK_STRLEN((const char *) duk__val); \ duk_memcpy_unsafe((void *) ((bw_ctx)->p), (const void *) duk__val, duk__val_len); \ (bw_ctx)->p += duk__val_len; \ } while (0) #define DUK_BW_WRITE_RAW_HSTRING(thr, bw_ctx, val) \ do { \ duk_size_t duk__val_len; \ duk__val_len = DUK_HSTRING_GET_BYTELEN((val)); \ duk_memcpy_unsafe((void *) ((bw_ctx)->p), (const void *) DUK_HSTRING_GET_DATA((val)), duk__val_len); \ (bw_ctx)->p += duk__val_len; \ } while (0) #define DUK_BW_WRITE_RAW_HBUFFER(thr, bw_ctx, val) \ do { \ duk_size_t duk__val_len; \ duk__val_len = DUK_HBUFFER_GET_SIZE((val)); \ duk_memcpy_unsafe((void *) ((bw_ctx)->p), \ (const void *) DUK_HBUFFER_GET_DATA_PTR((thr)->heap, (val)), \ duk__val_len); \ (bw_ctx)->p += duk__val_len; \ } while (0) #define DUK_BW_WRITE_RAW_HBUFFER_FIXED(thr, bw_ctx, val) \ do { \ duk_size_t duk__val_len; \ duk__val_len = DUK_HBUFFER_FIXED_GET_SIZE((val)); \ duk_memcpy_unsafe((void *) ((bw_ctx)->p), \ (const void *) DUK_HBUFFER_FIXED_GET_DATA_PTR((thr)->heap, (val)), \ duk__val_len); \ (bw_ctx)->p += duk__val_len; \ } while (0) #define DUK_BW_WRITE_RAW_HBUFFER_DYNAMIC(thr, bw_ctx, val) \ do { \ duk_size_t duk__val_len; \ duk__val_len = DUK_HBUFFER_DYNAMIC_GET_SIZE((val)); \ duk_memcpy_unsafe((void *) ((bw_ctx)->p), \ (const void *) DUK_HBUFFER_DYNAMIC_GET_DATA_PTR((thr)->heap, (val)), \ duk__val_len); \ (bw_ctx)->p += duk__val_len; \ } while (0) /* Append bytes from a slice already in the buffer. */ #define DUK_BW_WRITE_RAW_SLICE(thr, bw, dst_off, dst_len) duk_bw_write_raw_slice((thr), (bw), (dst_off), (dst_len)) /* Insert bytes in the middle of the buffer from an external buffer. */ #define DUK_BW_INSERT_RAW_BYTES(thr, bw, dst_off, buf, len) duk_bw_insert_raw_bytes((thr), (bw), (dst_off), (buf), (len)) /* Insert bytes in the middle of the buffer from a slice already * in the buffer. Source offset is interpreted "before" the operation. */ #define DUK_BW_INSERT_RAW_SLICE(thr, bw, dst_off, src_off, len) duk_bw_insert_raw_slice((thr), (bw), (dst_off), (src_off), (len)) /* Insert a reserved area somewhere in the buffer; caller fills it. * Evaluates to a (duk_uint_t *) pointing to the start of the reserved * area for convenience. */ #define DUK_BW_INSERT_RAW_AREA(thr, bw, off, len) duk_bw_insert_raw_area((thr), (bw), (off), (len)) /* Remove a slice from inside buffer. */ #define DUK_BW_REMOVE_RAW_SLICE(thr, bw, off, len) duk_bw_remove_raw_slice((thr), (bw), (off), (len)) /* Safe write calls which will ensure space first. */ #define DUK_BW_WRITE_ENSURE_U8(thr, bw_ctx, val) \ do { \ DUK_BW_ENSURE((thr), (bw_ctx), 1); \ DUK_BW_WRITE_RAW_U8((thr), (bw_ctx), (val)); \ } while (0) #define DUK_BW_WRITE_ENSURE_U8_2(thr, bw_ctx, val1, val2) \ do { \ DUK_BW_ENSURE((thr), (bw_ctx), 2); \ DUK_BW_WRITE_RAW_U8_2((thr), (bw_ctx), (val1), (val2)); \ } while (0) #define DUK_BW_WRITE_ENSURE_U8_3(thr, bw_ctx, val1, val2, val3) \ do { \ DUK_BW_ENSURE((thr), (bw_ctx), 3); \ DUK_BW_WRITE_RAW_U8_3((thr), (bw_ctx), (val1), (val2), (val3)); \ } while (0) #define DUK_BW_WRITE_ENSURE_U8_4(thr, bw_ctx, val1, val2, val3, val4) \ do { \ DUK_BW_ENSURE((thr), (bw_ctx), 4); \ DUK_BW_WRITE_RAW_U8_4((thr), (bw_ctx), (val1), (val2), (val3), (val4)); \ } while (0) #define DUK_BW_WRITE_ENSURE_U8_5(thr, bw_ctx, val1, val2, val3, val4, val5) \ do { \ DUK_BW_ENSURE((thr), (bw_ctx), 5); \ DUK_BW_WRITE_RAW_U8_5((thr), (bw_ctx), (val1), (val2), (val3), (val4), (val5)); \ } while (0) #define DUK_BW_WRITE_ENSURE_U8_6(thr, bw_ctx, val1, val2, val3, val4, val5, val6) \ do { \ DUK_BW_ENSURE((thr), (bw_ctx), 6); \ DUK_BW_WRITE_RAW_U8_6((thr), (bw_ctx), (val1), (val2), (val3), (val4), (val5), (val6)); \ } while (0) #define DUK_BW_WRITE_ENSURE_XUTF8(thr, bw_ctx, cp) \ do { \ DUK_BW_ENSURE((thr), (bw_ctx), DUK_UNICODE_MAX_XUTF8_LENGTH); \ DUK_BW_WRITE_RAW_XUTF8((thr), (bw_ctx), (cp)); \ } while (0) #define DUK_BW_WRITE_ENSURE_CESU8(thr, bw_ctx, cp) \ do { \ DUK_BW_ENSURE((thr), (bw_ctx), DUK_UNICODE_MAX_CESU8_LENGTH); \ DUK_BW_WRITE_RAW_CESU8((thr), (bw_ctx), (cp)); \ } while (0) /* XXX: add temporary duk__p pointer here too; sharing */ /* XXX: avoid unsafe */ #define DUK_BW_WRITE_ENSURE_BYTES(thr, bw_ctx, valptr, valsz) \ do { \ const void *duk__valptr; \ duk_size_t duk__valsz; \ duk__valptr = (const void *) (valptr); \ duk__valsz = (duk_size_t) (valsz); \ DUK_BW_ENSURE((thr), (bw_ctx), duk__valsz); \ duk_memcpy_unsafe((void *) ((bw_ctx)->p), duk__valptr, duk__valsz); \ (bw_ctx)->p += duk__valsz; \ } while (0) #define DUK_BW_WRITE_ENSURE_CSTRING(thr, bw_ctx, val) \ do { \ const duk_uint8_t *duk__val; \ duk_size_t duk__val_len; \ duk__val = (const duk_uint8_t *) (val); \ duk__val_len = DUK_STRLEN((const char *) duk__val); \ DUK_BW_ENSURE((thr), (bw_ctx), duk__val_len); \ duk_memcpy_unsafe((void *) ((bw_ctx)->p), (const void *) duk__val, duk__val_len); \ (bw_ctx)->p += duk__val_len; \ } while (0) #define DUK_BW_WRITE_ENSURE_HSTRING(thr, bw_ctx, val) \ do { \ duk_size_t duk__val_len; \ duk__val_len = DUK_HSTRING_GET_BYTELEN((val)); \ DUK_BW_ENSURE((thr), (bw_ctx), duk__val_len); \ duk_memcpy_unsafe((void *) ((bw_ctx)->p), (const void *) DUK_HSTRING_GET_DATA((val)), duk__val_len); \ (bw_ctx)->p += duk__val_len; \ } while (0) #define DUK_BW_WRITE_ENSURE_HBUFFER(thr, bw_ctx, val) \ do { \ duk_size_t duk__val_len; \ duk__val_len = DUK_HBUFFER_GET_SIZE((val)); \ DUK_BW_ENSURE((thr), (bw_ctx), duk__val_len); \ duk_memcpy_unsafe((void *) ((bw_ctx)->p), \ (const void *) DUK_HBUFFER_GET_DATA_PTR((thr)->heap, (val)), \ duk__val_len); \ (bw_ctx)->p += duk__val_len; \ } while (0) #define DUK_BW_WRITE_ENSURE_HBUFFER_FIXED(thr, bw_ctx, val) \ do { \ duk_size_t duk__val_len; \ duk__val_len = DUK_HBUFFER_FIXED_GET_SIZE((val)); \ DUK_BW_ENSURE((thr), (bw_ctx), duk__val_len); \ duk_memcpy_unsafe((void *) ((bw_ctx)->p), \ (const void *) DUK_HBUFFER_FIXED_GET_DATA_PTR((thr)->heap, (val)), \ duk__val_len); \ (bw_ctx)->p += duk__val_len; \ } while (0) #define DUK_BW_WRITE_ENSURE_HBUFFER_DYNAMIC(thr, bw_ctx, val) \ do { \ duk_size_t duk__val_len; \ duk__val_len = DUK_HBUFFER_DYNAMIC_GET_SIZE((val)); \ DUK_BW_ENSURE((thr), (bw_ctx), duk__val_len); \ duk_memcpy_unsafe((void *) ((bw_ctx)->p), \ (const void *) DUK_HBUFFER_DYNAMIC_GET_DATA_PTR((thr)->heap, (val)), \ duk__val_len); \ (bw_ctx)->p += duk__val_len; \ } while (0) #define DUK_BW_WRITE_ENSURE_SLICE(thr, bw, dst_off, dst_len) duk_bw_write_ensure_slice((thr), (bw), (dst_off), (dst_len)) #define DUK_BW_INSERT_ENSURE_BYTES(thr, bw, dst_off, buf, len) duk_bw_insert_ensure_bytes((thr), (bw), (dst_off), (buf), (len)) #define DUK_BW_INSERT_ENSURE_SLICE(thr, bw, dst_off, src_off, len) \ duk_bw_insert_ensure_slice((thr), (bw), (dst_off), (src_off), (len)) #define DUK_BW_INSERT_ENSURE_AREA(thr, bw, off, len) \ /* Evaluates to (duk_uint8_t *) pointing to start of area. */ \ duk_bw_insert_ensure_area((thr), (bw), (off), (len)) #define DUK_BW_REMOVE_ENSURE_SLICE(thr, bw, off, len) \ /* No difference between raw/ensure because the buffer shrinks. */ \ DUK_BW_REMOVE_RAW_SLICE((thr), (bw), (off), (len)) /* * Externs and prototypes */ #if !defined(DUK_SINGLE_FILE) DUK_INTERNAL_DECL const duk_uint8_t duk_lc_digits[36]; DUK_INTERNAL_DECL const duk_uint8_t duk_uc_nybbles[16]; DUK_INTERNAL_DECL const duk_int8_t duk_hex_dectab[256]; #if defined(DUK_USE_HEX_FASTPATH) DUK_INTERNAL_DECL const duk_int16_t duk_hex_dectab_shift4[256]; DUK_INTERNAL_DECL const duk_uint16_t duk_hex_enctab[256]; #endif #endif /* !DUK_SINGLE_FILE */ /* Note: assumes that duk_util_probe_steps size is 32 */ #if defined(DUK_USE_HOBJECT_HASH_PART) #if !defined(DUK_SINGLE_FILE) DUK_INTERNAL_DECL duk_uint8_t duk_util_probe_steps[32]; #endif /* !DUK_SINGLE_FILE */ #endif #if defined(DUK_USE_STRHASH_DENSE) DUK_INTERNAL_DECL duk_uint32_t duk_util_hashbytes(const duk_uint8_t *data, duk_size_t len, duk_uint32_t seed); #endif DUK_INTERNAL_DECL duk_uint32_t duk_bd_decode(duk_bitdecoder_ctx *ctx, duk_small_int_t bits); DUK_INTERNAL_DECL duk_small_uint_t duk_bd_decode_flag(duk_bitdecoder_ctx *ctx); DUK_INTERNAL_DECL duk_uint32_t duk_bd_decode_flagged(duk_bitdecoder_ctx *ctx, duk_small_int_t bits, duk_uint32_t def_value); DUK_INTERNAL_DECL duk_int32_t duk_bd_decode_flagged_signed(duk_bitdecoder_ctx *ctx, duk_small_int_t bits, duk_int32_t def_value); DUK_INTERNAL_DECL duk_uint32_t duk_bd_decode_varuint(duk_bitdecoder_ctx *ctx); DUK_INTERNAL_DECL duk_small_uint_t duk_bd_decode_bitpacked_string(duk_bitdecoder_ctx *bd, duk_uint8_t *out); DUK_INTERNAL_DECL void duk_be_encode(duk_bitencoder_ctx *ctx, duk_uint32_t data, duk_small_int_t bits); DUK_INTERNAL_DECL void duk_be_finish(duk_bitencoder_ctx *ctx); #if !defined(DUK_USE_GET_RANDOM_DOUBLE) DUK_INTERNAL_DECL duk_double_t duk_util_tinyrandom_get_double(duk_hthread *thr); DUK_INTERNAL_DECL void duk_util_tinyrandom_prepare_seed(duk_hthread *thr); #endif DUK_INTERNAL_DECL void duk_bw_init(duk_hthread *thr, duk_bufwriter_ctx *bw_ctx, duk_hbuffer_dynamic *h_buf); DUK_INTERNAL_DECL void duk_bw_init_pushbuf(duk_hthread *thr, duk_bufwriter_ctx *bw_ctx, duk_size_t buf_size); DUK_INTERNAL_DECL duk_uint8_t *duk_bw_resize(duk_hthread *thr, duk_bufwriter_ctx *bw_ctx, duk_size_t sz); DUK_INTERNAL_DECL void duk_bw_compact(duk_hthread *thr, duk_bufwriter_ctx *bw_ctx); DUK_INTERNAL_DECL void duk_bw_write_raw_slice(duk_hthread *thr, duk_bufwriter_ctx *bw, duk_size_t src_off, duk_size_t len); DUK_INTERNAL_DECL void duk_bw_write_ensure_slice(duk_hthread *thr, duk_bufwriter_ctx *bw, duk_size_t src_off, duk_size_t len); DUK_INTERNAL_DECL void duk_bw_insert_raw_bytes(duk_hthread *thr, duk_bufwriter_ctx *bw, duk_size_t dst_off, const duk_uint8_t *buf, duk_size_t len); DUK_INTERNAL_DECL void duk_bw_insert_ensure_bytes(duk_hthread *thr, duk_bufwriter_ctx *bw, duk_size_t dst_off, const duk_uint8_t *buf, duk_size_t len); DUK_INTERNAL_DECL void duk_bw_insert_raw_slice(duk_hthread *thr, duk_bufwriter_ctx *bw, duk_size_t dst_off, duk_size_t src_off, duk_size_t len); DUK_INTERNAL_DECL void duk_bw_insert_ensure_slice(duk_hthread *thr, duk_bufwriter_ctx *bw, duk_size_t dst_off, duk_size_t src_off, duk_size_t len); DUK_INTERNAL_DECL duk_uint8_t *duk_bw_insert_raw_area(duk_hthread *thr, duk_bufwriter_ctx *bw, duk_size_t off, duk_size_t len); DUK_INTERNAL_DECL duk_uint8_t *duk_bw_insert_ensure_area(duk_hthread *thr, duk_bufwriter_ctx *bw, duk_size_t off, duk_size_t len); DUK_INTERNAL_DECL void duk_bw_remove_raw_slice(duk_hthread *thr, duk_bufwriter_ctx *bw, duk_size_t off, duk_size_t len); /* No duk_bw_remove_ensure_slice(), functionality would be identical. */ DUK_INTERNAL_DECL duk_uint16_t duk_raw_read_u16_be(const duk_uint8_t *p); DUK_INTERNAL_DECL duk_uint32_t duk_raw_read_u32_be(const duk_uint8_t *p); DUK_INTERNAL_DECL duk_float_t duk_raw_read_float_be(const duk_uint8_t *p); DUK_INTERNAL_DECL duk_double_t duk_raw_read_double_be(const duk_uint8_t *p); DUK_INTERNAL_DECL duk_uint16_t duk_raw_readinc_u16_be(const duk_uint8_t **p); DUK_INTERNAL_DECL duk_uint32_t duk_raw_readinc_u32_be(const duk_uint8_t **p); DUK_INTERNAL_DECL duk_float_t duk_raw_readinc_float_be(const duk_uint8_t **p); DUK_INTERNAL_DECL duk_double_t duk_raw_readinc_double_be(const duk_uint8_t **p); DUK_INTERNAL_DECL void duk_raw_write_u16_be(duk_uint8_t *p, duk_uint16_t val); DUK_INTERNAL_DECL void duk_raw_write_u32_be(duk_uint8_t *p, duk_uint32_t val); DUK_INTERNAL_DECL void duk_raw_write_float_be(duk_uint8_t *p, duk_float_t val); DUK_INTERNAL_DECL void duk_raw_write_double_be(duk_uint8_t *p, duk_double_t val); DUK_INTERNAL_DECL duk_small_int_t duk_raw_write_xutf8(duk_uint8_t *p, duk_ucodepoint_t val); DUK_INTERNAL_DECL duk_small_int_t duk_raw_write_cesu8(duk_uint8_t *p, duk_ucodepoint_t val); DUK_INTERNAL_DECL void duk_raw_writeinc_u16_be(duk_uint8_t **p, duk_uint16_t val); DUK_INTERNAL_DECL void duk_raw_writeinc_u32_be(duk_uint8_t **p, duk_uint32_t val); DUK_INTERNAL_DECL void duk_raw_writeinc_float_be(duk_uint8_t **p, duk_float_t val); DUK_INTERNAL_DECL void duk_raw_writeinc_double_be(duk_uint8_t **p, duk_double_t val); DUK_INTERNAL_DECL void duk_raw_writeinc_xutf8(duk_uint8_t **p, duk_ucodepoint_t val); DUK_INTERNAL_DECL void duk_raw_writeinc_cesu8(duk_uint8_t **p, duk_ucodepoint_t val); #if defined(DUK_USE_DEBUGGER_SUPPORT) /* For now only needed by the debugger. */ DUK_INTERNAL_DECL void duk_byteswap_bytes(duk_uint8_t *p, duk_small_uint_t len); #endif DUK_INTERNAL_DECL duk_double_t duk_util_get_random_double(duk_hthread *thr); /* memcpy(), memmove() etc wrappers. The plain variants like duk_memcpy() * assume C99+ and 'src' and 'dst' pointers must be non-NULL even when the * operation size is zero. The unsafe variants like duk_memcpy_safe() deal * with the zero size case explicitly, and allow NULL pointers in that case * (which is undefined behavior in C99+). For the majority of actual targets * a NULL pointer with a zero length is fine in practice. These wrappers are * macros to force inlining; because there are hundreds of call sites, even a * few extra bytes per call site adds up to ~1kB footprint. */ #if defined(DUK_USE_ALLOW_UNDEFINED_BEHAVIOR) #define duk_memcpy(dst, src, len) \ do { \ void *duk__dst = (dst); \ const void *duk__src = (src); \ duk_size_t duk__len = (len); \ DUK_ASSERT(duk__dst != NULL || duk__len == 0U); \ DUK_ASSERT(duk__src != NULL || duk__len == 0U); \ (void) DUK_MEMCPY(duk__dst, duk__src, (size_t) duk__len); \ } while (0) #define duk_memcpy_unsafe(dst, src, len) duk_memcpy((dst), (src), (len)) #define duk_memmove(dst, src, len) \ do { \ void *duk__dst = (dst); \ const void *duk__src = (src); \ duk_size_t duk__len = (len); \ DUK_ASSERT(duk__dst != NULL || duk__len == 0U); \ DUK_ASSERT(duk__src != NULL || duk__len == 0U); \ (void) DUK_MEMMOVE(duk__dst, duk__src, (size_t) duk__len); \ } while (0) #define duk_memmove_unsafe(dst, src, len) duk_memmove((dst), (src), (len)) #define duk_memset(dst, val, len) \ do { \ void *duk__dst = (dst); \ duk_small_int_t duk__val = (val); \ duk_size_t duk__len = (len); \ DUK_ASSERT(duk__dst != NULL || duk__len == 0U); \ (void) DUK_MEMSET(duk__dst, duk__val, (size_t) duk__len); \ } while (0) #define duk_memset_unsafe(dst, val, len) duk_memset((dst), (val), (len)) #define duk_memzero(dst, len) \ do { \ void *duk__dst = (dst); \ duk_size_t duk__len = (len); \ DUK_ASSERT(duk__dst != NULL || duk__len == 0U); \ (void) DUK_MEMZERO(duk__dst, (size_t) duk__len); \ } while (0) #define duk_memzero_unsafe(dst, len) duk_memzero((dst), (len)) #else /* DUK_USE_ALLOW_UNDEFINED_BEHAVIOR */ #define duk_memcpy(dst, src, len) \ do { \ void *duk__dst = (dst); \ const void *duk__src = (src); \ duk_size_t duk__len = (len); \ DUK_ASSERT(duk__dst != NULL); \ DUK_ASSERT(duk__src != NULL); \ (void) DUK_MEMCPY(duk__dst, duk__src, (size_t) duk__len); \ } while (0) #define duk_memcpy_unsafe(dst, src, len) \ do { \ void *duk__dst = (dst); \ const void *duk__src = (src); \ duk_size_t duk__len = (len); \ DUK_ASSERT(duk__dst != NULL || duk__len == 0U); \ DUK_ASSERT(duk__src != NULL || duk__len == 0U); \ if (DUK_LIKELY(duk__len > 0U)) { \ DUK_ASSERT(duk__dst != NULL); \ DUK_ASSERT(duk__src != NULL); \ (void) DUK_MEMCPY(duk__dst, duk__src, (size_t) duk__len); \ } \ } while (0) #define duk_memmove(dst, src, len) \ do { \ void *duk__dst = (dst); \ const void *duk__src = (src); \ duk_size_t duk__len = (len); \ DUK_ASSERT(duk__dst != NULL); \ DUK_ASSERT(duk__src != NULL); \ (void) DUK_MEMMOVE(duk__dst, duk__src, (size_t) duk__len); \ } while (0) #define duk_memmove_unsafe(dst, src, len) \ do { \ void *duk__dst = (dst); \ const void *duk__src = (src); \ duk_size_t duk__len = (len); \ DUK_ASSERT(duk__dst != NULL || duk__len == 0U); \ DUK_ASSERT(duk__src != NULL || duk__len == 0U); \ if (DUK_LIKELY(duk__len > 0U)) { \ DUK_ASSERT(duk__dst != NULL); \ DUK_ASSERT(duk__src != NULL); \ (void) DUK_MEMMOVE(duk__dst, duk__src, (size_t) duk__len); \ } \ } while (0) #define duk_memset(dst, val, len) \ do { \ void *duk__dst = (dst); \ duk_small_int_t duk__val = (val); \ duk_size_t duk__len = (len); \ DUK_ASSERT(duk__dst != NULL); \ (void) DUK_MEMSET(duk__dst, duk__val, (size_t) duk__len); \ } while (0) #define duk_memset_unsafe(dst, val, len) \ do { \ void *duk__dst = (dst); \ duk_small_int_t duk__val = (val); \ duk_size_t duk__len = (len); \ DUK_ASSERT(duk__dst != NULL || duk__len == 0U); \ if (DUK_LIKELY(duk__len > 0U)) { \ DUK_ASSERT(duk__dst != NULL); \ (void) DUK_MEMSET(duk__dst, duk__val, (size_t) duk__len); \ } \ } while (0) #define duk_memzero(dst, len) \ do { \ void *duk__dst = (dst); \ duk_size_t duk__len = (len); \ DUK_ASSERT(duk__dst != NULL); \ (void) DUK_MEMZERO(duk__dst, (size_t) duk__len); \ } while (0) #define duk_memzero_unsafe(dst, len) \ do { \ void *duk__dst = (dst); \ duk_size_t duk__len = (len); \ DUK_ASSERT(duk__dst != NULL || duk__len == 0U); \ if (DUK_LIKELY(duk__len > 0U)) { \ DUK_ASSERT(duk__dst != NULL); \ (void) DUK_MEMZERO(duk__dst, (size_t) duk__len); \ } \ } while (0) #endif /* DUK_USE_ALLOW_UNDEFINED_BEHAVIOR */ DUK_INTERNAL_DECL duk_small_int_t duk_memcmp(const void *s1, const void *s2, duk_size_t len); DUK_INTERNAL_DECL duk_small_int_t duk_memcmp_unsafe(const void *s1, const void *s2, duk_size_t len); DUK_INTERNAL_DECL duk_bool_t duk_is_whole_get_int32_nonegzero(duk_double_t x, duk_int32_t *ival); DUK_INTERNAL_DECL duk_bool_t duk_is_whole_get_int32(duk_double_t x, duk_int32_t *ival); DUK_INTERNAL_DECL duk_bool_t duk_double_is_anyinf(duk_double_t x); DUK_INTERNAL_DECL duk_bool_t duk_double_is_posinf(duk_double_t x); DUK_INTERNAL_DECL duk_bool_t duk_double_is_neginf(duk_double_t x); DUK_INTERNAL_DECL duk_bool_t duk_double_is_nan(duk_double_t x); DUK_INTERNAL_DECL duk_bool_t duk_double_is_nan_or_zero(duk_double_t x); DUK_INTERNAL_DECL duk_bool_t duk_double_is_nan_or_inf(duk_double_t x); DUK_INTERNAL_DECL duk_bool_t duk_double_is_nan_zero_inf(duk_double_t x); DUK_INTERNAL_DECL duk_small_uint_t duk_double_signbit(duk_double_t x); DUK_INTERNAL_DECL duk_double_t duk_double_trunc_towards_zero(duk_double_t x); DUK_INTERNAL_DECL duk_bool_t duk_double_same_sign(duk_double_t x, duk_double_t y); DUK_INTERNAL_DECL duk_double_t duk_double_fmin(duk_double_t x, duk_double_t y); DUK_INTERNAL_DECL duk_double_t duk_double_fmax(duk_double_t x, duk_double_t y); DUK_INTERNAL_DECL duk_bool_t duk_double_is_finite(duk_double_t x); DUK_INTERNAL_DECL duk_bool_t duk_double_is_integer(duk_double_t x); DUK_INTERNAL_DECL duk_bool_t duk_double_is_safe_integer(duk_double_t x); DUK_INTERNAL_DECL duk_double_t duk_double_div(duk_double_t x, duk_double_t y); DUK_INTERNAL_DECL duk_int_t duk_double_to_int_t(duk_double_t x); DUK_INTERNAL_DECL duk_uint_t duk_double_to_uint_t(duk_double_t x); DUK_INTERNAL_DECL duk_int32_t duk_double_to_int32_t(duk_double_t x); DUK_INTERNAL_DECL duk_uint32_t duk_double_to_uint32_t(duk_double_t x); DUK_INTERNAL_DECL duk_float_t duk_double_to_float_t(duk_double_t x); DUK_INTERNAL_DECL duk_bool_t duk_double_equals(duk_double_t x, duk_double_t y); DUK_INTERNAL_DECL duk_bool_t duk_float_equals(duk_float_t x, duk_float_t y); /* * Miscellaneous */ /* Example: x = 0x10 = 0b00010000 * x - 1 = 0x0f = 0b00001111 * x & (x - 1) == 0 * * x = 0x07 = 0b00000111 * x - 1 = 0x06 = 0b00000110 * x & (x - 1) != 0 * * However, incorrectly true for x == 0 so check for that explicitly. */ #define DUK_IS_POWER_OF_TWO(x) ((x) != 0U && ((x) & ((x) -1U)) == 0U) #endif /* DUK_UTIL_H_INCLUDED */ /* #include duk_strings.h */ #line 1 "duk_strings.h" /* * Shared string macros. * * Using shared macros helps minimize strings data size because it's easy * to check if an existing string could be used. String constants don't * need to be all defined here; defining a string here makes sense if there's * a high chance the string could be reused. Also, using macros allows * a call site express the exact string needed, but the macro may map to an * approximate string to reduce unique string count. Macros can also be * more easily tuned for low memory targets than #if defined()s throughout * the code base. * * Because format strings behave differently in the call site (they need to * be followed by format arguments), they use a special prefix DUK_STR_FMT_. * * On some compilers using explicit shared strings is preferable; on others * it may be better to use straight literals because the compiler will combine * them anyway, and such strings won't end up unnecessarily in a symbol table. */ #if !defined(DUK_ERRMSG_H_INCLUDED) #define DUK_ERRMSG_H_INCLUDED /* Mostly API and built-in method related */ #define DUK_STR_INTERNAL_ERROR "internal error" #define DUK_STR_UNSUPPORTED "unsupported" #define DUK_STR_INVALID_COUNT "invalid count" #define DUK_STR_INVALID_ARGS "invalid args" #define DUK_STR_INVALID_STATE "invalid state" #define DUK_STR_INVALID_INPUT "invalid input" #define DUK_STR_INVALID_LENGTH "invalid length" #define DUK_STR_NOT_CONSTRUCTABLE "not constructable" #define DUK_STR_CONSTRUCT_ONLY "constructor requires 'new'" #define DUK_STR_NOT_CALLABLE "not callable" #define DUK_STR_NOT_EXTENSIBLE "not extensible" #define DUK_STR_NOT_WRITABLE "not writable" #define DUK_STR_NOT_CONFIGURABLE "not configurable" #define DUK_STR_INVALID_CONTEXT "invalid context" #define DUK_STR_INVALID_INDEX "invalid args" #define DUK_STR_PUSH_BEYOND_ALLOC_STACK "cannot push beyond allocated stack" #define DUK_STR_NOT_UNDEFINED "unexpected type" #define DUK_STR_NOT_NULL "unexpected type" #define DUK_STR_NOT_BOOLEAN "unexpected type" #define DUK_STR_NOT_NUMBER "unexpected type" #define DUK_STR_NOT_STRING "unexpected type" #define DUK_STR_NOT_OBJECT "unexpected type" #define DUK_STR_NOT_POINTER "unexpected type" #define DUK_STR_NOT_BUFFER "not buffer" /* still in use with verbose messages */ #define DUK_STR_UNEXPECTED_TYPE "unexpected type" #define DUK_STR_NOT_THREAD "unexpected type" #define DUK_STR_NOT_COMPFUNC "unexpected type" #define DUK_STR_NOT_NATFUNC "unexpected type" #define DUK_STR_NOT_C_FUNCTION "unexpected type" #define DUK_STR_NOT_FUNCTION "unexpected type" #define DUK_STR_NOT_REGEXP "unexpected type" #define DUK_STR_TOPRIMITIVE_FAILED "coercion to primitive failed" #define DUK_STR_NUMBER_OUTSIDE_RANGE "number outside range" #define DUK_STR_NOT_OBJECT_COERCIBLE "not object coercible" #define DUK_STR_CANNOT_NUMBER_COERCE_SYMBOL "cannot number coerce Symbol" #define DUK_STR_CANNOT_STRING_COERCE_SYMBOL "cannot string coerce Symbol" #define DUK_STR_STRING_TOO_LONG "string too long" #define DUK_STR_BUFFER_TOO_LONG "buffer too long" #define DUK_STR_ALLOC_FAILED "alloc failed" #define DUK_STR_WRONG_BUFFER_TYPE "wrong buffer type" #define DUK_STR_BASE64_ENCODE_FAILED "base64 encode failed" #define DUK_STR_SOURCE_DECODE_FAILED "source decode failed" #define DUK_STR_UTF8_DECODE_FAILED "utf-8 decode failed" #define DUK_STR_BASE64_DECODE_FAILED "base64 decode failed" #define DUK_STR_HEX_DECODE_FAILED "hex decode failed" #define DUK_STR_INVALID_BYTECODE "invalid bytecode" #define DUK_STR_NO_SOURCECODE "no sourcecode" #define DUK_STR_RESULT_TOO_LONG "result too long" #define DUK_STR_INVALID_CFUNC_RC "invalid C function rc" #define DUK_STR_INVALID_INSTANCEOF_RVAL "invalid instanceof rval" #define DUK_STR_INVALID_INSTANCEOF_RVAL_NOPROTO "instanceof rval has no .prototype" /* JSON */ #define DUK_STR_FMT_PTR "%p" #define DUK_STR_FMT_INVALID_JSON "invalid json (at offset %ld)" #define DUK_STR_CYCLIC_INPUT "cyclic input" /* Generic codec */ #define DUK_STR_DEC_RECLIMIT "decode recursion limit" #define DUK_STR_ENC_RECLIMIT "encode recursion limit" /* Object property access */ #define DUK_STR_INVALID_BASE "invalid base value" #define DUK_STR_STRICT_CALLER_READ "cannot read strict 'caller'" #define DUK_STR_PROXY_REJECTED "proxy rejected" #define DUK_STR_INVALID_ARRAY_LENGTH "invalid array length" #define DUK_STR_SETTER_UNDEFINED "setter undefined" #define DUK_STR_INVALID_DESCRIPTOR "invalid descriptor" /* Proxy */ #define DUK_STR_PROXY_REVOKED "proxy revoked" #define DUK_STR_INVALID_TRAP_RESULT "invalid trap result" /* Variables */ /* Lexer */ #define DUK_STR_INVALID_ESCAPE "invalid escape" #define DUK_STR_UNTERMINATED_STRING "unterminated string" #define DUK_STR_UNTERMINATED_COMMENT "unterminated comment" #define DUK_STR_UNTERMINATED_REGEXP "unterminated regexp" #define DUK_STR_TOKEN_LIMIT "token limit" #define DUK_STR_REGEXP_SUPPORT_DISABLED "regexp support disabled" #define DUK_STR_INVALID_NUMBER_LITERAL "invalid number literal" #define DUK_STR_INVALID_TOKEN "invalid token" /* Compiler */ #define DUK_STR_PARSE_ERROR "parse error" #define DUK_STR_DUPLICATE_LABEL "duplicate label" #define DUK_STR_INVALID_LABEL "invalid label" #define DUK_STR_INVALID_ARRAY_LITERAL "invalid array literal" #define DUK_STR_INVALID_OBJECT_LITERAL "invalid object literal" #define DUK_STR_INVALID_VAR_DECLARATION "invalid variable declaration" #define DUK_STR_CANNOT_DELETE_IDENTIFIER "cannot delete identifier" #define DUK_STR_INVALID_EXPRESSION "invalid expression" #define DUK_STR_INVALID_LVALUE "invalid lvalue" #define DUK_STR_INVALID_NEWTARGET "invalid new.target" #define DUK_STR_EXPECTED_IDENTIFIER "expected identifier" #define DUK_STR_EMPTY_EXPR_NOT_ALLOWED "empty expression not allowed" #define DUK_STR_INVALID_FOR "invalid for statement" #define DUK_STR_INVALID_SWITCH "invalid switch statement" #define DUK_STR_INVALID_BREAK_CONT_LABEL "invalid break/continue label" #define DUK_STR_INVALID_RETURN "invalid return" #define DUK_STR_INVALID_TRY "invalid try" #define DUK_STR_INVALID_THROW "invalid throw" #define DUK_STR_WITH_IN_STRICT_MODE "with in strict mode" #define DUK_STR_FUNC_STMT_NOT_ALLOWED "function statement not allowed" #define DUK_STR_UNTERMINATED_STMT "unterminated statement" #define DUK_STR_INVALID_ARG_NAME "invalid argument name" #define DUK_STR_INVALID_FUNC_NAME "invalid function name" #define DUK_STR_INVALID_GETSET_NAME "invalid getter/setter name" #define DUK_STR_FUNC_NAME_REQUIRED "function name required" /* RegExp */ #define DUK_STR_INVALID_QUANTIFIER "invalid regexp quantifier" #define DUK_STR_INVALID_QUANTIFIER_NO_ATOM "quantifier without preceding atom" #define DUK_STR_INVALID_QUANTIFIER_VALUES "quantifier values invalid (qmin > qmax)" #define DUK_STR_QUANTIFIER_TOO_MANY_COPIES "quantifier requires too many atom copies" #define DUK_STR_UNEXPECTED_CLOSING_PAREN "unexpected closing parenthesis" #define DUK_STR_UNEXPECTED_END_OF_PATTERN "unexpected end of pattern" #define DUK_STR_UNEXPECTED_REGEXP_TOKEN "unexpected token in regexp" #define DUK_STR_INVALID_REGEXP_FLAGS "invalid regexp flags" #define DUK_STR_INVALID_REGEXP_ESCAPE "invalid regexp escape" #define DUK_STR_INVALID_BACKREFS "invalid backreference(s)" #define DUK_STR_INVALID_REGEXP_CHARACTER "invalid regexp character" #define DUK_STR_INVALID_REGEXP_GROUP "invalid regexp group" #define DUK_STR_UNTERMINATED_CHARCLASS "unterminated character class" #define DUK_STR_INVALID_RANGE "invalid range" /* Limits */ #define DUK_STR_VALSTACK_LIMIT "valstack limit" #define DUK_STR_CALLSTACK_LIMIT "callstack limit" #define DUK_STR_PROTOTYPE_CHAIN_LIMIT "prototype chain limit" #define DUK_STR_BOUND_CHAIN_LIMIT "function call bound chain limit" #define DUK_STR_NATIVE_STACK_LIMIT "C stack depth limit" #define DUK_STR_COMPILER_RECURSION_LIMIT "compiler recursion limit" #define DUK_STR_BYTECODE_LIMIT "bytecode limit" #define DUK_STR_REG_LIMIT "register limit" #define DUK_STR_TEMP_LIMIT "temp limit" #define DUK_STR_CONST_LIMIT "const limit" #define DUK_STR_FUNC_LIMIT "function limit" #define DUK_STR_REGEXP_COMPILER_RECURSION_LIMIT "regexp compiler recursion limit" #define DUK_STR_REGEXP_EXECUTOR_RECURSION_LIMIT "regexp executor recursion limit" #define DUK_STR_REGEXP_EXECUTOR_STEP_LIMIT "regexp step limit" #endif /* DUK_ERRMSG_H_INCLUDED */ /* #include duk_js_bytecode.h */ #line 1 "duk_js_bytecode.h" /* * ECMAScript bytecode */ #if !defined(DUK_JS_BYTECODE_H_INCLUDED) #define DUK_JS_BYTECODE_H_INCLUDED /* * Bytecode instruction layout * =========================== * * Instructions are unsigned 32-bit integers divided as follows: * * !3!3!2!2!2!2!2!2!2!2!2!2!1!1!1!1!1!1!1!1!1!1! ! ! ! ! ! ! ! ! ! ! * !1!0!9!8!7!6!5!4!3!2!1!0!9!8!7!6!5!4!3!2!1!0!9!8!7!6!5!4!3!2!1!0! * +-----------------------------------------------+---------------+ * ! C ! B ! A ! OP ! * +-----------------------------------------------+---------------+ * * OP (8 bits): opcode (DUK_OP_*), access should be fastest * consecutive opcodes allocated when opcode needs flags * A (8 bits): typically a target register number * B (8 bits): typically first source register/constant number * C (8 bits): typically second source register/constant number * * Some instructions combine BC or ABC together for larger parameter values. * Signed integers (e.g. jump offsets) are encoded as unsigned, with an * opcode specific bias. * * Some opcodes have flags which are handled by allocating consecutive * opcodes to make space for 1-N flags. Flags can also be e.g. in the 'A' * field when there's room for the specific opcode. * * For example, if three flags were needed, they could be allocated from * the opcode field as follows: * * !3!3!2!2!2!2!2!2!2!2!2!2!1!1!1!1!1!1!1!1!1!1! ! ! ! ! ! ! ! ! ! ! * !1!0!9!8!7!6!5!4!3!2!1!0!9!8!7!6!5!4!3!2!1!0!9!8!7!6!5!4!3!2!1!0! * +-----------------------------------------------+---------------+ * ! C ! B ! A ! OP !Z!Y!X! * +-----------------------------------------------+---------------+ * * Some opcodes accept a reg/const argument which is handled by allocating * flags in the OP field, see DUK_BC_ISREG() and DUK_BC_ISCONST(). The * following convention is shared by most opcodes, so that the compiler * can handle reg/const flagging without opcode specific code paths: * * !3!3!2!2!2!2!2!2!2!2!2!2!1!1!1!1!1!1!1!1!1!1! ! ! ! ! ! ! ! ! ! ! * !1!0!9!8!7!6!5!4!3!2!1!0!9!8!7!6!5!4!3!2!1!0!9!8!7!6!5!4!3!2!1!0! * +-----------------------------------------------+---------------+ * ! C ! B ! A ! OP !Y!X! * +-----------------------------------------------+---------------+ * * X 1=B is const, 0=B is reg * Y 1=C is const, 0=C is reg * * In effect OP, OP + 1, OP + 2, and OP + 3 are allocated from the * 8-bit opcode space for a single logical opcode. The base opcode * number should be divisible by 4. If the opcode is called 'FOO' * the following opcode constants would be defined: * * DUK_OP_FOO 100 // base opcode number * DUK_OP_FOO_RR 100 // FOO, B=reg, C=reg * DUK_OP_FOO_CR 101 // FOO, B=const, C=reg * DUK_OP_FOO_RC 102 // FOO, B=reg, C=const * DUK_OP_FOO_CC 103 // FOO, B=const, C=const * * If only B or C is a reg/const, the unused opcode combinations can be * used for other opcodes (which take no reg/const argument). However, * such opcode values are initially reserved, at least while opcode space * is available. For example, if 'BAR' uses B for a register field and * C is a reg/const: * * DUK_OP_BAR 116 // base opcode number * DUK_OP_BAR_RR 116 // BAR, B=reg, C=reg * DUK_OP_BAR_CR_UNUSED 117 // unused, could be repurposed * DUK_OP_BAR_RC 118 // BAR, B=reg, C=const * DUK_OP_BAR_CC_UNUSED 119 // unused, could be repurposed * * Macro naming is a bit misleading, e.g. "ABC" in macro name but the * field layout is concretely "CBA" in the register. */ typedef duk_uint32_t duk_instr_t; #define DUK_BC_SHIFT_OP 0 #define DUK_BC_SHIFT_A 8 #define DUK_BC_SHIFT_B 16 #define DUK_BC_SHIFT_C 24 #define DUK_BC_SHIFT_BC DUK_BC_SHIFT_B #define DUK_BC_SHIFT_ABC DUK_BC_SHIFT_A #define DUK_BC_UNSHIFTED_MASK_OP 0xffUL #define DUK_BC_UNSHIFTED_MASK_A 0xffUL #define DUK_BC_UNSHIFTED_MASK_B 0xffUL #define DUK_BC_UNSHIFTED_MASK_C 0xffUL #define DUK_BC_UNSHIFTED_MASK_BC 0xffffUL #define DUK_BC_UNSHIFTED_MASK_ABC 0xffffffUL #define DUK_BC_SHIFTED_MASK_OP (DUK_BC_UNSHIFTED_MASK_OP << DUK_BC_SHIFT_OP) #define DUK_BC_SHIFTED_MASK_A (DUK_BC_UNSHIFTED_MASK_A << DUK_BC_SHIFT_A) #define DUK_BC_SHIFTED_MASK_B (DUK_BC_UNSHIFTED_MASK_B << DUK_BC_SHIFT_B) #define DUK_BC_SHIFTED_MASK_C (DUK_BC_UNSHIFTED_MASK_C << DUK_BC_SHIFT_C) #define DUK_BC_SHIFTED_MASK_BC (DUK_BC_UNSHIFTED_MASK_BC << DUK_BC_SHIFT_BC) #define DUK_BC_SHIFTED_MASK_ABC (DUK_BC_UNSHIFTED_MASK_ABC << DUK_BC_SHIFT_ABC) #define DUK_DEC_OP(x) ((x) &0xffUL) #define DUK_DEC_A(x) (((x) >> 8) & 0xffUL) #define DUK_DEC_B(x) (((x) >> 16) & 0xffUL) #define DUK_DEC_C(x) (((x) >> 24) & 0xffUL) #define DUK_DEC_BC(x) (((x) >> 16) & 0xffffUL) #define DUK_DEC_ABC(x) (((x) >> 8) & 0xffffffUL) #define DUK_ENC_OP(op) ((duk_instr_t) (op)) #define DUK_ENC_OP_ABC(op, abc) ((duk_instr_t) ((((duk_instr_t) (abc)) << 8) | ((duk_instr_t) (op)))) #define DUK_ENC_OP_A_BC(op, a, bc) \ ((duk_instr_t) ((((duk_instr_t) (bc)) << 16) | (((duk_instr_t) (a)) << 8) | ((duk_instr_t) (op)))) #define DUK_ENC_OP_A_B_C(op, a, b, c) \ ((duk_instr_t) ((((duk_instr_t) (c)) << 24) | (((duk_instr_t) (b)) << 16) | (((duk_instr_t) (a)) << 8) | \ ((duk_instr_t) (op)))) #define DUK_ENC_OP_A_B(op, a, b) DUK_ENC_OP_A_B_C((op), (a), (b), 0) #define DUK_ENC_OP_A(op, a) DUK_ENC_OP_A_B_C((op), (a), 0, 0) #define DUK_ENC_OP_BC(op, bc) DUK_ENC_OP_A_BC((op), 0, (bc)) /* Get opcode base value with B/C reg/const flags cleared. */ #define DUK_BC_NOREGCONST_OP(op) ((op) &0xfc) /* Constants should be signed so that signed arithmetic involving them * won't cause values to be coerced accidentally to unsigned. */ #define DUK_BC_OP_MIN 0 #define DUK_BC_OP_MAX 0xffL #define DUK_BC_A_MIN 0 #define DUK_BC_A_MAX 0xffL #define DUK_BC_B_MIN 0 #define DUK_BC_B_MAX 0xffL #define DUK_BC_C_MIN 0 #define DUK_BC_C_MAX 0xffL #define DUK_BC_BC_MIN 0 #define DUK_BC_BC_MAX 0xffffL #define DUK_BC_ABC_MIN 0 #define DUK_BC_ABC_MAX 0xffffffL /* Masks for B/C reg/const indicator in opcode field. */ #define DUK_BC_REGCONST_B (0x01UL) #define DUK_BC_REGCONST_C (0x02UL) /* Misc. masks for opcode field. */ #define DUK_BC_INCDECP_FLAG_DEC (0x04UL) #define DUK_BC_INCDECP_FLAG_POST (0x08UL) /* Opcodes. */ #define DUK_OP_LDREG 0 #define DUK_OP_STREG 1 #define DUK_OP_JUMP 2 #define DUK_OP_LDCONST 3 #define DUK_OP_LDINT 4 #define DUK_OP_LDINTX 5 #define DUK_OP_LDTHIS 6 #define DUK_OP_LDUNDEF 7 #define DUK_OP_LDNULL 8 #define DUK_OP_LDTRUE 9 #define DUK_OP_LDFALSE 10 #define DUK_OP_GETVAR 11 #define DUK_OP_BNOT 12 #define DUK_OP_LNOT 13 #define DUK_OP_UNM 14 #define DUK_OP_UNP 15 #define DUK_OP_EQ 16 #define DUK_OP_EQ_RR 16 #define DUK_OP_EQ_CR 17 #define DUK_OP_EQ_RC 18 #define DUK_OP_EQ_CC 19 #define DUK_OP_NEQ 20 #define DUK_OP_NEQ_RR 20 #define DUK_OP_NEQ_CR 21 #define DUK_OP_NEQ_RC 22 #define DUK_OP_NEQ_CC 23 #define DUK_OP_SEQ 24 #define DUK_OP_SEQ_RR 24 #define DUK_OP_SEQ_CR 25 #define DUK_OP_SEQ_RC 26 #define DUK_OP_SEQ_CC 27 #define DUK_OP_SNEQ 28 #define DUK_OP_SNEQ_RR 28 #define DUK_OP_SNEQ_CR 29 #define DUK_OP_SNEQ_RC 30 #define DUK_OP_SNEQ_CC 31 #define DUK_OP_GT 32 #define DUK_OP_GT_RR 32 #define DUK_OP_GT_CR 33 #define DUK_OP_GT_RC 34 #define DUK_OP_GT_CC 35 #define DUK_OP_GE 36 #define DUK_OP_GE_RR 36 #define DUK_OP_GE_CR 37 #define DUK_OP_GE_RC 38 #define DUK_OP_GE_CC 39 #define DUK_OP_LT 40 #define DUK_OP_LT_RR 40 #define DUK_OP_LT_CR 41 #define DUK_OP_LT_RC 42 #define DUK_OP_LT_CC 43 #define DUK_OP_LE 44 #define DUK_OP_LE_RR 44 #define DUK_OP_LE_CR 45 #define DUK_OP_LE_RC 46 #define DUK_OP_LE_CC 47 #define DUK_OP_IFTRUE 48 #define DUK_OP_IFTRUE_R 48 #define DUK_OP_IFTRUE_C 49 #define DUK_OP_IFFALSE 50 #define DUK_OP_IFFALSE_R 50 #define DUK_OP_IFFALSE_C 51 #define DUK_OP_ADD 52 #define DUK_OP_ADD_RR 52 #define DUK_OP_ADD_CR 53 #define DUK_OP_ADD_RC 54 #define DUK_OP_ADD_CC 55 #define DUK_OP_SUB 56 #define DUK_OP_SUB_RR 56 #define DUK_OP_SUB_CR 57 #define DUK_OP_SUB_RC 58 #define DUK_OP_SUB_CC 59 #define DUK_OP_MUL 60 #define DUK_OP_MUL_RR 60 #define DUK_OP_MUL_CR 61 #define DUK_OP_MUL_RC 62 #define DUK_OP_MUL_CC 63 #define DUK_OP_DIV 64 #define DUK_OP_DIV_RR 64 #define DUK_OP_DIV_CR 65 #define DUK_OP_DIV_RC 66 #define DUK_OP_DIV_CC 67 #define DUK_OP_MOD 68 #define DUK_OP_MOD_RR 68 #define DUK_OP_MOD_CR 69 #define DUK_OP_MOD_RC 70 #define DUK_OP_MOD_CC 71 #define DUK_OP_EXP 72 #define DUK_OP_EXP_RR 72 #define DUK_OP_EXP_CR 73 #define DUK_OP_EXP_RC 74 #define DUK_OP_EXP_CC 75 #define DUK_OP_BAND 76 #define DUK_OP_BAND_RR 76 #define DUK_OP_BAND_CR 77 #define DUK_OP_BAND_RC 78 #define DUK_OP_BAND_CC 79 #define DUK_OP_BOR 80 #define DUK_OP_BOR_RR 80 #define DUK_OP_BOR_CR 81 #define DUK_OP_BOR_RC 82 #define DUK_OP_BOR_CC 83 #define DUK_OP_BXOR 84 #define DUK_OP_BXOR_RR 84 #define DUK_OP_BXOR_CR 85 #define DUK_OP_BXOR_RC 86 #define DUK_OP_BXOR_CC 87 #define DUK_OP_BASL 88 #define DUK_OP_BASL_RR 88 #define DUK_OP_BASL_CR 89 #define DUK_OP_BASL_RC 90 #define DUK_OP_BASL_CC 91 #define DUK_OP_BLSR 92 #define DUK_OP_BLSR_RR 92 #define DUK_OP_BLSR_CR 93 #define DUK_OP_BLSR_RC 94 #define DUK_OP_BLSR_CC 95 #define DUK_OP_BASR 96 #define DUK_OP_BASR_RR 96 #define DUK_OP_BASR_CR 97 #define DUK_OP_BASR_RC 98 #define DUK_OP_BASR_CC 99 #define DUK_OP_INSTOF 100 #define DUK_OP_INSTOF_RR 100 #define DUK_OP_INSTOF_CR 101 #define DUK_OP_INSTOF_RC 102 #define DUK_OP_INSTOF_CC 103 #define DUK_OP_IN 104 #define DUK_OP_IN_RR 104 #define DUK_OP_IN_CR 105 #define DUK_OP_IN_RC 106 #define DUK_OP_IN_CC 107 #define DUK_OP_GETPROP 108 #define DUK_OP_GETPROP_RR 108 #define DUK_OP_GETPROP_CR 109 #define DUK_OP_GETPROP_RC 110 #define DUK_OP_GETPROP_CC 111 #define DUK_OP_PUTPROP 112 #define DUK_OP_PUTPROP_RR 112 #define DUK_OP_PUTPROP_CR 113 #define DUK_OP_PUTPROP_RC 114 #define DUK_OP_PUTPROP_CC 115 #define DUK_OP_DELPROP 116 #define DUK_OP_DELPROP_RR 116 #define DUK_OP_DELPROP_CR_UNUSED 117 /* unused now */ #define DUK_OP_DELPROP_RC 118 #define DUK_OP_DELPROP_CC_UNUSED 119 /* unused now */ #define DUK_OP_PREINCR 120 /* pre/post opcode values have constraints, */ #define DUK_OP_PREDECR 121 /* see duk_js_executor.c and duk_js_compiler.c. */ #define DUK_OP_POSTINCR 122 #define DUK_OP_POSTDECR 123 #define DUK_OP_PREINCV 124 #define DUK_OP_PREDECV 125 #define DUK_OP_POSTINCV 126 #define DUK_OP_POSTDECV 127 #define DUK_OP_PREINCP 128 /* pre/post inc/dec prop opcodes have constraints */ #define DUK_OP_PREINCP_RR 128 #define DUK_OP_PREINCP_CR 129 #define DUK_OP_PREINCP_RC 130 #define DUK_OP_PREINCP_CC 131 #define DUK_OP_PREDECP 132 #define DUK_OP_PREDECP_RR 132 #define DUK_OP_PREDECP_CR 133 #define DUK_OP_PREDECP_RC 134 #define DUK_OP_PREDECP_CC 135 #define DUK_OP_POSTINCP 136 #define DUK_OP_POSTINCP_RR 136 #define DUK_OP_POSTINCP_CR 137 #define DUK_OP_POSTINCP_RC 138 #define DUK_OP_POSTINCP_CC 139 #define DUK_OP_POSTDECP 140 #define DUK_OP_POSTDECP_RR 140 #define DUK_OP_POSTDECP_CR 141 #define DUK_OP_POSTDECP_RC 142 #define DUK_OP_POSTDECP_CC 143 #define DUK_OP_DECLVAR 144 #define DUK_OP_DECLVAR_RR 144 #define DUK_OP_DECLVAR_CR 145 #define DUK_OP_DECLVAR_RC 146 #define DUK_OP_DECLVAR_CC 147 #define DUK_OP_REGEXP 148 #define DUK_OP_REGEXP_RR 148 #define DUK_OP_REGEXP_CR 149 #define DUK_OP_REGEXP_RC 150 #define DUK_OP_REGEXP_CC 151 #define DUK_OP_CLOSURE 152 #define DUK_OP_TYPEOF 153 #define DUK_OP_TYPEOFID 154 #define DUK_OP_PUTVAR 155 #define DUK_OP_DELVAR 156 #define DUK_OP_RETREG 157 #define DUK_OP_RETUNDEF 158 #define DUK_OP_RETCONST 159 #define DUK_OP_RETCONSTN 160 /* return const without incref (e.g. number) */ #define DUK_OP_LABEL 161 #define DUK_OP_ENDLABEL 162 #define DUK_OP_BREAK 163 #define DUK_OP_CONTINUE 164 #define DUK_OP_TRYCATCH 165 #define DUK_OP_ENDTRY 166 #define DUK_OP_ENDCATCH 167 #define DUK_OP_ENDFIN 168 #define DUK_OP_THROW 169 #define DUK_OP_INVLHS 170 #define DUK_OP_CSREG 171 #define DUK_OP_CSVAR 172 #define DUK_OP_CSVAR_RR 172 #define DUK_OP_CSVAR_CR 173 #define DUK_OP_CSVAR_RC 174 #define DUK_OP_CSVAR_CC 175 #define DUK_OP_CALL0 176 /* DUK_OP_CALL0 & 0x0F must be zero. */ #define DUK_OP_CALL1 177 #define DUK_OP_CALL2 178 #define DUK_OP_CALL3 179 #define DUK_OP_CALL4 180 #define DUK_OP_CALL5 181 #define DUK_OP_CALL6 182 #define DUK_OP_CALL7 183 #define DUK_OP_CALL8 184 #define DUK_OP_CALL9 185 #define DUK_OP_CALL10 186 #define DUK_OP_CALL11 187 #define DUK_OP_CALL12 188 #define DUK_OP_CALL13 189 #define DUK_OP_CALL14 190 #define DUK_OP_CALL15 191 #define DUK_OP_NEWOBJ 192 #define DUK_OP_NEWARR 193 #define DUK_OP_MPUTOBJ 194 #define DUK_OP_MPUTOBJI 195 #define DUK_OP_INITSET 196 #define DUK_OP_INITGET 197 #define DUK_OP_MPUTARR 198 #define DUK_OP_MPUTARRI 199 #define DUK_OP_SETALEN 200 #define DUK_OP_INITENUM 201 #define DUK_OP_NEXTENUM 202 #define DUK_OP_NEWTARGET 203 #define DUK_OP_DEBUGGER 204 #define DUK_OP_NOP 205 #define DUK_OP_INVALID 206 #define DUK_OP_UNUSED207 207 #define DUK_OP_GETPROPC 208 #define DUK_OP_GETPROPC_RR 208 #define DUK_OP_GETPROPC_CR 209 #define DUK_OP_GETPROPC_RC 210 #define DUK_OP_GETPROPC_CC 211 #define DUK_OP_UNUSED212 212 #define DUK_OP_UNUSED213 213 #define DUK_OP_UNUSED214 214 #define DUK_OP_UNUSED215 215 #define DUK_OP_UNUSED216 216 #define DUK_OP_UNUSED217 217 #define DUK_OP_UNUSED218 218 #define DUK_OP_UNUSED219 219 #define DUK_OP_UNUSED220 220 #define DUK_OP_UNUSED221 221 #define DUK_OP_UNUSED222 222 #define DUK_OP_UNUSED223 223 #define DUK_OP_UNUSED224 224 #define DUK_OP_UNUSED225 225 #define DUK_OP_UNUSED226 226 #define DUK_OP_UNUSED227 227 #define DUK_OP_UNUSED228 228 #define DUK_OP_UNUSED229 229 #define DUK_OP_UNUSED230 230 #define DUK_OP_UNUSED231 231 #define DUK_OP_UNUSED232 232 #define DUK_OP_UNUSED233 233 #define DUK_OP_UNUSED234 234 #define DUK_OP_UNUSED235 235 #define DUK_OP_UNUSED236 236 #define DUK_OP_UNUSED237 237 #define DUK_OP_UNUSED238 238 #define DUK_OP_UNUSED239 239 #define DUK_OP_UNUSED240 240 #define DUK_OP_UNUSED241 241 #define DUK_OP_UNUSED242 242 #define DUK_OP_UNUSED243 243 #define DUK_OP_UNUSED244 244 #define DUK_OP_UNUSED245 245 #define DUK_OP_UNUSED246 246 #define DUK_OP_UNUSED247 247 #define DUK_OP_UNUSED248 248 #define DUK_OP_UNUSED249 249 #define DUK_OP_UNUSED250 250 #define DUK_OP_UNUSED251 251 #define DUK_OP_UNUSED252 252 #define DUK_OP_UNUSED253 253 #define DUK_OP_UNUSED254 254 #define DUK_OP_UNUSED255 255 #define DUK_OP_NONE 256 /* dummy value used as marker (doesn't fit in 8-bit field) */ /* XXX: Allocate flags from opcode field? Would take 16 opcode slots * but avoids shuffling in more cases. Maybe not worth it. */ /* DUK_OP_TRYCATCH flags in A. */ #define DUK_BC_TRYCATCH_FLAG_HAVE_CATCH (1U << 0) #define DUK_BC_TRYCATCH_FLAG_HAVE_FINALLY (1U << 1) #define DUK_BC_TRYCATCH_FLAG_CATCH_BINDING (1U << 2) #define DUK_BC_TRYCATCH_FLAG_WITH_BINDING (1U << 3) /* DUK_OP_DECLVAR flags in A; bottom bits are reserved for propdesc flags * (DUK_PROPDESC_FLAG_XXX). */ #define DUK_BC_DECLVAR_FLAG_FUNC_DECL (1U << 4) /* function declaration */ /* DUK_OP_CALLn flags, part of opcode field. Three lowest bits must match * DUK_CALL_FLAG_xxx directly. */ #define DUK_BC_CALL_FLAG_TAILCALL (1U << 0) #define DUK_BC_CALL_FLAG_CONSTRUCT (1U << 1) #define DUK_BC_CALL_FLAG_CALLED_AS_EVAL (1U << 2) #define DUK_BC_CALL_FLAG_INDIRECT (1U << 3) /* Misc constants and helper macros. */ #define DUK_BC_LDINT_BIAS (1L << 15) #define DUK_BC_LDINTX_SHIFT 16 #define DUK_BC_JUMP_BIAS (1L << 23) #endif /* DUK_JS_BYTECODE_H_INCLUDED */ /* #include duk_lexer.h */ #line 1 "duk_lexer.h" /* * Lexer defines. */ #if !defined(DUK_LEXER_H_INCLUDED) #define DUK_LEXER_H_INCLUDED typedef void (*duk_re_range_callback)(void *user, duk_codepoint_t r1, duk_codepoint_t r2, duk_bool_t direct); /* * A token is interpreted as any possible production of InputElementDiv * and InputElementRegExp, see E5 Section 7 in its entirety. Note that * the E5 "Token" production does not cover all actual tokens of the * language (which is explicitly stated in the specification, Section 7.5). * Null and boolean literals are defined as part of both ReservedWord * (E5 Section 7.6.1) and Literal (E5 Section 7.8) productions. Here, * null and boolean values have literal tokens, and are not reserved * words. * * Decimal literal negative/positive sign is -not- part of DUK_TOK_NUMBER. * The number tokens always have a non-negative value. The unary minus * operator in "-1.0" is optimized during compilation to yield a single * negative constant. * * Token numbering is free except that reserved words are required to be * in a continuous range and in a particular order. See genstrings.py. */ #define DUK_LEXER_INITCTX(ctx) duk_lexer_initctx((ctx)) #define DUK_LEXER_SETPOINT(ctx, pt) duk_lexer_setpoint((ctx), (pt)) #define DUK_LEXER_GETPOINT(ctx, pt) duk_lexer_getpoint((ctx), (pt)) /* Currently 6 characters of lookup are actually needed (duk_lexer.c). */ #define DUK_LEXER_WINDOW_SIZE 6 #if defined(DUK_USE_LEXER_SLIDING_WINDOW) #define DUK_LEXER_BUFFER_SIZE 64 #endif #define DUK_TOK_MINVAL 0 /* returned after EOF (infinite amount) */ #define DUK_TOK_EOF 0 /* identifier names (E5 Section 7.6) */ #define DUK_TOK_IDENTIFIER 1 /* reserved words: keywords */ #define DUK_TOK_START_RESERVED 2 #define DUK_TOK_BREAK 2 #define DUK_TOK_CASE 3 #define DUK_TOK_CATCH 4 #define DUK_TOK_CONTINUE 5 #define DUK_TOK_DEBUGGER 6 #define DUK_TOK_DEFAULT 7 #define DUK_TOK_DELETE 8 #define DUK_TOK_DO 9 #define DUK_TOK_ELSE 10 #define DUK_TOK_FINALLY 11 #define DUK_TOK_FOR 12 #define DUK_TOK_FUNCTION 13 #define DUK_TOK_IF 14 #define DUK_TOK_IN 15 #define DUK_TOK_INSTANCEOF 16 #define DUK_TOK_NEW 17 #define DUK_TOK_RETURN 18 #define DUK_TOK_SWITCH 19 #define DUK_TOK_THIS 20 #define DUK_TOK_THROW 21 #define DUK_TOK_TRY 22 #define DUK_TOK_TYPEOF 23 #define DUK_TOK_VAR 24 #define DUK_TOK_CONST 25 #define DUK_TOK_VOID 26 #define DUK_TOK_WHILE 27 #define DUK_TOK_WITH 28 /* reserved words: future reserved words */ #define DUK_TOK_CLASS 29 #define DUK_TOK_ENUM 30 #define DUK_TOK_EXPORT 31 #define DUK_TOK_EXTENDS 32 #define DUK_TOK_IMPORT 33 #define DUK_TOK_SUPER 34 /* "null", "true", and "false" are always reserved words. * Note that "get" and "set" are not! */ #define DUK_TOK_NULL 35 #define DUK_TOK_TRUE 36 #define DUK_TOK_FALSE 37 /* reserved words: additional future reserved words in strict mode */ #define DUK_TOK_START_STRICT_RESERVED 38 /* inclusive */ #define DUK_TOK_IMPLEMENTS 38 #define DUK_TOK_INTERFACE 39 #define DUK_TOK_LET 40 #define DUK_TOK_PACKAGE 41 #define DUK_TOK_PRIVATE 42 #define DUK_TOK_PROTECTED 43 #define DUK_TOK_PUBLIC 44 #define DUK_TOK_STATIC 45 #define DUK_TOK_YIELD 46 #define DUK_TOK_END_RESERVED 47 /* exclusive */ /* "get" and "set" are tokens but NOT ReservedWords. They are currently * parsed and identifiers and these defines are actually now unused. */ #define DUK_TOK_GET 47 #define DUK_TOK_SET 48 /* punctuators (unlike the spec, also includes "/" and "/=") */ #define DUK_TOK_LCURLY 49 #define DUK_TOK_RCURLY 50 #define DUK_TOK_LBRACKET 51 #define DUK_TOK_RBRACKET 52 #define DUK_TOK_LPAREN 53 #define DUK_TOK_RPAREN 54 #define DUK_TOK_PERIOD 55 #define DUK_TOK_SEMICOLON 56 #define DUK_TOK_COMMA 57 #define DUK_TOK_LT 58 #define DUK_TOK_GT 59 #define DUK_TOK_LE 60 #define DUK_TOK_GE 61 #define DUK_TOK_EQ 62 #define DUK_TOK_NEQ 63 #define DUK_TOK_SEQ 64 #define DUK_TOK_SNEQ 65 #define DUK_TOK_ADD 66 #define DUK_TOK_SUB 67 #define DUK_TOK_MUL 68 #define DUK_TOK_DIV 69 #define DUK_TOK_MOD 70 #define DUK_TOK_EXP 71 #define DUK_TOK_INCREMENT 72 #define DUK_TOK_DECREMENT 73 #define DUK_TOK_ALSHIFT 74 /* named "arithmetic" because result is signed */ #define DUK_TOK_ARSHIFT 75 #define DUK_TOK_RSHIFT 76 #define DUK_TOK_BAND 77 #define DUK_TOK_BOR 78 #define DUK_TOK_BXOR 79 #define DUK_TOK_LNOT 80 #define DUK_TOK_BNOT 81 #define DUK_TOK_LAND 82 #define DUK_TOK_LOR 83 #define DUK_TOK_QUESTION 84 #define DUK_TOK_COLON 85 #define DUK_TOK_EQUALSIGN 86 #define DUK_TOK_ADD_EQ 87 #define DUK_TOK_SUB_EQ 88 #define DUK_TOK_MUL_EQ 89 #define DUK_TOK_DIV_EQ 90 #define DUK_TOK_MOD_EQ 91 #define DUK_TOK_EXP_EQ 92 #define DUK_TOK_ALSHIFT_EQ 93 #define DUK_TOK_ARSHIFT_EQ 94 #define DUK_TOK_RSHIFT_EQ 95 #define DUK_TOK_BAND_EQ 96 #define DUK_TOK_BOR_EQ 97 #define DUK_TOK_BXOR_EQ 98 /* literals (E5 Section 7.8), except null, true, false, which are treated * like reserved words (above). */ #define DUK_TOK_NUMBER 99 #define DUK_TOK_STRING 100 #define DUK_TOK_REGEXP 101 #define DUK_TOK_MAXVAL 101 /* inclusive */ #define DUK_TOK_INVALID DUK_SMALL_UINT_MAX /* Convert heap string index to a token (reserved words) */ #define DUK_STRIDX_TO_TOK(x) ((x) -DUK_STRIDX_START_RESERVED + DUK_TOK_START_RESERVED) /* Sanity check */ #if (DUK_TOK_MAXVAL > 255) #error DUK_TOK_MAXVAL too large, code assumes it fits into 8 bits #endif /* Sanity checks for string and token defines */ #if (DUK_STRIDX_TO_TOK(DUK_STRIDX_BREAK) != DUK_TOK_BREAK) #error mismatch in token defines #endif #if (DUK_STRIDX_TO_TOK(DUK_STRIDX_CASE) != DUK_TOK_CASE) #error mismatch in token defines #endif #if (DUK_STRIDX_TO_TOK(DUK_STRIDX_CATCH) != DUK_TOK_CATCH) #error mismatch in token defines #endif #if (DUK_STRIDX_TO_TOK(DUK_STRIDX_CONTINUE) != DUK_TOK_CONTINUE) #error mismatch in token defines #endif #if (DUK_STRIDX_TO_TOK(DUK_STRIDX_DEBUGGER) != DUK_TOK_DEBUGGER) #error mismatch in token defines #endif #if (DUK_STRIDX_TO_TOK(DUK_STRIDX_DEFAULT) != DUK_TOK_DEFAULT) #error mismatch in token defines #endif #if (DUK_STRIDX_TO_TOK(DUK_STRIDX_DELETE) != DUK_TOK_DELETE) #error mismatch in token defines #endif #if (DUK_STRIDX_TO_TOK(DUK_STRIDX_DO) != DUK_TOK_DO) #error mismatch in token defines #endif #if (DUK_STRIDX_TO_TOK(DUK_STRIDX_ELSE) != DUK_TOK_ELSE) #error mismatch in token defines #endif #if (DUK_STRIDX_TO_TOK(DUK_STRIDX_FINALLY) != DUK_TOK_FINALLY) #error mismatch in token defines #endif #if (DUK_STRIDX_TO_TOK(DUK_STRIDX_FOR) != DUK_TOK_FOR) #error mismatch in token defines #endif #if (DUK_STRIDX_TO_TOK(DUK_STRIDX_LC_FUNCTION) != DUK_TOK_FUNCTION) #error mismatch in token defines #endif #if (DUK_STRIDX_TO_TOK(DUK_STRIDX_IF) != DUK_TOK_IF) #error mismatch in token defines #endif #if (DUK_STRIDX_TO_TOK(DUK_STRIDX_IN) != DUK_TOK_IN) #error mismatch in token defines #endif #if (DUK_STRIDX_TO_TOK(DUK_STRIDX_INSTANCEOF) != DUK_TOK_INSTANCEOF) #error mismatch in token defines #endif #if (DUK_STRIDX_TO_TOK(DUK_STRIDX_NEW) != DUK_TOK_NEW) #error mismatch in token defines #endif #if (DUK_STRIDX_TO_TOK(DUK_STRIDX_RETURN) != DUK_TOK_RETURN) #error mismatch in token defines #endif #if (DUK_STRIDX_TO_TOK(DUK_STRIDX_SWITCH) != DUK_TOK_SWITCH) #error mismatch in token defines #endif #if (DUK_STRIDX_TO_TOK(DUK_STRIDX_THIS) != DUK_TOK_THIS) #error mismatch in token defines #endif #if (DUK_STRIDX_TO_TOK(DUK_STRIDX_THROW) != DUK_TOK_THROW) #error mismatch in token defines #endif #if (DUK_STRIDX_TO_TOK(DUK_STRIDX_TRY) != DUK_TOK_TRY) #error mismatch in token defines #endif #if (DUK_STRIDX_TO_TOK(DUK_STRIDX_TYPEOF) != DUK_TOK_TYPEOF) #error mismatch in token defines #endif #if (DUK_STRIDX_TO_TOK(DUK_STRIDX_VAR) != DUK_TOK_VAR) #error mismatch in token defines #endif #if (DUK_STRIDX_TO_TOK(DUK_STRIDX_VOID) != DUK_TOK_VOID) #error mismatch in token defines #endif #if (DUK_STRIDX_TO_TOK(DUK_STRIDX_WHILE) != DUK_TOK_WHILE) #error mismatch in token defines #endif #if (DUK_STRIDX_TO_TOK(DUK_STRIDX_WITH) != DUK_TOK_WITH) #error mismatch in token defines #endif #if (DUK_STRIDX_TO_TOK(DUK_STRIDX_CLASS) != DUK_TOK_CLASS) #error mismatch in token defines #endif #if (DUK_STRIDX_TO_TOK(DUK_STRIDX_CONST) != DUK_TOK_CONST) #error mismatch in token defines #endif #if (DUK_STRIDX_TO_TOK(DUK_STRIDX_ENUM) != DUK_TOK_ENUM) #error mismatch in token defines #endif #if (DUK_STRIDX_TO_TOK(DUK_STRIDX_EXPORT) != DUK_TOK_EXPORT) #error mismatch in token defines #endif #if (DUK_STRIDX_TO_TOK(DUK_STRIDX_EXTENDS) != DUK_TOK_EXTENDS) #error mismatch in token defines #endif #if (DUK_STRIDX_TO_TOK(DUK_STRIDX_IMPORT) != DUK_TOK_IMPORT) #error mismatch in token defines #endif #if (DUK_STRIDX_TO_TOK(DUK_STRIDX_SUPER) != DUK_TOK_SUPER) #error mismatch in token defines #endif #if (DUK_STRIDX_TO_TOK(DUK_STRIDX_LC_NULL) != DUK_TOK_NULL) #error mismatch in token defines #endif #if (DUK_STRIDX_TO_TOK(DUK_STRIDX_TRUE) != DUK_TOK_TRUE) #error mismatch in token defines #endif #if (DUK_STRIDX_TO_TOK(DUK_STRIDX_FALSE) != DUK_TOK_FALSE) #error mismatch in token defines #endif #if (DUK_STRIDX_TO_TOK(DUK_STRIDX_IMPLEMENTS) != DUK_TOK_IMPLEMENTS) #error mismatch in token defines #endif #if (DUK_STRIDX_TO_TOK(DUK_STRIDX_INTERFACE) != DUK_TOK_INTERFACE) #error mismatch in token defines #endif #if (DUK_STRIDX_TO_TOK(DUK_STRIDX_LET) != DUK_TOK_LET) #error mismatch in token defines #endif #if (DUK_STRIDX_TO_TOK(DUK_STRIDX_PACKAGE) != DUK_TOK_PACKAGE) #error mismatch in token defines #endif #if (DUK_STRIDX_TO_TOK(DUK_STRIDX_PRIVATE) != DUK_TOK_PRIVATE) #error mismatch in token defines #endif #if (DUK_STRIDX_TO_TOK(DUK_STRIDX_PROTECTED) != DUK_TOK_PROTECTED) #error mismatch in token defines #endif #if (DUK_STRIDX_TO_TOK(DUK_STRIDX_PUBLIC) != DUK_TOK_PUBLIC) #error mismatch in token defines #endif #if (DUK_STRIDX_TO_TOK(DUK_STRIDX_STATIC) != DUK_TOK_STATIC) #error mismatch in token defines #endif #if (DUK_STRIDX_TO_TOK(DUK_STRIDX_YIELD) != DUK_TOK_YIELD) #error mismatch in token defines #endif /* Regexp tokens */ #define DUK_RETOK_EOF 0 #define DUK_RETOK_DISJUNCTION 1 #define DUK_RETOK_QUANTIFIER 2 #define DUK_RETOK_ASSERT_START 3 #define DUK_RETOK_ASSERT_END 4 #define DUK_RETOK_ASSERT_WORD_BOUNDARY 5 #define DUK_RETOK_ASSERT_NOT_WORD_BOUNDARY 6 #define DUK_RETOK_ASSERT_START_POS_LOOKAHEAD 7 #define DUK_RETOK_ASSERT_START_NEG_LOOKAHEAD 8 #define DUK_RETOK_ATOM_PERIOD 9 #define DUK_RETOK_ATOM_CHAR 10 #define DUK_RETOK_ATOM_DIGIT 11 /* assumptions in regexp compiler */ #define DUK_RETOK_ATOM_NOT_DIGIT 12 /* -""- */ #define DUK_RETOK_ATOM_WHITE 13 /* -""- */ #define DUK_RETOK_ATOM_NOT_WHITE 14 /* -""- */ #define DUK_RETOK_ATOM_WORD_CHAR 15 /* -""- */ #define DUK_RETOK_ATOM_NOT_WORD_CHAR 16 /* -""- */ #define DUK_RETOK_ATOM_BACKREFERENCE 17 #define DUK_RETOK_ATOM_START_CAPTURE_GROUP 18 #define DUK_RETOK_ATOM_START_NONCAPTURE_GROUP 19 #define DUK_RETOK_ATOM_START_CHARCLASS 20 #define DUK_RETOK_ATOM_START_CHARCLASS_INVERTED 21 #define DUK_RETOK_ATOM_END_GROUP 22 /* Constants for duk_lexer_ctx.buf. */ #define DUK_LEXER_TEMP_BUF_LIMIT 256 /* A token value. Can be memcpy()'d, but note that slot1/slot2 values are on the valstack. * Some fields (like num, str1, str2) are only valid for specific token types and may have * stale values otherwise. */ struct duk_token { duk_small_uint_t t; /* token type (with reserved word identification) */ duk_small_uint_t t_nores; /* token type (with reserved words as DUK_TOK_IDENTIFER) */ duk_double_t num; /* numeric value of token */ duk_hstring *str1; /* string 1 of token (borrowed, stored to ctx->slot1_idx) */ duk_hstring *str2; /* string 2 of token (borrowed, stored to ctx->slot2_idx) */ duk_size_t start_offset; /* start byte offset of token in lexer input */ duk_int_t start_line; /* start line of token (first char) */ duk_int_t num_escapes; /* number of escapes and line continuations (for directive prologue) */ duk_bool_t lineterm; /* token was preceded by a lineterm */ duk_bool_t allow_auto_semi; /* token allows automatic semicolon insertion (eof or preceded by newline) */ }; #define DUK_RE_QUANTIFIER_INFINITE ((duk_uint32_t) 0xffffffffUL) /* A regexp token value. */ struct duk_re_token { duk_small_uint_t t; /* token type */ duk_small_uint_t greedy; duk_uint32_t num; /* numeric value (character, count) */ duk_uint32_t qmin; duk_uint32_t qmax; }; /* A structure for 'snapshotting' a point for rewinding */ struct duk_lexer_point { duk_size_t offset; duk_int_t line; }; /* Lexer codepoint with additional info like offset/line number */ struct duk_lexer_codepoint { duk_codepoint_t codepoint; duk_size_t offset; duk_int_t line; }; /* Lexer context. Same context is used for ECMAScript and Regexp parsing. */ struct duk_lexer_ctx { #if defined(DUK_USE_LEXER_SLIDING_WINDOW) duk_lexer_codepoint *window; /* unicode code points, window[0] is always next, points to 'buffer' */ duk_lexer_codepoint buffer[DUK_LEXER_BUFFER_SIZE]; #else duk_lexer_codepoint window[DUK_LEXER_WINDOW_SIZE]; /* unicode code points, window[0] is always next */ #endif duk_hthread *thr; /* thread; minimizes argument passing */ const duk_uint8_t *input; /* input string (may be a user pointer) */ duk_size_t input_length; /* input byte length */ duk_size_t input_offset; /* input offset for window leading edge (not window[0]) */ duk_int_t input_line; /* input linenumber at input_offset (not window[0]), init to 1 */ duk_idx_t slot1_idx; /* valstack slot for 1st token value */ duk_idx_t slot2_idx; /* valstack slot for 2nd token value */ duk_idx_t buf_idx; /* valstack slot for temp buffer */ duk_hbuffer_dynamic *buf; /* temp accumulation buffer */ duk_bufwriter_ctx bw; /* bufwriter for temp accumulation */ duk_int_t token_count; /* number of tokens parsed */ duk_int_t token_limit; /* maximum token count before error (sanity backstop) */ duk_small_uint_t flags; /* lexer flags, use compiler flag defines for now */ }; /* * Prototypes */ DUK_INTERNAL_DECL void duk_lexer_initctx(duk_lexer_ctx *lex_ctx); DUK_INTERNAL_DECL void duk_lexer_getpoint(duk_lexer_ctx *lex_ctx, duk_lexer_point *pt); DUK_INTERNAL_DECL void duk_lexer_setpoint(duk_lexer_ctx *lex_ctx, duk_lexer_point *pt); DUK_INTERNAL_DECL void duk_lexer_parse_js_input_element(duk_lexer_ctx *lex_ctx, duk_token *out_token, duk_bool_t strict_mode, duk_bool_t regexp_mode); #if defined(DUK_USE_REGEXP_SUPPORT) DUK_INTERNAL_DECL void duk_lexer_parse_re_token(duk_lexer_ctx *lex_ctx, duk_re_token *out_token); DUK_INTERNAL_DECL void duk_lexer_parse_re_ranges(duk_lexer_ctx *lex_ctx, duk_re_range_callback gen_range, void *userdata); #endif /* DUK_USE_REGEXP_SUPPORT */ #endif /* DUK_LEXER_H_INCLUDED */ /* #include duk_js_compiler.h */ #line 1 "duk_js_compiler.h" /* * ECMAScript compiler. */ #if !defined(DUK_JS_COMPILER_H_INCLUDED) #define DUK_JS_COMPILER_H_INCLUDED /* ECMAScript compiler limits */ #define DUK_COMPILER_TOKEN_LIMIT 100000000L /* 1e8: protects against deeply nested inner functions */ /* maximum loopcount for peephole optimization */ #define DUK_COMPILER_PEEPHOLE_MAXITER 3 /* maximum bytecode length in instructions */ #define DUK_COMPILER_MAX_BYTECODE_LENGTH (256L * 1024L * 1024L) /* 1 GB */ /* * Compiler intermediate values * * Intermediate values describe either plain values (e.g. strings or * numbers) or binary operations which have not yet been coerced into * either a left-hand-side or right-hand-side role (e.g. object property). */ #define DUK_IVAL_NONE 0 /* no value */ #define DUK_IVAL_PLAIN 1 /* register, constant, or value */ #define DUK_IVAL_ARITH 2 /* binary arithmetic; DUK_OP_ADD, DUK_OP_EQ, other binary ops */ #define DUK_IVAL_PROP 3 /* property access */ #define DUK_IVAL_VAR 4 /* variable access */ #define DUK_ISPEC_NONE 0 /* no value */ #define DUK_ISPEC_VALUE 1 /* value resides in 'valstack_idx' */ #define DUK_ISPEC_REGCONST 2 /* value resides in a register or constant */ /* Bit mask which indicates that a regconst is a constant instead of a register. * Chosen so that when a regconst is cast to duk_int32_t, all consts are * negative values. */ #define DUK_REGCONST_CONST_MARKER DUK_INT32_MIN /* = -0x80000000 */ /* Type to represent a reg/const reference during compilation, with <0 * indicating a constant. Some call sites also use -1 to indicate 'none'. */ typedef duk_int32_t duk_regconst_t; typedef struct { duk_small_uint_t t; /* DUK_ISPEC_XXX */ duk_regconst_t regconst; duk_idx_t valstack_idx; /* always set; points to a reserved valstack slot */ } duk_ispec; typedef struct { /* * PLAIN: x1 * ARITH: x1 <op> x2 * PROP: x1.x2 * VAR: x1 (name) */ /* XXX: can be optimized for smaller footprint esp. on 32-bit environments */ duk_small_uint_t t; /* DUK_IVAL_XXX */ duk_small_uint_t op; /* bytecode opcode for binary ops */ duk_ispec x1; duk_ispec x2; } duk_ivalue; /* * Bytecode instruction representation during compilation * * Contains the actual instruction and (optionally) debug info. */ struct duk_compiler_instr { duk_instr_t ins; #if defined(DUK_USE_PC2LINE) duk_uint32_t line; #endif }; /* * Compiler state */ #define DUK_LABEL_FLAG_ALLOW_BREAK (1U << 0) #define DUK_LABEL_FLAG_ALLOW_CONTINUE (1U << 1) #define DUK_DECL_TYPE_VAR 0 #define DUK_DECL_TYPE_FUNC 1 /* XXX: optimize to 16 bytes */ typedef struct { duk_small_uint_t flags; duk_int_t label_id; /* numeric label_id (-1 reserved as marker) */ duk_hstring *h_label; /* borrowed label name */ duk_int_t catch_depth; /* catch depth at point of definition */ duk_int_t pc_label; /* pc of label statement: * pc+1: break jump site * pc+2: continue jump site */ /* Fast jumps (which avoid longjmp) jump directly to the jump sites * which are always known even while the iteration/switch statement * is still being parsed. A final peephole pass "straightens out" * the jumps. */ } duk_labelinfo; /* Compiling state of one function, eventually converted to duk_hcompfunc */ struct duk_compiler_func { /* These pointers are at the start of the struct so that they pack * nicely. Mixing pointers and integer values is bad on some * platforms (e.g. if int is 32 bits and pointers are 64 bits). */ duk_bufwriter_ctx bw_code; /* bufwriter for code */ duk_hstring *h_name; /* function name (borrowed reference), ends up in _name */ /* h_code: held in bw_code */ duk_hobject *h_consts; /* array */ duk_hobject *h_funcs; /* array of function templates: [func1, offset1, line1, func2, offset2, line2] * offset/line points to closing brace to allow skipping on pass 2 */ duk_hobject *h_decls; /* array of declarations: [ name1, val1, name2, val2, ... ] * valN = (typeN) | (fnum << 8), where fnum is inner func number (0 for vars) * record function and variable declarations in pass 1 */ duk_hobject *h_labelnames; /* array of active label names */ duk_hbuffer_dynamic *h_labelinfos; /* C array of duk_labelinfo */ duk_hobject *h_argnames; /* array of formal argument names (-> _Formals) */ duk_hobject *h_varmap; /* variable map for pass 2 (identifier -> register number or null (unmapped)) */ /* Value stack indices for tracking objects. */ /* code_idx: not needed */ duk_idx_t consts_idx; duk_idx_t funcs_idx; duk_idx_t decls_idx; duk_idx_t labelnames_idx; duk_idx_t labelinfos_idx; duk_idx_t argnames_idx; duk_idx_t varmap_idx; /* Temp reg handling. */ duk_regconst_t temp_first; /* first register that is a temporary (below: variables) */ duk_regconst_t temp_next; /* next temporary register to allocate */ duk_regconst_t temp_max; /* highest value of temp_reg (temp_max - 1 is highest used reg) */ /* Shuffle registers if large number of regs/consts. */ duk_regconst_t shuffle1; duk_regconst_t shuffle2; duk_regconst_t shuffle3; /* Stats for current expression being parsed. */ duk_int_t nud_count; duk_int_t led_count; duk_int_t paren_level; /* parenthesis count, 0 = top level */ duk_bool_t expr_lhs; /* expression is left-hand-side compatible */ duk_bool_t allow_in; /* current paren level allows 'in' token */ /* Misc. */ duk_int_t stmt_next; /* statement id allocation (running counter) */ duk_int_t label_next; /* label id allocation (running counter) */ duk_int_t catch_depth; /* catch stack depth */ duk_int_t with_depth; /* with stack depth (affects identifier lookups) */ duk_int_t fnum_next; /* inner function numbering */ duk_int_t num_formals; /* number of formal arguments */ duk_regconst_t reg_stmt_value; /* register for writing value of 'non-empty' statements (global or eval code), -1 is marker */ #if defined(DUK_USE_DEBUGGER_SUPPORT) duk_int_t min_line; /* XXX: typing (duk_hcompfunc has duk_uint32_t) */ duk_int_t max_line; #endif /* Status booleans. */ duk_uint8_t is_function; /* is an actual function (not global/eval code) */ duk_uint8_t is_eval; /* is eval code */ duk_uint8_t is_global; /* is global code */ duk_uint8_t is_namebinding; /* needs a name binding */ duk_uint8_t is_constructable; /* result is constructable */ duk_uint8_t is_setget; /* is a setter/getter */ duk_uint8_t is_strict; /* function is strict */ duk_uint8_t is_notail; /* function must not be tail called */ duk_uint8_t in_directive_prologue; /* parsing in "directive prologue", recognize directives */ duk_uint8_t in_scanning; /* parsing in "scanning" phase (first pass) */ duk_uint8_t may_direct_eval; /* function may call direct eval */ duk_uint8_t id_access_arguments; /* function refers to 'arguments' identifier */ duk_uint8_t id_access_slow; /* function makes one or more slow path accesses that won't match own static variables */ duk_uint8_t id_access_slow_own; /* function makes one or more slow path accesses that may match own static variables */ duk_uint8_t is_arguments_shadowed; /* argument/function declaration shadows 'arguments' */ duk_uint8_t needs_shuffle; /* function needs shuffle registers */ duk_uint8_t reject_regexp_in_adv; /* reject RegExp literal on next advance() call; needed for handling IdentifierName productions */ duk_uint8_t allow_regexp_in_adv; /* allow RegExp literal on next advance() call */ }; struct duk_compiler_ctx { duk_hthread *thr; /* filename being compiled (ends up in functions' '_filename' property) */ duk_hstring *h_filename; /* borrowed reference */ /* lexing (tokenization) state (contains two valstack slot indices) */ duk_lexer_ctx lex; /* current and previous token for parsing */ duk_token prev_token; duk_token curr_token; duk_idx_t tok11_idx; /* curr_token slot1 (matches 'lex' slot1_idx) */ duk_idx_t tok12_idx; /* curr_token slot2 (matches 'lex' slot2_idx) */ duk_idx_t tok21_idx; /* prev_token slot1 */ duk_idx_t tok22_idx; /* prev_token slot2 */ /* recursion limit */ duk_int_t recursion_depth; duk_int_t recursion_limit; /* code emission temporary */ duk_int_t emit_jumpslot_pc; /* current function being compiled (embedded instead of pointer for more compact access) */ duk_compiler_func curr_func; }; /* * Prototypes */ DUK_INTERNAL_DECL void duk_js_compile(duk_hthread *thr, const duk_uint8_t *src_buffer, duk_size_t src_length, duk_small_uint_t flags); #endif /* DUK_JS_COMPILER_H_INCLUDED */ /* #include duk_regexp.h */ #line 1 "duk_regexp.h" /* * Regular expression structs, constants, and bytecode defines. */ #if !defined(DUK_REGEXP_H_INCLUDED) #define DUK_REGEXP_H_INCLUDED /* maximum bytecode copies for {n,m} quantifiers */ #define DUK_RE_MAX_ATOM_COPIES 1000 /* regexp compilation limits */ #define DUK_RE_COMPILE_TOKEN_LIMIT 100000000L /* 1e8 */ /* regexp execution limits */ #define DUK_RE_EXECUTE_STEPS_LIMIT 1000000000L /* 1e9 */ /* regexp opcodes */ #define DUK_REOP_MATCH 1 #define DUK_REOP_CHAR 2 #define DUK_REOP_PERIOD 3 #define DUK_REOP_RANGES 4 #define DUK_REOP_INVRANGES 5 #define DUK_REOP_JUMP 6 #define DUK_REOP_SPLIT1 7 #define DUK_REOP_SPLIT2 8 #define DUK_REOP_SQMINIMAL 9 #define DUK_REOP_SQGREEDY 10 #define DUK_REOP_SAVE 11 #define DUK_REOP_WIPERANGE 12 #define DUK_REOP_LOOKPOS 13 #define DUK_REOP_LOOKNEG 14 #define DUK_REOP_BACKREFERENCE 15 #define DUK_REOP_ASSERT_START 16 #define DUK_REOP_ASSERT_END 17 #define DUK_REOP_ASSERT_WORD_BOUNDARY 18 #define DUK_REOP_ASSERT_NOT_WORD_BOUNDARY 19 /* flags */ #define DUK_RE_FLAG_GLOBAL (1U << 0) #define DUK_RE_FLAG_IGNORE_CASE (1U << 1) #define DUK_RE_FLAG_MULTILINE (1U << 2) struct duk_re_matcher_ctx { duk_hthread *thr; duk_uint32_t re_flags; const duk_uint8_t *input; const duk_uint8_t *input_end; const duk_uint8_t *bytecode; const duk_uint8_t *bytecode_end; const duk_uint8_t **saved; /* allocated from valstack (fixed buffer) */ duk_uint32_t nsaved; duk_uint32_t recursion_depth; duk_uint32_t recursion_limit; duk_uint32_t steps_count; duk_uint32_t steps_limit; }; struct duk_re_compiler_ctx { duk_hthread *thr; duk_uint32_t re_flags; duk_lexer_ctx lex; duk_re_token curr_token; duk_bufwriter_ctx bw; duk_uint32_t captures; /* highest capture number emitted so far (used as: ++captures) */ duk_uint32_t highest_backref; duk_uint32_t recursion_depth; duk_uint32_t recursion_limit; duk_uint32_t nranges; /* internal temporary value, used for char classes */ }; /* * Prototypes */ #if defined(DUK_USE_REGEXP_SUPPORT) DUK_INTERNAL_DECL void duk_regexp_compile(duk_hthread *thr); DUK_INTERNAL_DECL void duk_regexp_create_instance(duk_hthread *thr); DUK_INTERNAL_DECL void duk_regexp_match(duk_hthread *thr); DUK_INTERNAL_DECL void duk_regexp_match_force_global(duk_hthread *thr); /* hacky helper for String.prototype.split() */ #endif #endif /* DUK_REGEXP_H_INCLUDED */ /* #include duk_heaphdr.h */ #line 1 "duk_heaphdr.h" /* * Heap header definition and assorted macros, including ref counting. * Access all fields through the accessor macros. */ #if !defined(DUK_HEAPHDR_H_INCLUDED) #define DUK_HEAPHDR_H_INCLUDED /* * Common heap header * * All heap objects share the same flags and refcount fields. Objects other * than strings also need to have a single or double linked list pointers * for insertion into the "heap allocated" list. Strings have single linked * list pointers for string table chaining. * * Technically, 'h_refcount' must be wide enough to guarantee that it cannot * wrap; otherwise objects might be freed incorrectly after wrapping. The * default refcount field is 32 bits even on 64-bit systems: while that's in * theory incorrect, the Duktape heap needs to be larger than 64GB for the * count to actually wrap (assuming 16-byte duk_tvals). This is very unlikely * to ever be an issue, but if it is, disabling DUK_USE_REFCOUNT32 causes * Duktape to use size_t for refcounts which should always be safe. * * Heap header size on 32-bit platforms: 8 bytes without reference counting, * 16 bytes with reference counting. * * Note that 'raw' macros such as DUK_HEAPHDR_GET_REFCOUNT() are not * defined without DUK_USE_REFERENCE_COUNTING, so caller must #if defined() * around them. */ /* XXX: macro for shared header fields (avoids some padding issues) */ struct duk_heaphdr { duk_uint32_t h_flags; #if defined(DUK_USE_REFERENCE_COUNTING) #if defined(DUK_USE_ASSERTIONS) /* When assertions enabled, used by mark-and-sweep for refcount * validation. Largest reasonable type; also detects overflows. */ duk_size_t h_assert_refcount; #endif #if defined(DUK_USE_REFCOUNT16) duk_uint16_t h_refcount; #elif defined(DUK_USE_REFCOUNT32) duk_uint32_t h_refcount; #else duk_size_t h_refcount; #endif #endif /* DUK_USE_REFERENCE_COUNTING */ #if defined(DUK_USE_HEAPPTR16) duk_uint16_t h_next16; #else duk_heaphdr *h_next; #endif #if defined(DUK_USE_DOUBLE_LINKED_HEAP) /* refcounting requires direct heap frees, which in turn requires a dual linked heap */ #if defined(DUK_USE_HEAPPTR16) duk_uint16_t h_prev16; #else duk_heaphdr *h_prev; #endif #endif /* When DUK_USE_HEAPPTR16 (and DUK_USE_REFCOUNT16) is in use, the * struct won't align nicely to 4 bytes. This 16-bit extra field * is added to make the alignment clean; the field can be used by * heap objects when 16-bit packing is used. This field is now * conditional to DUK_USE_HEAPPTR16 only, but it is intended to be * used with DUK_USE_REFCOUNT16 and DUK_USE_DOUBLE_LINKED_HEAP; * this only matter to low memory environments anyway. */ #if defined(DUK_USE_HEAPPTR16) duk_uint16_t h_extra16; #endif }; struct duk_heaphdr_string { /* 16 bits would be enough for shared heaphdr flags and duk_hstring * flags. The initial parts of duk_heaphdr_string and duk_heaphdr * must match so changing the flags field size here would be quite * awkward. However, to minimize struct size, we can pack at least * 16 bits of duk_hstring data into the flags field. */ duk_uint32_t h_flags; #if defined(DUK_USE_REFERENCE_COUNTING) #if defined(DUK_USE_ASSERTIONS) /* When assertions enabled, used by mark-and-sweep for refcount * validation. Largest reasonable type; also detects overflows. */ duk_size_t h_assert_refcount; #endif #if defined(DUK_USE_REFCOUNT16) duk_uint16_t h_refcount; duk_uint16_t h_strextra16; /* round out to 8 bytes */ #elif defined(DUK_USE_REFCOUNT32) duk_uint32_t h_refcount; #else duk_size_t h_refcount; #endif #else duk_uint16_t h_strextra16; #endif /* DUK_USE_REFERENCE_COUNTING */ duk_hstring *h_next; /* No 'h_prev' pointer for strings. */ }; #define DUK_HEAPHDR_FLAGS_TYPE_MASK 0x00000003UL #define DUK_HEAPHDR_FLAGS_FLAG_MASK (~DUK_HEAPHDR_FLAGS_TYPE_MASK) /* 2 bits for heap type */ #define DUK_HEAPHDR_FLAGS_HEAP_START 2 /* 5 heap flags */ #define DUK_HEAPHDR_FLAGS_USER_START 7 /* 25 user flags */ #define DUK_HEAPHDR_HEAP_FLAG_NUMBER(n) (DUK_HEAPHDR_FLAGS_HEAP_START + (n)) #define DUK_HEAPHDR_USER_FLAG_NUMBER(n) (DUK_HEAPHDR_FLAGS_USER_START + (n)) #define DUK_HEAPHDR_HEAP_FLAG(n) (1UL << (DUK_HEAPHDR_FLAGS_HEAP_START + (n))) #define DUK_HEAPHDR_USER_FLAG(n) (1UL << (DUK_HEAPHDR_FLAGS_USER_START + (n))) #define DUK_HEAPHDR_FLAG_REACHABLE DUK_HEAPHDR_HEAP_FLAG(0) /* mark-and-sweep: reachable */ #define DUK_HEAPHDR_FLAG_TEMPROOT DUK_HEAPHDR_HEAP_FLAG(1) /* mark-and-sweep: children not processed */ #define DUK_HEAPHDR_FLAG_FINALIZABLE DUK_HEAPHDR_HEAP_FLAG(2) /* mark-and-sweep: finalizable (on current pass) */ #define DUK_HEAPHDR_FLAG_FINALIZED DUK_HEAPHDR_HEAP_FLAG(3) /* mark-and-sweep: finalized (on previous pass) */ #define DUK_HEAPHDR_FLAG_READONLY DUK_HEAPHDR_HEAP_FLAG(4) /* read-only object, in code section */ #define DUK_HTYPE_MIN 0 #define DUK_HTYPE_STRING 0 #define DUK_HTYPE_OBJECT 1 #define DUK_HTYPE_BUFFER 2 #define DUK_HTYPE_MAX 2 #if defined(DUK_USE_HEAPPTR16) #define DUK_HEAPHDR_GET_NEXT(heap, h) ((duk_heaphdr *) DUK_USE_HEAPPTR_DEC16((heap)->heap_udata, (h)->h_next16)) #define DUK_HEAPHDR_SET_NEXT(heap, h, val) \ do { \ (h)->h_next16 = DUK_USE_HEAPPTR_ENC16((heap)->heap_udata, (void *) val); \ } while (0) #else #define DUK_HEAPHDR_GET_NEXT(heap, h) ((h)->h_next) #define DUK_HEAPHDR_SET_NEXT(heap, h, val) \ do { \ (h)->h_next = (val); \ } while (0) #endif #if defined(DUK_USE_DOUBLE_LINKED_HEAP) #if defined(DUK_USE_HEAPPTR16) #define DUK_HEAPHDR_GET_PREV(heap, h) ((duk_heaphdr *) DUK_USE_HEAPPTR_DEC16((heap)->heap_udata, (h)->h_prev16)) #define DUK_HEAPHDR_SET_PREV(heap, h, val) \ do { \ (h)->h_prev16 = DUK_USE_HEAPPTR_ENC16((heap)->heap_udata, (void *) (val)); \ } while (0) #else #define DUK_HEAPHDR_GET_PREV(heap, h) ((h)->h_prev) #define DUK_HEAPHDR_SET_PREV(heap, h, val) \ do { \ (h)->h_prev = (val); \ } while (0) #endif #endif #if defined(DUK_USE_REFERENCE_COUNTING) #define DUK_HEAPHDR_GET_REFCOUNT(h) ((h)->h_refcount) #define DUK_HEAPHDR_SET_REFCOUNT(h, val) \ do { \ (h)->h_refcount = (val); \ DUK_ASSERT((h)->h_refcount == (val)); /* No truncation. */ \ } while (0) #define DUK_HEAPHDR_PREINC_REFCOUNT(h) (++(h)->h_refcount) /* result: updated refcount */ #define DUK_HEAPHDR_PREDEC_REFCOUNT(h) (--(h)->h_refcount) /* result: updated refcount */ #else /* refcount macros not defined without refcounting, caller must #if defined() now */ #endif /* DUK_USE_REFERENCE_COUNTING */ /* * Note: type is treated as a field separate from flags, so some masking is * involved in the macros below. */ #define DUK_HEAPHDR_GET_FLAGS_RAW(h) ((h)->h_flags) #define DUK_HEAPHDR_SET_FLAGS_RAW(h, val) \ do { \ (h)->h_flags = (val); \ } \ } #define DUK_HEAPHDR_GET_FLAGS(h) ((h)->h_flags & DUK_HEAPHDR_FLAGS_FLAG_MASK) #define DUK_HEAPHDR_SET_FLAGS(h, val) \ do { \ (h)->h_flags = ((h)->h_flags & ~(DUK_HEAPHDR_FLAGS_FLAG_MASK)) | (val); \ } while (0) #define DUK_HEAPHDR_GET_TYPE(h) ((h)->h_flags & DUK_HEAPHDR_FLAGS_TYPE_MASK) #define DUK_HEAPHDR_SET_TYPE(h, val) \ do { \ (h)->h_flags = ((h)->h_flags & ~(DUK_HEAPHDR_FLAGS_TYPE_MASK)) | (val); \ } while (0) /* Comparison for type >= DUK_HTYPE_MIN skipped; because DUK_HTYPE_MIN is zero * and the comparison is unsigned, it's always true and generates warnings. */ #define DUK_HEAPHDR_HTYPE_VALID(h) (DUK_HEAPHDR_GET_TYPE((h)) <= DUK_HTYPE_MAX) #define DUK_HEAPHDR_SET_TYPE_AND_FLAGS(h, tval, fval) \ do { \ (h)->h_flags = ((tval) &DUK_HEAPHDR_FLAGS_TYPE_MASK) | ((fval) &DUK_HEAPHDR_FLAGS_FLAG_MASK); \ } while (0) #define DUK_HEAPHDR_SET_FLAG_BITS(h, bits) \ do { \ DUK_ASSERT(((bits) & ~(DUK_HEAPHDR_FLAGS_FLAG_MASK)) == 0); \ (h)->h_flags |= (bits); \ } while (0) #define DUK_HEAPHDR_CLEAR_FLAG_BITS(h, bits) \ do { \ DUK_ASSERT(((bits) & ~(DUK_HEAPHDR_FLAGS_FLAG_MASK)) == 0); \ (h)->h_flags &= ~((bits)); \ } while (0) #define DUK_HEAPHDR_CHECK_FLAG_BITS(h, bits) (((h)->h_flags & (bits)) != 0) #define DUK_HEAPHDR_SET_REACHABLE(h) DUK_HEAPHDR_SET_FLAG_BITS((h), DUK_HEAPHDR_FLAG_REACHABLE) #define DUK_HEAPHDR_CLEAR_REACHABLE(h) DUK_HEAPHDR_CLEAR_FLAG_BITS((h), DUK_HEAPHDR_FLAG_REACHABLE) #define DUK_HEAPHDR_HAS_REACHABLE(h) DUK_HEAPHDR_CHECK_FLAG_BITS((h), DUK_HEAPHDR_FLAG_REACHABLE) #define DUK_HEAPHDR_SET_TEMPROOT(h) DUK_HEAPHDR_SET_FLAG_BITS((h), DUK_HEAPHDR_FLAG_TEMPROOT) #define DUK_HEAPHDR_CLEAR_TEMPROOT(h) DUK_HEAPHDR_CLEAR_FLAG_BITS((h), DUK_HEAPHDR_FLAG_TEMPROOT) #define DUK_HEAPHDR_HAS_TEMPROOT(h) DUK_HEAPHDR_CHECK_FLAG_BITS((h), DUK_HEAPHDR_FLAG_TEMPROOT) #define DUK_HEAPHDR_SET_FINALIZABLE(h) DUK_HEAPHDR_SET_FLAG_BITS((h), DUK_HEAPHDR_FLAG_FINALIZABLE) #define DUK_HEAPHDR_CLEAR_FINALIZABLE(h) DUK_HEAPHDR_CLEAR_FLAG_BITS((h), DUK_HEAPHDR_FLAG_FINALIZABLE) #define DUK_HEAPHDR_HAS_FINALIZABLE(h) DUK_HEAPHDR_CHECK_FLAG_BITS((h), DUK_HEAPHDR_FLAG_FINALIZABLE) #define DUK_HEAPHDR_SET_FINALIZED(h) DUK_HEAPHDR_SET_FLAG_BITS((h), DUK_HEAPHDR_FLAG_FINALIZED) #define DUK_HEAPHDR_CLEAR_FINALIZED(h) DUK_HEAPHDR_CLEAR_FLAG_BITS((h), DUK_HEAPHDR_FLAG_FINALIZED) #define DUK_HEAPHDR_HAS_FINALIZED(h) DUK_HEAPHDR_CHECK_FLAG_BITS((h), DUK_HEAPHDR_FLAG_FINALIZED) #define DUK_HEAPHDR_SET_READONLY(h) DUK_HEAPHDR_SET_FLAG_BITS((h), DUK_HEAPHDR_FLAG_READONLY) #define DUK_HEAPHDR_CLEAR_READONLY(h) DUK_HEAPHDR_CLEAR_FLAG_BITS((h), DUK_HEAPHDR_FLAG_READONLY) #define DUK_HEAPHDR_HAS_READONLY(h) DUK_HEAPHDR_CHECK_FLAG_BITS((h), DUK_HEAPHDR_FLAG_READONLY) /* get or set a range of flags; m=first bit number, n=number of bits */ #define DUK_HEAPHDR_GET_FLAG_RANGE(h, m, n) (((h)->h_flags >> (m)) & ((1UL << (n)) - 1UL)) #define DUK_HEAPHDR_SET_FLAG_RANGE(h, m, n, v) \ do { \ (h)->h_flags = ((h)->h_flags & (~(((1UL << (n)) - 1UL) << (m)))) | ((v) << (m)); \ } while (0) /* init pointer fields to null */ #if defined(DUK_USE_DOUBLE_LINKED_HEAP) #define DUK_HEAPHDR_INIT_NULLS(h) \ do { \ DUK_HEAPHDR_SET_NEXT((h), (void *) NULL); \ DUK_HEAPHDR_SET_PREV((h), (void *) NULL); \ } while (0) #else #define DUK_HEAPHDR_INIT_NULLS(h) \ do { \ DUK_HEAPHDR_SET_NEXT((h), (void *) NULL); \ } while (0) #endif #define DUK_HEAPHDR_STRING_INIT_NULLS(h) \ do { \ (h)->h_next = NULL; \ } while (0) /* * Type tests */ /* Take advantage of the fact that for DUK_HTYPE_xxx numbers the lowest bit * is only set for DUK_HTYPE_OBJECT (= 1). */ #if 0 #define DUK_HEAPHDR_IS_OBJECT(h) (DUK_HEAPHDR_GET_TYPE((h)) == DUK_HTYPE_OBJECT) #endif #define DUK_HEAPHDR_IS_OBJECT(h) ((h)->h_flags & 0x01UL) #define DUK_HEAPHDR_IS_STRING(h) (DUK_HEAPHDR_GET_TYPE((h)) == DUK_HTYPE_STRING) #define DUK_HEAPHDR_IS_BUFFER(h) (DUK_HEAPHDR_GET_TYPE((h)) == DUK_HTYPE_BUFFER) /* * Assert helpers */ /* Check that prev/next links are consistent: if e.g. h->prev is != NULL, * h->prev->next should point back to h. */ #if defined(DUK_USE_ASSERTIONS) DUK_INTERNAL_DECL void duk_heaphdr_assert_valid_subclassed(duk_heaphdr *h); DUK_INTERNAL_DECL void duk_heaphdr_assert_links(duk_heap *heap, duk_heaphdr *h); DUK_INTERNAL_DECL void duk_heaphdr_assert_valid(duk_heaphdr *h); #define DUK_HEAPHDR_ASSERT_LINKS(heap, h) \ do { \ duk_heaphdr_assert_links((heap), (h)); \ } while (0) #define DUK_HEAPHDR_ASSERT_VALID(h) \ do { \ duk_heaphdr_assert_valid((h)); \ } while (0) #else #define DUK_HEAPHDR_ASSERT_LINKS(heap, h) \ do { \ } while (0) #define DUK_HEAPHDR_ASSERT_VALID(h) \ do { \ } while (0) #endif #endif /* DUK_HEAPHDR_H_INCLUDED */ /* #include duk_refcount.h */ #line 1 "duk_refcount.h" /* * Reference counting helper macros. The macros take a thread argument * and must thus always be executed in a specific thread context. The * thread argument is not really needed anymore: DECREF can operate with * a heap pointer only, and INCREF needs neither. */ #if !defined(DUK_REFCOUNT_H_INCLUDED) #define DUK_REFCOUNT_H_INCLUDED #if defined(DUK_USE_REFERENCE_COUNTING) #if defined(DUK_USE_ROM_OBJECTS) /* With ROM objects "needs refcount update" is true when the value is * heap allocated and is not a ROM object. */ /* XXX: double evaluation for 'tv' argument. */ #define DUK_TVAL_NEEDS_REFCOUNT_UPDATE(tv) \ (DUK_TVAL_IS_HEAP_ALLOCATED((tv)) && !DUK_HEAPHDR_HAS_READONLY(DUK_TVAL_GET_HEAPHDR((tv)))) #define DUK_HEAPHDR_NEEDS_REFCOUNT_UPDATE(h) (!DUK_HEAPHDR_HAS_READONLY((h))) #else /* DUK_USE_ROM_OBJECTS */ /* Without ROM objects "needs refcount update" == is heap allocated. */ #define DUK_TVAL_NEEDS_REFCOUNT_UPDATE(tv) DUK_TVAL_IS_HEAP_ALLOCATED((tv)) #define DUK_HEAPHDR_NEEDS_REFCOUNT_UPDATE(h) 1 #endif /* DUK_USE_ROM_OBJECTS */ /* Fast variants, inline refcount operations except for refzero handling. * Can be used explicitly when speed is always more important than size. * For a good compiler and a single file build, these are basically the * same as a forced inline. */ #define DUK_TVAL_INCREF_FAST(thr, tv) \ do { \ duk_tval *duk__tv = (tv); \ DUK_ASSERT(duk__tv != NULL); \ if (DUK_TVAL_NEEDS_REFCOUNT_UPDATE(duk__tv)) { \ duk_heaphdr *duk__h = DUK_TVAL_GET_HEAPHDR(duk__tv); \ DUK_ASSERT(duk__h != NULL); \ DUK_ASSERT(DUK_HEAPHDR_HTYPE_VALID(duk__h)); \ DUK_HEAPHDR_PREINC_REFCOUNT(duk__h); \ DUK_ASSERT(DUK_HEAPHDR_GET_REFCOUNT(duk__h) != 0); /* No wrapping. */ \ } \ } while (0) #define DUK_TVAL_DECREF_FAST(thr, tv) \ do { \ duk_tval *duk__tv = (tv); \ DUK_ASSERT(duk__tv != NULL); \ if (DUK_TVAL_NEEDS_REFCOUNT_UPDATE(duk__tv)) { \ duk_heaphdr *duk__h = DUK_TVAL_GET_HEAPHDR(duk__tv); \ DUK_ASSERT(duk__h != NULL); \ DUK_ASSERT(DUK_HEAPHDR_HTYPE_VALID(duk__h)); \ DUK_ASSERT(DUK_HEAPHDR_GET_REFCOUNT(duk__h) > 0); \ if (DUK_HEAPHDR_PREDEC_REFCOUNT(duk__h) == 0) { \ duk_heaphdr_refzero((thr), duk__h); \ } \ } \ } while (0) #define DUK_TVAL_DECREF_NORZ_FAST(thr, tv) \ do { \ duk_tval *duk__tv = (tv); \ DUK_ASSERT(duk__tv != NULL); \ if (DUK_TVAL_NEEDS_REFCOUNT_UPDATE(duk__tv)) { \ duk_heaphdr *duk__h = DUK_TVAL_GET_HEAPHDR(duk__tv); \ DUK_ASSERT(duk__h != NULL); \ DUK_ASSERT(DUK_HEAPHDR_HTYPE_VALID(duk__h)); \ DUK_ASSERT(DUK_HEAPHDR_GET_REFCOUNT(duk__h) > 0); \ if (DUK_HEAPHDR_PREDEC_REFCOUNT(duk__h) == 0) { \ duk_heaphdr_refzero_norz((thr), duk__h); \ } \ } \ } while (0) #define DUK_HEAPHDR_INCREF_FAST(thr, h) \ do { \ duk_heaphdr *duk__h = (duk_heaphdr *) (h); \ DUK_ASSERT(duk__h != NULL); \ DUK_ASSERT(DUK_HEAPHDR_HTYPE_VALID(duk__h)); \ if (DUK_HEAPHDR_NEEDS_REFCOUNT_UPDATE(duk__h)) { \ DUK_HEAPHDR_PREINC_REFCOUNT(duk__h); \ DUK_ASSERT(DUK_HEAPHDR_GET_REFCOUNT(duk__h) != 0); /* No wrapping. */ \ } \ } while (0) #define DUK_HEAPHDR_DECREF_FAST_RAW(thr, h, rzcall, rzcast) \ do { \ duk_heaphdr *duk__h = (duk_heaphdr *) (h); \ DUK_ASSERT(duk__h != NULL); \ DUK_ASSERT(DUK_HEAPHDR_HTYPE_VALID(duk__h)); \ DUK_ASSERT(DUK_HEAPHDR_GET_REFCOUNT(duk__h) > 0); \ if (DUK_HEAPHDR_NEEDS_REFCOUNT_UPDATE(duk__h)) { \ if (DUK_HEAPHDR_PREDEC_REFCOUNT(duk__h) == 0) { \ (rzcall)((thr), (rzcast) duk__h); \ } \ } \ } while (0) #define DUK_HEAPHDR_DECREF_FAST(thr, h) DUK_HEAPHDR_DECREF_FAST_RAW((thr), (h), duk_heaphdr_refzero, duk_heaphdr *) #define DUK_HEAPHDR_DECREF_NORZ_FAST(thr, h) DUK_HEAPHDR_DECREF_FAST_RAW((thr), (h), duk_heaphdr_refzero_norz, duk_heaphdr *) /* Slow variants, call to a helper to reduce code size. * Can be used explicitly when size is always more important than speed. */ #define DUK_TVAL_INCREF_SLOW(thr, tv) \ do { \ duk_tval_incref((tv)); \ } while (0) #define DUK_TVAL_DECREF_SLOW(thr, tv) \ do { \ duk_tval_decref((thr), (tv)); \ } while (0) #define DUK_TVAL_DECREF_NORZ_SLOW(thr, tv) \ do { \ duk_tval_decref_norz((thr), (tv)); \ } while (0) #define DUK_HEAPHDR_INCREF_SLOW(thr, h) \ do { \ duk_heaphdr_incref((duk_heaphdr *) (h)); \ } while (0) #define DUK_HEAPHDR_DECREF_SLOW(thr, h) \ do { \ duk_heaphdr_decref((thr), (duk_heaphdr *) (h)); \ } while (0) #define DUK_HEAPHDR_DECREF_NORZ_SLOW(thr, h) \ do { \ duk_heaphdr_decref_norz((thr), (duk_heaphdr *) (h)); \ } while (0) #define DUK_HSTRING_INCREF_SLOW(thr, h) \ do { \ duk_heaphdr_incref((duk_heaphdr *) (h)); \ } while (0) #define DUK_HSTRING_DECREF_SLOW(thr, h) \ do { \ duk_heaphdr_decref((thr), (duk_heaphdr *) (h)); \ } while (0) #define DUK_HSTRING_DECREF_NORZ_SLOW(thr, h) \ do { \ duk_heaphdr_decref_norz((thr), (duk_heaphdr *) (h)); \ } while (0) #define DUK_HBUFFER_INCREF_SLOW(thr, h) \ do { \ duk_heaphdr_incref((duk_heaphdr *) (h)); \ } while (0) #define DUK_HBUFFER_DECREF_SLOW(thr, h) \ do { \ duk_heaphdr_decref((thr), (duk_heaphdr *) (h)); \ } while (0) #define DUK_HBUFFER_DECREF_NORZ_SLOW(thr, h) \ do { \ duk_heaphdr_decref_norz((thr), (duk_heaphdr *) (h)); \ } while (0) #define DUK_HOBJECT_INCREF_SLOW(thr, h) \ do { \ duk_heaphdr_incref((duk_heaphdr *) (h)); \ } while (0) #define DUK_HOBJECT_DECREF_SLOW(thr, h) \ do { \ duk_heaphdr_decref((thr), (duk_heaphdr *) (h)); \ } while (0) #define DUK_HOBJECT_DECREF_NORZ_SLOW(thr, h) \ do { \ duk_heaphdr_decref_norz((thr), (duk_heaphdr *) (h)); \ } while (0) /* Default variants. Selection depends on speed/size preference. * Concretely: with gcc 4.8.1 -Os x64 the difference in final binary * is about +1kB for _FAST variants. */ #if defined(DUK_USE_FAST_REFCOUNT_DEFAULT) /* XXX: It would be nice to specialize for specific duk_hobject subtypes * but current refzero queue handling prevents that. */ #define DUK_TVAL_INCREF(thr, tv) DUK_TVAL_INCREF_FAST((thr), (tv)) #define DUK_TVAL_DECREF(thr, tv) DUK_TVAL_DECREF_FAST((thr), (tv)) #define DUK_TVAL_DECREF_NORZ(thr, tv) DUK_TVAL_DECREF_NORZ_FAST((thr), (tv)) #define DUK_HEAPHDR_INCREF(thr, h) DUK_HEAPHDR_INCREF_FAST((thr), (h)) #define DUK_HEAPHDR_DECREF(thr, h) DUK_HEAPHDR_DECREF_FAST_RAW((thr), (h), duk_heaphdr_refzero, duk_heaphdr *) #define DUK_HEAPHDR_DECREF_NORZ(thr, h) DUK_HEAPHDR_DECREF_FAST_RAW((thr), (h), duk_heaphdr_refzero_norz, duk_heaphdr *) #define DUK_HSTRING_INCREF(thr, h) DUK_HEAPHDR_INCREF((thr), (duk_heaphdr *) (h)) #define DUK_HSTRING_DECREF(thr, h) DUK_HEAPHDR_DECREF_FAST_RAW((thr), (h), duk_hstring_refzero, duk_hstring *) #define DUK_HSTRING_DECREF_NORZ(thr, h) \ DUK_HEAPHDR_DECREF_FAST_RAW((thr), (h), duk_hstring_refzero, duk_hstring *) /* no 'norz' variant */ #define DUK_HOBJECT_INCREF(thr, h) DUK_HEAPHDR_INCREF((thr), (duk_heaphdr *) (h)) #define DUK_HOBJECT_DECREF(thr, h) DUK_HEAPHDR_DECREF_FAST_RAW((thr), (h), duk_hobject_refzero, duk_hobject *) #define DUK_HOBJECT_DECREF_NORZ(thr, h) DUK_HEAPHDR_DECREF_FAST_RAW((thr), (h), duk_hobject_refzero_norz, duk_hobject *) #define DUK_HBUFFER_INCREF(thr, h) DUK_HEAPHDR_INCREF((thr), (duk_heaphdr *) (h)) #define DUK_HBUFFER_DECREF(thr, h) DUK_HEAPHDR_DECREF_FAST_RAW((thr), (h), duk_hbuffer_refzero, duk_hbuffer *) #define DUK_HBUFFER_DECREF_NORZ(thr, h) \ DUK_HEAPHDR_DECREF_FAST_RAW((thr), (h), duk_hbuffer_refzero, duk_hbuffer *) /* no 'norz' variant */ #define DUK_HCOMPFUNC_INCREF(thr, h) DUK_HEAPHDR_INCREF((thr), (duk_heaphdr *) &(h)->obj) #define DUK_HCOMPFUNC_DECREF(thr, h) DUK_HEAPHDR_DECREF_FAST_RAW((thr), (h), duk_hobject_refzero, duk_hobject *) #define DUK_HCOMPFUNC_DECREF_NORZ(thr, h) DUK_HEAPHDR_DECREF_FAST_RAW((thr), (h), duk_hobject_refzero_norz, duk_hobject *) #define DUK_HNATFUNC_INCREF(thr, h) DUK_HEAPHDR_INCREF((thr), (duk_heaphdr *) &(h)->obj) #define DUK_HNATFUNC_DECREF(thr, h) DUK_HEAPHDR_DECREF_FAST_RAW((thr), (h), duk_hobject_refzero, duk_hobject *) #define DUK_HNATFUNC_DECREF_NORZ(thr, h) DUK_HEAPHDR_DECREF_FAST_RAW((thr), (h), duk_hobject_refzero_norz, duk_hobject *) #define DUK_HBUFOBJ_INCREF(thr, h) DUK_HEAPHDR_INCREF((thr), (duk_heaphdr *) &(h)->obj) #define DUK_HBUFOBJ_DECREF(thr, h) DUK_HEAPHDR_DECREF_FAST_RAW((thr), (h), duk_hobject_refzero, duk_hobject *) #define DUK_HBUFOBJ_DECREF_NORZ(thr, h) DUK_HEAPHDR_DECREF_FAST_RAW((thr), (h), duk_hobject_refzero_norz, duk_hobject *) #define DUK_HTHREAD_INCREF(thr, h) DUK_HEAPHDR_INCREF((thr), (duk_heaphdr *) &(h)->obj) #define DUK_HTHREAD_DECREF(thr, h) DUK_HEAPHDR_DECREF_FAST_RAW((thr), (h), duk_hobject_refzero, duk_hobject *) #define DUK_HTHREAD_DECREF_NORZ(thr, h) DUK_HEAPHDR_DECREF_FAST_RAW((thr), (h), duk_hobject_refzero_norz, duk_hobject *) #else #define DUK_TVAL_INCREF(thr, tv) DUK_TVAL_INCREF_SLOW((thr), (tv)) #define DUK_TVAL_DECREF(thr, tv) DUK_TVAL_DECREF_SLOW((thr), (tv)) #define DUK_TVAL_DECREF_NORZ(thr, tv) DUK_TVAL_DECREF_NORZ_SLOW((thr), (tv)) #define DUK_HEAPHDR_INCREF(thr, h) DUK_HEAPHDR_INCREF_SLOW((thr), (h)) #define DUK_HEAPHDR_DECREF(thr, h) DUK_HEAPHDR_DECREF_SLOW((thr), (h)) #define DUK_HEAPHDR_DECREF_NORZ(thr, h) DUK_HEAPHDR_DECREF_NORZ_SLOW((thr), (h)) #define DUK_HSTRING_INCREF(thr, h) DUK_HEAPHDR_INCREF((thr), (duk_heaphdr *) (h)) #define DUK_HSTRING_DECREF(thr, h) DUK_HSTRING_DECREF_SLOW((thr), (h)) #define DUK_HSTRING_DECREF_NORZ(thr, h) DUK_HSTRING_DECREF_NORZ_SLOW((thr), (h)) #define DUK_HOBJECT_INCREF(thr, h) DUK_HEAPHDR_INCREF((thr), (duk_heaphdr *) (h)) #define DUK_HOBJECT_DECREF(thr, h) DUK_HOBJECT_DECREF_SLOW((thr), (h)) #define DUK_HOBJECT_DECREF_NORZ(thr, h) DUK_HOBJECT_DECREF_NORZ_SLOW((thr), (h)) #define DUK_HBUFFER_INCREF(thr, h) DUK_HEAPHDR_INCREF((thr), (duk_heaphdr *) (h)) #define DUK_HBUFFER_DECREF(thr, h) DUK_HBUFFER_DECREF_SLOW((thr), (h)) #define DUK_HBUFFER_DECREF_NORZ(thr, h) DUK_HBUFFER_DECREF_NORZ_SLOW((thr), (h)) #define DUK_HCOMPFUNC_INCREF(thr, h) DUK_HEAPHDR_INCREF((thr), (duk_heaphdr *) &(h)->obj) #define DUK_HCOMPFUNC_DECREF(thr, h) DUK_HOBJECT_DECREF_SLOW((thr), (duk_hobject *) &(h)->obj) #define DUK_HCOMPFUNC_DECREF_NORZ(thr, h) DUK_HOBJECT_DECREF_NORZ_SLOW((thr), (duk_hobject *) &(h)->obj) #define DUK_HNATFUNC_INCREF(thr, h) DUK_HEAPHDR_INCREF((thr), (duk_heaphdr *) &(h)->obj) #define DUK_HNATFUNC_DECREF(thr, h) DUK_HOBJECT_DECREF_SLOW((thr), (duk_hobject *) &(h)->obj) #define DUK_HNATFUNC_DECREF_NORZ(thr, h) DUK_HOBJECT_DECREF_NORZ_SLOW((thr), (duk_hobject *) &(h)->obj) #define DUK_HBUFOBJ_INCREF(thr, h) DUK_HEAPHDR_INCREF((thr), (duk_heaphdr *) &(h)->obj) #define DUK_HBUFOBJ_DECREF(thr, h) DUK_HOBJECT_DECREF_SLOW((thr), (duk_hobject *) &(h)->obj) #define DUK_HBUFOB_DECREF_NORZ(thr, h) DUK_HOBJECT_DECREF_NORZ_SLOW((thr), (duk_hobject *) &(h)->obj) #define DUK_HTHREAD_INCREF(thr, h) DUK_HEAPHDR_INCREF((thr), (duk_heaphdr *) &(h)->obj) #define DUK_HTHREAD_DECREF(thr, h) DUK_HOBJECT_DECREF_SLOW((thr), (duk_hobject *) &(h)->obj) #define DUK_HTHREAD_DECREF_NORZ(thr, h) DUK_HOBJECT_DECREF_NORZ_SLOW((thr), (duk_hobject *) &(h)->obj) #endif /* Convenience for some situations; the above macros don't allow NULLs * for performance reasons. Macros cover only actually needed cases. */ #define DUK_HEAPHDR_INCREF_ALLOWNULL(thr, h) \ do { \ if ((h) != NULL) { \ DUK_HEAPHDR_INCREF((thr), (duk_heaphdr *) (h)); \ } \ } while (0) #define DUK_HEAPHDR_DECREF_ALLOWNULL(thr, h) \ do { \ if ((h) != NULL) { \ DUK_HEAPHDR_DECREF((thr), (duk_heaphdr *) (h)); \ } \ } while (0) #define DUK_HEAPHDR_DECREF_NORZ_ALLOWNULL(thr, h) \ do { \ if ((h) != NULL) { \ DUK_HEAPHDR_DECREF_NORZ((thr), (duk_heaphdr *) (h)); \ } \ } while (0) #define DUK_HOBJECT_INCREF_ALLOWNULL(thr, h) \ do { \ if ((h) != NULL) { \ DUK_HOBJECT_INCREF((thr), (h)); \ } \ } while (0) #define DUK_HOBJECT_DECREF_ALLOWNULL(thr, h) \ do { \ if ((h) != NULL) { \ DUK_HOBJECT_DECREF((thr), (h)); \ } \ } while (0) #define DUK_HOBJECT_DECREF_NORZ_ALLOWNULL(thr, h) \ do { \ if ((h) != NULL) { \ DUK_HOBJECT_DECREF_NORZ((thr), (h)); \ } \ } while (0) #define DUK_HBUFFER_INCREF_ALLOWNULL(thr, h) \ do { \ if ((h) != NULL) { \ DUK_HBUFFER_INCREF((thr), (h)); \ } \ } while (0) #define DUK_HBUFFER_DECREF_ALLOWNULL(thr, h) \ do { \ if ((h) != NULL) { \ DUK_HBUFFER_DECREF((thr), (h)); \ } \ } while (0) #define DUK_HBUFFER_DECREF_NORZ_ALLOWNULL(thr, h) \ do { \ if ((h) != NULL) { \ DUK_HBUFFER_DECREF_NORZ((thr), (h)); \ } \ } while (0) #define DUK_HTHREAD_INCREF_ALLOWNULL(thr, h) \ do { \ if ((h) != NULL) { \ DUK_HTHREAD_INCREF((thr), (h)); \ } \ } while (0) #define DUK_HTHREAD_DECREF_ALLOWNULL(thr, h) \ do { \ if ((h) != NULL) { \ DUK_HTHREAD_DECREF((thr), (h)); \ } \ } while (0) #define DUK_HTHREAD_DECREF_NORZ_ALLOWNULL(thr, h) \ do { \ if ((h) != NULL) { \ DUK_HTHREAD_DECREF_NORZ((thr), (h)); \ } \ } while (0) /* Called after one or more DECREF NORZ calls to handle pending side effects. * At present DECREF NORZ does freeing inline but doesn't execute finalizers, * so these macros check for pending finalizers and execute them. The FAST * variant is performance critical. */ #if defined(DUK_USE_FINALIZER_SUPPORT) #define DUK_REFZERO_CHECK_FAST(thr) \ do { \ duk_refzero_check_fast((thr)); \ } while (0) #define DUK_REFZERO_CHECK_SLOW(thr) \ do { \ duk_refzero_check_slow((thr)); \ } while (0) #else /* DUK_USE_FINALIZER_SUPPORT */ #define DUK_REFZERO_CHECK_FAST(thr) \ do { \ } while (0) #define DUK_REFZERO_CHECK_SLOW(thr) \ do { \ } while (0) #endif /* DUK_USE_FINALIZER_SUPPORT */ /* * Macros to set a duk_tval and update refcount of the target (decref the * old value and incref the new value if necessary). This is both performance * and footprint critical; any changes made should be measured for size/speed. */ #define DUK_TVAL_SET_UNDEFINED_UPDREF_ALT0(thr, tvptr_dst) \ do { \ duk_tval *tv__dst; \ duk_tval tv__tmp; \ tv__dst = (tvptr_dst); \ DUK_TVAL_SET_TVAL(&tv__tmp, tv__dst); \ DUK_TVAL_SET_UNDEFINED(tv__dst); \ DUK_TVAL_DECREF((thr), &tv__tmp); /* side effects */ \ } while (0) #define DUK_TVAL_SET_UNDEFINED_UPDREF_NORZ_ALT0(thr, tvptr_dst) \ do { \ duk_tval *tv__dst; \ duk_tval tv__tmp; \ tv__dst = (tvptr_dst); \ DUK_TVAL_SET_TVAL(&tv__tmp, tv__dst); \ DUK_TVAL_SET_UNDEFINED(tv__dst); \ DUK_TVAL_DECREF_NORZ((thr), &tv__tmp); \ } while (0) #define DUK_TVAL_SET_UNUSED_UPDREF_ALT0(thr, tvptr_dst) \ do { \ duk_tval *tv__dst; \ duk_tval tv__tmp; \ tv__dst = (tvptr_dst); \ DUK_TVAL_SET_TVAL(&tv__tmp, tv__dst); \ DUK_TVAL_SET_UNUSED(tv__dst); \ DUK_TVAL_DECREF((thr), &tv__tmp); /* side effects */ \ } while (0) #define DUK_TVAL_SET_NULL_UPDREF_ALT0(thr, tvptr_dst) \ do { \ duk_tval *tv__dst; \ duk_tval tv__tmp; \ tv__dst = (tvptr_dst); \ DUK_TVAL_SET_TVAL(&tv__tmp, tv__dst); \ DUK_TVAL_SET_NULL(tv__dst); \ DUK_TVAL_DECREF((thr), &tv__tmp); /* side effects */ \ } while (0) #define DUK_TVAL_SET_BOOLEAN_UPDREF_ALT0(thr, tvptr_dst, newval) \ do { \ duk_tval *tv__dst; \ duk_tval tv__tmp; \ tv__dst = (tvptr_dst); \ DUK_TVAL_SET_TVAL(&tv__tmp, tv__dst); \ DUK_TVAL_SET_BOOLEAN(tv__dst, (newval)); \ DUK_TVAL_DECREF((thr), &tv__tmp); /* side effects */ \ } while (0) #define DUK_TVAL_SET_NUMBER_UPDREF_ALT0(thr, tvptr_dst, newval) \ do { \ duk_tval *tv__dst; \ duk_tval tv__tmp; \ tv__dst = (tvptr_dst); \ DUK_TVAL_SET_TVAL(&tv__tmp, tv__dst); \ DUK_TVAL_SET_NUMBER(tv__dst, (newval)); \ DUK_TVAL_DECREF((thr), &tv__tmp); /* side effects */ \ } while (0) #define DUK_TVAL_SET_NUMBER_CHKFAST_UPDREF_ALT0(thr, tvptr_dst, newval) \ do { \ duk_tval *tv__dst; \ duk_tval tv__tmp; \ tv__dst = (tvptr_dst); \ DUK_TVAL_SET_TVAL(&tv__tmp, tv__dst); \ DUK_TVAL_SET_NUMBER_CHKFAST_FAST(tv__dst, (newval)); \ DUK_TVAL_DECREF((thr), &tv__tmp); /* side effects */ \ } while (0) #define DUK_TVAL_SET_DOUBLE_UPDREF_ALT0(thr, tvptr_dst, newval) \ do { \ duk_tval *tv__dst; \ duk_tval tv__tmp; \ tv__dst = (tvptr_dst); \ DUK_TVAL_SET_TVAL(&tv__tmp, tv__dst); \ DUK_TVAL_SET_DOUBLE(tv__dst, (newval)); \ DUK_TVAL_DECREF((thr), &tv__tmp); /* side effects */ \ } while (0) #define DUK_TVAL_SET_NAN_UPDREF_ALT0(thr, tvptr_dst) \ do { \ duk_tval *tv__dst; \ duk_tval tv__tmp; \ tv__dst = (tvptr_dst); \ DUK_TVAL_SET_TVAL(&tv__tmp, tv__dst); \ DUK_TVAL_SET_NAN(tv__dst); \ DUK_TVAL_DECREF((thr), &tv__tmp); /* side effects */ \ } while (0) #if defined(DUK_USE_FASTINT) #define DUK_TVAL_SET_I48_UPDREF_ALT0(thr, tvptr_dst, newval) \ do { \ duk_tval *tv__dst; \ duk_tval tv__tmp; \ tv__dst = (tvptr_dst); \ DUK_TVAL_SET_TVAL(&tv__tmp, tv__dst); \ DUK_TVAL_SET_I48(tv__dst, (newval)); \ DUK_TVAL_DECREF((thr), &tv__tmp); /* side effects */ \ } while (0) #define DUK_TVAL_SET_I32_UPDREF_ALT0(thr, tvptr_dst, newval) \ do { \ duk_tval *tv__dst; \ duk_tval tv__tmp; \ tv__dst = (tvptr_dst); \ DUK_TVAL_SET_TVAL(&tv__tmp, tv__dst); \ DUK_TVAL_SET_I32(tv__dst, (newval)); \ DUK_TVAL_DECREF((thr), &tv__tmp); /* side effects */ \ } while (0) #define DUK_TVAL_SET_U32_UPDREF_ALT0(thr, tvptr_dst, newval) \ do { \ duk_tval *tv__dst; \ duk_tval tv__tmp; \ tv__dst = (tvptr_dst); \ DUK_TVAL_SET_TVAL(&tv__tmp, tv__dst); \ DUK_TVAL_SET_U32(tv__dst, (newval)); \ DUK_TVAL_DECREF((thr), &tv__tmp); /* side effects */ \ } while (0) #else #define DUK_TVAL_SET_DOUBLE_CAST_UPDREF(thr, tvptr_dst, newval) \ DUK_TVAL_SET_DOUBLE_UPDREF((thr), (tvptr_dst), (duk_double_t) (newval)) #endif /* DUK_USE_FASTINT */ #define DUK_TVAL_SET_LIGHTFUNC_UPDREF_ALT0(thr, tvptr_dst, lf_v, lf_fp, lf_flags) \ do { \ duk_tval *tv__dst; \ duk_tval tv__tmp; \ tv__dst = (tvptr_dst); \ DUK_TVAL_SET_TVAL(&tv__tmp, tv__dst); \ DUK_TVAL_SET_LIGHTFUNC(tv__dst, (lf_v), (lf_fp), (lf_flags)); \ DUK_TVAL_DECREF((thr), &tv__tmp); /* side effects */ \ } while (0) #define DUK_TVAL_SET_STRING_UPDREF_ALT0(thr, tvptr_dst, newval) \ do { \ duk_tval *tv__dst; \ duk_tval tv__tmp; \ tv__dst = (tvptr_dst); \ DUK_TVAL_SET_TVAL(&tv__tmp, tv__dst); \ DUK_TVAL_SET_STRING(tv__dst, (newval)); \ DUK_HSTRING_INCREF((thr), (newval)); \ DUK_TVAL_DECREF((thr), &tv__tmp); /* side effects */ \ } while (0) #define DUK_TVAL_SET_OBJECT_UPDREF_ALT0(thr, tvptr_dst, newval) \ do { \ duk_tval *tv__dst; \ duk_tval tv__tmp; \ tv__dst = (tvptr_dst); \ DUK_TVAL_SET_TVAL(&tv__tmp, tv__dst); \ DUK_TVAL_SET_OBJECT(tv__dst, (newval)); \ DUK_HOBJECT_INCREF((thr), (newval)); \ DUK_TVAL_DECREF((thr), &tv__tmp); /* side effects */ \ } while (0) #define DUK_TVAL_SET_BUFFER_UPDREF_ALT0(thr, tvptr_dst, newval) \ do { \ duk_tval *tv__dst; \ duk_tval tv__tmp; \ tv__dst = (tvptr_dst); \ DUK_TVAL_SET_TVAL(&tv__tmp, tv__dst); \ DUK_TVAL_SET_BUFFER(tv__dst, (newval)); \ DUK_HBUFFER_INCREF((thr), (newval)); \ DUK_TVAL_DECREF((thr), &tv__tmp); /* side effects */ \ } while (0) #define DUK_TVAL_SET_POINTER_UPDREF_ALT0(thr, tvptr_dst, newval) \ do { \ duk_tval *tv__dst; \ duk_tval tv__tmp; \ tv__dst = (tvptr_dst); \ DUK_TVAL_SET_TVAL(&tv__tmp, tv__dst); \ DUK_TVAL_SET_POINTER(tv__dst, (newval)); \ DUK_TVAL_DECREF((thr), &tv__tmp); /* side effects */ \ } while (0) /* DUK_TVAL_SET_TVAL_UPDREF() is used a lot in executor, property lookups, * etc, so it's very important for performance. Measure when changing. * * NOTE: the source and destination duk_tval pointers may be the same, and * the macros MUST deal with that correctly. */ /* Original idiom used, minimal code size. */ #define DUK_TVAL_SET_TVAL_UPDREF_ALT0(thr, tvptr_dst, tvptr_src) \ do { \ duk_tval *tv__dst, *tv__src; \ duk_tval tv__tmp; \ tv__dst = (tvptr_dst); \ tv__src = (tvptr_src); \ DUK_TVAL_SET_TVAL(&tv__tmp, tv__dst); \ DUK_TVAL_SET_TVAL(tv__dst, tv__src); \ DUK_TVAL_INCREF((thr), tv__src); \ DUK_TVAL_DECREF((thr), &tv__tmp); /* side effects */ \ } while (0) /* Faster alternative: avoid making a temporary copy of tvptr_dst and use * fast incref/decref macros. */ #define DUK_TVAL_SET_TVAL_UPDREF_ALT1(thr, tvptr_dst, tvptr_src) \ do { \ duk_tval *tv__dst, *tv__src; \ duk_heaphdr *h__obj; \ tv__dst = (tvptr_dst); \ tv__src = (tvptr_src); \ DUK_TVAL_INCREF_FAST((thr), tv__src); \ if (DUK_TVAL_NEEDS_REFCOUNT_UPDATE(tv__dst)) { \ h__obj = DUK_TVAL_GET_HEAPHDR(tv__dst); \ DUK_ASSERT(h__obj != NULL); \ DUK_TVAL_SET_TVAL(tv__dst, tv__src); \ DUK_HEAPHDR_DECREF_FAST((thr), h__obj); /* side effects */ \ } else { \ DUK_TVAL_SET_TVAL(tv__dst, tv__src); \ } \ } while (0) /* XXX: no optimized variants yet */ #define DUK_TVAL_SET_UNDEFINED_UPDREF DUK_TVAL_SET_UNDEFINED_UPDREF_ALT0 #define DUK_TVAL_SET_UNDEFINED_UPDREF_NORZ DUK_TVAL_SET_UNDEFINED_UPDREF_NORZ_ALT0 #define DUK_TVAL_SET_UNUSED_UPDREF DUK_TVAL_SET_UNUSED_UPDREF_ALT0 #define DUK_TVAL_SET_NULL_UPDREF DUK_TVAL_SET_NULL_UPDREF_ALT0 #define DUK_TVAL_SET_BOOLEAN_UPDREF DUK_TVAL_SET_BOOLEAN_UPDREF_ALT0 #define DUK_TVAL_SET_NUMBER_UPDREF DUK_TVAL_SET_NUMBER_UPDREF_ALT0 #define DUK_TVAL_SET_NUMBER_CHKFAST_UPDREF DUK_TVAL_SET_NUMBER_CHKFAST_UPDREF_ALT0 #define DUK_TVAL_SET_DOUBLE_UPDREF DUK_TVAL_SET_DOUBLE_UPDREF_ALT0 #define DUK_TVAL_SET_NAN_UPDREF DUK_TVAL_SET_NAN_UPDREF_ALT0 #if defined(DUK_USE_FASTINT) #define DUK_TVAL_SET_I48_UPDREF DUK_TVAL_SET_I48_UPDREF_ALT0 #define DUK_TVAL_SET_I32_UPDREF DUK_TVAL_SET_I32_UPDREF_ALT0 #define DUK_TVAL_SET_U32_UPDREF DUK_TVAL_SET_U32_UPDREF_ALT0 #else #define DUK_TVAL_SET_I48_UPDREF DUK_TVAL_SET_DOUBLE_CAST_UPDREF /* XXX: fast int-to-double */ #define DUK_TVAL_SET_I32_UPDREF DUK_TVAL_SET_DOUBLE_CAST_UPDREF #define DUK_TVAL_SET_U32_UPDREF DUK_TVAL_SET_DOUBLE_CAST_UPDREF #endif /* DUK_USE_FASTINT */ #define DUK_TVAL_SET_FASTINT_UPDREF DUK_TVAL_SET_I48_UPDREF /* convenience */ #define DUK_TVAL_SET_LIGHTFUNC_UPDREF DUK_TVAL_SET_LIGHTFUNC_UPDREF_ALT0 #define DUK_TVAL_SET_STRING_UPDREF DUK_TVAL_SET_STRING_UPDREF_ALT0 #define DUK_TVAL_SET_OBJECT_UPDREF DUK_TVAL_SET_OBJECT_UPDREF_ALT0 #define DUK_TVAL_SET_BUFFER_UPDREF DUK_TVAL_SET_BUFFER_UPDREF_ALT0 #define DUK_TVAL_SET_POINTER_UPDREF DUK_TVAL_SET_POINTER_UPDREF_ALT0 #if defined(DUK_USE_FAST_REFCOUNT_DEFAULT) /* Optimized for speed. */ #define DUK_TVAL_SET_TVAL_UPDREF DUK_TVAL_SET_TVAL_UPDREF_ALT1 #define DUK_TVAL_SET_TVAL_UPDREF_FAST DUK_TVAL_SET_TVAL_UPDREF_ALT1 #define DUK_TVAL_SET_TVAL_UPDREF_SLOW DUK_TVAL_SET_TVAL_UPDREF_ALT0 #else /* Optimized for size. */ #define DUK_TVAL_SET_TVAL_UPDREF DUK_TVAL_SET_TVAL_UPDREF_ALT0 #define DUK_TVAL_SET_TVAL_UPDREF_FAST DUK_TVAL_SET_TVAL_UPDREF_ALT0 #define DUK_TVAL_SET_TVAL_UPDREF_SLOW DUK_TVAL_SET_TVAL_UPDREF_ALT0 #endif #else /* DUK_USE_REFERENCE_COUNTING */ #define DUK_TVAL_NEEDS_REFCOUNT_UPDATE(tv) 0 #define DUK_HEAPHDR_NEEDS_REFCOUNT_UPDATE(h) 0 #define DUK_TVAL_INCREF_FAST(thr, v) \ do { \ } while (0) /* nop */ #define DUK_TVAL_DECREF_FAST(thr, v) \ do { \ } while (0) /* nop */ #define DUK_TVAL_DECREF_NORZ_FAST(thr, v) \ do { \ } while (0) /* nop */ #define DUK_TVAL_INCREF_SLOW(thr, v) \ do { \ } while (0) /* nop */ #define DUK_TVAL_DECREF_SLOW(thr, v) \ do { \ } while (0) /* nop */ #define DUK_TVAL_DECREF_NORZ_SLOW(thr, v) \ do { \ } while (0) /* nop */ #define DUK_TVAL_INCREF(thr, v) \ do { \ } while (0) /* nop */ #define DUK_TVAL_DECREF(thr, v) \ do { \ } while (0) /* nop */ #define DUK_TVAL_DECREF_NORZ(thr, v) \ do { \ } while (0) /* nop */ #define DUK_HEAPHDR_INCREF_FAST(thr, h) \ do { \ } while (0) /* nop */ #define DUK_HEAPHDR_DECREF_FAST(thr, h) \ do { \ } while (0) /* nop */ #define DUK_HEAPHDR_DECREF_NORZ_FAST(thr, h) \ do { \ } while (0) /* nop */ #define DUK_HEAPHDR_INCREF_SLOW(thr, h) \ do { \ } while (0) /* nop */ #define DUK_HEAPHDR_DECREF_SLOW(thr, h) \ do { \ } while (0) /* nop */ #define DUK_HEAPHDR_DECREF_NORZ_SLOW(thr, h) \ do { \ } while (0) /* nop */ #define DUK_HEAPHDR_INCREF(thr, h) \ do { \ } while (0) /* nop */ #define DUK_HEAPHDR_DECREF(thr, h) \ do { \ } while (0) /* nop */ #define DUK_HEAPHDR_DECREF_NORZ(thr, h) \ do { \ } while (0) /* nop */ #define DUK_HSTRING_INCREF_FAST(thr, h) \ do { \ } while (0) /* nop */ #define DUK_HSTRING_DECREF_FAST(thr, h) \ do { \ } while (0) /* nop */ #define DUK_HSTRING_DECREF_NORZ_FAST(thr, h) \ do { \ } while (0) /* nop */ #define DUK_HSTRING_INCREF_SLOW(thr, h) \ do { \ } while (0) /* nop */ #define DUK_HSTRING_DECREF_SLOW(thr, h) \ do { \ } while (0) /* nop */ #define DUK_HSTRING_DECREF_NORZ_SLOW(thr, h) \ do { \ } while (0) /* nop */ #define DUK_HSTRING_INCREF(thr, h) \ do { \ } while (0) /* nop */ #define DUK_HSTRING_DECREF(thr, h) \ do { \ } while (0) /* nop */ #define DUK_HSTRING_DECREF_NORZ(thr, h) \ do { \ } while (0) /* nop */ #define DUK_HOBJECT_INCREF_FAST(thr, h) \ do { \ } while (0) /* nop */ #define DUK_HOBJECT_DECREF_FAST(thr, h) \ do { \ } while (0) /* nop */ #define DUK_HOBJECT_DECREF_NORZ_FAST(thr, h) \ do { \ } while (0) /* nop */ #define DUK_HOBJECT_INCREF_SLOW(thr, h) \ do { \ } while (0) /* nop */ #define DUK_HOBJECT_DECREF_SLOW(thr, h) \ do { \ } while (0) /* nop */ #define DUK_HOBJECT_DECREF_NORZ_SLOW(thr, h) \ do { \ } while (0) /* nop */ #define DUK_HOBJECT_INCREF(thr, h) \ do { \ } while (0) /* nop */ #define DUK_HOBJECT_DECREF(thr, h) \ do { \ } while (0) /* nop */ #define DUK_HOBJECT_DECREF_NORZ(thr, h) \ do { \ } while (0) /* nop */ #define DUK_HBUFFER_INCREF_FAST(thr, h) \ do { \ } while (0) /* nop */ #define DUK_HBUFFER_DECREF_FAST(thr, h) \ do { \ } while (0) /* nop */ #define DUK_HBUFFER_DECREF_NORZ_FAST(thr, h) \ do { \ } while (0) /* nop */ #define DUK_HBUFFER_INCREF_SLOW(thr, h) \ do { \ } while (0) /* nop */ #define DUK_HBUFFER_DECREF_SLOW(thr, h) \ do { \ } while (0) /* nop */ #define DUK_HBUFFER_DECREF_NORZ_SLOW(thr, h) \ do { \ } while (0) /* nop */ #define DUK_HBUFFER_INCREF(thr, h) \ do { \ } while (0) /* nop */ #define DUK_HBUFFER_DECREF(thr, h) \ do { \ } while (0) /* nop */ #define DUK_HBUFFER_DECREF_NORZ(thr, h) \ do { \ } while (0) /* nop */ #define DUK_HCOMPFUNC_INCREF(thr, h) \ do { \ } while (0) /* nop */ #define DUK_HCOMPFUNC_DECREF(thr, h) \ do { \ } while (0) /* nop */ #define DUK_HCOMPFUNC_DECREF_NORZ(thr, h) \ do { \ } while (0) /* nop */ #define DUK_HNATFUNC_INCREF(thr, h) \ do { \ } while (0) /* nop */ #define DUK_HNATFUNC_DECREF(thr, h) \ do { \ } while (0) /* nop */ #define DUK_HNATFUNC_DECREF_NORZ(thr, h) \ do { \ } while (0) /* nop */ #define DUK_HBUFOBJ_INCREF(thr, h) \ do { \ } while (0) /* nop */ #define DUK_HBUFOBJ_DECREF(thr, h) \ do { \ } while (0) /* nop */ #define DUK_HBUFOBJ_DECREF_NORZ(thr, h) \ do { \ } while (0) /* nop */ #define DUK_HTHREAD_INCREF(thr, h) \ do { \ } while (0) /* nop */ #define DUK_HTHREAD_DECREF(thr, h) \ do { \ } while (0) /* nop */ #define DUK_HTHREAD_DECREF_NORZ(thr, h) \ do { \ } while (0) /* nop */ #define DUK_HOBJECT_INCREF_ALLOWNULL(thr, h) \ do { \ } while (0) /* nop */ #define DUK_HOBJECT_DECREF_ALLOWNULL(thr, h) \ do { \ } while (0) /* nop */ #define DUK_HOBJECT_DECREF_NORZ_ALLOWNULL(thr, h) \ do { \ } while (0) /* nop */ #define DUK_HBUFFER_INCREF_ALLOWNULL(thr, h) \ do { \ } while (0) /* nop */ #define DUK_HBUFFER_DECREF_ALLOWNULL(thr, h) \ do { \ } while (0) /* nop */ #define DUK_HBUFFER_DECREF_NORZ_ALLOWNULL(thr, h) \ do { \ } while (0) /* nop */ #define DUK_REFZERO_CHECK_FAST(thr) \ do { \ } while (0) /* nop */ #define DUK_REFZERO_CHECK_SLOW(thr) \ do { \ } while (0) /* nop */ #define DUK_TVAL_SET_UNDEFINED_UPDREF_ALT0(thr, tvptr_dst) \ do { \ duk_tval *tv__dst; \ tv__dst = (tvptr_dst); \ DUK_TVAL_SET_UNDEFINED(tv__dst); \ DUK_UNREF((thr)); \ } while (0) #define DUK_TVAL_SET_UNUSED_UPDREF_ALT0(thr, tvptr_dst) \ do { \ duk_tval *tv__dst; \ tv__dst = (tvptr_dst); \ DUK_TVAL_SET_UNUSED(tv__dst); \ DUK_UNREF((thr)); \ } while (0) #define DUK_TVAL_SET_NULL_UPDREF_ALT0(thr, tvptr_dst) \ do { \ duk_tval *tv__dst; \ tv__dst = (tvptr_dst); \ DUK_TVAL_SET_NULL(tv__dst); \ DUK_UNREF((thr)); \ } while (0) #define DUK_TVAL_SET_BOOLEAN_UPDREF_ALT0(thr, tvptr_dst, newval) \ do { \ duk_tval *tv__dst; \ tv__dst = (tvptr_dst); \ DUK_TVAL_SET_BOOLEAN(tv__dst, (newval)); \ DUK_UNREF((thr)); \ } while (0) #define DUK_TVAL_SET_NUMBER_UPDREF_ALT0(thr, tvptr_dst, newval) \ do { \ duk_tval *tv__dst; \ tv__dst = (tvptr_dst); \ DUK_TVAL_SET_NUMBER(tv__dst, (newval)); \ DUK_UNREF((thr)); \ } while (0) #define DUK_TVAL_SET_NUMBER_CHKFAST_UPDREF_ALT0(thr, tvptr_dst, newval) \ do { \ duk_tval *tv__dst; \ tv__dst = (tvptr_dst); \ DUK_TVAL_SET_NUMBER_CHKFAST_FAST(tv__dst, (newval)); \ DUK_UNREF((thr)); \ } while (0) #define DUK_TVAL_SET_DOUBLE_UPDREF_ALT0(thr, tvptr_dst, newval) \ do { \ duk_tval *tv__dst; \ tv__dst = (tvptr_dst); \ DUK_TVAL_SET_DOUBLE(tv__dst, (newval)); \ DUK_UNREF((thr)); \ } while (0) #define DUK_TVAL_SET_NAN_UPDREF_ALT0(thr, tvptr_dst) \ do { \ duk_tval *tv__dst; \ tv__dst = (tvptr_dst); \ DUK_TVAL_SET_NAN(tv__dst); \ DUK_UNREF((thr)); \ } while (0) #if defined(DUK_USE_FASTINT) #define DUK_TVAL_SET_I48_UPDREF_ALT0(thr, tvptr_dst, newval) \ do { \ duk_tval *tv__dst; \ tv__dst = (tvptr_dst); \ DUK_TVAL_SET_I48(tv__dst, (newval)); \ DUK_UNREF((thr)); \ } while (0) #define DUK_TVAL_SET_I32_UPDREF_ALT0(thr, tvptr_dst, newval) \ do { \ duk_tval *tv__dst; \ tv__dst = (tvptr_dst); \ DUK_TVAL_SET_I32(tv__dst, (newval)); \ DUK_UNREF((thr)); \ } while (0) #define DUK_TVAL_SET_U32_UPDREF_ALT0(thr, tvptr_dst, newval) \ do { \ duk_tval *tv__dst; \ tv__dst = (tvptr_dst); \ DUK_TVAL_SET_U32(tv__dst, (newval)); \ DUK_UNREF((thr)); \ } while (0) #else #define DUK_TVAL_SET_DOUBLE_CAST_UPDREF(thr, tvptr_dst, newval) \ DUK_TVAL_SET_DOUBLE_UPDREF((thr), (tvptr_dst), (duk_double_t) (newval)) #endif /* DUK_USE_FASTINT */ #define DUK_TVAL_SET_LIGHTFUNC_UPDREF_ALT0(thr, tvptr_dst, lf_v, lf_fp, lf_flags) \ do { \ duk_tval *tv__dst; \ tv__dst = (tvptr_dst); \ DUK_TVAL_SET_LIGHTFUNC(tv__dst, (lf_v), (lf_fp), (lf_flags)); \ DUK_UNREF((thr)); \ } while (0) #define DUK_TVAL_SET_STRING_UPDREF_ALT0(thr, tvptr_dst, newval) \ do { \ duk_tval *tv__dst; \ tv__dst = (tvptr_dst); \ DUK_TVAL_SET_STRING(tv__dst, (newval)); \ DUK_UNREF((thr)); \ } while (0) #define DUK_TVAL_SET_OBJECT_UPDREF_ALT0(thr, tvptr_dst, newval) \ do { \ duk_tval *tv__dst; \ tv__dst = (tvptr_dst); \ DUK_TVAL_SET_OBJECT(tv__dst, (newval)); \ DUK_UNREF((thr)); \ } while (0) #define DUK_TVAL_SET_BUFFER_UPDREF_ALT0(thr, tvptr_dst, newval) \ do { \ duk_tval *tv__dst; \ tv__dst = (tvptr_dst); \ DUK_TVAL_SET_BUFFER(tv__dst, (newval)); \ DUK_UNREF((thr)); \ } while (0) #define DUK_TVAL_SET_POINTER_UPDREF_ALT0(thr, tvptr_dst, newval) \ do { \ duk_tval *tv__dst; \ tv__dst = (tvptr_dst); \ DUK_TVAL_SET_POINTER(tv__dst, (newval)); \ DUK_UNREF((thr)); \ } while (0) #define DUK_TVAL_SET_TVAL_UPDREF_ALT0(thr, tvptr_dst, tvptr_src) \ do { \ duk_tval *tv__dst, *tv__src; \ tv__dst = (tvptr_dst); \ tv__src = (tvptr_src); \ DUK_TVAL_SET_TVAL(tv__dst, tv__src); \ DUK_UNREF((thr)); \ } while (0) #define DUK_TVAL_SET_UNDEFINED_UPDREF DUK_TVAL_SET_UNDEFINED_UPDREF_ALT0 #define DUK_TVAL_SET_UNDEFINED_UPDREF_NORZ DUK_TVAL_SET_UNDEFINED_UPDREF_ALT0 #define DUK_TVAL_SET_UNUSED_UPDREF DUK_TVAL_SET_UNUSED_UPDREF_ALT0 #define DUK_TVAL_SET_NULL_UPDREF DUK_TVAL_SET_NULL_UPDREF_ALT0 #define DUK_TVAL_SET_BOOLEAN_UPDREF DUK_TVAL_SET_BOOLEAN_UPDREF_ALT0 #define DUK_TVAL_SET_NUMBER_UPDREF DUK_TVAL_SET_NUMBER_UPDREF_ALT0 #define DUK_TVAL_SET_NUMBER_CHKFAST_UPDREF DUK_TVAL_SET_NUMBER_CHKFAST_UPDREF_ALT0 #define DUK_TVAL_SET_DOUBLE_UPDREF DUK_TVAL_SET_DOUBLE_UPDREF_ALT0 #define DUK_TVAL_SET_NAN_UPDREF DUK_TVAL_SET_NAN_UPDREF_ALT0 #if defined(DUK_USE_FASTINT) #define DUK_TVAL_SET_I48_UPDREF DUK_TVAL_SET_I48_UPDREF_ALT0 #define DUK_TVAL_SET_I32_UPDREF DUK_TVAL_SET_I32_UPDREF_ALT0 #define DUK_TVAL_SET_U32_UPDREF DUK_TVAL_SET_U32_UPDREF_ALT0 #else #define DUK_TVAL_SET_I48_UPDREF DUK_TVAL_SET_DOUBLE_CAST_UPDREF /* XXX: fast-int-to-double */ #define DUK_TVAL_SET_I32_UPDREF DUK_TVAL_SET_DOUBLE_CAST_UPDREF #define DUK_TVAL_SET_U32_UPDREF DUK_TVAL_SET_DOUBLE_CAST_UPDREF #endif /* DUK_USE_FASTINT */ #define DUK_TVAL_SET_FASTINT_UPDREF DUK_TVAL_SET_I48_UPDREF /* convenience */ #define DUK_TVAL_SET_LIGHTFUNC_UPDREF DUK_TVAL_SET_LIGHTFUNC_UPDREF_ALT0 #define DUK_TVAL_SET_STRING_UPDREF DUK_TVAL_SET_STRING_UPDREF_ALT0 #define DUK_TVAL_SET_OBJECT_UPDREF DUK_TVAL_SET_OBJECT_UPDREF_ALT0 #define DUK_TVAL_SET_BUFFER_UPDREF DUK_TVAL_SET_BUFFER_UPDREF_ALT0 #define DUK_TVAL_SET_POINTER_UPDREF DUK_TVAL_SET_POINTER_UPDREF_ALT0 #define DUK_TVAL_SET_TVAL_UPDREF DUK_TVAL_SET_TVAL_UPDREF_ALT0 #define DUK_TVAL_SET_TVAL_UPDREF_FAST DUK_TVAL_SET_TVAL_UPDREF_ALT0 #define DUK_TVAL_SET_TVAL_UPDREF_SLOW DUK_TVAL_SET_TVAL_UPDREF_ALT0 #endif /* DUK_USE_REFERENCE_COUNTING */ /* * Some convenience macros that don't have optimized implementations now. */ #define DUK_TVAL_SET_TVAL_UPDREF_NORZ(thr, tv_dst, tv_src) \ do { \ duk_hthread *duk__thr = (thr); \ duk_tval *duk__dst = (tv_dst); \ duk_tval *duk__src = (tv_src); \ DUK_UNREF(duk__thr); \ DUK_TVAL_DECREF_NORZ(thr, duk__dst); \ DUK_TVAL_SET_TVAL(duk__dst, duk__src); \ DUK_TVAL_INCREF(thr, duk__dst); \ } while (0) #define DUK_TVAL_SET_U32_UPDREF_NORZ(thr, tv_dst, val) \ do { \ duk_hthread *duk__thr = (thr); \ duk_tval *duk__dst = (tv_dst); \ duk_uint32_t duk__val = (duk_uint32_t) (val); \ DUK_UNREF(duk__thr); \ DUK_TVAL_DECREF_NORZ(thr, duk__dst); \ DUK_TVAL_SET_U32(duk__dst, duk__val); \ } while (0) /* * Prototypes */ #if defined(DUK_USE_REFERENCE_COUNTING) #if defined(DUK_USE_FINALIZER_SUPPORT) DUK_INTERNAL_DECL void duk_refzero_check_slow(duk_hthread *thr); DUK_INTERNAL_DECL void duk_refzero_check_fast(duk_hthread *thr); #endif DUK_INTERNAL_DECL void duk_heaphdr_refcount_finalize_norz(duk_heap *heap, duk_heaphdr *hdr); DUK_INTERNAL_DECL void duk_hobject_refcount_finalize_norz(duk_heap *heap, duk_hobject *h); #if 0 /* Not needed: fast path handles inline; slow path uses duk_heaphdr_decref() which is needed anyway. */ DUK_INTERNAL_DECL void duk_hstring_decref(duk_hthread *thr, duk_hstring *h); DUK_INTERNAL_DECL void duk_hstring_decref_norz(duk_hthread *thr, duk_hstring *h); DUK_INTERNAL_DECL void duk_hbuffer_decref(duk_hthread *thr, duk_hbuffer *h); DUK_INTERNAL_DECL void duk_hbuffer_decref_norz(duk_hthread *thr, duk_hbuffer *h); DUK_INTERNAL_DECL void duk_hobject_decref(duk_hthread *thr, duk_hobject *h); DUK_INTERNAL_DECL void duk_hobject_decref_norz(duk_hthread *thr, duk_hobject *h); #endif DUK_INTERNAL_DECL void duk_heaphdr_refzero(duk_hthread *thr, duk_heaphdr *h); DUK_INTERNAL_DECL void duk_heaphdr_refzero_norz(duk_hthread *thr, duk_heaphdr *h); #if defined(DUK_USE_FAST_REFCOUNT_DEFAULT) DUK_INTERNAL_DECL void duk_hstring_refzero(duk_hthread *thr, duk_hstring *h); /* no 'norz' variant */ DUK_INTERNAL_DECL void duk_hbuffer_refzero(duk_hthread *thr, duk_hbuffer *h); /* no 'norz' variant */ DUK_INTERNAL_DECL void duk_hobject_refzero(duk_hthread *thr, duk_hobject *h); DUK_INTERNAL_DECL void duk_hobject_refzero_norz(duk_hthread *thr, duk_hobject *h); #else DUK_INTERNAL_DECL void duk_tval_incref(duk_tval *tv); DUK_INTERNAL_DECL void duk_tval_decref(duk_hthread *thr, duk_tval *tv); DUK_INTERNAL_DECL void duk_tval_decref_norz(duk_hthread *thr, duk_tval *tv); DUK_INTERNAL_DECL void duk_heaphdr_incref(duk_heaphdr *h); DUK_INTERNAL_DECL void duk_heaphdr_decref(duk_hthread *thr, duk_heaphdr *h); DUK_INTERNAL_DECL void duk_heaphdr_decref_norz(duk_hthread *thr, duk_heaphdr *h); #endif #else /* DUK_USE_REFERENCE_COUNTING */ /* no refcounting */ #endif /* DUK_USE_REFERENCE_COUNTING */ #endif /* DUK_REFCOUNT_H_INCLUDED */ /* #include duk_api_internal.h */ #line 1 "duk_api_internal.h" /* * Internal API calls which have (stack and other) semantics similar * to the public API. */ #if !defined(DUK_API_INTERNAL_H_INCLUDED) #define DUK_API_INTERNAL_H_INCLUDED /* Inline macro helpers. */ #if defined(DUK_USE_PREFER_SIZE) #define DUK_INLINE_PERF #define DUK_ALWAYS_INLINE_PERF #define DUK_NOINLINE_PERF #else #define DUK_INLINE_PERF DUK_INLINE #define DUK_ALWAYS_INLINE_PERF DUK_ALWAYS_INLINE #define DUK_NOINLINE_PERF DUK_NOINLINE #endif /* Inline macro helpers, for bytecode executor. */ #if defined(DUK_USE_EXEC_PREFER_SIZE) #define DUK_EXEC_INLINE_PERF #define DUK_EXEC_ALWAYS_INLINE_PERF #define DUK_EXEC_NOINLINE_PERF #else #define DUK_EXEC_INLINE_PERF DUK_INLINE #define DUK_EXEC_ALWAYS_INLINE_PERF DUK_ALWAYS_INLINE #define DUK_EXEC_NOINLINE_PERF DUK_NOINLINE #endif /* duk_push_sprintf constants */ #define DUK_PUSH_SPRINTF_INITIAL_SIZE 256L #define DUK_PUSH_SPRINTF_SANITY_LIMIT (1L * 1024L * 1024L * 1024L) /* Flag ORed to err_code to indicate __FILE__ / __LINE__ is not * blamed as source of error for error fileName / lineNumber. */ #define DUK_ERRCODE_FLAG_NOBLAME_FILELINE (1L << 24) /* Current convention is to use duk_size_t for value stack sizes and global indices, * and duk_idx_t for local frame indices. */ DUK_INTERNAL_DECL void duk_valstack_grow_check_throw(duk_hthread *thr, duk_size_t min_bytes); DUK_INTERNAL_DECL duk_bool_t duk_valstack_grow_check_nothrow(duk_hthread *thr, duk_size_t min_bytes); DUK_INTERNAL_DECL void duk_valstack_shrink_check_nothrow(duk_hthread *thr, duk_bool_t snug); DUK_INTERNAL_DECL void duk_copy_tvals_incref(duk_hthread *thr, duk_tval *tv_dst, duk_tval *tv_src, duk_size_t count); DUK_INTERNAL_DECL duk_tval *duk_reserve_gap(duk_hthread *thr, duk_idx_t idx_base, duk_idx_t count); DUK_INTERNAL_DECL void duk_set_top_unsafe(duk_hthread *thr, duk_idx_t idx); DUK_INTERNAL_DECL void duk_set_top_and_wipe(duk_hthread *thr, duk_idx_t top, duk_idx_t idx_wipe_start); DUK_INTERNAL_DECL void duk_dup_0(duk_hthread *thr); DUK_INTERNAL_DECL void duk_dup_1(duk_hthread *thr); DUK_INTERNAL_DECL void duk_dup_2(duk_hthread *thr); /* duk_dup_m1() would be same as duk_dup_top() */ DUK_INTERNAL_DECL void duk_dup_m2(duk_hthread *thr); DUK_INTERNAL_DECL void duk_dup_m3(duk_hthread *thr); DUK_INTERNAL_DECL void duk_dup_m4(duk_hthread *thr); DUK_INTERNAL_DECL void duk_remove_unsafe(duk_hthread *thr, duk_idx_t idx); DUK_INTERNAL_DECL void duk_remove_m2(duk_hthread *thr); DUK_INTERNAL_DECL void duk_remove_n(duk_hthread *thr, duk_idx_t idx, duk_idx_t count); DUK_INTERNAL_DECL void duk_remove_n_unsafe(duk_hthread *thr, duk_idx_t idx, duk_idx_t count); DUK_INTERNAL_DECL duk_int_t duk_get_type_tval(duk_tval *tv); DUK_INTERNAL_DECL duk_uint_t duk_get_type_mask_tval(duk_tval *tv); #if defined(DUK_USE_VERBOSE_ERRORS) && defined(DUK_USE_PARANOID_ERRORS) DUK_INTERNAL_DECL const char *duk_get_type_name(duk_hthread *thr, duk_idx_t idx); #endif DUK_INTERNAL_DECL duk_small_uint_t duk_get_class_number(duk_hthread *thr, duk_idx_t idx); DUK_INTERNAL_DECL duk_tval *duk_get_tval(duk_hthread *thr, duk_idx_t idx); DUK_INTERNAL_DECL duk_tval *duk_get_tval_or_unused(duk_hthread *thr, duk_idx_t idx); DUK_INTERNAL_DECL duk_tval *duk_require_tval(duk_hthread *thr, duk_idx_t idx); DUK_INTERNAL_DECL void duk_push_tval(duk_hthread *thr, duk_tval *tv); /* Push the current 'this' binding; throw TypeError if binding is not object * coercible (CheckObjectCoercible). */ DUK_INTERNAL_DECL void duk_push_this_check_object_coercible(duk_hthread *thr); /* duk_push_this() + CheckObjectCoercible() + duk_to_object() */ DUK_INTERNAL_DECL duk_hobject *duk_push_this_coercible_to_object(duk_hthread *thr); /* duk_push_this() + CheckObjectCoercible() + duk_to_string() */ DUK_INTERNAL_DECL duk_hstring *duk_push_this_coercible_to_string(duk_hthread *thr); DUK_INTERNAL_DECL duk_hstring *duk_push_uint_to_hstring(duk_hthread *thr, duk_uint_t i); /* Get a borrowed duk_tval pointer to the current 'this' binding. Caller must * make sure there's an active callstack entry. Note that the returned pointer * is unstable with regards to side effects. */ DUK_INTERNAL_DECL duk_tval *duk_get_borrowed_this_tval(duk_hthread *thr); /* XXX: add fastint support? */ #define duk_push_u64(thr, val) duk_push_number((thr), (duk_double_t) (val)) #define duk_push_i64(thr, val) duk_push_number((thr), (duk_double_t) (val)) /* duk_push_(u)int() is guaranteed to support at least (un)signed 32-bit range */ #define duk_push_u32(thr, val) duk_push_uint((thr), (duk_uint_t) (val)) #define duk_push_i32(thr, val) duk_push_int((thr), (duk_int_t) (val)) /* sometimes stack and array indices need to go on the stack */ #define duk_push_idx(thr, val) duk_push_int((thr), (duk_int_t) (val)) #define duk_push_uarridx(thr, val) duk_push_uint((thr), (duk_uint_t) (val)) #define duk_push_size_t(thr, val) duk_push_uint((thr), (duk_uint_t) (val)) /* XXX: assumed to fit for now */ DUK_INTERNAL_DECL duk_bool_t duk_is_string_notsymbol(duk_hthread *thr, duk_idx_t idx); DUK_INTERNAL_DECL duk_bool_t duk_is_callable_tval(duk_hthread *thr, duk_tval *tv); DUK_INTERNAL_DECL duk_bool_t duk_is_bare_object(duk_hthread *thr, duk_idx_t idx); DUK_INTERNAL_DECL duk_hstring *duk_get_hstring(duk_hthread *thr, duk_idx_t idx); DUK_INTERNAL_DECL duk_hstring *duk_get_hstring_notsymbol(duk_hthread *thr, duk_idx_t idx); DUK_INTERNAL_DECL const char *duk_get_string_notsymbol(duk_hthread *thr, duk_idx_t idx); DUK_INTERNAL_DECL duk_hobject *duk_get_hobject(duk_hthread *thr, duk_idx_t idx); DUK_INTERNAL_DECL duk_hbuffer *duk_get_hbuffer(duk_hthread *thr, duk_idx_t idx); DUK_INTERNAL_DECL duk_hthread *duk_get_hthread(duk_hthread *thr, duk_idx_t idx); DUK_INTERNAL_DECL duk_hcompfunc *duk_get_hcompfunc(duk_hthread *thr, duk_idx_t idx); DUK_INTERNAL_DECL duk_hnatfunc *duk_get_hnatfunc(duk_hthread *thr, duk_idx_t idx); DUK_INTERNAL_DECL void *duk_get_buffer_data_raw(duk_hthread *thr, duk_idx_t idx, duk_size_t *out_size, void *def_ptr, duk_size_t def_len, duk_bool_t throw_flag, duk_bool_t *out_isbuffer); DUK_INTERNAL_DECL duk_hobject *duk_get_hobject_with_class(duk_hthread *thr, duk_idx_t idx, duk_small_uint_t classnum); DUK_INTERNAL_DECL duk_hobject *duk_get_hobject_promote_mask(duk_hthread *thr, duk_idx_t idx, duk_uint_t type_mask); DUK_INTERNAL_DECL duk_hobject *duk_require_hobject_promote_mask(duk_hthread *thr, duk_idx_t idx, duk_uint_t type_mask); DUK_INTERNAL_DECL duk_hobject *duk_require_hobject_accept_mask(duk_hthread *thr, duk_idx_t idx, duk_uint_t type_mask); #define duk_require_hobject_promote_lfunc(thr, idx) duk_require_hobject_promote_mask((thr), (idx), DUK_TYPE_MASK_LIGHTFUNC) #define duk_get_hobject_promote_lfunc(thr, idx) duk_get_hobject_promote_mask((thr), (idx), DUK_TYPE_MASK_LIGHTFUNC) #if 0 /*unused*/ DUK_INTERNAL_DECL void *duk_get_voidptr(duk_hthread *thr, duk_idx_t idx); #endif DUK_INTERNAL_DECL duk_hstring *duk_known_hstring(duk_hthread *thr, duk_idx_t idx); DUK_INTERNAL_DECL duk_hobject *duk_known_hobject(duk_hthread *thr, duk_idx_t idx); DUK_INTERNAL_DECL duk_hbuffer *duk_known_hbuffer(duk_hthread *thr, duk_idx_t idx); DUK_INTERNAL_DECL duk_hcompfunc *duk_known_hcompfunc(duk_hthread *thr, duk_idx_t idx); DUK_INTERNAL_DECL duk_hnatfunc *duk_known_hnatfunc(duk_hthread *thr, duk_idx_t idx); DUK_INTERNAL_DECL duk_double_t duk_to_number_tval(duk_hthread *thr, duk_tval *tv); DUK_INTERNAL_DECL duk_hstring *duk_to_hstring(duk_hthread *thr, duk_idx_t idx); DUK_INTERNAL_DECL duk_hstring *duk_to_hstring_m1(duk_hthread *thr); DUK_INTERNAL_DECL duk_hstring *duk_to_hstring_acceptsymbol(duk_hthread *thr, duk_idx_t idx); DUK_INTERNAL_DECL duk_hobject *duk_to_hobject(duk_hthread *thr, duk_idx_t idx); DUK_INTERNAL_DECL duk_double_t duk_to_number_m1(duk_hthread *thr); DUK_INTERNAL_DECL duk_double_t duk_to_number_m2(duk_hthread *thr); DUK_INTERNAL_DECL duk_bool_t duk_to_boolean_top_pop(duk_hthread *thr); #if defined(DUK_USE_DEBUGGER_SUPPORT) /* only needed by debugger for now */ DUK_INTERNAL_DECL duk_hstring *duk_safe_to_hstring(duk_hthread *thr, duk_idx_t idx); #endif DUK_INTERNAL_DECL void duk_push_class_string_tval(duk_hthread *thr, duk_tval *tv, duk_bool_t avoid_side_effects); DUK_INTERNAL_DECL duk_int_t duk_to_int_clamped_raw(duk_hthread *thr, duk_idx_t idx, duk_int_t minval, duk_int_t maxval, duk_bool_t *out_clamped); /* out_clamped=NULL, RangeError if outside range */ DUK_INTERNAL_DECL duk_int_t duk_to_int_clamped(duk_hthread *thr, duk_idx_t idx, duk_int_t minval, duk_int_t maxval); DUK_INTERNAL_DECL duk_int_t duk_to_int_check_range(duk_hthread *thr, duk_idx_t idx, duk_int_t minval, duk_int_t maxval); #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) DUK_INTERNAL_DECL duk_uint8_t duk_to_uint8clamped(duk_hthread *thr, duk_idx_t idx); #endif DUK_INTERNAL_DECL duk_hstring *duk_to_property_key_hstring(duk_hthread *thr, duk_idx_t idx); DUK_INTERNAL_DECL duk_hstring *duk_require_hstring(duk_hthread *thr, duk_idx_t idx); DUK_INTERNAL_DECL duk_hstring *duk_require_hstring_notsymbol(duk_hthread *thr, duk_idx_t idx); DUK_INTERNAL_DECL const char *duk_require_lstring_notsymbol(duk_hthread *thr, duk_idx_t idx, duk_size_t *out_len); DUK_INTERNAL_DECL const char *duk_require_string_notsymbol(duk_hthread *thr, duk_idx_t idx); DUK_INTERNAL_DECL duk_hobject *duk_require_hobject(duk_hthread *thr, duk_idx_t idx); DUK_INTERNAL_DECL duk_hbuffer *duk_require_hbuffer(duk_hthread *thr, duk_idx_t idx); DUK_INTERNAL_DECL duk_hthread *duk_require_hthread(duk_hthread *thr, duk_idx_t idx); DUK_INTERNAL_DECL duk_hcompfunc *duk_require_hcompfunc(duk_hthread *thr, duk_idx_t idx); DUK_INTERNAL_DECL duk_hnatfunc *duk_require_hnatfunc(duk_hthread *thr, duk_idx_t idx); DUK_INTERNAL_DECL duk_hobject *duk_require_hobject_with_class(duk_hthread *thr, duk_idx_t idx, duk_small_uint_t classnum); DUK_INTERNAL_DECL void duk_push_hstring(duk_hthread *thr, duk_hstring *h); DUK_INTERNAL_DECL void duk_push_hstring_stridx(duk_hthread *thr, duk_small_uint_t stridx); DUK_INTERNAL_DECL void duk_push_hstring_empty(duk_hthread *thr); DUK_INTERNAL_DECL void duk_push_hobject(duk_hthread *thr, duk_hobject *h); DUK_INTERNAL_DECL void duk_push_hbuffer(duk_hthread *thr, duk_hbuffer *h); #define duk_push_hthread(thr, h) duk_push_hobject((thr), (duk_hobject *) (h)) #define duk_push_hnatfunc(thr, h) duk_push_hobject((thr), (duk_hobject *) (h)) DUK_INTERNAL_DECL void duk_push_hobject_bidx(duk_hthread *thr, duk_small_int_t builtin_idx); DUK_INTERNAL_DECL duk_hobject *duk_push_object_helper(duk_hthread *thr, duk_uint_t hobject_flags_and_class, duk_small_int_t prototype_bidx); DUK_INTERNAL_DECL duk_hobject *duk_push_object_helper_proto(duk_hthread *thr, duk_uint_t hobject_flags_and_class, duk_hobject *proto); DUK_INTERNAL_DECL duk_hcompfunc *duk_push_hcompfunc(duk_hthread *thr); DUK_INTERNAL_DECL duk_hboundfunc *duk_push_hboundfunc(duk_hthread *thr); DUK_INTERNAL_DECL void duk_push_c_function_builtin(duk_hthread *thr, duk_c_function func, duk_int_t nargs); DUK_INTERNAL_DECL void duk_push_c_function_builtin_noconstruct(duk_hthread *thr, duk_c_function func, duk_int_t nargs); /* XXX: duk_push_harray() and duk_push_hcompfunc() are inconsistent with * duk_push_hobject() etc which don't create a new value. */ DUK_INTERNAL_DECL duk_harray *duk_push_harray(duk_hthread *thr); DUK_INTERNAL_DECL duk_harray *duk_push_harray_with_size(duk_hthread *thr, duk_uint32_t size); DUK_INTERNAL_DECL duk_tval *duk_push_harray_with_size_outptr(duk_hthread *thr, duk_uint32_t size); DUK_INTERNAL_DECL void duk_push_string_funcptr(duk_hthread *thr, duk_uint8_t *ptr, duk_size_t sz); DUK_INTERNAL_DECL void duk_push_lightfunc_name_raw(duk_hthread *thr, duk_c_function func, duk_small_uint_t lf_flags); DUK_INTERNAL_DECL void duk_push_lightfunc_name(duk_hthread *thr, duk_tval *tv); DUK_INTERNAL_DECL void duk_push_lightfunc_tostring(duk_hthread *thr, duk_tval *tv); #if 0 /* not used yet */ DUK_INTERNAL_DECL void duk_push_hnatfunc_name(duk_hthread *thr, duk_hnatfunc *h); #endif #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) DUK_INTERNAL_DECL duk_hbufobj *duk_push_bufobj_raw(duk_hthread *thr, duk_uint_t hobject_flags_and_class, duk_small_int_t prototype_bidx); #endif DUK_INTERNAL_DECL void *duk_push_fixed_buffer_nozero(duk_hthread *thr, duk_size_t len); DUK_INTERNAL_DECL void *duk_push_fixed_buffer_zero(duk_hthread *thr, duk_size_t len); DUK_INTERNAL_DECL const char *duk_push_string_readable(duk_hthread *thr, duk_idx_t idx); DUK_INTERNAL_DECL const char *duk_push_string_tval_readable(duk_hthread *thr, duk_tval *tv); DUK_INTERNAL_DECL const char *duk_push_string_tval_readable_error(duk_hthread *thr, duk_tval *tv); /* The duk_xxx_prop_stridx_short() variants expect their arguments to be short * enough to be packed into a single 32-bit integer argument. Argument limits * vary per call; typically 16 bits are assigned to the signed value stack index * and the stridx. In practice these work well for footprint with constant * arguments and such call sites are also easiest to verify to be correct. */ DUK_INTERNAL_DECL duk_bool_t duk_get_prop_stridx(duk_hthread *thr, duk_idx_t obj_idx, duk_small_uint_t stridx); /* [] -> [val] */ DUK_INTERNAL_DECL duk_bool_t duk_get_prop_stridx_short_raw(duk_hthread *thr, duk_uint_t packed_args); #define duk_get_prop_stridx_short(thr, obj_idx, stridx) \ (DUK_ASSERT_EXPR((duk_int_t) (obj_idx) >= -0x8000L && (duk_int_t) (obj_idx) <= 0x7fffL), \ DUK_ASSERT_EXPR((duk_int_t) (stridx) >= 0 && (duk_int_t) (stridx) <= 0xffffL), \ duk_get_prop_stridx_short_raw((thr), (((duk_uint_t) (obj_idx)) << 16) + ((duk_uint_t) (stridx)))) DUK_INTERNAL_DECL duk_bool_t duk_get_prop_stridx_boolean(duk_hthread *thr, duk_idx_t obj_idx, duk_small_uint_t stridx, duk_bool_t *out_has_prop); /* [] -> [] */ DUK_INTERNAL_DECL duk_bool_t duk_xget_owndataprop(duk_hthread *thr, duk_idx_t obj_idx); DUK_INTERNAL_DECL duk_bool_t duk_xget_owndataprop_stridx(duk_hthread *thr, duk_idx_t obj_idx, duk_small_uint_t stridx); DUK_INTERNAL_DECL duk_bool_t duk_xget_owndataprop_stridx_short_raw(duk_hthread *thr, duk_uint_t packed_args); #define duk_xget_owndataprop_stridx_short(thr, obj_idx, stridx) \ (DUK_ASSERT_EXPR((duk_int_t) (obj_idx) >= -0x8000L && (duk_int_t) (obj_idx) <= 0x7fffL), \ DUK_ASSERT_EXPR((duk_int_t) (stridx) >= 0 && (duk_int_t) (stridx) <= 0xffffL), \ duk_xget_owndataprop_stridx_short_raw((thr), (((duk_uint_t) (obj_idx)) << 16) + ((duk_uint_t) (stridx)))) DUK_INTERNAL_DECL duk_bool_t duk_put_prop_stridx(duk_hthread *thr, duk_idx_t obj_idx, duk_small_uint_t stridx); /* [val] -> [] */ DUK_INTERNAL_DECL duk_bool_t duk_put_prop_stridx_short_raw(duk_hthread *thr, duk_uint_t packed_args); #define duk_put_prop_stridx_short(thr, obj_idx, stridx) \ (DUK_ASSERT_EXPR((duk_int_t) (obj_idx) >= -0x8000L && (duk_int_t) (obj_idx) <= 0x7fffL), \ DUK_ASSERT_EXPR((duk_int_t) (stridx) >= 0 && (duk_int_t) (stridx) <= 0xffffL), \ duk_put_prop_stridx_short_raw((thr), (((duk_uint_t) (obj_idx)) << 16) + ((duk_uint_t) (stridx)))) DUK_INTERNAL_DECL duk_bool_t duk_del_prop_stridx(duk_hthread *thr, duk_idx_t obj_idx, duk_small_uint_t stridx); /* [] -> [] */ #if 0 /* Too few call sites to be useful. */ DUK_INTERNAL_DECL duk_bool_t duk_del_prop_stridx_short_raw(duk_hthread *thr, duk_uint_t packed_args); #define duk_del_prop_stridx_short(thr, obj_idx, stridx) \ (DUK_ASSERT_EXPR((obj_idx) >= -0x8000L && (obj_idx) <= 0x7fffL), \ DUK_ASSERT_EXPR((stridx) >= 0 && (stridx) <= 0xffffL), \ duk_del_prop_stridx_short_raw((thr), (((duk_uint_t) (obj_idx)) << 16) + ((duk_uint_t) (stridx)))) #endif #define duk_del_prop_stridx_short(thr, obj_idx, stridx) duk_del_prop_stridx((thr), (obj_idx), (stridx)) DUK_INTERNAL_DECL duk_bool_t duk_has_prop_stridx(duk_hthread *thr, duk_idx_t obj_idx, duk_small_uint_t stridx); /* [] -> [] */ #if 0 /* Too few call sites to be useful. */ DUK_INTERNAL_DECL duk_bool_t duk_has_prop_stridx_short_raw(duk_hthread *thr, duk_uint_t packed_args); #define duk_has_prop_stridx_short(thr, obj_idx, stridx) \ (DUK_ASSERT_EXPR((obj_idx) >= -0x8000L && (obj_idx) <= 0x7fffL), \ DUK_ASSERT_EXPR((stridx) >= 0 && (stridx) <= 0xffffL), \ duk_has_prop_stridx_short_raw((thr), (((duk_uint_t) (obj_idx)) << 16) + ((duk_uint_t) (stridx)))) #endif #define duk_has_prop_stridx_short(thr, obj_idx, stridx) duk_has_prop_stridx((thr), (obj_idx), (stridx)) DUK_INTERNAL_DECL void duk_xdef_prop(duk_hthread *thr, duk_idx_t obj_idx, duk_small_uint_t desc_flags); /* [key val] -> [] */ DUK_INTERNAL_DECL void duk_xdef_prop_index(duk_hthread *thr, duk_idx_t obj_idx, duk_uarridx_t arr_idx, duk_small_uint_t desc_flags); /* [val] -> [] */ /* XXX: Because stridx and desc_flags have a limited range, this call could * always pack stridx and desc_flags into a single argument. */ DUK_INTERNAL_DECL void duk_xdef_prop_stridx(duk_hthread *thr, duk_idx_t obj_idx, duk_small_uint_t stridx, duk_small_uint_t desc_flags); /* [val] -> [] */ DUK_INTERNAL_DECL void duk_xdef_prop_stridx_short_raw(duk_hthread *thr, duk_uint_t packed_args); #define duk_xdef_prop_stridx_short(thr, obj_idx, stridx, desc_flags) \ (DUK_ASSERT_EXPR((duk_int_t) (obj_idx) >= -0x80L && (duk_int_t) (obj_idx) <= 0x7fL), \ DUK_ASSERT_EXPR((duk_int_t) (stridx) >= 0 && (duk_int_t) (stridx) <= 0xffffL), \ DUK_ASSERT_EXPR((duk_int_t) (desc_flags) >= 0 && (duk_int_t) (desc_flags) <= 0xffL), \ duk_xdef_prop_stridx_short_raw((thr), \ (((duk_uint_t) (obj_idx)) << 24) + (((duk_uint_t) (stridx)) << 8) + \ (duk_uint_t) (desc_flags))) #define duk_xdef_prop_wec(thr, obj_idx) duk_xdef_prop((thr), (obj_idx), DUK_PROPDESC_FLAGS_WEC) #define duk_xdef_prop_index_wec(thr, obj_idx, arr_idx) duk_xdef_prop_index((thr), (obj_idx), (arr_idx), DUK_PROPDESC_FLAGS_WEC) #define duk_xdef_prop_stridx_wec(thr, obj_idx, stridx) duk_xdef_prop_stridx((thr), (obj_idx), (stridx), DUK_PROPDESC_FLAGS_WEC) #define duk_xdef_prop_stridx_short_wec(thr, obj_idx, stridx) \ duk_xdef_prop_stridx_short((thr), (obj_idx), (stridx), DUK_PROPDESC_FLAGS_WEC) #if 0 /*unused*/ DUK_INTERNAL_DECL void duk_xdef_prop_stridx_builtin(duk_hthread *thr, duk_idx_t obj_idx, duk_small_uint_t stridx, duk_small_int_t builtin_idx, duk_small_uint_t desc_flags); /* [] -> [] */ #endif DUK_INTERNAL_DECL void duk_xdef_prop_stridx_thrower(duk_hthread *thr, duk_idx_t obj_idx, duk_small_uint_t stridx); /* [] -> [] */ DUK_INTERNAL_DECL duk_bool_t duk_get_method_stridx(duk_hthread *thr, duk_idx_t idx, duk_small_uint_t stridx); DUK_INTERNAL_DECL void duk_pack(duk_hthread *thr, duk_idx_t count); DUK_INTERNAL_DECL duk_idx_t duk_unpack_array_like(duk_hthread *thr, duk_idx_t idx); #if 0 DUK_INTERNAL_DECL void duk_unpack(duk_hthread *thr); #endif DUK_INTERNAL_DECL void duk_push_symbol_descriptive_string(duk_hthread *thr, duk_hstring *h); DUK_INTERNAL_DECL void duk_resolve_nonbound_function(duk_hthread *thr); DUK_INTERNAL_DECL duk_idx_t duk_get_top_require_min(duk_hthread *thr, duk_idx_t min_top); DUK_INTERNAL_DECL duk_idx_t duk_get_top_index_unsafe(duk_hthread *thr); DUK_INTERNAL_DECL void duk_pop_n_unsafe(duk_hthread *thr, duk_idx_t count); DUK_INTERNAL_DECL void duk_pop_unsafe(duk_hthread *thr); DUK_INTERNAL_DECL void duk_pop_2_unsafe(duk_hthread *thr); DUK_INTERNAL_DECL void duk_pop_3_unsafe(duk_hthread *thr); DUK_INTERNAL_DECL void duk_pop_n_nodecref_unsafe(duk_hthread *thr, duk_idx_t count); DUK_INTERNAL_DECL void duk_pop_nodecref_unsafe(duk_hthread *thr); DUK_INTERNAL_DECL void duk_pop_2_nodecref_unsafe(duk_hthread *thr); DUK_INTERNAL_DECL void duk_pop_3_nodecref_unsafe(duk_hthread *thr); DUK_INTERNAL_DECL void duk_pop_undefined(duk_hthread *thr); DUK_INTERNAL_DECL void duk_compact_m1(duk_hthread *thr); DUK_INTERNAL_DECL void duk_seal_freeze_raw(duk_hthread *thr, duk_idx_t obj_idx, duk_bool_t is_freeze); DUK_INTERNAL_DECL void duk_insert_undefined(duk_hthread *thr, duk_idx_t idx); DUK_INTERNAL_DECL void duk_insert_undefined_n(duk_hthread *thr, duk_idx_t idx, duk_idx_t count); DUK_INTERNAL_DECL void duk_concat_2(duk_hthread *thr); DUK_INTERNAL_DECL duk_int_t duk_pcall_method_flags(duk_hthread *thr, duk_idx_t nargs, duk_small_uint_t call_flags); #if defined(DUK_USE_SYMBOL_BUILTIN) DUK_INTERNAL_DECL void duk_to_primitive_ordinary(duk_hthread *thr, duk_idx_t idx, duk_int_t hint); #endif DUK_INTERNAL_DECL void duk_clear_prototype(duk_hthread *thr, duk_idx_t idx); /* Raw internal valstack access macros: access is unsafe so call site * must have a guarantee that the index is valid. When that is the case, * using these macro results in faster and smaller code than duk_get_tval(). * Both 'ctx' and 'idx' are evaluted multiple times, but only for asserts. */ #define DUK_ASSERT_VALID_NEGIDX(thr, idx) \ (DUK_ASSERT_EXPR((duk_int_t) (idx) < 0), DUK_ASSERT_EXPR(duk_is_valid_index((thr), (idx)))) #define DUK_ASSERT_VALID_POSIDX(thr, idx) \ (DUK_ASSERT_EXPR((duk_int_t) (idx) >= 0), DUK_ASSERT_EXPR(duk_is_valid_index((thr), (idx)))) #define DUK_GET_TVAL_NEGIDX(thr, idx) (DUK_ASSERT_VALID_NEGIDX((thr), (idx)), ((duk_hthread *) (thr))->valstack_top + (idx)) #define DUK_GET_TVAL_POSIDX(thr, idx) (DUK_ASSERT_VALID_POSIDX((thr), (idx)), ((duk_hthread *) (thr))->valstack_bottom + (idx)) #define DUK_GET_HOBJECT_NEGIDX(thr, idx) \ (DUK_ASSERT_VALID_NEGIDX((thr), (idx)), DUK_TVAL_GET_OBJECT(((duk_hthread *) (thr))->valstack_top + (idx))) #define DUK_GET_HOBJECT_POSIDX(thr, idx) \ (DUK_ASSERT_VALID_POSIDX((thr), (idx)), DUK_TVAL_GET_OBJECT(((duk_hthread *) (thr))->valstack_bottom + (idx))) #define DUK_GET_THIS_TVAL_PTR(thr) (DUK_ASSERT_EXPR((thr)->valstack_bottom > (thr)->valstack), (thr)->valstack_bottom - 1) DUK_INTERNAL_DECL duk_double_t duk_time_get_ecmascript_time(duk_hthread *thr); DUK_INTERNAL_DECL duk_double_t duk_time_get_ecmascript_time_nofrac(duk_hthread *thr); DUK_INTERNAL_DECL duk_double_t duk_time_get_monotonic_time(duk_hthread *thr); #endif /* DUK_API_INTERNAL_H_INCLUDED */ /* #include duk_hstring.h */ #line 1 "duk_hstring.h" /* * Heap string representation. * * Strings are byte sequences ordinarily stored in extended UTF-8 format, * allowing values larger than the official UTF-8 range (used internally) * and also allowing UTF-8 encoding of surrogate pairs (CESU-8 format). * Strings may also be invalid UTF-8 altogether which is the case e.g. with * strings used as internal property names and raw buffers converted to * strings. In such cases the 'clen' field contains an inaccurate value. * * ECMAScript requires support for 32-bit long strings. However, since each * 16-bit codepoint can take 3 bytes in CESU-8, this representation can only * support about 1.4G codepoint long strings in extreme cases. This is not * really a practical issue. */ #if !defined(DUK_HSTRING_H_INCLUDED) #define DUK_HSTRING_H_INCLUDED /* Impose a maximum string length for now. Restricted artificially to * ensure adding a heap header length won't overflow size_t. The limit * should be synchronized with DUK_HBUFFER_MAX_BYTELEN. * * E5.1 makes provisions to support strings longer than 4G characters. * This limit should be eliminated on 64-bit platforms (and increased * closer to maximum support on 32-bit platforms). */ #if defined(DUK_USE_STRLEN16) #define DUK_HSTRING_MAX_BYTELEN (0x0000ffffUL) #else #define DUK_HSTRING_MAX_BYTELEN (0x7fffffffUL) #endif /* XXX: could add flags for "is valid CESU-8" (ECMAScript compatible strings), * "is valid UTF-8", "is valid extended UTF-8" (internal strings are not, * regexp bytecode is), and "contains non-BMP characters". These are not * needed right now. */ /* With lowmem builds the high 16 bits of duk_heaphdr are used for other * purposes, so this leaves 7 duk_heaphdr flags and 9 duk_hstring flags. */ #define DUK_HSTRING_FLAG_ASCII DUK_HEAPHDR_USER_FLAG(0) /* string is ASCII, clen == blen */ #define DUK_HSTRING_FLAG_ARRIDX DUK_HEAPHDR_USER_FLAG(1) /* string is a valid array index */ #define DUK_HSTRING_FLAG_SYMBOL DUK_HEAPHDR_USER_FLAG(2) /* string is a symbol (invalid utf-8) */ #define DUK_HSTRING_FLAG_HIDDEN \ DUK_HEAPHDR_USER_FLAG(3) /* string is a hidden symbol (implies symbol, Duktape 1.x internal string) */ #define DUK_HSTRING_FLAG_RESERVED_WORD DUK_HEAPHDR_USER_FLAG(4) /* string is a reserved word (non-strict) */ #define DUK_HSTRING_FLAG_STRICT_RESERVED_WORD DUK_HEAPHDR_USER_FLAG(5) /* string is a reserved word (strict) */ #define DUK_HSTRING_FLAG_EVAL_OR_ARGUMENTS DUK_HEAPHDR_USER_FLAG(6) /* string is 'eval' or 'arguments' */ #define DUK_HSTRING_FLAG_EXTDATA DUK_HEAPHDR_USER_FLAG(7) /* string data is external (duk_hstring_external) */ #define DUK_HSTRING_FLAG_PINNED_LITERAL DUK_HEAPHDR_USER_FLAG(8) /* string is a literal, and pinned */ #define DUK_HSTRING_HAS_ASCII(x) DUK_HEAPHDR_CHECK_FLAG_BITS(&(x)->hdr, DUK_HSTRING_FLAG_ASCII) #define DUK_HSTRING_HAS_ARRIDX(x) DUK_HEAPHDR_CHECK_FLAG_BITS(&(x)->hdr, DUK_HSTRING_FLAG_ARRIDX) #define DUK_HSTRING_HAS_SYMBOL(x) DUK_HEAPHDR_CHECK_FLAG_BITS(&(x)->hdr, DUK_HSTRING_FLAG_SYMBOL) #define DUK_HSTRING_HAS_HIDDEN(x) DUK_HEAPHDR_CHECK_FLAG_BITS(&(x)->hdr, DUK_HSTRING_FLAG_HIDDEN) #define DUK_HSTRING_HAS_RESERVED_WORD(x) DUK_HEAPHDR_CHECK_FLAG_BITS(&(x)->hdr, DUK_HSTRING_FLAG_RESERVED_WORD) #define DUK_HSTRING_HAS_STRICT_RESERVED_WORD(x) DUK_HEAPHDR_CHECK_FLAG_BITS(&(x)->hdr, DUK_HSTRING_FLAG_STRICT_RESERVED_WORD) #define DUK_HSTRING_HAS_EVAL_OR_ARGUMENTS(x) DUK_HEAPHDR_CHECK_FLAG_BITS(&(x)->hdr, DUK_HSTRING_FLAG_EVAL_OR_ARGUMENTS) #define DUK_HSTRING_HAS_EXTDATA(x) DUK_HEAPHDR_CHECK_FLAG_BITS(&(x)->hdr, DUK_HSTRING_FLAG_EXTDATA) #define DUK_HSTRING_HAS_PINNED_LITERAL(x) DUK_HEAPHDR_CHECK_FLAG_BITS(&(x)->hdr, DUK_HSTRING_FLAG_PINNED_LITERAL) #define DUK_HSTRING_SET_ASCII(x) DUK_HEAPHDR_SET_FLAG_BITS(&(x)->hdr, DUK_HSTRING_FLAG_ASCII) #define DUK_HSTRING_SET_ARRIDX(x) DUK_HEAPHDR_SET_FLAG_BITS(&(x)->hdr, DUK_HSTRING_FLAG_ARRIDX) #define DUK_HSTRING_SET_SYMBOL(x) DUK_HEAPHDR_SET_FLAG_BITS(&(x)->hdr, DUK_HSTRING_FLAG_SYMBOL) #define DUK_HSTRING_SET_HIDDEN(x) DUK_HEAPHDR_SET_FLAG_BITS(&(x)->hdr, DUK_HSTRING_FLAG_HIDDEN) #define DUK_HSTRING_SET_RESERVED_WORD(x) DUK_HEAPHDR_SET_FLAG_BITS(&(x)->hdr, DUK_HSTRING_FLAG_RESERVED_WORD) #define DUK_HSTRING_SET_STRICT_RESERVED_WORD(x) DUK_HEAPHDR_SET_FLAG_BITS(&(x)->hdr, DUK_HSTRING_FLAG_STRICT_RESERVED_WORD) #define DUK_HSTRING_SET_EVAL_OR_ARGUMENTS(x) DUK_HEAPHDR_SET_FLAG_BITS(&(x)->hdr, DUK_HSTRING_FLAG_EVAL_OR_ARGUMENTS) #define DUK_HSTRING_SET_EXTDATA(x) DUK_HEAPHDR_SET_FLAG_BITS(&(x)->hdr, DUK_HSTRING_FLAG_EXTDATA) #define DUK_HSTRING_SET_PINNED_LITERAL(x) DUK_HEAPHDR_SET_FLAG_BITS(&(x)->hdr, DUK_HSTRING_FLAG_PINNED_LITERAL) #define DUK_HSTRING_CLEAR_ASCII(x) DUK_HEAPHDR_CLEAR_FLAG_BITS(&(x)->hdr, DUK_HSTRING_FLAG_ASCII) #define DUK_HSTRING_CLEAR_ARRIDX(x) DUK_HEAPHDR_CLEAR_FLAG_BITS(&(x)->hdr, DUK_HSTRING_FLAG_ARRIDX) #define DUK_HSTRING_CLEAR_SYMBOL(x) DUK_HEAPHDR_CLEAR_FLAG_BITS(&(x)->hdr, DUK_HSTRING_FLAG_SYMBOL) #define DUK_HSTRING_CLEAR_HIDDEN(x) DUK_HEAPHDR_CLEAR_FLAG_BITS(&(x)->hdr, DUK_HSTRING_FLAG_HIDDEN) #define DUK_HSTRING_CLEAR_RESERVED_WORD(x) DUK_HEAPHDR_CLEAR_FLAG_BITS(&(x)->hdr, DUK_HSTRING_FLAG_RESERVED_WORD) #define DUK_HSTRING_CLEAR_STRICT_RESERVED_WORD(x) DUK_HEAPHDR_CLEAR_FLAG_BITS(&(x)->hdr, DUK_HSTRING_FLAG_STRICT_RESERVED_WORD) #define DUK_HSTRING_CLEAR_EVAL_OR_ARGUMENTS(x) DUK_HEAPHDR_CLEAR_FLAG_BITS(&(x)->hdr, DUK_HSTRING_FLAG_EVAL_OR_ARGUMENTS) #define DUK_HSTRING_CLEAR_EXTDATA(x) DUK_HEAPHDR_CLEAR_FLAG_BITS(&(x)->hdr, DUK_HSTRING_FLAG_EXTDATA) #define DUK_HSTRING_CLEAR_PINNED_LITERAL(x) DUK_HEAPHDR_CLEAR_FLAG_BITS(&(x)->hdr, DUK_HSTRING_FLAG_PINNED_LITERAL) #if 0 /* Slightly smaller code without explicit flag, but explicit flag \ * is very useful when 'clen' is dropped. \ */ #define DUK_HSTRING_IS_ASCII(x) (DUK_HSTRING_GET_BYTELEN((x)) == DUK_HSTRING_GET_CHARLEN((x))) #endif #define DUK_HSTRING_IS_ASCII(x) DUK_HSTRING_HAS_ASCII((x)) /* lazily set! */ #define DUK_HSTRING_IS_EMPTY(x) (DUK_HSTRING_GET_BYTELEN((x)) == 0) #if defined(DUK_USE_STRHASH16) #define DUK_HSTRING_GET_HASH(x) ((x)->hdr.h_flags >> 16) #define DUK_HSTRING_SET_HASH(x, v) \ do { \ (x)->hdr.h_flags = ((x)->hdr.h_flags & 0x0000ffffUL) | ((v) << 16); \ } while (0) #else #define DUK_HSTRING_GET_HASH(x) ((x)->hash) #define DUK_HSTRING_SET_HASH(x, v) \ do { \ (x)->hash = (v); \ } while (0) #endif #if defined(DUK_USE_STRLEN16) #define DUK_HSTRING_GET_BYTELEN(x) ((x)->hdr.h_strextra16) #define DUK_HSTRING_SET_BYTELEN(x, v) \ do { \ (x)->hdr.h_strextra16 = (v); \ } while (0) #if defined(DUK_USE_HSTRING_CLEN) #define DUK_HSTRING_GET_CHARLEN(x) duk_hstring_get_charlen((x)) #define DUK_HSTRING_SET_CHARLEN(x, v) \ do { \ (x)->clen16 = (v); \ } while (0) #else #define DUK_HSTRING_GET_CHARLEN(x) duk_hstring_get_charlen((x)) #define DUK_HSTRING_SET_CHARLEN(x, v) \ do { \ DUK_ASSERT(0); /* should never be called */ \ } while (0) #endif #else #define DUK_HSTRING_GET_BYTELEN(x) ((x)->blen) #define DUK_HSTRING_SET_BYTELEN(x, v) \ do { \ (x)->blen = (v); \ } while (0) #define DUK_HSTRING_GET_CHARLEN(x) duk_hstring_get_charlen((x)) #define DUK_HSTRING_SET_CHARLEN(x, v) \ do { \ (x)->clen = (v); \ } while (0) #endif #if defined(DUK_USE_HSTRING_EXTDATA) #define DUK_HSTRING_GET_EXTDATA(x) ((x)->extdata) #define DUK_HSTRING_GET_DATA(x) \ (DUK_HSTRING_HAS_EXTDATA((x)) ? DUK_HSTRING_GET_EXTDATA((const duk_hstring_external *) (x)) : \ ((const duk_uint8_t *) ((x) + 1))) #else #define DUK_HSTRING_GET_DATA(x) ((const duk_uint8_t *) ((x) + 1)) #endif #define DUK_HSTRING_GET_DATA_END(x) (DUK_HSTRING_GET_DATA((x)) + (x)->blen) /* Marker value; in E5 2^32-1 is not a valid array index (2^32-2 is highest * valid). */ #define DUK_HSTRING_NO_ARRAY_INDEX (0xffffffffUL) #if defined(DUK_USE_HSTRING_ARRIDX) #define DUK_HSTRING_GET_ARRIDX_FAST(h) ((h)->arridx) #define DUK_HSTRING_GET_ARRIDX_SLOW(h) ((h)->arridx) #else /* Get array index related to string (or return DUK_HSTRING_NO_ARRAY_INDEX); * avoids helper call if string has no array index value. */ #define DUK_HSTRING_GET_ARRIDX_FAST(h) \ (DUK_HSTRING_HAS_ARRIDX((h)) ? duk_js_to_arrayindex_hstring_fast_known((h)) : DUK_HSTRING_NO_ARRAY_INDEX) /* Slower but more compact variant. */ #define DUK_HSTRING_GET_ARRIDX_SLOW(h) (duk_js_to_arrayindex_hstring_fast((h))) #endif /* XXX: these actually fit into duk_hstring */ #define DUK_SYMBOL_TYPE_HIDDEN 0 #define DUK_SYMBOL_TYPE_GLOBAL 1 #define DUK_SYMBOL_TYPE_LOCAL 2 #define DUK_SYMBOL_TYPE_WELLKNOWN 3 /* Assertion for duk_hstring validity. */ #if defined(DUK_USE_ASSERTIONS) DUK_INTERNAL_DECL void duk_hstring_assert_valid(duk_hstring *h); #define DUK_HSTRING_ASSERT_VALID(h) \ do { \ duk_hstring_assert_valid((h)); \ } while (0) #else #define DUK_HSTRING_ASSERT_VALID(h) \ do { \ } while (0) #endif /* * Misc */ struct duk_hstring { /* Smaller heaphdr than for other objects, because strings are held * in string intern table which requires no link pointers. Much of * the 32-bit flags field is unused by flags, so we can stuff a 16-bit * field in there. */ duk_heaphdr_string hdr; /* String hash. */ #if defined(DUK_USE_STRHASH16) /* If 16-bit hash is in use, stuff it into duk_heaphdr_string flags. */ #else duk_uint32_t hash; #endif /* Precomputed array index (or DUK_HSTRING_NO_ARRAY_INDEX). */ #if defined(DUK_USE_HSTRING_ARRIDX) duk_uarridx_t arridx; #endif /* Length in bytes (not counting NUL term). */ #if defined(DUK_USE_STRLEN16) /* placed in duk_heaphdr_string */ #else duk_uint32_t blen; #endif /* Length in codepoints (must be E5 compatible). */ #if defined(DUK_USE_STRLEN16) #if defined(DUK_USE_HSTRING_CLEN) duk_uint16_t clen16; #else /* computed live */ #endif #else duk_uint32_t clen; #endif /* * String data of 'blen+1' bytes follows (+1 for NUL termination * convenience for C API). No alignment needs to be guaranteed * for strings, but fields above should guarantee alignment-by-4 * (but not alignment-by-8). */ }; /* The external string struct is defined even when the feature is inactive. */ struct duk_hstring_external { duk_hstring str; /* * For an external string, the NUL-terminated string data is stored * externally. The user must guarantee that data behind this pointer * doesn't change while it's used. */ const duk_uint8_t *extdata; }; /* * Prototypes */ DUK_INTERNAL_DECL duk_ucodepoint_t duk_hstring_char_code_at_raw(duk_hthread *thr, duk_hstring *h, duk_uint_t pos, duk_bool_t surrogate_aware); DUK_INTERNAL_DECL duk_bool_t duk_hstring_equals_ascii_cstring(duk_hstring *h, const char *cstr); DUK_INTERNAL_DECL duk_size_t duk_hstring_get_charlen(duk_hstring *h); #if !defined(DUK_USE_HSTRING_LAZY_CLEN) DUK_INTERNAL_DECL void duk_hstring_init_charlen(duk_hstring *h); #endif #endif /* DUK_HSTRING_H_INCLUDED */ /* #include duk_hobject.h */ #line 1 "duk_hobject.h" /* * Heap object representation. * * Heap objects are used for ECMAScript objects, arrays, and functions, * but also for internal control like declarative and object environment * records. Compiled functions, native functions, and threads are also * objects but with an extended C struct. * * Objects provide the required ECMAScript semantics and exotic behaviors * especially for property access. * * Properties are stored in three conceptual parts: * * 1. A linear 'entry part' contains ordered key-value-attributes triples * and is the main method of string properties. * * 2. An optional linear 'array part' is used for array objects to store a * (dense) range of [0,N[ array indexed entries with default attributes * (writable, enumerable, configurable). If the array part would become * sparse or non-default attributes are required, the array part is * abandoned and moved to the 'entry part'. * * 3. An optional 'hash part' is used to optimize lookups of the entry * part; it is used only for objects with sufficiently many properties * and can be abandoned without loss of information. * * These three conceptual parts are stored in a single memory allocated area. * This minimizes memory allocation overhead but also means that all three * parts are resized together, and makes property access a bit complicated. */ #if !defined(DUK_HOBJECT_H_INCLUDED) #define DUK_HOBJECT_H_INCLUDED /* Object flags. Make sure this stays in sync with debugger object * inspection code. */ /* XXX: some flags are object subtype specific (e.g. common to all function * subtypes, duk_harray, etc) and could be reused for different subtypes. */ #define DUK_HOBJECT_FLAG_EXTENSIBLE DUK_HEAPHDR_USER_FLAG(0) /* object is extensible */ #define DUK_HOBJECT_FLAG_CONSTRUCTABLE DUK_HEAPHDR_USER_FLAG(1) /* object is constructable */ #define DUK_HOBJECT_FLAG_CALLABLE DUK_HEAPHDR_USER_FLAG(2) /* object is callable */ #define DUK_HOBJECT_FLAG_BOUNDFUNC DUK_HEAPHDR_USER_FLAG(3) /* object established using Function.prototype.bind() */ #define DUK_HOBJECT_FLAG_COMPFUNC DUK_HEAPHDR_USER_FLAG(4) /* object is a compiled function (duk_hcompfunc) */ #define DUK_HOBJECT_FLAG_NATFUNC DUK_HEAPHDR_USER_FLAG(5) /* object is a native function (duk_hnatfunc) */ #define DUK_HOBJECT_FLAG_BUFOBJ DUK_HEAPHDR_USER_FLAG(6) /* object is a buffer object (duk_hbufobj) (always exotic) */ #define DUK_HOBJECT_FLAG_FASTREFS \ DUK_HEAPHDR_USER_FLAG(7) /* object has no fields needing DECREF/marking beyond base duk_hobject header */ #define DUK_HOBJECT_FLAG_ARRAY_PART DUK_HEAPHDR_USER_FLAG(8) /* object has an array part (a_size may still be 0) */ #define DUK_HOBJECT_FLAG_STRICT DUK_HEAPHDR_USER_FLAG(9) /* function: function object is strict */ #define DUK_HOBJECT_FLAG_NOTAIL DUK_HEAPHDR_USER_FLAG(10) /* function: function must not be tail called */ #define DUK_HOBJECT_FLAG_NEWENV DUK_HEAPHDR_USER_FLAG(11) /* function: create new environment when called (see duk_hcompfunc) */ #define DUK_HOBJECT_FLAG_NAMEBINDING \ DUK_HEAPHDR_USER_FLAG( \ 12) /* function: create binding for func name (function templates only, used for named function expressions) */ #define DUK_HOBJECT_FLAG_CREATEARGS DUK_HEAPHDR_USER_FLAG(13) /* function: create an arguments object on function call */ #define DUK_HOBJECT_FLAG_HAVE_FINALIZER DUK_HEAPHDR_USER_FLAG(14) /* object has a callable (own) finalizer property */ #define DUK_HOBJECT_FLAG_EXOTIC_ARRAY DUK_HEAPHDR_USER_FLAG(15) /* 'Array' object, array length and index exotic behavior */ #define DUK_HOBJECT_FLAG_EXOTIC_STRINGOBJ DUK_HEAPHDR_USER_FLAG(16) /* 'String' object, array index exotic behavior */ #define DUK_HOBJECT_FLAG_EXOTIC_ARGUMENTS \ DUK_HEAPHDR_USER_FLAG(17) /* 'Arguments' object and has arguments exotic behavior (non-strict callee) */ #define DUK_HOBJECT_FLAG_EXOTIC_PROXYOBJ DUK_HEAPHDR_USER_FLAG(18) /* 'Proxy' object */ #define DUK_HOBJECT_FLAG_SPECIAL_CALL DUK_HEAPHDR_USER_FLAG(19) /* special casing in call behavior, for .call(), .apply(), etc. */ #define DUK_HOBJECT_FLAG_CLASS_BASE DUK_HEAPHDR_USER_FLAG_NUMBER(20) #define DUK_HOBJECT_FLAG_CLASS_BITS 5 #define DUK_HOBJECT_GET_CLASS_NUMBER(h) \ DUK_HEAPHDR_GET_FLAG_RANGE(&(h)->hdr, DUK_HOBJECT_FLAG_CLASS_BASE, DUK_HOBJECT_FLAG_CLASS_BITS) #define DUK_HOBJECT_SET_CLASS_NUMBER(h, v) \ DUK_HEAPHDR_SET_FLAG_RANGE(&(h)->hdr, DUK_HOBJECT_FLAG_CLASS_BASE, DUK_HOBJECT_FLAG_CLASS_BITS, (v)) #define DUK_HOBJECT_GET_CLASS_MASK(h) \ (1UL << DUK_HEAPHDR_GET_FLAG_RANGE(&(h)->hdr, DUK_HOBJECT_FLAG_CLASS_BASE, DUK_HOBJECT_FLAG_CLASS_BITS)) /* Macro for creating flag initializer from a class number. * Unsigned type cast is needed to avoid warnings about coercing * a signed integer to an unsigned one; the largest class values * have the highest bit (bit 31) set which causes this. */ #define DUK_HOBJECT_CLASS_AS_FLAGS(v) (((duk_uint_t) (v)) << DUK_HOBJECT_FLAG_CLASS_BASE) /* E5 Section 8.6.2 + custom classes */ #define DUK_HOBJECT_CLASS_NONE 0 #define DUK_HOBJECT_CLASS_OBJECT 1 #define DUK_HOBJECT_CLASS_ARRAY 2 #define DUK_HOBJECT_CLASS_FUNCTION 3 #define DUK_HOBJECT_CLASS_ARGUMENTS 4 #define DUK_HOBJECT_CLASS_BOOLEAN 5 #define DUK_HOBJECT_CLASS_DATE 6 #define DUK_HOBJECT_CLASS_ERROR 7 #define DUK_HOBJECT_CLASS_JSON 8 #define DUK_HOBJECT_CLASS_MATH 9 #define DUK_HOBJECT_CLASS_NUMBER 10 #define DUK_HOBJECT_CLASS_REGEXP 11 #define DUK_HOBJECT_CLASS_STRING 12 #define DUK_HOBJECT_CLASS_GLOBAL 13 #define DUK_HOBJECT_CLASS_SYMBOL 14 #define DUK_HOBJECT_CLASS_OBJENV 15 /* custom */ #define DUK_HOBJECT_CLASS_DECENV 16 /* custom */ #define DUK_HOBJECT_CLASS_POINTER 17 /* custom */ #define DUK_HOBJECT_CLASS_THREAD 18 /* custom; implies DUK_HOBJECT_IS_THREAD */ #define DUK_HOBJECT_CLASS_BUFOBJ_MIN 19 #define DUK_HOBJECT_CLASS_ARRAYBUFFER 19 /* implies DUK_HOBJECT_IS_BUFOBJ */ #define DUK_HOBJECT_CLASS_DATAVIEW 20 #define DUK_HOBJECT_CLASS_INT8ARRAY 21 #define DUK_HOBJECT_CLASS_UINT8ARRAY 22 #define DUK_HOBJECT_CLASS_UINT8CLAMPEDARRAY 23 #define DUK_HOBJECT_CLASS_INT16ARRAY 24 #define DUK_HOBJECT_CLASS_UINT16ARRAY 25 #define DUK_HOBJECT_CLASS_INT32ARRAY 26 #define DUK_HOBJECT_CLASS_UINT32ARRAY 27 #define DUK_HOBJECT_CLASS_FLOAT32ARRAY 28 #define DUK_HOBJECT_CLASS_FLOAT64ARRAY 29 #define DUK_HOBJECT_CLASS_BUFOBJ_MAX 29 #define DUK_HOBJECT_CLASS_MAX 29 /* Class masks. */ #define DUK_HOBJECT_CMASK_ALL ((1UL << (DUK_HOBJECT_CLASS_MAX + 1)) - 1UL) #define DUK_HOBJECT_CMASK_NONE (1UL << DUK_HOBJECT_CLASS_NONE) #define DUK_HOBJECT_CMASK_ARGUMENTS (1UL << DUK_HOBJECT_CLASS_ARGUMENTS) #define DUK_HOBJECT_CMASK_ARRAY (1UL << DUK_HOBJECT_CLASS_ARRAY) #define DUK_HOBJECT_CMASK_BOOLEAN (1UL << DUK_HOBJECT_CLASS_BOOLEAN) #define DUK_HOBJECT_CMASK_DATE (1UL << DUK_HOBJECT_CLASS_DATE) #define DUK_HOBJECT_CMASK_ERROR (1UL << DUK_HOBJECT_CLASS_ERROR) #define DUK_HOBJECT_CMASK_FUNCTION (1UL << DUK_HOBJECT_CLASS_FUNCTION) #define DUK_HOBJECT_CMASK_JSON (1UL << DUK_HOBJECT_CLASS_JSON) #define DUK_HOBJECT_CMASK_MATH (1UL << DUK_HOBJECT_CLASS_MATH) #define DUK_HOBJECT_CMASK_NUMBER (1UL << DUK_HOBJECT_CLASS_NUMBER) #define DUK_HOBJECT_CMASK_OBJECT (1UL << DUK_HOBJECT_CLASS_OBJECT) #define DUK_HOBJECT_CMASK_REGEXP (1UL << DUK_HOBJECT_CLASS_REGEXP) #define DUK_HOBJECT_CMASK_STRING (1UL << DUK_HOBJECT_CLASS_STRING) #define DUK_HOBJECT_CMASK_GLOBAL (1UL << DUK_HOBJECT_CLASS_GLOBAL) #define DUK_HOBJECT_CMASK_SYMBOL (1UL << DUK_HOBJECT_CLASS_SYMBOL) #define DUK_HOBJECT_CMASK_OBJENV (1UL << DUK_HOBJECT_CLASS_OBJENV) #define DUK_HOBJECT_CMASK_DECENV (1UL << DUK_HOBJECT_CLASS_DECENV) #define DUK_HOBJECT_CMASK_POINTER (1UL << DUK_HOBJECT_CLASS_POINTER) #define DUK_HOBJECT_CMASK_ARRAYBUFFER (1UL << DUK_HOBJECT_CLASS_ARRAYBUFFER) #define DUK_HOBJECT_CMASK_DATAVIEW (1UL << DUK_HOBJECT_CLASS_DATAVIEW) #define DUK_HOBJECT_CMASK_INT8ARRAY (1UL << DUK_HOBJECT_CLASS_INT8ARRAY) #define DUK_HOBJECT_CMASK_UINT8ARRAY (1UL << DUK_HOBJECT_CLASS_UINT8ARRAY) #define DUK_HOBJECT_CMASK_UINT8CLAMPEDARRAY (1UL << DUK_HOBJECT_CLASS_UINT8CLAMPEDARRAY) #define DUK_HOBJECT_CMASK_INT16ARRAY (1UL << DUK_HOBJECT_CLASS_INT16ARRAY) #define DUK_HOBJECT_CMASK_UINT16ARRAY (1UL << DUK_HOBJECT_CLASS_UINT16ARRAY) #define DUK_HOBJECT_CMASK_INT32ARRAY (1UL << DUK_HOBJECT_CLASS_INT32ARRAY) #define DUK_HOBJECT_CMASK_UINT32ARRAY (1UL << DUK_HOBJECT_CLASS_UINT32ARRAY) #define DUK_HOBJECT_CMASK_FLOAT32ARRAY (1UL << DUK_HOBJECT_CLASS_FLOAT32ARRAY) #define DUK_HOBJECT_CMASK_FLOAT64ARRAY (1UL << DUK_HOBJECT_CLASS_FLOAT64ARRAY) #define DUK_HOBJECT_CMASK_ALL_BUFOBJS \ (DUK_HOBJECT_CMASK_ARRAYBUFFER | DUK_HOBJECT_CMASK_DATAVIEW | DUK_HOBJECT_CMASK_INT8ARRAY | DUK_HOBJECT_CMASK_UINT8ARRAY | \ DUK_HOBJECT_CMASK_UINT8CLAMPEDARRAY | DUK_HOBJECT_CMASK_INT16ARRAY | DUK_HOBJECT_CMASK_UINT16ARRAY | \ DUK_HOBJECT_CMASK_INT32ARRAY | DUK_HOBJECT_CMASK_UINT32ARRAY | DUK_HOBJECT_CMASK_FLOAT32ARRAY | \ DUK_HOBJECT_CMASK_FLOAT64ARRAY) #define DUK_HOBJECT_IS_OBJENV(h) (DUK_HOBJECT_GET_CLASS_NUMBER((h)) == DUK_HOBJECT_CLASS_OBJENV) #define DUK_HOBJECT_IS_DECENV(h) (DUK_HOBJECT_GET_CLASS_NUMBER((h)) == DUK_HOBJECT_CLASS_DECENV) #define DUK_HOBJECT_IS_ENV(h) (DUK_HOBJECT_IS_OBJENV((h)) || DUK_HOBJECT_IS_DECENV((h))) #define DUK_HOBJECT_IS_ARRAY(h) DUK_HOBJECT_HAS_EXOTIC_ARRAY((h)) /* Rely on class Array <=> exotic Array */ #define DUK_HOBJECT_IS_BOUNDFUNC(h) DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_BOUNDFUNC) #define DUK_HOBJECT_IS_COMPFUNC(h) DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_COMPFUNC) #define DUK_HOBJECT_IS_NATFUNC(h) DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_NATFUNC) #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) #define DUK_HOBJECT_IS_BUFOBJ(h) DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_BUFOBJ) #else #define DUK_HOBJECT_IS_BUFOBJ(h) 0 #endif #define DUK_HOBJECT_IS_THREAD(h) (DUK_HOBJECT_GET_CLASS_NUMBER((h)) == DUK_HOBJECT_CLASS_THREAD) #if defined(DUK_USE_ES6_PROXY) #define DUK_HOBJECT_IS_PROXY(h) DUK_HOBJECT_HAS_EXOTIC_PROXYOBJ((h)) #else #define DUK_HOBJECT_IS_PROXY(h) 0 #endif #define DUK_HOBJECT_IS_NONBOUND_FUNCTION(h) \ DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_COMPFUNC | DUK_HOBJECT_FLAG_NATFUNC) #define DUK_HOBJECT_IS_FUNCTION(h) \ DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_BOUNDFUNC | DUK_HOBJECT_FLAG_COMPFUNC | DUK_HOBJECT_FLAG_NATFUNC) #define DUK_HOBJECT_IS_CALLABLE(h) DUK_HOBJECT_HAS_CALLABLE((h)) /* Object has any exotic behavior(s). */ #define DUK_HOBJECT_EXOTIC_BEHAVIOR_FLAGS \ (DUK_HOBJECT_FLAG_EXOTIC_ARRAY | DUK_HOBJECT_FLAG_EXOTIC_ARGUMENTS | DUK_HOBJECT_FLAG_EXOTIC_STRINGOBJ | \ DUK_HOBJECT_FLAG_BUFOBJ | DUK_HOBJECT_FLAG_EXOTIC_PROXYOBJ) #define DUK_HOBJECT_HAS_EXOTIC_BEHAVIOR(h) DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_EXOTIC_BEHAVIOR_FLAGS) /* Object has any virtual properties (not counting Proxy behavior). */ #define DUK_HOBJECT_VIRTUAL_PROPERTY_FLAGS \ (DUK_HOBJECT_FLAG_EXOTIC_ARRAY | DUK_HOBJECT_FLAG_EXOTIC_STRINGOBJ | DUK_HOBJECT_FLAG_BUFOBJ) #define DUK_HOBJECT_HAS_VIRTUAL_PROPERTIES(h) DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_VIRTUAL_PROPERTY_FLAGS) #define DUK_HOBJECT_HAS_EXTENSIBLE(h) DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXTENSIBLE) #define DUK_HOBJECT_HAS_CONSTRUCTABLE(h) DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_CONSTRUCTABLE) #define DUK_HOBJECT_HAS_CALLABLE(h) DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_CALLABLE) #define DUK_HOBJECT_HAS_BOUNDFUNC(h) DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_BOUNDFUNC) #define DUK_HOBJECT_HAS_COMPFUNC(h) DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_COMPFUNC) #define DUK_HOBJECT_HAS_NATFUNC(h) DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_NATFUNC) #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) #define DUK_HOBJECT_HAS_BUFOBJ(h) DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_BUFOBJ) #else #define DUK_HOBJECT_HAS_BUFOBJ(h) 0 #endif #define DUK_HOBJECT_HAS_FASTREFS(h) DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_FASTREFS) #define DUK_HOBJECT_HAS_ARRAY_PART(h) DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_ARRAY_PART) #define DUK_HOBJECT_HAS_STRICT(h) DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_STRICT) #define DUK_HOBJECT_HAS_NOTAIL(h) DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_NOTAIL) #define DUK_HOBJECT_HAS_NEWENV(h) DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_NEWENV) #define DUK_HOBJECT_HAS_NAMEBINDING(h) DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_NAMEBINDING) #define DUK_HOBJECT_HAS_CREATEARGS(h) DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_CREATEARGS) #define DUK_HOBJECT_HAS_HAVE_FINALIZER(h) DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_HAVE_FINALIZER) #define DUK_HOBJECT_HAS_EXOTIC_ARRAY(h) DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXOTIC_ARRAY) #define DUK_HOBJECT_HAS_EXOTIC_STRINGOBJ(h) DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXOTIC_STRINGOBJ) #define DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(h) DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXOTIC_ARGUMENTS) #if defined(DUK_USE_ES6_PROXY) #define DUK_HOBJECT_HAS_EXOTIC_PROXYOBJ(h) DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXOTIC_PROXYOBJ) #else #define DUK_HOBJECT_HAS_EXOTIC_PROXYOBJ(h) 0 #endif #define DUK_HOBJECT_HAS_SPECIAL_CALL(h) DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_SPECIAL_CALL) #define DUK_HOBJECT_SET_EXTENSIBLE(h) DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXTENSIBLE) #define DUK_HOBJECT_SET_CONSTRUCTABLE(h) DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_CONSTRUCTABLE) #define DUK_HOBJECT_SET_CALLABLE(h) DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_CALLABLE) #define DUK_HOBJECT_SET_BOUNDFUNC(h) DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_BOUNDFUNC) #define DUK_HOBJECT_SET_COMPFUNC(h) DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_COMPFUNC) #define DUK_HOBJECT_SET_NATFUNC(h) DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_NATFUNC) #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) #define DUK_HOBJECT_SET_BUFOBJ(h) DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_BUFOBJ) #endif #define DUK_HOBJECT_SET_FASTREFS(h) DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_FASTREFS) #define DUK_HOBJECT_SET_ARRAY_PART(h) DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_ARRAY_PART) #define DUK_HOBJECT_SET_STRICT(h) DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_STRICT) #define DUK_HOBJECT_SET_NOTAIL(h) DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_NOTAIL) #define DUK_HOBJECT_SET_NEWENV(h) DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_NEWENV) #define DUK_HOBJECT_SET_NAMEBINDING(h) DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_NAMEBINDING) #define DUK_HOBJECT_SET_CREATEARGS(h) DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_CREATEARGS) #define DUK_HOBJECT_SET_HAVE_FINALIZER(h) DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_HAVE_FINALIZER) #define DUK_HOBJECT_SET_EXOTIC_ARRAY(h) DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXOTIC_ARRAY) #define DUK_HOBJECT_SET_EXOTIC_STRINGOBJ(h) DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXOTIC_STRINGOBJ) #define DUK_HOBJECT_SET_EXOTIC_ARGUMENTS(h) DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXOTIC_ARGUMENTS) #if defined(DUK_USE_ES6_PROXY) #define DUK_HOBJECT_SET_EXOTIC_PROXYOBJ(h) DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXOTIC_PROXYOBJ) #endif #define DUK_HOBJECT_SET_SPECIAL_CALL(h) DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_SPECIAL_CALL) #define DUK_HOBJECT_CLEAR_EXTENSIBLE(h) DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXTENSIBLE) #define DUK_HOBJECT_CLEAR_CONSTRUCTABLE(h) DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_CONSTRUCTABLE) #define DUK_HOBJECT_CLEAR_CALLABLE(h) DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_CALLABLE) #define DUK_HOBJECT_CLEAR_BOUNDFUNC(h) DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_BOUNDFUNC) #define DUK_HOBJECT_CLEAR_COMPFUNC(h) DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_COMPFUNC) #define DUK_HOBJECT_CLEAR_NATFUNC(h) DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_NATFUNC) #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) #define DUK_HOBJECT_CLEAR_BUFOBJ(h) DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_BUFOBJ) #endif #define DUK_HOBJECT_CLEAR_FASTREFS(h) DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_FASTREFS) #define DUK_HOBJECT_CLEAR_ARRAY_PART(h) DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_ARRAY_PART) #define DUK_HOBJECT_CLEAR_STRICT(h) DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_STRICT) #define DUK_HOBJECT_CLEAR_NOTAIL(h) DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_NOTAIL) #define DUK_HOBJECT_CLEAR_NEWENV(h) DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_NEWENV) #define DUK_HOBJECT_CLEAR_NAMEBINDING(h) DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_NAMEBINDING) #define DUK_HOBJECT_CLEAR_CREATEARGS(h) DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_CREATEARGS) #define DUK_HOBJECT_CLEAR_HAVE_FINALIZER(h) DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_HAVE_FINALIZER) #define DUK_HOBJECT_CLEAR_EXOTIC_ARRAY(h) DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXOTIC_ARRAY) #define DUK_HOBJECT_CLEAR_EXOTIC_STRINGOBJ(h) DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXOTIC_STRINGOBJ) #define DUK_HOBJECT_CLEAR_EXOTIC_ARGUMENTS(h) DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXOTIC_ARGUMENTS) #if defined(DUK_USE_ES6_PROXY) #define DUK_HOBJECT_CLEAR_EXOTIC_PROXYOBJ(h) DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXOTIC_PROXYOBJ) #endif #define DUK_HOBJECT_CLEAR_SPECIAL_CALL(h) DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_SPECIAL_CALL) /* Object can/cannot use FASTREFS, i.e. has no strong reference fields beyond * duk_hobject base header. This is used just for asserts so doesn't need to * be optimized. */ #define DUK_HOBJECT_PROHIBITS_FASTREFS(h) \ (DUK_HOBJECT_IS_COMPFUNC((h)) || DUK_HOBJECT_IS_DECENV((h)) || DUK_HOBJECT_IS_OBJENV((h)) || DUK_HOBJECT_IS_BUFOBJ((h)) || \ DUK_HOBJECT_IS_THREAD((h)) || DUK_HOBJECT_IS_PROXY((h)) || DUK_HOBJECT_IS_BOUNDFUNC((h))) #define DUK_HOBJECT_ALLOWS_FASTREFS(h) (!DUK_HOBJECT_PROHIBITS_FASTREFS((h))) /* Flags used for property attributes in duk_propdesc and packed flags. * Must fit into 8 bits. */ #define DUK_PROPDESC_FLAG_WRITABLE (1U << 0) /* E5 Section 8.6.1 */ #define DUK_PROPDESC_FLAG_ENUMERABLE (1U << 1) /* E5 Section 8.6.1 */ #define DUK_PROPDESC_FLAG_CONFIGURABLE (1U << 2) /* E5 Section 8.6.1 */ #define DUK_PROPDESC_FLAG_ACCESSOR (1U << 3) /* accessor */ #define DUK_PROPDESC_FLAG_VIRTUAL \ (1U << 4) /* property is virtual: used in duk_propdesc, never stored \ * (used by e.g. buffer virtual properties) \ */ #define DUK_PROPDESC_FLAGS_MASK \ (DUK_PROPDESC_FLAG_WRITABLE | DUK_PROPDESC_FLAG_ENUMERABLE | DUK_PROPDESC_FLAG_CONFIGURABLE | DUK_PROPDESC_FLAG_ACCESSOR) /* Additional flags which are passed in the same flags argument as property * flags but are not stored in object properties. */ #define DUK_PROPDESC_FLAG_NO_OVERWRITE (1U << 4) /* internal define property: skip write silently if exists */ /* Convenience defines for property attributes. */ #define DUK_PROPDESC_FLAGS_NONE 0 #define DUK_PROPDESC_FLAGS_W (DUK_PROPDESC_FLAG_WRITABLE) #define DUK_PROPDESC_FLAGS_E (DUK_PROPDESC_FLAG_ENUMERABLE) #define DUK_PROPDESC_FLAGS_C (DUK_PROPDESC_FLAG_CONFIGURABLE) #define DUK_PROPDESC_FLAGS_WE (DUK_PROPDESC_FLAG_WRITABLE | DUK_PROPDESC_FLAG_ENUMERABLE) #define DUK_PROPDESC_FLAGS_WC (DUK_PROPDESC_FLAG_WRITABLE | DUK_PROPDESC_FLAG_CONFIGURABLE) #define DUK_PROPDESC_FLAGS_EC (DUK_PROPDESC_FLAG_ENUMERABLE | DUK_PROPDESC_FLAG_CONFIGURABLE) #define DUK_PROPDESC_FLAGS_WEC (DUK_PROPDESC_FLAG_WRITABLE | DUK_PROPDESC_FLAG_ENUMERABLE | DUK_PROPDESC_FLAG_CONFIGURABLE) /* Flags for duk_hobject_get_own_propdesc() and variants. */ #define DUK_GETDESC_FLAG_PUSH_VALUE (1U << 0) /* push value to stack */ #define DUK_GETDESC_FLAG_IGNORE_PROTOLOOP (1U << 1) /* don't throw for prototype loop */ /* * Macro for object validity check * * Assert for currently guaranteed relations between flags, for instance. */ #if defined(DUK_USE_ASSERTIONS) DUK_INTERNAL_DECL void duk_hobject_assert_valid(duk_hobject *h); #define DUK_HOBJECT_ASSERT_VALID(h) \ do { \ duk_hobject_assert_valid((h)); \ } while (0) #else #define DUK_HOBJECT_ASSERT_VALID(h) \ do { \ } while (0) #endif /* * Macros to access the 'props' allocation. */ #if defined(DUK_USE_HEAPPTR16) #define DUK_HOBJECT_GET_PROPS(heap, h) ((duk_uint8_t *) DUK_USE_HEAPPTR_DEC16((heap)->heap_udata, ((duk_heaphdr *) (h))->h_extra16)) #define DUK_HOBJECT_SET_PROPS(heap, h, x) \ do { \ ((duk_heaphdr *) (h))->h_extra16 = DUK_USE_HEAPPTR_ENC16((heap)->heap_udata, (void *) (x)); \ } while (0) #else #define DUK_HOBJECT_GET_PROPS(heap, h) ((h)->props) #define DUK_HOBJECT_SET_PROPS(heap, h, x) \ do { \ (h)->props = (duk_uint8_t *) (x); \ } while (0) #endif #if defined(DUK_USE_HOBJECT_LAYOUT_1) /* LAYOUT 1 */ #define DUK_HOBJECT_E_GET_KEY_BASE(heap, h) ((duk_hstring **) (void *) (DUK_HOBJECT_GET_PROPS((heap), (h)))) #define DUK_HOBJECT_E_GET_VALUE_BASE(heap, h) \ ((duk_propvalue *) (void *) (DUK_HOBJECT_GET_PROPS((heap), (h)) + DUK_HOBJECT_GET_ESIZE((h)) * sizeof(duk_hstring *))) #define DUK_HOBJECT_E_GET_FLAGS_BASE(heap, h) \ ((duk_uint8_t *) (void *) (DUK_HOBJECT_GET_PROPS((heap), (h)) + \ DUK_HOBJECT_GET_ESIZE((h)) * (sizeof(duk_hstring *) + sizeof(duk_propvalue)))) #define DUK_HOBJECT_A_GET_BASE(heap, h) \ ((duk_tval *) (void *) (DUK_HOBJECT_GET_PROPS((heap), (h)) + \ DUK_HOBJECT_GET_ESIZE((h)) * \ (sizeof(duk_hstring *) + sizeof(duk_propvalue) + sizeof(duk_uint8_t)))) #define DUK_HOBJECT_H_GET_BASE(heap, h) \ ((duk_uint32_t *) (void *) (DUK_HOBJECT_GET_PROPS((heap), (h)) + \ DUK_HOBJECT_GET_ESIZE((h)) * \ (sizeof(duk_hstring *) + sizeof(duk_propvalue) + sizeof(duk_uint8_t)) + \ DUK_HOBJECT_GET_ASIZE((h)) * sizeof(duk_tval))) #define DUK_HOBJECT_P_COMPUTE_SIZE(n_ent, n_arr, n_hash) \ ((n_ent) * (sizeof(duk_hstring *) + sizeof(duk_propvalue) + sizeof(duk_uint8_t)) + (n_arr) * sizeof(duk_tval) + \ (n_hash) * sizeof(duk_uint32_t)) #define DUK_HOBJECT_P_SET_REALLOC_PTRS(p_base, set_e_k, set_e_pv, set_e_f, set_a, set_h, n_ent, n_arr, n_hash) \ do { \ (set_e_k) = (duk_hstring **) (void *) (p_base); \ (set_e_pv) = (duk_propvalue *) (void *) ((set_e_k) + (n_ent)); \ (set_e_f) = (duk_uint8_t *) (void *) ((set_e_pv) + (n_ent)); \ (set_a) = (duk_tval *) (void *) ((set_e_f) + (n_ent)); \ (set_h) = (duk_uint32_t *) (void *) ((set_a) + (n_arr)); \ } while (0) #elif defined(DUK_USE_HOBJECT_LAYOUT_2) /* LAYOUT 2 */ #if (DUK_USE_ALIGN_BY == 4) #define DUK_HOBJECT_E_FLAG_PADDING(e_sz) ((4 - (e_sz)) & 0x03) #elif (DUK_USE_ALIGN_BY == 8) #define DUK_HOBJECT_E_FLAG_PADDING(e_sz) ((8 - (e_sz)) & 0x07) #elif (DUK_USE_ALIGN_BY == 1) #define DUK_HOBJECT_E_FLAG_PADDING(e_sz) 0 #else #error invalid DUK_USE_ALIGN_BY #endif #define DUK_HOBJECT_E_GET_KEY_BASE(heap, h) \ ((duk_hstring **) (void *) (DUK_HOBJECT_GET_PROPS((heap), (h)) + DUK_HOBJECT_GET_ESIZE((h)) * sizeof(duk_propvalue))) #define DUK_HOBJECT_E_GET_VALUE_BASE(heap, h) ((duk_propvalue *) (void *) (DUK_HOBJECT_GET_PROPS((heap), (h)))) #define DUK_HOBJECT_E_GET_FLAGS_BASE(heap, h) \ ((duk_uint8_t *) (void *) (DUK_HOBJECT_GET_PROPS((heap), (h)) + \ DUK_HOBJECT_GET_ESIZE((h)) * (sizeof(duk_hstring *) + sizeof(duk_propvalue)))) #define DUK_HOBJECT_A_GET_BASE(heap, h) \ ((duk_tval *) (void *) (DUK_HOBJECT_GET_PROPS((heap), (h)) + \ DUK_HOBJECT_GET_ESIZE((h)) * \ (sizeof(duk_hstring *) + sizeof(duk_propvalue) + sizeof(duk_uint8_t)) + \ DUK_HOBJECT_E_FLAG_PADDING(DUK_HOBJECT_GET_ESIZE((h))))) #define DUK_HOBJECT_H_GET_BASE(heap, h) \ ((duk_uint32_t *) (void *) (DUK_HOBJECT_GET_PROPS((heap), (h)) + \ DUK_HOBJECT_GET_ESIZE((h)) * \ (sizeof(duk_hstring *) + sizeof(duk_propvalue) + sizeof(duk_uint8_t)) + \ DUK_HOBJECT_E_FLAG_PADDING(DUK_HOBJECT_GET_ESIZE((h))) + \ DUK_HOBJECT_GET_ASIZE((h)) * sizeof(duk_tval))) #define DUK_HOBJECT_P_COMPUTE_SIZE(n_ent, n_arr, n_hash) \ ((n_ent) * (sizeof(duk_hstring *) + sizeof(duk_propvalue) + sizeof(duk_uint8_t)) + DUK_HOBJECT_E_FLAG_PADDING((n_ent)) + \ (n_arr) * sizeof(duk_tval) + (n_hash) * sizeof(duk_uint32_t)) #define DUK_HOBJECT_P_SET_REALLOC_PTRS(p_base, set_e_k, set_e_pv, set_e_f, set_a, set_h, n_ent, n_arr, n_hash) \ do { \ (set_e_pv) = (duk_propvalue *) (void *) (p_base); \ (set_e_k) = (duk_hstring **) (void *) ((set_e_pv) + (n_ent)); \ (set_e_f) = (duk_uint8_t *) (void *) ((set_e_k) + (n_ent)); \ (set_a) = (duk_tval *) (void *) (((duk_uint8_t *) (set_e_f)) + sizeof(duk_uint8_t) * (n_ent) + \ DUK_HOBJECT_E_FLAG_PADDING((n_ent))); \ (set_h) = (duk_uint32_t *) (void *) ((set_a) + (n_arr)); \ } while (0) #elif defined(DUK_USE_HOBJECT_LAYOUT_3) /* LAYOUT 3 */ #define DUK_HOBJECT_E_GET_KEY_BASE(heap, h) \ ((duk_hstring **) (void *) (DUK_HOBJECT_GET_PROPS((heap), (h)) + DUK_HOBJECT_GET_ESIZE((h)) * sizeof(duk_propvalue) + \ DUK_HOBJECT_GET_ASIZE((h)) * sizeof(duk_tval))) #define DUK_HOBJECT_E_GET_VALUE_BASE(heap, h) ((duk_propvalue *) (void *) (DUK_HOBJECT_GET_PROPS((heap), (h)))) #define DUK_HOBJECT_E_GET_FLAGS_BASE(heap, h) \ ((duk_uint8_t *) (void *) (DUK_HOBJECT_GET_PROPS((heap), (h)) + \ DUK_HOBJECT_GET_ESIZE((h)) * (sizeof(duk_propvalue) + sizeof(duk_hstring *)) + \ DUK_HOBJECT_GET_ASIZE((h)) * sizeof(duk_tval) + \ DUK_HOBJECT_GET_HSIZE((h)) * sizeof(duk_uint32_t))) #define DUK_HOBJECT_A_GET_BASE(heap, h) \ ((duk_tval *) (void *) (DUK_HOBJECT_GET_PROPS((heap), (h)) + DUK_HOBJECT_GET_ESIZE((h)) * sizeof(duk_propvalue))) #define DUK_HOBJECT_H_GET_BASE(heap, h) \ ((duk_uint32_t *) (void *) (DUK_HOBJECT_GET_PROPS((heap), (h)) + \ DUK_HOBJECT_GET_ESIZE((h)) * (sizeof(duk_propvalue) + sizeof(duk_hstring *)) + \ DUK_HOBJECT_GET_ASIZE((h)) * sizeof(duk_tval))) #define DUK_HOBJECT_P_COMPUTE_SIZE(n_ent, n_arr, n_hash) \ ((n_ent) * (sizeof(duk_propvalue) + sizeof(duk_hstring *) + sizeof(duk_uint8_t)) + (n_arr) * sizeof(duk_tval) + \ (n_hash) * sizeof(duk_uint32_t)) #define DUK_HOBJECT_P_SET_REALLOC_PTRS(p_base, set_e_k, set_e_pv, set_e_f, set_a, set_h, n_ent, n_arr, n_hash) \ do { \ (set_e_pv) = (duk_propvalue *) (void *) (p_base); \ (set_a) = (duk_tval *) (void *) ((set_e_pv) + (n_ent)); \ (set_e_k) = (duk_hstring **) (void *) ((set_a) + (n_arr)); \ (set_h) = (duk_uint32_t *) (void *) ((set_e_k) + (n_ent)); \ (set_e_f) = (duk_uint8_t *) (void *) ((set_h) + (n_hash)); \ } while (0) #else #error invalid hobject layout defines #endif /* hobject property layout */ #define DUK_HOBJECT_P_ALLOC_SIZE(h) \ DUK_HOBJECT_P_COMPUTE_SIZE(DUK_HOBJECT_GET_ESIZE((h)), DUK_HOBJECT_GET_ASIZE((h)), DUK_HOBJECT_GET_HSIZE((h))) #define DUK_HOBJECT_E_GET_KEY(heap, h, i) (DUK_HOBJECT_E_GET_KEY_BASE((heap), (h))[(i)]) #define DUK_HOBJECT_E_GET_KEY_PTR(heap, h, i) (&DUK_HOBJECT_E_GET_KEY_BASE((heap), (h))[(i)]) #define DUK_HOBJECT_E_GET_VALUE(heap, h, i) (DUK_HOBJECT_E_GET_VALUE_BASE((heap), (h))[(i)]) #define DUK_HOBJECT_E_GET_VALUE_PTR(heap, h, i) (&DUK_HOBJECT_E_GET_VALUE_BASE((heap), (h))[(i)]) #define DUK_HOBJECT_E_GET_VALUE_TVAL(heap, h, i) (DUK_HOBJECT_E_GET_VALUE((heap), (h), (i)).v) #define DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(heap, h, i) (&DUK_HOBJECT_E_GET_VALUE((heap), (h), (i)).v) #define DUK_HOBJECT_E_GET_VALUE_GETTER(heap, h, i) (DUK_HOBJECT_E_GET_VALUE((heap), (h), (i)).a.get) #define DUK_HOBJECT_E_GET_VALUE_GETTER_PTR(heap, h, i) (&DUK_HOBJECT_E_GET_VALUE((heap), (h), (i)).a.get) #define DUK_HOBJECT_E_GET_VALUE_SETTER(heap, h, i) (DUK_HOBJECT_E_GET_VALUE((heap), (h), (i)).a.set) #define DUK_HOBJECT_E_GET_VALUE_SETTER_PTR(heap, h, i) (&DUK_HOBJECT_E_GET_VALUE((heap), (h), (i)).a.set) #define DUK_HOBJECT_E_GET_FLAGS(heap, h, i) (DUK_HOBJECT_E_GET_FLAGS_BASE((heap), (h))[(i)]) #define DUK_HOBJECT_E_GET_FLAGS_PTR(heap, h, i) (&DUK_HOBJECT_E_GET_FLAGS_BASE((heap), (h))[(i)]) #define DUK_HOBJECT_A_GET_VALUE(heap, h, i) (DUK_HOBJECT_A_GET_BASE((heap), (h))[(i)]) #define DUK_HOBJECT_A_GET_VALUE_PTR(heap, h, i) (&DUK_HOBJECT_A_GET_BASE((heap), (h))[(i)]) #define DUK_HOBJECT_H_GET_INDEX(heap, h, i) (DUK_HOBJECT_H_GET_BASE((heap), (h))[(i)]) #define DUK_HOBJECT_H_GET_INDEX_PTR(heap, h, i) (&DUK_HOBJECT_H_GET_BASE((heap), (h))[(i)]) #define DUK_HOBJECT_E_SET_KEY(heap, h, i, k) \ do { \ DUK_HOBJECT_E_GET_KEY((heap), (h), (i)) = (k); \ } while (0) #define DUK_HOBJECT_E_SET_VALUE(heap, h, i, v) \ do { \ DUK_HOBJECT_E_GET_VALUE((heap), (h), (i)) = (v); \ } while (0) #define DUK_HOBJECT_E_SET_VALUE_TVAL(heap, h, i, v) \ do { \ DUK_HOBJECT_E_GET_VALUE((heap), (h), (i)).v = (v); \ } while (0) #define DUK_HOBJECT_E_SET_VALUE_GETTER(heap, h, i, v) \ do { \ DUK_HOBJECT_E_GET_VALUE((heap), (h), (i)).a.get = (v); \ } while (0) #define DUK_HOBJECT_E_SET_VALUE_SETTER(heap, h, i, v) \ do { \ DUK_HOBJECT_E_GET_VALUE((heap), (h), (i)).a.set = (v); \ } while (0) #define DUK_HOBJECT_E_SET_FLAGS(heap, h, i, f) \ do { \ DUK_HOBJECT_E_GET_FLAGS((heap), (h), (i)) = (duk_uint8_t) (f); \ } while (0) #define DUK_HOBJECT_A_SET_VALUE(heap, h, i, v) \ do { \ DUK_HOBJECT_A_GET_VALUE((heap), (h), (i)) = (v); \ } while (0) #define DUK_HOBJECT_A_SET_VALUE_TVAL(heap, h, i, v) DUK_HOBJECT_A_SET_VALUE((heap), (h), (i), (v)) /* alias for above */ #define DUK_HOBJECT_H_SET_INDEX(heap, h, i, v) \ do { \ DUK_HOBJECT_H_GET_INDEX((heap), (h), (i)) = (v); \ } while (0) #define DUK_HOBJECT_E_SET_FLAG_BITS(heap, h, i, mask) \ do { \ DUK_HOBJECT_E_GET_FLAGS_BASE((heap), (h))[(i)] |= (mask); \ } while (0) #define DUK_HOBJECT_E_CLEAR_FLAG_BITS(heap, h, i, mask) \ do { \ DUK_HOBJECT_E_GET_FLAGS_BASE((heap), (h))[(i)] &= ~(mask); \ } while (0) #define DUK_HOBJECT_E_SLOT_IS_WRITABLE(heap, h, i) ((DUK_HOBJECT_E_GET_FLAGS((heap), (h), (i)) & DUK_PROPDESC_FLAG_WRITABLE) != 0) #define DUK_HOBJECT_E_SLOT_IS_ENUMERABLE(heap, h, i) \ ((DUK_HOBJECT_E_GET_FLAGS((heap), (h), (i)) & DUK_PROPDESC_FLAG_ENUMERABLE) != 0) #define DUK_HOBJECT_E_SLOT_IS_CONFIGURABLE(heap, h, i) \ ((DUK_HOBJECT_E_GET_FLAGS((heap), (h), (i)) & DUK_PROPDESC_FLAG_CONFIGURABLE) != 0) #define DUK_HOBJECT_E_SLOT_IS_ACCESSOR(heap, h, i) ((DUK_HOBJECT_E_GET_FLAGS((heap), (h), (i)) & DUK_PROPDESC_FLAG_ACCESSOR) != 0) #define DUK_HOBJECT_E_SLOT_SET_WRITABLE(heap, h, i) DUK_HOBJECT_E_SET_FLAG_BITS((heap), (h), (i), DUK_PROPDESC_FLAG_WRITABLE) #define DUK_HOBJECT_E_SLOT_SET_ENUMERABLE(heap, h, i) DUK_HOBJECT_E_SET_FLAG_BITS((heap), (h), (i), DUK_PROPDESC_FLAG_ENUMERABLE) #define DUK_HOBJECT_E_SLOT_SET_CONFIGURABLE(heap, h, i) \ DUK_HOBJECT_E_SET_FLAG_BITS((heap), (h), (i), DUK_PROPDESC_FLAG_CONFIGURABLE) #define DUK_HOBJECT_E_SLOT_SET_ACCESSOR(heap, h, i) DUK_HOBJECT_E_SET_FLAG_BITS((heap), (h), (i), DUK_PROPDESC_FLAG_ACCESSOR) #define DUK_HOBJECT_E_SLOT_CLEAR_WRITABLE(heap, h, i) DUK_HOBJECT_E_CLEAR_FLAG_BITS((heap), (h), (i), DUK_PROPDESC_FLAG_WRITABLE) #define DUK_HOBJECT_E_SLOT_CLEAR_ENUMERABLE(heap, h, i) \ DUK_HOBJECT_E_CLEAR_FLAG_BITS((heap), (h), (i), DUK_PROPDESC_FLAG_ENUMERABLE) #define DUK_HOBJECT_E_SLOT_CLEAR_CONFIGURABLE(heap, h, i) \ DUK_HOBJECT_E_CLEAR_FLAG_BITS((heap), (h), (i), DUK_PROPDESC_FLAG_CONFIGURABLE) #define DUK_HOBJECT_E_SLOT_CLEAR_ACCESSOR(heap, h, i) DUK_HOBJECT_E_CLEAR_FLAG_BITS((heap), (h), (i), DUK_PROPDESC_FLAG_ACCESSOR) #define DUK_PROPDESC_IS_WRITABLE(p) (((p)->flags & DUK_PROPDESC_FLAG_WRITABLE) != 0) #define DUK_PROPDESC_IS_ENUMERABLE(p) (((p)->flags & DUK_PROPDESC_FLAG_ENUMERABLE) != 0) #define DUK_PROPDESC_IS_CONFIGURABLE(p) (((p)->flags & DUK_PROPDESC_FLAG_CONFIGURABLE) != 0) #define DUK_PROPDESC_IS_ACCESSOR(p) (((p)->flags & DUK_PROPDESC_FLAG_ACCESSOR) != 0) #define DUK_HOBJECT_HASHIDX_UNUSED 0xffffffffUL #define DUK_HOBJECT_HASHIDX_DELETED 0xfffffffeUL /* * Macros for accessing size fields */ #if defined(DUK_USE_OBJSIZES16) #define DUK_HOBJECT_GET_ESIZE(h) ((h)->e_size16) #define DUK_HOBJECT_SET_ESIZE(h, v) \ do { \ (h)->e_size16 = (v); \ } while (0) #define DUK_HOBJECT_GET_ENEXT(h) ((h)->e_next16) #define DUK_HOBJECT_SET_ENEXT(h, v) \ do { \ (h)->e_next16 = (v); \ } while (0) #define DUK_HOBJECT_POSTINC_ENEXT(h) ((h)->e_next16++) #define DUK_HOBJECT_GET_ASIZE(h) ((h)->a_size16) #define DUK_HOBJECT_SET_ASIZE(h, v) \ do { \ (h)->a_size16 = (v); \ } while (0) #if defined(DUK_USE_HOBJECT_HASH_PART) #define DUK_HOBJECT_GET_HSIZE(h) ((h)->h_size16) #define DUK_HOBJECT_SET_HSIZE(h, v) \ do { \ (h)->h_size16 = (v); \ } while (0) #else #define DUK_HOBJECT_GET_HSIZE(h) 0 #define DUK_HOBJECT_SET_HSIZE(h, v) \ do { \ DUK_ASSERT((v) == 0); \ } while (0) #endif #else #define DUK_HOBJECT_GET_ESIZE(h) ((h)->e_size) #define DUK_HOBJECT_SET_ESIZE(h, v) \ do { \ (h)->e_size = (v); \ } while (0) #define DUK_HOBJECT_GET_ENEXT(h) ((h)->e_next) #define DUK_HOBJECT_SET_ENEXT(h, v) \ do { \ (h)->e_next = (v); \ } while (0) #define DUK_HOBJECT_POSTINC_ENEXT(h) ((h)->e_next++) #define DUK_HOBJECT_GET_ASIZE(h) ((h)->a_size) #define DUK_HOBJECT_SET_ASIZE(h, v) \ do { \ (h)->a_size = (v); \ } while (0) #if defined(DUK_USE_HOBJECT_HASH_PART) #define DUK_HOBJECT_GET_HSIZE(h) ((h)->h_size) #define DUK_HOBJECT_SET_HSIZE(h, v) \ do { \ (h)->h_size = (v); \ } while (0) #else #define DUK_HOBJECT_GET_HSIZE(h) 0 #define DUK_HOBJECT_SET_HSIZE(h, v) \ do { \ DUK_ASSERT((v) == 0); \ } while (0) #endif #endif /* * Misc */ /* Maximum prototype traversal depth. Sanity limit which handles e.g. * prototype loops (even complex ones like 1->2->3->4->2->3->4->2->3->4). */ #define DUK_HOBJECT_PROTOTYPE_CHAIN_SANITY 10000L /* * ECMAScript [[Class]] */ /* range check not necessary because all 4-bit values are mapped */ #define DUK_HOBJECT_CLASS_NUMBER_TO_STRIDX(n) duk_class_number_to_stridx[(n)] #define DUK_HOBJECT_GET_CLASS_STRING(heap, h) \ DUK_HEAP_GET_STRING((heap), DUK_HOBJECT_CLASS_NUMBER_TO_STRIDX(DUK_HOBJECT_GET_CLASS_NUMBER((h)))) /* * Macros for property handling */ #if defined(DUK_USE_HEAPPTR16) #define DUK_HOBJECT_GET_PROTOTYPE(heap, h) ((duk_hobject *) DUK_USE_HEAPPTR_DEC16((heap)->heap_udata, (h)->prototype16)) #define DUK_HOBJECT_SET_PROTOTYPE(heap, h, x) \ do { \ (h)->prototype16 = DUK_USE_HEAPPTR_ENC16((heap)->heap_udata, (void *) (x)); \ } while (0) #else #define DUK_HOBJECT_GET_PROTOTYPE(heap, h) ((h)->prototype) #define DUK_HOBJECT_SET_PROTOTYPE(heap, h, x) \ do { \ (h)->prototype = (x); \ } while (0) #endif /* Set prototype, DECREF earlier value, INCREF new value (tolerating NULLs). */ #define DUK_HOBJECT_SET_PROTOTYPE_UPDREF(thr, h, p) duk_hobject_set_prototype_updref((thr), (h), (p)) /* Set initial prototype, assume NULL previous prototype, INCREF new value, * tolerate NULL. */ #define DUK_HOBJECT_SET_PROTOTYPE_INIT_INCREF(thr, h, proto) \ do { \ duk_hthread *duk__thr = (thr); \ duk_hobject *duk__obj = (h); \ duk_hobject *duk__proto = (proto); \ DUK_UNREF(duk__thr); \ DUK_ASSERT(DUK_HOBJECT_GET_PROTOTYPE(duk__thr->heap, duk__obj) == NULL); \ DUK_HOBJECT_SET_PROTOTYPE(duk__thr->heap, duk__obj, duk__proto); \ DUK_HOBJECT_INCREF_ALLOWNULL(duk__thr, duk__proto); \ } while (0) /* * Finalizer check */ #if defined(DUK_USE_HEAPPTR16) #define DUK_HOBJECT_HAS_FINALIZER_FAST(heap, h) duk_hobject_has_finalizer_fast_raw((heap), (h)) #else #define DUK_HOBJECT_HAS_FINALIZER_FAST(heap, h) duk_hobject_has_finalizer_fast_raw((h)) #endif /* * Resizing and hash behavior */ /* Sanity limit on max number of properties (allocated, not necessarily used). * This is somewhat arbitrary, but if we're close to 2**32 properties some * algorithms will fail (e.g. hash size selection, next prime selection). * Also, we use negative array/entry table indices to indicate 'not found', * so anything above 0x80000000 will cause trouble now. */ #if defined(DUK_USE_OBJSIZES16) #define DUK_HOBJECT_MAX_PROPERTIES 0x0000ffffUL #else #define DUK_HOBJECT_MAX_PROPERTIES 0x3fffffffUL /* 2**30-1 ~= 1G properties */ #endif /* internal align target for props allocation, must be 2*n for some n */ #if (DUK_USE_ALIGN_BY == 4) #define DUK_HOBJECT_ALIGN_TARGET 4 #elif (DUK_USE_ALIGN_BY == 8) #define DUK_HOBJECT_ALIGN_TARGET 8 #elif (DUK_USE_ALIGN_BY == 1) #define DUK_HOBJECT_ALIGN_TARGET 1 #else #error invalid DUK_USE_ALIGN_BY #endif /* * PC-to-line constants */ #define DUK_PC2LINE_SKIP 64 /* maximum length for a SKIP-1 diffstream: 35 bits per entry, rounded up to bytes */ #define DUK_PC2LINE_MAX_DIFF_LENGTH (((DUK_PC2LINE_SKIP - 1) * 35 + 7) / 8) /* * Struct defs */ struct duk_propaccessor { duk_hobject *get; duk_hobject *set; }; union duk_propvalue { /* The get/set pointers could be 16-bit pointer compressed but it * would make no difference on 32-bit platforms because duk_tval is * 8 bytes or more anyway. */ duk_tval v; duk_propaccessor a; }; struct duk_propdesc { /* read-only values 'lifted' for ease of use */ duk_small_uint_t flags; duk_hobject *get; duk_hobject *set; /* for updating (all are set to < 0 for virtual properties) */ duk_int_t e_idx; /* prop index in 'entry part', < 0 if not there */ duk_int_t h_idx; /* prop index in 'hash part', < 0 if not there */ duk_int_t a_idx; /* prop index in 'array part', < 0 if not there */ }; struct duk_hobject { duk_heaphdr hdr; /* * 'props' contains {key,value,flags} entries, optional array entries, and * an optional hash lookup table for non-array entries in a single 'sliced' * allocation. There are several layout options, which differ slightly in * generated code size/speed and alignment/padding; duk_features.h selects * the layout used. * * Layout 1 (DUK_USE_HOBJECT_LAYOUT_1): * * e_size * sizeof(duk_hstring *) bytes of entry keys (e_next gc reachable) * e_size * sizeof(duk_propvalue) bytes of entry values (e_next gc reachable) * e_size * sizeof(duk_uint8_t) bytes of entry flags (e_next gc reachable) * a_size * sizeof(duk_tval) bytes of (opt) array values (plain only) (all gc reachable) * h_size * sizeof(duk_uint32_t) bytes of (opt) hash indexes to entries (e_size), * 0xffffffffUL = unused, 0xfffffffeUL = deleted * * Layout 2 (DUK_USE_HOBJECT_LAYOUT_2): * * e_size * sizeof(duk_propvalue) bytes of entry values (e_next gc reachable) * e_size * sizeof(duk_hstring *) bytes of entry keys (e_next gc reachable) * e_size * sizeof(duk_uint8_t) + pad bytes of entry flags (e_next gc reachable) * a_size * sizeof(duk_tval) bytes of (opt) array values (plain only) (all gc reachable) * h_size * sizeof(duk_uint32_t) bytes of (opt) hash indexes to entries (e_size), * 0xffffffffUL = unused, 0xfffffffeUL = deleted * * Layout 3 (DUK_USE_HOBJECT_LAYOUT_3): * * e_size * sizeof(duk_propvalue) bytes of entry values (e_next gc reachable) * a_size * sizeof(duk_tval) bytes of (opt) array values (plain only) (all gc reachable) * e_size * sizeof(duk_hstring *) bytes of entry keys (e_next gc reachable) * h_size * sizeof(duk_uint32_t) bytes of (opt) hash indexes to entries (e_size), * 0xffffffffUL = unused, 0xfffffffeUL = deleted * e_size * sizeof(duk_uint8_t) bytes of entry flags (e_next gc reachable) * * In layout 1, the 'e_next' count is rounded to 4 or 8 on platforms * requiring 4 or 8 byte alignment. This ensures proper alignment * for the entries, at the cost of memory footprint. However, it's * probably preferable to use another layout on such platforms instead. * * In layout 2, the key and value parts are swapped to avoid padding * the key array on platforms requiring alignment by 8. The flags part * is padded to get alignment for array entries. The 'e_next' count does * not need to be rounded as in layout 1. * * In layout 3, entry values and array values are always aligned properly, * and assuming pointers are at most 8 bytes, so are the entry keys. Hash * indices will be properly aligned (assuming pointers are at least 4 bytes). * Finally, flags don't need additional alignment. This layout provides * compact allocations without padding (even on platforms with alignment * requirements) at the cost of a bit slower lookups. * * Objects with few keys don't have a hash index; keys are looked up linearly, * which is cache efficient because the keys are consecutive. Larger objects * have a hash index part which contains integer indexes to the entries part. * * A single allocation reduces memory allocation overhead but requires more * work when any part needs to be resized. A sliced allocation for entries * makes linear key matching faster on most platforms (more locality) and * skimps on flags size (which would be followed by 3 bytes of padding in * most architectures if entries were placed in a struct). * * 'props' also contains internal properties distinguished with a non-BMP * prefix. Often used properties should be placed early in 'props' whenever * possible to make accessing them as fast a possible. */ #if defined(DUK_USE_HEAPPTR16) /* Located in duk_heaphdr h_extra16. Subclasses of duk_hobject (like * duk_hcompfunc) are not free to use h_extra16 for this reason. */ #else duk_uint8_t *props; #endif /* prototype: the only internal property lifted outside 'e' as it is so central */ #if defined(DUK_USE_HEAPPTR16) duk_uint16_t prototype16; #else duk_hobject *prototype; #endif #if defined(DUK_USE_OBJSIZES16) duk_uint16_t e_size16; duk_uint16_t e_next16; duk_uint16_t a_size16; #if defined(DUK_USE_HOBJECT_HASH_PART) duk_uint16_t h_size16; #endif #else duk_uint32_t e_size; /* entry part size */ duk_uint32_t e_next; /* index for next new key ([0,e_next[ are gc reachable) */ duk_uint32_t a_size; /* array part size (entirely gc reachable) */ #if defined(DUK_USE_HOBJECT_HASH_PART) duk_uint32_t h_size; /* hash part size or 0 if unused */ #endif #endif }; /* * Exposed data */ #if !defined(DUK_SINGLE_FILE) DUK_INTERNAL_DECL duk_uint8_t duk_class_number_to_stridx[32]; #endif /* !DUK_SINGLE_FILE */ /* * Prototypes */ /* alloc and init */ DUK_INTERNAL_DECL duk_hobject *duk_hobject_alloc_unchecked(duk_heap *heap, duk_uint_t hobject_flags); DUK_INTERNAL_DECL duk_hobject *duk_hobject_alloc(duk_hthread *thr, duk_uint_t hobject_flags); DUK_INTERNAL_DECL duk_harray *duk_harray_alloc(duk_hthread *thr, duk_uint_t hobject_flags); DUK_INTERNAL_DECL duk_hcompfunc *duk_hcompfunc_alloc(duk_hthread *thr, duk_uint_t hobject_flags); DUK_INTERNAL_DECL duk_hnatfunc *duk_hnatfunc_alloc(duk_hthread *thr, duk_uint_t hobject_flags); DUK_INTERNAL_DECL duk_hboundfunc *duk_hboundfunc_alloc(duk_heap *heap, duk_uint_t hobject_flags); #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) DUK_INTERNAL_DECL duk_hbufobj *duk_hbufobj_alloc(duk_hthread *thr, duk_uint_t hobject_flags); #endif DUK_INTERNAL_DECL duk_hthread *duk_hthread_alloc_unchecked(duk_heap *heap, duk_uint_t hobject_flags); DUK_INTERNAL_DECL duk_hthread *duk_hthread_alloc(duk_hthread *thr, duk_uint_t hobject_flags); DUK_INTERNAL_DECL duk_hdecenv *duk_hdecenv_alloc(duk_hthread *thr, duk_uint_t hobject_flags); DUK_INTERNAL_DECL duk_hobjenv *duk_hobjenv_alloc(duk_hthread *thr, duk_uint_t hobject_flags); DUK_INTERNAL_DECL duk_hproxy *duk_hproxy_alloc(duk_hthread *thr, duk_uint_t hobject_flags); /* resize */ DUK_INTERNAL_DECL void duk_hobject_realloc_props(duk_hthread *thr, duk_hobject *obj, duk_uint32_t new_e_size, duk_uint32_t new_a_size, duk_uint32_t new_h_size, duk_bool_t abandon_array); DUK_INTERNAL_DECL void duk_hobject_resize_entrypart(duk_hthread *thr, duk_hobject *obj, duk_uint32_t new_e_size); #if 0 /*unused*/ DUK_INTERNAL_DECL void duk_hobject_resize_arraypart(duk_hthread *thr, duk_hobject *obj, duk_uint32_t new_a_size); #endif /* low-level property functions */ DUK_INTERNAL_DECL duk_bool_t duk_hobject_find_entry(duk_heap *heap, duk_hobject *obj, duk_hstring *key, duk_int_t *e_idx, duk_int_t *h_idx); DUK_INTERNAL_DECL duk_tval *duk_hobject_find_entry_tval_ptr(duk_heap *heap, duk_hobject *obj, duk_hstring *key); DUK_INTERNAL_DECL duk_tval *duk_hobject_find_entry_tval_ptr_stridx(duk_heap *heap, duk_hobject *obj, duk_small_uint_t stridx); DUK_INTERNAL_DECL duk_tval *duk_hobject_find_entry_tval_ptr_and_attrs(duk_heap *heap, duk_hobject *obj, duk_hstring *key, duk_uint_t *out_attrs); DUK_INTERNAL_DECL duk_tval *duk_hobject_find_array_entry_tval_ptr(duk_heap *heap, duk_hobject *obj, duk_uarridx_t i); DUK_INTERNAL_DECL duk_bool_t duk_hobject_get_own_propdesc(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, duk_propdesc *out_desc, duk_small_uint_t flags); /* core property functions */ DUK_INTERNAL_DECL duk_bool_t duk_hobject_getprop(duk_hthread *thr, duk_tval *tv_obj, duk_tval *tv_key); DUK_INTERNAL_DECL duk_bool_t duk_hobject_putprop(duk_hthread *thr, duk_tval *tv_obj, duk_tval *tv_key, duk_tval *tv_val, duk_bool_t throw_flag); DUK_INTERNAL_DECL duk_bool_t duk_hobject_delprop(duk_hthread *thr, duk_tval *tv_obj, duk_tval *tv_key, duk_bool_t throw_flag); DUK_INTERNAL_DECL duk_bool_t duk_hobject_hasprop(duk_hthread *thr, duk_tval *tv_obj, duk_tval *tv_key); /* internal property functions */ #define DUK_DELPROP_FLAG_THROW (1U << 0) #define DUK_DELPROP_FLAG_FORCE (1U << 1) DUK_INTERNAL_DECL duk_bool_t duk_hobject_delprop_raw(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, duk_small_uint_t flags); DUK_INTERNAL_DECL duk_bool_t duk_hobject_hasprop_raw(duk_hthread *thr, duk_hobject *obj, duk_hstring *key); DUK_INTERNAL_DECL void duk_hobject_define_property_internal(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, duk_small_uint_t flags); DUK_INTERNAL_DECL void duk_hobject_define_property_internal_arridx(duk_hthread *thr, duk_hobject *obj, duk_uarridx_t arr_idx, duk_small_uint_t flags); DUK_INTERNAL_DECL duk_size_t duk_hobject_get_length(duk_hthread *thr, duk_hobject *obj); #if defined(DUK_USE_HEAPPTR16) DUK_INTERNAL_DECL duk_bool_t duk_hobject_has_finalizer_fast_raw(duk_heap *heap, duk_hobject *obj); #else DUK_INTERNAL_DECL duk_bool_t duk_hobject_has_finalizer_fast_raw(duk_hobject *obj); #endif /* helpers for defineProperty() and defineProperties() */ DUK_INTERNAL_DECL void duk_hobject_prepare_property_descriptor(duk_hthread *thr, duk_idx_t idx_in, duk_uint_t *out_defprop_flags, duk_idx_t *out_idx_value, duk_hobject **out_getter, duk_hobject **out_setter); DUK_INTERNAL_DECL duk_bool_t duk_hobject_define_property_helper(duk_hthread *thr, duk_uint_t defprop_flags, duk_hobject *obj, duk_hstring *key, duk_idx_t idx_value, duk_hobject *get, duk_hobject *set, duk_bool_t throw_flag); /* Object built-in methods */ DUK_INTERNAL_DECL void duk_hobject_object_get_own_property_descriptor(duk_hthread *thr, duk_idx_t obj_idx); DUK_INTERNAL_DECL void duk_hobject_object_seal_freeze_helper(duk_hthread *thr, duk_hobject *obj, duk_bool_t is_freeze); DUK_INTERNAL_DECL duk_bool_t duk_hobject_object_is_sealed_frozen_helper(duk_hthread *thr, duk_hobject *obj, duk_bool_t is_frozen); DUK_INTERNAL_DECL duk_bool_t duk_hobject_object_ownprop_helper(duk_hthread *thr, duk_small_uint_t required_desc_flags); /* internal properties */ DUK_INTERNAL_DECL duk_tval *duk_hobject_get_internal_value_tval_ptr(duk_heap *heap, duk_hobject *obj); DUK_INTERNAL_DECL duk_hstring *duk_hobject_get_internal_value_string(duk_heap *heap, duk_hobject *obj); DUK_INTERNAL_DECL duk_harray *duk_hobject_get_formals(duk_hthread *thr, duk_hobject *obj); DUK_INTERNAL_DECL duk_hobject *duk_hobject_get_varmap(duk_hthread *thr, duk_hobject *obj); /* hobject management functions */ DUK_INTERNAL_DECL void duk_hobject_compact_props(duk_hthread *thr, duk_hobject *obj); /* ES2015 proxy */ #if defined(DUK_USE_ES6_PROXY) DUK_INTERNAL_DECL duk_bool_t duk_hobject_proxy_check(duk_hobject *obj, duk_hobject **out_target, duk_hobject **out_handler); DUK_INTERNAL_DECL duk_hobject *duk_hobject_resolve_proxy_target(duk_hobject *obj); #endif /* enumeration */ DUK_INTERNAL_DECL void duk_hobject_enumerator_create(duk_hthread *thr, duk_small_uint_t enum_flags); DUK_INTERNAL_DECL duk_ret_t duk_hobject_get_enumerated_keys(duk_hthread *thr, duk_small_uint_t enum_flags); DUK_INTERNAL_DECL duk_bool_t duk_hobject_enumerator_next(duk_hthread *thr, duk_bool_t get_value); /* macros */ DUK_INTERNAL_DECL void duk_hobject_set_prototype_updref(duk_hthread *thr, duk_hobject *h, duk_hobject *p); /* pc2line */ #if defined(DUK_USE_PC2LINE) DUK_INTERNAL_DECL void duk_hobject_pc2line_pack(duk_hthread *thr, duk_compiler_instr *instrs, duk_uint_fast32_t length); DUK_INTERNAL_DECL duk_uint_fast32_t duk_hobject_pc2line_query(duk_hthread *thr, duk_idx_t idx_func, duk_uint_fast32_t pc); #endif /* misc */ DUK_INTERNAL_DECL duk_bool_t duk_hobject_prototype_chain_contains(duk_hthread *thr, duk_hobject *h, duk_hobject *p, duk_bool_t ignore_loop); #if !defined(DUK_USE_OBJECT_BUILTIN) /* These declarations are needed when related built-in is disabled and * genbuiltins.py won't automatically emit the declerations. */ DUK_INTERNAL_DECL duk_ret_t duk_bi_object_prototype_to_string(duk_hthread *thr); DUK_INTERNAL_DECL duk_ret_t duk_bi_function_prototype(duk_hthread *thr); #endif #endif /* DUK_HOBJECT_H_INCLUDED */ /* #include duk_hcompfunc.h */ #line 1 "duk_hcompfunc.h" /* * Heap compiled function (ECMAScript function) representation. * * There is a single data buffer containing the ECMAScript function's * bytecode, constants, and inner functions. */ #if !defined(DUK_HCOMPFUNC_H_INCLUDED) #define DUK_HCOMPFUNC_H_INCLUDED /* * Field accessor macros */ /* XXX: casts could be improved, especially for GET/SET DATA */ #if defined(DUK_USE_HEAPPTR16) #define DUK_HCOMPFUNC_GET_DATA(heap, h) ((duk_hbuffer_fixed *) (void *) DUK_USE_HEAPPTR_DEC16((heap)->heap_udata, (h)->data16)) #define DUK_HCOMPFUNC_SET_DATA(heap, h, v) \ do { \ (h)->data16 = DUK_USE_HEAPPTR_ENC16((heap)->heap_udata, (void *) (v)); \ } while (0) #define DUK_HCOMPFUNC_GET_FUNCS(heap, h) ((duk_hobject **) (void *) (DUK_USE_HEAPPTR_DEC16((heap)->heap_udata, (h)->funcs16))) #define DUK_HCOMPFUNC_SET_FUNCS(heap, h, v) \ do { \ (h)->funcs16 = DUK_USE_HEAPPTR_ENC16((heap)->heap_udata, (void *) (v)); \ } while (0) #define DUK_HCOMPFUNC_GET_BYTECODE(heap, h) ((duk_instr_t *) (void *) (DUK_USE_HEAPPTR_DEC16((heap)->heap_udata, (h)->bytecode16))) #define DUK_HCOMPFUNC_SET_BYTECODE(heap, h, v) \ do { \ (h)->bytecode16 = DUK_USE_HEAPPTR_ENC16((heap)->heap_udata, (void *) (v)); \ } while (0) #define DUK_HCOMPFUNC_GET_LEXENV(heap, h) ((duk_hobject *) (void *) (DUK_USE_HEAPPTR_DEC16((heap)->heap_udata, (h)->lex_env16))) #define DUK_HCOMPFUNC_SET_LEXENV(heap, h, v) \ do { \ (h)->lex_env16 = DUK_USE_HEAPPTR_ENC16((heap)->heap_udata, (void *) (v)); \ } while (0) #define DUK_HCOMPFUNC_GET_VARENV(heap, h) ((duk_hobject *) (void *) (DUK_USE_HEAPPTR_DEC16((heap)->heap_udata, (h)->var_env16))) #define DUK_HCOMPFUNC_SET_VARENV(heap, h, v) \ do { \ (h)->var_env16 = DUK_USE_HEAPPTR_ENC16((heap)->heap_udata, (void *) (v)); \ } while (0) #else #define DUK_HCOMPFUNC_GET_DATA(heap, h) ((duk_hbuffer_fixed *) (void *) (h)->data) #define DUK_HCOMPFUNC_SET_DATA(heap, h, v) \ do { \ (h)->data = (duk_hbuffer *) (v); \ } while (0) #define DUK_HCOMPFUNC_GET_FUNCS(heap, h) ((h)->funcs) #define DUK_HCOMPFUNC_SET_FUNCS(heap, h, v) \ do { \ (h)->funcs = (v); \ } while (0) #define DUK_HCOMPFUNC_GET_BYTECODE(heap, h) ((h)->bytecode) #define DUK_HCOMPFUNC_SET_BYTECODE(heap, h, v) \ do { \ (h)->bytecode = (v); \ } while (0) #define DUK_HCOMPFUNC_GET_LEXENV(heap, h) ((h)->lex_env) #define DUK_HCOMPFUNC_SET_LEXENV(heap, h, v) \ do { \ (h)->lex_env = (v); \ } while (0) #define DUK_HCOMPFUNC_GET_VARENV(heap, h) ((h)->var_env) #define DUK_HCOMPFUNC_SET_VARENV(heap, h, v) \ do { \ (h)->var_env = (v); \ } while (0) #endif /* * Accessor macros for function specific data areas */ /* Note: assumes 'data' is always a fixed buffer */ #define DUK_HCOMPFUNC_GET_BUFFER_BASE(heap, h) DUK_HBUFFER_FIXED_GET_DATA_PTR((heap), DUK_HCOMPFUNC_GET_DATA((heap), (h))) #define DUK_HCOMPFUNC_GET_CONSTS_BASE(heap, h) ((duk_tval *) (void *) DUK_HCOMPFUNC_GET_BUFFER_BASE((heap), (h))) #define DUK_HCOMPFUNC_GET_FUNCS_BASE(heap, h) DUK_HCOMPFUNC_GET_FUNCS((heap), (h)) #define DUK_HCOMPFUNC_GET_CODE_BASE(heap, h) DUK_HCOMPFUNC_GET_BYTECODE((heap), (h)) #define DUK_HCOMPFUNC_GET_CONSTS_END(heap, h) ((duk_tval *) (void *) DUK_HCOMPFUNC_GET_FUNCS((heap), (h))) #define DUK_HCOMPFUNC_GET_FUNCS_END(heap, h) ((duk_hobject **) (void *) DUK_HCOMPFUNC_GET_BYTECODE((heap), (h))) /* XXX: double evaluation of DUK_HCOMPFUNC_GET_DATA() */ #define DUK_HCOMPFUNC_GET_CODE_END(heap, h) \ ((duk_instr_t *) (void *) (DUK_HBUFFER_FIXED_GET_DATA_PTR((heap), DUK_HCOMPFUNC_GET_DATA((heap), (h))) + \ DUK_HBUFFER_GET_SIZE((duk_hbuffer *) DUK_HCOMPFUNC_GET_DATA((heap), h)))) #define DUK_HCOMPFUNC_GET_CONSTS_SIZE(heap, h) \ ((duk_size_t) (((const duk_uint8_t *) DUK_HCOMPFUNC_GET_CONSTS_END((heap), (h))) - \ ((const duk_uint8_t *) DUK_HCOMPFUNC_GET_CONSTS_BASE((heap), (h))))) #define DUK_HCOMPFUNC_GET_FUNCS_SIZE(heap, h) \ ((duk_size_t) (((const duk_uint8_t *) DUK_HCOMPFUNC_GET_FUNCS_END((heap), (h))) - \ ((const duk_uint8_t *) DUK_HCOMPFUNC_GET_FUNCS_BASE((heap), (h))))) #define DUK_HCOMPFUNC_GET_CODE_SIZE(heap, h) \ ((duk_size_t) (((const duk_uint8_t *) DUK_HCOMPFUNC_GET_CODE_END((heap), (h))) - \ ((const duk_uint8_t *) DUK_HCOMPFUNC_GET_CODE_BASE((heap), (h))))) #define DUK_HCOMPFUNC_GET_CONSTS_COUNT(heap, h) ((duk_size_t) (DUK_HCOMPFUNC_GET_CONSTS_SIZE((heap), (h)) / sizeof(duk_tval))) #define DUK_HCOMPFUNC_GET_FUNCS_COUNT(heap, h) ((duk_size_t) (DUK_HCOMPFUNC_GET_FUNCS_SIZE((heap), (h)) / sizeof(duk_hobject *))) #define DUK_HCOMPFUNC_GET_CODE_COUNT(heap, h) ((duk_size_t) (DUK_HCOMPFUNC_GET_CODE_SIZE((heap), (h)) / sizeof(duk_instr_t))) /* * Validity assert */ #if defined(DUK_USE_ASSERTIONS) DUK_INTERNAL_DECL void duk_hcompfunc_assert_valid(duk_hcompfunc *h); #define DUK_HCOMPFUNC_ASSERT_VALID(h) \ do { \ duk_hcompfunc_assert_valid((h)); \ } while (0) #else #define DUK_HCOMPFUNC_ASSERT_VALID(h) \ do { \ } while (0) #endif /* * Main struct */ struct duk_hcompfunc { /* shared object part */ duk_hobject obj; /* * Pointers to function data area for faster access. Function * data is a buffer shared between all closures of the same * "template" function. The data buffer is always fixed (non- * dynamic, hence stable), with a layout as follows: * * constants (duk_tval) * inner functions (duk_hobject *) * bytecode (duk_instr_t) * * Note: bytecode end address can be computed from 'data' buffer * size. It is not strictly necessary functionally, assuming * bytecode never jumps outside its allocated area. However, * it's a safety/robustness feature for avoiding the chance of * executing random data as bytecode due to a compiler error. * * Note: values in the data buffer must be incref'd (they will * be decref'd on release) for every compiledfunction referring * to the 'data' element. */ /* Data area, fixed allocation, stable data ptrs. */ #if defined(DUK_USE_HEAPPTR16) duk_uint16_t data16; #else duk_hbuffer *data; #endif /* No need for constants pointer (= same as data). * * When using 16-bit packing alignment to 4 is nice. 'funcs' will be * 4-byte aligned because 'constants' are duk_tvals. For now the * inner function pointers are not compressed, so that 'bytecode' will * also be 4-byte aligned. */ #if defined(DUK_USE_HEAPPTR16) duk_uint16_t funcs16; duk_uint16_t bytecode16; #else duk_hobject **funcs; duk_instr_t *bytecode; #endif /* Lexenv: lexical environment of closure, NULL for templates. * Varenv: variable environment of closure, NULL for templates. */ #if defined(DUK_USE_HEAPPTR16) duk_uint16_t lex_env16; duk_uint16_t var_env16; #else duk_hobject *lex_env; duk_hobject *var_env; #endif /* * 'nregs' registers are allocated on function entry, at most 'nargs' * are initialized to arguments, and the rest to undefined. Arguments * above 'nregs' are not mapped to registers. All registers in the * active stack range must be initialized because they are GC reachable. * 'nargs' is needed so that if the function is given more than 'nargs' * arguments, the additional arguments do not 'clobber' registers * beyond 'nregs' which must be consistently initialized to undefined. * * Usually there is no need to know which registers are mapped to * local variables. Registers may be allocated to variable in any * way (even including gaps). However, a register-variable mapping * must be the same for the duration of the function execution and * the register cannot be used for anything else. * * When looking up variables by name, the '_Varmap' map is used. * When an activation closes, registers mapped to arguments are * copied into the environment record based on the same map. The * reverse map (from register to variable) is not currently needed * at run time, except for debugging, so it is not maintained. */ duk_uint16_t nregs; /* regs to allocate */ duk_uint16_t nargs; /* number of arguments allocated to regs */ /* * Additional control information is placed into the object itself * as internal properties to avoid unnecessary fields for the * majority of functions. The compiler tries to omit internal * control fields when possible. * * Function templates: * * { * name: "func", // declaration, named function expressions * fileName: <debug info for creating nice errors> * _Varmap: { "arg1": 0, "arg2": 1, "varname": 2 }, * _Formals: [ "arg1", "arg2" ], * _Source: "function func(arg1, arg2) { ... }", * _Pc2line: <debug info for pc-to-line mapping>, * } * * Function instances: * * { * length: 2, * prototype: { constructor: <func> }, * caller: <thrower>, * arguments: <thrower>, * name: "func", // declaration, named function expressions * fileName: <debug info for creating nice errors> * _Varmap: { "arg1": 0, "arg2": 1, "varname": 2 }, * _Formals: [ "arg1", "arg2" ], * _Source: "function func(arg1, arg2) { ... }", * _Pc2line: <debug info for pc-to-line mapping>, * } * * More detailed description of these properties can be found * in the documentation. */ #if defined(DUK_USE_DEBUGGER_SUPPORT) /* Line number range for function. Needed during debugging to * determine active breakpoints. */ duk_uint32_t start_line; duk_uint32_t end_line; #endif }; #endif /* DUK_HCOMPFUNC_H_INCLUDED */ /* #include duk_hnatfunc.h */ #line 1 "duk_hnatfunc.h" /* * Heap native function representation. */ #if !defined(DUK_HNATFUNC_H_INCLUDED) #define DUK_HNATFUNC_H_INCLUDED #if defined(DUK_USE_ASSERTIONS) DUK_INTERNAL_DECL void duk_hnatfunc_assert_valid(duk_hnatfunc *h); #define DUK_HNATFUNC_ASSERT_VALID(h) \ do { \ duk_hnatfunc_assert_valid((h)); \ } while (0) #else #define DUK_HNATFUNC_ASSERT_VALID(h) \ do { \ } while (0) #endif #define DUK_HNATFUNC_NARGS_VARARGS ((duk_int16_t) -1) #define DUK_HNATFUNC_NARGS_MAX ((duk_int16_t) 0x7fff) struct duk_hnatfunc { /* shared object part */ duk_hobject obj; duk_c_function func; duk_int16_t nargs; duk_int16_t magic; /* The 'magic' field allows an opaque 16-bit field to be accessed by the * Duktape/C function. This allows, for instance, the same native function * to be used for a set of very similar functions, with the 'magic' field * providing the necessary non-argument flags / values to guide the behavior * of the native function. The value is signed on purpose: it is easier to * convert a signed value to unsigned (simply AND with 0xffff) than vice * versa. * * Note: cannot place nargs/magic into the heaphdr flags, because * duk_hobject takes almost all flags already. */ }; #endif /* DUK_HNATFUNC_H_INCLUDED */ /* #include duk_hboundfunc.h */ #line 1 "duk_hboundfunc.h" /* * Bound function representation. */ #if !defined(DUK_HBOUNDFUNC_H_INCLUDED) #define DUK_HBOUNDFUNC_H_INCLUDED /* Artificial limit for args length. Ensures arithmetic won't overflow * 32 bits when combining bound functions. */ #define DUK_HBOUNDFUNC_MAX_ARGS 0x20000000UL #if defined(DUK_USE_ASSERTIONS) DUK_INTERNAL_DECL void duk_hboundfunc_assert_valid(duk_hboundfunc *h); #define DUK_HBOUNDFUNC_ASSERT_VALID(h) \ do { \ duk_hboundfunc_assert_valid((h)); \ } while (0) #else #define DUK_HBOUNDFUNC_ASSERT_VALID(h) \ do { \ } while (0) #endif struct duk_hboundfunc { /* Shared object part. */ duk_hobject obj; /* Final target function, stored as duk_tval so that lightfunc can be * represented too. */ duk_tval target; /* This binding. */ duk_tval this_binding; /* Arguments to prepend. */ duk_tval *args; /* Separate allocation. */ duk_idx_t nargs; }; #endif /* DUK_HBOUNDFUNC_H_INCLUDED */ /* #include duk_hbufobj.h */ #line 1 "duk_hbufobj.h" /* * Heap Buffer object representation. Used for all Buffer variants. */ #if !defined(DUK_HBUFOBJ_H_INCLUDED) #define DUK_HBUFOBJ_H_INCLUDED #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) /* All element accessors are host endian now (driven by TypedArray spec). */ #define DUK_HBUFOBJ_ELEM_UINT8 0 #define DUK_HBUFOBJ_ELEM_UINT8CLAMPED 1 #define DUK_HBUFOBJ_ELEM_INT8 2 #define DUK_HBUFOBJ_ELEM_UINT16 3 #define DUK_HBUFOBJ_ELEM_INT16 4 #define DUK_HBUFOBJ_ELEM_UINT32 5 #define DUK_HBUFOBJ_ELEM_INT32 6 #define DUK_HBUFOBJ_ELEM_FLOAT32 7 #define DUK_HBUFOBJ_ELEM_FLOAT64 8 #define DUK_HBUFOBJ_ELEM_MAX 8 #if defined(DUK_USE_ASSERTIONS) DUK_INTERNAL_DECL void duk_hbufobj_assert_valid(duk_hbufobj *h); #define DUK_HBUFOBJ_ASSERT_VALID(h) \ do { \ duk_hbufobj_assert_valid((h)); \ } while (0) #else #define DUK_HBUFOBJ_ASSERT_VALID(h) \ do { \ } while (0) #endif /* Get the current data pointer (caller must ensure buf != NULL) as a * duk_uint8_t ptr. Note that the result may be NULL if the underlying * buffer has zero size and is not a fixed buffer. */ #define DUK_HBUFOBJ_GET_SLICE_BASE(heap, h) \ (DUK_ASSERT_EXPR((h) != NULL), \ DUK_ASSERT_EXPR((h)->buf != NULL), \ (((duk_uint8_t *) DUK_HBUFFER_GET_DATA_PTR((heap), (h)->buf)) + (h)->offset)) /* True if slice is full, i.e. offset is zero and length covers the entire * buffer. This status may change independently of the duk_hbufobj if * the underlying buffer is dynamic and changes without the hbufobj * being changed. */ #define DUK_HBUFOBJ_FULL_SLICE(h) \ (DUK_ASSERT_EXPR((h) != NULL), \ DUK_ASSERT_EXPR((h)->buf != NULL), \ ((h)->offset == 0 && (h)->length == DUK_HBUFFER_GET_SIZE((h)->buf))) /* Validate that the whole slice [0,length[ is contained in the underlying * buffer. Caller must ensure 'buf' != NULL. */ #define DUK_HBUFOBJ_VALID_SLICE(h) \ (DUK_ASSERT_EXPR((h) != NULL), \ DUK_ASSERT_EXPR((h)->buf != NULL), \ ((h)->offset + (h)->length <= DUK_HBUFFER_GET_SIZE((h)->buf))) /* Validate byte read/write for virtual 'offset', i.e. check that the * offset, taking into account h->offset, is within the underlying * buffer size. This is a safety check which is needed to ensure * that even a misconfigured duk_hbufobj never causes memory unsafe * behavior (e.g. if an underlying dynamic buffer changes after being * setup). Caller must ensure 'buf' != NULL. */ #define DUK_HBUFOBJ_VALID_BYTEOFFSET_INCL(h, off) \ (DUK_ASSERT_EXPR((h) != NULL), DUK_ASSERT_EXPR((h)->buf != NULL), ((h)->offset + (off) < DUK_HBUFFER_GET_SIZE((h)->buf))) #define DUK_HBUFOBJ_VALID_BYTEOFFSET_EXCL(h, off) \ (DUK_ASSERT_EXPR((h) != NULL), DUK_ASSERT_EXPR((h)->buf != NULL), ((h)->offset + (off) <= DUK_HBUFFER_GET_SIZE((h)->buf))) /* Clamp an input byte length (already assumed to be within the nominal * duk_hbufobj 'length') to the current dynamic buffer limits to yield * a byte length limit that's safe for memory accesses. This value can * be invalidated by any side effect because it may trigger a user * callback that resizes the underlying buffer. */ #define DUK_HBUFOBJ_CLAMP_BYTELENGTH(h, len) (DUK_ASSERT_EXPR((h) != NULL), duk_hbufobj_clamp_bytelength((h), (len))) /* Typed arrays have virtual indices, ArrayBuffer and DataView do not. */ #define DUK_HBUFOBJ_HAS_VIRTUAL_INDICES(h) ((h)->is_typedarray) struct duk_hbufobj { /* Shared object part. */ duk_hobject obj; /* Underlying buffer (refcounted), may be NULL. */ duk_hbuffer *buf; /* .buffer reference to an ArrayBuffer, may be NULL. */ duk_hobject *buf_prop; /* Slice and accessor information. * * Because the underlying buffer may be dynamic, these may be * invalidated by the buffer being modified so that both offset * and length should be validated before every access. Behavior * when the underlying buffer has changed doesn't need to be clean: * virtual 'length' doesn't need to be affected, reads can return * zero/NaN, and writes can be ignored. * * Note that a data pointer cannot be precomputed because 'buf' may * be dynamic and its pointer unstable. */ duk_uint_t offset; /* byte offset to buf */ duk_uint_t length; /* byte index limit for element access, exclusive */ duk_uint8_t shift; /* element size shift: * 0 = u8/i8 * 1 = u16/i16 * 2 = u32/i32/float * 3 = double */ duk_uint8_t elem_type; /* element type */ duk_uint8_t is_typedarray; }; DUK_INTERNAL_DECL duk_uint_t duk_hbufobj_clamp_bytelength(duk_hbufobj *h_bufobj, duk_uint_t len); DUK_INTERNAL_DECL void duk_hbufobj_push_uint8array_from_plain(duk_hthread *thr, duk_hbuffer *h_buf); DUK_INTERNAL_DECL void duk_hbufobj_push_validated_read(duk_hthread *thr, duk_hbufobj *h_bufobj, duk_uint8_t *p, duk_small_uint_t elem_size); DUK_INTERNAL_DECL void duk_hbufobj_validated_write(duk_hthread *thr, duk_hbufobj *h_bufobj, duk_uint8_t *p, duk_small_uint_t elem_size); DUK_INTERNAL_DECL void duk_hbufobj_promote_plain(duk_hthread *thr, duk_idx_t idx); #else /* DUK_USE_BUFFEROBJECT_SUPPORT */ /* nothing */ #endif /* DUK_USE_BUFFEROBJECT_SUPPORT */ #endif /* DUK_HBUFOBJ_H_INCLUDED */ /* #include duk_hthread.h */ #line 1 "duk_hthread.h" /* * Heap thread object representation. * * duk_hthread is also the 'context' for public API functions via a * different typedef. Most API calls operate on the topmost frame * of the value stack only. */ #if !defined(DUK_HTHREAD_H_INCLUDED) #define DUK_HTHREAD_H_INCLUDED /* * Stack constants */ /* Initial valstack size, roughly 0.7kiB. */ #define DUK_VALSTACK_INITIAL_SIZE 96U /* Internal extra elements assumed on function entry, always added to * user-defined 'extra' for e.g. the duk_check_stack() call. */ #define DUK_VALSTACK_INTERNAL_EXTRA 32U /* Number of elements guaranteed to be user accessible (in addition to call * arguments) on Duktape/C function entry. This is the major public API * commitment. */ #define DUK_VALSTACK_API_ENTRY_MINIMUM DUK_API_ENTRY_STACK /* * Activation defines */ #define DUK_ACT_FLAG_STRICT (1U << 0) /* function executes in strict mode */ #define DUK_ACT_FLAG_TAILCALLED (1U << 1) /* activation has tail called one or more times */ #define DUK_ACT_FLAG_CONSTRUCT (1U << 2) /* function executes as a constructor (called via "new") */ #define DUK_ACT_FLAG_PREVENT_YIELD (1U << 3) /* activation prevents yield (native call or "new") */ #define DUK_ACT_FLAG_DIRECT_EVAL (1U << 4) /* activation is a direct eval call */ #define DUK_ACT_FLAG_CONSTRUCT_PROXY (1U << 5) /* activation is for Proxy 'construct' call, special return value handling */ #define DUK_ACT_FLAG_BREAKPOINT_ACTIVE (1U << 6) /* activation has active breakpoint(s) */ #define DUK_ACT_GET_FUNC(act) ((act)->func) /* * Flags for __FILE__ / __LINE__ registered into tracedata */ #define DUK_TB_FLAG_NOBLAME_FILELINE (1U << 0) /* don't report __FILE__ / __LINE__ as fileName/lineNumber */ /* * Catcher defines */ /* XXX: remove catcher type entirely */ /* flags field: LLLLLLFT, L = label (24 bits), F = flags (4 bits), T = type (4 bits) */ #define DUK_CAT_TYPE_MASK 0x0000000fUL #define DUK_CAT_TYPE_BITS 4 #define DUK_CAT_LABEL_MASK 0xffffff00UL #define DUK_CAT_LABEL_BITS 24 #define DUK_CAT_LABEL_SHIFT 8 #define DUK_CAT_FLAG_CATCH_ENABLED (1U << 4) /* catch part will catch */ #define DUK_CAT_FLAG_FINALLY_ENABLED (1U << 5) /* finally part will catch */ #define DUK_CAT_FLAG_CATCH_BINDING_ENABLED (1U << 6) /* request to create catch binding */ #define DUK_CAT_FLAG_LEXENV_ACTIVE (1U << 7) /* catch or with binding is currently active */ #define DUK_CAT_TYPE_UNKNOWN 0 #define DUK_CAT_TYPE_TCF 1 #define DUK_CAT_TYPE_LABEL 2 #define DUK_CAT_GET_TYPE(c) ((c)->flags & DUK_CAT_TYPE_MASK) #define DUK_CAT_GET_LABEL(c) (((c)->flags & DUK_CAT_LABEL_MASK) >> DUK_CAT_LABEL_SHIFT) #define DUK_CAT_HAS_CATCH_ENABLED(c) ((c)->flags & DUK_CAT_FLAG_CATCH_ENABLED) #define DUK_CAT_HAS_FINALLY_ENABLED(c) ((c)->flags & DUK_CAT_FLAG_FINALLY_ENABLED) #define DUK_CAT_HAS_CATCH_BINDING_ENABLED(c) ((c)->flags & DUK_CAT_FLAG_CATCH_BINDING_ENABLED) #define DUK_CAT_HAS_LEXENV_ACTIVE(c) ((c)->flags & DUK_CAT_FLAG_LEXENV_ACTIVE) #define DUK_CAT_SET_CATCH_ENABLED(c) \ do { \ (c)->flags |= DUK_CAT_FLAG_CATCH_ENABLED; \ } while (0) #define DUK_CAT_SET_FINALLY_ENABLED(c) \ do { \ (c)->flags |= DUK_CAT_FLAG_FINALLY_ENABLED; \ } while (0) #define DUK_CAT_SET_CATCH_BINDING_ENABLED(c) \ do { \ (c)->flags |= DUK_CAT_FLAG_CATCH_BINDING_ENABLED; \ } while (0) #define DUK_CAT_SET_LEXENV_ACTIVE(c) \ do { \ (c)->flags |= DUK_CAT_FLAG_LEXENV_ACTIVE; \ } while (0) #define DUK_CAT_CLEAR_CATCH_ENABLED(c) \ do { \ (c)->flags &= ~DUK_CAT_FLAG_CATCH_ENABLED; \ } while (0) #define DUK_CAT_CLEAR_FINALLY_ENABLED(c) \ do { \ (c)->flags &= ~DUK_CAT_FLAG_FINALLY_ENABLED; \ } while (0) #define DUK_CAT_CLEAR_CATCH_BINDING_ENABLED(c) \ do { \ (c)->flags &= ~DUK_CAT_FLAG_CATCH_BINDING_ENABLED; \ } while (0) #define DUK_CAT_CLEAR_LEXENV_ACTIVE(c) \ do { \ (c)->flags &= ~DUK_CAT_FLAG_LEXENV_ACTIVE; \ } while (0) /* * Thread defines */ #if defined(DUK_USE_ROM_STRINGS) #define DUK_HTHREAD_GET_STRING(thr, idx) ((duk_hstring *) DUK_LOSE_CONST(duk_rom_strings_stridx[(idx)])) #else /* DUK_USE_ROM_STRINGS */ #if defined(DUK_USE_HEAPPTR16) #define DUK_HTHREAD_GET_STRING(thr, idx) ((duk_hstring *) DUK_USE_HEAPPTR_DEC16((thr)->heap->heap_udata, (thr)->strs16[(idx)])) #else #define DUK_HTHREAD_GET_STRING(thr, idx) ((thr)->strs[(idx)]) #endif #endif /* DUK_USE_ROM_STRINGS */ /* values for the state field */ #define DUK_HTHREAD_STATE_INACTIVE 1 /* thread not currently running */ #define DUK_HTHREAD_STATE_RUNNING 2 /* thread currently running (only one at a time) */ #define DUK_HTHREAD_STATE_RESUMED 3 /* thread resumed another thread (active but not running) */ #define DUK_HTHREAD_STATE_YIELDED 4 /* thread has yielded */ #define DUK_HTHREAD_STATE_TERMINATED 5 /* thread has terminated */ /* Executor interrupt default interval when nothing else requires a * smaller value. The default interval must be small enough to allow * for reasonable execution timeout checking but large enough to keep * impact on execution performance low. */ #if defined(DUK_USE_INTERRUPT_COUNTER) #define DUK_HTHREAD_INTCTR_DEFAULT (256L * 1024L) #endif /* * Assert context is valid: non-NULL pointer, fields look sane. * * This is used by public API call entrypoints to catch invalid 'ctx' pointers * as early as possible; invalid 'ctx' pointers cause very odd and difficult to * diagnose behavior so it's worth checking even when the check is not 100%. */ #if defined(DUK_USE_ASSERTIONS) /* Assertions for internals. */ DUK_INTERNAL_DECL void duk_hthread_assert_valid(duk_hthread *thr); #define DUK_HTHREAD_ASSERT_VALID(thr) \ do { \ duk_hthread_assert_valid((thr)); \ } while (0) /* Assertions for public API calls; a bit stronger. */ DUK_INTERNAL_DECL void duk_ctx_assert_valid(duk_hthread *thr); #define DUK_CTX_ASSERT_VALID(thr) \ do { \ duk_ctx_assert_valid((thr)); \ } while (0) #else #define DUK_HTHREAD_ASSERT_VALID(thr) \ do { \ } while (0) #define DUK_CTX_ASSERT_VALID(thr) \ do { \ } while (0) #endif /* Assertions for API call entry specifically. Checks 'ctx' but also may * check internal state (e.g. not in a debugger transport callback). */ #define DUK_ASSERT_API_ENTRY(thr) \ do { \ DUK_CTX_ASSERT_VALID((thr)); \ DUK_ASSERT((thr)->heap != NULL); \ DUK_ASSERT((thr)->heap->dbg_calling_transport == 0); \ } while (0) /* * Assertion helpers. */ #define DUK_ASSERT_STRIDX_VALID(val) DUK_ASSERT((duk_uint_t) (val) < DUK_HEAP_NUM_STRINGS) #define DUK_ASSERT_BIDX_VALID(val) DUK_ASSERT((duk_uint_t) (val) < DUK_NUM_BUILTINS) /* * Misc */ /* Fast access to 'this' binding. Assumes there's a call in progress. */ #define DUK_HTHREAD_THIS_PTR(thr) \ (DUK_ASSERT_EXPR((thr) != NULL), DUK_ASSERT_EXPR((thr)->valstack_bottom > (thr)->valstack), (thr)->valstack_bottom - 1) /* * Struct defines */ /* Fields are ordered for alignment/packing. */ struct duk_activation { duk_tval tv_func; /* borrowed: full duk_tval for function being executed; for lightfuncs */ duk_hobject *func; /* borrowed: function being executed; for bound function calls, this is the final, real function, NULL for lightfuncs */ duk_activation *parent; /* previous (parent) activation (or NULL if none) */ duk_hobject *var_env; /* current variable environment (may be NULL if delayed) */ duk_hobject *lex_env; /* current lexical environment (may be NULL if delayed) */ duk_catcher *cat; /* current catcher (or NULL) */ #if defined(DUK_USE_NONSTD_FUNC_CALLER_PROPERTY) /* Previous value of 'func' caller, restored when unwound. Only in use * when 'func' is non-strict. */ duk_hobject *prev_caller; #endif duk_instr_t *curr_pc; /* next instruction to execute (points to 'func' bytecode, stable pointer), NULL for native calls */ /* bottom_byteoff and retval_byteoff are only used for book-keeping * of ECMAScript-initiated calls, to allow returning to an ECMAScript * function properly. */ /* Bottom of valstack for this activation, used to reset * valstack_bottom on return; offset is absolute. There's * no need to track 'top' because native call handling deals * with that using locals, and for ECMAScript returns 'nregs' * indicates the necessary top. */ duk_size_t bottom_byteoff; /* Return value when returning to this activation (points to caller * reg, not callee reg); offset is absolute (only set if activation is * not topmost). * * Note: bottom_byteoff is always set, while retval_byteoff is only * applicable for activations below the topmost one. Currently * retval_byteoff for the topmost activation is considered garbage * (and it not initialized on entry or cleared on return; may contain * previous or garbage values). */ duk_size_t retval_byteoff; /* Current 'this' binding is the value just below bottom. * Previously, 'this' binding was handled with an index to the * (calling) valstack. This works for everything except tail * calls, which must not "accumulate" valstack temps. */ /* Value stack reserve (valstack_end) byte offset to be restored * when returning to this activation. Only used by the bytecode * executor. */ duk_size_t reserve_byteoff; #if defined(DUK_USE_DEBUGGER_SUPPORT) duk_uint32_t prev_line; /* needed for stepping */ #endif duk_small_uint_t flags; }; struct duk_catcher { duk_catcher *parent; /* previous (parent) catcher (or NULL if none) */ duk_hstring *h_varname; /* borrowed reference to catch variable name (or NULL if none) */ /* (reference is valid as long activation exists) */ duk_instr_t *pc_base; /* resume execution from pc_base or pc_base+1 (points to 'func' bytecode, stable pointer) */ duk_size_t idx_base; /* idx_base and idx_base+1 get completion value and type */ duk_uint32_t flags; /* type and control flags, label number */ /* XXX: could pack 'flags' and 'idx_base' to same value in practice, * on 32-bit targets this would make duk_catcher 16 bytes. */ }; struct duk_hthread { /* Shared object part */ duk_hobject obj; /* Pointer to bytecode executor's 'curr_pc' variable. Used to copy * the current PC back into the topmost activation when activation * state is about to change (or "syncing" is otherwise needed). This * is rather awkward but important for performance, see execution.rst. */ duk_instr_t **ptr_curr_pc; /* Backpointers. */ duk_heap *heap; /* Current strictness flag: affects API calls. */ duk_uint8_t strict; /* Thread state. */ duk_uint8_t state; duk_uint8_t unused1; duk_uint8_t unused2; /* XXX: Valstack and callstack are currently assumed to have non-NULL * pointers. Relaxing this would not lead to big benefits (except * perhaps for terminated threads). */ /* Value stack: these are expressed as pointers for faster stack * manipulation. [valstack,valstack_top[ is GC-reachable, * [valstack_top,valstack_alloc_end[ is not GC-reachable but kept * initialized as 'undefined'. [valstack,valstack_end[ is the * guaranteed/reserved space and the valstack cannot be resized to * a smaller size. [valstack_end,valstack_alloc_end[ is currently * allocated slack that can be used to grow the current guaranteed * space but may be shrunk away without notice. * * * <----------------------- guaranteed ---> * <---- slack ---> * <--- frame ---> * .-------------+=============+----------+--------------. * |xxxxxxxxxxxxx|yyyyyyyyyyyyy|uuuuuuuuuu|uuuuuuuuuuuuuu| * `-------------+=============+----------+--------------' * * ^ ^ ^ ^ ^ * | | | | | * valstack bottom top end alloc_end * * xxx = arbitrary values, below current frame * yyy = arbitrary values, inside current frame * uuu = outside active value stack, initialized to 'undefined' */ duk_tval *valstack; /* start of valstack allocation */ duk_tval *valstack_end; /* end of valstack reservation/guarantee (exclusive) */ duk_tval *valstack_alloc_end; /* end of valstack allocation */ duk_tval *valstack_bottom; /* bottom of current frame */ duk_tval *valstack_top; /* top of current frame (exclusive) */ /* Call stack, represented as a linked list starting from the current * activation (or NULL if nothing is active). */ duk_activation *callstack_curr; /* current activation (or NULL if none) */ duk_size_t callstack_top; /* number of activation records in callstack (0 if none) */ duk_size_t callstack_preventcount; /* number of activation records in callstack preventing a yield */ /* Yield/resume book-keeping. */ duk_hthread *resumer; /* who resumed us (if any) */ /* Current compiler state (if any), used for augmenting SyntaxErrors. */ duk_compiler_ctx *compile_ctx; #if defined(DUK_USE_INTERRUPT_COUNTER) /* Interrupt counter for triggering a slow path check for execution * timeout, debugger interaction such as breakpoints, etc. The value * is valid for the current running thread, and both the init and * counter values are copied whenever a thread switch occurs. It's * important for the counter to be conveniently accessible for the * bytecode executor inner loop for performance reasons. */ duk_int_t interrupt_counter; /* countdown state */ duk_int_t interrupt_init; /* start value for current countdown */ #endif /* Builtin-objects; may or may not be shared with other threads, * threads existing in different "compartments" will have different * built-ins. Must be stored on a per-thread basis because there * is no intermediate structure for a thread group / compartment. * This takes quite a lot of space, currently 43x4 = 172 bytes on * 32-bit platforms. * * In some cases the builtins array could be ROM based, but it's * sometimes edited (e.g. for sandboxing) so it's better to keep * this array in RAM. */ duk_hobject *builtins[DUK_NUM_BUILTINS]; /* Convenience copies from heap/vm for faster access. */ #if defined(DUK_USE_ROM_STRINGS) /* No field needed when strings are in ROM. */ #else #if defined(DUK_USE_HEAPPTR16) duk_uint16_t *strs16; #else duk_hstring **strs; #endif #endif }; /* * Prototypes */ DUK_INTERNAL_DECL void duk_hthread_copy_builtin_objects(duk_hthread *thr_from, duk_hthread *thr_to); DUK_INTERNAL_DECL void duk_hthread_create_builtin_objects(duk_hthread *thr); DUK_INTERNAL_DECL duk_bool_t duk_hthread_init_stacks(duk_heap *heap, duk_hthread *thr); DUK_INTERNAL_DECL void duk_hthread_terminate(duk_hthread *thr); DUK_INTERNAL_DECL duk_activation *duk_hthread_activation_alloc(duk_hthread *thr); DUK_INTERNAL_DECL void duk_hthread_activation_free(duk_hthread *thr, duk_activation *act); DUK_INTERNAL_DECL void duk_hthread_activation_unwind_norz(duk_hthread *thr); DUK_INTERNAL_DECL void duk_hthread_activation_unwind_reuse_norz(duk_hthread *thr); DUK_INTERNAL_DECL duk_activation *duk_hthread_get_activation_for_level(duk_hthread *thr, duk_int_t level); DUK_INTERNAL_DECL duk_catcher *duk_hthread_catcher_alloc(duk_hthread *thr); DUK_INTERNAL_DECL void duk_hthread_catcher_free(duk_hthread *thr, duk_catcher *cat); DUK_INTERNAL_DECL void duk_hthread_catcher_unwind_norz(duk_hthread *thr, duk_activation *act); DUK_INTERNAL_DECL void duk_hthread_catcher_unwind_nolexenv_norz(duk_hthread *thr, duk_activation *act); #if defined(DUK_USE_FINALIZER_TORTURE) DUK_INTERNAL_DECL void duk_hthread_valstack_torture_realloc(duk_hthread *thr); #endif DUK_INTERNAL_DECL void *duk_hthread_get_valstack_ptr(duk_heap *heap, void *ud); /* indirect allocs */ #if defined(DUK_USE_DEBUGGER_SUPPORT) DUK_INTERNAL_DECL duk_uint_fast32_t duk_hthread_get_act_curr_pc(duk_hthread *thr, duk_activation *act); #endif DUK_INTERNAL_DECL duk_uint_fast32_t duk_hthread_get_act_prev_pc(duk_hthread *thr, duk_activation *act); DUK_INTERNAL_DECL void duk_hthread_sync_currpc(duk_hthread *thr); DUK_INTERNAL_DECL void duk_hthread_sync_and_null_currpc(duk_hthread *thr); #endif /* DUK_HTHREAD_H_INCLUDED */ /* #include duk_harray.h */ #line 1 "duk_harray.h" /* * Array object representation, used for actual Array instances. * * All objects with the exotic array behavior (which must coincide with having * internal class array) MUST be duk_harrays. No other object can be a * duk_harray. However, duk_harrays may not always have an array part. */ #if !defined(DUK_HARRAY_H_INCLUDED) #define DUK_HARRAY_H_INCLUDED #if defined(DUK_USE_ASSERTIONS) DUK_INTERNAL_DECL void duk_harray_assert_valid(duk_harray *h); #define DUK_HARRAY_ASSERT_VALID(h) \ do { \ duk_harray_assert_valid((h)); \ } while (0) #else #define DUK_HARRAY_ASSERT_VALID(h) \ do { \ } while (0) #endif #define DUK_HARRAY_LENGTH_WRITABLE(h) (!(h)->length_nonwritable) #define DUK_HARRAY_LENGTH_NONWRITABLE(h) ((h)->length_nonwritable) #define DUK_HARRAY_SET_LENGTH_WRITABLE(h) \ do { \ (h)->length_nonwritable = 0; \ } while (0) #define DUK_HARRAY_SET_LENGTH_NONWRITABLE(h) \ do { \ (h)->length_nonwritable = 1; \ } while (0) struct duk_harray { /* Shared object part. */ duk_hobject obj; /* Array .length. * * At present Array .length may be smaller, equal, or even larger * than the allocated underlying array part. Fast path code must * always take this into account carefully. */ duk_uint32_t length; /* Array .length property attributes. The property is always * non-enumerable and non-configurable. It's initially writable * but per Object.defineProperty() rules it can be made non-writable * even if it is non-configurable. Thus we need to track the * writability explicitly. * * XXX: this field to be eliminated and moved into duk_hobject * flags field to save space. */ duk_bool_t length_nonwritable; }; #endif /* DUK_HARRAY_H_INCLUDED */ /* #include duk_henv.h */ #line 1 "duk_henv.h" /* * Environment object representation. */ #if !defined(DUK_HENV_H_INCLUDED) #define DUK_HENV_H_INCLUDED #if defined(DUK_USE_ASSERTIONS) DUK_INTERNAL_DECL void duk_hdecenv_assert_valid(duk_hdecenv *h); DUK_INTERNAL_DECL void duk_hobjenv_assert_valid(duk_hobjenv *h); #define DUK_HDECENV_ASSERT_VALID(h) \ do { \ duk_hdecenv_assert_valid((h)); \ } while (0) #define DUK_HOBJENV_ASSERT_VALID(h) \ do { \ duk_hobjenv_assert_valid((h)); \ } while (0) #else #define DUK_HDECENV_ASSERT_VALID(h) \ do { \ } while (0) #define DUK_HOBJENV_ASSERT_VALID(h) \ do { \ } while (0) #endif struct duk_hdecenv { /* Shared object part. */ duk_hobject obj; /* These control variables provide enough information to access live * variables for a closure that is still open. If thread == NULL, * the record is closed and the identifiers are in the property table. */ duk_hthread *thread; duk_hobject *varmap; duk_size_t regbase_byteoff; }; struct duk_hobjenv { /* Shared object part. */ duk_hobject obj; /* Target object and 'this' binding for object binding. */ duk_hobject *target; /* The 'target' object is used as a this binding in only some object * environments. For example, the global environment does not provide * a this binding, but a with statement does. */ duk_bool_t has_this; }; #endif /* DUK_HENV_H_INCLUDED */ /* #include duk_hbuffer.h */ #line 1 "duk_hbuffer.h" /* * Heap buffer representation. * * Heap allocated user data buffer which is either: * * 1. A fixed size buffer (data follows header statically) * 2. A dynamic size buffer (data pointer follows header) * * The data pointer for a variable size buffer of zero size may be NULL. */ #if !defined(DUK_HBUFFER_H_INCLUDED) #define DUK_HBUFFER_H_INCLUDED /* * Flags * * Fixed buffer: 0 * Dynamic buffer: DUK_HBUFFER_FLAG_DYNAMIC * External buffer: DUK_HBUFFER_FLAG_DYNAMIC | DUK_HBUFFER_FLAG_EXTERNAL */ #define DUK_HBUFFER_FLAG_DYNAMIC DUK_HEAPHDR_USER_FLAG(0) /* buffer is behind a pointer, dynamic or external */ #define DUK_HBUFFER_FLAG_EXTERNAL DUK_HEAPHDR_USER_FLAG(1) /* buffer pointer is to an externally allocated buffer */ #define DUK_HBUFFER_HAS_DYNAMIC(x) DUK_HEAPHDR_CHECK_FLAG_BITS(&(x)->hdr, DUK_HBUFFER_FLAG_DYNAMIC) #define DUK_HBUFFER_HAS_EXTERNAL(x) DUK_HEAPHDR_CHECK_FLAG_BITS(&(x)->hdr, DUK_HBUFFER_FLAG_EXTERNAL) #define DUK_HBUFFER_SET_DYNAMIC(x) DUK_HEAPHDR_SET_FLAG_BITS(&(x)->hdr, DUK_HBUFFER_FLAG_DYNAMIC) #define DUK_HBUFFER_SET_EXTERNAL(x) DUK_HEAPHDR_SET_FLAG_BITS(&(x)->hdr, DUK_HBUFFER_FLAG_EXTERNAL) #define DUK_HBUFFER_CLEAR_DYNAMIC(x) DUK_HEAPHDR_CLEAR_FLAG_BITS(&(x)->hdr, DUK_HBUFFER_FLAG_DYNAMIC) #define DUK_HBUFFER_CLEAR_EXTERNAL(x) DUK_HEAPHDR_CLEAR_FLAG_BITS(&(x)->hdr, DUK_HBUFFER_FLAG_EXTERNAL) /* * Misc defines */ /* Impose a maximum buffer length for now. Restricted artificially to * ensure resize computations or adding a heap header length won't * overflow size_t and that a signed duk_int_t can hold a buffer * length. The limit should be synchronized with DUK_HSTRING_MAX_BYTELEN. */ #if defined(DUK_USE_BUFLEN16) #define DUK_HBUFFER_MAX_BYTELEN (0x0000ffffUL) #else /* Intentionally not 0x7fffffffUL; at least JSON code expects that * 2*len + 2 fits in 32 bits. */ #define DUK_HBUFFER_MAX_BYTELEN (0x7ffffffeUL) #endif /* * Field access */ #if defined(DUK_USE_BUFLEN16) /* size stored in duk_heaphdr unused flag bits */ #define DUK_HBUFFER_GET_SIZE(x) ((x)->hdr.h_flags >> 16) #define DUK_HBUFFER_SET_SIZE(x, v) \ do { \ duk_size_t duk__v; \ duk__v = (v); \ DUK_ASSERT(duk__v <= 0xffffUL); \ (x)->hdr.h_flags = ((x)->hdr.h_flags & 0x0000ffffUL) | (((duk_uint32_t) duk__v) << 16); \ } while (0) #define DUK_HBUFFER_ADD_SIZE(x, dv) \ do { \ (x)->hdr.h_flags += ((dv) << 16); \ } while (0) #define DUK_HBUFFER_SUB_SIZE(x, dv) \ do { \ (x)->hdr.h_flags -= ((dv) << 16); \ } while (0) #else #define DUK_HBUFFER_GET_SIZE(x) (((duk_hbuffer *) (x))->size) #define DUK_HBUFFER_SET_SIZE(x, v) \ do { \ ((duk_hbuffer *) (x))->size = (v); \ } while (0) #define DUK_HBUFFER_ADD_SIZE(x, dv) \ do { \ (x)->size += (dv); \ } while (0) #define DUK_HBUFFER_SUB_SIZE(x, dv) \ do { \ (x)->size -= (dv); \ } while (0) #endif #define DUK_HBUFFER_FIXED_GET_SIZE(x) DUK_HBUFFER_GET_SIZE((duk_hbuffer *) (x)) #define DUK_HBUFFER_FIXED_SET_SIZE(x, v) DUK_HBUFFER_SET_SIZE((duk_hbuffer *) (x)) #define DUK_HBUFFER_DYNAMIC_GET_SIZE(x) DUK_HBUFFER_GET_SIZE((duk_hbuffer *) (x)) #define DUK_HBUFFER_DYNAMIC_SET_SIZE(x, v) DUK_HBUFFER_SET_SIZE((duk_hbuffer *) (x), (v)) #define DUK_HBUFFER_DYNAMIC_ADD_SIZE(x, dv) DUK_HBUFFER_ADD_SIZE((duk_hbuffer *) (x), (dv)) #define DUK_HBUFFER_DYNAMIC_SUB_SIZE(x, dv) DUK_HBUFFER_SUB_SIZE((duk_hbuffer *) (x), (dv)) #define DUK_HBUFFER_EXTERNAL_GET_SIZE(x) DUK_HBUFFER_GET_SIZE((duk_hbuffer *) (x)) #define DUK_HBUFFER_EXTERNAL_SET_SIZE(x, v) DUK_HBUFFER_SET_SIZE((duk_hbuffer *) (x), (v)) #define DUK_HBUFFER_FIXED_GET_DATA_PTR(heap, x) ((duk_uint8_t *) (((duk_hbuffer_fixed *) (void *) (x)) + 1)) #if defined(DUK_USE_HEAPPTR16) #define DUK_HBUFFER_DYNAMIC_GET_DATA_PTR(heap, x) \ ((void *) DUK_USE_HEAPPTR_DEC16((heap)->heap_udata, ((duk_heaphdr *) (x))->h_extra16)) #define DUK_HBUFFER_DYNAMIC_SET_DATA_PTR(heap, x, v) \ do { \ ((duk_heaphdr *) (x))->h_extra16 = DUK_USE_HEAPPTR_ENC16((heap)->heap_udata, (void *) (v)); \ } while (0) #define DUK_HBUFFER_DYNAMIC_SET_DATA_PTR_NULL(heap, x) \ do { \ ((duk_heaphdr *) (x))->h_extra16 = 0; /* assume 0 <=> NULL */ \ } while (0) #else #define DUK_HBUFFER_DYNAMIC_GET_DATA_PTR(heap, x) ((x)->curr_alloc) #define DUK_HBUFFER_DYNAMIC_SET_DATA_PTR(heap, x, v) \ do { \ (x)->curr_alloc = (void *) (v); \ } while (0) #define DUK_HBUFFER_DYNAMIC_SET_DATA_PTR_NULL(heap, x) \ do { \ (x)->curr_alloc = (void *) NULL; \ } while (0) #endif /* No pointer compression because pointer is potentially outside of * Duktape heap. */ #if defined(DUK_USE_HEAPPTR16) #define DUK_HBUFFER_EXTERNAL_GET_DATA_PTR(heap, x) ((void *) (x)->curr_alloc) #define DUK_HBUFFER_EXTERNAL_SET_DATA_PTR(heap, x, v) \ do { \ (x)->curr_alloc = (void *) (v); \ } while (0) #define DUK_HBUFFER_EXTERNAL_SET_DATA_PTR_NULL(heap, x) \ do { \ (x)->curr_alloc = (void *) NULL; \ } while (0) #else #define DUK_HBUFFER_EXTERNAL_GET_DATA_PTR(heap, x) ((void *) (x)->curr_alloc) #define DUK_HBUFFER_EXTERNAL_SET_DATA_PTR(heap, x, v) \ do { \ (x)->curr_alloc = (void *) (v); \ } while (0) #define DUK_HBUFFER_EXTERNAL_SET_DATA_PTR_NULL(heap, x) \ do { \ (x)->curr_alloc = (void *) NULL; \ } while (0) #endif /* Get a pointer to the current buffer contents (matching current allocation * size). May be NULL for zero size dynamic/external buffer. */ #if defined(DUK_USE_HEAPPTR16) #define DUK_HBUFFER_GET_DATA_PTR(heap, x) \ (DUK_HBUFFER_HAS_DYNAMIC((x)) ? \ (DUK_HBUFFER_HAS_EXTERNAL((x)) ? DUK_HBUFFER_EXTERNAL_GET_DATA_PTR((heap), (duk_hbuffer_external *) (x)) : \ DUK_HBUFFER_DYNAMIC_GET_DATA_PTR((heap), (duk_hbuffer_dynamic *) (x))) : \ DUK_HBUFFER_FIXED_GET_DATA_PTR((heap), (duk_hbuffer_fixed *) (void *) (x))) #else /* Without heap pointer compression duk_hbuffer_dynamic and duk_hbuffer_external * have the same layout so checking for fixed vs. dynamic (or external) is enough. */ #define DUK_HBUFFER_GET_DATA_PTR(heap, x) \ (DUK_HBUFFER_HAS_DYNAMIC((x)) ? DUK_HBUFFER_DYNAMIC_GET_DATA_PTR((heap), (duk_hbuffer_dynamic *) (x)) : \ DUK_HBUFFER_FIXED_GET_DATA_PTR((heap), (duk_hbuffer_fixed *) (void *) (x))) #endif /* Validity assert. */ #if defined(DUK_USE_ASSERTIONS) DUK_INTERNAL_DECL void duk_hbuffer_assert_valid(duk_hbuffer *h); #define DUK_HBUFFER_ASSERT_VALID(h) \ do { \ duk_hbuffer_assert_valid((h)); \ } while (0) #else #define DUK_HBUFFER_ASSERT_VALID(h) \ do { \ } while (0) #endif /* * Structs */ /* Shared prefix for all buffer types. */ struct duk_hbuffer { duk_heaphdr hdr; /* It's not strictly necessary to track the current size, but * it is useful for writing robust native code. */ /* Current size. */ #if defined(DUK_USE_BUFLEN16) /* Stored in duk_heaphdr unused flags. */ #else duk_size_t size; #endif /* * Data following the header depends on the DUK_HBUFFER_FLAG_DYNAMIC * flag. * * If the flag is clear (the buffer is a fixed size one), the buffer * data follows the header directly, consisting of 'size' bytes. * * If the flag is set, the actual buffer is allocated separately, and * a few control fields follow the header. Specifically: * * - a "void *" pointing to the current allocation * - a duk_size_t indicating the full allocated size (always >= 'size') * * If DUK_HBUFFER_FLAG_EXTERNAL is set, the buffer has been allocated * by user code, so that Duktape won't be able to resize it and won't * free it. This allows buffers to point to e.g. an externally * allocated structure such as a frame buffer. * * Unlike strings, no terminator byte (NUL) is guaranteed after the * data. This would be convenient, but would pad aligned user buffers * unnecessarily upwards in size. For instance, if user code requested * a 64-byte dynamic buffer, 65 bytes would actually be allocated which * would then potentially round upwards to perhaps 68 or 72 bytes. */ }; /* Fixed buffer; data follows struct, with proper alignment guaranteed by * struct size. */ #if (DUK_USE_ALIGN_BY == 8) && defined(DUK_USE_PACK_MSVC_PRAGMA) #pragma pack(push, 8) #endif struct duk_hbuffer_fixed { /* A union is used here as a portable struct size / alignment trick: * by adding a 32-bit or a 64-bit (unused) union member, the size of * the struct is effectively forced to be a multiple of 4 or 8 bytes * (respectively) without increasing the size of the struct unless * necessary. */ union { struct { duk_heaphdr hdr; #if defined(DUK_USE_BUFLEN16) /* Stored in duk_heaphdr unused flags. */ #else duk_size_t size; #endif } s; #if (DUK_USE_ALIGN_BY == 4) duk_uint32_t dummy_for_align4; #elif (DUK_USE_ALIGN_BY == 8) duk_double_t dummy_for_align8_1; #if defined(DUK_USE_64BIT_OPS) duk_uint64_t dummy_for_align8_2; #endif #elif (DUK_USE_ALIGN_BY == 1) /* no extra padding */ #else #error invalid DUK_USE_ALIGN_BY #endif } u; /* * Data follows the struct header. The struct size is padded by the * compiler based on the struct members. This guarantees that the * buffer data will be aligned-by-4 but not necessarily aligned-by-8. * * On platforms where alignment does not matter, the struct padding * could be removed (if there is any). On platforms where alignment * by 8 is required, the struct size must be forced to be a multiple * of 8 by some means. Without it, some user code may break, and also * Duktape itself breaks (e.g. the compiler stores duk_tvals in a * dynamic buffer). */ } #if (DUK_USE_ALIGN_BY == 8) && defined(DUK_USE_PACK_GCC_ATTR) __attribute__((aligned(8))) #elif (DUK_USE_ALIGN_BY == 8) && defined(DUK_USE_PACK_CLANG_ATTR) __attribute__((aligned(8))) #endif ; #if (DUK_USE_ALIGN_BY == 8) && defined(DUK_USE_PACK_MSVC_PRAGMA) #pragma pack(pop) #endif /* Dynamic buffer with 'curr_alloc' pointing to a dynamic area allocated using * heap allocation primitives. Also used for external buffers when low memory * options are not used. */ struct duk_hbuffer_dynamic { duk_heaphdr hdr; #if defined(DUK_USE_BUFLEN16) /* Stored in duk_heaphdr unused flags. */ #else duk_size_t size; #endif #if defined(DUK_USE_HEAPPTR16) /* Stored in duk_heaphdr h_extra16. */ #else void *curr_alloc; /* may be NULL if alloc_size == 0 */ #endif /* * Allocation size for 'curr_alloc' is alloc_size. There is no * automatic NUL terminator for buffers (see above for rationale). * * 'curr_alloc' is explicitly allocated with heap allocation * primitives and will thus always have alignment suitable for * e.g. duk_tval and an IEEE double. */ }; /* External buffer with 'curr_alloc' managed by user code and pointing to an * arbitrary address. When heap pointer compression is not used, this struct * has the same layout as duk_hbuffer_dynamic. */ struct duk_hbuffer_external { duk_heaphdr hdr; #if defined(DUK_USE_BUFLEN16) /* Stored in duk_heaphdr unused flags. */ #else duk_size_t size; #endif /* Cannot be compressed as a heap pointer because may point to * an arbitrary address. */ void *curr_alloc; /* may be NULL if alloc_size == 0 */ }; /* * Prototypes */ DUK_INTERNAL_DECL duk_hbuffer *duk_hbuffer_alloc(duk_heap *heap, duk_size_t size, duk_small_uint_t flags, void **out_bufdata); DUK_INTERNAL_DECL void *duk_hbuffer_get_dynalloc_ptr(duk_heap *heap, void *ud); /* indirect allocs */ /* dynamic buffer ops */ DUK_INTERNAL_DECL void duk_hbuffer_resize(duk_hthread *thr, duk_hbuffer_dynamic *buf, duk_size_t new_size); DUK_INTERNAL_DECL void duk_hbuffer_reset(duk_hthread *thr, duk_hbuffer_dynamic *buf); #endif /* DUK_HBUFFER_H_INCLUDED */ /* #include duk_hproxy.h */ #line 1 "duk_hproxy.h" /* * Proxy object representation. */ #if !defined(DUK_HPROXY_H_INCLUDED) #define DUK_HPROXY_H_INCLUDED #if defined(DUK_USE_ASSERTIONS) DUK_INTERNAL_DECL void duk_hproxy_assert_valid(duk_hproxy *h); #define DUK_HPROXY_ASSERT_VALID(h) \ do { \ duk_hproxy_assert_valid((h)); \ } while (0) #else #define DUK_HPROXY_ASSERT_VALID(h) \ do { \ } while (0) #endif struct duk_hproxy { /* Shared object part. */ duk_hobject obj; /* Proxy target object. */ duk_hobject *target; /* Proxy handlers (traps). */ duk_hobject *handler; }; #endif /* DUK_HPROXY_H_INCLUDED */ /* #include duk_heap.h */ #line 1 "duk_heap.h" /* * Heap structure. * * Heap contains allocated heap objects, interned strings, and built-in * strings for one or more threads. */ #if !defined(DUK_HEAP_H_INCLUDED) #define DUK_HEAP_H_INCLUDED /* alloc function typedefs in duktape.h */ /* * Heap flags */ #define DUK_HEAP_FLAG_MARKANDSWEEP_RECLIMIT_REACHED \ (1U << 0) /* mark-and-sweep marking reached a recursion limit and must use multi-pass marking */ #define DUK_HEAP_FLAG_INTERRUPT_RUNNING (1U << 1) /* executor interrupt running (used to avoid nested interrupts) */ #define DUK_HEAP_FLAG_FINALIZER_NORESCUE (1U << 2) /* heap destruction ongoing, finalizer rescue no longer possible */ #define DUK_HEAP_FLAG_DEBUGGER_PAUSED (1U << 3) /* debugger is paused: talk with debug client until step/resume */ #define DUK__HEAP_HAS_FLAGS(heap, bits) ((heap)->flags & (bits)) #define DUK__HEAP_SET_FLAGS(heap, bits) \ do { \ (heap)->flags |= (bits); \ } while (0) #define DUK__HEAP_CLEAR_FLAGS(heap, bits) \ do { \ (heap)->flags &= ~(bits); \ } while (0) #define DUK_HEAP_HAS_MARKANDSWEEP_RECLIMIT_REACHED(heap) DUK__HEAP_HAS_FLAGS((heap), DUK_HEAP_FLAG_MARKANDSWEEP_RECLIMIT_REACHED) #define DUK_HEAP_HAS_INTERRUPT_RUNNING(heap) DUK__HEAP_HAS_FLAGS((heap), DUK_HEAP_FLAG_INTERRUPT_RUNNING) #define DUK_HEAP_HAS_FINALIZER_NORESCUE(heap) DUK__HEAP_HAS_FLAGS((heap), DUK_HEAP_FLAG_FINALIZER_NORESCUE) #define DUK_HEAP_HAS_DEBUGGER_PAUSED(heap) DUK__HEAP_HAS_FLAGS((heap), DUK_HEAP_FLAG_DEBUGGER_PAUSED) #define DUK_HEAP_SET_MARKANDSWEEP_RECLIMIT_REACHED(heap) DUK__HEAP_SET_FLAGS((heap), DUK_HEAP_FLAG_MARKANDSWEEP_RECLIMIT_REACHED) #define DUK_HEAP_SET_INTERRUPT_RUNNING(heap) DUK__HEAP_SET_FLAGS((heap), DUK_HEAP_FLAG_INTERRUPT_RUNNING) #define DUK_HEAP_SET_FINALIZER_NORESCUE(heap) DUK__HEAP_SET_FLAGS((heap), DUK_HEAP_FLAG_FINALIZER_NORESCUE) #define DUK_HEAP_SET_DEBUGGER_PAUSED(heap) DUK__HEAP_SET_FLAGS((heap), DUK_HEAP_FLAG_DEBUGGER_PAUSED) #define DUK_HEAP_CLEAR_MARKANDSWEEP_RECLIMIT_REACHED(heap) \ DUK__HEAP_CLEAR_FLAGS((heap), DUK_HEAP_FLAG_MARKANDSWEEP_RECLIMIT_REACHED) #define DUK_HEAP_CLEAR_INTERRUPT_RUNNING(heap) DUK__HEAP_CLEAR_FLAGS((heap), DUK_HEAP_FLAG_INTERRUPT_RUNNING) #define DUK_HEAP_CLEAR_FINALIZER_NORESCUE(heap) DUK__HEAP_CLEAR_FLAGS((heap), DUK_HEAP_FLAG_FINALIZER_NORESCUE) #define DUK_HEAP_CLEAR_DEBUGGER_PAUSED(heap) DUK__HEAP_CLEAR_FLAGS((heap), DUK_HEAP_FLAG_DEBUGGER_PAUSED) /* * Longjmp types, also double as identifying continuation type for a rethrow (in 'finally') */ #define DUK_LJ_TYPE_UNKNOWN 0 /* unused */ #define DUK_LJ_TYPE_THROW 1 /* value1 -> error object */ #define DUK_LJ_TYPE_YIELD 2 /* value1 -> yield value, iserror -> error / normal */ #define DUK_LJ_TYPE_RESUME 3 /* value1 -> resume value, value2 -> resumee thread, iserror -> error/normal */ #define DUK_LJ_TYPE_BREAK 4 /* value1 -> label number, pseudo-type to indicate a break continuation (for ENDFIN) */ #define DUK_LJ_TYPE_CONTINUE 5 /* value1 -> label number, pseudo-type to indicate a continue continuation (for ENDFIN) */ #define DUK_LJ_TYPE_RETURN 6 /* value1 -> return value, pseudo-type to indicate a return continuation (for ENDFIN) */ #define DUK_LJ_TYPE_NORMAL 7 /* no value, pseudo-type to indicate a normal continuation (for ENDFIN) */ /* * Mark-and-sweep flags * * These are separate from heap level flags now but could be merged. * The heap structure only contains a 'base mark-and-sweep flags' * field and the GC caller can impose further flags. */ /* Emergency mark-and-sweep: try extra hard, even at the cost of * performance. */ #define DUK_MS_FLAG_EMERGENCY (1U << 0) /* Postpone rescue decisions for reachable objects with FINALIZED set. * Used during finalize_list processing to avoid incorrect rescue * decisions due to finalize_list being a reachability root. */ #define DUK_MS_FLAG_POSTPONE_RESCUE (1U << 1) /* Don't compact objects; needed during object property table resize * to prevent a recursive resize. It would suffice to protect only the * current object being resized, but this is not yet implemented. */ #define DUK_MS_FLAG_NO_OBJECT_COMPACTION (1U << 2) /* * Thread switching * * To switch heap->curr_thread, use the macro below so that interrupt counters * get updated correctly. The macro allows a NULL target thread because that * happens e.g. in call handling. */ #if defined(DUK_USE_INTERRUPT_COUNTER) #define DUK_HEAP_SWITCH_THREAD(heap, newthr) duk_heap_switch_thread((heap), (newthr)) #else #define DUK_HEAP_SWITCH_THREAD(heap, newthr) \ do { \ (heap)->curr_thread = (newthr); \ } while (0) #endif /* * Stats */ #if defined(DUK_USE_DEBUG) #define DUK_STATS_INC(heap, fieldname) \ do { \ (heap)->fieldname += 1; \ } while (0) #else #define DUK_STATS_INC(heap, fieldname) \ do { \ } while (0) #endif /* * Other heap related defines */ /* Mark-and-sweep interval is relative to combined count of objects and * strings kept in the heap during the latest mark-and-sweep pass. * Fixed point .8 multiplier and .0 adder. Trigger count (interval) is * decreased by each (re)allocation attempt (regardless of size), and each * refzero processed object. * * 'SKIP' indicates how many (re)allocations to wait until a retry if * GC is skipped because there is no thread do it with yet (happens * only during init phases). */ #if defined(DUK_USE_REFERENCE_COUNTING) #define DUK_HEAP_MARK_AND_SWEEP_TRIGGER_MULT 12800L /* 50x heap size */ #define DUK_HEAP_MARK_AND_SWEEP_TRIGGER_ADD 1024L #define DUK_HEAP_MARK_AND_SWEEP_TRIGGER_SKIP 256L #else #define DUK_HEAP_MARK_AND_SWEEP_TRIGGER_MULT 256L /* 1x heap size */ #define DUK_HEAP_MARK_AND_SWEEP_TRIGGER_ADD 1024L #define DUK_HEAP_MARK_AND_SWEEP_TRIGGER_SKIP 256L #endif /* GC torture. */ #if defined(DUK_USE_GC_TORTURE) #define DUK_GC_TORTURE(heap) \ do { \ duk_heap_mark_and_sweep((heap), 0); \ } while (0) #else #define DUK_GC_TORTURE(heap) \ do { \ } while (0) #endif /* Stringcache is used for speeding up char-offset-to-byte-offset * translations for non-ASCII strings. */ #define DUK_HEAP_STRCACHE_SIZE 4 #define DUK_HEAP_STRINGCACHE_NOCACHE_LIMIT 16 /* strings up to the this length are not cached */ /* Some list management macros. */ #define DUK_HEAP_INSERT_INTO_HEAP_ALLOCATED(heap, hdr) duk_heap_insert_into_heap_allocated((heap), (hdr)) #if defined(DUK_USE_REFERENCE_COUNTING) #define DUK_HEAP_REMOVE_FROM_HEAP_ALLOCATED(heap, hdr) duk_heap_remove_from_heap_allocated((heap), (hdr)) #endif #if defined(DUK_USE_FINALIZER_SUPPORT) #define DUK_HEAP_INSERT_INTO_FINALIZE_LIST(heap, hdr) duk_heap_insert_into_finalize_list((heap), (hdr)) #define DUK_HEAP_REMOVE_FROM_FINALIZE_LIST(heap, hdr) duk_heap_remove_from_finalize_list((heap), (hdr)) #endif /* * Built-in strings */ /* heap string indices are autogenerated in duk_strings.h */ #if defined(DUK_USE_ROM_STRINGS) #define DUK_HEAP_GET_STRING(heap, idx) ((duk_hstring *) DUK_LOSE_CONST(duk_rom_strings_stridx[(idx)])) #else /* DUK_USE_ROM_STRINGS */ #if defined(DUK_USE_HEAPPTR16) #define DUK_HEAP_GET_STRING(heap, idx) ((duk_hstring *) DUK_USE_HEAPPTR_DEC16((heap)->heap_udata, (heap)->strs16[(idx)])) #else #define DUK_HEAP_GET_STRING(heap, idx) ((heap)->strs[(idx)]) #endif #endif /* DUK_USE_ROM_STRINGS */ /* * Raw memory calls: relative to heap, but no GC interaction */ #define DUK_ALLOC_RAW(heap, size) ((heap)->alloc_func((heap)->heap_udata, (size))) #define DUK_REALLOC_RAW(heap, ptr, newsize) ((heap)->realloc_func((heap)->heap_udata, (void *) (ptr), (newsize))) #define DUK_FREE_RAW(heap, ptr) ((heap)->free_func((heap)->heap_udata, (void *) (ptr))) /* * Memory calls: relative to heap, GC interaction, but no error throwing. * * XXX: Currently a mark-and-sweep triggered by memory allocation will run * using the heap->heap_thread. This thread is also used for running * mark-and-sweep finalization; this is not ideal because it breaks the * isolation between multiple global environments. * * Notes: * * - DUK_FREE() is required to ignore NULL and any other possible return * value of a zero-sized alloc/realloc (same as ANSI C free()). * * - There is no DUK_REALLOC_ZEROED because we don't assume to know the * old size. Caller must zero the reallocated memory. * * - DUK_REALLOC_INDIRECT() must be used when a mark-and-sweep triggered * by an allocation failure might invalidate the original 'ptr', thus * causing a realloc retry to use an invalid pointer. Example: we're * reallocating the value stack and a finalizer resizes the same value * stack during mark-and-sweep. The indirect variant requests for the * current location of the pointer being reallocated using a callback * right before every realloc attempt; this circuitous approach is used * to avoid strict aliasing issues in a more straightforward indirect * pointer (void **) approach. Note: the pointer in the storage * location is read but is NOT updated; the caller must do that. */ /* callback for indirect reallocs, request for current pointer */ typedef void *(*duk_mem_getptr)(duk_heap *heap, void *ud); #define DUK_ALLOC(heap, size) duk_heap_mem_alloc((heap), (size)) #define DUK_ALLOC_ZEROED(heap, size) duk_heap_mem_alloc_zeroed((heap), (size)) #define DUK_REALLOC(heap, ptr, newsize) duk_heap_mem_realloc((heap), (ptr), (newsize)) #define DUK_REALLOC_INDIRECT(heap, cb, ud, newsize) duk_heap_mem_realloc_indirect((heap), (cb), (ud), (newsize)) #define DUK_FREE(heap, ptr) duk_heap_mem_free((heap), (ptr)) /* * Checked allocation, relative to a thread * * DUK_FREE_CHECKED() doesn't actually throw, but accepts a 'thr' argument * for convenience. */ #define DUK_ALLOC_CHECKED(thr, size) duk_heap_mem_alloc_checked((thr), (size)) #define DUK_ALLOC_CHECKED_ZEROED(thr, size) duk_heap_mem_alloc_checked_zeroed((thr), (size)) #define DUK_FREE_CHECKED(thr, ptr) duk_heap_mem_free((thr)->heap, (ptr)) /* * Memory constants */ #define DUK_HEAP_ALLOC_FAIL_MARKANDSWEEP_LIMIT \ 10 /* Retry allocation after mark-and-sweep for this \ * many times. A single mark-and-sweep round is \ * not guaranteed to free all unreferenced memory \ * because of finalization (in fact, ANY number of \ * rounds is strictly not enough). \ */ #define DUK_HEAP_ALLOC_FAIL_MARKANDSWEEP_EMERGENCY_LIMIT \ 3 /* Starting from this round, use emergency mode \ * for mark-and-sweep. \ */ /* * Debugger support */ /* Maximum number of breakpoints. Only breakpoints that are set are * consulted so increasing this has no performance impact. */ #define DUK_HEAP_MAX_BREAKPOINTS 16 /* Opcode interval for a Date-based status/peek rate limit check. Only * relevant when debugger is attached. Requesting a timestamp may be a * slow operation on some platforms so this shouldn't be too low. On the * other hand a high value makes Duktape react to a pause request slowly. */ #define DUK_HEAP_DBG_RATELIMIT_OPCODES 4000 /* Milliseconds between status notify and transport peeks. */ #define DUK_HEAP_DBG_RATELIMIT_MILLISECS 200 /* Debugger pause flags. */ #define DUK_PAUSE_FLAG_ONE_OPCODE (1U << 0) /* pause when a single opcode has been executed */ #define DUK_PAUSE_FLAG_ONE_OPCODE_ACTIVE (1U << 1) /* one opcode pause actually active; artifact of current implementation */ #define DUK_PAUSE_FLAG_LINE_CHANGE (1U << 2) /* pause when current line number changes */ #define DUK_PAUSE_FLAG_FUNC_ENTRY (1U << 3) /* pause when entering a function */ #define DUK_PAUSE_FLAG_FUNC_EXIT (1U << 4) /* pause when exiting current function */ #define DUK_PAUSE_FLAG_CAUGHT_ERROR (1U << 5) /* pause when about to throw an error that is caught */ #define DUK_PAUSE_FLAG_UNCAUGHT_ERROR (1U << 6) /* pause when about to throw an error that won't be caught */ struct duk_breakpoint { duk_hstring *filename; duk_uint32_t line; }; /* * String cache should ideally be at duk_hthread level, but that would * cause string finalization to slow down relative to the number of * threads; string finalization must check the string cache for "weak" * references to the string being finalized to avoid dead pointers. * * Thus, string caches are now at the heap level now. */ struct duk_strcache_entry { duk_hstring *h; duk_uint32_t bidx; duk_uint32_t cidx; }; /* * Longjmp state, contains the information needed to perform a longjmp. * Longjmp related values are written to value1, value2, and iserror. */ struct duk_ljstate { duk_jmpbuf *jmpbuf_ptr; /* current setjmp() catchpoint */ duk_small_uint_t type; /* longjmp type */ duk_bool_t iserror; /* isError flag for yield */ duk_tval value1; /* 1st related value (type specific) */ duk_tval value2; /* 2nd related value (type specific) */ }; #define DUK_ASSERT_LJSTATE_UNSET(heap) \ do { \ DUK_ASSERT(heap != NULL); \ DUK_ASSERT(heap->lj.type == DUK_LJ_TYPE_UNKNOWN); \ DUK_ASSERT(heap->lj.iserror == 0); \ DUK_ASSERT(DUK_TVAL_IS_UNDEFINED(&heap->lj.value1)); \ DUK_ASSERT(DUK_TVAL_IS_UNDEFINED(&heap->lj.value2)); \ } while (0) #define DUK_ASSERT_LJSTATE_SET(heap) \ do { \ DUK_ASSERT(heap != NULL); \ DUK_ASSERT(heap->lj.type != DUK_LJ_TYPE_UNKNOWN); \ } while (0) /* * Literal intern cache */ struct duk_litcache_entry { const duk_uint8_t *addr; duk_hstring *h; }; /* * Main heap structure */ #if defined(DUK_USE_ASSERTIONS) DUK_INTERNAL_DECL void duk_heap_assert_valid(duk_heap *heap); #define DUK_HEAP_ASSERT_VALID(heap) \ do { \ duk_heap_assert_valid((heap)); \ } while (0) #else #define DUK_HEAP_ASSERT_VALID(heap) \ do { \ } while (0) #endif struct duk_heap { duk_small_uint_t flags; /* Allocator functions. */ duk_alloc_function alloc_func; duk_realloc_function realloc_func; duk_free_function free_func; /* Heap udata, used for allocator functions but also for other heap * level callbacks like fatal function, pointer compression, etc. */ void *heap_udata; /* Fatal error handling, called e.g. when a longjmp() is needed but * lj.jmpbuf_ptr is NULL. fatal_func must never return; it's not * declared as "noreturn" because doing that for typedefs is a bit * challenging portability-wise. */ duk_fatal_function fatal_func; /* Main list of allocated heap objects. Objects are either here, * in finalize_list waiting for processing, or in refzero_list * temporarily while a DECREF refzero cascade finishes. */ duk_heaphdr *heap_allocated; /* Temporary work list for freeing a cascade of objects when a DECREF * (or DECREF_NORZ) encounters a zero refcount. Using a work list * allows fixed C stack size when refcounts go to zero for a chain of * objects. Outside of DECREF this is always a NULL because DECREF is * processed without side effects (only memory free calls). */ #if defined(DUK_USE_REFERENCE_COUNTING) duk_heaphdr *refzero_list; #endif #if defined(DUK_USE_FINALIZER_SUPPORT) /* Work list for objects to be finalized. */ duk_heaphdr *finalize_list; #if defined(DUK_USE_ASSERTIONS) /* Object whose finalizer is executing right now (no nesting). */ duk_heaphdr *currently_finalizing; #endif #endif /* Freelist for duk_activations and duk_catchers. */ #if defined(DUK_USE_CACHE_ACTIVATION) duk_activation *activation_free; #endif #if defined(DUK_USE_CACHE_CATCHER) duk_catcher *catcher_free; #endif /* Voluntary mark-and-sweep trigger counter. Intentionally signed * because we continue decreasing the value when voluntary GC cannot * run. */ #if defined(DUK_USE_VOLUNTARY_GC) duk_int_t ms_trigger_counter; #endif /* Mark-and-sweep recursion control: too deep recursion causes * multi-pass processing to avoid growing C stack without bound. */ duk_uint_t ms_recursion_depth; /* Mark-and-sweep flags automatically active (used for critical sections). */ duk_small_uint_t ms_base_flags; /* Mark-and-sweep running flag. Prevents re-entry, and also causes * refzero events to be ignored (= objects won't be queued to refzero_list). * * 0: mark-and-sweep not running * 1: mark-and-sweep is running * 2: heap destruction active or debugger active, prevent mark-and-sweep * and refzero processing (but mark-and-sweep not itself running) */ duk_uint_t ms_running; /* Mark-and-sweep prevent count, stacking. Used to avoid M&S side * effects (besides finalizers which are controlled separately) such * as compacting the string table or object property tables. This * is also bumped when ms_running is set to prevent recursive re-entry. * Can also be bumped when mark-and-sweep is not running. */ duk_uint_t ms_prevent_count; /* Finalizer processing prevent count, stacking. Bumped when finalizers * are processed to prevent recursive finalizer processing (first call site * processing finalizers handles all finalizers until the list is empty). * Can also be bumped explicitly to prevent finalizer execution. */ duk_uint_t pf_prevent_count; /* When processing finalize_list, don't actually run finalizers but * queue finalizable objects back to heap_allocated as is. This is * used during heap destruction to deal with finalizers that keep * on creating more finalizable garbage. */ duk_uint_t pf_skip_finalizers; #if defined(DUK_USE_ASSERTIONS) /* Set when we're in a critical path where an error throw would cause * e.g. sandboxing/protected call violations or state corruption. This * is just used for asserts. */ duk_bool_t error_not_allowed; #endif #if defined(DUK_USE_ASSERTIONS) /* Set when heap is still being initialized, helps with writing * some assertions. */ duk_bool_t heap_initializing; #endif /* Marker for detecting internal "double faults", errors thrown when * we're trying to create an error object, see duk_error_throw.c. */ duk_bool_t creating_error; /* Marker for indicating we're calling a user error augmentation * (errCreate/errThrow) function. Errors created/thrown during * such a call are not augmented. */ #if defined(DUK_USE_AUGMENT_ERROR_THROW) || defined(DUK_USE_AUGMENT_ERROR_CREATE) duk_bool_t augmenting_error; #endif /* Longjmp state. */ duk_ljstate lj; /* Heap thread, used internally and for finalization. */ duk_hthread *heap_thread; /* Current running thread. */ duk_hthread *curr_thread; /* Heap level "stash" object (e.g., various reachability roots). */ duk_hobject *heap_object; /* duk_handle_call / duk_handle_safe_call recursion depth limiting */ duk_int_t call_recursion_depth; duk_int_t call_recursion_limit; /* Mix-in value for computing string hashes; should be reasonably unpredictable. */ duk_uint32_t hash_seed; /* Random number state for duk_util_tinyrandom.c. */ #if !defined(DUK_USE_GET_RANDOM_DOUBLE) #if defined(DUK_USE_PREFER_SIZE) || !defined(DUK_USE_64BIT_OPS) duk_uint32_t rnd_state; /* State for Shamir's three-op algorithm */ #else duk_uint64_t rnd_state[2]; /* State for xoroshiro128+ */ #endif #endif /* Counter for unique local symbol creation. */ /* XXX: When 64-bit types are available, it would be more efficient to * use a duk_uint64_t at least for incrementing but maybe also for * string formatting in the Symbol constructor. */ duk_uint32_t sym_counter[2]; /* For manual debugging: instruction count based on executor and * interrupt counter book-keeping. Inspect debug logs to see how * they match up. */ #if defined(DUK_USE_INTERRUPT_COUNTER) && defined(DUK_USE_DEBUG) duk_int_t inst_count_exec; duk_int_t inst_count_interrupt; #endif /* Debugger state. */ #if defined(DUK_USE_DEBUGGER_SUPPORT) /* Callbacks and udata; dbg_read_cb != NULL is used to indicate attached state. */ duk_debug_read_function dbg_read_cb; /* required, NULL implies detached */ duk_debug_write_function dbg_write_cb; /* required */ duk_debug_peek_function dbg_peek_cb; duk_debug_read_flush_function dbg_read_flush_cb; duk_debug_write_flush_function dbg_write_flush_cb; duk_debug_request_function dbg_request_cb; duk_debug_detached_function dbg_detached_cb; void *dbg_udata; /* The following are only relevant when debugger is attached. */ duk_bool_t dbg_processing; /* currently processing messages or breakpoints: don't enter message processing recursively (e.g. no breakpoints when processing debugger eval) */ duk_bool_t dbg_state_dirty; /* resend state next time executor is about to run */ duk_bool_t dbg_force_restart; /* force executor restart to recheck breakpoints; used to handle function returns (see GH-303) */ duk_bool_t dbg_detaching; /* debugger detaching; used to avoid calling detach handler recursively */ duk_small_uint_t dbg_pause_flags; /* flags for automatic pause behavior */ duk_activation *dbg_pause_act; /* activation related to pause behavior (pause on line change, function entry/exit) */ duk_uint32_t dbg_pause_startline; /* starting line number for line change related pause behavior */ duk_breakpoint dbg_breakpoints[DUK_HEAP_MAX_BREAKPOINTS]; /* breakpoints: [0,breakpoint_count[ gc reachable */ duk_small_uint_t dbg_breakpoint_count; duk_breakpoint *dbg_breakpoints_active[DUK_HEAP_MAX_BREAKPOINTS + 1]; /* currently active breakpoints: NULL term, borrowed pointers */ /* XXX: make active breakpoints actual copies instead of pointers? */ /* These are for rate limiting Status notifications and transport peeking. */ duk_uint_t dbg_exec_counter; /* cumulative opcode execution count (overflows are OK) */ duk_uint_t dbg_last_counter; /* value of dbg_exec_counter when we last did a Date-based check */ duk_double_t dbg_last_time; /* time when status/peek was last done (Date-based rate limit) */ /* Used to support single-byte stream lookahead. */ duk_bool_t dbg_have_next_byte; duk_uint8_t dbg_next_byte; #endif /* DUK_USE_DEBUGGER_SUPPORT */ #if defined(DUK_USE_ASSERTIONS) duk_bool_t dbg_calling_transport; /* transport call in progress, calling into Duktape forbidden */ #endif /* String intern table (weak refs). */ #if defined(DUK_USE_STRTAB_PTRCOMP) duk_uint16_t *strtable16; #else duk_hstring **strtable; #endif duk_uint32_t st_mask; /* mask for lookup, st_size - 1 */ duk_uint32_t st_size; /* stringtable size */ #if (DUK_USE_STRTAB_MINSIZE != DUK_USE_STRTAB_MAXSIZE) duk_uint32_t st_count; /* string count for resize load factor checks */ #endif duk_bool_t st_resizing; /* string table is being resized; avoid recursive resize */ /* String access cache (codepoint offset -> byte offset) for fast string * character looping; 'weak' reference which needs special handling in GC. */ duk_strcache_entry strcache[DUK_HEAP_STRCACHE_SIZE]; #if defined(DUK_USE_LITCACHE_SIZE) /* Literal intern cache. When enabled, strings interned as literals * (e.g. duk_push_literal()) will be pinned and cached for the lifetime * of the heap. */ duk_litcache_entry litcache[DUK_USE_LITCACHE_SIZE]; #endif /* Built-in strings. */ #if defined(DUK_USE_ROM_STRINGS) /* No field needed when strings are in ROM. */ #else #if defined(DUK_USE_HEAPPTR16) duk_uint16_t strs16[DUK_HEAP_NUM_STRINGS]; #else duk_hstring *strs[DUK_HEAP_NUM_STRINGS]; #endif #endif /* Stats. */ #if defined(DUK_USE_DEBUG) duk_int_t stats_exec_opcodes; duk_int_t stats_exec_interrupt; duk_int_t stats_exec_throw; duk_int_t stats_call_all; duk_int_t stats_call_tailcall; duk_int_t stats_call_ecmatoecma; duk_int_t stats_safecall_all; duk_int_t stats_safecall_nothrow; duk_int_t stats_safecall_throw; duk_int_t stats_ms_try_count; duk_int_t stats_ms_skip_count; duk_int_t stats_ms_emergency_count; duk_int_t stats_strtab_intern_hit; duk_int_t stats_strtab_intern_miss; duk_int_t stats_strtab_resize_check; duk_int_t stats_strtab_resize_grow; duk_int_t stats_strtab_resize_shrink; duk_int_t stats_strtab_litcache_hit; duk_int_t stats_strtab_litcache_miss; duk_int_t stats_strtab_litcache_pin; duk_int_t stats_object_realloc_props; duk_int_t stats_object_abandon_array; duk_int_t stats_getownpropdesc_count; duk_int_t stats_getownpropdesc_hit; duk_int_t stats_getownpropdesc_miss; duk_int_t stats_getpropdesc_count; duk_int_t stats_getpropdesc_hit; duk_int_t stats_getpropdesc_miss; duk_int_t stats_getprop_all; duk_int_t stats_getprop_arrayidx; duk_int_t stats_getprop_bufobjidx; duk_int_t stats_getprop_bufferidx; duk_int_t stats_getprop_bufferlen; duk_int_t stats_getprop_stringidx; duk_int_t stats_getprop_stringlen; duk_int_t stats_getprop_proxy; duk_int_t stats_getprop_arguments; duk_int_t stats_putprop_all; duk_int_t stats_putprop_arrayidx; duk_int_t stats_putprop_bufobjidx; duk_int_t stats_putprop_bufferidx; duk_int_t stats_putprop_proxy; duk_int_t stats_getvar_all; duk_int_t stats_putvar_all; duk_int_t stats_envrec_delayedcreate; duk_int_t stats_envrec_create; duk_int_t stats_envrec_newenv; duk_int_t stats_envrec_oldenv; duk_int_t stats_envrec_pushclosure; #endif }; /* * Prototypes */ DUK_INTERNAL_DECL duk_heap *duk_heap_alloc(duk_alloc_function alloc_func, duk_realloc_function realloc_func, duk_free_function free_func, void *heap_udata, duk_fatal_function fatal_func); DUK_INTERNAL_DECL void duk_heap_free(duk_heap *heap); DUK_INTERNAL_DECL void duk_free_hobject(duk_heap *heap, duk_hobject *h); DUK_INTERNAL_DECL void duk_free_hbuffer(duk_heap *heap, duk_hbuffer *h); DUK_INTERNAL_DECL void duk_free_hstring(duk_heap *heap, duk_hstring *h); DUK_INTERNAL_DECL void duk_heap_free_heaphdr_raw(duk_heap *heap, duk_heaphdr *hdr); DUK_INTERNAL_DECL void duk_heap_insert_into_heap_allocated(duk_heap *heap, duk_heaphdr *hdr); #if defined(DUK_USE_REFERENCE_COUNTING) DUK_INTERNAL_DECL void duk_heap_remove_from_heap_allocated(duk_heap *heap, duk_heaphdr *hdr); #endif #if defined(DUK_USE_FINALIZER_SUPPORT) DUK_INTERNAL_DECL void duk_heap_insert_into_finalize_list(duk_heap *heap, duk_heaphdr *hdr); DUK_INTERNAL_DECL void duk_heap_remove_from_finalize_list(duk_heap *heap, duk_heaphdr *hdr); #endif #if defined(DUK_USE_ASSERTIONS) DUK_INTERNAL_DECL duk_bool_t duk_heap_in_heap_allocated(duk_heap *heap, duk_heaphdr *ptr); #endif #if defined(DUK_USE_INTERRUPT_COUNTER) DUK_INTERNAL_DECL void duk_heap_switch_thread(duk_heap *heap, duk_hthread *new_thr); #endif DUK_INTERNAL_DECL duk_hstring *duk_heap_strtable_intern(duk_heap *heap, const duk_uint8_t *str, duk_uint32_t blen); DUK_INTERNAL_DECL duk_hstring *duk_heap_strtable_intern_checked(duk_hthread *thr, const duk_uint8_t *str, duk_uint32_t len); #if defined(DUK_USE_LITCACHE_SIZE) DUK_INTERNAL_DECL duk_hstring *duk_heap_strtable_intern_literal_checked(duk_hthread *thr, const duk_uint8_t *str, duk_uint32_t blen); #endif DUK_INTERNAL_DECL duk_hstring *duk_heap_strtable_intern_u32(duk_heap *heap, duk_uint32_t val); DUK_INTERNAL_DECL duk_hstring *duk_heap_strtable_intern_u32_checked(duk_hthread *thr, duk_uint32_t val); #if defined(DUK_USE_REFERENCE_COUNTING) DUK_INTERNAL_DECL void duk_heap_strtable_unlink(duk_heap *heap, duk_hstring *h); #endif DUK_INTERNAL_DECL void duk_heap_strtable_unlink_prev(duk_heap *heap, duk_hstring *h, duk_hstring *prev); DUK_INTERNAL_DECL void duk_heap_strtable_force_resize(duk_heap *heap); DUK_INTERNAL void duk_heap_strtable_free(duk_heap *heap); #if defined(DUK_USE_DEBUG) DUK_INTERNAL void duk_heap_strtable_dump(duk_heap *heap); #endif DUK_INTERNAL_DECL void duk_heap_strcache_string_remove(duk_heap *heap, duk_hstring *h); DUK_INTERNAL_DECL duk_uint_fast32_t duk_heap_strcache_offset_char2byte(duk_hthread *thr, duk_hstring *h, duk_uint_fast32_t char_offset); #if defined(DUK_USE_PROVIDE_DEFAULT_ALLOC_FUNCTIONS) DUK_INTERNAL_DECL void *duk_default_alloc_function(void *udata, duk_size_t size); DUK_INTERNAL_DECL void *duk_default_realloc_function(void *udata, void *ptr, duk_size_t newsize); DUK_INTERNAL_DECL void duk_default_free_function(void *udata, void *ptr); #endif DUK_INTERNAL_DECL void *duk_heap_mem_alloc(duk_heap *heap, duk_size_t size); DUK_INTERNAL_DECL void *duk_heap_mem_alloc_zeroed(duk_heap *heap, duk_size_t size); DUK_INTERNAL_DECL void *duk_heap_mem_alloc_checked(duk_hthread *thr, duk_size_t size); DUK_INTERNAL_DECL void *duk_heap_mem_alloc_checked_zeroed(duk_hthread *thr, duk_size_t size); DUK_INTERNAL_DECL void *duk_heap_mem_realloc(duk_heap *heap, void *ptr, duk_size_t newsize); DUK_INTERNAL_DECL void *duk_heap_mem_realloc_indirect(duk_heap *heap, duk_mem_getptr cb, void *ud, duk_size_t newsize); DUK_INTERNAL_DECL void duk_heap_mem_free(duk_heap *heap, void *ptr); DUK_INTERNAL_DECL void duk_heap_free_freelists(duk_heap *heap); #if defined(DUK_USE_FINALIZER_SUPPORT) DUK_INTERNAL_DECL void duk_heap_run_finalizer(duk_heap *heap, duk_hobject *obj); DUK_INTERNAL_DECL void duk_heap_process_finalize_list(duk_heap *heap); #endif /* DUK_USE_FINALIZER_SUPPORT */ DUK_INTERNAL_DECL void duk_heap_mark_and_sweep(duk_heap *heap, duk_small_uint_t flags); DUK_INTERNAL_DECL duk_uint32_t duk_heap_hashstring(duk_heap *heap, const duk_uint8_t *str, duk_size_t len); #endif /* DUK_HEAP_H_INCLUDED */ /* #include duk_debugger.h */ #line 1 "duk_debugger.h" #if !defined(DUK_DEBUGGER_H_INCLUDED) #define DUK_DEBUGGER_H_INCLUDED /* Debugger protocol version is defined in the public API header. */ /* Initial bytes for markers. */ #define DUK_DBG_IB_EOM 0x00 #define DUK_DBG_IB_REQUEST 0x01 #define DUK_DBG_IB_REPLY 0x02 #define DUK_DBG_IB_ERROR 0x03 #define DUK_DBG_IB_NOTIFY 0x04 /* Other initial bytes. */ #define DUK_DBG_IB_INT4 0x10 #define DUK_DBG_IB_STR4 0x11 #define DUK_DBG_IB_STR2 0x12 #define DUK_DBG_IB_BUF4 0x13 #define DUK_DBG_IB_BUF2 0x14 #define DUK_DBG_IB_UNUSED 0x15 #define DUK_DBG_IB_UNDEFINED 0x16 #define DUK_DBG_IB_NULL 0x17 #define DUK_DBG_IB_TRUE 0x18 #define DUK_DBG_IB_FALSE 0x19 #define DUK_DBG_IB_NUMBER 0x1a #define DUK_DBG_IB_OBJECT 0x1b #define DUK_DBG_IB_POINTER 0x1c #define DUK_DBG_IB_LIGHTFUNC 0x1d #define DUK_DBG_IB_HEAPPTR 0x1e /* The short string/integer initial bytes starting from 0x60 don't have * defines now. */ /* Error codes. */ #define DUK_DBG_ERR_UNKNOWN 0x00 #define DUK_DBG_ERR_UNSUPPORTED 0x01 #define DUK_DBG_ERR_TOOMANY 0x02 #define DUK_DBG_ERR_NOTFOUND 0x03 #define DUK_DBG_ERR_APPLICATION 0x04 /* Commands and notifys initiated by Duktape. */ #define DUK_DBG_CMD_STATUS 0x01 #define DUK_DBG_CMD_UNUSED_2 0x02 /* Duktape 1.x: print notify */ #define DUK_DBG_CMD_UNUSED_3 0x03 /* Duktape 1.x: alert notify */ #define DUK_DBG_CMD_UNUSED_4 0x04 /* Duktape 1.x: log notify */ #define DUK_DBG_CMD_THROW 0x05 #define DUK_DBG_CMD_DETACHING 0x06 #define DUK_DBG_CMD_APPNOTIFY 0x07 /* Commands initiated by debug client. */ #define DUK_DBG_CMD_BASICINFO 0x10 #define DUK_DBG_CMD_TRIGGERSTATUS 0x11 #define DUK_DBG_CMD_PAUSE 0x12 #define DUK_DBG_CMD_RESUME 0x13 #define DUK_DBG_CMD_STEPINTO 0x14 #define DUK_DBG_CMD_STEPOVER 0x15 #define DUK_DBG_CMD_STEPOUT 0x16 #define DUK_DBG_CMD_LISTBREAK 0x17 #define DUK_DBG_CMD_ADDBREAK 0x18 #define DUK_DBG_CMD_DELBREAK 0x19 #define DUK_DBG_CMD_GETVAR 0x1a #define DUK_DBG_CMD_PUTVAR 0x1b #define DUK_DBG_CMD_GETCALLSTACK 0x1c #define DUK_DBG_CMD_GETLOCALS 0x1d #define DUK_DBG_CMD_EVAL 0x1e #define DUK_DBG_CMD_DETACH 0x1f #define DUK_DBG_CMD_DUMPHEAP 0x20 #define DUK_DBG_CMD_GETBYTECODE 0x21 #define DUK_DBG_CMD_APPREQUEST 0x22 #define DUK_DBG_CMD_GETHEAPOBJINFO 0x23 #define DUK_DBG_CMD_GETOBJPROPDESC 0x24 #define DUK_DBG_CMD_GETOBJPROPDESCRANGE 0x25 /* The low 8 bits map directly to duk_hobject.h DUK_PROPDESC_FLAG_xxx. * The remaining flags are specific to the debugger. */ #define DUK_DBG_PROPFLAG_SYMBOL (1U << 8) #define DUK_DBG_PROPFLAG_HIDDEN (1U << 9) #if defined(DUK_USE_DEBUGGER_SUPPORT) DUK_INTERNAL_DECL void duk_debug_do_detach(duk_heap *heap); DUK_INTERNAL_DECL duk_bool_t duk_debug_read_peek(duk_hthread *thr); DUK_INTERNAL_DECL void duk_debug_write_flush(duk_hthread *thr); DUK_INTERNAL_DECL void duk_debug_skip_bytes(duk_hthread *thr, duk_size_t length); DUK_INTERNAL_DECL void duk_debug_skip_byte(duk_hthread *thr); DUK_INTERNAL_DECL void duk_debug_read_bytes(duk_hthread *thr, duk_uint8_t *data, duk_size_t length); DUK_INTERNAL_DECL duk_uint8_t duk_debug_read_byte(duk_hthread *thr); DUK_INTERNAL_DECL duk_int32_t duk_debug_read_int(duk_hthread *thr); DUK_INTERNAL_DECL duk_hstring *duk_debug_read_hstring(duk_hthread *thr); /* XXX: exposed duk_debug_read_pointer */ /* XXX: exposed duk_debug_read_buffer */ /* XXX: exposed duk_debug_read_hbuffer */ #if 0 DUK_INTERNAL_DECL duk_heaphdr *duk_debug_read_heapptr(duk_hthread *thr); #endif #if defined(DUK_USE_DEBUGGER_INSPECT) DUK_INTERNAL_DECL duk_heaphdr *duk_debug_read_any_ptr(duk_hthread *thr); #endif DUK_INTERNAL_DECL duk_tval *duk_debug_read_tval(duk_hthread *thr); DUK_INTERNAL_DECL void duk_debug_write_bytes(duk_hthread *thr, const duk_uint8_t *data, duk_size_t length); DUK_INTERNAL_DECL void duk_debug_write_byte(duk_hthread *thr, duk_uint8_t x); DUK_INTERNAL_DECL void duk_debug_write_unused(duk_hthread *thr); DUK_INTERNAL_DECL void duk_debug_write_undefined(duk_hthread *thr); #if defined(DUK_USE_DEBUGGER_INSPECT) DUK_INTERNAL_DECL void duk_debug_write_null(duk_hthread *thr); #endif DUK_INTERNAL_DECL void duk_debug_write_boolean(duk_hthread *thr, duk_uint_t val); DUK_INTERNAL_DECL void duk_debug_write_int(duk_hthread *thr, duk_int32_t x); DUK_INTERNAL_DECL void duk_debug_write_uint(duk_hthread *thr, duk_uint32_t x); DUK_INTERNAL_DECL void duk_debug_write_string(duk_hthread *thr, const char *data, duk_size_t length); DUK_INTERNAL_DECL void duk_debug_write_cstring(duk_hthread *thr, const char *data); DUK_INTERNAL_DECL void duk_debug_write_hstring(duk_hthread *thr, duk_hstring *h); DUK_INTERNAL_DECL void duk_debug_write_buffer(duk_hthread *thr, const char *data, duk_size_t length); DUK_INTERNAL_DECL void duk_debug_write_hbuffer(duk_hthread *thr, duk_hbuffer *h); DUK_INTERNAL_DECL void duk_debug_write_pointer(duk_hthread *thr, void *ptr); #if defined(DUK_USE_DEBUGGER_DUMPHEAP) || defined(DUK_USE_DEBUGGER_INSPECT) DUK_INTERNAL_DECL void duk_debug_write_heapptr(duk_hthread *thr, duk_heaphdr *h); #endif DUK_INTERNAL_DECL void duk_debug_write_hobject(duk_hthread *thr, duk_hobject *obj); DUK_INTERNAL_DECL void duk_debug_write_tval(duk_hthread *thr, duk_tval *tv); #if 0 /* unused */ DUK_INTERNAL_DECL void duk_debug_write_request(duk_hthread *thr, duk_small_uint_t command); #endif DUK_INTERNAL_DECL void duk_debug_write_reply(duk_hthread *thr); DUK_INTERNAL_DECL void duk_debug_write_error_eom(duk_hthread *thr, duk_small_uint_t err_code, const char *msg); DUK_INTERNAL_DECL void duk_debug_write_notify(duk_hthread *thr, duk_small_uint_t command); DUK_INTERNAL_DECL void duk_debug_write_eom(duk_hthread *thr); DUK_INTERNAL_DECL duk_uint_fast32_t duk_debug_curr_line(duk_hthread *thr); DUK_INTERNAL_DECL void duk_debug_send_status(duk_hthread *thr); #if defined(DUK_USE_DEBUGGER_THROW_NOTIFY) DUK_INTERNAL_DECL void duk_debug_send_throw(duk_hthread *thr, duk_bool_t fatal); #endif DUK_INTERNAL_DECL void duk_debug_halt_execution(duk_hthread *thr, duk_bool_t use_prev_pc); DUK_INTERNAL_DECL duk_bool_t duk_debug_process_messages(duk_hthread *thr, duk_bool_t no_block); DUK_INTERNAL_DECL duk_small_int_t duk_debug_add_breakpoint(duk_hthread *thr, duk_hstring *filename, duk_uint32_t line); DUK_INTERNAL_DECL duk_bool_t duk_debug_remove_breakpoint(duk_hthread *thr, duk_small_uint_t breakpoint_index); DUK_INTERNAL_DECL duk_bool_t duk_debug_is_attached(duk_heap *heap); DUK_INTERNAL_DECL duk_bool_t duk_debug_is_paused(duk_heap *heap); DUK_INTERNAL_DECL void duk_debug_set_paused(duk_heap *heap); DUK_INTERNAL_DECL void duk_debug_clear_paused(duk_heap *heap); DUK_INTERNAL_DECL void duk_debug_clear_pause_state(duk_heap *heap); #endif /* DUK_USE_DEBUGGER_SUPPORT */ #endif /* DUK_DEBUGGER_H_INCLUDED */ /* #include duk_debug.h */ #line 1 "duk_debug.h" /* * Debugging macros, DUK_DPRINT() and its variants in particular. * * DUK_DPRINT() allows formatted debug prints, and supports standard * and Duktape specific formatters. See duk_debug_vsnprintf.c for details. * * DUK_D(x), DUK_DD(x), and DUK_DDD(x) are used together with log macros * for technical reasons. They are concretely used to hide 'x' from the * compiler when the corresponding log level is disabled. This allows * clean builds on non-C99 compilers, at the cost of more verbose code. * Examples: * * DUK_D(DUK_DPRINT("foo")); * DUK_DD(DUK_DDPRINT("foo")); * DUK_DDD(DUK_DDDPRINT("foo")); * * This approach is preferable to the old "double parentheses" hack because * double parentheses make the C99 solution worse: __FILE__ and __LINE__ can * no longer be added transparently without going through globals, which * works poorly with threading. */ #if !defined(DUK_DEBUG_H_INCLUDED) #define DUK_DEBUG_H_INCLUDED #if defined(DUK_USE_DEBUG) #if defined(DUK_USE_DEBUG_LEVEL) && (DUK_USE_DEBUG_LEVEL >= 0) #define DUK_D(x) x #else #define DUK_D(x) \ do { \ } while (0) /* omit */ #endif #if defined(DUK_USE_DEBUG_LEVEL) && (DUK_USE_DEBUG_LEVEL >= 1) #define DUK_DD(x) x #else #define DUK_DD(x) \ do { \ } while (0) /* omit */ #endif #if defined(DUK_USE_DEBUG_LEVEL) && (DUK_USE_DEBUG_LEVEL >= 2) #define DUK_DDD(x) x #else #define DUK_DDD(x) \ do { \ } while (0) /* omit */ #endif /* * Exposed debug macros: debugging enabled */ #if defined(DUK_USE_VARIADIC_MACROS) /* Note: combining __FILE__, __LINE__, and __func__ into fmt would be * possible compile time, but waste some space with shared function names. */ #define DUK__DEBUG_LOG(lev, ...) \ duk_debug_log((duk_int_t) (lev), DUK_FILE_MACRO, (duk_int_t) DUK_LINE_MACRO, DUK_FUNC_MACRO, __VA_ARGS__); #if defined(DUK_USE_DEBUG_LEVEL) && (DUK_USE_DEBUG_LEVEL >= 0) #define DUK_DPRINT(...) DUK__DEBUG_LOG(DUK_LEVEL_DEBUG, __VA_ARGS__) #else #define DUK_DPRINT(...) #endif #if defined(DUK_USE_DEBUG_LEVEL) && (DUK_USE_DEBUG_LEVEL >= 1) #define DUK_DDPRINT(...) DUK__DEBUG_LOG(DUK_LEVEL_DDEBUG, __VA_ARGS__) #else #define DUK_DDPRINT(...) #endif #if defined(DUK_USE_DEBUG_LEVEL) && (DUK_USE_DEBUG_LEVEL >= 2) #define DUK_DDDPRINT(...) DUK__DEBUG_LOG(DUK_LEVEL_DDDEBUG, __VA_ARGS__) #else #define DUK_DDDPRINT(...) #endif #else /* DUK_USE_VARIADIC_MACROS */ #define DUK__DEBUG_STASH(lev) \ (void) DUK_SNPRINTF(duk_debug_file_stash, DUK_DEBUG_STASH_SIZE, "%s", (const char *) DUK_FILE_MACRO), \ (void) (duk_debug_file_stash[DUK_DEBUG_STASH_SIZE - 1] = (char) 0), \ (void) (duk_debug_line_stash = (duk_int_t) DUK_LINE_MACRO), \ (void) DUK_SNPRINTF(duk_debug_func_stash, DUK_DEBUG_STASH_SIZE, "%s", (const char *) DUK_FUNC_MACRO), \ (void) (duk_debug_func_stash[DUK_DEBUG_STASH_SIZE - 1] = (char) 0), (void) (duk_debug_level_stash = (lev)) /* Without variadic macros resort to comma expression trickery to handle debug * prints. This generates a lot of harmless warnings. These hacks are not * needed normally because DUK_D() and friends will hide the entire debug log * statement from the compiler. */ #if defined(DUK_USE_DEBUG_LEVEL) && (DUK_USE_DEBUG_LEVEL >= 0) #define DUK_DPRINT DUK__DEBUG_STASH(DUK_LEVEL_DEBUG), (void) duk_debug_log /* args go here in parens */ #else #define DUK_DPRINT 0 && /* args go here as a comma expression in parens */ #endif #if defined(DUK_USE_DEBUG_LEVEL) && (DUK_USE_DEBUG_LEVEL >= 1) #define DUK_DDPRINT DUK__DEBUG_STASH(DUK_LEVEL_DDEBUG), (void) duk_debug_log /* args go here in parens */ #else #define DUK_DDPRINT 0 && /* args */ #endif #if defined(DUK_USE_DEBUG_LEVEL) && (DUK_USE_DEBUG_LEVEL >= 2) #define DUK_DDDPRINT DUK__DEBUG_STASH(DUK_LEVEL_DDDEBUG), (void) duk_debug_log /* args go here in parens */ #else #define DUK_DDDPRINT 0 && /* args */ #endif #endif /* DUK_USE_VARIADIC_MACROS */ #else /* DUK_USE_DEBUG */ /* * Exposed debug macros: debugging disabled */ #define DUK_D(x) \ do { \ } while (0) /* omit */ #define DUK_DD(x) \ do { \ } while (0) /* omit */ #define DUK_DDD(x) \ do { \ } while (0) /* omit */ #if defined(DUK_USE_VARIADIC_MACROS) #define DUK_DPRINT(...) #define DUK_DDPRINT(...) #define DUK_DDDPRINT(...) #else /* DUK_USE_VARIADIC_MACROS */ #define DUK_DPRINT 0 && /* args go here as a comma expression in parens */ #define DUK_DDPRINT 0 && /* args */ #define DUK_DDDPRINT 0 && /* args */ #endif /* DUK_USE_VARIADIC_MACROS */ #endif /* DUK_USE_DEBUG */ /* * Structs */ #if defined(DUK_USE_DEBUG) struct duk_fixedbuffer { duk_uint8_t *buffer; duk_size_t length; duk_size_t offset; duk_bool_t truncated; }; #endif /* * Prototypes */ #if defined(DUK_USE_DEBUG) DUK_INTERNAL_DECL duk_int_t duk_debug_vsnprintf(char *str, duk_size_t size, const char *format, va_list ap); #if 0 /*unused*/ DUK_INTERNAL_DECL duk_int_t duk_debug_snprintf(char *str, duk_size_t size, const char *format, ...); #endif DUK_INTERNAL_DECL void duk_debug_format_funcptr(char *buf, duk_size_t buf_size, duk_uint8_t *fptr, duk_size_t fptr_size); #if defined(DUK_USE_VARIADIC_MACROS) DUK_INTERNAL_DECL void duk_debug_log(duk_int_t level, const char *file, duk_int_t line, const char *func, const char *fmt, ...); #else /* DUK_USE_VARIADIC_MACROS */ /* parameter passing, not thread safe */ #define DUK_DEBUG_STASH_SIZE 128 #if !defined(DUK_SINGLE_FILE) DUK_INTERNAL_DECL char duk_debug_file_stash[DUK_DEBUG_STASH_SIZE]; DUK_INTERNAL_DECL duk_int_t duk_debug_line_stash; DUK_INTERNAL_DECL char duk_debug_func_stash[DUK_DEBUG_STASH_SIZE]; DUK_INTERNAL_DECL duk_int_t duk_debug_level_stash; #endif DUK_INTERNAL_DECL void duk_debug_log(const char *fmt, ...); #endif /* DUK_USE_VARIADIC_MACROS */ DUK_INTERNAL_DECL void duk_fb_put_bytes(duk_fixedbuffer *fb, const duk_uint8_t *buffer, duk_size_t length); DUK_INTERNAL_DECL void duk_fb_put_byte(duk_fixedbuffer *fb, duk_uint8_t x); DUK_INTERNAL_DECL void duk_fb_put_cstring(duk_fixedbuffer *fb, const char *x); DUK_INTERNAL_DECL void duk_fb_sprintf(duk_fixedbuffer *fb, const char *fmt, ...); DUK_INTERNAL_DECL void duk_fb_put_funcptr(duk_fixedbuffer *fb, duk_uint8_t *fptr, duk_size_t fptr_size); DUK_INTERNAL_DECL duk_bool_t duk_fb_is_full(duk_fixedbuffer *fb); #endif /* DUK_USE_DEBUG */ #endif /* DUK_DEBUG_H_INCLUDED */ /* #include duk_error.h */ #line 1 "duk_error.h" /* * Error handling macros, assertion macro, error codes. * * There are three types of 'errors': * * 1. Ordinary errors relative to a thread, cause a longjmp, catchable. * 2. Fatal errors relative to a heap, cause fatal handler to be called. * 3. Fatal errors without context, cause the default (not heap specific) * fatal handler to be called. * * Fatal errors without context are used by debug code such as assertions. * By providing a fatal error handler for a Duktape heap, user code can * avoid fatal errors without context in non-debug builds. */ #if !defined(DUK_ERROR_H_INCLUDED) #define DUK_ERROR_H_INCLUDED /* * Error codes: defined in duktape.h * * Error codes are used as a shorthand to throw exceptions from inside * the implementation. The appropriate ECMAScript object is constructed * based on the code. ECMAScript code throws objects directly. The error * codes are defined in the public API header because they are also used * by calling code. */ /* * Normal error * * Normal error is thrown with a longjmp() through the current setjmp() * catchpoint record in the duk_heap. The 'curr_thread' of the duk_heap * identifies the throwing thread. * * Error formatting is usually unnecessary. The error macros provide a * zero argument version (no formatting) and separate macros for small * argument counts. Variadic macros are not used to avoid portability * issues and avoid the need for stash-based workarounds when they're not * available. Vararg calls are avoided for non-formatted error calls * because vararg call sites are larger than normal, and there are a lot * of call sites with no formatting. * * Note that special formatting provided by debug macros is NOT available. * * The _RAW variants allow the caller to specify file and line. This makes * it easier to write checked calls which want to use the call site of the * checked function, not the error macro call inside the checked function. */ #if defined(DUK_USE_VERBOSE_ERRORS) /* Because there are quite many call sites, pack error code (require at most * 8-bit) into a single argument. */ #define DUK_ERROR(thr, err, msg) \ do { \ duk_errcode_t duk__err = (err); \ duk_int_t duk__line = (duk_int_t) DUK_LINE_MACRO; \ DUK_ASSERT(duk__err >= 0 && duk__err <= 0xff); \ DUK_ASSERT(duk__line >= 0 && duk__line <= 0x00ffffffL); \ duk_err_handle_error((thr), DUK_FILE_MACRO, (((duk_uint_t) duk__err) << 24) | ((duk_uint_t) duk__line), (msg)); \ } while (0) #define DUK_ERROR_RAW(thr, file, line, err, msg) \ do { \ duk_errcode_t duk__err = (err); \ duk_int_t duk__line = (duk_int_t) (line); \ DUK_ASSERT(duk__err >= 0 && duk__err <= 0xff); \ DUK_ASSERT(duk__line >= 0 && duk__line <= 0x00ffffffL); \ duk_err_handle_error((thr), (file), (((duk_uint_t) duk__err) << 24) | ((duk_uint_t) duk__line), (msg)); \ } while (0) #define DUK_ERROR_FMT1(thr, err, fmt, arg1) \ do { \ duk_errcode_t duk__err = (err); \ duk_int_t duk__line = (duk_int_t) DUK_LINE_MACRO; \ DUK_ASSERT(duk__err >= 0 && duk__err <= 0xff); \ DUK_ASSERT(duk__line >= 0 && duk__line <= 0x00ffffffL); \ duk_err_handle_error_fmt((thr), \ DUK_FILE_MACRO, \ (((duk_uint_t) duk__err) << 24) | ((duk_uint_t) duk__line), \ (fmt), \ (arg1)); \ } while (0) #define DUK_ERROR_RAW_FMT1(thr, file, line, err, fmt, arg1) \ do { \ duk_errcode_t duk__err = (err); \ duk_int_t duk__line = (duk_int_t) (line); \ DUK_ASSERT(duk__err >= 0 && duk__err <= 0xff); \ DUK_ASSERT(duk__line >= 0 && duk__line <= 0x00ffffffL); \ duk_err_handle_error_fmt((thr), \ (file), \ (((duk_uint_t) duk__err) << 24) | ((duk_uint_t) duk__line), \ (fmt), \ (arg1)); \ } while (0) #define DUK_ERROR_FMT2(thr, err, fmt, arg1, arg2) \ do { \ duk_errcode_t duk__err = (err); \ duk_int_t duk__line = (duk_int_t) DUK_LINE_MACRO; \ DUK_ASSERT(duk__err >= 0 && duk__err <= 0xff); \ DUK_ASSERT(duk__line >= 0 && duk__line <= 0x00ffffffL); \ duk_err_handle_error_fmt((thr), \ DUK_FILE_MACRO, \ (((duk_uint_t) duk__err) << 24) | ((duk_uint_t) duk__line), \ (fmt), \ (arg1), \ (arg2)); \ } while (0) #define DUK_ERROR_RAW_FMT2(thr, file, line, err, fmt, arg1, arg2) \ do { \ duk_errcode_t duk__err = (err); \ duk_int_t duk__line = (duk_int_t) (line); \ DUK_ASSERT(duk__err >= 0 && duk__err <= 0xff); \ DUK_ASSERT(duk__line >= 0 && duk__line <= 0x00ffffffL); \ duk_err_handle_error_fmt((thr), \ (file), \ (((duk_uint_t) duk__err) << 24) | ((duk_uint_t) duk__line), \ (fmt), \ (arg1), \ (arg2)); \ } while (0) #define DUK_ERROR_FMT3(thr, err, fmt, arg1, arg2, arg3) \ do { \ duk_errcode_t duk__err = (err); \ duk_int_t duk__line = (duk_int_t) DUK_LINE_MACRO; \ DUK_ASSERT(duk__err >= 0 && duk__err <= 0xff); \ DUK_ASSERT(duk__line >= 0 && duk__line <= 0x00ffffffL); \ duk_err_handle_error_fmt((thr), \ DUK_FILE_MACRO, \ (((duk_uint_t) duk__err) << 24) | ((duk_uint_t) duk__line), \ (fmt), \ (arg1), \ (arg2), \ (arg3)); \ } while (0) #define DUK_ERROR_RAW_FMT3(thr, file, line, err, fmt, arg1, arg2, arg3) \ do { \ duk_errcode_t duk__err = (err); \ duk_int_t duk__line = (duk_int_t) (line); \ DUK_ASSERT(duk__err >= 0 && duk__err <= 0xff); \ DUK_ASSERT(duk__line >= 0 && duk__line <= 0x00ffffffL); \ duk_err_handle_error_fmt((thr), \ (file), \ (((duk_uint_t) duk__err) << 24) | ((duk_uint_t) duk__line), \ (fmt), \ (arg1), \ (arg2), \ (arg3)); \ } while (0) #define DUK_ERROR_FMT4(thr, err, fmt, arg1, arg2, arg3, arg4) \ do { \ duk_errcode_t duk__err = (err); \ duk_int_t duk__line = (duk_int_t) DUK_LINE_MACRO; \ DUK_ASSERT(duk__err >= 0 && duk__err <= 0xff); \ DUK_ASSERT(duk__line >= 0 && duk__line <= 0x00ffffffL); \ duk_err_handle_error_fmt((thr), \ DUK_FILE_MACRO, \ (((duk_uint_t) duk__err) << 24) | ((duk_uint_t) duk__line), \ (fmt), \ (arg1), \ (arg2), \ (arg3), \ (arg4)); \ } while (0) #define DUK_ERROR_RAW_FMT4(thr, file, line, err, fmt, arg1, arg2, arg3, arg4) \ do { \ duk_errcode_t duk__err = (err); \ duk_int_t duk__line = (duk_int_t) (line); \ DUK_ASSERT(duk__err >= 0 && duk__err <= 0xff); \ DUK_ASSERT(duk__line >= 0 && duk__line <= 0x00ffffffL); \ duk_err_handle_error_fmt((thr), \ (file), \ (((duk_uint_t) duk__err) << 24) | ((duk_uint_t) duk__line), \ (fmt), \ (arg1), \ (arg2), \ (arg3), \ (arg4)); \ } while (0) #else /* DUK_USE_VERBOSE_ERRORS */ #define DUK_ERROR(thr, err, msg) duk_err_handle_error((thr), (err)) #define DUK_ERROR_RAW(thr, file, line, err, msg) duk_err_handle_error((thr), (err)) #define DUK_ERROR_FMT1(thr, err, fmt, arg1) DUK_ERROR((thr), (err), (fmt)) #define DUK_ERROR_RAW_FMT1(thr, file, line, err, fmt, arg1) DUK_ERROR_RAW((thr), (file), (line), (err), (fmt)) #define DUK_ERROR_FMT2(thr, err, fmt, arg1, arg2) DUK_ERROR((thr), (err), (fmt)) #define DUK_ERROR_RAW_FMT2(thr, file, line, err, fmt, arg1, arg2) DUK_ERROR_RAW((thr), (file), (line), (err), (fmt)) #define DUK_ERROR_FMT3(thr, err, fmt, arg1, arg2, arg3) DUK_ERROR((thr), (err), (fmt)) #define DUK_ERROR_RAW_FMT3(thr, file, line, err, fmt, arg1, arg2, arg3) DUK_ERROR_RAW((thr), (file), (line), (err), (fmt)) #define DUK_ERROR_FMT4(thr, err, fmt, arg1, arg2, arg3, arg4) DUK_ERROR((thr), (err), (fmt)) #define DUK_ERROR_RAW_FMT4(thr, file, line, err, fmt, arg1, arg2, arg3, arg4) DUK_ERROR_RAW((thr), (file), (line), (err), (fmt)) #endif /* DUK_USE_VERBOSE_ERRORS */ /* * Fatal error without context * * The macro is an expression to make it compatible with DUK_ASSERT_EXPR(). */ #define DUK_FATAL_WITHOUT_CONTEXT(msg) duk_default_fatal_handler(NULL, (msg)) /* * Error throwing helpers * * The goal is to provide verbose and configurable error messages. Call * sites should be clean in source code and compile to a small footprint. * Small footprint is also useful for performance because small cold paths * reduce code cache pressure. Adding macros here only makes sense if there * are enough call sites to get concrete benefits. * * DUK_ERROR_xxx() macros are generic and can be used anywhere. * * DUK_DCERROR_xxx() macros can only be used in Duktape/C functions where * the "return DUK_RET_xxx;" shorthand is available for low memory targets. * The DUK_DCERROR_xxx() macros always either throw or perform a * 'return DUK_RET_xxx' from the calling function. */ #if defined(DUK_USE_VERBOSE_ERRORS) /* Verbose errors with key/value summaries (non-paranoid) or without key/value * summaries (paranoid, for some security sensitive environments), the paranoid * vs. non-paranoid distinction affects only a few specific errors. */ #if defined(DUK_USE_PARANOID_ERRORS) #define DUK_ERROR_REQUIRE_TYPE_INDEX(thr, idx, expectname, lowmemstr) \ do { \ duk_err_require_type_index((thr), DUK_FILE_MACRO, (duk_int_t) DUK_LINE_MACRO, (idx), (expectname)); \ } while (0) #else /* DUK_USE_PARANOID_ERRORS */ #define DUK_ERROR_REQUIRE_TYPE_INDEX(thr, idx, expectname, lowmemstr) \ do { \ duk_err_require_type_index((thr), DUK_FILE_MACRO, (duk_int_t) DUK_LINE_MACRO, (idx), (expectname)); \ } while (0) #endif /* DUK_USE_PARANOID_ERRORS */ #define DUK_ERROR_INTERNAL(thr) \ do { \ duk_err_error_internal((thr), DUK_FILE_MACRO, (duk_int_t) DUK_LINE_MACRO); \ } while (0) #define DUK_DCERROR_INTERNAL(thr) \ do { \ DUK_ERROR_INTERNAL((thr)); \ return 0; \ } while (0) #define DUK_ERROR_ALLOC_FAILED(thr) \ do { \ duk_err_error_alloc_failed((thr), DUK_FILE_MACRO, (duk_int_t) DUK_LINE_MACRO); \ } while (0) #define DUK_ERROR_UNSUPPORTED(thr) \ do { \ DUK_ERROR((thr), DUK_ERR_ERROR, DUK_STR_UNSUPPORTED); \ } while (0) #define DUK_DCERROR_UNSUPPORTED(thr) \ do { \ DUK_ERROR_UNSUPPORTED((thr)); \ return 0; \ } while (0) #define DUK_ERROR_ERROR(thr, msg) \ do { \ duk_err_error((thr), DUK_FILE_MACRO, (duk_int_t) DUK_LINE_MACRO, (msg)); \ } while (0) #define DUK_ERROR_RANGE_INDEX(thr, idx) \ do { \ duk_err_range_index((thr), DUK_FILE_MACRO, (duk_int_t) DUK_LINE_MACRO, (idx)); \ } while (0) #define DUK_ERROR_RANGE_PUSH_BEYOND(thr) \ do { \ duk_err_range_push_beyond((thr), DUK_FILE_MACRO, (duk_int_t) DUK_LINE_MACRO); \ } while (0) #define DUK_ERROR_RANGE_INVALID_ARGS(thr) \ do { \ DUK_ERROR_RANGE((thr), DUK_STR_INVALID_ARGS); \ } while (0) #define DUK_DCERROR_RANGE_INVALID_ARGS(thr) \ do { \ DUK_ERROR_RANGE_INVALID_ARGS((thr)); \ return 0; \ } while (0) #define DUK_ERROR_RANGE_INVALID_COUNT(thr) \ do { \ DUK_ERROR_RANGE((thr), DUK_STR_INVALID_COUNT); \ } while (0) #define DUK_DCERROR_RANGE_INVALID_COUNT(thr) \ do { \ DUK_ERROR_RANGE_INVALID_COUNT((thr)); \ return 0; \ } while (0) #define DUK_ERROR_RANGE_INVALID_LENGTH(thr) \ do { \ DUK_ERROR_RANGE((thr), DUK_STR_INVALID_LENGTH); \ } while (0) #define DUK_DCERROR_RANGE_INVALID_LENGTH(thr) \ do { \ DUK_ERROR_RANGE_INVALID_LENGTH((thr)); \ return 0; \ } while (0) #define DUK_ERROR_RANGE(thr, msg) \ do { \ duk_err_range((thr), DUK_FILE_MACRO, (duk_int_t) DUK_LINE_MACRO, (msg)); \ } while (0) #define DUK_ERROR_EVAL(thr, msg) \ do { \ DUK_ERROR((thr), DUK_ERR_EVAL_ERROR, (msg)); \ } while (0) #define DUK_ERROR_REFERENCE(thr, msg) \ do { \ DUK_ERROR((thr), DUK_ERR_REFERENCE_ERROR, (msg)); \ } while (0) #define DUK_ERROR_SYNTAX(thr, msg) \ do { \ DUK_ERROR((thr), DUK_ERR_SYNTAX_ERROR, (msg)); \ } while (0) #define DUK_ERROR_TYPE_INVALID_ARGS(thr) \ do { \ duk_err_type_invalid_args((thr), DUK_FILE_MACRO, (duk_int_t) DUK_LINE_MACRO); \ } while (0) #define DUK_DCERROR_TYPE_INVALID_ARGS(thr) \ do { \ DUK_ERROR_TYPE_INVALID_ARGS((thr)); \ return 0; \ } while (0) #define DUK_ERROR_TYPE_INVALID_STATE(thr) \ do { \ duk_err_type_invalid_state((thr), DUK_FILE_MACRO, (duk_int_t) DUK_LINE_MACRO); \ } while (0) #define DUK_DCERROR_TYPE_INVALID_STATE(thr) \ do { \ DUK_ERROR_TYPE_INVALID_STATE((thr)); \ return 0; \ } while (0) #define DUK_ERROR_TYPE_INVALID_TRAP_RESULT(thr) \ do { \ duk_err_type_invalid_trap_result((thr), DUK_FILE_MACRO, (duk_int_t) DUK_LINE_MACRO); \ } while (0) #define DUK_DCERROR_TYPE_INVALID_TRAP_RESULT(thr) \ do { \ DUK_ERROR_TYPE((thr), DUK_STR_INVALID_TRAP_RESULT); \ } while (0) #define DUK_ERROR_TYPE(thr, msg) \ do { \ DUK_ERROR((thr), DUK_ERR_TYPE_ERROR, (msg)); \ } while (0) #define DUK_ERROR_URI(thr, msg) \ do { \ DUK_ERROR((thr), DUK_ERR_URI_ERROR, (msg)); \ } while (0) #else /* DUK_USE_VERBOSE_ERRORS */ /* Non-verbose errors for low memory targets: no file, line, or message. */ #define DUK_ERROR_REQUIRE_TYPE_INDEX(thr, idx, expectname, lowmemstr) \ do { \ duk_err_type((thr)); \ } while (0) #define DUK_ERROR_INTERNAL(thr) \ do { \ duk_err_error((thr)); \ } while (0) #define DUK_DCERROR_INTERNAL(thr) \ do { \ DUK_UNREF((thr)); \ return DUK_RET_ERROR; \ } while (0) #define DUK_ERROR_ALLOC_FAILED(thr) \ do { \ duk_err_error((thr)); \ } while (0) #define DUK_ERROR_UNSUPPORTED(thr) \ do { \ duk_err_error((thr)); \ } while (0) #define DUK_DCERROR_UNSUPPORTED(thr) \ do { \ DUK_UNREF((thr)); \ return DUK_RET_ERROR; \ } while (0) #define DUK_ERROR_ERROR(thr, msg) \ do { \ duk_err_error((thr)); \ } while (0) #define DUK_ERROR_RANGE_INDEX(thr, idx) \ do { \ duk_err_range((thr)); \ } while (0) #define DUK_ERROR_RANGE_PUSH_BEYOND(thr) \ do { \ duk_err_range((thr)); \ } while (0) #define DUK_ERROR_RANGE_INVALID_ARGS(thr) \ do { \ duk_err_range((thr)); \ } while (0) #define DUK_DCERROR_RANGE_INVALID_ARGS(thr) \ do { \ DUK_UNREF((thr)); \ return DUK_RET_RANGE_ERROR; \ } while (0) #define DUK_ERROR_RANGE_INVALID_COUNT(thr) \ do { \ duk_err_range((thr)); \ } while (0) #define DUK_DCERROR_RANGE_INVALID_COUNT(thr) \ do { \ DUK_UNREF((thr)); \ return DUK_RET_RANGE_ERROR; \ } while (0) #define DUK_ERROR_RANGE_INVALID_LENGTH(thr) \ do { \ duk_err_range((thr)); \ } while (0) #define DUK_DCERROR_RANGE_INVALID_LENGTH(thr) \ do { \ DUK_UNREF((thr)); \ return DUK_RET_RANGE_ERROR; \ } while (0) #define DUK_ERROR_RANGE(thr, msg) \ do { \ duk_err_range((thr)); \ } while (0) #define DUK_ERROR_EVAL(thr, msg) \ do { \ duk_err_eval((thr)); \ } while (0) #define DUK_ERROR_REFERENCE(thr, msg) \ do { \ duk_err_reference((thr)); \ } while (0) #define DUK_ERROR_SYNTAX(thr, msg) \ do { \ duk_err_syntax((thr)); \ } while (0) #define DUK_ERROR_TYPE_INVALID_ARGS(thr) \ do { \ duk_err_type((thr)); \ } while (0) #define DUK_DCERROR_TYPE_INVALID_ARGS(thr) \ do { \ DUK_UNREF((thr)); \ return DUK_RET_TYPE_ERROR; \ } while (0) #define DUK_ERROR_TYPE_INVALID_STATE(thr) \ do { \ duk_err_type((thr)); \ } while (0) #define DUK_DCERROR_TYPE_INVALID_STATE(thr) \ do { \ duk_err_type((thr)); \ } while (0) #define DUK_ERROR_TYPE_INVALID_TRAP_RESULT(thr) \ do { \ duk_err_type((thr)); \ } while (0) #define DUK_DCERROR_TYPE_INVALID_TRAP_RESULT(thr) \ do { \ DUK_UNREF((thr)); \ return DUK_RET_TYPE_ERROR; \ } while (0) #define DUK_ERROR_TYPE_INVALID_TRAP_RESULT(thr) \ do { \ duk_err_type((thr)); \ } while (0) #define DUK_ERROR_TYPE(thr, msg) \ do { \ duk_err_type((thr)); \ } while (0) #define DUK_ERROR_URI(thr, msg) \ do { \ duk_err_uri((thr)); \ } while (0) #endif /* DUK_USE_VERBOSE_ERRORS */ /* * Assert macro: failure causes a fatal error. * * NOTE: since the assert macro doesn't take a heap/context argument, there's * no way to look up a heap/context specific fatal error handler which may have * been given by the application. Instead, assertion failures always use the * internal default fatal error handler; it can be replaced via duk_config.h * and then applies to all Duktape heaps. */ #if defined(DUK_USE_ASSERTIONS) /* The message should be a compile time constant without formatting (less risk); * we don't care about assertion text size because they're not used in production * builds. */ #define DUK_ASSERT(x) \ do { \ if (!(x)) { \ DUK_FATAL_WITHOUT_CONTEXT("assertion failed: " #x " (" DUK_FILE_MACRO \ ":" DUK_MACRO_STRINGIFY(DUK_LINE_MACRO) ")"); \ } \ } while (0) /* Assertion compatible inside a comma expression, evaluates to void. */ #define DUK_ASSERT_EXPR(x) \ ((void) ((x) ? 0 : \ (DUK_FATAL_WITHOUT_CONTEXT("assertion failed: " #x " (" DUK_FILE_MACRO \ ":" DUK_MACRO_STRINGIFY(DUK_LINE_MACRO) ")"), \ 0))) #else /* DUK_USE_ASSERTIONS */ #define DUK_ASSERT(x) \ do { /* assertion omitted */ \ } while (0) #define DUK_ASSERT_EXPR(x) ((void) 0) #endif /* DUK_USE_ASSERTIONS */ /* this variant is used when an assert would generate a compile warning by * being always true (e.g. >= 0 comparison for an unsigned value */ #define DUK_ASSERT_DISABLE(x) \ do { /* assertion disabled */ \ } while (0) /* * Assertion helpers */ #if defined(DUK_USE_ASSERTIONS) && defined(DUK_USE_REFERENCE_COUNTING) #define DUK_ASSERT_REFCOUNT_NONZERO_HEAPHDR(h) \ do { \ DUK_ASSERT((h) == NULL || DUK_HEAPHDR_GET_REFCOUNT((duk_heaphdr *) (h)) > 0); \ } while (0) #define DUK_ASSERT_REFCOUNT_NONZERO_TVAL(tv) \ do { \ if ((tv) != NULL && DUK_TVAL_IS_HEAP_ALLOCATED((tv))) { \ DUK_ASSERT(DUK_HEAPHDR_GET_REFCOUNT(DUK_TVAL_GET_HEAPHDR((tv))) > 0); \ } \ } while (0) #else #define DUK_ASSERT_REFCOUNT_NONZERO_HEAPHDR(h) /* no refcount check */ #define DUK_ASSERT_REFCOUNT_NONZERO_TVAL(tv) /* no refcount check */ #endif #define DUK_ASSERT_TOP(ctx, n) DUK_ASSERT((duk_idx_t) duk_get_top((ctx)) == (duk_idx_t) (n)) #if defined(DUK_USE_ASSERTIONS) && defined(DUK_USE_PACKED_TVAL) #define DUK_ASSERT_DOUBLE_IS_NORMALIZED(dval) \ do { \ duk_double_union duk__assert_tmp_du; \ duk__assert_tmp_du.d = (dval); \ DUK_ASSERT(DUK_DBLUNION_IS_NORMALIZED(&duk__assert_tmp_du)); \ } while (0) #else #define DUK_ASSERT_DOUBLE_IS_NORMALIZED(dval) /* nop */ #endif #define DUK_ASSERT_VS_SPACE(thr) DUK_ASSERT(thr->valstack_top < thr->valstack_end) /* * Helper to initialize a memory area (e.g. struct) with garbage when * assertions enabled. */ #if defined(DUK_USE_ASSERTIONS) #define DUK_ASSERT_SET_GARBAGE(ptr, size) \ do { \ duk_memset_unsafe((void *) (ptr), 0x5a, size); \ } while (0) #else #define DUK_ASSERT_SET_GARBAGE(ptr, size) \ do { \ } while (0) #endif /* * Helper for valstack space * * Caller of DUK_ASSERT_VALSTACK_SPACE() estimates the number of free stack entries * required for its own use, and any child calls which are not (a) Duktape API calls * or (b) Duktape calls which involve extending the valstack (e.g. getter call). */ #define DUK_VALSTACK_ASSERT_EXTRA \ 5 /* this is added to checks to allow for Duktape \ * API calls in addition to function's own use \ */ #if defined(DUK_USE_ASSERTIONS) #define DUK_ASSERT_VALSTACK_SPACE(thr, n) \ do { \ DUK_ASSERT((thr) != NULL); \ DUK_ASSERT((thr)->valstack_end - (thr)->valstack_top >= (n) + DUK_VALSTACK_ASSERT_EXTRA); \ } while (0) #else #define DUK_ASSERT_VALSTACK_SPACE(thr, n) /* no valstack space check */ #endif /* * Prototypes */ #if defined(DUK_USE_VERBOSE_ERRORS) DUK_NORETURN( DUK_INTERNAL_DECL void duk_err_handle_error(duk_hthread *thr, const char *filename, duk_uint_t line_and_code, const char *msg)); DUK_NORETURN(DUK_INTERNAL_DECL void duk_err_handle_error_fmt(duk_hthread *thr, const char *filename, duk_uint_t line_and_code, const char *fmt, ...)); #else /* DUK_USE_VERBOSE_ERRORS */ DUK_NORETURN(DUK_INTERNAL_DECL void duk_err_handle_error(duk_hthread *thr, duk_errcode_t code)); #endif /* DUK_USE_VERBOSE_ERRORS */ #if defined(DUK_USE_VERBOSE_ERRORS) DUK_NORETURN(DUK_INTERNAL_DECL void duk_err_create_and_throw(duk_hthread *thr, duk_errcode_t code, const char *msg, const char *filename, duk_int_t line)); #else DUK_NORETURN(DUK_INTERNAL_DECL void duk_err_create_and_throw(duk_hthread *thr, duk_errcode_t code)); #endif DUK_NORETURN(DUK_INTERNAL_DECL void duk_error_throw_from_negative_rc(duk_hthread *thr, duk_ret_t rc)); #define DUK_AUGMENT_FLAG_NOBLAME_FILELINE (1U << 0) /* if set, don't blame C file/line for .fileName and .lineNumber */ #define DUK_AUGMENT_FLAG_SKIP_ONE (1U << 1) /* if set, skip topmost activation in traceback construction */ #if defined(DUK_USE_AUGMENT_ERROR_CREATE) DUK_INTERNAL_DECL void duk_err_augment_error_create(duk_hthread *thr, duk_hthread *thr_callstack, const char *filename, duk_int_t line, duk_small_uint_t flags); #endif #if defined(DUK_USE_AUGMENT_ERROR_THROW) DUK_INTERNAL_DECL void duk_err_augment_error_throw(duk_hthread *thr); #endif #if defined(DUK_USE_VERBOSE_ERRORS) #if defined(DUK_USE_PARANOID_ERRORS) DUK_NORETURN(DUK_INTERNAL_DECL void duk_err_require_type_index(duk_hthread *thr, const char *filename, duk_int_t linenumber, duk_idx_t idx, const char *expect_name)); #else DUK_NORETURN(DUK_INTERNAL_DECL void duk_err_require_type_index(duk_hthread *thr, const char *filename, duk_int_t linenumber, duk_idx_t idx, const char *expect_name)); #endif DUK_NORETURN(DUK_INTERNAL_DECL void duk_err_error_internal(duk_hthread *thr, const char *filename, duk_int_t linenumber)); DUK_NORETURN(DUK_INTERNAL_DECL void duk_err_error_alloc_failed(duk_hthread *thr, const char *filename, duk_int_t linenumber)); DUK_NORETURN( DUK_INTERNAL_DECL void duk_err_error(duk_hthread *thr, const char *filename, duk_int_t linenumber, const char *message)); DUK_NORETURN( DUK_INTERNAL_DECL void duk_err_range_index(duk_hthread *thr, const char *filename, duk_int_t linenumber, duk_idx_t idx)); DUK_NORETURN(DUK_INTERNAL_DECL void duk_err_range_push_beyond(duk_hthread *thr, const char *filename, duk_int_t linenumber)); DUK_NORETURN( DUK_INTERNAL_DECL void duk_err_range(duk_hthread *thr, const char *filename, duk_int_t linenumber, const char *message)); DUK_NORETURN(DUK_INTERNAL_DECL void duk_err_type_invalid_args(duk_hthread *thr, const char *filename, duk_int_t linenumber)); DUK_NORETURN(DUK_INTERNAL_DECL void duk_err_type_invalid_state(duk_hthread *thr, const char *filename, duk_int_t linenumber)); DUK_NORETURN(DUK_INTERNAL_DECL void duk_err_type_invalid_trap_result(duk_hthread *thr, const char *filename, duk_int_t linenumber)); #else /* DUK_VERBOSE_ERRORS */ DUK_NORETURN(DUK_INTERNAL_DECL void duk_err_error(duk_hthread *thr)); DUK_NORETURN(DUK_INTERNAL_DECL void duk_err_range(duk_hthread *thr)); DUK_NORETURN(DUK_INTERNAL_DECL void duk_err_eval(duk_hthread *thr)); DUK_NORETURN(DUK_INTERNAL_DECL void duk_err_reference(duk_hthread *thr)); DUK_NORETURN(DUK_INTERNAL_DECL void duk_err_syntax(duk_hthread *thr)); DUK_NORETURN(DUK_INTERNAL_DECL void duk_err_type(duk_hthread *thr)); DUK_NORETURN(DUK_INTERNAL_DECL void duk_err_uri(duk_hthread *thr)); #endif /* DUK_VERBOSE_ERRORS */ DUK_NORETURN(DUK_INTERNAL_DECL void duk_err_longjmp(duk_hthread *thr)); DUK_NORETURN(DUK_INTERNAL_DECL void duk_default_fatal_handler(void *udata, const char *msg)); DUK_INTERNAL_DECL void duk_err_setup_ljstate1(duk_hthread *thr, duk_small_uint_t lj_type, duk_tval *tv_val); #if defined(DUK_USE_DEBUGGER_SUPPORT) DUK_INTERNAL_DECL void duk_err_check_debugger_integration(duk_hthread *thr); #endif DUK_INTERNAL_DECL duk_hobject *duk_error_prototype_from_code(duk_hthread *thr, duk_errcode_t err_code); #endif /* DUK_ERROR_H_INCLUDED */ /* #include duk_unicode.h */ #line 1 "duk_unicode.h" /* * Unicode helpers */ #if !defined(DUK_UNICODE_H_INCLUDED) #define DUK_UNICODE_H_INCLUDED /* * UTF-8 / XUTF-8 / CESU-8 constants */ #define DUK_UNICODE_MAX_XUTF8_LENGTH 7 /* up to 36 bit codepoints */ #define DUK_UNICODE_MAX_XUTF8_BMP_LENGTH 3 /* all codepoints up to U+FFFF */ #define DUK_UNICODE_MAX_CESU8_LENGTH 6 /* all codepoints up to U+10FFFF */ #define DUK_UNICODE_MAX_CESU8_BMP_LENGTH 3 /* all codepoints up to U+FFFF */ /* * Useful Unicode codepoints * * Integer constants must be signed to avoid unexpected coercions * in comparisons. */ #define DUK_UNICODE_CP_ZWNJ 0x200cL /* zero-width non-joiner */ #define DUK_UNICODE_CP_ZWJ 0x200dL /* zero-width joiner */ #define DUK_UNICODE_CP_REPLACEMENT_CHARACTER \ 0xfffdL /* http://en.wikipedia.org/wiki/Replacement_character#Replacement_character \ */ /* * ASCII character constants * * C character literals like 'x' have a platform specific value and do * not match ASCII (UTF-8) values on e.g. EBCDIC platforms. So, use * these (admittedly awkward) constants instead. These constants must * also have signed values to avoid unexpected coercions in comparisons. * * http://en.wikipedia.org/wiki/ASCII */ #define DUK_ASC_NUL 0x00 #define DUK_ASC_SOH 0x01 #define DUK_ASC_STX 0x02 #define DUK_ASC_ETX 0x03 #define DUK_ASC_EOT 0x04 #define DUK_ASC_ENQ 0x05 #define DUK_ASC_ACK 0x06 #define DUK_ASC_BEL 0x07 #define DUK_ASC_BS 0x08 #define DUK_ASC_HT 0x09 #define DUK_ASC_LF 0x0a #define DUK_ASC_VT 0x0b #define DUK_ASC_FF 0x0c #define DUK_ASC_CR 0x0d #define DUK_ASC_SO 0x0e #define DUK_ASC_SI 0x0f #define DUK_ASC_DLE 0x10 #define DUK_ASC_DC1 0x11 #define DUK_ASC_DC2 0x12 #define DUK_ASC_DC3 0x13 #define DUK_ASC_DC4 0x14 #define DUK_ASC_NAK 0x15 #define DUK_ASC_SYN 0x16 #define DUK_ASC_ETB 0x17 #define DUK_ASC_CAN 0x18 #define DUK_ASC_EM 0x19 #define DUK_ASC_SUB 0x1a #define DUK_ASC_ESC 0x1b #define DUK_ASC_FS 0x1c #define DUK_ASC_GS 0x1d #define DUK_ASC_RS 0x1e #define DUK_ASC_US 0x1f #define DUK_ASC_SPACE 0x20 #define DUK_ASC_EXCLAMATION 0x21 #define DUK_ASC_DOUBLEQUOTE 0x22 #define DUK_ASC_HASH 0x23 #define DUK_ASC_DOLLAR 0x24 #define DUK_ASC_PERCENT 0x25 #define DUK_ASC_AMP 0x26 #define DUK_ASC_SINGLEQUOTE 0x27 #define DUK_ASC_LPAREN 0x28 #define DUK_ASC_RPAREN 0x29 #define DUK_ASC_STAR 0x2a #define DUK_ASC_PLUS 0x2b #define DUK_ASC_COMMA 0x2c #define DUK_ASC_MINUS 0x2d #define DUK_ASC_PERIOD 0x2e #define DUK_ASC_SLASH 0x2f #define DUK_ASC_0 0x30 #define DUK_ASC_1 0x31 #define DUK_ASC_2 0x32 #define DUK_ASC_3 0x33 #define DUK_ASC_4 0x34 #define DUK_ASC_5 0x35 #define DUK_ASC_6 0x36 #define DUK_ASC_7 0x37 #define DUK_ASC_8 0x38 #define DUK_ASC_9 0x39 #define DUK_ASC_COLON 0x3a #define DUK_ASC_SEMICOLON 0x3b #define DUK_ASC_LANGLE 0x3c #define DUK_ASC_EQUALS 0x3d #define DUK_ASC_RANGLE 0x3e #define DUK_ASC_QUESTION 0x3f #define DUK_ASC_ATSIGN 0x40 #define DUK_ASC_UC_A 0x41 #define DUK_ASC_UC_B 0x42 #define DUK_ASC_UC_C 0x43 #define DUK_ASC_UC_D 0x44 #define DUK_ASC_UC_E 0x45 #define DUK_ASC_UC_F 0x46 #define DUK_ASC_UC_G 0x47 #define DUK_ASC_UC_H 0x48 #define DUK_ASC_UC_I 0x49 #define DUK_ASC_UC_J 0x4a #define DUK_ASC_UC_K 0x4b #define DUK_ASC_UC_L 0x4c #define DUK_ASC_UC_M 0x4d #define DUK_ASC_UC_N 0x4e #define DUK_ASC_UC_O 0x4f #define DUK_ASC_UC_P 0x50 #define DUK_ASC_UC_Q 0x51 #define DUK_ASC_UC_R 0x52 #define DUK_ASC_UC_S 0x53 #define DUK_ASC_UC_T 0x54 #define DUK_ASC_UC_U 0x55 #define DUK_ASC_UC_V 0x56 #define DUK_ASC_UC_W 0x57 #define DUK_ASC_UC_X 0x58 #define DUK_ASC_UC_Y 0x59 #define DUK_ASC_UC_Z 0x5a #define DUK_ASC_LBRACKET 0x5b #define DUK_ASC_BACKSLASH 0x5c #define DUK_ASC_RBRACKET 0x5d #define DUK_ASC_CARET 0x5e #define DUK_ASC_UNDERSCORE 0x5f #define DUK_ASC_GRAVE 0x60 #define DUK_ASC_LC_A 0x61 #define DUK_ASC_LC_B 0x62 #define DUK_ASC_LC_C 0x63 #define DUK_ASC_LC_D 0x64 #define DUK_ASC_LC_E 0x65 #define DUK_ASC_LC_F 0x66 #define DUK_ASC_LC_G 0x67 #define DUK_ASC_LC_H 0x68 #define DUK_ASC_LC_I 0x69 #define DUK_ASC_LC_J 0x6a #define DUK_ASC_LC_K 0x6b #define DUK_ASC_LC_L 0x6c #define DUK_ASC_LC_M 0x6d #define DUK_ASC_LC_N 0x6e #define DUK_ASC_LC_O 0x6f #define DUK_ASC_LC_P 0x70 #define DUK_ASC_LC_Q 0x71 #define DUK_ASC_LC_R 0x72 #define DUK_ASC_LC_S 0x73 #define DUK_ASC_LC_T 0x74 #define DUK_ASC_LC_U 0x75 #define DUK_ASC_LC_V 0x76 #define DUK_ASC_LC_W 0x77 #define DUK_ASC_LC_X 0x78 #define DUK_ASC_LC_Y 0x79 #define DUK_ASC_LC_Z 0x7a #define DUK_ASC_LCURLY 0x7b #define DUK_ASC_PIPE 0x7c #define DUK_ASC_RCURLY 0x7d #define DUK_ASC_TILDE 0x7e #define DUK_ASC_DEL 0x7f /* * Miscellaneous */ /* Uppercase A is 0x41, lowercase a is 0x61; OR 0x20 to convert uppercase * to lowercase. */ #define DUK_LOWERCASE_CHAR_ASCII(x) ((x) | 0x20) /* * Unicode tables */ #if defined(DUK_USE_SOURCE_NONBMP) /* * Automatically generated by extract_chars.py, do not edit! */ extern const duk_uint8_t duk_unicode_ids_noa[1116]; #else /* * Automatically generated by extract_chars.py, do not edit! */ extern const duk_uint8_t duk_unicode_ids_noabmp[625]; #endif #if defined(DUK_USE_SOURCE_NONBMP) /* * Automatically generated by extract_chars.py, do not edit! */ extern const duk_uint8_t duk_unicode_ids_m_let_noa[42]; #else /* * Automatically generated by extract_chars.py, do not edit! */ extern const duk_uint8_t duk_unicode_ids_m_let_noabmp[24]; #endif #if defined(DUK_USE_SOURCE_NONBMP) /* * Automatically generated by extract_chars.py, do not edit! */ extern const duk_uint8_t duk_unicode_idp_m_ids_noa[576]; #else /* * Automatically generated by extract_chars.py, do not edit! */ extern const duk_uint8_t duk_unicode_idp_m_ids_noabmp[358]; #endif /* * Automatically generated by extract_caseconv.py, do not edit! */ extern const duk_uint8_t duk_unicode_caseconv_uc[1411]; extern const duk_uint8_t duk_unicode_caseconv_lc[706]; #if defined(DUK_USE_REGEXP_CANON_WORKAROUND) /* * Automatically generated by extract_caseconv.py, do not edit! */ extern const duk_uint16_t duk_unicode_re_canon_lookup[65536]; #endif #if defined(DUK_USE_REGEXP_CANON_BITMAP) /* * Automatically generated by extract_caseconv.py, do not edit! */ #define DUK_CANON_BITMAP_BLKSIZE 32 #define DUK_CANON_BITMAP_BLKSHIFT 5 #define DUK_CANON_BITMAP_BLKMASK 31 extern const duk_uint8_t duk_unicode_re_canon_bitmap[256]; #endif /* * Extern */ /* duk_unicode_support.c */ #if !defined(DUK_SINGLE_FILE) DUK_INTERNAL_DECL const duk_uint8_t duk_unicode_xutf8_markers[7]; DUK_INTERNAL_DECL const duk_uint16_t duk_unicode_re_ranges_digit[2]; DUK_INTERNAL_DECL const duk_uint16_t duk_unicode_re_ranges_white[22]; DUK_INTERNAL_DECL const duk_uint16_t duk_unicode_re_ranges_wordchar[8]; DUK_INTERNAL_DECL const duk_uint16_t duk_unicode_re_ranges_not_digit[4]; DUK_INTERNAL_DECL const duk_uint16_t duk_unicode_re_ranges_not_white[24]; DUK_INTERNAL_DECL const duk_uint16_t duk_unicode_re_ranges_not_wordchar[10]; DUK_INTERNAL_DECL const duk_int8_t duk_is_idchar_tab[128]; #endif /* !DUK_SINGLE_FILE */ /* * Prototypes */ DUK_INTERNAL_DECL duk_small_int_t duk_unicode_get_xutf8_length(duk_ucodepoint_t cp); #if defined(DUK_USE_ASSERTIONS) DUK_INTERNAL_DECL duk_small_int_t duk_unicode_get_cesu8_length(duk_ucodepoint_t cp); #endif DUK_INTERNAL_DECL duk_small_int_t duk_unicode_encode_xutf8(duk_ucodepoint_t cp, duk_uint8_t *out); DUK_INTERNAL_DECL duk_small_int_t duk_unicode_encode_cesu8(duk_ucodepoint_t cp, duk_uint8_t *out); DUK_INTERNAL_DECL duk_small_int_t duk_unicode_decode_xutf8(duk_hthread *thr, const duk_uint8_t **ptr, const duk_uint8_t *ptr_start, const duk_uint8_t *ptr_end, duk_ucodepoint_t *out_cp); DUK_INTERNAL_DECL duk_ucodepoint_t duk_unicode_decode_xutf8_checked(duk_hthread *thr, const duk_uint8_t **ptr, const duk_uint8_t *ptr_start, const duk_uint8_t *ptr_end); DUK_INTERNAL_DECL duk_size_t duk_unicode_unvalidated_utf8_length(const duk_uint8_t *data, duk_size_t blen); DUK_INTERNAL_DECL duk_bool_t duk_unicode_is_utf8_compatible(const duk_uint8_t *buf, duk_size_t len); DUK_INTERNAL_DECL duk_small_int_t duk_unicode_is_whitespace(duk_codepoint_t cp); DUK_INTERNAL_DECL duk_small_int_t duk_unicode_is_line_terminator(duk_codepoint_t cp); DUK_INTERNAL_DECL duk_small_int_t duk_unicode_is_identifier_start(duk_codepoint_t cp); DUK_INTERNAL_DECL duk_small_int_t duk_unicode_is_identifier_part(duk_codepoint_t cp); DUK_INTERNAL_DECL duk_small_int_t duk_unicode_is_letter(duk_codepoint_t cp); DUK_INTERNAL_DECL void duk_unicode_case_convert_string(duk_hthread *thr, duk_bool_t uppercase); #if defined(DUK_USE_REGEXP_SUPPORT) DUK_INTERNAL_DECL duk_codepoint_t duk_unicode_re_canonicalize_char(duk_hthread *thr, duk_codepoint_t cp); DUK_INTERNAL_DECL duk_small_int_t duk_unicode_re_is_wordchar(duk_codepoint_t cp); #endif #endif /* DUK_UNICODE_H_INCLUDED */ /* #include duk_json.h */ #line 1 "duk_json.h" /* * Defines for JSON, especially duk_bi_json.c. */ #if !defined(DUK_JSON_H_INCLUDED) #define DUK_JSON_H_INCLUDED /* Encoding/decoding flags */ #define DUK_JSON_FLAG_ASCII_ONLY (1U << 0) /* escape any non-ASCII characters */ #define DUK_JSON_FLAG_AVOID_KEY_QUOTES (1U << 1) /* avoid key quotes when key is an ASCII Identifier */ #define DUK_JSON_FLAG_EXT_CUSTOM (1U << 2) /* extended types: custom encoding */ #define DUK_JSON_FLAG_EXT_COMPATIBLE (1U << 3) /* extended types: compatible encoding */ /* How much stack to require on entry to object/array encode */ #define DUK_JSON_ENC_REQSTACK 32 /* How much stack to require on entry to object/array decode */ #define DUK_JSON_DEC_REQSTACK 32 /* How large a loop detection stack to use */ #define DUK_JSON_ENC_LOOPARRAY 64 /* Encoding state. Heap object references are all borrowed. */ typedef struct { duk_hthread *thr; duk_bufwriter_ctx bw; /* output bufwriter */ duk_hobject *h_replacer; /* replacer function */ duk_hstring *h_gap; /* gap (if empty string, NULL) */ duk_idx_t idx_proplist; /* explicit PropertyList */ duk_idx_t idx_loop; /* valstack index of loop detection object */ duk_small_uint_t flags; duk_small_uint_t flag_ascii_only; duk_small_uint_t flag_avoid_key_quotes; #if defined(DUK_USE_JX) || defined(DUK_USE_JC) duk_small_uint_t flag_ext_custom; duk_small_uint_t flag_ext_compatible; duk_small_uint_t flag_ext_custom_or_compatible; #endif duk_uint_t recursion_depth; duk_uint_t recursion_limit; duk_uint_t mask_for_undefined; /* type bit mask: types which certainly produce 'undefined' */ #if defined(DUK_USE_JX) || defined(DUK_USE_JC) duk_small_uint_t stridx_custom_undefined; duk_small_uint_t stridx_custom_nan; duk_small_uint_t stridx_custom_neginf; duk_small_uint_t stridx_custom_posinf; duk_small_uint_t stridx_custom_function; #endif duk_hobject *visiting[DUK_JSON_ENC_LOOPARRAY]; /* indexed by recursion_depth */ } duk_json_enc_ctx; typedef struct { duk_hthread *thr; const duk_uint8_t *p; const duk_uint8_t *p_start; const duk_uint8_t *p_end; duk_idx_t idx_reviver; duk_small_uint_t flags; #if defined(DUK_USE_JX) || defined(DUK_USE_JC) duk_small_uint_t flag_ext_custom; duk_small_uint_t flag_ext_compatible; duk_small_uint_t flag_ext_custom_or_compatible; #endif duk_int_t recursion_depth; duk_int_t recursion_limit; } duk_json_dec_ctx; #endif /* DUK_JSON_H_INCLUDED */ /* #include duk_js.h */ #line 1 "duk_js.h" /* * ECMAScript execution, support primitives. */ #if !defined(DUK_JS_H_INCLUDED) #define DUK_JS_H_INCLUDED /* Flags for call handling. Lowest flags must match bytecode DUK_BC_CALL_FLAG_xxx 1:1. */ #define DUK_CALL_FLAG_TAILCALL (1U << 0) /* setup for a tail call */ #define DUK_CALL_FLAG_CONSTRUCT (1U << 1) /* constructor call (i.e. called as 'new Foo()') */ #define DUK_CALL_FLAG_CALLED_AS_EVAL (1U << 2) /* call was made using the identifier 'eval' */ #define DUK_CALL_FLAG_ALLOW_ECMATOECMA (1U << 3) /* ecma-to-ecma call with executor reuse is possible */ #define DUK_CALL_FLAG_DIRECT_EVAL (1U << 4) /* call is a direct eval call */ #define DUK_CALL_FLAG_CONSTRUCT_PROXY (1U << 5) /* handled via 'construct' proxy trap, check return value invariant(s) */ #define DUK_CALL_FLAG_DEFAULT_INSTANCE_UPDATED \ (1U << 6) /* prototype of 'default instance' updated, temporary flag in call handling */ /* Flags for duk_js_equals_helper(). */ #define DUK_EQUALS_FLAG_SAMEVALUE (1U << 0) /* use SameValue instead of non-strict equality */ #define DUK_EQUALS_FLAG_STRICT (1U << 1) /* use strict equality instead of non-strict equality */ /* Flags for duk_js_compare_helper(). */ #define DUK_COMPARE_FLAG_NEGATE (1U << 0) /* negate result */ #define DUK_COMPARE_FLAG_EVAL_LEFT_FIRST (1U << 1) /* eval left argument first */ /* conversions, coercions, comparison, etc */ DUK_INTERNAL_DECL duk_bool_t duk_js_toboolean(duk_tval *tv); DUK_INTERNAL_DECL duk_double_t duk_js_tonumber(duk_hthread *thr, duk_tval *tv); DUK_INTERNAL_DECL duk_double_t duk_js_tointeger_number(duk_double_t x); DUK_INTERNAL_DECL duk_double_t duk_js_tointeger(duk_hthread *thr, duk_tval *tv); DUK_INTERNAL_DECL duk_uint32_t duk_js_touint32(duk_hthread *thr, duk_tval *tv); DUK_INTERNAL_DECL duk_int32_t duk_js_toint32(duk_hthread *thr, duk_tval *tv); DUK_INTERNAL_DECL duk_uint16_t duk_js_touint16(duk_hthread *thr, duk_tval *tv); DUK_INTERNAL_DECL duk_uarridx_t duk_js_to_arrayindex_string(const duk_uint8_t *str, duk_uint32_t blen); #if !defined(DUK_USE_HSTRING_ARRIDX) DUK_INTERNAL_DECL duk_uarridx_t duk_js_to_arrayindex_hstring_fast_known(duk_hstring *h); DUK_INTERNAL_DECL duk_uarridx_t duk_js_to_arrayindex_hstring_fast(duk_hstring *h); #endif DUK_INTERNAL_DECL duk_bool_t duk_js_equals_helper(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y, duk_small_uint_t flags); DUK_INTERNAL_DECL duk_small_int_t duk_js_data_compare(const duk_uint8_t *buf1, const duk_uint8_t *buf2, duk_size_t len1, duk_size_t len2); DUK_INTERNAL_DECL duk_small_int_t duk_js_string_compare(duk_hstring *h1, duk_hstring *h2); #if 0 /* unused */ DUK_INTERNAL_DECL duk_small_int_t duk_js_buffer_compare(duk_heap *heap, duk_hbuffer *h1, duk_hbuffer *h2); #endif DUK_INTERNAL_DECL duk_bool_t duk_js_compare_helper(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y, duk_small_uint_t flags); DUK_INTERNAL_DECL duk_bool_t duk_js_instanceof(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y); #if defined(DUK_USE_SYMBOL_BUILTIN) DUK_INTERNAL_DECL duk_bool_t duk_js_instanceof_ordinary(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y); #endif DUK_INTERNAL_DECL duk_bool_t duk_js_in(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y); DUK_INTERNAL_DECL duk_small_uint_t duk_js_typeof_stridx(duk_tval *tv_x); DUK_INTERNAL_DECL duk_bool_t duk_js_isarray_hobject(duk_hobject *h); DUK_INTERNAL_DECL duk_bool_t duk_js_isarray(duk_tval *tv); /* arithmetic */ DUK_INTERNAL_DECL double duk_js_arith_pow(double x, double y); DUK_INTERNAL_DECL double duk_js_arith_mod(double x, double y); #define duk_js_equals(thr, tv_x, tv_y) duk_js_equals_helper((thr), (tv_x), (tv_y), 0) #define duk_js_strict_equals(tv_x, tv_y) duk_js_equals_helper(NULL, (tv_x), (tv_y), DUK_EQUALS_FLAG_STRICT) #define duk_js_samevalue(tv_x, tv_y) duk_js_equals_helper(NULL, (tv_x), (tv_y), DUK_EQUALS_FLAG_SAMEVALUE) /* E5 Sections 11.8.1, 11.8.5; x < y */ #define duk_js_lessthan(thr, tv_x, tv_y) duk_js_compare_helper((thr), (tv_x), (tv_Y), DUK_COMPARE_FLAG_EVAL_LEFT_FIRST) /* E5 Sections 11.8.2, 11.8.5; x > y --> y < x */ #define duk_js_greaterthan(thr, tv_x, tv_y) duk_js_compare_helper((thr), (tv_y), (tv_x), 0) /* E5 Sections 11.8.3, 11.8.5; x <= y --> not (x > y) --> not (y < x) */ #define duk_js_lessthanorequal(thr, tv_x, tv_y) duk_js_compare_helper((thr), (tv_y), (tv_x), DUK_COMPARE_FLAG_NEGATE) /* E5 Sections 11.8.4, 11.8.5; x >= y --> not (x < y) */ #define duk_js_greaterthanorequal(thr, tv_x, tv_y) \ duk_js_compare_helper((thr), (tv_x), (tv_y), DUK_COMPARE_FLAG_EVAL_LEFT_FIRST | DUK_COMPARE_FLAG_NEGATE) /* identifiers and environment handling */ #if 0 /*unused*/ DUK_INTERNAL duk_bool_t duk_js_hasvar_envrec(duk_hthread *thr, duk_hobject *env, duk_hstring *name); #endif DUK_INTERNAL_DECL duk_bool_t duk_js_getvar_envrec(duk_hthread *thr, duk_hobject *env, duk_hstring *name, duk_bool_t throw_flag); DUK_INTERNAL_DECL duk_bool_t duk_js_getvar_activation(duk_hthread *thr, duk_activation *act, duk_hstring *name, duk_bool_t throw_flag); DUK_INTERNAL_DECL void duk_js_putvar_envrec(duk_hthread *thr, duk_hobject *env, duk_hstring *name, duk_tval *val, duk_bool_t strict); DUK_INTERNAL_DECL void duk_js_putvar_activation(duk_hthread *thr, duk_activation *act, duk_hstring *name, duk_tval *val, duk_bool_t strict); #if 0 /*unused*/ DUK_INTERNAL_DECL duk_bool_t duk_js_delvar_envrec(duk_hthread *thr, duk_hobject *env, duk_hstring *name); #endif DUK_INTERNAL_DECL duk_bool_t duk_js_delvar_activation(duk_hthread *thr, duk_activation *act, duk_hstring *name); DUK_INTERNAL_DECL duk_bool_t duk_js_declvar_activation(duk_hthread *thr, duk_activation *act, duk_hstring *name, duk_tval *val, duk_small_uint_t prop_flags, duk_bool_t is_func_decl); DUK_INTERNAL_DECL void duk_js_init_activation_environment_records_delayed(duk_hthread *thr, duk_activation *act); DUK_INTERNAL_DECL void duk_js_close_environment_record(duk_hthread *thr, duk_hobject *env); DUK_INTERNAL_DECL duk_hobject *duk_create_activation_environment_record(duk_hthread *thr, duk_hobject *func, duk_size_t bottom_byteoff); DUK_INTERNAL_DECL void duk_js_push_closure(duk_hthread *thr, duk_hcompfunc *fun_temp, duk_hobject *outer_var_env, duk_hobject *outer_lex_env, duk_bool_t add_auto_proto); /* call handling */ DUK_INTERNAL_DECL void duk_native_stack_check(duk_hthread *thr); DUK_INTERNAL_DECL duk_int_t duk_handle_call_unprotected(duk_hthread *thr, duk_idx_t idx_func, duk_small_uint_t call_flags); DUK_INTERNAL_DECL duk_int_t duk_handle_call_unprotected_nargs(duk_hthread *thr, duk_idx_t nargs, duk_small_uint_t call_flags); DUK_INTERNAL_DECL duk_int_t duk_handle_safe_call(duk_hthread *thr, duk_safe_call_function func, void *udata, duk_idx_t num_stack_args, duk_idx_t num_stack_res); DUK_INTERNAL_DECL void duk_call_construct_postprocess(duk_hthread *thr, duk_small_uint_t proxy_invariant); #if defined(DUK_USE_VERBOSE_ERRORS) DUK_INTERNAL_DECL void duk_call_setup_propcall_error(duk_hthread *thr, duk_tval *tv_base, duk_tval *tv_key); #endif /* bytecode execution */ DUK_INTERNAL_DECL void duk_js_execute_bytecode(duk_hthread *exec_thr); #endif /* DUK_JS_H_INCLUDED */ /* #include duk_numconv.h */ #line 1 "duk_numconv.h" /* * Number-to-string conversion. The semantics of these is very tightly * bound with the ECMAScript semantics required for call sites. */ #if !defined(DUK_NUMCONV_H_INCLUDED) #define DUK_NUMCONV_H_INCLUDED /* Output a specified number of digits instead of using the shortest * form. Used for toPrecision() and toFixed(). */ #define DUK_N2S_FLAG_FIXED_FORMAT (1U << 0) /* Force exponential format. Used for toExponential(). */ #define DUK_N2S_FLAG_FORCE_EXP (1U << 1) /* If number would need zero padding (for whole number part), use * exponential format instead. E.g. if input number is 12300, 3 * digits are generated ("123"), output "1.23e+4" instead of "12300". * Used for toPrecision(). */ #define DUK_N2S_FLAG_NO_ZERO_PAD (1U << 2) /* Digit count indicates number of fractions (i.e. an absolute * digit index instead of a relative one). Used together with * DUK_N2S_FLAG_FIXED_FORMAT for toFixed(). */ #define DUK_N2S_FLAG_FRACTION_DIGITS (1U << 3) /* * String-to-number conversion */ /* Maximum exponent value when parsing numbers. This is not strictly * compliant as there should be no upper limit, but as we parse the * exponent without a bigint, impose some limit. The limit should be * small enough that multiplying it (or limit-1 to be precise) won't * overflow signed 32-bit integer range. Exponent is only parsed with * radix 10, but with maximum radix (36) a safe limit is: * (10000000*36).toString(16) -> '15752a00' */ #define DUK_S2N_MAX_EXPONENT 10000000L /* Trim white space (= allow leading and trailing whitespace) */ #define DUK_S2N_FLAG_TRIM_WHITE (1U << 0) /* Allow exponent */ #define DUK_S2N_FLAG_ALLOW_EXP (1U << 1) /* Allow trailing garbage (e.g. treat "123foo" as "123) */ #define DUK_S2N_FLAG_ALLOW_GARBAGE (1U << 2) /* Allow leading plus sign */ #define DUK_S2N_FLAG_ALLOW_PLUS (1U << 3) /* Allow leading minus sign */ #define DUK_S2N_FLAG_ALLOW_MINUS (1U << 4) /* Allow 'Infinity' */ #define DUK_S2N_FLAG_ALLOW_INF (1U << 5) /* Allow fraction part */ #define DUK_S2N_FLAG_ALLOW_FRAC (1U << 6) /* Allow naked fraction (e.g. ".123") */ #define DUK_S2N_FLAG_ALLOW_NAKED_FRAC (1U << 7) /* Allow empty fraction (e.g. "123.") */ #define DUK_S2N_FLAG_ALLOW_EMPTY_FRAC (1U << 8) /* Allow empty string to be interpreted as 0 */ #define DUK_S2N_FLAG_ALLOW_EMPTY_AS_ZERO (1U << 9) /* Allow leading zeroes (e.g. "0123" -> "123") */ #define DUK_S2N_FLAG_ALLOW_LEADING_ZERO (1U << 10) /* Allow automatic detection of hex base ("0x" or "0X" prefix), * overrides radix argument and forces integer mode. */ #define DUK_S2N_FLAG_ALLOW_AUTO_HEX_INT (1U << 11) /* Allow automatic detection of legacy octal base ("0n"), * overrides radix argument and forces integer mode. */ #define DUK_S2N_FLAG_ALLOW_AUTO_LEGACY_OCT_INT (1U << 12) /* Allow automatic detection of ES2015 octal base ("0o123"), * overrides radix argument and forces integer mode. */ #define DUK_S2N_FLAG_ALLOW_AUTO_OCT_INT (1U << 13) /* Allow automatic detection of ES2015 binary base ("0b10001"), * overrides radix argument and forces integer mode. */ #define DUK_S2N_FLAG_ALLOW_AUTO_BIN_INT (1U << 14) /* * Prototypes */ DUK_INTERNAL_DECL void duk_numconv_stringify(duk_hthread *thr, duk_small_int_t radix, duk_small_int_t digits, duk_small_uint_t flags); DUK_INTERNAL_DECL void duk_numconv_parse(duk_hthread *thr, duk_small_int_t radix, duk_small_uint_t flags); #endif /* DUK_NUMCONV_H_INCLUDED */ /* #include duk_bi_protos.h */ #line 1 "duk_bi_protos.h" /* * Prototypes for built-in functions not automatically covered by the * header declarations emitted by genbuiltins.py. */ #if !defined(DUK_BUILTIN_PROTOS_H_INCLUDED) #define DUK_BUILTIN_PROTOS_H_INCLUDED /* Buffer size needed for ISO 8601 formatting. * Accurate value is 32 + 1 for NUL termination: * >>> len('+123456-01-23T12:34:56.123+12:34') * 32 * Include additional space to be safe. */ #define DUK_BI_DATE_ISO8601_BUFSIZE 40 /* Helpers exposed for internal use */ DUK_INTERNAL_DECL void duk_bi_date_timeval_to_parts(duk_double_t d, duk_int_t *parts, duk_double_t *dparts, duk_small_uint_t flags); DUK_INTERNAL_DECL duk_double_t duk_bi_date_get_timeval_from_dparts(duk_double_t *dparts, duk_small_uint_t flags); DUK_INTERNAL_DECL duk_bool_t duk_bi_date_is_leap_year(duk_int_t year); DUK_INTERNAL_DECL duk_bool_t duk_bi_date_timeval_in_valid_range(duk_double_t x); DUK_INTERNAL_DECL duk_bool_t duk_bi_date_year_in_valid_range(duk_double_t year); DUK_INTERNAL_DECL duk_bool_t duk_bi_date_timeval_in_leeway_range(duk_double_t x); /* Built-in providers */ #if defined(DUK_USE_DATE_NOW_GETTIMEOFDAY) DUK_INTERNAL_DECL duk_double_t duk_bi_date_get_now_gettimeofday(void); #endif #if defined(DUK_USE_DATE_NOW_TIME) DUK_INTERNAL_DECL duk_double_t duk_bi_date_get_now_time(void); #endif #if defined(DUK_USE_DATE_NOW_WINDOWS) DUK_INTERNAL_DECL duk_double_t duk_bi_date_get_now_windows(void); #endif #if defined(DUK_USE_DATE_NOW_WINDOWS_SUBMS) DUK_INTERNAL_DECL duk_double_t duk_bi_date_get_now_windows_subms(void); #endif #if defined(DUK_USE_DATE_TZO_GMTIME_R) || defined(DUK_USE_DATE_TZO_GMTIME_S) || defined(DUK_USE_DATE_TZO_GMTIME) DUK_INTERNAL_DECL duk_int_t duk_bi_date_get_local_tzoffset_gmtime(duk_double_t d); #endif #if defined(DUK_USE_DATE_TZO_WINDOWS) DUK_INTERNAL_DECL duk_int_t duk_bi_date_get_local_tzoffset_windows(duk_double_t d); #endif #if defined(DUK_USE_DATE_TZO_WINDOWS_NO_DST) DUK_INTERNAL_DECL duk_int_t duk_bi_date_get_local_tzoffset_windows_no_dst(duk_double_t d); #endif #if defined(DUK_USE_DATE_PRS_STRPTIME) DUK_INTERNAL_DECL duk_bool_t duk_bi_date_parse_string_strptime(duk_hthread *thr, const char *str); #endif #if defined(DUK_USE_DATE_PRS_GETDATE) DUK_INTERNAL_DECL duk_bool_t duk_bi_date_parse_string_getdate(duk_hthread *thr, const char *str); #endif #if defined(DUK_USE_DATE_FMT_STRFTIME) DUK_INTERNAL_DECL duk_bool_t duk_bi_date_format_parts_strftime(duk_hthread *thr, duk_int_t *parts, duk_int_t tzoffset, duk_small_uint_t flags); #endif #if defined(DUK_USE_GET_MONOTONIC_TIME_CLOCK_GETTIME) DUK_INTERNAL_DECL duk_double_t duk_bi_date_get_monotonic_time_clock_gettime(void); #endif #if defined(DUK_USE_GET_MONOTONIC_TIME_WINDOWS_QPC) DUK_INTERNAL_DECL duk_double_t duk_bi_date_get_monotonic_time_windows_qpc(void); #endif DUK_INTERNAL_DECL void duk_bi_json_parse_helper(duk_hthread *thr, duk_idx_t idx_value, duk_idx_t idx_reviver, duk_small_uint_t flags); DUK_INTERNAL_DECL void duk_bi_json_stringify_helper(duk_hthread *thr, duk_idx_t idx_value, duk_idx_t idx_replacer, duk_idx_t idx_space, duk_small_uint_t flags); DUK_INTERNAL_DECL duk_ret_t duk_textdecoder_decode_utf8_nodejs(duk_hthread *thr); #if defined(DUK_USE_ES6_PROXY) DUK_INTERNAL_DECL void duk_proxy_ownkeys_postprocess(duk_hthread *thr, duk_hobject *h_proxy_target, duk_uint_t flags); #endif #endif /* DUK_BUILTIN_PROTOS_H_INCLUDED */ /* #include duk_selftest.h */ #line 1 "duk_selftest.h" /* * Selftest code */ #if !defined(DUK_SELFTEST_H_INCLUDED) #define DUK_SELFTEST_H_INCLUDED #if defined(DUK_USE_SELF_TESTS) DUK_INTERNAL_DECL duk_uint_t duk_selftest_run_tests(duk_alloc_function alloc_func, duk_realloc_function realloc_func, duk_free_function free_func, void *udata); #endif #endif /* DUK_SELFTEST_H_INCLUDED */ #line 76 "duk_internal.h" #endif /* DUK_INTERNAL_H_INCLUDED */ #line 10 "duk_replacements.c" #if defined(DUK_USE_COMPUTED_NAN) DUK_INTERNAL double duk_computed_nan; #endif #if defined(DUK_USE_COMPUTED_INFINITY) DUK_INTERNAL double duk_computed_infinity; #endif #if defined(DUK_USE_REPL_FPCLASSIFY) DUK_INTERNAL int duk_repl_fpclassify(double x) { duk_double_union u; duk_uint_fast16_t expt; duk_small_int_t mzero; u.d = x; expt = (duk_uint_fast16_t) (u.us[DUK_DBL_IDX_US0] & 0x7ff0UL); if (expt > 0x0000UL && expt < 0x7ff0UL) { /* expt values [0x001,0x7fe] = normal */ return DUK_FP_NORMAL; } mzero = (u.ui[DUK_DBL_IDX_UI1] == 0 && (u.ui[DUK_DBL_IDX_UI0] & 0x000fffffUL) == 0); if (expt == 0x0000UL) { /* expt 0x000 is zero/subnormal */ if (mzero) { return DUK_FP_ZERO; } else { return DUK_FP_SUBNORMAL; } } else { /* expt 0xfff is infinite/nan */ if (mzero) { return DUK_FP_INFINITE; } else { return DUK_FP_NAN; } } } #endif #if defined(DUK_USE_REPL_SIGNBIT) DUK_INTERNAL int duk_repl_signbit(double x) { duk_double_union u; u.d = x; return (int) (u.uc[DUK_DBL_IDX_UC0] & 0x80UL); } #endif #if defined(DUK_USE_REPL_ISFINITE) DUK_INTERNAL int duk_repl_isfinite(double x) { int c = DUK_FPCLASSIFY(x); if (c == DUK_FP_NAN || c == DUK_FP_INFINITE) { return 0; } else { return 1; } } #endif #if defined(DUK_USE_REPL_ISNAN) DUK_INTERNAL int duk_repl_isnan(double x) { int c = DUK_FPCLASSIFY(x); return (c == DUK_FP_NAN); } #endif #if defined(DUK_USE_REPL_ISINF) DUK_INTERNAL int duk_repl_isinf(double x) { int c = DUK_FPCLASSIFY(x); return (c == DUK_FP_INFINITE); } #endif #line 1 "duk_debug_macros.c" /* * Debugging macro calls. */ /* #include duk_internal.h -> already included */ #if defined(DUK_USE_DEBUG) /* * Debugging enabled */ #include <stdio.h> #include <stdlib.h> #include <stdarg.h> #if !defined(DUK_USE_DEBUG_WRITE) #error debugging enabled (DUK_USE_DEBUG) but DUK_USE_DEBUG_WRITE not defined #endif #define DUK__DEBUG_BUFSIZE DUK_USE_DEBUG_BUFSIZE #if defined(DUK_USE_VARIADIC_MACROS) DUK_INTERNAL void duk_debug_log(duk_int_t level, const char *file, duk_int_t line, const char *func, const char *fmt, ...) { va_list ap; long arg_level; const char *arg_file; long arg_line; const char *arg_func; const char *arg_msg; char buf[DUK__DEBUG_BUFSIZE]; va_start(ap, fmt); duk_memzero((void *) buf, (size_t) DUK__DEBUG_BUFSIZE); duk_debug_vsnprintf(buf, DUK__DEBUG_BUFSIZE - 1, fmt, ap); arg_level = (long) level; arg_file = (const char *) file; arg_line = (long) line; arg_func = (const char *) func; arg_msg = (const char *) buf; DUK_USE_DEBUG_WRITE(arg_level, arg_file, arg_line, arg_func, arg_msg); va_end(ap); } #else /* DUK_USE_VARIADIC_MACROS */ DUK_INTERNAL char duk_debug_file_stash[DUK_DEBUG_STASH_SIZE]; DUK_INTERNAL duk_int_t duk_debug_line_stash; DUK_INTERNAL char duk_debug_func_stash[DUK_DEBUG_STASH_SIZE]; DUK_INTERNAL duk_int_t duk_debug_level_stash; DUK_INTERNAL void duk_debug_log(const char *fmt, ...) { va_list ap; long arg_level; const char *arg_file; long arg_line; const char *arg_func; const char *arg_msg; char buf[DUK__DEBUG_BUFSIZE]; va_start(ap, fmt); duk_memzero((void *) buf, (size_t) DUK__DEBUG_BUFSIZE); duk_debug_vsnprintf(buf, DUK__DEBUG_BUFSIZE - 1, fmt, ap); arg_level = (long) duk_debug_level_stash; arg_file = (const char *) duk_debug_file_stash; arg_line = (long) duk_debug_line_stash; arg_func = (const char *) duk_debug_func_stash; arg_msg = (const char *) buf; DUK_USE_DEBUG_WRITE(arg_level, arg_file, arg_line, arg_func, arg_msg); va_end(ap); } #endif /* DUK_USE_VARIADIC_MACROS */ #else /* DUK_USE_DEBUG */ /* * Debugging disabled */ #endif /* DUK_USE_DEBUG */ /* automatic undefs */ #undef DUK__DEBUG_BUFSIZE #line 1 "duk_builtins.c" /* * Automatically generated by genbuiltins.py, do not edit! */ /* #include duk_internal.h -> already included */ #if defined(DUK_USE_ASSERTIONS) #define DUK__REFCINIT(refc) 0 /*h_assert_refcount*/, (refc) /*actual*/ #else #define DUK__REFCINIT(refc) (refc) /*actual*/ #endif #if defined(DUK_USE_ROM_STRINGS) #error ROM support not enabled, rerun configure.py with --rom-support #else /* DUK_USE_ROM_STRINGS */ DUK_INTERNAL const duk_uint8_t duk_strings_data[972] = { 79,40,209,144,168,105,6,78,54,139,89,185,44,48,46,90,120,8,154,140,35,103, 35,113,193,73,5,52,112,180,104,166,135,52,188,4,98,12,27,146,156,80,211,31, 129,115,150,64,52,220,109,24,18,68,156,24,38,67,114,36,55,9,119,151,132, 140,93,18,113,128,153,201,212,201,205,2,248,8,196,24,224,104,82,146,40,224, 193,48,114,168,37,147,196,54,123,28,4,98,12,43,148,67,103,177,192,70,32, 196,121,68,54,123,28,18,192,199,144,124,4,98,12,43,136,108,244,117,184,8, 196,24,95,40,134,207,71,91,128,140,65,133,113,13,158,158,151,1,24,131,11, 229,16,217,233,233,112,17,136,48,206,21,110,4,244,244,184,8,196,24,103,10, 183,2,122,218,156,4,98,12,24,203,112,64,179,113,193,79,8,218,155,131,32, 184,70,212,220,13,10,82,68,252,123,144,217,146,38,228,207,18,0,100,37,64, 178,212,11,161,17,104,162,96,10,200,193,57,165,65,169,16,5,100,81,27,70,18, 32,10,200,68,185,13,116,221,197,184,64,89,57,41,197,13,49,234,5,208,156, 113,87,55,118,147,20,187,56,161,166,92,221,212,73,210,236,226,134,153,115, 119,76,201,203,179,138,26,99,73,212,136,136,164,25,174,137,56,32,72,137, 101,23,52,45,13,34,86,9,79,136,104,201,114,149,96,52,138,134,140,151,75, 226,233,186,120,121,22,39,54,83,141,5,55,68,236,36,164,3,16,225,115,150,64, 52,205,163,2,72,154,83,138,26,99,75,12,11,150,103,5,36,20,211,70,140,133, 67,72,49,241,160,227,81,196,52,168,106,39,132,252,183,136,105,80,212,79,2, 249,110,128,126,88,95,133,109,237,237,237,151,235,127,46,249,119,203,190, 186,206,33,181,2,208,61,190,12,19,34,65,19,81,132,108,228,97,1,107,33,12, 32,45,100,137,64,247,175,9,19,155,41,198,130,155,134,69,146,100,227,226, 231,146,51,192,204,73,140,224,145,221,102,241,68,196,169,248,30,75,12,11, 151,242,233,187,143,138,24,137,162,164,255,253,63,3,201,97,129,114,254,92, 112,75,136,108,166,6,136,159,255,167,224,121,44,48,46,95,203,166,238,74, 113,67,77,201,128,223,255,223,224,121,44,48,46,95,203,145,46,9,205,16,39, 201,62,36,0,192,21,147,255,238,145,39,199,197,211,116,240,242,113,197,78, 214,211,226,233,187,107,105,19,119,37,56,161,166,52,221,212,201,205,36,240, 242,16,96,152,12,26,20,164,137,150,70,154,103,28,137,50,202,96,18,132,241, 41,104,105,56,218,48,36,138,183,57,56,128,68,24,38,2,52,12,34,10,133,147, 141,3,8,119,185,13,153,34,125,206,76,17,49,38,93,206,52,151,154,119,56,28, 76,130,112,200,141,206,21,209,96,23,35,238,114,160,139,0,243,238,114,78, 164,68,68,110,113,226,210,90,26,66,110,113,128,121,247,57,80,68,141,170, 183,56,84,52,11,70,73,19,110,114,160,93,8,113,57,143,66,200,84,53,244,154, 73,24,240,81,32,38,68,18,49,228,207,23,88,100,109,70,114,92,193,4,137,173, 168,36,220,73,19,247,247,182,168,209,144,187,223,58,156,104,79,190,183,127, 123,105,160,110,247,206,167,26,19,239,173,223,222,218,67,75,189,243,169, 198,132,251,235,183,247,182,154,134,151,123,231,83,141,9,247,215,111,239, 109,22,141,22,247,206,167,26,19,239,172,223,218,45,26,47,157,78,52,39,223, 74,24,144,10,32,129,34,20,64,152,142,129,57,179,67,104,68,12,129,161,140, 72,156,100,40,40,185,152,100,89,38,65,13,196,34,228,67,149,13,2,215,129, 149,209,65,104,209,77,14,104,144,81,33,170,67,101,48,52,68,113,70,210,88, 209,36,233,22,154,86,68,196,114,76,232,145,102,120,186,195,156,112,105,225, 228,113,71,80,68,162,115,101,50,85,200,25,108,116,44,132,178,38,114,137,96, 148,136,70,209,134,37,222,232,204,228,188,200,209,200,200,99,221,25,150,84, 121,34,70,209,107,36,227,66,20,160,92,136,164,49,235,35,8,217,201,40,108, 201,18,128,68,26,201,51,188,2,80,12,67,190,40,168,38,68,190,46,153,5,50,12, 207,160,86,129,26,83,4,208,34,225,4,88,192, }; #endif /* DUK_USE_ROM_STRINGS */ #if defined(DUK_USE_ROM_OBJECTS) #error ROM support not enabled, rerun configure.py with --rom-support #else /* DUK_USE_ROM_OBJECTS */ /* native functions: 185 */ DUK_INTERNAL const duk_c_function duk_bi_native_functions[185] = { NULL, duk_bi_array_constructor, duk_bi_array_constructor_is_array, duk_bi_array_prototype_concat, duk_bi_array_prototype_indexof_shared, duk_bi_array_prototype_iter_shared, duk_bi_array_prototype_join_shared, duk_bi_array_prototype_pop, duk_bi_array_prototype_push, duk_bi_array_prototype_reduce_shared, duk_bi_array_prototype_reverse, duk_bi_array_prototype_shift, duk_bi_array_prototype_slice, duk_bi_array_prototype_sort, duk_bi_array_prototype_splice, duk_bi_array_prototype_to_string, duk_bi_array_prototype_unshift, duk_bi_arraybuffer_constructor, duk_bi_arraybuffer_isview, duk_bi_boolean_constructor, duk_bi_boolean_prototype_tostring_shared, duk_bi_buffer_compare_shared, duk_bi_buffer_readfield, duk_bi_buffer_slice_shared, duk_bi_buffer_writefield, duk_bi_cbor_decode, duk_bi_cbor_encode, duk_bi_dataview_constructor, duk_bi_date_constructor, duk_bi_date_constructor_now, duk_bi_date_constructor_parse, duk_bi_date_constructor_utc, duk_bi_date_prototype_get_shared, duk_bi_date_prototype_get_timezone_offset, duk_bi_date_prototype_set_shared, duk_bi_date_prototype_set_time, duk_bi_date_prototype_to_json, duk_bi_date_prototype_toprimitive, duk_bi_date_prototype_tostring_shared, duk_bi_date_prototype_value_of, duk_bi_duktape_object_act, duk_bi_duktape_object_compact, duk_bi_duktape_object_dec, duk_bi_duktape_object_enc, duk_bi_duktape_object_fin, duk_bi_duktape_object_gc, duk_bi_duktape_object_info, duk_bi_error_constructor_shared, duk_bi_error_prototype_filename_getter, duk_bi_error_prototype_filename_setter, duk_bi_error_prototype_linenumber_getter, duk_bi_error_prototype_linenumber_setter, duk_bi_error_prototype_stack_getter, duk_bi_error_prototype_stack_setter, duk_bi_error_prototype_to_string, duk_bi_function_constructor, duk_bi_function_prototype, duk_bi_function_prototype_apply, duk_bi_function_prototype_bind, duk_bi_function_prototype_call, duk_bi_function_prototype_hasinstance, duk_bi_function_prototype_to_string, duk_bi_global_object_decode_uri, duk_bi_global_object_decode_uri_component, duk_bi_global_object_encode_uri, duk_bi_global_object_encode_uri_component, duk_bi_global_object_escape, duk_bi_global_object_eval, duk_bi_global_object_is_finite, duk_bi_global_object_is_nan, duk_bi_global_object_parse_float, duk_bi_global_object_parse_int, duk_bi_global_object_unescape, duk_bi_json_object_parse, duk_bi_json_object_stringify, duk_bi_math_object_clz32, duk_bi_math_object_hypot, duk_bi_math_object_imul, duk_bi_math_object_max, duk_bi_math_object_min, duk_bi_math_object_onearg_shared, duk_bi_math_object_random, duk_bi_math_object_sign, duk_bi_math_object_twoarg_shared, duk_bi_native_function_length, duk_bi_native_function_name, duk_bi_nodejs_buffer_byte_length, duk_bi_nodejs_buffer_concat, duk_bi_nodejs_buffer_constructor, duk_bi_nodejs_buffer_copy, duk_bi_nodejs_buffer_fill, duk_bi_nodejs_buffer_is_buffer, duk_bi_nodejs_buffer_is_encoding, duk_bi_nodejs_buffer_tojson, duk_bi_nodejs_buffer_tostring, duk_bi_nodejs_buffer_write, duk_bi_number_check_shared, duk_bi_number_constructor, duk_bi_number_prototype_to_exponential, duk_bi_number_prototype_to_fixed, duk_bi_number_prototype_to_locale_string, duk_bi_number_prototype_to_precision, duk_bi_number_prototype_to_string, duk_bi_number_prototype_value_of, duk_bi_object_constructor, duk_bi_object_constructor_assign, duk_bi_object_constructor_create, duk_bi_object_constructor_define_properties, duk_bi_object_constructor_define_property, duk_bi_object_constructor_get_own_property_descriptor, duk_bi_object_constructor_is, duk_bi_object_constructor_is_extensible, duk_bi_object_constructor_is_sealed_frozen_shared, duk_bi_object_constructor_keys_shared, duk_bi_object_constructor_prevent_extensions, duk_bi_object_constructor_seal_freeze_shared, duk_bi_object_getprototype_shared, duk_bi_object_prototype_defineaccessor, duk_bi_object_prototype_has_own_property, duk_bi_object_prototype_is_prototype_of, duk_bi_object_prototype_lookupaccessor, duk_bi_object_prototype_property_is_enumerable, duk_bi_object_prototype_to_locale_string, duk_bi_object_prototype_to_string, duk_bi_object_prototype_value_of, duk_bi_object_setprototype_shared, duk_bi_performance_now, duk_bi_pointer_constructor, duk_bi_pointer_prototype_tostring_shared, duk_bi_proxy_constructor, duk_bi_reflect_apply, duk_bi_reflect_construct, duk_bi_reflect_object_delete_property, duk_bi_reflect_object_get, duk_bi_reflect_object_has, duk_bi_reflect_object_set, duk_bi_regexp_constructor, duk_bi_regexp_prototype_exec, duk_bi_regexp_prototype_flags, duk_bi_regexp_prototype_shared_getter, duk_bi_regexp_prototype_test, duk_bi_regexp_prototype_tostring, duk_bi_string_constructor, duk_bi_string_constructor_from_char_code, duk_bi_string_constructor_from_code_point, duk_bi_string_prototype_caseconv_shared, duk_bi_string_prototype_char_at, duk_bi_string_prototype_char_code_at, duk_bi_string_prototype_concat, duk_bi_string_prototype_includes, duk_bi_string_prototype_indexof_shared, duk_bi_string_prototype_locale_compare, duk_bi_string_prototype_match, duk_bi_string_prototype_repeat, duk_bi_string_prototype_replace, duk_bi_string_prototype_search, duk_bi_string_prototype_slice, duk_bi_string_prototype_split, duk_bi_string_prototype_startswith_endswith, duk_bi_string_prototype_substr, duk_bi_string_prototype_substring, duk_bi_string_prototype_to_string, duk_bi_string_prototype_trim, duk_bi_symbol_constructor_shared, duk_bi_symbol_key_for, duk_bi_symbol_toprimitive, duk_bi_symbol_tostring_shared, duk_bi_textdecoder_constructor, duk_bi_textdecoder_prototype_decode, duk_bi_textdecoder_prototype_shared_getter, duk_bi_textencoder_constructor, duk_bi_textencoder_prototype_encode, duk_bi_textencoder_prototype_encoding_getter, duk_bi_thread_constructor, duk_bi_thread_current, duk_bi_thread_resume, duk_bi_thread_yield, duk_bi_type_error_thrower, duk_bi_typedarray_buffer_getter, duk_bi_typedarray_bytelength_getter, duk_bi_typedarray_byteoffset_getter, duk_bi_typedarray_constructor, duk_bi_typedarray_set, duk_bi_uint8array_allocplain, duk_bi_uint8array_plainof, }; #if defined(DUK_USE_DOUBLE_LE) DUK_INTERNAL const duk_uint8_t duk_builtins_data[4281] = { 144,148,105,226,32,68,52,228,254,12,104,202,37,132,52,167,194,138,105,245, 124,57,28,211,57,18,64,52,239,126,44,138,111,175,241,164,19,87,145,30,33, 167,22,145,159,8,211,139,9,225,42,5,240,145,139,163,163,8,211,139,10,228, 64,211,19,132,140,93,29,56,70,156,88,119,34,66,146,36,104,137,194,70,46, 142,172,35,78,44,47,146,195,102,11,240,145,139,163,175,8,211,139,9,228,240, 242,112,145,139,163,179,8,211,139,8,237,34,130,118,49,116,118,225,26,48,0, 1,98,29,201,158,46,183,39,135,147,132,140,93,16,132,76,66,33,8,66,16,132, 33,8,66,26,180,105,97,167,68,150,34,33,154,112,0,1,91,247,35,79,111,237, 198,174,232,47,31,23,95,17,13,31,249,96,211,49,50,53,214,77,141,24,0,0,181, 10,228,240,242,15,128,140,65,128,134,188,0,0,90,167,97,181,224,0,2,213,62, 53,224,0,2,213,66,237,120,0,0,181,81,204,107,192,0,5,170,150,67,94,0,0,45, 84,245,90,240,0,1,106,169,162,215,128,0,11,85,93,150,188,0,0,90,171,111,53, 109,22,162,26,48,0,1,84,23,201,146,243,225,26,39,12,145,136,104,192,0,5,61, 11,228,201,121,240,100,19,134,72,196,33,195,14,40,203,112,64,190,76,232, 145,153,136,0,0,0,0,0,0,31,15,249,152,0,0,0,0,0,0,30,15,249,120,144,13,96, 155,194,56,80,206,36,67,141,20,228,70,57,81,206,100,131,156,39,132,168,23, 194,70,46,137,208,21,200,129,166,39,9,24,186,39,72,119,34,66,146,36,104, 137,194,70,46,137,212,23,201,97,179,5,248,72,197,209,58,194,121,60,60,156, 36,98,232,157,129,29,164,80,78,198,46,137,218,146,121,25,71,146,9,209,5, 209,61,48,126,14,138,152,30,67,186,23,143,139,175,131,202,135,228,72,85, 144,83,60,179,30,94,209,233,102,30,98,105,230,103,30,114,121,231,104,30, 122,137,231,233,30,130,153,232,106,30,138,169,232,235,30,144,67,193,25,19, 136,108,207,30,41,224,140,137,194,173,192,153,228,5,242,100,188,248,70,137, 195,36,79,78,47,147,37,231,193,144,78,25,34,122,145,111,36,74,232,176,13, 17,61,234,226,93,207,148,160,84,75,141,7,27,161,32,33,18,225,80,212,76,154, 2,2,70,65,56,100,237,34,140,209,2,67,32,156,50,118,145,64,186,230,61,205, 35,103,155,32,36,141,19,134,78,210,40,206,16,36,70,137,195,39,105,20,11, 174,99,220,210,54,121,210,1,137,33,1,228,207,16,17,70,146,66,3,201,164,32, 0,65,112,152,56,196,159,31,23,77,211,195,201,199,23,160,72,214,246,81,6,12, 73,241,214,111,31,23,60,145,158,56,50,72,81,67,230,232,242,80,19,49,39,199, 89,188,124,92,242,70,120,227,64,194,75,154,72,12,9,73,6,111,21,120,12,40, 144,19,39,25,0,225,144,168,105,56,248,185,228,140,241,200,96,64,100,42,26, 78,62,46,121,35,52,18,92,116,1,36,64,47,158,64,49,98,66,100,156,242,65,23, 196,149,35,103,194,94,100,108,144,230,203,156,64,66,37,201,16,11,32,249, 132,4,34,92,44,93,146,55,152,72,24,137,112,151,153,27,36,5,100,229,144,8, 162,98,92,210,5,76,73,241,214,111,31,23,60,145,158,57,44,48,46,92,185,164, 160,72,151,41,0,50,107,179,244,59,36,93,127,92,6,19,172,3,11,216,0,56,224, 151,29,102,241,241,115,201,25,227,164,64,106,37,199,197,211,116,240,242, 113,197,233,144,40,248,185,228,140,241,196,75,132,109,24,72,128,43,39,84, 129,13,173,161,144,168,105,56,98,78,100,142,214,215,69,1,13,173,161,144, 168,105,57,34,78,100,142,214,215,69,16,67,107,105,110,114,168,254,24,147, 153,35,181,181,212,32,67,107,105,110,114,168,254,72,147,153,35,181,181,212, 36,65,130,3,144,8,26,252,200,13,30,85,16,16,64,90,242,231,192,64,161,163, 203,31,26,172,193,17,4,23,105,159,96,27,172,251,16,32,196,4,14,137,112,17, 136,48,164,28,134,80,215,202,1,132,130,8,12,39,52,64,155,31,24,56,36,1,189, 207,132,0,35,233,35,195,62,3,196,149,36,100,72,160,2,200,232,44,227,0,11, 37,160,68,142,128,36,157,25,200,32,26,79,90,4,73,43,192,122,54,71,65,103, 44,248,14,134,140,151,227,138,231,208,45,96,148,248,134,140,151,227,138, 231,240,1,255,254,10,74,146,56,128,104,4,147,152,72,6,144,28,174,143,8,1, 30,1,165,3,96,31,0,211,3,21,11,153,35,0,211,131,68,131,160,137,16,250,5, 196,131,160,137,200,160,199,156,67,248,0,255,255,65,140,10,48,177,115,56, 35,130,60,19,134,79,89,240,52,177,115,56,39,12,156,123,144,217,251,15,135, 34,167,30,20,170,154,255,232,12,47,244,0,97,28,17,224,39,238,32,40,71,4, 120,39,12,156,4,253,228,5,137,195,39,30,228,54,124,4,253,228,128,194,115, 68,9,252,15,128,232,104,201,126,56,191,35,64,90,193,41,241,13,25,47,199,23, 228,105,3,86,225,1,100,224,156,199,130,36,249,144,10,192,76,71,250,16,15, 18,61,96,17,62,200,3,72,128,136,143,247,32,22,75,64,137,248,64,22,79,90,39, 249,64,38,84,12,167,20,52,223,196,2,230,238,45,214,36,120,32,72,158,208,4, 102,238,45,194,2,201,197,186,196,143,4,9,19,218,0,92,221,202,61,228,143,4, 9,19,218,8,35,55,113,110,16,22,78,81,239,36,120,32,72,158,208,64,73,197,12, 255,0,13,18,60,128,159,212,128,169,76,17,156,185,100,76,255,163,64,65,26, 57,114,200,153,255,70,144,33,13,18,232,50,75,226,104,6,149,3,41,199,246, 130,12,128,28,142,156,120,203,175,158,8,194,207,1,6,81,20,79,88,11,237,84, 11,161,32,127,255,255,255,255,255,247,191,137,235,16,221,170,129,116,36,0, 16,0,0,0,0,0,0,12,196,0,0,0,0,0,0,15,135,242,61,123,164,137,162,164,218,67, 74,134,162,120,128,0,0,0,0,0,1,224,254,71,173,33,129,52,84,155,72,105,80, 212,79,16,0,0,0,0,0,0,60,63,195,244,143,146,22,230,192,0,0,0,0,0,0,176,60, 33,214,2,251,82,1,73,180,134,204,134,36,96,127,255,255,255,255,255,159,161, 144,235,16,221,169,0,164,218,67,102,67,18,48,63,255,255,255,255,255,207, 240,196,60,17,145,56,134,204,241,226,158,8,200,156,42,220,9,158,65,196,34, 92,42,26,137,147,120,64,74,37,196,54,100,49,35,188,36,5,68,184,208,113,187, 194,80,212,75,146,1,73,196,54,100,49,35,188,38,57,37,56,240,0,0,0,0,0,0,0, 0,32,235,248,68,48,156,2,24,94,24,0,243,119,10,139,144,123,242,3,102,238, 18,239,115,72,217,160,11,223,16,23,55,113,241,32,145,36,57,188,18,16,102,3, 5,120,35,34,89,32,15,180,152,173,127,0,218,235,88,0,228,180,227,200,0,0,0, 0,0,0,248,127,197,107,240,64,6,77,220,24,38,78,74,113,67,77,130,4,12,155, 185,52,48,156,148,226,134,155,4,10,194,96,129,132,166,238,45,194,2,201,193, 130,100,228,167,20,52,216,32,113,41,187,139,112,128,178,114,104,97,57,41, 197,13,54,8,32,48,216,32,130,195,224,130,19,97,124,134,23,6,0,57,137,62,77, 12,38,12,0,179,18,124,45,22,190,96,128,141,176,134,28,98,79,180,152,139, 218,45,124,193,1,27,97,16,32,196,159,24,230,204,246,194,40,89,137,62,210, 98,103,92,217,158,216,70,7,49,39,193,130,100,182,17,194,140,73,246,147,16, 250,9,146,216,72,6,49,39,193,131,22,194,72,73,137,62,210,98,31,65,139,97, 40,32,196,159,14,234,70,86,194,88,89,137,62,210,98,63,93,72,202,216,76,10, 49,39,198,33,180,153,37,108,38,134,152,147,237,38,38,117,13,164,201,43,97, 56,40,196,159,36,65,57,163,149,176,158,26,98,79,180,152,165,210,9,205,28, 173,133,0,243,18,124,98,22,180,72,130,115,71,43,97,68,72,196,159,105,49,51, 168,90,209,34,9,205,28,173,133,33,19,18,124,154,24,76,185,164,227,138,89, 18,119,0,7,145,39,201,161,132,188,64,124,137,62,49,11,90,36,65,57,163,149, 210,166,37,34,79,180,152,153,212,45,104,145,4,230,142,87,74,160,84,137,62, 72,130,115,71,43,171,234,134,200,147,237,38,41,116,130,115,71,43,171,235,5, 72,147,227,16,218,76,146,186,254,184,108,137,62,210,98,103,80,218,76,146, 186,254,192,68,137,62,29,212,140,174,207,178,23,34,79,180,152,143,215,82, 50,187,62,208,60,137,62,12,19,37,210,182,21,34,79,180,152,135,208,76,151, 74,224,68,137,62,49,205,153,238,175,186,23,34,79,180,152,153,215,54,103, 186,190,240,92,137,62,22,139,95,48,64,70,235,251,225,210,36,251,73,136,189, 162,215,204,16,17,186,255,2,14,98,79,152,32,35,108,48,64,242,36,249,130,2, 55,75,6,212,224,72,200,51,128,114,108,28,100,128,0,0,0,0,0,0,0,12,110,127, 48,98,115,249,201,117,243,249,195,21,159,206,38,47,63,156,86,8,75,144,94, 82,1,38,73,79,208,67,95,233,1,6,128,14,79,129,186,40,249,18,149,182,207, 144,200,155,188,248,204,105,184,207,142,199,137,175,201,0,159,72,10,5,21, 221,10,120,74,129,124,36,98,232,228,74,81,62,160,20,10,107,186,21,114,32, 105,137,194,70,46,142,68,165,19,235,1,64,170,187,161,119,34,66,146,36,104, 137,194,70,46,142,68,165,19,236,1,64,174,187,161,95,37,134,204,23,225,35, 23,71,34,82,137,246,128,160,89,93,208,167,147,195,201,194,70,46,142,68,165, 19,238,1,64,182,187,161,71,105,20,19,177,139,163,145,41,68,16,7,6,15,82,70, 72,115,96,0,0,0,0,0,27,234,32,91,60,165,195,201,194,8,134,149,216,162,0, 192,41,225,8,2,48,177,36,1,149,13,196,15,0,200,209,97,199,128,99,32,176, 195,192,113,57,143,0,167,133,32,230,80,28,202,139,175,238,2,48,189,192,20, 1,119,80,87,193,186,129,89,56,72,197,209,200,193,185,35,23,71,109,13,219, 36,98,232,237,156,13,26,208,211,14,102,19,87,137,91,95,128,0,10,96,24,92,0, 0,83,2,53,56,0,0,165,3,28,204,160,160,226,100,226,200,211,76,241,240,0,1, 102,8,22,75,64,137,73,20,230,105,133,7,19,39,22,70,154,103,143,128,0,11,48, 20,28,76,156,113,75,34,78,62,0,0,45,3,103,31,0,0,22,65,44,57,137,62,33,179, 216,162,152,192,131,18,124,162,27,61,138,41,108,32,196,159,16,217,232,235, 81,76,104,73,137,62,81,13,158,142,181,20,184,16,98,79,136,108,244,244,168, 166,56,36,196,159,40,134,207,79,74,138,93,10,49,39,194,173,192,158,158,149, 20,188,20,98,79,133,91,129,61,109,74,41,124,30,68,159,16,217,236,83,108,96, 68,137,62,81,13,158,197,54,182,17,34,79,136,108,244,117,169,182,52,38,68, 159,40,134,207,71,90,155,92,8,145,39,196,54,122,122,84,219,28,19,34,79,148, 67,103,167,165,77,174,133,72,147,225,86,224,79,79,74,155,94,10,145,39,194, 173,192,158,182,165,54,190,206,25,212,35,208,226,100,150,211,201,29,162,44, 140,35,103,0,0,0,0,0,0,3,192,252,206,25,228,35,208,226,100,150,211,201,29, 162,44,140,35,103,0,0,0,0,0,0,3,192,252,206,25,244,35,208,226,100,150,211, 201,29,162,44,140,35,103,0,0,0,0,0,0,3,192,252,206,26,4,35,208,226,100,150, 211,201,29,162,44,140,35,103,0,0,0,0,0,0,0,1,0,206,26,20,35,208,226,100, 150,211,201,29,162,44,140,35,103,0,0,0,0,0,0,0,1,0,206,26,36,35,208,226, 100,150,211,201,29,162,44,140,35,103,0,0,0,0,0,0,0,65,0,206,26,52,35,208, 226,100,150,211,201,29,162,44,140,35,103,0,0,0,0,0,0,0,65,0,206,26,68,35, 208,226,100,150,211,201,29,162,44,140,35,103,0,0,0,0,0,0,0,65,0,206,26,84, 35,208,226,100,150,211,201,29,162,44,140,35,103,0,0,0,0,0,0,0,129,0,195, 154,99,16,38,36,0,251,68,117,179,216,162,128,68,72,1,241,13,158,197,20,150, 25,18,0,125,162,58,217,232,235,117,100,162,136,25,18,0,125,162,58,217,232, 235,116,36,162,145,2,226,64,15,136,108,244,117,186,178,81,73,129,113,32,7, 196,54,122,58,221,9,40,165,64,200,144,3,237,17,214,207,79,75,171,37,20,80, 200,144,3,237,17,214,207,79,75,161,37,20,138,23,18,0,124,67,103,167,165, 213,146,138,77,11,137,0,62,33,179,211,210,232,73,69,42,133,196,128,31,10, 183,2,125,89,40,163,5,196,128,31,10,183,2,125,9,40,164,96,200,144,3,224, 221,64,172,157,89,40,163,134,68,128,31,6,234,5,100,232,73,69,35,133,68,128, 31,104,142,182,125,89,40,180,0,168,144,3,237,17,214,207,161,37,22,144,19, 18,0,124,67,103,213,146,139,80,9,137,0,62,33,179,232,73,69,172,5,90,40,153, 59,68,117,179,216,166,192,77,162,137,147,136,108,246,41,180,176,219,69,19, 39,104,142,182,122,58,221,89,41,178,6,218,40,153,59,68,117,179,209,214,232, 73,77,162,6,90,40,153,56,134,207,71,91,171,37,54,152,25,104,162,100,226,27, 61,29,110,132,148,218,160,109,162,137,147,180,71,91,61,61,46,172,148,217, 67,109,20,76,157,162,58,217,233,233,116,36,166,209,67,45,20,76,156,67,103, 167,165,213,146,155,77,12,180,81,50,113,13,158,158,151,66,74,109,84,50,209, 68,201,194,173,192,159,86,74,108,193,150,138,38,78,21,110,4,250,18,83,104, 193,182,138,38,78,13,212,10,201,213,146,155,56,109,162,137,147,131,117,2, 178,116,36,166,209,194,237,20,76,157,162,58,217,245,100,167,16,2,237,20,76, 157,162,58,217,244,36,167,18,2,173,20,76,156,67,103,213,146,156,80,10,180, 81,50,113,13,159,66,74,113,97,175,221,48,216,110,64,4,42,22,189,179,0,196, 133,0,185,80,32,28,78,99,193,18,80,36,4,19,159,141,172,0,178,90,4,74,73,0, 22,209,68,201,187,129,4,2,8,3,132,64,60,36,6,149,113,72,176,171,240,84,0, 157,91,116,116,32,11,42,218,221,216,181,129,32,3,234,219,165,3,188,231,235, 249,8,187,152,252,47,86,227,105,18,7,244,17,91,42,56,175,185,248,110,173, 198,209,208,36,0,238,82,97,87,188,189,179,240,93,122,32,12,22,162,42,125, 144,132,160,7,236,161,25,232,237,105,64,205,59,127,102,158,160,230,63,11, 217,66,51,210,129,154,118,254,205,61,65,236,127,171,197,34,168,48,6,90,194, 1,0,39,75,88,72,8,9,33,186,194,80,64,76,13,214,19,2,130,96,110,150,189,0, 65,6,51,214,20,128,65,17,11,214,19,130,137,121,211,210,211,144,6,39,75,88, 80,0,201,119,235,10,8,41,86,231,71,88,80,129,79,135,186,122,133,224,34,25, 69,234,80,3,91,141,172,40,96,139,113,180,181,133,36,21,110,54,142,134,176, 165,1,176,23,213,47,0,216,134,234,215,128,111,117,181,232,128,209,3,70,230, 107,64,5,139,168,209,235,10,32,36,144,102,235,136,3,146,27,172,40,160,146, 132,103,172,40,192,115,3,117,133,28,22,113,163,69,172,41,103,1,66,188,17, 145,52,168,4,202,113,67,76,130,227,76,194,13,240,108,0,0,83,224,0,2,193,0, 104,146,84,97,48,0,1,94,192,56,169,24,145,179,192,0,5,112,8,56,16,32,128, 56,18,52,125,230,86,147,190,140,28,50,21,13,39,31,23,60,145,158,57,12,141, 47,129,6,155,194,188,24,49,39,199,89,188,124,92,242,70,120,224,201,33,69, 15,155,163,201,68,14,49,39,199,197,211,116,240,242,113,197,232,18,180,254, 36,3,17,46,18,243,35,100,128,172,156,178,70,163,154,76,34,248,146,164,108, 248,75,204,141,146,28,217,115,137,27,95,27,241,173,236,162,160,224,200,2, 206,9,113,13,148,192,209,18,22,164,146,37,193,57,162,4,249,39,196,128,24,2, 178,66,213,136,68,201,16,77,209,131,31,192,242,88,96,92,191,151,34,100,136, 38,232,255,252,92,221,199,197,12,68,209,82,66,212,11,155,185,41,197,13,55, 38,3,66,213,47,135,254,72,12,162,99,133,116,112,0,1,72,66,14,16,16,50,37, 202,160,150,154,66,14,20,8,57,192,28,24,80,113,50,113,100,105,166,120,248, 0,0,179,1,65,196,201,199,20,178,36,227,224,0,2,208,54,113,240,0,1,100,11, 181,192,0,5,178,1,18,160,65,24,131,20,145,25,188,48,132,122,28,76,146,218, 121,35,180,69,145,132,108,224,0,0,0,0,0,0,120,31,153,188,56,132,122,28,76, 146,218,121,35,180,69,145,132,108,224,0,0,0,0,0,0,120,31,168,160,45,110,23, 30,176,33,184,0,0,183,32,29,235,2,27,199,23,0,0,23,4,51,120,129,8,244,56, 153,37,180,242,71,104,139,35,8,217,192,0,0,0,0,0,0,240,63,51,120,145,8,244, 56,153,37,180,242,71,104,139,35,8,217,192,0,0,0,0,0,0,0,64,51,120,161,8, 244,56,153,37,180,242,71,104,139,35,8,217,192,0,0,0,0,0,0,0,64,51,120,177, 8,244,56,153,37,180,242,71,104,139,35,8,217,192,0,0,0,0,0,0,16,64,51,120, 193,8,244,56,153,37,180,242,71,104,139,35,8,217,192,0,0,0,0,0,0,16,64,51, 120,209,8,244,56,153,37,180,242,71,104,139,35,8,217,192,0,0,0,0,0,0,16,64, 51,120,225,8,244,56,153,37,180,242,71,104,139,35,8,217,192,0,0,0,0,0,0,32, 64,32,227,194,0,97,57,162,4,246,104,5,34,92,35,68,225,161,166,220,16,16, 137,112,52,41,73,29,185,1,65,196,201,197,145,166,153,246,72,3,137,204,120, 34,74,8,199,1,67,17,162,112,201,84,128,97,144,78,25,42,16,131,169,1,205,66, 8,35,68,225,161,166,239,128,0,10,192,64,196,104,156,50,96,0,2,172,73,240, 117,96,57,170,97,4,104,156,52,52,221,240,0,1,82,1,74,9,129,125,240,0,1,82, 32,148,25,174,137,58,23,51,190,0,0,42,69,64,195,32,156,50,96,0,2,160,81, 238,2,3,107,173,218,3,192, }; #elif defined(DUK_USE_DOUBLE_BE) DUK_INTERNAL const duk_uint8_t duk_builtins_data[4281] = { 144,148,105,226,32,68,52,228,254,12,104,202,37,132,52,167,194,138,105,245, 124,57,28,211,57,18,64,52,239,126,44,138,111,175,241,164,19,87,145,30,33, 167,22,145,159,8,211,139,9,225,42,5,240,145,139,163,163,8,211,139,10,228, 64,211,19,132,140,93,29,56,70,156,88,119,34,66,146,36,104,137,194,70,46, 142,172,35,78,44,47,146,195,102,11,240,145,139,163,175,8,211,139,9,228,240, 242,112,145,139,163,179,8,211,139,8,237,34,130,118,49,116,118,225,26,48,0, 1,98,29,201,158,46,183,39,135,147,132,140,93,16,132,76,66,33,8,66,16,132, 33,8,66,26,180,105,97,167,68,150,34,33,154,112,0,1,91,247,35,79,111,237, 198,174,232,47,31,23,95,17,13,31,249,96,211,49,50,53,214,77,141,24,0,0,181, 10,228,240,242,15,128,140,65,128,134,188,0,0,90,167,97,181,224,0,2,213,62, 53,224,0,2,213,66,237,120,0,0,181,81,204,107,192,0,5,170,150,67,94,0,0,45, 84,245,90,240,0,1,106,169,162,215,128,0,11,85,93,150,188,0,0,90,171,111,53, 109,22,162,26,48,0,1,84,23,201,146,243,225,26,39,12,145,136,104,192,0,5,61, 11,228,201,121,240,100,19,134,72,196,33,195,14,40,203,112,64,190,76,232, 145,153,136,15,255,0,0,0,0,0,0,25,152,15,254,0,0,0,0,0,0,25,120,144,13,96, 155,194,56,80,206,36,67,141,20,228,70,57,81,206,100,131,156,39,132,168,23, 194,70,46,137,208,21,200,129,166,39,9,24,186,39,72,119,34,66,146,36,104, 137,194,70,46,137,212,23,201,97,179,5,248,72,197,209,58,194,121,60,60,156, 36,98,232,157,129,29,164,80,78,198,46,137,218,146,121,25,71,146,9,209,5, 209,61,48,126,14,138,152,30,67,186,23,143,139,175,131,202,135,228,72,85, 144,83,60,179,30,94,209,233,102,30,98,105,230,103,30,114,121,231,104,30, 122,137,231,233,30,130,153,232,106,30,138,169,232,235,30,144,67,193,25,19, 136,108,207,30,41,224,140,137,194,173,192,153,228,5,242,100,188,248,70,137, 195,36,79,78,47,147,37,231,193,144,78,25,34,122,145,111,36,74,232,176,13, 17,61,234,226,93,207,148,160,84,75,141,7,27,161,32,33,18,225,80,212,76,154, 2,2,70,65,56,100,237,34,140,209,2,67,32,156,50,118,145,64,186,230,61,205, 35,103,155,32,36,141,19,134,78,210,40,206,16,36,70,137,195,39,105,20,11, 174,99,220,210,54,121,210,1,137,33,1,228,207,16,17,70,146,66,3,201,164,32, 0,65,112,152,56,196,159,31,23,77,211,195,201,199,23,160,72,214,246,81,6,12, 73,241,214,111,31,23,60,145,158,56,50,72,81,67,230,232,242,80,19,49,39,199, 89,188,124,92,242,70,120,227,64,194,75,154,72,12,9,73,6,111,21,120,12,40, 144,19,39,25,0,225,144,168,105,56,248,185,228,140,241,200,96,64,100,42,26, 78,62,46,121,35,52,18,92,116,1,36,64,47,158,64,49,98,66,100,156,242,65,23, 196,149,35,103,194,94,100,108,144,230,203,156,64,66,37,201,16,11,32,249, 132,4,34,92,44,93,146,55,152,72,24,137,112,151,153,27,36,5,100,229,144,8, 162,98,92,210,5,76,73,241,214,111,31,23,60,145,158,57,44,48,46,92,185,164, 160,72,151,41,0,50,107,179,244,59,36,93,127,92,6,19,172,3,11,216,0,56,224, 151,29,102,241,241,115,201,25,227,164,64,106,37,199,197,211,116,240,242, 113,197,233,144,40,248,185,228,140,241,196,75,132,109,24,72,128,43,39,84, 129,13,173,161,144,168,105,56,98,78,100,142,214,215,69,1,13,173,161,144, 168,105,57,34,78,100,142,214,215,69,16,67,107,105,110,114,168,254,24,147, 153,35,181,181,212,32,67,107,105,110,114,168,254,72,147,153,35,181,181,212, 36,65,130,3,144,8,26,252,200,13,30,85,16,16,64,90,242,231,192,64,161,163, 203,31,26,172,193,17,4,23,105,159,96,27,172,251,16,32,196,4,14,137,112,17, 136,48,164,28,134,80,215,202,1,132,130,8,12,39,52,64,155,31,24,56,36,1,189, 207,132,0,35,233,35,195,62,3,196,149,36,100,72,160,2,200,232,44,227,0,11, 37,160,68,142,128,36,157,25,200,32,26,79,90,4,73,43,192,122,54,71,65,103, 44,248,14,134,140,151,227,138,231,208,45,96,148,248,134,140,151,227,138, 231,240,1,255,254,10,74,146,56,128,104,4,147,152,72,6,144,28,174,143,8,1, 30,1,165,3,96,31,0,211,3,21,11,153,35,0,211,131,68,131,160,137,16,250,5, 196,131,160,137,200,160,199,156,67,248,0,255,255,65,140,10,48,177,115,56, 35,130,60,19,134,79,89,240,52,177,115,56,39,12,156,123,144,217,251,15,135, 34,167,30,20,170,154,255,232,12,47,244,0,97,28,17,224,39,238,32,40,71,4, 120,39,12,156,4,253,228,5,137,195,39,30,228,54,124,4,253,228,128,194,115, 68,9,252,15,128,232,104,201,126,56,191,35,64,90,193,41,241,13,25,47,199,23, 228,105,3,86,225,1,100,224,156,199,130,36,249,144,10,192,76,71,250,16,15, 18,61,96,17,62,200,3,72,128,136,143,247,32,22,75,64,137,248,64,22,79,90,39, 249,64,38,84,12,167,20,52,223,196,2,230,238,45,214,36,120,32,72,158,208,4, 102,238,45,194,2,201,197,186,196,143,4,9,19,218,0,92,221,202,61,228,143,4, 9,19,218,8,35,55,113,110,16,22,78,81,239,36,120,32,72,158,208,64,73,197,12, 255,0,13,18,60,128,159,212,128,169,76,17,156,185,100,76,255,163,64,65,26, 57,114,200,153,255,70,144,33,13,18,232,50,75,226,104,6,149,3,41,199,246, 130,12,128,28,142,156,120,203,175,158,8,194,207,1,6,81,20,79,88,11,237,84, 11,161,32,63,247,255,255,255,255,255,255,137,235,16,221,170,129,116,36,0,0, 0,0,0,0,0,0,28,196,7,255,128,0,0,0,0,0,2,61,123,164,137,162,164,218,67,74, 134,162,120,128,255,224,0,0,0,0,0,0,71,173,33,129,52,84,155,72,105,80,212, 79,16,63,252,0,0,0,0,0,0,3,244,143,146,22,230,192,60,176,0,0,0,0,0,0,33, 214,2,251,82,1,73,180,134,204,134,36,96,33,159,255,255,255,255,255,255,144, 235,16,221,169,0,164,218,67,102,67,18,48,48,207,255,255,255,255,255,255, 196,60,17,145,56,134,204,241,226,158,8,200,156,42,220,9,158,65,196,34,92, 42,26,137,147,120,64,74,37,196,54,100,49,35,188,36,5,68,184,208,113,187, 194,80,212,75,146,1,73,196,54,100,49,35,188,38,57,37,56,240,0,0,0,0,0,0,0, 0,32,235,248,68,48,156,2,24,94,24,0,243,119,10,139,144,123,242,3,102,238, 18,239,115,72,217,160,11,223,16,23,55,113,241,32,145,36,57,188,18,16,102,3, 5,120,35,34,89,32,15,180,152,173,127,0,218,235,88,0,228,180,227,200,127, 248,0,0,0,0,0,0,197,107,240,64,6,77,220,24,38,78,74,113,67,77,130,4,12,155, 185,52,48,156,148,226,134,155,4,10,194,96,129,132,166,238,45,194,2,201,193, 130,100,228,167,20,52,216,32,113,41,187,139,112,128,178,114,104,97,57,41, 197,13,54,8,32,48,216,32,130,195,224,130,19,97,124,134,23,6,0,57,137,62,77, 12,38,12,0,179,18,124,45,22,190,96,128,141,176,134,28,98,79,180,152,139, 218,45,124,193,1,27,97,16,32,196,159,24,230,204,246,194,40,89,137,62,210, 98,103,92,217,158,216,70,7,49,39,193,130,100,182,17,194,140,73,246,147,16, 250,9,146,216,72,6,49,39,193,131,22,194,72,73,137,62,210,98,31,65,139,97, 40,32,196,159,14,234,70,86,194,88,89,137,62,210,98,63,93,72,202,216,76,10, 49,39,198,33,180,153,37,108,38,134,152,147,237,38,38,117,13,164,201,43,97, 56,40,196,159,36,65,57,163,149,176,158,26,98,79,180,152,165,210,9,205,28, 173,133,0,243,18,124,98,22,180,72,130,115,71,43,97,68,72,196,159,105,49,51, 168,90,209,34,9,205,28,173,133,33,19,18,124,154,24,76,185,164,227,138,89, 18,119,0,7,145,39,201,161,132,188,64,124,137,62,49,11,90,36,65,57,163,149, 210,166,37,34,79,180,152,153,212,45,104,145,4,230,142,87,74,160,84,137,62, 72,130,115,71,43,171,234,134,200,147,237,38,41,116,130,115,71,43,171,235,5, 72,147,227,16,218,76,146,186,254,184,108,137,62,210,98,103,80,218,76,146, 186,254,192,68,137,62,29,212,140,174,207,178,23,34,79,180,152,143,215,82, 50,187,62,208,60,137,62,12,19,37,210,182,21,34,79,180,152,135,208,76,151, 74,224,68,137,62,49,205,153,238,175,186,23,34,79,180,152,153,215,54,103, 186,190,240,92,137,62,22,139,95,48,64,70,235,251,225,210,36,251,73,136,189, 162,215,204,16,17,186,255,2,14,98,79,152,32,35,108,48,64,242,36,249,130,2, 55,75,6,212,224,72,200,51,128,114,108,28,100,128,0,0,0,0,0,0,0,12,110,127, 48,98,115,249,201,117,243,249,195,21,159,206,38,47,63,156,86,8,75,144,94, 82,1,38,73,79,208,67,95,233,1,6,128,14,79,129,186,40,249,18,149,182,207, 144,200,155,188,248,204,105,184,207,142,199,137,175,201,0,159,72,10,5,21, 221,10,120,74,129,124,36,98,232,228,74,81,62,160,20,10,107,186,21,114,32, 105,137,194,70,46,142,68,165,19,235,1,64,170,187,161,119,34,66,146,36,104, 137,194,70,46,142,68,165,19,236,1,64,174,187,161,95,37,134,204,23,225,35, 23,71,34,82,137,246,128,160,89,93,208,167,147,195,201,194,70,46,142,68,165, 19,238,1,64,182,187,161,71,105,20,19,177,139,163,145,41,68,16,7,6,15,82,70, 72,115,96,32,106,27,128,0,0,0,0,91,60,165,195,201,194,8,134,149,216,162,0, 192,41,225,8,2,48,177,36,1,149,13,196,15,0,200,209,97,199,128,99,32,176, 195,192,113,57,143,0,167,133,32,230,80,28,202,139,175,238,2,48,189,192,20, 1,119,80,87,193,186,129,89,56,72,197,209,200,193,185,35,23,71,109,13,219, 36,98,232,237,156,13,26,208,211,14,102,19,87,137,91,95,128,0,10,96,24,92,0, 0,83,2,53,56,0,0,165,3,28,204,160,160,226,100,226,200,211,76,241,240,0,1, 102,8,22,75,64,137,73,20,230,105,133,7,19,39,22,70,154,103,143,128,0,11,48, 20,28,76,156,113,75,34,78,62,0,0,45,3,103,31,0,0,22,65,44,57,137,62,33,179, 216,162,152,192,131,18,124,162,27,61,138,41,108,32,196,159,16,217,232,235, 81,76,104,73,137,62,81,13,158,142,181,20,184,16,98,79,136,108,244,244,168, 166,56,36,196,159,40,134,207,79,74,138,93,10,49,39,194,173,192,158,158,149, 20,188,20,98,79,133,91,129,61,109,74,41,124,30,68,159,16,217,236,83,108,96, 68,137,62,81,13,158,197,54,182,17,34,79,136,108,244,117,169,182,52,38,68, 159,40,134,207,71,90,155,92,8,145,39,196,54,122,122,84,219,28,19,34,79,148, 67,103,167,165,77,174,133,72,147,225,86,224,79,79,74,155,94,10,145,39,194, 173,192,158,182,165,54,190,206,25,212,35,208,226,100,150,211,201,29,162,44, 140,35,103,0,255,192,0,0,0,0,0,0,206,25,228,35,208,226,100,150,211,201,29, 162,44,140,35,103,0,255,192,0,0,0,0,0,0,206,25,244,35,208,226,100,150,211, 201,29,162,44,140,35,103,0,255,192,0,0,0,0,0,0,206,26,4,35,208,226,100,150, 211,201,29,162,44,140,35,103,1,0,0,0,0,0,0,0,0,206,26,20,35,208,226,100, 150,211,201,29,162,44,140,35,103,1,0,0,0,0,0,0,0,0,206,26,36,35,208,226, 100,150,211,201,29,162,44,140,35,103,1,0,64,0,0,0,0,0,0,206,26,52,35,208, 226,100,150,211,201,29,162,44,140,35,103,1,0,64,0,0,0,0,0,0,206,26,68,35, 208,226,100,150,211,201,29,162,44,140,35,103,1,0,64,0,0,0,0,0,0,206,26,84, 35,208,226,100,150,211,201,29,162,44,140,35,103,1,0,128,0,0,0,0,0,0,195, 154,99,16,38,36,0,251,68,117,179,216,162,128,68,72,1,241,13,158,197,20,150, 25,18,0,125,162,58,217,232,235,117,100,162,136,25,18,0,125,162,58,217,232, 235,116,36,162,145,2,226,64,15,136,108,244,117,186,178,81,73,129,113,32,7, 196,54,122,58,221,9,40,165,64,200,144,3,237,17,214,207,79,75,171,37,20,80, 200,144,3,237,17,214,207,79,75,161,37,20,138,23,18,0,124,67,103,167,165, 213,146,138,77,11,137,0,62,33,179,211,210,232,73,69,42,133,196,128,31,10, 183,2,125,89,40,163,5,196,128,31,10,183,2,125,9,40,164,96,200,144,3,224, 221,64,172,157,89,40,163,134,68,128,31,6,234,5,100,232,73,69,35,133,68,128, 31,104,142,182,125,89,40,180,0,168,144,3,237,17,214,207,161,37,22,144,19, 18,0,124,67,103,213,146,139,80,9,137,0,62,33,179,232,73,69,172,5,90,40,153, 59,68,117,179,216,166,192,77,162,137,147,136,108,246,41,180,176,219,69,19, 39,104,142,182,122,58,221,89,41,178,6,218,40,153,59,68,117,179,209,214,232, 73,77,162,6,90,40,153,56,134,207,71,91,171,37,54,152,25,104,162,100,226,27, 61,29,110,132,148,218,160,109,162,137,147,180,71,91,61,61,46,172,148,217, 67,109,20,76,157,162,58,217,233,233,116,36,166,209,67,45,20,76,156,67,103, 167,165,213,146,155,77,12,180,81,50,113,13,158,158,151,66,74,109,84,50,209, 68,201,194,173,192,159,86,74,108,193,150,138,38,78,21,110,4,250,18,83,104, 193,182,138,38,78,13,212,10,201,213,146,155,56,109,162,137,147,131,117,2, 178,116,36,166,209,194,237,20,76,157,162,58,217,245,100,167,16,2,237,20,76, 157,162,58,217,244,36,167,18,2,173,20,76,156,67,103,213,146,156,80,10,180, 81,50,113,13,159,66,74,113,97,175,221,48,216,110,64,4,42,22,189,179,0,196, 133,0,185,80,32,28,78,99,193,18,80,36,4,19,159,141,172,0,178,90,4,74,73,0, 22,209,68,201,187,129,4,2,8,3,132,64,60,36,4,0,91,240,168,177,69,118,144, 157,91,116,116,32,32,1,53,216,221,218,170,139,3,234,219,165,0,255,152,185, 11,251,232,231,188,47,86,227,105,18,1,255,184,170,59,41,92,23,240,110,173, 198,209,208,36,3,253,188,183,177,82,110,80,224,93,122,32,32,4,144,253,170, 34,22,140,7,236,161,25,232,237,105,64,63,230,160,158,102,127,59,205,11,217, 66,51,210,128,127,237,65,60,204,254,119,155,171,197,34,168,48,6,90,194,1,0, 39,75,88,72,8,9,33,186,194,80,64,76,13,214,19,2,130,96,110,150,189,0,65,6, 51,214,20,128,65,17,11,214,19,130,137,121,211,210,211,144,6,39,75,88,80,0, 201,119,235,10,8,41,86,231,71,88,80,129,79,135,186,122,133,224,34,25,69, 234,80,3,91,141,172,40,96,139,113,180,181,133,36,21,110,54,142,134,176,165, 1,176,23,213,47,0,216,134,234,215,128,111,117,181,232,128,209,3,70,230,107, 64,5,139,168,209,235,10,32,36,144,102,235,136,3,146,27,172,40,160,146,132, 103,172,40,192,115,3,117,133,28,22,113,163,69,172,41,103,1,66,188,17,145, 52,168,4,202,113,67,76,130,227,76,194,13,240,108,0,0,83,224,0,2,193,0,104, 146,84,97,48,0,1,94,192,56,169,24,145,179,192,0,5,112,8,56,16,32,128,56,18, 52,125,230,86,147,190,140,28,50,21,13,39,31,23,60,145,158,57,12,141,47,129, 6,155,194,188,24,49,39,199,89,188,124,92,242,70,120,224,201,33,69,15,155, 163,201,68,14,49,39,199,197,211,116,240,242,113,197,232,18,180,254,36,3,17, 46,18,243,35,100,128,172,156,178,70,163,154,76,34,248,146,164,108,248,75, 204,141,146,28,217,115,137,27,95,27,241,173,236,162,160,224,200,2,206,9, 113,13,148,192,209,18,22,164,146,37,193,57,162,4,249,39,196,128,24,2,178, 66,213,136,68,201,16,77,209,131,31,192,242,88,96,92,191,151,34,100,136,38, 232,255,252,92,221,199,197,12,68,209,82,66,212,11,155,185,41,197,13,55,38, 3,66,213,47,135,254,72,12,162,99,133,116,112,0,1,72,66,14,16,16,50,37,202, 160,150,154,66,14,20,8,57,192,28,24,80,113,50,113,100,105,166,120,248,0,0, 179,1,65,196,201,199,20,178,36,227,224,0,2,208,54,113,240,0,1,100,11,181, 192,0,5,178,1,18,160,65,24,131,20,145,25,188,48,132,122,28,76,146,218,121, 35,180,69,145,132,108,224,31,248,0,0,0,0,0,0,25,188,56,132,122,28,76,146, 218,121,35,180,69,145,132,108,224,31,248,0,0,0,0,0,0,40,160,45,110,23,30, 176,33,184,0,0,183,32,29,235,2,27,199,23,0,0,23,4,51,120,129,8,244,56,153, 37,180,242,71,104,139,35,8,217,192,63,240,0,0,0,0,0,0,51,120,145,8,244,56, 153,37,180,242,71,104,139,35,8,217,192,64,0,0,0,0,0,0,0,51,120,161,8,244, 56,153,37,180,242,71,104,139,35,8,217,192,64,0,0,0,0,0,0,0,51,120,177,8, 244,56,153,37,180,242,71,104,139,35,8,217,192,64,16,0,0,0,0,0,0,51,120,193, 8,244,56,153,37,180,242,71,104,139,35,8,217,192,64,16,0,0,0,0,0,0,51,120, 209,8,244,56,153,37,180,242,71,104,139,35,8,217,192,64,16,0,0,0,0,0,0,51, 120,225,8,244,56,153,37,180,242,71,104,139,35,8,217,192,64,32,0,0,0,0,0,0, 32,227,194,0,97,57,162,4,246,104,5,34,92,35,68,225,161,166,220,16,16,137, 112,52,41,73,29,185,1,65,196,201,197,145,166,153,246,72,3,137,204,120,34, 74,8,199,1,67,17,162,112,201,84,128,97,144,78,25,42,16,131,169,1,205,66,8, 35,68,225,161,166,239,128,0,10,192,64,196,104,156,50,96,0,2,172,73,240,117, 96,57,170,97,4,104,156,52,52,221,240,0,1,82,1,74,9,129,125,240,0,1,82,32, 148,25,174,137,58,23,51,190,0,0,42,69,64,195,32,156,50,96,0,2,160,81,238,2, 3,107,173,218,3,192, }; #elif defined(DUK_USE_DOUBLE_ME) DUK_INTERNAL const duk_uint8_t duk_builtins_data[4281] = { 144,148,105,226,32,68,52,228,254,12,104,202,37,132,52,167,194,138,105,245, 124,57,28,211,57,18,64,52,239,126,44,138,111,175,241,164,19,87,145,30,33, 167,22,145,159,8,211,139,9,225,42,5,240,145,139,163,163,8,211,139,10,228, 64,211,19,132,140,93,29,56,70,156,88,119,34,66,146,36,104,137,194,70,46, 142,172,35,78,44,47,146,195,102,11,240,145,139,163,175,8,211,139,9,228,240, 242,112,145,139,163,179,8,211,139,8,237,34,130,118,49,116,118,225,26,48,0, 1,98,29,201,158,46,183,39,135,147,132,140,93,16,132,76,66,33,8,66,16,132, 33,8,66,26,180,105,97,167,68,150,34,33,154,112,0,1,91,247,35,79,111,237, 198,174,232,47,31,23,95,17,13,31,249,96,211,49,50,53,214,77,141,24,0,0,181, 10,228,240,242,15,128,140,65,128,134,188,0,0,90,167,97,181,224,0,2,213,62, 53,224,0,2,213,66,237,120,0,0,181,81,204,107,192,0,5,170,150,67,94,0,0,45, 84,245,90,240,0,1,106,169,162,215,128,0,11,85,93,150,188,0,0,90,171,111,53, 109,22,162,26,48,0,1,84,23,201,146,243,225,26,39,12,145,136,104,192,0,5,61, 11,228,201,121,240,100,19,134,72,196,33,195,14,40,203,112,64,190,76,232, 145,153,136,0,0,31,15,224,0,0,0,25,152,0,0,30,15,224,0,0,0,25,120,144,13, 96,155,194,56,80,206,36,67,141,20,228,70,57,81,206,100,131,156,39,132,168, 23,194,70,46,137,208,21,200,129,166,39,9,24,186,39,72,119,34,66,146,36,104, 137,194,70,46,137,212,23,201,97,179,5,248,72,197,209,58,194,121,60,60,156, 36,98,232,157,129,29,164,80,78,198,46,137,218,146,121,25,71,146,9,209,5, 209,61,48,126,14,138,152,30,67,186,23,143,139,175,131,202,135,228,72,85, 144,83,60,179,30,94,209,233,102,30,98,105,230,103,30,114,121,231,104,30, 122,137,231,233,30,130,153,232,106,30,138,169,232,235,30,144,67,193,25,19, 136,108,207,30,41,224,140,137,194,173,192,153,228,5,242,100,188,248,70,137, 195,36,79,78,47,147,37,231,193,144,78,25,34,122,145,111,36,74,232,176,13, 17,61,234,226,93,207,148,160,84,75,141,7,27,161,32,33,18,225,80,212,76,154, 2,2,70,65,56,100,237,34,140,209,2,67,32,156,50,118,145,64,186,230,61,205, 35,103,155,32,36,141,19,134,78,210,40,206,16,36,70,137,195,39,105,20,11, 174,99,220,210,54,121,210,1,137,33,1,228,207,16,17,70,146,66,3,201,164,32, 0,65,112,152,56,196,159,31,23,77,211,195,201,199,23,160,72,214,246,81,6,12, 73,241,214,111,31,23,60,145,158,56,50,72,81,67,230,232,242,80,19,49,39,199, 89,188,124,92,242,70,120,227,64,194,75,154,72,12,9,73,6,111,21,120,12,40, 144,19,39,25,0,225,144,168,105,56,248,185,228,140,241,200,96,64,100,42,26, 78,62,46,121,35,52,18,92,116,1,36,64,47,158,64,49,98,66,100,156,242,65,23, 196,149,35,103,194,94,100,108,144,230,203,156,64,66,37,201,16,11,32,249, 132,4,34,92,44,93,146,55,152,72,24,137,112,151,153,27,36,5,100,229,144,8, 162,98,92,210,5,76,73,241,214,111,31,23,60,145,158,57,44,48,46,92,185,164, 160,72,151,41,0,50,107,179,244,59,36,93,127,92,6,19,172,3,11,216,0,56,224, 151,29,102,241,241,115,201,25,227,164,64,106,37,199,197,211,116,240,242, 113,197,233,144,40,248,185,228,140,241,196,75,132,109,24,72,128,43,39,84, 129,13,173,161,144,168,105,56,98,78,100,142,214,215,69,1,13,173,161,144, 168,105,57,34,78,100,142,214,215,69,16,67,107,105,110,114,168,254,24,147, 153,35,181,181,212,32,67,107,105,110,114,168,254,72,147,153,35,181,181,212, 36,65,130,3,144,8,26,252,200,13,30,85,16,16,64,90,242,231,192,64,161,163, 203,31,26,172,193,17,4,23,105,159,96,27,172,251,16,32,196,4,14,137,112,17, 136,48,164,28,134,80,215,202,1,132,130,8,12,39,52,64,155,31,24,56,36,1,189, 207,132,0,35,233,35,195,62,3,196,149,36,100,72,160,2,200,232,44,227,0,11, 37,160,68,142,128,36,157,25,200,32,26,79,90,4,73,43,192,122,54,71,65,103, 44,248,14,134,140,151,227,138,231,208,45,96,148,248,134,140,151,227,138, 231,240,1,255,254,10,74,146,56,128,104,4,147,152,72,6,144,28,174,143,8,1, 30,1,165,3,96,31,0,211,3,21,11,153,35,0,211,131,68,131,160,137,16,250,5, 196,131,160,137,200,160,199,156,67,248,0,255,255,65,140,10,48,177,115,56, 35,130,60,19,134,79,89,240,52,177,115,56,39,12,156,123,144,217,251,15,135, 34,167,30,20,170,154,255,232,12,47,244,0,97,28,17,224,39,238,32,40,71,4, 120,39,12,156,4,253,228,5,137,195,39,30,228,54,124,4,253,228,128,194,115, 68,9,252,15,128,232,104,201,126,56,191,35,64,90,193,41,241,13,25,47,199,23, 228,105,3,86,225,1,100,224,156,199,130,36,249,144,10,192,76,71,250,16,15, 18,61,96,17,62,200,3,72,128,136,143,247,32,22,75,64,137,248,64,22,79,90,39, 249,64,38,84,12,167,20,52,223,196,2,230,238,45,214,36,120,32,72,158,208,4, 102,238,45,194,2,201,197,186,196,143,4,9,19,218,0,92,221,202,61,228,143,4, 9,19,218,8,35,55,113,110,16,22,78,81,239,36,120,32,72,158,208,64,73,197,12, 255,0,13,18,60,128,159,212,128,169,76,17,156,185,100,76,255,163,64,65,26, 57,114,200,153,255,70,144,33,13,18,232,50,75,226,104,6,149,3,41,199,246, 130,12,128,28,142,156,120,203,175,158,8,194,207,1,6,81,20,79,88,11,237,84, 11,161,32,127,255,247,191,255,255,255,255,137,235,16,221,170,129,116,36,0, 0,0,0,0,16,0,0,12,196,0,0,15,135,240,0,0,0,2,61,123,164,137,162,164,218,67, 74,134,162,120,128,0,1,224,254,0,0,0,0,71,173,33,129,52,84,155,72,105,80, 212,79,16,0,0,60,63,192,0,0,0,3,244,143,146,22,230,192,0,0,176,60,0,0,0,0, 33,214,2,251,82,1,73,180,134,204,134,36,96,127,255,159,161,255,255,255,255, 144,235,16,221,169,0,164,218,67,102,67,18,48,63,255,207,240,255,255,255, 255,196,60,17,145,56,134,204,241,226,158,8,200,156,42,220,9,158,65,196,34, 92,42,26,137,147,120,64,74,37,196,54,100,49,35,188,36,5,68,184,208,113,187, 194,80,212,75,146,1,73,196,54,100,49,35,188,38,57,37,56,240,0,0,0,0,0,0,0, 0,32,235,248,68,48,156,2,24,94,24,0,243,119,10,139,144,123,242,3,102,238, 18,239,115,72,217,160,11,223,16,23,55,113,241,32,145,36,57,188,18,16,102,3, 5,120,35,34,89,32,15,180,152,173,127,0,218,235,88,0,228,180,227,200,0,0, 248,127,0,0,0,0,197,107,240,64,6,77,220,24,38,78,74,113,67,77,130,4,12,155, 185,52,48,156,148,226,134,155,4,10,194,96,129,132,166,238,45,194,2,201,193, 130,100,228,167,20,52,216,32,113,41,187,139,112,128,178,114,104,97,57,41, 197,13,54,8,32,48,216,32,130,195,224,130,19,97,124,134,23,6,0,57,137,62,77, 12,38,12,0,179,18,124,45,22,190,96,128,141,176,134,28,98,79,180,152,139, 218,45,124,193,1,27,97,16,32,196,159,24,230,204,246,194,40,89,137,62,210, 98,103,92,217,158,216,70,7,49,39,193,130,100,182,17,194,140,73,246,147,16, 250,9,146,216,72,6,49,39,193,131,22,194,72,73,137,62,210,98,31,65,139,97, 40,32,196,159,14,234,70,86,194,88,89,137,62,210,98,63,93,72,202,216,76,10, 49,39,198,33,180,153,37,108,38,134,152,147,237,38,38,117,13,164,201,43,97, 56,40,196,159,36,65,57,163,149,176,158,26,98,79,180,152,165,210,9,205,28, 173,133,0,243,18,124,98,22,180,72,130,115,71,43,97,68,72,196,159,105,49,51, 168,90,209,34,9,205,28,173,133,33,19,18,124,154,24,76,185,164,227,138,89, 18,119,0,7,145,39,201,161,132,188,64,124,137,62,49,11,90,36,65,57,163,149, 210,166,37,34,79,180,152,153,212,45,104,145,4,230,142,87,74,160,84,137,62, 72,130,115,71,43,171,234,134,200,147,237,38,41,116,130,115,71,43,171,235,5, 72,147,227,16,218,76,146,186,254,184,108,137,62,210,98,103,80,218,76,146, 186,254,192,68,137,62,29,212,140,174,207,178,23,34,79,180,152,143,215,82, 50,187,62,208,60,137,62,12,19,37,210,182,21,34,79,180,152,135,208,76,151, 74,224,68,137,62,49,205,153,238,175,186,23,34,79,180,152,153,215,54,103, 186,190,240,92,137,62,22,139,95,48,64,70,235,251,225,210,36,251,73,136,189, 162,215,204,16,17,186,255,2,14,98,79,152,32,35,108,48,64,242,36,249,130,2, 55,75,6,212,224,72,200,51,128,114,108,28,100,128,0,0,0,0,0,0,0,12,110,127, 48,98,115,249,201,117,243,249,195,21,159,206,38,47,63,156,86,8,75,144,94, 82,1,38,73,79,208,67,95,233,1,6,128,14,79,129,186,40,249,18,149,182,207, 144,200,155,188,248,204,105,184,207,142,199,137,175,201,0,159,72,10,5,21, 221,10,120,74,129,124,36,98,232,228,74,81,62,160,20,10,107,186,21,114,32, 105,137,194,70,46,142,68,165,19,235,1,64,170,187,161,119,34,66,146,36,104, 137,194,70,46,142,68,165,19,236,1,64,174,187,161,95,37,134,204,23,225,35, 23,71,34,82,137,246,128,160,89,93,208,167,147,195,201,194,70,46,142,68,165, 19,238,1,64,182,187,161,71,105,20,19,177,139,163,145,41,68,16,7,6,15,82,70, 72,115,96,0,27,234,32,0,0,0,0,91,60,165,195,201,194,8,134,149,216,162,0, 192,41,225,8,2,48,177,36,1,149,13,196,15,0,200,209,97,199,128,99,32,176, 195,192,113,57,143,0,167,133,32,230,80,28,202,139,175,238,2,48,189,192,20, 1,119,80,87,193,186,129,89,56,72,197,209,200,193,185,35,23,71,109,13,219, 36,98,232,237,156,13,26,208,211,14,102,19,87,137,91,95,128,0,10,96,24,92,0, 0,83,2,53,56,0,0,165,3,28,204,160,160,226,100,226,200,211,76,241,240,0,1, 102,8,22,75,64,137,73,20,230,105,133,7,19,39,22,70,154,103,143,128,0,11,48, 20,28,76,156,113,75,34,78,62,0,0,45,3,103,31,0,0,22,65,44,57,137,62,33,179, 216,162,152,192,131,18,124,162,27,61,138,41,108,32,196,159,16,217,232,235, 81,76,104,73,137,62,81,13,158,142,181,20,184,16,98,79,136,108,244,244,168, 166,56,36,196,159,40,134,207,79,74,138,93,10,49,39,194,173,192,158,158,149, 20,188,20,98,79,133,91,129,61,109,74,41,124,30,68,159,16,217,236,83,108,96, 68,137,62,81,13,158,197,54,182,17,34,79,136,108,244,117,169,182,52,38,68, 159,40,134,207,71,90,155,92,8,145,39,196,54,122,122,84,219,28,19,34,79,148, 67,103,167,165,77,174,133,72,147,225,86,224,79,79,74,155,94,10,145,39,194, 173,192,158,182,165,54,190,206,25,212,35,208,226,100,150,211,201,29,162,44, 140,35,103,0,0,3,192,252,0,0,0,0,206,25,228,35,208,226,100,150,211,201,29, 162,44,140,35,103,0,0,3,192,252,0,0,0,0,206,25,244,35,208,226,100,150,211, 201,29,162,44,140,35,103,0,0,3,192,252,0,0,0,0,206,26,4,35,208,226,100,150, 211,201,29,162,44,140,35,103,0,0,0,1,0,0,0,0,0,206,26,20,35,208,226,100, 150,211,201,29,162,44,140,35,103,0,0,0,1,0,0,0,0,0,206,26,36,35,208,226, 100,150,211,201,29,162,44,140,35,103,0,0,0,65,0,0,0,0,0,206,26,52,35,208, 226,100,150,211,201,29,162,44,140,35,103,0,0,0,65,0,0,0,0,0,206,26,68,35, 208,226,100,150,211,201,29,162,44,140,35,103,0,0,0,65,0,0,0,0,0,206,26,84, 35,208,226,100,150,211,201,29,162,44,140,35,103,0,0,0,129,0,0,0,0,0,195, 154,99,16,38,36,0,251,68,117,179,216,162,128,68,72,1,241,13,158,197,20,150, 25,18,0,125,162,58,217,232,235,117,100,162,136,25,18,0,125,162,58,217,232, 235,116,36,162,145,2,226,64,15,136,108,244,117,186,178,81,73,129,113,32,7, 196,54,122,58,221,9,40,165,64,200,144,3,237,17,214,207,79,75,171,37,20,80, 200,144,3,237,17,214,207,79,75,161,37,20,138,23,18,0,124,67,103,167,165, 213,146,138,77,11,137,0,62,33,179,211,210,232,73,69,42,133,196,128,31,10, 183,2,125,89,40,163,5,196,128,31,10,183,2,125,9,40,164,96,200,144,3,224, 221,64,172,157,89,40,163,134,68,128,31,6,234,5,100,232,73,69,35,133,68,128, 31,104,142,182,125,89,40,180,0,168,144,3,237,17,214,207,161,37,22,144,19, 18,0,124,67,103,213,146,139,80,9,137,0,62,33,179,232,73,69,172,5,90,40,153, 59,68,117,179,216,166,192,77,162,137,147,136,108,246,41,180,176,219,69,19, 39,104,142,182,122,58,221,89,41,178,6,218,40,153,59,68,117,179,209,214,232, 73,77,162,6,90,40,153,56,134,207,71,91,171,37,54,152,25,104,162,100,226,27, 61,29,110,132,148,218,160,109,162,137,147,180,71,91,61,61,46,172,148,217, 67,109,20,76,157,162,58,217,233,233,116,36,166,209,67,45,20,76,156,67,103, 167,165,213,146,155,77,12,180,81,50,113,13,158,158,151,66,74,109,84,50,209, 68,201,194,173,192,159,86,74,108,193,150,138,38,78,21,110,4,250,18,83,104, 193,182,138,38,78,13,212,10,201,213,146,155,56,109,162,137,147,131,117,2, 178,116,36,166,209,194,237,20,76,157,162,58,217,245,100,167,16,2,237,20,76, 157,162,58,217,244,36,167,18,2,173,20,76,156,67,103,213,146,156,80,10,180, 81,50,113,13,159,66,74,113,97,175,221,48,216,110,64,4,42,22,189,179,0,196, 133,0,185,80,32,28,78,99,193,18,80,36,4,19,159,141,172,0,178,90,4,74,73,0, 22,209,68,201,187,129,4,2,8,3,132,64,60,36,0,171,240,84,6,149,113,72,176, 157,91,116,116,32,88,181,129,32,11,42,218,221,131,234,219,165,1,8,187,152, 255,188,231,235,248,47,86,227,105,18,2,56,175,185,255,244,17,91,40,110,173, 198,209,208,36,7,188,189,179,240,238,82,97,80,93,122,32,125,144,132,160,12, 22,162,42,7,236,161,25,232,237,105,64,158,160,230,63,205,59,127,102,11,217, 66,51,210,129,61,65,236,127,154,118,254,205,171,197,34,168,48,6,90,194,1,0, 39,75,88,72,8,9,33,186,194,80,64,76,13,214,19,2,130,96,110,150,189,0,65,6, 51,214,20,128,65,17,11,214,19,130,137,121,211,210,211,144,6,39,75,88,80,0, 201,119,235,10,8,41,86,231,71,88,80,129,79,135,186,122,133,224,34,25,69, 234,80,3,91,141,172,40,96,139,113,180,181,133,36,21,110,54,142,134,176,165, 1,176,23,213,47,0,216,134,234,215,128,111,117,181,232,128,209,3,70,230,107, 64,5,139,168,209,235,10,32,36,144,102,235,136,3,146,27,172,40,160,146,132, 103,172,40,192,115,3,117,133,28,22,113,163,69,172,41,103,1,66,188,17,145, 52,168,4,202,113,67,76,130,227,76,194,13,240,108,0,0,83,224,0,2,193,0,104, 146,84,97,48,0,1,94,192,56,169,24,145,179,192,0,5,112,8,56,16,32,128,56,18, 52,125,230,86,147,190,140,28,50,21,13,39,31,23,60,145,158,57,12,141,47,129, 6,155,194,188,24,49,39,199,89,188,124,92,242,70,120,224,201,33,69,15,155, 163,201,68,14,49,39,199,197,211,116,240,242,113,197,232,18,180,254,36,3,17, 46,18,243,35,100,128,172,156,178,70,163,154,76,34,248,146,164,108,248,75, 204,141,146,28,217,115,137,27,95,27,241,173,236,162,160,224,200,2,206,9, 113,13,148,192,209,18,22,164,146,37,193,57,162,4,249,39,196,128,24,2,178, 66,213,136,68,201,16,77,209,131,31,192,242,88,96,92,191,151,34,100,136,38, 232,255,252,92,221,199,197,12,68,209,82,66,212,11,155,185,41,197,13,55,38, 3,66,213,47,135,254,72,12,162,99,133,116,112,0,1,72,66,14,16,16,50,37,202, 160,150,154,66,14,20,8,57,192,28,24,80,113,50,113,100,105,166,120,248,0,0, 179,1,65,196,201,199,20,178,36,227,224,0,2,208,54,113,240,0,1,100,11,181, 192,0,5,178,1,18,160,65,24,131,20,145,25,188,48,132,122,28,76,146,218,121, 35,180,69,145,132,108,224,0,0,120,31,128,0,0,0,25,188,56,132,122,28,76,146, 218,121,35,180,69,145,132,108,224,0,0,120,31,128,0,0,0,40,160,45,110,23,30, 176,33,184,0,0,183,32,29,235,2,27,199,23,0,0,23,4,51,120,129,8,244,56,153, 37,180,242,71,104,139,35,8,217,192,0,0,240,63,0,0,0,0,51,120,145,8,244,56, 153,37,180,242,71,104,139,35,8,217,192,0,0,0,64,0,0,0,0,51,120,161,8,244, 56,153,37,180,242,71,104,139,35,8,217,192,0,0,0,64,0,0,0,0,51,120,177,8, 244,56,153,37,180,242,71,104,139,35,8,217,192,0,0,16,64,0,0,0,0,51,120,193, 8,244,56,153,37,180,242,71,104,139,35,8,217,192,0,0,16,64,0,0,0,0,51,120, 209,8,244,56,153,37,180,242,71,104,139,35,8,217,192,0,0,16,64,0,0,0,0,51, 120,225,8,244,56,153,37,180,242,71,104,139,35,8,217,192,0,0,32,64,0,0,0,0, 32,227,194,0,97,57,162,4,246,104,5,34,92,35,68,225,161,166,220,16,16,137, 112,52,41,73,29,185,1,65,196,201,197,145,166,153,246,72,3,137,204,120,34, 74,8,199,1,67,17,162,112,201,84,128,97,144,78,25,42,16,131,169,1,205,66,8, 35,68,225,161,166,239,128,0,10,192,64,196,104,156,50,96,0,2,172,73,240,117, 96,57,170,97,4,104,156,52,52,221,240,0,1,82,1,74,9,129,125,240,0,1,82,32, 148,25,174,137,58,23,51,190,0,0,42,69,64,195,32,156,50,96,0,2,160,81,238,2, 3,107,173,218,3,192, }; #else #error invalid endianness defines #endif #endif /* DUK_USE_ROM_OBJECTS */ /* automatic undefs */ #undef DUK__REFCINIT #line 1 "duk_error_macros.c" /* * Error and fatal handling. */ /* #include duk_internal.h -> already included */ #define DUK__ERRFMT_BUFSIZE 256 /* size for formatting buffers */ #if defined(DUK_USE_VERBOSE_ERRORS) DUK_INTERNAL DUK_COLD void duk_err_handle_error_fmt(duk_hthread *thr, const char *filename, duk_uint_t line_and_code, const char *fmt, ...) { va_list ap; char msg[DUK__ERRFMT_BUFSIZE]; va_start(ap, fmt); (void) DUK_VSNPRINTF(msg, sizeof(msg), fmt, ap); msg[sizeof(msg) - 1] = (char) 0; duk_err_create_and_throw(thr, (duk_errcode_t) (line_and_code >> 24), msg, filename, (duk_int_t) (line_and_code & 0x00ffffffL)); va_end(ap); /* dead code, but ensures portability (see Linux man page notes) */ } DUK_INTERNAL DUK_COLD void duk_err_handle_error(duk_hthread *thr, const char *filename, duk_uint_t line_and_code, const char *msg) { duk_err_create_and_throw(thr, (duk_errcode_t) (line_and_code >> 24), msg, filename, (duk_int_t) (line_and_code & 0x00ffffffL)); } #else /* DUK_USE_VERBOSE_ERRORS */ DUK_INTERNAL DUK_COLD void duk_err_handle_error(duk_hthread *thr, duk_errcode_t code) { duk_err_create_and_throw(thr, code); } #endif /* DUK_USE_VERBOSE_ERRORS */ /* * Error throwing helpers */ #if defined(DUK_USE_VERBOSE_ERRORS) #if defined(DUK_USE_PARANOID_ERRORS) DUK_INTERNAL DUK_COLD void duk_err_require_type_index(duk_hthread *thr, const char *filename, duk_int_t linenumber, duk_idx_t idx, const char *expect_name) { DUK_ERROR_RAW_FMT3(thr, filename, linenumber, DUK_ERR_TYPE_ERROR, "%s required, found %s (stack index %ld)", expect_name, duk_get_type_name(thr, idx), (long) idx); } #else DUK_INTERNAL DUK_COLD void duk_err_require_type_index(duk_hthread *thr, const char *filename, duk_int_t linenumber, duk_idx_t idx, const char *expect_name) { DUK_ERROR_RAW_FMT3(thr, filename, linenumber, DUK_ERR_TYPE_ERROR, "%s required, found %s (stack index %ld)", expect_name, duk_push_string_readable(thr, idx), (long) idx); } #endif DUK_INTERNAL DUK_COLD void duk_err_error_internal(duk_hthread *thr, const char *filename, duk_int_t linenumber) { DUK_ERROR_RAW(thr, filename, linenumber, DUK_ERR_ERROR, DUK_STR_INTERNAL_ERROR); } DUK_INTERNAL DUK_COLD void duk_err_error_alloc_failed(duk_hthread *thr, const char *filename, duk_int_t linenumber) { DUK_ERROR_RAW(thr, filename, linenumber, DUK_ERR_ERROR, DUK_STR_ALLOC_FAILED); } DUK_INTERNAL DUK_COLD void duk_err_error(duk_hthread *thr, const char *filename, duk_int_t linenumber, const char *message) { DUK_ERROR_RAW(thr, filename, linenumber, DUK_ERR_ERROR, message); } DUK_INTERNAL DUK_COLD void duk_err_range(duk_hthread *thr, const char *filename, duk_int_t linenumber, const char *message) { DUK_ERROR_RAW(thr, filename, linenumber, DUK_ERR_RANGE_ERROR, message); } DUK_INTERNAL DUK_COLD void duk_err_range_index(duk_hthread *thr, const char *filename, duk_int_t linenumber, duk_idx_t idx) { DUK_ERROR_RAW_FMT1(thr, filename, linenumber, DUK_ERR_RANGE_ERROR, "invalid stack index %ld", (long) (idx)); } DUK_INTERNAL DUK_COLD void duk_err_range_push_beyond(duk_hthread *thr, const char *filename, duk_int_t linenumber) { DUK_ERROR_RAW(thr, filename, linenumber, DUK_ERR_RANGE_ERROR, DUK_STR_PUSH_BEYOND_ALLOC_STACK); } DUK_INTERNAL DUK_COLD void duk_err_type_invalid_args(duk_hthread *thr, const char *filename, duk_int_t linenumber) { DUK_ERROR_RAW(thr, filename, linenumber, DUK_ERR_TYPE_ERROR, DUK_STR_INVALID_ARGS); } DUK_INTERNAL DUK_COLD void duk_err_type_invalid_state(duk_hthread *thr, const char *filename, duk_int_t linenumber) { DUK_ERROR_RAW(thr, filename, linenumber, DUK_ERR_TYPE_ERROR, DUK_STR_INVALID_STATE); } DUK_INTERNAL DUK_COLD void duk_err_type_invalid_trap_result(duk_hthread *thr, const char *filename, duk_int_t linenumber) { DUK_ERROR_RAW(thr, filename, linenumber, DUK_ERR_TYPE_ERROR, DUK_STR_INVALID_TRAP_RESULT); } #else /* The file/line arguments are NULL and 0, they're ignored by DUK_ERROR_RAW() * when non-verbose errors are used. */ DUK_NORETURN(DUK_LOCAL_DECL void duk__err_shared(duk_hthread *thr, duk_errcode_t code)); DUK_LOCAL void duk__err_shared(duk_hthread *thr, duk_errcode_t code) { DUK_ERROR_RAW(thr, NULL, 0, code, NULL); } DUK_INTERNAL DUK_COLD void duk_err_error(duk_hthread *thr) { duk__err_shared(thr, DUK_ERR_ERROR); } DUK_INTERNAL DUK_COLD void duk_err_range(duk_hthread *thr) { duk__err_shared(thr, DUK_ERR_RANGE_ERROR); } DUK_INTERNAL DUK_COLD void duk_err_eval(duk_hthread *thr) { duk__err_shared(thr, DUK_ERR_EVAL_ERROR); } DUK_INTERNAL DUK_COLD void duk_err_reference(duk_hthread *thr) { duk__err_shared(thr, DUK_ERR_REFERENCE_ERROR); } DUK_INTERNAL DUK_COLD void duk_err_syntax(duk_hthread *thr) { duk__err_shared(thr, DUK_ERR_SYNTAX_ERROR); } DUK_INTERNAL DUK_COLD void duk_err_type(duk_hthread *thr) { duk__err_shared(thr, DUK_ERR_TYPE_ERROR); } DUK_INTERNAL DUK_COLD void duk_err_uri(duk_hthread *thr) { duk__err_shared(thr, DUK_ERR_URI_ERROR); } #endif /* * Default fatal error handler */ DUK_INTERNAL DUK_COLD void duk_default_fatal_handler(void *udata, const char *msg) { DUK_UNREF(udata); DUK_UNREF(msg); msg = msg ? msg : "NULL"; #if defined(DUK_USE_FATAL_HANDLER) /* duk_config.h provided a custom default fatal handler. */ DUK_D(DUK_DPRINT("custom default fatal error handler called: %s", msg)); DUK_USE_FATAL_HANDLER(udata, msg); #elif defined(DUK_USE_CPP_EXCEPTIONS) /* With C++ use a duk_fatal_exception which user code can catch in * a natural way. */ DUK_D(DUK_DPRINT("built-in default C++ fatal error handler called: %s", msg)); throw duk_fatal_exception(msg); #else /* Default behavior is to abort() on error. There's no printout * which makes this awkward, so it's always recommended to use an * explicit fatal error handler. * * ==================================================================== * NOTE: If you are seeing this, you are most likely dealing with an * uncaught error. You should provide a fatal error handler in Duktape * heap creation, and should consider using a protected call as your * first call into an empty Duktape context to properly handle errors. * See: * - http://duktape.org/guide.html#error-handling * - http://wiki.duktape.org/HowtoFatalErrors.html * - http://duktape.org/api.html#taglist-protected * ==================================================================== */ DUK_D(DUK_DPRINT("built-in default fatal error handler called: %s", msg)); DUK_ABORT(); #endif DUK_D(DUK_DPRINT("fatal error handler returned, enter forever loop")); for (;;) { /* Loop forever to ensure we don't return. */ } } /* automatic undefs */ #undef DUK__ERRFMT_BUFSIZE #line 1 "duk_unicode_support.c" /* * Various Unicode help functions for character classification predicates, * case conversion, decoding, etc. */ /* #include duk_internal.h -> already included */ /* * Fast path tables */ #if defined(DUK_USE_IDCHAR_FASTPATH) DUK_INTERNAL const duk_int8_t duk_is_idchar_tab[128] = { /* 0: not IdentifierStart or IdentifierPart * 1: IdentifierStart and IdentifierPart * -1: IdentifierPart only */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x00...0x0f */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x10...0x1f */ 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x20...0x2f */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, /* 0x30...0x3f */ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x40...0x4f */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, /* 0x50...0x5f */ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x60...0x6f */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0 /* 0x70...0x7f */ }; #endif /* * XUTF-8 and CESU-8 encoding/decoding */ DUK_INTERNAL duk_small_int_t duk_unicode_get_xutf8_length(duk_ucodepoint_t cp) { duk_uint_fast32_t x = (duk_uint_fast32_t) cp; if (x < 0x80UL) { /* 7 bits */ return 1; } else if (x < 0x800UL) { /* 11 bits */ return 2; } else if (x < 0x10000UL) { /* 16 bits */ return 3; } else if (x < 0x200000UL) { /* 21 bits */ return 4; } else if (x < 0x4000000UL) { /* 26 bits */ return 5; } else if (x < (duk_ucodepoint_t) 0x80000000UL) { /* 31 bits */ return 6; } else { /* 36 bits */ return 7; } } #if defined(DUK_USE_ASSERTIONS) DUK_INTERNAL duk_small_int_t duk_unicode_get_cesu8_length(duk_ucodepoint_t cp) { duk_uint_fast32_t x = (duk_uint_fast32_t) cp; if (x < 0x80UL) { /* 7 bits */ return 1; } else if (x < 0x800UL) { /* 11 bits */ return 2; } else if (x < 0x10000UL) { /* 16 bits */ return 3; } else { /* Encoded as surrogate pair, each encoding to 3 bytes for * 6 bytes total. Codepoints above U+10FFFF encode as 6 bytes * too, see duk_unicode_encode_cesu8(). */ return 3 + 3; } } #endif /* DUK_USE_ASSERTIONS */ DUK_INTERNAL const duk_uint8_t duk_unicode_xutf8_markers[7] = { 0x00, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe }; /* Encode to extended UTF-8; 'out' must have space for at least * DUK_UNICODE_MAX_XUTF8_LENGTH bytes. Allows encoding of any * 32-bit (unsigned) codepoint. */ DUK_INTERNAL duk_small_int_t duk_unicode_encode_xutf8(duk_ucodepoint_t cp, duk_uint8_t *out) { duk_uint_fast32_t x = (duk_uint_fast32_t) cp; duk_small_int_t len; duk_uint8_t marker; duk_small_int_t i; len = duk_unicode_get_xutf8_length(cp); DUK_ASSERT(len > 0); marker = duk_unicode_xutf8_markers[len - 1]; /* 64-bit OK because always >= 0 */ i = len; DUK_ASSERT(i > 0); do { i--; if (i > 0) { out[i] = (duk_uint8_t) (0x80 + (x & 0x3f)); x >>= 6; } else { /* Note: masking of 'x' is not necessary because of * range check and shifting -> no bits overlapping * the marker should be set. */ out[0] = (duk_uint8_t) (marker + x); } } while (i > 0); return len; } /* Encode to CESU-8; 'out' must have space for at least * DUK_UNICODE_MAX_CESU8_LENGTH bytes; codepoints above U+10FFFF * will encode to garbage but won't overwrite the output buffer. */ DUK_INTERNAL duk_small_int_t duk_unicode_encode_cesu8(duk_ucodepoint_t cp, duk_uint8_t *out) { duk_uint_fast32_t x = (duk_uint_fast32_t) cp; duk_small_int_t len; if (x < 0x80UL) { out[0] = (duk_uint8_t) x; len = 1; } else if (x < 0x800UL) { out[0] = (duk_uint8_t) (0xc0 + ((x >> 6) & 0x1f)); out[1] = (duk_uint8_t) (0x80 + (x & 0x3f)); len = 2; } else if (x < 0x10000UL) { /* surrogate pairs get encoded here */ out[0] = (duk_uint8_t) (0xe0 + ((x >> 12) & 0x0f)); out[1] = (duk_uint8_t) (0x80 + ((x >> 6) & 0x3f)); out[2] = (duk_uint8_t) (0x80 + (x & 0x3f)); len = 3; } else { /* * Unicode codepoints above U+FFFF are encoded as surrogate * pairs here. This ensures that all CESU-8 codepoints are * 16-bit values as expected in ECMAScript. The surrogate * pairs always get a 3-byte encoding (each) in CESU-8. * See: http://en.wikipedia.org/wiki/Surrogate_pair * * 20-bit codepoint, 10 bits (A and B) per surrogate pair: * * x = 0b00000000 0000AAAA AAAAAABB BBBBBBBB * sp1 = 0b110110AA AAAAAAAA (0xd800 + ((x >> 10) & 0x3ff)) * sp2 = 0b110111BB BBBBBBBB (0xdc00 + (x & 0x3ff)) * * Encoded into CESU-8: * * sp1 -> 0b11101101 (0xe0 + ((sp1 >> 12) & 0x0f)) * -> 0b1010AAAA (0x80 + ((sp1 >> 6) & 0x3f)) * -> 0b10AAAAAA (0x80 + (sp1 & 0x3f)) * sp2 -> 0b11101101 (0xe0 + ((sp2 >> 12) & 0x0f)) * -> 0b1011BBBB (0x80 + ((sp2 >> 6) & 0x3f)) * -> 0b10BBBBBB (0x80 + (sp2 & 0x3f)) * * Note that 0x10000 must be subtracted first. The code below * avoids the sp1, sp2 temporaries which saves around 20 bytes * of code. */ x -= 0x10000UL; out[0] = (duk_uint8_t) (0xed); out[1] = (duk_uint8_t) (0xa0 + ((x >> 16) & 0x0f)); out[2] = (duk_uint8_t) (0x80 + ((x >> 10) & 0x3f)); out[3] = (duk_uint8_t) (0xed); out[4] = (duk_uint8_t) (0xb0 + ((x >> 6) & 0x0f)); out[5] = (duk_uint8_t) (0x80 + (x & 0x3f)); len = 6; } return len; } /* Decode helper. Return zero on error. */ DUK_INTERNAL duk_small_int_t duk_unicode_decode_xutf8(duk_hthread *thr, const duk_uint8_t **ptr, const duk_uint8_t *ptr_start, const duk_uint8_t *ptr_end, duk_ucodepoint_t *out_cp) { const duk_uint8_t *p; duk_uint32_t res; duk_uint_fast8_t ch; duk_small_int_t n; DUK_UNREF(thr); p = *ptr; if (p < ptr_start || p >= ptr_end) { goto fail; } /* * UTF-8 decoder which accepts longer than standard byte sequences. * This allows full 32-bit code points to be used. */ ch = (duk_uint_fast8_t) (*p++); if (ch < 0x80) { /* 0xxx xxxx [7 bits] */ res = (duk_uint32_t) (ch & 0x7f); n = 0; } else if (ch < 0xc0) { /* 10xx xxxx -> invalid */ goto fail; } else if (ch < 0xe0) { /* 110x xxxx 10xx xxxx [11 bits] */ res = (duk_uint32_t) (ch & 0x1f); n = 1; } else if (ch < 0xf0) { /* 1110 xxxx 10xx xxxx 10xx xxxx [16 bits] */ res = (duk_uint32_t) (ch & 0x0f); n = 2; } else if (ch < 0xf8) { /* 1111 0xxx 10xx xxxx 10xx xxxx 10xx xxxx [21 bits] */ res = (duk_uint32_t) (ch & 0x07); n = 3; } else if (ch < 0xfc) { /* 1111 10xx 10xx xxxx 10xx xxxx 10xx xxxx 10xx xxxx [26 bits] */ res = (duk_uint32_t) (ch & 0x03); n = 4; } else if (ch < 0xfe) { /* 1111 110x 10xx xxxx 10xx xxxx 10xx xxxx 10xx xxxx 10xx xxxx [31 bits] */ res = (duk_uint32_t) (ch & 0x01); n = 5; } else if (ch < 0xff) { /* 1111 1110 10xx xxxx 10xx xxxx 10xx xxxx 10xx xxxx 10xx xxxx 10xx xxxx [36 bits] */ res = (duk_uint32_t) (0); n = 6; } else { /* 8-byte format could be: * 1111 1111 10xx xxxx 10xx xxxx 10xx xxxx 10xx xxxx 10xx xxxx 10xx xxxx 10xx xxxx [41 bits] * * However, this format would not have a zero bit following the * leading one bits and would not allow 0xFF to be used as an * "invalid xutf-8" marker for internal keys. Further, 8-byte * encodings (up to 41 bit code points) are not currently needed. */ goto fail; } DUK_ASSERT(p >= ptr_start); /* verified at beginning */ if (p + n > ptr_end) { /* check pointer at end */ goto fail; } while (n > 0) { DUK_ASSERT(p >= ptr_start && p < ptr_end); ch = (duk_uint_fast8_t) (*p++); #if 0 if (ch & 0xc0 != 0x80) { /* not a continuation byte */ p--; *ptr = p; *out_cp = DUK_UNICODE_CP_REPLACEMENT_CHARACTER; return 1; } #endif res = (res << 6) + (duk_uint32_t) (ch & 0x3f); n--; } *ptr = p; *out_cp = res; return 1; fail: return 0; } /* used by e.g. duk_regexp_executor.c, string built-ins */ DUK_INTERNAL duk_ucodepoint_t duk_unicode_decode_xutf8_checked(duk_hthread *thr, const duk_uint8_t **ptr, const duk_uint8_t *ptr_start, const duk_uint8_t *ptr_end) { duk_ucodepoint_t cp; if (duk_unicode_decode_xutf8(thr, ptr, ptr_start, ptr_end, &cp)) { return cp; } DUK_ERROR_INTERNAL(thr); DUK_WO_NORETURN(return 0;); } /* Compute (extended) utf-8 length without codepoint encoding validation, * used for string interning. * * NOTE: This algorithm is performance critical, more so than string hashing * in some cases. It is needed when interning a string and needs to scan * every byte of the string with no skipping. Having an ASCII fast path * is useful if possible in the algorithm. The current algorithms were * chosen from several variants, based on x64 gcc -O2 testing. See: * https://github.com/svaarala/duktape/pull/422 * * NOTE: must match tools/dukutil.py:duk_unicode_unvalidated_utf8_length(). */ #if defined(DUK_USE_PREFER_SIZE) /* Small variant; roughly 150 bytes smaller than the fast variant. */ DUK_INTERNAL duk_size_t duk_unicode_unvalidated_utf8_length(const duk_uint8_t *data, duk_size_t blen) { const duk_uint8_t *p; const duk_uint8_t *p_end; duk_size_t ncont; duk_size_t clen; p = data; p_end = data + blen; ncont = 0; while (p != p_end) { duk_uint8_t x; x = *p++; if (DUK_UNLIKELY(x >= 0x80 && x <= 0xbf)) { ncont++; } } DUK_ASSERT(ncont <= blen); clen = blen - ncont; DUK_ASSERT(clen <= blen); return clen; } #else /* DUK_USE_PREFER_SIZE */ /* This seems like a good overall approach. Fast path for ASCII in 4 byte * blocks. */ DUK_INTERNAL duk_size_t duk_unicode_unvalidated_utf8_length(const duk_uint8_t *data, duk_size_t blen) { const duk_uint8_t *p; const duk_uint8_t *p_end; const duk_uint32_t *p32_end; const duk_uint32_t *p32; duk_size_t ncont; duk_size_t clen; ncont = 0; /* number of continuation (non-initial) bytes in [0x80,0xbf] */ p = data; p_end = data + blen; if (blen < 16) { goto skip_fastpath; } /* Align 'p' to 4; the input data may have arbitrary alignment. * End of string check not needed because blen >= 16. */ while (((duk_size_t) (const void *) p) & 0x03U) { duk_uint8_t x; x = *p++; if (DUK_UNLIKELY(x >= 0x80 && x <= 0xbf)) { ncont++; } } /* Full, aligned 4-byte reads. */ p32_end = (const duk_uint32_t *) (const void *) (p + ((duk_size_t) (p_end - p) & (duk_size_t) (~0x03))); p32 = (const duk_uint32_t *) (const void *) p; while (p32 != (const duk_uint32_t *) p32_end) { duk_uint32_t x; x = *p32++; if (DUK_LIKELY((x & 0x80808080UL) == 0)) { ; /* ASCII fast path */ } else { /* Flip highest bit of each byte which changes * the bit pattern 10xxxxxx into 00xxxxxx which * allows an easy bit mask test. */ x ^= 0x80808080UL; if (DUK_UNLIKELY(!(x & 0xc0000000UL))) { ncont++; } if (DUK_UNLIKELY(!(x & 0x00c00000UL))) { ncont++; } if (DUK_UNLIKELY(!(x & 0x0000c000UL))) { ncont++; } if (DUK_UNLIKELY(!(x & 0x000000c0UL))) { ncont++; } } } p = (const duk_uint8_t *) p32; /* Fall through to handle the rest. */ skip_fastpath: while (p != p_end) { duk_uint8_t x; x = *p++; if (DUK_UNLIKELY(x >= 0x80 && x <= 0xbf)) { ncont++; } } DUK_ASSERT(ncont <= blen); clen = blen - ncont; DUK_ASSERT(clen <= blen); return clen; } #endif /* DUK_USE_PREFER_SIZE */ /* Check whether a string is UTF-8 compatible or not. */ DUK_INTERNAL duk_bool_t duk_unicode_is_utf8_compatible(const duk_uint8_t *buf, duk_size_t len) { duk_size_t i = 0; #if !defined(DUK_USE_PREFER_SIZE) duk_size_t len_safe; #endif /* Many practical strings are ASCII only, so use a fast path check * to check chunks of bytes at once with minimal branch cost. */ #if !defined(DUK_USE_PREFER_SIZE) len_safe = len & ~0x03UL; for (; i < len_safe; i += 4) { duk_uint8_t t = buf[i] | buf[i + 1] | buf[i + 2] | buf[i + 3]; if (DUK_UNLIKELY((t & 0x80U) != 0U)) { /* At least one byte was outside 0x00-0x7f, break * out to slow path (and remain there). * * XXX: We could also deal with the problem character * and resume fast path later. */ break; } } #endif for (; i < len;) { duk_uint8_t t; duk_size_t left; duk_size_t ncont; duk_uint32_t cp; duk_uint32_t mincp; t = buf[i++]; if (DUK_LIKELY((t & 0x80U) == 0U)) { /* Fast path, ASCII. */ continue; } /* Non-ASCII start byte, slow path. * * 10xx xxxx -> continuation byte * 110x xxxx + 1*CONT -> [0x80, 0x7ff] * 1110 xxxx + 2*CONT -> [0x800, 0xffff], must reject [0xd800,0xdfff] * 1111 0xxx + 3*CONT -> [0x10000, 0x10ffff] */ left = len - i; if (t <= 0xdfU) { /* 1101 1111 = 0xdf */ if (t <= 0xbfU) { /* 1011 1111 = 0xbf */ return 0; } ncont = 1; mincp = 0x80UL; cp = t & 0x1fU; } else if (t <= 0xefU) { /* 1110 1111 = 0xef */ ncont = 2; mincp = 0x800UL; cp = t & 0x0fU; } else if (t <= 0xf7U) { /* 1111 0111 = 0xf7 */ ncont = 3; mincp = 0x10000UL; cp = t & 0x07U; } else { return 0; } if (left < ncont) { return 0; } while (ncont > 0U) { t = buf[i++]; if ((t & 0xc0U) != 0x80U) { /* 10xx xxxx */ return 0; } cp = (cp << 6) + (t & 0x3fU); ncont--; } if (cp < mincp || cp > 0x10ffffUL || (cp >= 0xd800UL && cp <= 0xdfffUL)) { return 0; } } return 1; } /* * Unicode range matcher * * Matches a codepoint against a packed bitstream of character ranges. * Used for slow path Unicode matching. */ /* Must match tools/extract_chars.py, generate_match_table3(). */ DUK_LOCAL duk_uint32_t duk__uni_decode_value(duk_bitdecoder_ctx *bd_ctx) { duk_uint32_t t; t = (duk_uint32_t) duk_bd_decode(bd_ctx, 4); if (t <= 0x0eU) { return t; } t = (duk_uint32_t) duk_bd_decode(bd_ctx, 8); if (t <= 0xfdU) { return t + 0x0f; } if (t == 0xfeU) { t = (duk_uint32_t) duk_bd_decode(bd_ctx, 12); return t + 0x0fU + 0xfeU; } else { t = (duk_uint32_t) duk_bd_decode(bd_ctx, 24); return t + 0x0fU + 0xfeU + 0x1000UL; } } DUK_LOCAL duk_small_int_t duk__uni_range_match(const duk_uint8_t *unitab, duk_size_t unilen, duk_codepoint_t cp) { duk_bitdecoder_ctx bd_ctx; duk_codepoint_t prev_re; duk_memzero(&bd_ctx, sizeof(bd_ctx)); bd_ctx.data = (const duk_uint8_t *) unitab; bd_ctx.length = (duk_size_t) unilen; prev_re = 0; for (;;) { duk_codepoint_t r1, r2; r1 = (duk_codepoint_t) duk__uni_decode_value(&bd_ctx); if (r1 == 0) { break; } r2 = (duk_codepoint_t) duk__uni_decode_value(&bd_ctx); r1 = prev_re + r1; r2 = r1 + r2; prev_re = r2; /* [r1,r2] is the range */ DUK_DDD(DUK_DDDPRINT("duk__uni_range_match: cp=%06lx range=[0x%06lx,0x%06lx]", (unsigned long) cp, (unsigned long) r1, (unsigned long) r2)); if (cp >= r1 && cp <= r2) { return 1; } } return 0; } /* * "WhiteSpace" production check. */ DUK_INTERNAL duk_small_int_t duk_unicode_is_whitespace(duk_codepoint_t cp) { /* * E5 Section 7.2 specifies six characters specifically as * white space: * * 0009;<control>;Cc;0;S;;;;;N;CHARACTER TABULATION;;;; * 000B;<control>;Cc;0;S;;;;;N;LINE TABULATION;;;; * 000C;<control>;Cc;0;WS;;;;;N;FORM FEED (FF);;;; * 0020;SPACE;Zs;0;WS;;;;;N;;;;; * 00A0;NO-BREAK SPACE;Zs;0;CS;<noBreak> 0020;;;;N;NON-BREAKING SPACE;;;; * FEFF;ZERO WIDTH NO-BREAK SPACE;Cf;0;BN;;;;;N;BYTE ORDER MARK;;;; * * It also specifies any Unicode category 'Zs' characters as white * space. These can be extracted with the "tools/extract_chars.py" script. * Current result: * * RAW OUTPUT: * =========== * 0020;SPACE;Zs;0;WS;;;;;N;;;;; * 00A0;NO-BREAK SPACE;Zs;0;CS;<noBreak> 0020;;;;N;NON-BREAKING SPACE;;;; * 1680;OGHAM SPACE MARK;Zs;0;WS;;;;;N;;;;; * 180E;MONGOLIAN VOWEL SEPARATOR;Zs;0;WS;;;;;N;;;;; * 2000;EN QUAD;Zs;0;WS;2002;;;;N;;;;; * 2001;EM QUAD;Zs;0;WS;2003;;;;N;;;;; * 2002;EN SPACE;Zs;0;WS;<compat> 0020;;;;N;;;;; * 2003;EM SPACE;Zs;0;WS;<compat> 0020;;;;N;;;;; * 2004;THREE-PER-EM SPACE;Zs;0;WS;<compat> 0020;;;;N;;;;; * 2005;FOUR-PER-EM SPACE;Zs;0;WS;<compat> 0020;;;;N;;;;; * 2006;SIX-PER-EM SPACE;Zs;0;WS;<compat> 0020;;;;N;;;;; * 2007;FIGURE SPACE;Zs;0;WS;<noBreak> 0020;;;;N;;;;; * 2008;PUNCTUATION SPACE;Zs;0;WS;<compat> 0020;;;;N;;;;; * 2009;THIN SPACE;Zs;0;WS;<compat> 0020;;;;N;;;;; * 200A;HAIR SPACE;Zs;0;WS;<compat> 0020;;;;N;;;;; * 202F;NARROW NO-BREAK SPACE;Zs;0;CS;<noBreak> 0020;;;;N;;;;; * 205F;MEDIUM MATHEMATICAL SPACE;Zs;0;WS;<compat> 0020;;;;N;;;;; * 3000;IDEOGRAPHIC SPACE;Zs;0;WS;<wide> 0020;;;;N;;;;; * * RANGES: * ======= * 0x0020 * 0x00a0 * 0x1680 * 0x180e * 0x2000 ... 0x200a * 0x202f * 0x205f * 0x3000 * * A manual decoder (below) is probably most compact for this. */ duk_uint_fast8_t lo; duk_uint_fast32_t hi; /* cp == -1 (EOF) never matches and causes return value 0 */ lo = (duk_uint_fast8_t) (cp & 0xff); hi = (duk_uint_fast32_t) (cp >> 8); /* does not fit into an uchar */ if (hi == 0x0000UL) { if (lo == 0x09U || lo == 0x0bU || lo == 0x0cU || lo == 0x20U || lo == 0xa0U) { return 1; } } else if (hi == 0x0020UL) { if (lo <= 0x0aU || lo == 0x2fU || lo == 0x5fU) { return 1; } } else if (cp == 0x1680L || cp == 0x180eL || cp == 0x3000L || cp == 0xfeffL) { return 1; } return 0; } /* * "LineTerminator" production check. */ DUK_INTERNAL duk_small_int_t duk_unicode_is_line_terminator(duk_codepoint_t cp) { /* * E5 Section 7.3 * * A LineTerminatorSequence essentially merges <CR> <LF> sequences * into a single line terminator. This must be handled by the caller. */ if (cp == 0x000aL || cp == 0x000dL || cp == 0x2028L || cp == 0x2029L) { return 1; } return 0; } /* * "IdentifierStart" production check. */ DUK_INTERNAL duk_small_int_t duk_unicode_is_identifier_start(duk_codepoint_t cp) { /* * E5 Section 7.6: * * IdentifierStart: * UnicodeLetter * $ * _ * \ UnicodeEscapeSequence * * IdentifierStart production has one multi-character production: * * \ UnicodeEscapeSequence * * The '\' character is -not- matched by this function. Rather, the caller * should decode the escape and then call this function to check whether the * decoded character is acceptable (see discussion in E5 Section 7.6). * * The "UnicodeLetter" alternative of the production allows letters * from various Unicode categories. These can be extracted with the * "tools/extract_chars.py" script. * * Because the result has hundreds of Unicode codepoint ranges, matching * for any values >= 0x80 are done using a very slow range-by-range scan * and a packed range format. * * The ASCII portion (codepoints 0x00 ... 0x7f) is fast-pathed below because * it matters the most. The ASCII related ranges of IdentifierStart are: * * 0x0041 ... 0x005a ['A' ... 'Z'] * 0x0061 ... 0x007a ['a' ... 'z'] * 0x0024 ['$'] * 0x005f ['_'] */ /* ASCII (and EOF) fast path -- quick accept and reject */ if (cp <= 0x7fL) { #if defined(DUK_USE_IDCHAR_FASTPATH) return (cp >= 0) && (duk_is_idchar_tab[cp] > 0); #else if ((cp >= 'a' && cp <= 'z') || (cp >= 'A' && cp <= 'Z') || cp == '_' || cp == '$') { return 1; } return 0; #endif } /* Non-ASCII slow path (range-by-range linear comparison), very slow */ #if defined(DUK_USE_SOURCE_NONBMP) if (duk__uni_range_match(duk_unicode_ids_noa, (duk_size_t) sizeof(duk_unicode_ids_noa), (duk_codepoint_t) cp)) { return 1; } return 0; #else if (cp < 0x10000L) { if (duk__uni_range_match(duk_unicode_ids_noabmp, sizeof(duk_unicode_ids_noabmp), (duk_codepoint_t) cp)) { return 1; } return 0; } else { /* without explicit non-BMP support, assume non-BMP characters * are always accepted as identifier characters. */ return 1; } #endif } /* * "IdentifierPart" production check. */ DUK_INTERNAL duk_small_int_t duk_unicode_is_identifier_part(duk_codepoint_t cp) { /* * E5 Section 7.6: * * IdentifierPart: * IdentifierStart * UnicodeCombiningMark * UnicodeDigit * UnicodeConnectorPunctuation * <ZWNJ> [U+200C] * <ZWJ> [U+200D] * * IdentifierPart production has one multi-character production * as part of its IdentifierStart alternative. The '\' character * of an escape sequence is not matched here, see discussion in * duk_unicode_is_identifier_start(). * * To match non-ASCII characters (codepoints >= 0x80), a very slow * linear range-by-range scan is used. The codepoint is first compared * to the IdentifierStart ranges, and if it doesn't match, then to a * set consisting of code points in IdentifierPart but not in * IdentifierStart. This is done to keep the unicode range data small, * at the expense of speed. * * The ASCII fast path consists of: * * 0x0030 ... 0x0039 ['0' ... '9', UnicodeDigit] * 0x0041 ... 0x005a ['A' ... 'Z', IdentifierStart] * 0x0061 ... 0x007a ['a' ... 'z', IdentifierStart] * 0x0024 ['$', IdentifierStart] * 0x005f ['_', IdentifierStart and * UnicodeConnectorPunctuation] * * UnicodeCombiningMark has no code points <= 0x7f. * * The matching code reuses the "identifier start" tables, and then * consults a separate range set for characters in "identifier part" * but not in "identifier start". These can be extracted with the * "tools/extract_chars.py" script. * * UnicodeCombiningMark -> categories Mn, Mc * UnicodeDigit -> categories Nd * UnicodeConnectorPunctuation -> categories Pc */ /* ASCII (and EOF) fast path -- quick accept and reject */ if (cp <= 0x7fL) { #if defined(DUK_USE_IDCHAR_FASTPATH) return (cp >= 0) && (duk_is_idchar_tab[cp] != 0); #else if ((cp >= 'a' && cp <= 'z') || (cp >= 'A' && cp <= 'Z') || (cp >= '0' && cp <= '9') || cp == '_' || cp == '$') { return 1; } return 0; #endif } /* Non-ASCII slow path (range-by-range linear comparison), very slow */ #if defined(DUK_USE_SOURCE_NONBMP) if (duk__uni_range_match(duk_unicode_ids_noa, sizeof(duk_unicode_ids_noa), (duk_codepoint_t) cp) || duk__uni_range_match(duk_unicode_idp_m_ids_noa, sizeof(duk_unicode_idp_m_ids_noa), (duk_codepoint_t) cp)) { return 1; } return 0; #else if (cp < 0x10000L) { if (duk__uni_range_match(duk_unicode_ids_noabmp, sizeof(duk_unicode_ids_noabmp), (duk_codepoint_t) cp) || duk__uni_range_match(duk_unicode_idp_m_ids_noabmp, sizeof(duk_unicode_idp_m_ids_noabmp), (duk_codepoint_t) cp)) { return 1; } return 0; } else { /* without explicit non-BMP support, assume non-BMP characters * are always accepted as identifier characters. */ return 1; } #endif } /* * Unicode letter check. */ DUK_INTERNAL duk_small_int_t duk_unicode_is_letter(duk_codepoint_t cp) { /* * Unicode letter is now taken to be the categories: * * Lu, Ll, Lt, Lm, Lo * * (Not sure if this is exactly correct.) * * The ASCII fast path consists of: * * 0x0041 ... 0x005a ['A' ... 'Z'] * 0x0061 ... 0x007a ['a' ... 'z'] */ /* ASCII (and EOF) fast path -- quick accept and reject */ if (cp <= 0x7fL) { if ((cp >= 'a' && cp <= 'z') || (cp >= 'A' && cp <= 'Z')) { return 1; } return 0; } /* Non-ASCII slow path (range-by-range linear comparison), very slow */ #if defined(DUK_USE_SOURCE_NONBMP) if (duk__uni_range_match(duk_unicode_ids_noa, sizeof(duk_unicode_ids_noa), (duk_codepoint_t) cp) && !duk__uni_range_match(duk_unicode_ids_m_let_noa, sizeof(duk_unicode_ids_m_let_noa), (duk_codepoint_t) cp)) { return 1; } return 0; #else if (cp < 0x10000L) { if (duk__uni_range_match(duk_unicode_ids_noabmp, sizeof(duk_unicode_ids_noabmp), (duk_codepoint_t) cp) && !duk__uni_range_match(duk_unicode_ids_m_let_noabmp, sizeof(duk_unicode_ids_m_let_noabmp), (duk_codepoint_t) cp)) { return 1; } return 0; } else { /* without explicit non-BMP support, assume non-BMP characters * are always accepted as letters. */ return 1; } #endif } /* * Complex case conversion helper which decodes a bit-packed conversion * control stream generated by tools/extract_caseconv.py. The conversion * is very slow because it runs through the conversion data in a linear * fashion to save space (which is why ASCII characters have a special * fast path before arriving here). * * The particular bit counts etc have been determined experimentally to * be small but still sufficient, and must match the Python script * (tools/extract_caseconv.py). * * The return value is the case converted codepoint or -1 if the conversion * results in multiple characters (this is useful for regexp Canonicalization * operation). If 'buf' is not NULL, the result codepoint(s) are also * appended to the hbuffer. * * Context and locale specific rules must be checked before consulting * this function. */ DUK_LOCAL duk_codepoint_t duk__slow_case_conversion(duk_hthread *thr, duk_bufwriter_ctx *bw, duk_codepoint_t cp, duk_bitdecoder_ctx *bd_ctx) { duk_small_int_t skip = 0; duk_small_int_t n; duk_small_int_t t; duk_small_int_t count; duk_codepoint_t tmp_cp; duk_codepoint_t start_i; duk_codepoint_t start_o; DUK_ASSERT(bd_ctx != NULL); DUK_UNREF(thr); DUK_DDD(DUK_DDDPRINT("slow case conversion for codepoint: %ld", (long) cp)); /* range conversion with a "skip" */ DUK_DDD(DUK_DDDPRINT("checking ranges")); for (;;) { skip++; n = (duk_small_int_t) duk_bd_decode(bd_ctx, 6); if (n == 0x3f) { /* end marker */ break; } DUK_DDD(DUK_DDDPRINT("skip=%ld, n=%ld", (long) skip, (long) n)); while (n--) { start_i = (duk_codepoint_t) duk_bd_decode(bd_ctx, 16); start_o = (duk_codepoint_t) duk_bd_decode(bd_ctx, 16); count = (duk_small_int_t) duk_bd_decode(bd_ctx, 7); DUK_DDD(DUK_DDDPRINT("range: start_i=%ld, start_o=%ld, count=%ld, skip=%ld", (long) start_i, (long) start_o, (long) count, (long) skip)); if (cp >= start_i) { tmp_cp = cp - start_i; /* always >= 0 */ if (tmp_cp < (duk_codepoint_t) count * (duk_codepoint_t) skip && (tmp_cp % (duk_codepoint_t) skip) == 0) { DUK_DDD(DUK_DDDPRINT("range matches input codepoint")); cp = start_o + tmp_cp; goto single; } } } } /* 1:1 conversion */ n = (duk_small_int_t) duk_bd_decode(bd_ctx, 7); DUK_DDD(DUK_DDDPRINT("checking 1:1 conversions (count %ld)", (long) n)); while (n--) { start_i = (duk_codepoint_t) duk_bd_decode(bd_ctx, 16); start_o = (duk_codepoint_t) duk_bd_decode(bd_ctx, 16); DUK_DDD(DUK_DDDPRINT("1:1 conversion %ld -> %ld", (long) start_i, (long) start_o)); if (cp == start_i) { DUK_DDD(DUK_DDDPRINT("1:1 matches input codepoint")); cp = start_o; goto single; } } /* complex, multicharacter conversion */ n = (duk_small_int_t) duk_bd_decode(bd_ctx, 7); DUK_DDD(DUK_DDDPRINT("checking 1:n conversions (count %ld)", (long) n)); while (n--) { start_i = (duk_codepoint_t) duk_bd_decode(bd_ctx, 16); t = (duk_small_int_t) duk_bd_decode(bd_ctx, 2); DUK_DDD(DUK_DDDPRINT("1:n conversion %ld -> %ld chars", (long) start_i, (long) t)); if (cp == start_i) { DUK_DDD(DUK_DDDPRINT("1:n matches input codepoint")); if (bw != NULL) { while (t--) { tmp_cp = (duk_codepoint_t) duk_bd_decode(bd_ctx, 16); DUK_BW_WRITE_RAW_XUTF8(thr, bw, (duk_ucodepoint_t) tmp_cp); } } return -1; } else { while (t--) { (void) duk_bd_decode(bd_ctx, 16); } } } /* default: no change */ DUK_DDD(DUK_DDDPRINT("no rule matches, output is same as input")); /* fall through */ single: if (bw != NULL) { DUK_BW_WRITE_RAW_XUTF8(thr, bw, (duk_ucodepoint_t) cp); } return cp; } /* * Case conversion helper, with context/local sensitivity. * For proper case conversion, one needs to know the character * and the preceding and following characters, as well as * locale/language. */ /* XXX: add 'language' argument when locale/language sensitive rule * support added. */ DUK_LOCAL duk_codepoint_t duk__case_transform_helper(duk_hthread *thr, duk_bufwriter_ctx *bw, duk_codepoint_t cp, duk_codepoint_t prev, duk_codepoint_t next, duk_bool_t uppercase) { duk_bitdecoder_ctx bd_ctx; /* fast path for ASCII */ if (cp < 0x80L) { /* XXX: there are language sensitive rules for the ASCII range. * If/when language/locale support is implemented, they need to * be implemented here for the fast path. There are no context * sensitive rules for ASCII range. */ if (uppercase) { if (cp >= 'a' && cp <= 'z') { cp = cp - 'a' + 'A'; } } else { if (cp >= 'A' && cp <= 'Z') { cp = cp - 'A' + 'a'; } } if (bw != NULL) { DUK_BW_WRITE_RAW_U8(thr, bw, (duk_uint8_t) cp); } return cp; } /* context and locale specific rules which cannot currently be represented * in the caseconv bitstream: hardcoded rules in C */ if (uppercase) { /* XXX: turkish / azeri */ } else { /* * Final sigma context specific rule. This is a rather tricky * rule and this handling is probably not 100% correct now. * The rule is not locale/language specific so it is supported. */ if (cp == 0x03a3L && /* U+03A3 = GREEK CAPITAL LETTER SIGMA */ duk_unicode_is_letter(prev) && /* prev exists and is not a letter */ !duk_unicode_is_letter(next)) { /* next does not exist or next is not a letter */ /* Capital sigma occurred at "end of word", lowercase to * U+03C2 = GREEK SMALL LETTER FINAL SIGMA. Otherwise * fall through and let the normal rules lowercase it to * U+03C3 = GREEK SMALL LETTER SIGMA. */ cp = 0x03c2L; goto singlechar; } /* XXX: lithuanian not implemented */ /* XXX: lithuanian, explicit dot rules */ /* XXX: turkish / azeri, lowercase rules */ } /* 1:1 or special conversions, but not locale/context specific: script generated rules */ duk_memzero(&bd_ctx, sizeof(bd_ctx)); if (uppercase) { bd_ctx.data = (const duk_uint8_t *) duk_unicode_caseconv_uc; bd_ctx.length = (duk_size_t) sizeof(duk_unicode_caseconv_uc); } else { bd_ctx.data = (const duk_uint8_t *) duk_unicode_caseconv_lc; bd_ctx.length = (duk_size_t) sizeof(duk_unicode_caseconv_lc); } return duk__slow_case_conversion(thr, bw, cp, &bd_ctx); singlechar: if (bw != NULL) { DUK_BW_WRITE_RAW_XUTF8(thr, bw, (duk_ucodepoint_t) cp); } return cp; /* unused now, not needed until Turkish/Azeri */ #if 0 nochar: return -1; #endif } /* * Replace valstack top with case converted version. */ DUK_INTERNAL void duk_unicode_case_convert_string(duk_hthread *thr, duk_bool_t uppercase) { duk_hstring *h_input; duk_bufwriter_ctx bw_alloc; duk_bufwriter_ctx *bw; const duk_uint8_t *p, *p_start, *p_end; duk_codepoint_t prev, curr, next; h_input = duk_require_hstring(thr, -1); /* Accept symbols. */ DUK_ASSERT(h_input != NULL); bw = &bw_alloc; DUK_BW_INIT_PUSHBUF(thr, bw, DUK_HSTRING_GET_BYTELEN(h_input)); /* [ ... input buffer ] */ p_start = (const duk_uint8_t *) DUK_HSTRING_GET_DATA(h_input); p_end = p_start + DUK_HSTRING_GET_BYTELEN(h_input); p = p_start; prev = -1; DUK_UNREF(prev); curr = -1; next = -1; for (;;) { prev = curr; curr = next; next = -1; if (p < p_end) { next = (duk_codepoint_t) duk_unicode_decode_xutf8_checked(thr, &p, p_start, p_end); } else { /* end of input and last char has been processed */ if (curr < 0) { break; } } /* on first round, skip */ if (curr >= 0) { /* XXX: could add a fast path to process chunks of input codepoints, * but relative benefit would be quite small. */ /* Ensure space for maximum multi-character result; estimate is overkill. */ DUK_BW_ENSURE(thr, bw, 8 * DUK_UNICODE_MAX_XUTF8_LENGTH); duk__case_transform_helper(thr, bw, (duk_codepoint_t) curr, prev, next, uppercase); } } DUK_BW_COMPACT(thr, bw); (void) duk_buffer_to_string(thr, -1); /* Safe, output is encoded. */ /* invalidates h_buf pointer */ duk_remove_m2(thr); } #if defined(DUK_USE_REGEXP_SUPPORT) /* * Canonicalize() abstract operation needed for canonicalization of individual * codepoints during regexp compilation and execution, see E5 Section 15.10.2.8. * Note that codepoints are canonicalized one character at a time, so no context * specific rules can apply. Locale specific rules can apply, though. */ DUK_INTERNAL duk_codepoint_t duk_unicode_re_canonicalize_char(duk_hthread *thr, duk_codepoint_t cp) { #if defined(DUK_USE_REGEXP_CANON_WORKAROUND) /* Fast canonicalization lookup at the cost of 128kB footprint. */ DUK_ASSERT(cp >= 0); DUK_UNREF(thr); if (DUK_LIKELY(cp < 0x10000L)) { return (duk_codepoint_t) duk_unicode_re_canon_lookup[cp]; } return cp; #else /* DUK_USE_REGEXP_CANON_WORKAROUND */ duk_codepoint_t y; y = duk__case_transform_helper(thr, NULL, /* NULL is allowed, no output */ cp, /* curr char */ -1, /* prev char */ -1, /* next char */ 1); /* uppercase */ if ((y < 0) || (cp >= 0x80 && y < 0x80)) { /* multiple codepoint conversion or non-ASCII mapped to ASCII * --> leave as is. */ return cp; } return y; #endif /* DUK_USE_REGEXP_CANON_WORKAROUND */ } /* * E5 Section 15.10.2.6 "IsWordChar" abstract operation. Assume * x < 0 for characters read outside the string. */ DUK_INTERNAL duk_small_int_t duk_unicode_re_is_wordchar(duk_codepoint_t x) { /* * Note: the description in E5 Section 15.10.2.6 has a typo, it * contains 'A' twice and lacks 'a'; the intent is [0-9a-zA-Z_]. */ if ((x >= '0' && x <= '9') || (x >= 'a' && x <= 'z') || (x >= 'A' && x <= 'Z') || (x == '_')) { return 1; } return 0; } /* * Regexp range tables */ /* exposed because lexer needs these too */ DUK_INTERNAL const duk_uint16_t duk_unicode_re_ranges_digit[2] = { (duk_uint16_t) 0x0030UL, (duk_uint16_t) 0x0039UL, }; DUK_INTERNAL const duk_uint16_t duk_unicode_re_ranges_white[22] = { (duk_uint16_t) 0x0009UL, (duk_uint16_t) 0x000DUL, (duk_uint16_t) 0x0020UL, (duk_uint16_t) 0x0020UL, (duk_uint16_t) 0x00A0UL, (duk_uint16_t) 0x00A0UL, (duk_uint16_t) 0x1680UL, (duk_uint16_t) 0x1680UL, (duk_uint16_t) 0x180EUL, (duk_uint16_t) 0x180EUL, (duk_uint16_t) 0x2000UL, (duk_uint16_t) 0x200AUL, (duk_uint16_t) 0x2028UL, (duk_uint16_t) 0x2029UL, (duk_uint16_t) 0x202FUL, (duk_uint16_t) 0x202FUL, (duk_uint16_t) 0x205FUL, (duk_uint16_t) 0x205FUL, (duk_uint16_t) 0x3000UL, (duk_uint16_t) 0x3000UL, (duk_uint16_t) 0xFEFFUL, (duk_uint16_t) 0xFEFFUL, }; DUK_INTERNAL const duk_uint16_t duk_unicode_re_ranges_wordchar[8] = { (duk_uint16_t) 0x0030UL, (duk_uint16_t) 0x0039UL, (duk_uint16_t) 0x0041UL, (duk_uint16_t) 0x005AUL, (duk_uint16_t) 0x005FUL, (duk_uint16_t) 0x005FUL, (duk_uint16_t) 0x0061UL, (duk_uint16_t) 0x007AUL, }; DUK_INTERNAL const duk_uint16_t duk_unicode_re_ranges_not_digit[4] = { (duk_uint16_t) 0x0000UL, (duk_uint16_t) 0x002FUL, (duk_uint16_t) 0x003AUL, (duk_uint16_t) 0xFFFFUL, }; DUK_INTERNAL const duk_uint16_t duk_unicode_re_ranges_not_white[24] = { (duk_uint16_t) 0x0000UL, (duk_uint16_t) 0x0008UL, (duk_uint16_t) 0x000EUL, (duk_uint16_t) 0x001FUL, (duk_uint16_t) 0x0021UL, (duk_uint16_t) 0x009FUL, (duk_uint16_t) 0x00A1UL, (duk_uint16_t) 0x167FUL, (duk_uint16_t) 0x1681UL, (duk_uint16_t) 0x180DUL, (duk_uint16_t) 0x180FUL, (duk_uint16_t) 0x1FFFUL, (duk_uint16_t) 0x200BUL, (duk_uint16_t) 0x2027UL, (duk_uint16_t) 0x202AUL, (duk_uint16_t) 0x202EUL, (duk_uint16_t) 0x2030UL, (duk_uint16_t) 0x205EUL, (duk_uint16_t) 0x2060UL, (duk_uint16_t) 0x2FFFUL, (duk_uint16_t) 0x3001UL, (duk_uint16_t) 0xFEFEUL, (duk_uint16_t) 0xFF00UL, (duk_uint16_t) 0xFFFFUL, }; DUK_INTERNAL const duk_uint16_t duk_unicode_re_ranges_not_wordchar[10] = { (duk_uint16_t) 0x0000UL, (duk_uint16_t) 0x002FUL, (duk_uint16_t) 0x003AUL, (duk_uint16_t) 0x0040UL, (duk_uint16_t) 0x005BUL, (duk_uint16_t) 0x005EUL, (duk_uint16_t) 0x0060UL, (duk_uint16_t) 0x0060UL, (duk_uint16_t) 0x007BUL, (duk_uint16_t) 0xFFFFUL, }; #endif /* DUK_USE_REGEXP_SUPPORT */ #line 1 "duk_util_memrw.c" /* * Macro support functions for reading/writing raw data. * * These are done using memcpy to ensure they're valid even for unaligned * reads/writes on platforms where alignment counts. On x86 at least gcc * is able to compile these into a bswap+mov. "Always inline" is used to * ensure these macros compile to minimal code. */ /* #include duk_internal.h -> already included */ union duk__u16_union { duk_uint8_t b[2]; duk_uint16_t x; }; typedef union duk__u16_union duk__u16_union; union duk__u32_union { duk_uint8_t b[4]; duk_uint32_t x; }; typedef union duk__u32_union duk__u32_union; #if defined(DUK_USE_64BIT_OPS) union duk__u64_union { duk_uint8_t b[8]; duk_uint64_t x; }; typedef union duk__u64_union duk__u64_union; #endif DUK_INTERNAL DUK_ALWAYS_INLINE duk_uint16_t duk_raw_read_u16_be(const duk_uint8_t *p) { duk__u16_union u; duk_memcpy((void *) u.b, (const void *) p, (size_t) 2); u.x = DUK_NTOH16(u.x); return u.x; } DUK_INTERNAL DUK_ALWAYS_INLINE duk_uint32_t duk_raw_read_u32_be(const duk_uint8_t *p) { duk__u32_union u; duk_memcpy((void *) u.b, (const void *) p, (size_t) 4); u.x = DUK_NTOH32(u.x); return u.x; } DUK_INTERNAL DUK_ALWAYS_INLINE duk_float_t duk_raw_read_float_be(const duk_uint8_t *p) { duk_float_union fu; duk_memcpy((void *) fu.uc, (const void *) p, (size_t) 4); duk_fltunion_big_to_host(&fu); return fu.f; } DUK_INTERNAL DUK_ALWAYS_INLINE duk_double_t duk_raw_read_double_be(const duk_uint8_t *p) { duk_double_union du; duk_memcpy((void *) du.uc, (const void *) p, (size_t) 8); duk_dblunion_big_to_host(&du); return du.d; } DUK_INTERNAL DUK_ALWAYS_INLINE duk_uint16_t duk_raw_readinc_u16_be(const duk_uint8_t **p) { duk_uint16_t res = duk_raw_read_u16_be(*p); *p += 2; return res; } DUK_INTERNAL DUK_ALWAYS_INLINE duk_uint32_t duk_raw_readinc_u32_be(const duk_uint8_t **p) { duk_uint32_t res = duk_raw_read_u32_be(*p); *p += 4; return res; } DUK_INTERNAL DUK_ALWAYS_INLINE duk_float_t duk_raw_readinc_float_be(const duk_uint8_t **p) { duk_float_t res = duk_raw_read_float_be(*p); *p += 4; return res; } DUK_INTERNAL DUK_ALWAYS_INLINE duk_double_t duk_raw_readinc_double_be(const duk_uint8_t **p) { duk_double_t res = duk_raw_read_double_be(*p); *p += 8; return res; } DUK_INTERNAL DUK_ALWAYS_INLINE void duk_raw_write_u16_be(duk_uint8_t *p, duk_uint16_t val) { duk__u16_union u; u.x = DUK_HTON16(val); duk_memcpy((void *) p, (const void *) u.b, (size_t) 2); } DUK_INTERNAL DUK_ALWAYS_INLINE void duk_raw_write_u32_be(duk_uint8_t *p, duk_uint32_t val) { duk__u32_union u; u.x = DUK_HTON32(val); duk_memcpy((void *) p, (const void *) u.b, (size_t) 4); } DUK_INTERNAL DUK_ALWAYS_INLINE void duk_raw_write_float_be(duk_uint8_t *p, duk_float_t val) { duk_float_union fu; fu.f = val; duk_fltunion_host_to_big(&fu); duk_memcpy((void *) p, (const void *) fu.uc, (size_t) 4); } DUK_INTERNAL DUK_ALWAYS_INLINE void duk_raw_write_double_be(duk_uint8_t *p, duk_double_t val) { duk_double_union du; du.d = val; duk_dblunion_host_to_big(&du); duk_memcpy((void *) p, (const void *) du.uc, (size_t) 8); } DUK_INTERNAL duk_small_int_t duk_raw_write_xutf8(duk_uint8_t *p, duk_ucodepoint_t val) { duk_small_int_t len = duk_unicode_encode_xutf8(val, p); return len; } DUK_INTERNAL duk_small_int_t duk_raw_write_cesu8(duk_uint8_t *p, duk_ucodepoint_t val) { duk_small_int_t len = duk_unicode_encode_cesu8(val, p); return len; } DUK_INTERNAL DUK_ALWAYS_INLINE void duk_raw_writeinc_u16_be(duk_uint8_t **p, duk_uint16_t val) { duk_raw_write_u16_be(*p, val); *p += 2; } DUK_INTERNAL DUK_ALWAYS_INLINE void duk_raw_writeinc_u32_be(duk_uint8_t **p, duk_uint32_t val) { duk_raw_write_u32_be(*p, val); *p += 4; } DUK_INTERNAL DUK_ALWAYS_INLINE void duk_raw_writeinc_float_be(duk_uint8_t **p, duk_float_t val) { duk_raw_write_float_be(*p, val); *p += 4; } DUK_INTERNAL DUK_ALWAYS_INLINE void duk_raw_writeinc_double_be(duk_uint8_t **p, duk_double_t val) { duk_raw_write_double_be(*p, val); *p += 8; } DUK_INTERNAL void duk_raw_writeinc_xutf8(duk_uint8_t **p, duk_ucodepoint_t val) { duk_small_int_t len = duk_unicode_encode_xutf8(val, *p); *p += len; } DUK_INTERNAL void duk_raw_writeinc_cesu8(duk_uint8_t **p, duk_ucodepoint_t val) { duk_small_int_t len = duk_unicode_encode_cesu8(val, *p); *p += len; } #line 1 "duk_util_misc.c" /* * Misc util stuff. */ /* #include duk_internal.h -> already included */ /* * Lowercase digits for radix values 2 to 36. Also doubles as lowercase * hex nybble table. */ DUK_INTERNAL const duk_uint8_t duk_lc_digits[36] = { DUK_ASC_0, DUK_ASC_1, DUK_ASC_2, DUK_ASC_3, DUK_ASC_4, DUK_ASC_5, DUK_ASC_6, DUK_ASC_7, DUK_ASC_8, DUK_ASC_9, DUK_ASC_LC_A, DUK_ASC_LC_B, DUK_ASC_LC_C, DUK_ASC_LC_D, DUK_ASC_LC_E, DUK_ASC_LC_F, DUK_ASC_LC_G, DUK_ASC_LC_H, DUK_ASC_LC_I, DUK_ASC_LC_J, DUK_ASC_LC_K, DUK_ASC_LC_L, DUK_ASC_LC_M, DUK_ASC_LC_N, DUK_ASC_LC_O, DUK_ASC_LC_P, DUK_ASC_LC_Q, DUK_ASC_LC_R, DUK_ASC_LC_S, DUK_ASC_LC_T, DUK_ASC_LC_U, DUK_ASC_LC_V, DUK_ASC_LC_W, DUK_ASC_LC_X, DUK_ASC_LC_Y, DUK_ASC_LC_Z }; DUK_INTERNAL const duk_uint8_t duk_uc_nybbles[16] = { DUK_ASC_0, DUK_ASC_1, DUK_ASC_2, DUK_ASC_3, DUK_ASC_4, DUK_ASC_5, DUK_ASC_6, DUK_ASC_7, DUK_ASC_8, DUK_ASC_9, DUK_ASC_UC_A, DUK_ASC_UC_B, DUK_ASC_UC_C, DUK_ASC_UC_D, DUK_ASC_UC_E, DUK_ASC_UC_F }; /* * Table for hex decoding ASCII hex digits */ DUK_INTERNAL const duk_int8_t duk_hex_dectab[256] = { /* -1 if invalid */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0x00-0x0f */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0x10-0x1f */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0x20-0x2f */ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, /* 0x30-0x3f */ -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0x40-0x4f */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0x50-0x5f */ -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0x60-0x6f */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0x70-0x7f */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0x80-0x8f */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0x90-0x9f */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0xa0-0xaf */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0xb0-0xbf */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0xc0-0xcf */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0xd0-0xdf */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0xe0-0xef */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 /* 0xf0-0xff */ }; #if defined(DUK_USE_HEX_FASTPATH) /* Preshifted << 4. Must use 16-bit entry to allow negative value signaling. */ DUK_INTERNAL const duk_int16_t duk_hex_dectab_shift4[256] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0x00-0x0f */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0x10-0x1f */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0x20-0x2f */ 0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, -1, -1, -1, -1, -1, -1, /* 0x30-0x3f */ -1, 0xa0, 0xb0, 0xc0, 0xd0, 0xe0, 0xf0, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0x40-0x4f */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0x50-0x5f */ -1, 0xa0, 0xb0, 0xc0, 0xd0, 0xe0, 0xf0, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0x60-0x6f */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0x70-0x7f */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0x80-0x8f */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0x90-0x9f */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0xa0-0xaf */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0xb0-0xbf */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0xc0-0xcf */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0xd0-0xdf */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0xe0-0xef */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 /* 0xf0-0xff */ }; #endif /* * Table for hex encoding bytes */ #if defined(DUK_USE_HEX_FASTPATH) /* Lookup to encode one byte directly into 2 characters: * * def genhextab(bswap): * for i in xrange(256): * t = chr(i).encode('hex') * if bswap: * t = t[1] + t[0] * print('0x' + t.encode('hex') + 'U') * print('big endian'); genhextab(False) * print('little endian'); genhextab(True) */ DUK_INTERNAL const duk_uint16_t duk_hex_enctab[256] = { #if defined(DUK_USE_INTEGER_BE) 0x3030U, 0x3031U, 0x3032U, 0x3033U, 0x3034U, 0x3035U, 0x3036U, 0x3037U, 0x3038U, 0x3039U, 0x3061U, 0x3062U, 0x3063U, 0x3064U, 0x3065U, 0x3066U, 0x3130U, 0x3131U, 0x3132U, 0x3133U, 0x3134U, 0x3135U, 0x3136U, 0x3137U, 0x3138U, 0x3139U, 0x3161U, 0x3162U, 0x3163U, 0x3164U, 0x3165U, 0x3166U, 0x3230U, 0x3231U, 0x3232U, 0x3233U, 0x3234U, 0x3235U, 0x3236U, 0x3237U, 0x3238U, 0x3239U, 0x3261U, 0x3262U, 0x3263U, 0x3264U, 0x3265U, 0x3266U, 0x3330U, 0x3331U, 0x3332U, 0x3333U, 0x3334U, 0x3335U, 0x3336U, 0x3337U, 0x3338U, 0x3339U, 0x3361U, 0x3362U, 0x3363U, 0x3364U, 0x3365U, 0x3366U, 0x3430U, 0x3431U, 0x3432U, 0x3433U, 0x3434U, 0x3435U, 0x3436U, 0x3437U, 0x3438U, 0x3439U, 0x3461U, 0x3462U, 0x3463U, 0x3464U, 0x3465U, 0x3466U, 0x3530U, 0x3531U, 0x3532U, 0x3533U, 0x3534U, 0x3535U, 0x3536U, 0x3537U, 0x3538U, 0x3539U, 0x3561U, 0x3562U, 0x3563U, 0x3564U, 0x3565U, 0x3566U, 0x3630U, 0x3631U, 0x3632U, 0x3633U, 0x3634U, 0x3635U, 0x3636U, 0x3637U, 0x3638U, 0x3639U, 0x3661U, 0x3662U, 0x3663U, 0x3664U, 0x3665U, 0x3666U, 0x3730U, 0x3731U, 0x3732U, 0x3733U, 0x3734U, 0x3735U, 0x3736U, 0x3737U, 0x3738U, 0x3739U, 0x3761U, 0x3762U, 0x3763U, 0x3764U, 0x3765U, 0x3766U, 0x3830U, 0x3831U, 0x3832U, 0x3833U, 0x3834U, 0x3835U, 0x3836U, 0x3837U, 0x3838U, 0x3839U, 0x3861U, 0x3862U, 0x3863U, 0x3864U, 0x3865U, 0x3866U, 0x3930U, 0x3931U, 0x3932U, 0x3933U, 0x3934U, 0x3935U, 0x3936U, 0x3937U, 0x3938U, 0x3939U, 0x3961U, 0x3962U, 0x3963U, 0x3964U, 0x3965U, 0x3966U, 0x6130U, 0x6131U, 0x6132U, 0x6133U, 0x6134U, 0x6135U, 0x6136U, 0x6137U, 0x6138U, 0x6139U, 0x6161U, 0x6162U, 0x6163U, 0x6164U, 0x6165U, 0x6166U, 0x6230U, 0x6231U, 0x6232U, 0x6233U, 0x6234U, 0x6235U, 0x6236U, 0x6237U, 0x6238U, 0x6239U, 0x6261U, 0x6262U, 0x6263U, 0x6264U, 0x6265U, 0x6266U, 0x6330U, 0x6331U, 0x6332U, 0x6333U, 0x6334U, 0x6335U, 0x6336U, 0x6337U, 0x6338U, 0x6339U, 0x6361U, 0x6362U, 0x6363U, 0x6364U, 0x6365U, 0x6366U, 0x6430U, 0x6431U, 0x6432U, 0x6433U, 0x6434U, 0x6435U, 0x6436U, 0x6437U, 0x6438U, 0x6439U, 0x6461U, 0x6462U, 0x6463U, 0x6464U, 0x6465U, 0x6466U, 0x6530U, 0x6531U, 0x6532U, 0x6533U, 0x6534U, 0x6535U, 0x6536U, 0x6537U, 0x6538U, 0x6539U, 0x6561U, 0x6562U, 0x6563U, 0x6564U, 0x6565U, 0x6566U, 0x6630U, 0x6631U, 0x6632U, 0x6633U, 0x6634U, 0x6635U, 0x6636U, 0x6637U, 0x6638U, 0x6639U, 0x6661U, 0x6662U, 0x6663U, 0x6664U, 0x6665U, 0x6666U #else /* DUK_USE_INTEGER_BE */ 0x3030U, 0x3130U, 0x3230U, 0x3330U, 0x3430U, 0x3530U, 0x3630U, 0x3730U, 0x3830U, 0x3930U, 0x6130U, 0x6230U, 0x6330U, 0x6430U, 0x6530U, 0x6630U, 0x3031U, 0x3131U, 0x3231U, 0x3331U, 0x3431U, 0x3531U, 0x3631U, 0x3731U, 0x3831U, 0x3931U, 0x6131U, 0x6231U, 0x6331U, 0x6431U, 0x6531U, 0x6631U, 0x3032U, 0x3132U, 0x3232U, 0x3332U, 0x3432U, 0x3532U, 0x3632U, 0x3732U, 0x3832U, 0x3932U, 0x6132U, 0x6232U, 0x6332U, 0x6432U, 0x6532U, 0x6632U, 0x3033U, 0x3133U, 0x3233U, 0x3333U, 0x3433U, 0x3533U, 0x3633U, 0x3733U, 0x3833U, 0x3933U, 0x6133U, 0x6233U, 0x6333U, 0x6433U, 0x6533U, 0x6633U, 0x3034U, 0x3134U, 0x3234U, 0x3334U, 0x3434U, 0x3534U, 0x3634U, 0x3734U, 0x3834U, 0x3934U, 0x6134U, 0x6234U, 0x6334U, 0x6434U, 0x6534U, 0x6634U, 0x3035U, 0x3135U, 0x3235U, 0x3335U, 0x3435U, 0x3535U, 0x3635U, 0x3735U, 0x3835U, 0x3935U, 0x6135U, 0x6235U, 0x6335U, 0x6435U, 0x6535U, 0x6635U, 0x3036U, 0x3136U, 0x3236U, 0x3336U, 0x3436U, 0x3536U, 0x3636U, 0x3736U, 0x3836U, 0x3936U, 0x6136U, 0x6236U, 0x6336U, 0x6436U, 0x6536U, 0x6636U, 0x3037U, 0x3137U, 0x3237U, 0x3337U, 0x3437U, 0x3537U, 0x3637U, 0x3737U, 0x3837U, 0x3937U, 0x6137U, 0x6237U, 0x6337U, 0x6437U, 0x6537U, 0x6637U, 0x3038U, 0x3138U, 0x3238U, 0x3338U, 0x3438U, 0x3538U, 0x3638U, 0x3738U, 0x3838U, 0x3938U, 0x6138U, 0x6238U, 0x6338U, 0x6438U, 0x6538U, 0x6638U, 0x3039U, 0x3139U, 0x3239U, 0x3339U, 0x3439U, 0x3539U, 0x3639U, 0x3739U, 0x3839U, 0x3939U, 0x6139U, 0x6239U, 0x6339U, 0x6439U, 0x6539U, 0x6639U, 0x3061U, 0x3161U, 0x3261U, 0x3361U, 0x3461U, 0x3561U, 0x3661U, 0x3761U, 0x3861U, 0x3961U, 0x6161U, 0x6261U, 0x6361U, 0x6461U, 0x6561U, 0x6661U, 0x3062U, 0x3162U, 0x3262U, 0x3362U, 0x3462U, 0x3562U, 0x3662U, 0x3762U, 0x3862U, 0x3962U, 0x6162U, 0x6262U, 0x6362U, 0x6462U, 0x6562U, 0x6662U, 0x3063U, 0x3163U, 0x3263U, 0x3363U, 0x3463U, 0x3563U, 0x3663U, 0x3763U, 0x3863U, 0x3963U, 0x6163U, 0x6263U, 0x6363U, 0x6463U, 0x6563U, 0x6663U, 0x3064U, 0x3164U, 0x3264U, 0x3364U, 0x3464U, 0x3564U, 0x3664U, 0x3764U, 0x3864U, 0x3964U, 0x6164U, 0x6264U, 0x6364U, 0x6464U, 0x6564U, 0x6664U, 0x3065U, 0x3165U, 0x3265U, 0x3365U, 0x3465U, 0x3565U, 0x3665U, 0x3765U, 0x3865U, 0x3965U, 0x6165U, 0x6265U, 0x6365U, 0x6465U, 0x6565U, 0x6665U, 0x3066U, 0x3166U, 0x3266U, 0x3366U, 0x3466U, 0x3566U, 0x3666U, 0x3766U, 0x3866U, 0x3966U, 0x6166U, 0x6266U, 0x6366U, 0x6466U, 0x6566U, 0x6666U #endif /* DUK_USE_INTEGER_BE */ }; #endif /* DUK_USE_HEX_FASTPATH */ /* * Arbitrary byteswap for potentially unaligned values * * Used to byteswap pointers e.g. in debugger code. */ #if defined(DUK_USE_DEBUGGER_SUPPORT) /* For now only needed by the debugger. */ DUK_INTERNAL void duk_byteswap_bytes(duk_uint8_t *p, duk_small_uint_t len) { duk_uint8_t tmp; duk_uint8_t *q = p + len - 1; while (p - q < 0) { tmp = *p; *p = *q; *q = tmp; p++; q--; } } #endif /* * Random */ DUK_INTERNAL duk_double_t duk_util_get_random_double(duk_hthread *thr) { #if defined(DUK_USE_GET_RANDOM_DOUBLE) return DUK_USE_GET_RANDOM_DOUBLE(thr->heap->heap_udata); #else return duk_util_tinyrandom_get_double(thr); #endif } #line 1 "duk_hobject_class.c" /* * Hobject ECMAScript [[Class]]. */ /* #include duk_internal.h -> already included */ #if (DUK_STRIDX_UC_ARGUMENTS > 255) #error constant too large #endif #if (DUK_STRIDX_UC_ARRAY > 255) #error constant too large #endif #if (DUK_STRIDX_UC_BOOLEAN > 255) #error constant too large #endif #if (DUK_STRIDX_UC_DATE > 255) #error constant too large #endif #if (DUK_STRIDX_UC_ERROR > 255) #error constant too large #endif #if (DUK_STRIDX_UC_FUNCTION > 255) #error constant too large #endif #if (DUK_STRIDX_JSON > 255) #error constant too large #endif #if (DUK_STRIDX_MATH > 255) #error constant too large #endif #if (DUK_STRIDX_UC_NUMBER > 255) #error constant too large #endif #if (DUK_STRIDX_UC_OBJECT > 255) #error constant too large #endif #if (DUK_STRIDX_REG_EXP > 255) #error constant too large #endif #if (DUK_STRIDX_UC_STRING > 255) #error constant too large #endif #if (DUK_STRIDX_GLOBAL > 255) #error constant too large #endif #if (DUK_STRIDX_OBJ_ENV > 255) #error constant too large #endif #if (DUK_STRIDX_DEC_ENV > 255) #error constant too large #endif #if (DUK_STRIDX_UC_POINTER > 255) #error constant too large #endif #if (DUK_STRIDX_UC_THREAD > 255) #error constant too large #endif #if (DUK_STRIDX_ARRAY_BUFFER > 255) #error constant too large #endif #if (DUK_STRIDX_DATA_VIEW > 255) #error constant too large #endif #if (DUK_STRIDX_INT8_ARRAY > 255) #error constant too large #endif #if (DUK_STRIDX_UINT8_ARRAY > 255) #error constant too large #endif #if (DUK_STRIDX_UINT8_CLAMPED_ARRAY > 255) #error constant too large #endif #if (DUK_STRIDX_INT16_ARRAY > 255) #error constant too large #endif #if (DUK_STRIDX_UINT16_ARRAY > 255) #error constant too large #endif #if (DUK_STRIDX_INT32_ARRAY > 255) #error constant too large #endif #if (DUK_STRIDX_UINT32_ARRAY > 255) #error constant too large #endif #if (DUK_STRIDX_FLOAT32_ARRAY > 255) #error constant too large #endif #if (DUK_STRIDX_FLOAT64_ARRAY > 255) #error constant too large #endif #if (DUK_STRIDX_EMPTY_STRING > 255) #error constant too large #endif /* Note: assumes that these string indexes are 8-bit, genstrings.py must ensure that */ DUK_INTERNAL duk_uint8_t duk_class_number_to_stridx[32] = { DUK_STRIDX_EMPTY_STRING, /* NONE, intentionally empty */ DUK_STRIDX_UC_OBJECT, DUK_STRIDX_UC_ARRAY, DUK_STRIDX_UC_FUNCTION, DUK_STRIDX_UC_ARGUMENTS, DUK_STRIDX_UC_BOOLEAN, DUK_STRIDX_UC_DATE, DUK_STRIDX_UC_ERROR, DUK_STRIDX_JSON, DUK_STRIDX_MATH, DUK_STRIDX_UC_NUMBER, DUK_STRIDX_REG_EXP, DUK_STRIDX_UC_STRING, DUK_STRIDX_GLOBAL, DUK_STRIDX_UC_SYMBOL, DUK_STRIDX_OBJ_ENV, DUK_STRIDX_DEC_ENV, DUK_STRIDX_UC_POINTER, DUK_STRIDX_UC_THREAD, DUK_STRIDX_ARRAY_BUFFER, DUK_STRIDX_DATA_VIEW, DUK_STRIDX_INT8_ARRAY, DUK_STRIDX_UINT8_ARRAY, DUK_STRIDX_UINT8_CLAMPED_ARRAY, DUK_STRIDX_INT16_ARRAY, DUK_STRIDX_UINT16_ARRAY, DUK_STRIDX_INT32_ARRAY, DUK_STRIDX_UINT32_ARRAY, DUK_STRIDX_FLOAT32_ARRAY, DUK_STRIDX_FLOAT64_ARRAY, DUK_STRIDX_EMPTY_STRING, /* UNUSED, intentionally empty */ DUK_STRIDX_EMPTY_STRING, /* UNUSED, intentionally empty */ }; #line 1 "duk_alloc_default.c" /* * Default allocation functions. * * Assumes behavior such as malloc allowing zero size, yielding * a NULL or a unique pointer which is a no-op for free. */ /* #include duk_internal.h -> already included */ #if defined(DUK_USE_PROVIDE_DEFAULT_ALLOC_FUNCTIONS) DUK_INTERNAL void *duk_default_alloc_function(void *udata, duk_size_t size) { void *res; DUK_UNREF(udata); res = DUK_ANSI_MALLOC(size); DUK_DDD(DUK_DDDPRINT("default alloc function: %lu -> %p", (unsigned long) size, (void *) res)); return res; } DUK_INTERNAL void *duk_default_realloc_function(void *udata, void *ptr, duk_size_t newsize) { void *res; DUK_UNREF(udata); res = DUK_ANSI_REALLOC(ptr, newsize); DUK_DDD(DUK_DDDPRINT("default realloc function: %p %lu -> %p", (void *) ptr, (unsigned long) newsize, (void *) res)); return res; } DUK_INTERNAL void duk_default_free_function(void *udata, void *ptr) { DUK_DDD(DUK_DDDPRINT("default free function: %p", (void *) ptr)); DUK_UNREF(udata); DUK_ANSI_FREE(ptr); } #endif /* DUK_USE_PROVIDE_DEFAULT_ALLOC_FUNCTIONS */ #line 1 "duk_api_buffer.c" /* * Buffer */ /* #include duk_internal.h -> already included */ DUK_EXTERNAL void *duk_resize_buffer(duk_hthread *thr, duk_idx_t idx, duk_size_t new_size) { duk_hbuffer_dynamic *h; DUK_ASSERT_API_ENTRY(thr); h = (duk_hbuffer_dynamic *) duk_require_hbuffer(thr, idx); DUK_ASSERT(h != NULL); if (!(DUK_HBUFFER_HAS_DYNAMIC(h) && !DUK_HBUFFER_HAS_EXTERNAL(h))) { DUK_ERROR_TYPE(thr, DUK_STR_WRONG_BUFFER_TYPE); DUK_WO_NORETURN(return NULL;); } /* Maximum size check is handled by callee. */ duk_hbuffer_resize(thr, h, new_size); return DUK_HBUFFER_DYNAMIC_GET_DATA_PTR(thr->heap, h); } DUK_EXTERNAL void *duk_steal_buffer(duk_hthread *thr, duk_idx_t idx, duk_size_t *out_size) { duk_hbuffer_dynamic *h; void *ptr; duk_size_t sz; DUK_ASSERT_API_ENTRY(thr); h = (duk_hbuffer_dynamic *) duk_require_hbuffer(thr, idx); DUK_ASSERT(h != NULL); if (!(DUK_HBUFFER_HAS_DYNAMIC(h) && !DUK_HBUFFER_HAS_EXTERNAL(h))) { DUK_ERROR_TYPE(thr, DUK_STR_WRONG_BUFFER_TYPE); DUK_WO_NORETURN(return NULL;); } /* Forget the previous allocation, setting size to 0 and alloc to * NULL. Caller is responsible for freeing the previous allocation. * Getting the allocation and clearing it is done in the same API * call to avoid any chance of a realloc. */ ptr = DUK_HBUFFER_DYNAMIC_GET_DATA_PTR(thr->heap, h); sz = DUK_HBUFFER_DYNAMIC_GET_SIZE(h); if (out_size) { *out_size = sz; } DUK_HBUFFER_DYNAMIC_SET_DATA_PTR_NULL(thr->heap, h); DUK_HBUFFER_DYNAMIC_SET_SIZE(h, 0); return ptr; } DUK_EXTERNAL void duk_config_buffer(duk_hthread *thr, duk_idx_t idx, void *ptr, duk_size_t len) { duk_hbuffer_external *h; DUK_ASSERT_API_ENTRY(thr); h = (duk_hbuffer_external *) duk_require_hbuffer(thr, idx); DUK_ASSERT(h != NULL); if (!DUK_HBUFFER_HAS_EXTERNAL(h)) { DUK_ERROR_TYPE(thr, DUK_STR_WRONG_BUFFER_TYPE); DUK_WO_NORETURN(return;); } DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(h)); DUK_HBUFFER_EXTERNAL_SET_DATA_PTR(thr->heap, h, ptr); DUK_HBUFFER_EXTERNAL_SET_SIZE(h, len); } #line 1 "duk_api_bytecode.c" /* * Bytecode dump/load * * The bytecode load primitive is more important performance-wise than the * dump primitive. * * Unlike most Duktape API calls, bytecode dump/load is not guaranteed to be * memory safe for invalid arguments - caller beware! There's little point * in trying to achieve memory safety unless bytecode instructions are also * validated which is not easy to do with indirect register references etc. */ /* #include duk_internal.h -> already included */ #if defined(DUK_USE_BYTECODE_DUMP_SUPPORT) #define DUK__SER_MARKER 0xbf #define DUK__SER_STRING 0x00 #define DUK__SER_NUMBER 0x01 #define DUK__BYTECODE_INITIAL_ALLOC 256 #define DUK__NO_FORMALS 0xffffffffUL /* * Dump/load helpers, xxx_raw() helpers do no buffer checks */ DUK_LOCAL const duk_uint8_t *duk__load_string_raw(duk_hthread *thr, const duk_uint8_t *p) { duk_uint32_t len; len = DUK_RAW_READINC_U32_BE(p); duk_push_lstring(thr, (const char *) p, len); p += len; return p; } DUK_LOCAL const duk_uint8_t *duk__load_buffer_raw(duk_hthread *thr, const duk_uint8_t *p) { duk_uint32_t len; duk_uint8_t *buf; len = DUK_RAW_READINC_U32_BE(p); buf = (duk_uint8_t *) duk_push_fixed_buffer_nozero(thr, (duk_size_t) len); DUK_ASSERT(buf != NULL); duk_memcpy((void *) buf, (const void *) p, (size_t) len); p += len; return p; } DUK_LOCAL duk_uint8_t *duk__dump_hstring_raw(duk_uint8_t *p, duk_hstring *h) { duk_size_t len; duk_uint32_t tmp32; DUK_ASSERT(h != NULL); len = DUK_HSTRING_GET_BYTELEN(h); DUK_ASSERT(len <= 0xffffffffUL); /* string limits */ tmp32 = (duk_uint32_t) len; DUK_RAW_WRITEINC_U32_BE(p, tmp32); duk_memcpy((void *) p, (const void *) DUK_HSTRING_GET_DATA(h), len); p += len; return p; } DUK_LOCAL duk_uint8_t *duk__dump_hbuffer_raw(duk_hthread *thr, duk_uint8_t *p, duk_hbuffer *h) { duk_size_t len; duk_uint32_t tmp32; DUK_ASSERT(thr != NULL); DUK_ASSERT(h != NULL); DUK_UNREF(thr); len = DUK_HBUFFER_GET_SIZE(h); DUK_ASSERT(len <= 0xffffffffUL); /* buffer limits */ tmp32 = (duk_uint32_t) len; DUK_RAW_WRITEINC_U32_BE(p, tmp32); /* When len == 0, buffer data pointer may be NULL. */ duk_memcpy_unsafe((void *) p, (const void *) DUK_HBUFFER_GET_DATA_PTR(thr->heap, h), len); p += len; return p; } DUK_LOCAL duk_uint8_t *duk__dump_string_prop(duk_hthread *thr, duk_uint8_t *p, duk_bufwriter_ctx *bw_ctx, duk_hobject *func, duk_small_uint_t stridx) { duk_hstring *h_str; duk_tval *tv; tv = duk_hobject_find_entry_tval_ptr_stridx(thr->heap, (duk_hobject *) func, stridx); if (tv != NULL && DUK_TVAL_IS_STRING(tv)) { h_str = DUK_TVAL_GET_STRING(tv); DUK_ASSERT(h_str != NULL); } else { h_str = DUK_HTHREAD_STRING_EMPTY_STRING(thr); DUK_ASSERT(h_str != NULL); } DUK_ASSERT(DUK_HSTRING_MAX_BYTELEN <= 0x7fffffffUL); /* ensures no overflow */ p = DUK_BW_ENSURE_RAW(thr, bw_ctx, 4U + DUK_HSTRING_GET_BYTELEN(h_str), p); p = duk__dump_hstring_raw(p, h_str); return p; } DUK_LOCAL duk_uint8_t *duk__dump_buffer_prop(duk_hthread *thr, duk_uint8_t *p, duk_bufwriter_ctx *bw_ctx, duk_hobject *func, duk_small_uint_t stridx) { duk_tval *tv; tv = duk_hobject_find_entry_tval_ptr_stridx(thr->heap, (duk_hobject *) func, stridx); if (tv != NULL && DUK_TVAL_IS_BUFFER(tv)) { duk_hbuffer *h_buf; h_buf = DUK_TVAL_GET_BUFFER(tv); DUK_ASSERT(h_buf != NULL); DUK_ASSERT(DUK_HBUFFER_MAX_BYTELEN <= 0x7fffffffUL); /* ensures no overflow */ p = DUK_BW_ENSURE_RAW(thr, bw_ctx, 4U + DUK_HBUFFER_GET_SIZE(h_buf), p); p = duk__dump_hbuffer_raw(thr, p, h_buf); } else { p = DUK_BW_ENSURE_RAW(thr, bw_ctx, 4U, p); DUK_RAW_WRITEINC_U32_BE(p, 0); } return p; } DUK_LOCAL duk_uint8_t *duk__dump_uint32_prop(duk_hthread *thr, duk_uint8_t *p, duk_bufwriter_ctx *bw_ctx, duk_hobject *func, duk_small_uint_t stridx, duk_uint32_t def_value) { duk_tval *tv; duk_uint32_t val; tv = duk_hobject_find_entry_tval_ptr_stridx(thr->heap, (duk_hobject *) func, stridx); if (tv != NULL && DUK_TVAL_IS_NUMBER(tv)) { val = (duk_uint32_t) DUK_TVAL_GET_NUMBER(tv); } else { val = def_value; } p = DUK_BW_ENSURE_RAW(thr, bw_ctx, 4U, p); DUK_RAW_WRITEINC_U32_BE(p, val); return p; } DUK_LOCAL duk_uint8_t *duk__dump_varmap(duk_hthread *thr, duk_uint8_t *p, duk_bufwriter_ctx *bw_ctx, duk_hobject *func) { duk_hobject *h; h = duk_hobject_get_varmap(thr, (duk_hobject *) func); if (h != NULL) { duk_uint_fast32_t i; /* We know _Varmap only has own properties so walk property * table directly. We also know _Varmap is dense and all * values are numbers; assert for these. GC and finalizers * shouldn't affect _Varmap so side effects should be fine. */ for (i = 0; i < (duk_uint_fast32_t) DUK_HOBJECT_GET_ENEXT(h); i++) { duk_hstring *key; duk_tval *tv_val; duk_uint32_t val; key = DUK_HOBJECT_E_GET_KEY(thr->heap, h, i); DUK_ASSERT(key != NULL); /* _Varmap is dense */ DUK_ASSERT(!DUK_HOBJECT_E_SLOT_IS_ACCESSOR(thr->heap, h, i)); tv_val = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(thr->heap, h, i); DUK_ASSERT(tv_val != NULL); DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv_val)); /* known to be number; in fact an integer */ #if defined(DUK_USE_FASTINT) DUK_ASSERT(DUK_TVAL_IS_FASTINT(tv_val)); DUK_ASSERT(DUK_TVAL_GET_FASTINT(tv_val) == (duk_int64_t) DUK_TVAL_GET_FASTINT_U32(tv_val)); /* known to be 32-bit */ val = DUK_TVAL_GET_FASTINT_U32(tv_val); #else val = (duk_uint32_t) DUK_TVAL_GET_NUMBER(tv_val); #endif DUK_ASSERT(DUK_HSTRING_MAX_BYTELEN <= 0x7fffffffUL); /* ensures no overflow */ p = DUK_BW_ENSURE_RAW(thr, bw_ctx, 4U + DUK_HSTRING_GET_BYTELEN(key) + 4U, p); p = duk__dump_hstring_raw(p, key); DUK_RAW_WRITEINC_U32_BE(p, val); } } p = DUK_BW_ENSURE_RAW(thr, bw_ctx, 4U, p); DUK_RAW_WRITEINC_U32_BE(p, 0); /* end of _Varmap */ return p; } DUK_LOCAL duk_uint8_t *duk__dump_formals(duk_hthread *thr, duk_uint8_t *p, duk_bufwriter_ctx *bw_ctx, duk_hobject *func) { duk_harray *h; h = duk_hobject_get_formals(thr, (duk_hobject *) func); if (h != NULL) { duk_uint32_t i; /* Here we rely on _Formals being a dense array containing * strings. This should be the case unless _Formals has been * tweaked by the application (which we don't support right * now). */ p = DUK_BW_ENSURE_RAW(thr, bw_ctx, 4U, p); DUK_ASSERT(h->length != DUK__NO_FORMALS); /* limits */ DUK_RAW_WRITEINC_U32_BE(p, h->length); for (i = 0; i < h->length; i++) { duk_tval *tv_val; duk_hstring *varname; tv_val = DUK_HOBJECT_A_GET_VALUE_PTR(thr->heap, (duk_hobject *) h, i); DUK_ASSERT(tv_val != NULL); DUK_ASSERT(DUK_TVAL_IS_STRING(tv_val)); varname = DUK_TVAL_GET_STRING(tv_val); DUK_ASSERT(varname != NULL); DUK_ASSERT(DUK_HSTRING_GET_BYTELEN(varname) >= 1); DUK_ASSERT(DUK_HSTRING_MAX_BYTELEN <= 0x7fffffffUL); /* ensures no overflow */ p = DUK_BW_ENSURE_RAW(thr, bw_ctx, 4U + DUK_HSTRING_GET_BYTELEN(varname), p); p = duk__dump_hstring_raw(p, varname); } } else { DUK_DD(DUK_DDPRINT("dumping function without _Formals, emit marker to indicate missing _Formals")); p = DUK_BW_ENSURE_RAW(thr, bw_ctx, 4U, p); DUK_RAW_WRITEINC_U32_BE(p, DUK__NO_FORMALS); /* marker: no formals */ } return p; } static duk_uint8_t *duk__dump_func(duk_hthread *thr, duk_hcompfunc *func, duk_bufwriter_ctx *bw_ctx, duk_uint8_t *p) { duk_tval *tv, *tv_end; duk_instr_t *ins, *ins_end; duk_hobject **fn, **fn_end; duk_hstring *h_str; duk_uint32_t count_instr; duk_uint32_t tmp32; duk_uint16_t tmp16; duk_double_t d; DUK_DD(DUK_DDPRINT("dumping function %p to %p: " "consts=[%p,%p[ (%ld bytes, %ld items), " "funcs=[%p,%p[ (%ld bytes, %ld items), " "code=[%p,%p[ (%ld bytes, %ld items)", (void *) func, (void *) p, (void *) DUK_HCOMPFUNC_GET_CONSTS_BASE(thr->heap, func), (void *) DUK_HCOMPFUNC_GET_CONSTS_END(thr->heap, func), (long) DUK_HCOMPFUNC_GET_CONSTS_SIZE(thr->heap, func), (long) DUK_HCOMPFUNC_GET_CONSTS_COUNT(thr->heap, func), (void *) DUK_HCOMPFUNC_GET_FUNCS_BASE(thr->heap, func), (void *) DUK_HCOMPFUNC_GET_FUNCS_END(thr->heap, func), (long) DUK_HCOMPFUNC_GET_FUNCS_SIZE(thr->heap, func), (long) DUK_HCOMPFUNC_GET_FUNCS_COUNT(thr->heap, func), (void *) DUK_HCOMPFUNC_GET_CODE_BASE(thr->heap, func), (void *) DUK_HCOMPFUNC_GET_CODE_END(thr->heap, func), (long) DUK_HCOMPFUNC_GET_CODE_SIZE(thr->heap, func), (long) DUK_HCOMPFUNC_GET_CODE_COUNT(thr->heap, func))); DUK_ASSERT(DUK_USE_ESBC_MAX_BYTES <= 0x7fffffffUL); /* ensures no overflow */ count_instr = (duk_uint32_t) DUK_HCOMPFUNC_GET_CODE_COUNT(thr->heap, func); p = DUK_BW_ENSURE_RAW(thr, bw_ctx, 3U * 4U + 2U * 2U + 3U * 4U + count_instr * 4U, p); /* Fixed header info. */ tmp32 = count_instr; DUK_RAW_WRITEINC_U32_BE(p, tmp32); tmp32 = (duk_uint32_t) DUK_HCOMPFUNC_GET_CONSTS_COUNT(thr->heap, func); DUK_RAW_WRITEINC_U32_BE(p, tmp32); tmp32 = (duk_uint32_t) DUK_HCOMPFUNC_GET_FUNCS_COUNT(thr->heap, func); DUK_RAW_WRITEINC_U32_BE(p, tmp32); tmp16 = func->nregs; DUK_RAW_WRITEINC_U16_BE(p, tmp16); tmp16 = func->nargs; DUK_RAW_WRITEINC_U16_BE(p, tmp16); #if defined(DUK_USE_DEBUGGER_SUPPORT) tmp32 = func->start_line; DUK_RAW_WRITEINC_U32_BE(p, tmp32); tmp32 = func->end_line; DUK_RAW_WRITEINC_U32_BE(p, tmp32); #else DUK_RAW_WRITEINC_U32_BE(p, 0); DUK_RAW_WRITEINC_U32_BE(p, 0); #endif tmp32 = DUK_HEAPHDR_GET_FLAGS((duk_heaphdr *) func); /* masks flags, only duk_hobject flags */ tmp32 &= ~(DUK_HOBJECT_FLAG_HAVE_FINALIZER); /* finalizer flag is lost */ DUK_RAW_WRITEINC_U32_BE(p, tmp32); /* Bytecode instructions: endian conversion needed unless * platform is big endian. */ ins = DUK_HCOMPFUNC_GET_CODE_BASE(thr->heap, func); ins_end = DUK_HCOMPFUNC_GET_CODE_END(thr->heap, func); DUK_ASSERT((duk_size_t) (ins_end - ins) == (duk_size_t) count_instr); #if defined(DUK_USE_INTEGER_BE) duk_memcpy_unsafe((void *) p, (const void *) ins, count_instr * sizeof(duk_instr_t)); p += count_instr * sizeof(duk_instr_t); DUK_UNREF(ins_end); #else while (ins != ins_end) { tmp32 = (duk_uint32_t) (*ins); DUK_RAW_WRITEINC_U32_BE(p, tmp32); ins++; } #endif /* Constants: variable size encoding. */ tv = DUK_HCOMPFUNC_GET_CONSTS_BASE(thr->heap, func); tv_end = DUK_HCOMPFUNC_GET_CONSTS_END(thr->heap, func); while (tv != tv_end) { /* constants are strings or numbers now */ DUK_ASSERT(DUK_TVAL_IS_STRING(tv) || DUK_TVAL_IS_NUMBER(tv)); if (DUK_TVAL_IS_STRING(tv)) { h_str = DUK_TVAL_GET_STRING(tv); DUK_ASSERT(h_str != NULL); DUK_ASSERT(DUK_HSTRING_MAX_BYTELEN <= 0x7fffffffUL); /* ensures no overflow */ p = DUK_BW_ENSURE_RAW(thr, bw_ctx, 1U + 4U + DUK_HSTRING_GET_BYTELEN(h_str), p); *p++ = DUK__SER_STRING; p = duk__dump_hstring_raw(p, h_str); } else { DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv)); p = DUK_BW_ENSURE_RAW(thr, bw_ctx, 1U + 8U, p); *p++ = DUK__SER_NUMBER; d = DUK_TVAL_GET_NUMBER(tv); DUK_RAW_WRITEINC_DOUBLE_BE(p, d); } tv++; } /* Inner functions recursively. */ fn = (duk_hobject **) DUK_HCOMPFUNC_GET_FUNCS_BASE(thr->heap, func); fn_end = (duk_hobject **) DUK_HCOMPFUNC_GET_FUNCS_END(thr->heap, func); while (fn != fn_end) { /* XXX: This causes recursion up to inner function depth * which is normally not an issue, e.g. mark-and-sweep uses * a recursion limiter to avoid C stack issues. Avoiding * this would mean some sort of a work list or just refusing * to serialize deep functions. */ DUK_ASSERT(DUK_HOBJECT_IS_COMPFUNC(*fn)); p = duk__dump_func(thr, (duk_hcompfunc *) *fn, bw_ctx, p); fn++; } /* Lexenv and varenv are not dumped. */ /* Object extra properties. * * There are some difference between function templates and functions. * For example, function templates don't have .length and nargs is * normally used to instantiate the functions. */ p = duk__dump_uint32_prop(thr, p, bw_ctx, (duk_hobject *) func, DUK_STRIDX_LENGTH, (duk_uint32_t) func->nargs); #if defined(DUK_USE_FUNC_NAME_PROPERTY) p = duk__dump_string_prop(thr, p, bw_ctx, (duk_hobject *) func, DUK_STRIDX_NAME); #endif #if defined(DUK_USE_FUNC_FILENAME_PROPERTY) p = duk__dump_string_prop(thr, p, bw_ctx, (duk_hobject *) func, DUK_STRIDX_FILE_NAME); #endif #if defined(DUK_USE_PC2LINE) p = duk__dump_buffer_prop(thr, p, bw_ctx, (duk_hobject *) func, DUK_STRIDX_INT_PC2LINE); #endif p = duk__dump_varmap(thr, p, bw_ctx, (duk_hobject *) func); p = duk__dump_formals(thr, p, bw_ctx, (duk_hobject *) func); DUK_DD(DUK_DDPRINT("serialized function %p -> final pointer %p", (void *) func, (void *) p)); return p; } /* Load a function from bytecode. The function object returned here must * match what is created by duk_js_push_closure() with respect to its flags, * properties, etc. * * NOTE: there are intentionally no input buffer length / bound checks. * Adding them would be easy but wouldn't ensure memory safety as untrusted * or broken bytecode is unsafe during execution unless the opcodes themselves * are validated (which is quite complex, especially for indirect opcodes). */ #define DUK__ASSERT_LEFT(n) \ do { \ DUK_ASSERT((duk_size_t) (p_end - p) >= (duk_size_t) (n)); \ } while (0) static const duk_uint8_t *duk__load_func(duk_hthread *thr, const duk_uint8_t *p, const duk_uint8_t *p_end) { duk_hcompfunc *h_fun; duk_hbuffer *h_data; duk_size_t data_size; duk_uint32_t count_instr, count_const, count_funcs; duk_uint32_t n; duk_uint32_t tmp32; duk_small_uint_t const_type; duk_uint8_t *fun_data; duk_uint8_t *q; duk_idx_t idx_base; duk_tval *tv1; duk_uarridx_t arr_idx; duk_uarridx_t arr_limit; duk_hobject *func_env; duk_bool_t need_pop; /* XXX: There's some overlap with duk_js_closure() here, but * seems difficult to share code. Ensure that the final function * looks the same as created by duk_js_closure(). */ DUK_ASSERT(thr != NULL); DUK_DD(DUK_DDPRINT("loading function, p=%p, p_end=%p", (const void *) p, (const void *) p_end)); DUK__ASSERT_LEFT(3 * 4); count_instr = DUK_RAW_READINC_U32_BE(p); count_const = DUK_RAW_READINC_U32_BE(p); count_funcs = DUK_RAW_READINC_U32_BE(p); data_size = sizeof(duk_tval) * count_const + sizeof(duk_hobject *) * count_funcs + sizeof(duk_instr_t) * count_instr; DUK_DD(DUK_DDPRINT("instr=%ld, const=%ld, funcs=%ld, data_size=%ld", (long) count_instr, (long) count_const, (long) count_const, (long) data_size)); /* Value stack is used to ensure reachability of constants and * inner functions being loaded. Require enough space to handle * large functions correctly. */ duk_require_stack(thr, (duk_idx_t) (2 + count_const + count_funcs)); idx_base = duk_get_top(thr); /* Push function object, init flags etc. This must match * duk_js_push_closure() quite carefully. */ h_fun = duk_push_hcompfunc(thr); DUK_ASSERT(h_fun != NULL); DUK_ASSERT(DUK_HOBJECT_IS_COMPFUNC((duk_hobject *) h_fun)); DUK_ASSERT(DUK_HCOMPFUNC_GET_DATA(thr->heap, h_fun) == NULL); DUK_ASSERT(DUK_HCOMPFUNC_GET_FUNCS(thr->heap, h_fun) == NULL); DUK_ASSERT(DUK_HCOMPFUNC_GET_BYTECODE(thr->heap, h_fun) == NULL); DUK_ASSERT(DUK_HOBJECT_GET_PROTOTYPE(thr->heap, (duk_hobject *) h_fun) == thr->builtins[DUK_BIDX_FUNCTION_PROTOTYPE]); h_fun->nregs = DUK_RAW_READINC_U16_BE(p); h_fun->nargs = DUK_RAW_READINC_U16_BE(p); #if defined(DUK_USE_DEBUGGER_SUPPORT) h_fun->start_line = DUK_RAW_READINC_U32_BE(p); h_fun->end_line = DUK_RAW_READINC_U32_BE(p); #else p += 8; /* skip line info */ #endif /* duk_hcompfunc flags; quite version specific */ tmp32 = DUK_RAW_READINC_U32_BE(p); DUK_HEAPHDR_SET_FLAGS((duk_heaphdr *) h_fun, tmp32); /* masks flags to only change duk_hobject flags */ /* standard prototype (no need to set here, already set) */ DUK_ASSERT(DUK_HOBJECT_GET_PROTOTYPE(thr->heap, (duk_hobject *) h_fun) == thr->builtins[DUK_BIDX_FUNCTION_PROTOTYPE]); #if 0 DUK_HOBJECT_SET_PROTOTYPE_UPDREF(thr, &h_fun->obj, thr->builtins[DUK_BIDX_FUNCTION_PROTOTYPE]); #endif /* assert just a few critical flags */ DUK_ASSERT(DUK_HEAPHDR_GET_TYPE((duk_heaphdr *) h_fun) == DUK_HTYPE_OBJECT); DUK_ASSERT(!DUK_HOBJECT_HAS_BOUNDFUNC(&h_fun->obj)); DUK_ASSERT(DUK_HOBJECT_HAS_COMPFUNC(&h_fun->obj)); DUK_ASSERT(!DUK_HOBJECT_HAS_NATFUNC(&h_fun->obj)); DUK_ASSERT(!DUK_HOBJECT_IS_THREAD(&h_fun->obj)); DUK_ASSERT(!DUK_HOBJECT_IS_PROXY(&h_fun->obj)); DUK_ASSERT(!DUK_HOBJECT_HAS_EXOTIC_ARRAY(&h_fun->obj)); DUK_ASSERT(!DUK_HOBJECT_HAS_EXOTIC_STRINGOBJ(&h_fun->obj)); DUK_ASSERT(!DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(&h_fun->obj)); /* Create function 'data' buffer but don't attach it yet. */ fun_data = (duk_uint8_t *) duk_push_fixed_buffer_nozero(thr, data_size); DUK_ASSERT(fun_data != NULL); /* Load bytecode instructions. */ DUK_ASSERT(sizeof(duk_instr_t) == 4); DUK__ASSERT_LEFT(count_instr * sizeof(duk_instr_t)); #if defined(DUK_USE_INTEGER_BE) q = fun_data + sizeof(duk_tval) * count_const + sizeof(duk_hobject *) * count_funcs; duk_memcpy((void *) q, (const void *) p, sizeof(duk_instr_t) * count_instr); p += sizeof(duk_instr_t) * count_instr; #else q = fun_data + sizeof(duk_tval) * count_const + sizeof(duk_hobject *) * count_funcs; for (n = count_instr; n > 0; n--) { *((duk_instr_t *) (void *) q) = DUK_RAW_READINC_U32_BE(p); q += sizeof(duk_instr_t); } #endif /* Load constants onto value stack but don't yet copy to buffer. */ for (n = count_const; n > 0; n--) { DUK__ASSERT_LEFT(1); const_type = DUK_RAW_READINC_U8(p); switch (const_type) { case DUK__SER_STRING: { p = duk__load_string_raw(thr, p); break; } case DUK__SER_NUMBER: { /* Important to do a fastint check so that constants are * properly read back as fastints. */ duk_tval tv_tmp; duk_double_t val; DUK__ASSERT_LEFT(8); val = DUK_RAW_READINC_DOUBLE_BE(p); DUK_TVAL_SET_NUMBER_CHKFAST_SLOW(&tv_tmp, val); duk_push_tval(thr, &tv_tmp); break; } default: { goto format_error; } } } /* Load inner functions to value stack, but don't yet copy to buffer. */ for (n = count_funcs; n > 0; n--) { p = duk__load_func(thr, p, p_end); if (p == NULL) { goto format_error; } } /* With constants and inner functions on value stack, we can now * atomically finish the function 'data' buffer, bump refcounts, * etc. * * Here we take advantage of the value stack being just a duk_tval * array: we can just memcpy() the constants as long as we incref * them afterwards. */ h_data = (duk_hbuffer *) duk_known_hbuffer(thr, idx_base + 1); DUK_ASSERT(!DUK_HBUFFER_HAS_DYNAMIC(h_data)); DUK_HCOMPFUNC_SET_DATA(thr->heap, h_fun, h_data); DUK_HBUFFER_INCREF(thr, h_data); tv1 = duk_get_tval(thr, idx_base + 2); /* may be NULL if no constants or inner funcs */ DUK_ASSERT((count_const == 0 && count_funcs == 0) || tv1 != NULL); q = fun_data; duk_memcpy_unsafe((void *) q, (const void *) tv1, sizeof(duk_tval) * count_const); for (n = count_const; n > 0; n--) { DUK_TVAL_INCREF_FAST(thr, (duk_tval *) (void *) q); /* no side effects */ q += sizeof(duk_tval); } tv1 += count_const; DUK_HCOMPFUNC_SET_FUNCS(thr->heap, h_fun, (duk_hobject **) (void *) q); for (n = count_funcs; n > 0; n--) { duk_hobject *h_obj; DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv1)); h_obj = DUK_TVAL_GET_OBJECT(tv1); DUK_ASSERT(h_obj != NULL); tv1++; DUK_HOBJECT_INCREF(thr, h_obj); *((duk_hobject **) (void *) q) = h_obj; q += sizeof(duk_hobject *); } DUK_HCOMPFUNC_SET_BYTECODE(thr->heap, h_fun, (duk_instr_t *) (void *) q); /* The function object is now reachable and refcounts are fine, * so we can pop off all the temporaries. */ DUK_DDD(DUK_DDDPRINT("function is reachable, reset top; func: %!iT", duk_get_tval(thr, idx_base))); duk_set_top(thr, idx_base + 1); /* Setup function properties. */ tmp32 = DUK_RAW_READINC_U32_BE(p); duk_push_u32(thr, tmp32); duk_xdef_prop_stridx_short(thr, -2, DUK_STRIDX_LENGTH, DUK_PROPDESC_FLAGS_C); #if defined(DUK_USE_FUNC_NAME_PROPERTY) p = duk__load_string_raw(thr, p); /* -> [ func funcname ] */ func_env = thr->builtins[DUK_BIDX_GLOBAL_ENV]; DUK_ASSERT(func_env != NULL); need_pop = 0; if (DUK_HOBJECT_HAS_NAMEBINDING((duk_hobject *) h_fun)) { /* Original function instance/template had NAMEBINDING. * Must create a lexical environment on loading to allow * recursive functions like 'function foo() { foo(); }'. */ duk_hdecenv *new_env; new_env = duk_hdecenv_alloc(thr, DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_DECENV)); DUK_ASSERT(new_env != NULL); DUK_ASSERT(new_env->thread == NULL); /* Closed. */ DUK_ASSERT(new_env->varmap == NULL); DUK_ASSERT(new_env->regbase_byteoff == 0); DUK_HDECENV_ASSERT_VALID(new_env); DUK_ASSERT(DUK_HOBJECT_GET_PROTOTYPE(thr->heap, (duk_hobject *) new_env) == NULL); DUK_HOBJECT_SET_PROTOTYPE(thr->heap, (duk_hobject *) new_env, func_env); DUK_HOBJECT_INCREF(thr, func_env); func_env = (duk_hobject *) new_env; duk_push_hobject(thr, (duk_hobject *) new_env); duk_dup_m2(thr); /* -> [ func funcname env funcname ] */ duk_dup(thr, idx_base); /* -> [ func funcname env funcname func ] */ duk_xdef_prop(thr, -3, DUK_PROPDESC_FLAGS_NONE); /* -> [ func funcname env ] */ need_pop = 1; /* Need to pop env, but -after- updating h_fun and increfs. */ } DUK_ASSERT(func_env != NULL); DUK_HCOMPFUNC_SET_LEXENV(thr->heap, h_fun, func_env); DUK_HCOMPFUNC_SET_VARENV(thr->heap, h_fun, func_env); DUK_HOBJECT_INCREF(thr, func_env); DUK_HOBJECT_INCREF(thr, func_env); if (need_pop) { duk_pop(thr); } duk_xdef_prop_stridx_short(thr, -2, DUK_STRIDX_NAME, DUK_PROPDESC_FLAGS_C); #endif /* DUK_USE_FUNC_NAME_PROPERTY */ #if defined(DUK_USE_FUNC_FILENAME_PROPERTY) p = duk__load_string_raw(thr, p); duk_xdef_prop_stridx_short(thr, -2, DUK_STRIDX_FILE_NAME, DUK_PROPDESC_FLAGS_C); #endif /* DUK_USE_FUNC_FILENAME_PROPERTY */ if (DUK_HOBJECT_HAS_CONSTRUCTABLE((duk_hobject *) h_fun)) { /* Restore empty external .prototype only for constructable * functions. The prototype object should inherit from * Object.prototype. */ duk_push_object(thr); DUK_ASSERT(!duk_is_bare_object(thr, -1)); duk_dup_m2(thr); duk_xdef_prop_stridx_short(thr, -2, DUK_STRIDX_CONSTRUCTOR, DUK_PROPDESC_FLAGS_WC); /* func.prototype.constructor = func */ duk_compact_m1(thr); duk_xdef_prop_stridx_short(thr, -2, DUK_STRIDX_PROTOTYPE, DUK_PROPDESC_FLAGS_W); } #if defined(DUK_USE_PC2LINE) p = duk__load_buffer_raw(thr, p); duk_xdef_prop_stridx_short(thr, -2, DUK_STRIDX_INT_PC2LINE, DUK_PROPDESC_FLAGS_WC); #endif /* DUK_USE_PC2LINE */ duk_push_bare_object(thr); /* _Varmap */ for (;;) { /* XXX: awkward */ p = duk__load_string_raw(thr, p); if (duk_get_length(thr, -1) == 0) { duk_pop(thr); break; } tmp32 = DUK_RAW_READINC_U32_BE(p); duk_push_u32(thr, tmp32); duk_put_prop(thr, -3); } duk_compact_m1(thr); duk_xdef_prop_stridx_short(thr, -2, DUK_STRIDX_INT_VARMAP, DUK_PROPDESC_FLAGS_NONE); /* _Formals may have been missing in the original function, which is * handled using a marker length. */ arr_limit = DUK_RAW_READINC_U32_BE(p); if (arr_limit != DUK__NO_FORMALS) { duk_push_bare_array(thr); /* _Formals */ for (arr_idx = 0; arr_idx < arr_limit; arr_idx++) { p = duk__load_string_raw(thr, p); duk_put_prop_index(thr, -2, arr_idx); } duk_compact_m1(thr); duk_xdef_prop_stridx_short(thr, -2, DUK_STRIDX_INT_FORMALS, DUK_PROPDESC_FLAGS_NONE); } else { DUK_DD(DUK_DDPRINT("no _Formals in dumped function")); } /* Return with final function pushed on stack top. */ DUK_DD(DUK_DDPRINT("final loaded function: %!iT", duk_get_tval(thr, -1))); DUK_ASSERT_TOP(thr, idx_base + 1); return p; format_error: return NULL; } DUK_EXTERNAL void duk_dump_function(duk_hthread *thr) { duk_hcompfunc *func; duk_bufwriter_ctx bw_ctx_alloc; duk_bufwriter_ctx *bw_ctx = &bw_ctx_alloc; duk_uint8_t *p; DUK_ASSERT_API_ENTRY(thr); /* Bound functions don't have all properties so we'd either need to * lookup the non-bound target function or reject bound functions. * For now, bound functions are rejected with TypeError. */ func = duk_require_hcompfunc(thr, -1); DUK_ASSERT(func != NULL); DUK_ASSERT(!DUK_HOBJECT_HAS_BOUNDFUNC(&func->obj)); /* Estimating the result size beforehand would be costly, so * start with a reasonable size and extend as needed. */ DUK_BW_INIT_PUSHBUF(thr, bw_ctx, DUK__BYTECODE_INITIAL_ALLOC); p = DUK_BW_GET_PTR(thr, bw_ctx); *p++ = DUK__SER_MARKER; p = duk__dump_func(thr, func, bw_ctx, p); DUK_BW_SET_PTR(thr, bw_ctx, p); DUK_BW_COMPACT(thr, bw_ctx); DUK_DD(DUK_DDPRINT("serialized result: %!T", duk_get_tval(thr, -1))); duk_remove_m2(thr); /* [ ... func buf ] -> [ ... buf ] */ } DUK_EXTERNAL void duk_load_function(duk_hthread *thr) { const duk_uint8_t *p_buf, *p, *p_end; duk_size_t sz; DUK_ASSERT_API_ENTRY(thr); p_buf = (duk_uint8_t *) duk_require_buffer(thr, -1, &sz); DUK_ASSERT(p_buf != NULL); /* The caller is responsible for being sure that bytecode being loaded * is valid and trusted. Invalid bytecode can cause memory unsafe * behavior directly during loading or later during bytecode execution * (instruction validation would be quite complex to implement). * * This signature check is the only sanity check for detecting * accidental invalid inputs. The initial byte ensures no ordinary * string or Symbol will be accepted by accident. */ p = p_buf; p_end = p_buf + sz; if (sz < 1 || p[0] != DUK__SER_MARKER) { goto format_error; } p++; p = duk__load_func(thr, p, p_end); if (p == NULL) { goto format_error; } duk_remove_m2(thr); /* [ ... buf func ] -> [ ... func ] */ return; format_error: DUK_ERROR_TYPE(thr, DUK_STR_INVALID_BYTECODE); DUK_WO_NORETURN(return;); } #else /* DUK_USE_BYTECODE_DUMP_SUPPORT */ DUK_EXTERNAL void duk_dump_function(duk_hthread *thr) { DUK_ASSERT_API_ENTRY(thr); DUK_ERROR_UNSUPPORTED(thr); DUK_WO_NORETURN(return;); } DUK_EXTERNAL void duk_load_function(duk_hthread *thr) { DUK_ASSERT_API_ENTRY(thr); DUK_ERROR_UNSUPPORTED(thr); DUK_WO_NORETURN(return;); } #endif /* DUK_USE_BYTECODE_DUMP_SUPPORT */ /* automatic undefs */ #undef DUK__ASSERT_LEFT #undef DUK__BYTECODE_INITIAL_ALLOC #undef DUK__NO_FORMALS #undef DUK__SER_MARKER #undef DUK__SER_NUMBER #undef DUK__SER_STRING #line 1 "duk_api_call.c" /* * Calls. * * Protected variants should avoid ever throwing an error. Must be careful * to catch errors related to value stack manipulation and property lookup, * not just the call itself. * * The only exception is when arguments are insane, e.g. nargs/nrets are out * of bounds; in such cases an error is thrown for two reasons. First, we * can't always respect the value stack input/output guarantees in such cases * so the caller would end up with the value stack in an unexpected state. * Second, an attempt to create an error might itself fail (although this * could be avoided by pushing a preallocated object/string or a primitive * value). */ /* #include duk_internal.h -> already included */ /* * Helpers */ struct duk__pcall_prop_args { duk_idx_t obj_idx; duk_idx_t nargs; duk_small_uint_t call_flags; }; typedef struct duk__pcall_prop_args duk__pcall_prop_args; struct duk__pcall_method_args { duk_idx_t nargs; duk_small_uint_t call_flags; }; typedef struct duk__pcall_method_args duk__pcall_method_args; struct duk__pcall_args { duk_idx_t nargs; duk_small_uint_t call_flags; }; typedef struct duk__pcall_args duk__pcall_args; /* Compute and validate idx_func for a certain 'nargs' and 'other' * parameter count (1 or 2, depending on whether 'this' binding is * present). */ DUK_LOCAL duk_idx_t duk__call_get_idx_func(duk_hthread *thr, duk_idx_t nargs, duk_idx_t other) { duk_idx_t idx_func; /* XXX: byte arithmetic? */ DUK_ASSERT(other >= 0); idx_func = duk_get_top(thr) - nargs - other; if (DUK_UNLIKELY((idx_func | nargs) < 0)) { /* idx_func < 0 || nargs < 0; OR sign bits */ DUK_ERROR_TYPE_INVALID_ARGS(thr); DUK_WO_NORETURN(return 0;); } DUK_ASSERT(duk_is_valid_index(thr, idx_func)); return idx_func; } /* Compute idx_func, assume index will be valid. This is a valid assumption * for protected calls: nargs < 0 is checked explicitly and duk_safe_call() * validates the argument count. */ DUK_LOCAL duk_idx_t duk__call_get_idx_func_unvalidated(duk_hthread *thr, duk_idx_t nargs, duk_idx_t other) { duk_idx_t idx_func; /* XXX: byte arithmetic? */ DUK_ASSERT(nargs >= 0); DUK_ASSERT(other >= 0); idx_func = duk_get_top(thr) - nargs - other; DUK_ASSERT(idx_func >= 0); DUK_ASSERT(duk_is_valid_index(thr, idx_func)); return idx_func; } /* Prepare value stack for a method call through an object property. * May currently throw an error e.g. when getting the property. */ DUK_LOCAL void duk__call_prop_prep_stack(duk_hthread *thr, duk_idx_t normalized_obj_idx, duk_idx_t nargs) { DUK_CTX_ASSERT_VALID(thr); DUK_ASSERT(nargs >= 0); DUK_DDD(DUK_DDDPRINT("duk__call_prop_prep_stack, normalized_obj_idx=%ld, nargs=%ld, stacktop=%ld", (long) normalized_obj_idx, (long) nargs, (long) duk_get_top(thr))); /* [... key arg1 ... argN] */ /* duplicate key */ duk_dup(thr, -nargs - 1); /* Note: -nargs alone would fail for nargs == 0, this is OK */ (void) duk_get_prop(thr, normalized_obj_idx); DUK_DDD(DUK_DDDPRINT("func: %!T", (duk_tval *) duk_get_tval(thr, -1))); #if defined(DUK_USE_VERBOSE_ERRORS) if (DUK_UNLIKELY(!duk_is_callable(thr, -1))) { duk_tval *tv_base; duk_tval *tv_key; /* tv_targ is passed on stack top (at index -1). */ tv_base = DUK_GET_TVAL_POSIDX(thr, normalized_obj_idx); tv_key = DUK_GET_TVAL_NEGIDX(thr, -nargs - 2); DUK_ASSERT(tv_base >= thr->valstack_bottom && tv_base < thr->valstack_top); DUK_ASSERT(tv_key >= thr->valstack_bottom && tv_key < thr->valstack_top); duk_call_setup_propcall_error(thr, tv_base, tv_key); } #endif /* [... key arg1 ... argN func] */ duk_replace(thr, -nargs - 2); /* [... func arg1 ... argN] */ duk_dup(thr, normalized_obj_idx); duk_insert(thr, -nargs - 1); /* [... func this arg1 ... argN] */ } DUK_EXTERNAL void duk_call(duk_hthread *thr, duk_idx_t nargs) { duk_small_uint_t call_flags; duk_idx_t idx_func; DUK_ASSERT_API_ENTRY(thr); idx_func = duk__call_get_idx_func(thr, nargs, 1); DUK_ASSERT(duk_is_valid_index(thr, idx_func)); duk_insert_undefined(thr, idx_func + 1); call_flags = 0; /* not protected, respect reclimit, not constructor */ duk_handle_call_unprotected(thr, idx_func, call_flags); } DUK_EXTERNAL void duk_call_method(duk_hthread *thr, duk_idx_t nargs) { duk_small_uint_t call_flags; duk_idx_t idx_func; DUK_ASSERT_API_ENTRY(thr); idx_func = duk__call_get_idx_func(thr, nargs, 2); DUK_ASSERT(duk_is_valid_index(thr, idx_func)); call_flags = 0; /* not protected, respect reclimit, not constructor */ duk_handle_call_unprotected(thr, idx_func, call_flags); } DUK_EXTERNAL void duk_call_prop(duk_hthread *thr, duk_idx_t obj_idx, duk_idx_t nargs) { /* * XXX: if duk_handle_call() took values through indices, this could be * made much more sensible. However, duk_handle_call() needs to fudge * the 'this' and 'func' values to handle bound functions, which is now * done "in-place", so this is not a trivial change. */ DUK_ASSERT_API_ENTRY(thr); obj_idx = duk_require_normalize_index(thr, obj_idx); /* make absolute */ if (DUK_UNLIKELY(nargs < 0)) { DUK_ERROR_TYPE_INVALID_ARGS(thr); DUK_WO_NORETURN(return;); } duk__call_prop_prep_stack(thr, obj_idx, nargs); duk_call_method(thr, nargs); } DUK_LOCAL duk_ret_t duk__pcall_raw(duk_hthread *thr, void *udata) { duk__pcall_args *args; duk_idx_t idx_func; duk_int_t ret; DUK_CTX_ASSERT_VALID(thr); DUK_ASSERT(udata != NULL); args = (duk__pcall_args *) udata; idx_func = duk__call_get_idx_func_unvalidated(thr, args->nargs, 1); DUK_ASSERT(duk_is_valid_index(thr, idx_func)); duk_insert_undefined(thr, idx_func + 1); ret = duk_handle_call_unprotected(thr, idx_func, args->call_flags); DUK_ASSERT(ret == 0); DUK_UNREF(ret); return 1; } DUK_EXTERNAL duk_int_t duk_pcall(duk_hthread *thr, duk_idx_t nargs) { duk__pcall_args args; DUK_ASSERT_API_ENTRY(thr); args.nargs = nargs; if (DUK_UNLIKELY(nargs < 0)) { DUK_ERROR_TYPE_INVALID_ARGS(thr); DUK_WO_NORETURN(return DUK_EXEC_ERROR;); } args.call_flags = 0; return duk_safe_call(thr, duk__pcall_raw, (void *) &args /*udata*/, nargs + 1 /*nargs*/, 1 /*nrets*/); } DUK_LOCAL duk_ret_t duk__pcall_method_raw(duk_hthread *thr, void *udata) { duk__pcall_method_args *args; duk_idx_t idx_func; duk_int_t ret; DUK_CTX_ASSERT_VALID(thr); DUK_ASSERT(udata != NULL); args = (duk__pcall_method_args *) udata; idx_func = duk__call_get_idx_func_unvalidated(thr, args->nargs, 2); DUK_ASSERT(duk_is_valid_index(thr, idx_func)); ret = duk_handle_call_unprotected(thr, idx_func, args->call_flags); DUK_ASSERT(ret == 0); DUK_UNREF(ret); return 1; } DUK_INTERNAL duk_int_t duk_pcall_method_flags(duk_hthread *thr, duk_idx_t nargs, duk_small_uint_t call_flags) { duk__pcall_method_args args; DUK_ASSERT_API_ENTRY(thr); args.nargs = nargs; if (DUK_UNLIKELY(nargs < 0)) { DUK_ERROR_TYPE_INVALID_ARGS(thr); DUK_WO_NORETURN(return DUK_EXEC_ERROR;); } args.call_flags = call_flags; return duk_safe_call(thr, duk__pcall_method_raw, (void *) &args /*udata*/, nargs + 2 /*nargs*/, 1 /*nrets*/); } DUK_EXTERNAL duk_int_t duk_pcall_method(duk_hthread *thr, duk_idx_t nargs) { DUK_ASSERT_API_ENTRY(thr); return duk_pcall_method_flags(thr, nargs, 0); } DUK_LOCAL duk_ret_t duk__pcall_prop_raw(duk_hthread *thr, void *udata) { duk__pcall_prop_args *args; duk_idx_t obj_idx; duk_int_t ret; DUK_CTX_ASSERT_VALID(thr); DUK_ASSERT(udata != NULL); args = (duk__pcall_prop_args *) udata; obj_idx = duk_require_normalize_index(thr, args->obj_idx); /* make absolute */ duk__call_prop_prep_stack(thr, obj_idx, args->nargs); ret = duk_handle_call_unprotected_nargs(thr, args->nargs, args->call_flags); DUK_ASSERT(ret == 0); DUK_UNREF(ret); return 1; } DUK_EXTERNAL duk_int_t duk_pcall_prop(duk_hthread *thr, duk_idx_t obj_idx, duk_idx_t nargs) { duk__pcall_prop_args args; DUK_ASSERT_API_ENTRY(thr); args.obj_idx = obj_idx; args.nargs = nargs; if (DUK_UNLIKELY(nargs < 0)) { DUK_ERROR_TYPE_INVALID_ARGS(thr); DUK_WO_NORETURN(return DUK_EXEC_ERROR;); } args.call_flags = 0; return duk_safe_call(thr, duk__pcall_prop_raw, (void *) &args /*udata*/, nargs + 1 /*nargs*/, 1 /*nrets*/); } DUK_EXTERNAL duk_int_t duk_safe_call(duk_hthread *thr, duk_safe_call_function func, void *udata, duk_idx_t nargs, duk_idx_t nrets) { duk_int_t rc; DUK_ASSERT_API_ENTRY(thr); /* nargs condition; fail if: top - bottom < nargs * <=> top < bottom + nargs * nrets condition; fail if: end - (top - nargs) < nrets * <=> end - top + nargs < nrets * <=> end + nargs < top + nrets */ /* XXX: check for any reserve? */ if (DUK_UNLIKELY((nargs | nrets) < 0 || /* nargs < 0 || nrets < 0; OR sign bits */ thr->valstack_top < thr->valstack_bottom + nargs || /* nargs too large compared to top */ thr->valstack_end + nargs < thr->valstack_top + nrets)) { /* nrets too large compared to reserve */ DUK_D(DUK_DPRINT("not enough stack reserve for safe call or invalid arguments: " "nargs=%ld < 0 (?), nrets=%ld < 0 (?), top=%ld < bottom=%ld + nargs=%ld (?), " "end=%ld + nargs=%ld < top=%ld + nrets=%ld (?)", (long) nargs, (long) nrets, (long) (thr->valstack_top - thr->valstack), (long) (thr->valstack_bottom - thr->valstack), (long) nargs, (long) (thr->valstack_end - thr->valstack), (long) nargs, (long) (thr->valstack_top - thr->valstack), (long) nrets)); DUK_ERROR_TYPE_INVALID_ARGS(thr); DUK_WO_NORETURN(return DUK_EXEC_ERROR;); } rc = duk_handle_safe_call(thr, /* thread */ func, /* func */ udata, /* udata */ nargs, /* num_stack_args */ nrets); /* num_stack_res */ return rc; } DUK_EXTERNAL void duk_new(duk_hthread *thr, duk_idx_t nargs) { duk_idx_t idx_func; DUK_ASSERT_API_ENTRY(thr); idx_func = duk__call_get_idx_func(thr, nargs, 1); DUK_ASSERT(duk_is_valid_index(thr, idx_func)); duk_push_object(thr); /* default instance; internal proto updated by call handling */ duk_insert(thr, idx_func + 1); duk_handle_call_unprotected(thr, idx_func, DUK_CALL_FLAG_CONSTRUCT); } DUK_LOCAL duk_ret_t duk__pnew_helper(duk_hthread *thr, void *udata) { duk_idx_t nargs; DUK_ASSERT(udata != NULL); nargs = *((duk_idx_t *) udata); duk_new(thr, nargs); return 1; } DUK_EXTERNAL duk_int_t duk_pnew(duk_hthread *thr, duk_idx_t nargs) { duk_int_t rc; DUK_ASSERT_API_ENTRY(thr); /* For now, just use duk_safe_call() to wrap duk_new(). We can't * simply use a protected duk_handle_call() because pushing the * default instance might throw. */ if (DUK_UNLIKELY(nargs < 0)) { DUK_ERROR_TYPE_INVALID_ARGS(thr); DUK_WO_NORETURN(return DUK_EXEC_ERROR;); } rc = duk_safe_call(thr, duk__pnew_helper, (void *) &nargs /*udata*/, nargs + 1 /*nargs*/, 1 /*nrets*/); return rc; } DUK_EXTERNAL duk_bool_t duk_is_constructor_call(duk_hthread *thr) { duk_activation *act; DUK_ASSERT_API_ENTRY(thr); act = thr->callstack_curr; if (act != NULL) { return ((act->flags & DUK_ACT_FLAG_CONSTRUCT) != 0 ? 1 : 0); } return 0; } DUK_EXTERNAL void duk_require_constructor_call(duk_hthread *thr) { DUK_ASSERT_API_ENTRY(thr); if (!duk_is_constructor_call(thr)) { DUK_ERROR_TYPE(thr, DUK_STR_CONSTRUCT_ONLY); DUK_WO_NORETURN(return;); } } DUK_EXTERNAL duk_bool_t duk_is_strict_call(duk_hthread *thr) { duk_activation *act; /* For user code this could just return 1 (strict) always * because all Duktape/C functions are considered strict, * and strict is also the default when nothing is running. * However, Duktape may call this function internally when * the current activation is an ECMAScript function, so * this cannot be replaced by a 'return 1' without fixing * the internal call sites. */ DUK_ASSERT_API_ENTRY(thr); act = thr->callstack_curr; if (act != NULL) { return ((act->flags & DUK_ACT_FLAG_STRICT) != 0 ? 1 : 0); } else { /* Strict by default. */ return 1; } } /* * Duktape/C function magic */ DUK_EXTERNAL duk_int_t duk_get_current_magic(duk_hthread *thr) { duk_activation *act; duk_hobject *func; DUK_ASSERT_API_ENTRY(thr); act = thr->callstack_curr; if (act) { func = DUK_ACT_GET_FUNC(act); if (!func) { duk_tval *tv = &act->tv_func; duk_small_uint_t lf_flags; lf_flags = DUK_TVAL_GET_LIGHTFUNC_FLAGS(tv); return (duk_int_t) DUK_LFUNC_FLAGS_GET_MAGIC(lf_flags); } DUK_ASSERT(func != NULL); if (DUK_HOBJECT_IS_NATFUNC(func)) { duk_hnatfunc *nf = (duk_hnatfunc *) func; return (duk_int_t) nf->magic; } } return 0; } DUK_EXTERNAL duk_int_t duk_get_magic(duk_hthread *thr, duk_idx_t idx) { duk_tval *tv; duk_hobject *h; DUK_ASSERT_API_ENTRY(thr); tv = duk_require_tval(thr, idx); if (DUK_TVAL_IS_OBJECT(tv)) { h = DUK_TVAL_GET_OBJECT(tv); DUK_ASSERT(h != NULL); if (!DUK_HOBJECT_HAS_NATFUNC(h)) { goto type_error; } return (duk_int_t) ((duk_hnatfunc *) h)->magic; } else if (DUK_TVAL_IS_LIGHTFUNC(tv)) { duk_small_uint_t lf_flags = DUK_TVAL_GET_LIGHTFUNC_FLAGS(tv); return (duk_int_t) DUK_LFUNC_FLAGS_GET_MAGIC(lf_flags); } /* fall through */ type_error: DUK_ERROR_TYPE(thr, DUK_STR_UNEXPECTED_TYPE); DUK_WO_NORETURN(return 0;); } DUK_EXTERNAL void duk_set_magic(duk_hthread *thr, duk_idx_t idx, duk_int_t magic) { duk_hnatfunc *nf; DUK_ASSERT_API_ENTRY(thr); nf = duk_require_hnatfunc(thr, idx); DUK_ASSERT(nf != NULL); nf->magic = (duk_int16_t) magic; } /* * Misc helpers */ /* Resolve a bound function on value stack top to a non-bound target * (leave other values as is). */ DUK_INTERNAL void duk_resolve_nonbound_function(duk_hthread *thr) { duk_tval *tv; DUK_HTHREAD_ASSERT_VALID(thr); tv = DUK_GET_TVAL_NEGIDX(thr, -1); if (DUK_TVAL_IS_OBJECT(tv)) { duk_hobject *h; h = DUK_TVAL_GET_OBJECT(tv); DUK_ASSERT(h != NULL); if (DUK_HOBJECT_HAS_BOUNDFUNC(h)) { duk_push_tval(thr, &((duk_hboundfunc *) (void *) h)->target); duk_replace(thr, -2); #if 0 DUK_TVAL_SET_TVAL(tv, &((duk_hboundfunc *) h)->target); DUK_TVAL_INCREF(thr, tv); DUK_HOBJECT_DECREF_NORZ(thr, h); #endif /* Rely on Function.prototype.bind() on never creating a bound * function whose target is not proper. This is now safe * because the target is not even an internal property but a * struct member. */ DUK_ASSERT(duk_is_lightfunc(thr, -1) || duk_is_callable(thr, -1)); } } /* Lightfuncs cannot be bound but are always callable and * constructable. */ } #line 1 "duk_api_codec.c" /* * Encoding and decoding basic formats: hex, base64. * * These are in-place operations which may allow an optimized implementation. * * Base-64: https://tools.ietf.org/html/rfc4648#section-4 */ /* #include duk_internal.h -> already included */ /* * Misc helpers */ /* Shared handling for encode/decode argument. Fast path handling for * buffer and string values because they're the most common. In particular, * avoid creating a temporary string or buffer when possible. Return value * is guaranteed to be non-NULL, even for zero length input. */ DUK_LOCAL const duk_uint8_t *duk__prep_codec_arg(duk_hthread *thr, duk_idx_t idx, duk_size_t *out_len) { const void *def_ptr = (const void *) out_len; /* Any non-NULL pointer will do. */ const void *ptr; duk_bool_t isbuffer; DUK_ASSERT(out_len != NULL); DUK_ASSERT(def_ptr != NULL); DUK_ASSERT(duk_is_valid_index(thr, idx)); /* checked by caller */ ptr = (const void *) duk_get_buffer_data_raw(thr, idx, out_len, NULL /*def_ptr*/, 0 /*def_size*/, 0 /*throw_flag*/, &isbuffer); if (isbuffer) { DUK_ASSERT(ptr != NULL || *out_len == 0U); if (DUK_UNLIKELY(ptr == NULL)) { ptr = def_ptr; } DUK_ASSERT(ptr != NULL); } else { /* For strings a non-NULL pointer is always guaranteed because * at least a NUL will be present. */ ptr = (const void *) duk_to_lstring(thr, idx, out_len); DUK_ASSERT(ptr != NULL); } DUK_ASSERT(ptr != NULL); return (const duk_uint8_t *) ptr; } /* * Base64 */ #if defined(DUK_USE_BASE64_SUPPORT) /* Bytes emitted for number of padding characters in range [0,4]. */ DUK_LOCAL const duk_int8_t duk__base64_decode_nequal_step[5] = { 3, /* #### -> 24 bits, emit 3 bytes */ 2, /* ###= -> 18 bits, emit 2 bytes */ 1, /* ##== -> 12 bits, emit 1 byte */ -1, /* #=== -> 6 bits, error */ 0, /* ==== -> 0 bits, emit 0 bytes */ }; #if defined(DUK_USE_BASE64_FASTPATH) DUK_LOCAL const duk_uint8_t duk__base64_enctab_fast[64] = { 0x41U, 0x42U, 0x43U, 0x44U, 0x45U, 0x46U, 0x47U, 0x48U, 0x49U, 0x4aU, 0x4bU, 0x4cU, 0x4dU, 0x4eU, 0x4fU, 0x50U, /* A...P */ 0x51U, 0x52U, 0x53U, 0x54U, 0x55U, 0x56U, 0x57U, 0x58U, 0x59U, 0x5aU, 0x61U, 0x62U, 0x63U, 0x64U, 0x65U, 0x66U, /* Q...f */ 0x67U, 0x68U, 0x69U, 0x6aU, 0x6bU, 0x6cU, 0x6dU, 0x6eU, 0x6fU, 0x70U, 0x71U, 0x72U, 0x73U, 0x74U, 0x75U, 0x76U, /* g...v */ 0x77U, 0x78U, 0x79U, 0x7aU, 0x30U, 0x31U, 0x32U, 0x33U, 0x34U, 0x35U, 0x36U, 0x37U, 0x38U, 0x39U, 0x2bU, 0x2fU /* w.../ */ }; #endif /* DUK_USE_BASE64_FASTPATH */ #if defined(DUK_USE_BASE64_FASTPATH) /* Decode table for one byte of input: * -1 = allowed whitespace * -2 = padding * -3 = error * 0...63 decoded bytes */ DUK_LOCAL const duk_int8_t duk__base64_dectab_fast[256] = { -3, -3, -3, -3, -3, -3, -3, -3, -3, -1, -1, -3, -3, -1, -3, -3, /* 0x00...0x0f */ -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, /* 0x10...0x1f */ -1, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, 62, -3, -3, -3, 63, /* 0x20...0x2f */ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -3, -3, -3, -2, -3, -3, /* 0x30...0x3f */ -3, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, /* 0x40...0x4f */ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -3, -3, -3, -3, -3, /* 0x50...0x5f */ -3, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, /* 0x60...0x6f */ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -3, -3, -3, -3, -3, /* 0x70...0x7f */ -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, /* 0x80...0x8f */ -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, /* 0x90...0x9f */ -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, /* 0xa0...0xaf */ -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, /* 0xb0...0xbf */ -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, /* 0xc0...0xcf */ -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, /* 0xd0...0xdf */ -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, /* 0xe0...0xef */ -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3 /* 0xf0...0xff */ }; #endif /* DUK_USE_BASE64_FASTPATH */ #if defined(DUK_USE_BASE64_FASTPATH) DUK_LOCAL DUK_ALWAYS_INLINE void duk__base64_encode_fast_3(const duk_uint8_t *src, duk_uint8_t *dst) { duk_uint_t t; t = (duk_uint_t) src[0]; t = (t << 8) + (duk_uint_t) src[1]; t = (t << 8) + (duk_uint_t) src[2]; dst[0] = duk__base64_enctab_fast[t >> 18]; dst[1] = duk__base64_enctab_fast[(t >> 12) & 0x3fU]; dst[2] = duk__base64_enctab_fast[(t >> 6) & 0x3fU]; dst[3] = duk__base64_enctab_fast[t & 0x3fU]; #if 0 /* Tested: not faster on x64, most likely due to aliasing between * output and input index computation. */ /* aaaaaabb bbbbcccc ccdddddd */ dst[0] = duk__base64_enctab_fast[(src[0] >> 2) & 0x3fU]; dst[1] = duk__base64_enctab_fast[((src[0] << 4) & 0x30U) | ((src[1] >> 4) & 0x0fU)]; dst[2] = duk__base64_enctab_fast[((src[1] << 2) & 0x3fU) | ((src[2] >> 6) & 0x03U)]; dst[3] = duk__base64_enctab_fast[src[2] & 0x3fU]; #endif } DUK_LOCAL DUK_ALWAYS_INLINE void duk__base64_encode_fast_2(const duk_uint8_t *src, duk_uint8_t *dst) { duk_uint_t t; t = (duk_uint_t) src[0]; t = (t << 8) + (duk_uint_t) src[1]; dst[0] = duk__base64_enctab_fast[t >> 10]; /* XXXXXX-- -------- */ dst[1] = duk__base64_enctab_fast[(t >> 4) & 0x3fU]; /* ------XX XXXX---- */ dst[2] = duk__base64_enctab_fast[(t << 2) & 0x3fU]; /* -------- ----XXXX */ dst[3] = DUK_ASC_EQUALS; } DUK_LOCAL DUK_ALWAYS_INLINE void duk__base64_encode_fast_1(const duk_uint8_t *src, duk_uint8_t *dst) { duk_uint_t t; t = (duk_uint_t) src[0]; dst[0] = duk__base64_enctab_fast[t >> 2]; /* XXXXXX-- */ dst[1] = duk__base64_enctab_fast[(t << 4) & 0x3fU]; /* ------XX */ dst[2] = DUK_ASC_EQUALS; dst[3] = DUK_ASC_EQUALS; } DUK_LOCAL void duk__base64_encode_helper(const duk_uint8_t *src, duk_size_t srclen, duk_uint8_t *dst) { duk_size_t n; const duk_uint8_t *p; duk_uint8_t *q; n = srclen; p = src; q = dst; if (n >= 16U) { /* Fast path, unrolled by 4, allows interleaving. Process * 12-byte input chunks which encode to 16-char output chunks. * Only enter when at least one block is emitted (avoids div+mul * for short inputs too). */ const duk_uint8_t *p_end_fast; p_end_fast = p + ((n / 12U) * 12U); DUK_ASSERT(p_end_fast >= p + 12); do { duk__base64_encode_fast_3(p, q); duk__base64_encode_fast_3(p + 3, q + 4); duk__base64_encode_fast_3(p + 6, q + 8); duk__base64_encode_fast_3(p + 9, q + 12); p += 12; q += 16; } while (DUK_LIKELY(p != p_end_fast)); DUK_ASSERT(src + srclen >= p); n = (duk_size_t) (src + srclen - p); DUK_ASSERT(n < 12U); } /* Remainder. */ while (n >= 3U) { duk__base64_encode_fast_3(p, q); p += 3; q += 4; n -= 3U; } DUK_ASSERT(n == 0U || n == 1U || n == 2U); if (n == 1U) { duk__base64_encode_fast_1(p, q); #if 0 /* Unnecessary. */ p += 1; q += 4; n -= 1U; #endif } else if (n == 2U) { duk__base64_encode_fast_2(p, q); #if 0 /* Unnecessary. */ p += 2; q += 4; n -= 2U; #endif } else { DUK_ASSERT(n == 0U); /* nothing to do */ ; } } #else /* DUK_USE_BASE64_FASTPATH */ DUK_LOCAL void duk__base64_encode_helper(const duk_uint8_t *src, duk_size_t srclen, duk_uint8_t *dst) { duk_small_uint_t i, npad; duk_uint_t t, x, y; const duk_uint8_t *p; const duk_uint8_t *p_end; duk_uint8_t *q; p = src; p_end = src + srclen; q = dst; npad = 0U; while (p < p_end) { /* Read 3 bytes into 't', padded by zero. */ t = 0; for (i = 0; i < 3; i++) { t = t << 8; if (p < p_end) { t += (duk_uint_t) (*p++); } else { /* This only happens on the last loop and we're * guaranteed to exit on the next loop. */ npad++; } } DUK_ASSERT(npad <= 2U); /* Emit 4 encoded characters. If npad > 0, some of the * chars will be incorrect (zero bits) but we fix up the * padding after the loop. A straightforward 64-byte * lookup would be faster and cleaner, but this is shorter. */ for (i = 0; i < 4; i++) { x = ((t >> 18) & 0x3fU); t = t << 6; if (x <= 51U) { if (x <= 25) { y = x + DUK_ASC_UC_A; } else { y = x - 26 + DUK_ASC_LC_A; } } else { if (x <= 61U) { y = x - 52 + DUK_ASC_0; } else if (x == 62) { y = DUK_ASC_PLUS; } else { DUK_ASSERT(x == 63); y = DUK_ASC_SLASH; } } *q++ = (duk_uint8_t) y; } } /* Handle padding by rewriting 0-2 bogus characters at the end. * * Missing bytes npad base64 example * 0 0 #### * 1 1 ###= * 2 2 ##== */ DUK_ASSERT(npad <= 2U); while (npad > 0U) { *(q - npad) = DUK_ASC_EQUALS; npad--; } } #endif /* DUK_USE_BASE64_FASTPATH */ #if defined(DUK_USE_BASE64_FASTPATH) DUK_LOCAL duk_bool_t duk__base64_decode_helper(const duk_uint8_t *src, duk_size_t srclen, duk_uint8_t *dst, duk_uint8_t **out_dst_final) { duk_int_t x; duk_uint_t t; duk_small_uint_t n_equal; duk_int8_t step; const duk_uint8_t *p; const duk_uint8_t *p_end; const duk_uint8_t *p_end_safe; duk_uint8_t *q; DUK_ASSERT(src != NULL); /* Required by pointer arithmetic below, which fails for NULL. */ p = src; p_end = src + srclen; p_end_safe = p_end - 8; /* If 'src <= src_end_safe', safe to read 8 bytes. */ q = dst; /* Alternate between a fast path which processes clean groups with no * padding or whitespace, and a slow path which processes one arbitrary * group and then re-enters the fast path. This handles e.g. base64 * with newlines reasonably well because the majority of a line is in * the fast path. */ for (;;) { /* Fast path, on each loop handle two 4-char input groups. * If both are clean, emit 6 bytes and continue. If first * is clean, emit 3 bytes and drop out; otherwise emit * nothing and drop out. This approach could be extended to * more groups per loop, but for inputs with e.g. periodic * newlines (which are common) it might not be an improvement. */ while (DUK_LIKELY(p <= p_end_safe)) { duk_int_t t1, t2; /* The lookup byte is intentionally sign extended to * (at least) 32 bits and then ORed. This ensures * that is at least 1 byte is negative, the highest * bit of the accumulator will be set at the end and * we don't need to check every byte. * * Read all input bytes first before writing output * bytes to minimize aliasing. */ DUK_DDD(DUK_DDDPRINT("fast loop: p=%p, p_end_safe=%p, p_end=%p", (const void *) p, (const void *) p_end_safe, (const void *) p_end)); t1 = (duk_int_t) duk__base64_dectab_fast[p[0]]; t1 = (duk_int_t) ((duk_uint_t) t1 << 6) | (duk_int_t) duk__base64_dectab_fast[p[1]]; t1 = (duk_int_t) ((duk_uint_t) t1 << 6) | (duk_int_t) duk__base64_dectab_fast[p[2]]; t1 = (duk_int_t) ((duk_uint_t) t1 << 6) | (duk_int_t) duk__base64_dectab_fast[p[3]]; t2 = (duk_int_t) duk__base64_dectab_fast[p[4]]; t2 = (duk_int_t) ((duk_uint_t) t2 << 6) | (duk_int_t) duk__base64_dectab_fast[p[5]]; t2 = (duk_int_t) ((duk_uint_t) t2 << 6) | (duk_int_t) duk__base64_dectab_fast[p[6]]; t2 = (duk_int_t) ((duk_uint_t) t2 << 6) | (duk_int_t) duk__base64_dectab_fast[p[7]]; q[0] = (duk_uint8_t) (((duk_uint_t) t1 >> 16) & 0xffU); q[1] = (duk_uint8_t) (((duk_uint_t) t1 >> 8) & 0xffU); q[2] = (duk_uint8_t) ((duk_uint_t) t1 & 0xffU); q[3] = (duk_uint8_t) (((duk_uint_t) t2 >> 16) & 0xffU); q[4] = (duk_uint8_t) (((duk_uint_t) t2 >> 8) & 0xffU); q[5] = (duk_uint8_t) ((duk_uint_t) t2 & 0xffU); /* Optimistic check using one branch. */ if (DUK_LIKELY((t1 | t2) >= 0)) { p += 8; q += 6; } else if (t1 >= 0) { DUK_DDD( DUK_DDDPRINT("fast loop first group was clean, second was not, process one slow path group")); DUK_ASSERT(t2 < 0); p += 4; q += 3; break; } else { DUK_DDD(DUK_DDDPRINT( "fast loop first group was not clean, second does not matter, process one slow path group")); DUK_ASSERT(t1 < 0); break; } } /* fast path */ /* Slow path step 1: try to scan a 4-character encoded group, * end-of-input, or start-of-padding. We exit with: * 1. n_chars == 4: full group, no padding, no end-of-input. * 2. n_chars < 4: partial group (may also be 0), encountered * padding or end of input. * * The accumulator is initialized to 1; this allows us to detect * a full group by comparing >= 0x1000000 without an extra * counter variable. */ t = 1UL; for (;;) { DUK_DDD(DUK_DDDPRINT("slow loop: p=%p, p_end=%p, t=%lu", (const void *) p, (const void *) p_end, (unsigned long) t)); if (DUK_LIKELY(p < p_end)) { x = duk__base64_dectab_fast[*p++]; if (DUK_LIKELY(x >= 0)) { DUK_ASSERT(x >= 0 && x <= 63); t = (t << 6) + (duk_uint_t) x; if (t >= 0x1000000UL) { break; } } else if (x == -1) { continue; /* allowed ascii whitespace */ } else if (x == -2) { p--; break; /* start of padding */ } else { DUK_ASSERT(x == -3); goto decode_error; } } else { break; /* end of input */ } } /* slow path step 1 */ /* Complete the padding by simulating pad characters, * regardless of actual input padding chars. */ n_equal = 0; while (t < 0x1000000UL) { t = (t << 6) + 0U; n_equal++; } /* Slow path step 2: deal with full/partial group, padding, * etc. Note that for num chars in [0,3] we intentionally emit * 3 bytes but don't step forward that much, buffer space is * guaranteed in setup. * * num chars: * 0 #### no output (= step 0) * 1 #=== reject, 6 bits of data * 2 ##== 12 bits of data, output 1 byte (= step 1) * 3 ###= 18 bits of data, output 2 bytes (= step 2) * 4 #### 24 bits of data, output 3 bytes (= step 3) */ q[0] = (duk_uint8_t) ((t >> 16) & 0xffU); q[1] = (duk_uint8_t) ((t >> 8) & 0xffU); q[2] = (duk_uint8_t) (t & 0xffU); DUK_ASSERT(n_equal <= 4); step = duk__base64_decode_nequal_step[n_equal]; if (DUK_UNLIKELY(step < 0)) { goto decode_error; } q += step; /* Slow path step 3: read and ignore padding and whitespace * until (a) next non-padding and non-whitespace character * after which we resume the fast path, or (b) end of input. * This allows us to accept missing, partial, full, and extra * padding cases uniformly. We also support concatenated * base-64 documents because we resume scanning afterwards. * * Note that to support concatenated documents well, the '=' * padding found inside the input must also allow for 'extra' * padding. For example, 'Zm===' decodes to 'f' and has one * extra padding char. So, 'Zm===Zm' should decode 'ff', even * though the standard break-up would be 'Zm==' + '=Zm' which * doesn't make sense. * * We also accept prepended padding like '==Zm9', because it * is equivalent to an empty document with extra padding ('==') * followed by a valid document. */ for (;;) { if (DUK_UNLIKELY(p >= p_end)) { goto done; } x = duk__base64_dectab_fast[*p++]; if (x == -1 || x == -2) { ; /* padding or whitespace, keep eating */ } else { p--; break; /* backtrack and go back to fast path, even for -1 */ } } /* slow path step 3 */ } /* outer fast+slow path loop */ done: DUK_DDD(DUK_DDDPRINT("done; p=%p, p_end=%p", (const void *) p, (const void *) p_end)); DUK_ASSERT(p == p_end); *out_dst_final = q; return 1; decode_error: return 0; } #else /* DUK_USE_BASE64_FASTPATH */ DUK_LOCAL duk_bool_t duk__base64_decode_helper(const duk_uint8_t *src, duk_size_t srclen, duk_uint8_t *dst, duk_uint8_t **out_dst_final) { duk_uint_t t, x; duk_int_t y; duk_int8_t step; const duk_uint8_t *p; const duk_uint8_t *p_end; duk_uint8_t *q; /* 0x09, 0x0a, or 0x0d */ duk_uint32_t mask_white = (1U << 9) | (1U << 10) | (1U << 13); /* 't' tracks progress of the decoded group: * * t == 1 no valid chars yet * t >= 0x40 1x6 = 6 bits shifted in * t >= 0x1000 2x6 = 12 bits shifted in * t >= 0x40000 3x6 = 18 bits shifted in * t >= 0x1000000 4x6 = 24 bits shifted in * * By initializing t=1 there's no need for a separate counter for * the number of characters found so far. */ p = src; p_end = src + srclen; q = dst; t = 1UL; for (;;) { duk_small_uint_t n_equal; DUK_ASSERT(t >= 1U); if (p >= p_end) { /* End of input: if input exists, treat like * start of padding, finish the block, then * re-enter here to see we're done. */ if (t == 1U) { break; } else { goto simulate_padding; } } x = *p++; if (x >= 0x41U) { /* Valid: a-z and A-Z. */ DUK_ASSERT(x >= 0x41U && x <= 0xffU); if (x >= 0x61U && x <= 0x7aU) { y = (duk_int_t) x - 0x61 + 26; } else if (x <= 0x5aU) { y = (duk_int_t) x - 0x41; } else { goto decode_error; } } else if (x >= 0x30U) { /* Valid: 0-9 and =. */ DUK_ASSERT(x >= 0x30U && x <= 0x40U); if (x <= 0x39U) { y = (duk_int_t) x - 0x30 + 52; } else if (x == 0x3dU) { /* Skip padding and whitespace unless we're in the * middle of a block. Otherwise complete group by * simulating shifting in the correct padding. */ if (t == 1U) { continue; } goto simulate_padding; } else { goto decode_error; } } else if (x >= 0x20U) { /* Valid: +, /, and 0x20 whitespace. */ DUK_ASSERT(x >= 0x20U && x <= 0x2fU); if (x == 0x2bU) { y = 62; } else if (x == 0x2fU) { y = 63; } else if (x == 0x20U) { continue; } else { goto decode_error; } } else { /* Valid: whitespace. */ duk_uint32_t m; DUK_ASSERT(x < 0x20U); /* 0x00 to 0x1f */ m = (1U << x); if (mask_white & m) { /* Allow basic ASCII whitespace. */ continue; } else { goto decode_error; } } DUK_ASSERT(y >= 0 && y <= 63); t = (t << 6) + (duk_uint_t) y; if (t < 0x1000000UL) { continue; } /* fall through; no padding will be added */ simulate_padding: n_equal = 0; while (t < 0x1000000UL) { t = (t << 6) + 0U; n_equal++; } /* Output 3 bytes from 't' and advance as needed. */ q[0] = (duk_uint8_t) ((t >> 16) & 0xffU); q[1] = (duk_uint8_t) ((t >> 8) & 0xffU); q[2] = (duk_uint8_t) (t & 0xffU); DUK_ASSERT(n_equal <= 4U); step = duk__base64_decode_nequal_step[n_equal]; if (step < 0) { goto decode_error; } q += step; /* Re-enter loop. The actual padding characters are skipped * by the main loop. This handles cases like missing, partial, * full, and extra padding, and allows parsing of concatenated * documents (with extra padding) like: Zm===Zm. Also extra * prepended padding is accepted: ===Zm9v. */ t = 1U; } DUK_ASSERT(t == 1UL); *out_dst_final = q; return 1; decode_error: return 0; } #endif /* DUK_USE_BASE64_FASTPATH */ DUK_EXTERNAL const char *duk_base64_encode(duk_hthread *thr, duk_idx_t idx) { const duk_uint8_t *src; duk_size_t srclen; duk_size_t dstlen; duk_uint8_t *dst; const char *ret; DUK_ASSERT_API_ENTRY(thr); idx = duk_require_normalize_index(thr, idx); src = duk__prep_codec_arg(thr, idx, &srclen); DUK_ASSERT(src != NULL); /* Compute exact output length. Computation must not wrap; this * limit works for 32-bit size_t: * >>> srclen = 3221225469 * >>> '%x' % ((srclen + 2) / 3 * 4) * 'fffffffc' */ if (srclen > 3221225469UL) { goto type_error; } dstlen = (srclen + 2U) / 3U * 4U; dst = (duk_uint8_t *) duk_push_fixed_buffer_nozero(thr, dstlen); duk__base64_encode_helper((const duk_uint8_t *) src, srclen, dst); ret = duk_buffer_to_string(thr, -1); /* Safe, result is ASCII. */ duk_replace(thr, idx); return ret; type_error: DUK_ERROR_TYPE(thr, DUK_STR_BASE64_ENCODE_FAILED); DUK_WO_NORETURN(return NULL;); } DUK_EXTERNAL void duk_base64_decode(duk_hthread *thr, duk_idx_t idx) { const duk_uint8_t *src; duk_size_t srclen; duk_size_t dstlen; duk_uint8_t *dst; duk_uint8_t *dst_final; DUK_ASSERT_API_ENTRY(thr); idx = duk_require_normalize_index(thr, idx); src = duk__prep_codec_arg(thr, idx, &srclen); DUK_ASSERT(src != NULL); /* Round up and add safety margin. Avoid addition before division to * avoid possibility of wrapping. Margin includes +3 for rounding up, * and +3 for one extra group: the decoder may emit and then backtrack * a full group (3 bytes) from zero-sized input for technical reasons. * Similarly, 'xx' may ecause 1+3 = bytes to be emitted and then * backtracked. */ dstlen = (srclen / 4) * 3 + 6; /* upper limit, assuming no whitespace etc */ dst = (duk_uint8_t *) duk_push_dynamic_buffer(thr, dstlen); /* Note: for dstlen=0, dst may be NULL */ if (!duk__base64_decode_helper((const duk_uint8_t *) src, srclen, dst, &dst_final)) { goto type_error; } /* XXX: convert to fixed buffer? */ (void) duk_resize_buffer(thr, -1, (duk_size_t) (dst_final - dst)); duk_replace(thr, idx); return; type_error: DUK_ERROR_TYPE(thr, DUK_STR_BASE64_DECODE_FAILED); DUK_WO_NORETURN(return;); } #else /* DUK_USE_BASE64_SUPPORT */ DUK_EXTERNAL const char *duk_base64_encode(duk_hthread *thr, duk_idx_t idx) { DUK_UNREF(idx); DUK_ERROR_UNSUPPORTED(thr); DUK_WO_NORETURN(return NULL;); } DUK_EXTERNAL void duk_base64_decode(duk_hthread *thr, duk_idx_t idx) { DUK_UNREF(idx); DUK_ERROR_UNSUPPORTED(thr); DUK_WO_NORETURN(return;); } #endif /* DUK_USE_BASE64_SUPPORT */ /* * Hex */ #if defined(DUK_USE_HEX_SUPPORT) DUK_EXTERNAL const char *duk_hex_encode(duk_hthread *thr, duk_idx_t idx) { const duk_uint8_t *inp; duk_size_t len; duk_size_t i; duk_uint8_t *buf; const char *ret; #if defined(DUK_USE_HEX_FASTPATH) duk_size_t len_safe; duk_uint16_t *p16; #endif DUK_ASSERT_API_ENTRY(thr); idx = duk_require_normalize_index(thr, idx); inp = duk__prep_codec_arg(thr, idx, &len); DUK_ASSERT(inp != NULL); /* Fixed buffer, no zeroing because we'll fill all the data. */ buf = (duk_uint8_t *) duk_push_fixed_buffer_nozero(thr, len * 2); DUK_ASSERT(buf != NULL); #if defined(DUK_USE_HEX_FASTPATH) DUK_ASSERT((((duk_size_t) buf) & 0x01U) == 0); /* pointer is aligned, guaranteed for fixed buffer */ p16 = (duk_uint16_t *) (void *) buf; len_safe = len & ~0x03U; for (i = 0; i < len_safe; i += 4) { p16[0] = duk_hex_enctab[inp[i]]; p16[1] = duk_hex_enctab[inp[i + 1]]; p16[2] = duk_hex_enctab[inp[i + 2]]; p16[3] = duk_hex_enctab[inp[i + 3]]; p16 += 4; } for (; i < len; i++) { *p16++ = duk_hex_enctab[inp[i]]; } #else /* DUK_USE_HEX_FASTPATH */ for (i = 0; i < len; i++) { duk_small_uint_t t; t = (duk_small_uint_t) inp[i]; buf[i * 2 + 0] = duk_lc_digits[t >> 4]; buf[i * 2 + 1] = duk_lc_digits[t & 0x0f]; } #endif /* DUK_USE_HEX_FASTPATH */ /* XXX: Using a string return value forces a string intern which is * not always necessary. As a rough performance measure, hex encode * time for tests/perf/test-hex-encode.js dropped from ~35s to ~15s * without string coercion. Change to returning a buffer and let the * caller coerce to string if necessary? */ ret = duk_buffer_to_string(thr, -1); /* Safe, result is ASCII. */ duk_replace(thr, idx); return ret; } DUK_EXTERNAL void duk_hex_decode(duk_hthread *thr, duk_idx_t idx) { const duk_uint8_t *inp; duk_size_t len; duk_size_t i; duk_int_t t; duk_uint8_t *buf; #if defined(DUK_USE_HEX_FASTPATH) duk_int_t chk; duk_uint8_t *p; duk_size_t len_safe; #endif DUK_ASSERT_API_ENTRY(thr); idx = duk_require_normalize_index(thr, idx); inp = duk__prep_codec_arg(thr, idx, &len); DUK_ASSERT(inp != NULL); if (len & 0x01) { goto type_error; } /* Fixed buffer, no zeroing because we'll fill all the data. */ buf = (duk_uint8_t *) duk_push_fixed_buffer_nozero(thr, len / 2); DUK_ASSERT(buf != NULL); #if defined(DUK_USE_HEX_FASTPATH) p = buf; len_safe = len & ~0x07U; for (i = 0; i < len_safe; i += 8) { t = ((duk_int_t) duk_hex_dectab_shift4[inp[i]]) | ((duk_int_t) duk_hex_dectab[inp[i + 1]]); chk = t; p[0] = (duk_uint8_t) t; t = ((duk_int_t) duk_hex_dectab_shift4[inp[i + 2]]) | ((duk_int_t) duk_hex_dectab[inp[i + 3]]); chk |= t; p[1] = (duk_uint8_t) t; t = ((duk_int_t) duk_hex_dectab_shift4[inp[i + 4]]) | ((duk_int_t) duk_hex_dectab[inp[i + 5]]); chk |= t; p[2] = (duk_uint8_t) t; t = ((duk_int_t) duk_hex_dectab_shift4[inp[i + 6]]) | ((duk_int_t) duk_hex_dectab[inp[i + 7]]); chk |= t; p[3] = (duk_uint8_t) t; p += 4; /* Check if any lookup above had a negative result. */ if (DUK_UNLIKELY(chk < 0)) { goto type_error; } } for (; i < len; i += 2) { /* First cast to duk_int_t to sign extend, second cast to * duk_uint_t to avoid signed left shift, and final cast to * duk_int_t result type. */ t = (duk_int_t) ((((duk_uint_t) (duk_int_t) duk_hex_dectab[inp[i]]) << 4U) | ((duk_uint_t) (duk_int_t) duk_hex_dectab[inp[i + 1]])); if (DUK_UNLIKELY(t < 0)) { goto type_error; } *p++ = (duk_uint8_t) t; } #else /* DUK_USE_HEX_FASTPATH */ for (i = 0; i < len; i += 2) { /* For invalid characters the value -1 gets extended to * at least 16 bits. If either nybble is invalid, the * resulting 't' will be < 0. */ t = (duk_int_t) ((((duk_uint_t) (duk_int_t) duk_hex_dectab[inp[i]]) << 4U) | ((duk_uint_t) (duk_int_t) duk_hex_dectab[inp[i + 1]])); if (DUK_UNLIKELY(t < 0)) { goto type_error; } buf[i >> 1] = (duk_uint8_t) t; } #endif /* DUK_USE_HEX_FASTPATH */ duk_replace(thr, idx); return; type_error: DUK_ERROR_TYPE(thr, DUK_STR_HEX_DECODE_FAILED); DUK_WO_NORETURN(return;); } #else /* DUK_USE_HEX_SUPPORT */ DUK_EXTERNAL const char *duk_hex_encode(duk_hthread *thr, duk_idx_t idx) { DUK_UNREF(idx); DUK_ERROR_UNSUPPORTED(thr); DUK_WO_NORETURN(return NULL;); } DUK_EXTERNAL void duk_hex_decode(duk_hthread *thr, duk_idx_t idx) { DUK_UNREF(idx); DUK_ERROR_UNSUPPORTED(thr); DUK_WO_NORETURN(return;); } #endif /* DUK_USE_HEX_SUPPORT */ /* * JSON */ #if defined(DUK_USE_JSON_SUPPORT) DUK_EXTERNAL const char *duk_json_encode(duk_hthread *thr, duk_idx_t idx) { #if defined(DUK_USE_ASSERTIONS) duk_idx_t top_at_entry; #endif const char *ret; DUK_ASSERT_API_ENTRY(thr); #if defined(DUK_USE_ASSERTIONS) top_at_entry = duk_get_top(thr); #endif idx = duk_require_normalize_index(thr, idx); duk_bi_json_stringify_helper(thr, idx /*idx_value*/, DUK_INVALID_INDEX /*idx_replacer*/, DUK_INVALID_INDEX /*idx_space*/, 0 /*flags*/); DUK_ASSERT(duk_is_string(thr, -1)); duk_replace(thr, idx); ret = duk_get_string(thr, idx); DUK_ASSERT(duk_get_top(thr) == top_at_entry); return ret; } DUK_EXTERNAL void duk_json_decode(duk_hthread *thr, duk_idx_t idx) { #if defined(DUK_USE_ASSERTIONS) duk_idx_t top_at_entry; #endif DUK_ASSERT_API_ENTRY(thr); #if defined(DUK_USE_ASSERTIONS) top_at_entry = duk_get_top(thr); #endif idx = duk_require_normalize_index(thr, idx); duk_bi_json_parse_helper(thr, idx /*idx_value*/, DUK_INVALID_INDEX /*idx_reviver*/, 0 /*flags*/); duk_replace(thr, idx); DUK_ASSERT(duk_get_top(thr) == top_at_entry); } #else /* DUK_USE_JSON_SUPPORT */ DUK_EXTERNAL const char *duk_json_encode(duk_hthread *thr, duk_idx_t idx) { DUK_ASSERT_API_ENTRY(thr); DUK_UNREF(idx); DUK_ERROR_UNSUPPORTED(thr); DUK_WO_NORETURN(return NULL;); } DUK_EXTERNAL void duk_json_decode(duk_hthread *thr, duk_idx_t idx) { DUK_ASSERT_API_ENTRY(thr); DUK_UNREF(idx); DUK_ERROR_UNSUPPORTED(thr); DUK_WO_NORETURN(return;); } #endif /* DUK_USE_JSON_SUPPORT */ #line 1 "duk_api_compile.c" /* * Compilation and evaluation */ /* #include duk_internal.h -> already included */ typedef struct duk__compile_raw_args duk__compile_raw_args; struct duk__compile_raw_args { duk_size_t src_length; /* should be first on 64-bit platforms */ const duk_uint8_t *src_buffer; duk_uint_t flags; }; /* Eval is just a wrapper now. */ DUK_EXTERNAL duk_int_t duk_eval_raw(duk_hthread *thr, const char *src_buffer, duk_size_t src_length, duk_uint_t flags) { duk_int_t rc; DUK_ASSERT_API_ENTRY(thr); /* Note: strictness is *not* inherited from the current Duktape/C. * This would be confusing because the current strictness state * depends on whether we're running inside a Duktape/C activation * (= strict mode) or outside of any activation (= non-strict mode). * See tests/api/test-eval-strictness.c for more discussion. */ /* [ ... source? filename? ] (depends on flags) */ rc = duk_compile_raw(thr, src_buffer, src_length, flags | DUK_COMPILE_EVAL); /* may be safe, or non-safe depending on flags */ /* [ ... closure/error ] */ if (rc != DUK_EXEC_SUCCESS) { rc = DUK_EXEC_ERROR; goto got_rc; } duk_push_global_object(thr); /* explicit 'this' binding, see GH-164 */ if (flags & DUK_COMPILE_SAFE) { rc = duk_pcall_method(thr, 0); } else { duk_call_method(thr, 0); rc = DUK_EXEC_SUCCESS; } /* [ ... result/error ] */ got_rc: if (flags & DUK_COMPILE_NORESULT) { duk_pop(thr); } return rc; } /* Helper which can be called both directly and with duk_safe_call(). */ DUK_LOCAL duk_ret_t duk__do_compile(duk_hthread *thr, void *udata) { duk__compile_raw_args *comp_args; duk_uint_t flags; duk_hcompfunc *h_templ; DUK_CTX_ASSERT_VALID(thr); DUK_ASSERT(udata != NULL); /* Note: strictness is not inherited from the current Duktape/C * context. Otherwise it would not be possible to compile * non-strict code inside a Duktape/C activation (which is * always strict now). See tests/api/test-eval-strictness.c * for discussion. */ /* [ ... source? filename? ] (depends on flags) */ comp_args = (duk__compile_raw_args *) udata; flags = comp_args->flags; if (flags & DUK_COMPILE_NOFILENAME) { /* Automatic filename: 'eval' or 'input'. */ duk_push_hstring_stridx(thr, (flags & DUK_COMPILE_EVAL) ? DUK_STRIDX_EVAL : DUK_STRIDX_INPUT); } /* [ ... source? filename ] */ if (!comp_args->src_buffer) { duk_hstring *h_sourcecode; h_sourcecode = duk_get_hstring(thr, -2); if ((flags & DUK_COMPILE_NOSOURCE) || /* args incorrect */ (h_sourcecode == NULL)) { /* e.g. duk_push_string_file_raw() pushed undefined */ DUK_ERROR_TYPE(thr, DUK_STR_NO_SOURCECODE); DUK_WO_NORETURN(return 0;); } DUK_ASSERT(h_sourcecode != NULL); comp_args->src_buffer = (const duk_uint8_t *) DUK_HSTRING_GET_DATA(h_sourcecode); comp_args->src_length = (duk_size_t) DUK_HSTRING_GET_BYTELEN(h_sourcecode); } DUK_ASSERT(comp_args->src_buffer != NULL); if (flags & DUK_COMPILE_FUNCTION) { flags |= DUK_COMPILE_EVAL | DUK_COMPILE_FUNCEXPR; } /* [ ... source? filename ] */ duk_js_compile(thr, comp_args->src_buffer, comp_args->src_length, flags); /* [ ... source? func_template ] */ if (flags & DUK_COMPILE_NOSOURCE) { ; } else { duk_remove_m2(thr); } /* [ ... func_template ] */ h_templ = (duk_hcompfunc *) duk_known_hobject(thr, -1); duk_js_push_closure(thr, h_templ, thr->builtins[DUK_BIDX_GLOBAL_ENV], thr->builtins[DUK_BIDX_GLOBAL_ENV], 1 /*add_auto_proto*/); duk_remove_m2(thr); /* -> [ ... closure ] */ /* [ ... closure ] */ return 1; } DUK_EXTERNAL duk_int_t duk_compile_raw(duk_hthread *thr, const char *src_buffer, duk_size_t src_length, duk_uint_t flags) { duk__compile_raw_args comp_args_alloc; duk__compile_raw_args *comp_args = &comp_args_alloc; DUK_ASSERT_API_ENTRY(thr); if ((flags & DUK_COMPILE_STRLEN) && (src_buffer != NULL)) { /* String length is computed here to avoid multiple evaluation * of a macro argument in the calling side. */ src_length = DUK_STRLEN(src_buffer); } comp_args->src_buffer = (const duk_uint8_t *) src_buffer; comp_args->src_length = src_length; comp_args->flags = flags; /* [ ... source? filename? ] (depends on flags) */ if (flags & DUK_COMPILE_SAFE) { duk_int_t rc; duk_int_t nargs; duk_int_t nrets = 1; /* Arguments can be: [ source? filename? &comp_args] so that * nargs is 1 to 3. Call site encodes the correct nargs count * directly into flags. */ nargs = flags & 0x07; DUK_ASSERT(nargs == ((flags & DUK_COMPILE_NOSOURCE) ? 0 : 1) + ((flags & DUK_COMPILE_NOFILENAME) ? 0 : 1)); rc = duk_safe_call(thr, duk__do_compile, (void *) comp_args, nargs, nrets); /* [ ... closure ] */ return rc; } (void) duk__do_compile(thr, (void *) comp_args); /* [ ... closure ] */ return DUK_EXEC_SUCCESS; } #line 1 "duk_api_debug.c" /* * Debugging related API calls */ /* #include duk_internal.h -> already included */ #if defined(DUK_USE_JSON_SUPPORT) DUK_EXTERNAL void duk_push_context_dump(duk_hthread *thr) { duk_idx_t idx; duk_idx_t top; DUK_ASSERT_API_ENTRY(thr); /* We don't duk_require_stack() here now, but rely on the caller having * enough space. */ top = duk_get_top(thr); duk_push_bare_array(thr); for (idx = 0; idx < top; idx++) { duk_dup(thr, idx); duk_put_prop_index(thr, -2, (duk_uarridx_t) idx); } /* XXX: conversion errors should not propagate outwards. * Perhaps values need to be coerced individually? */ duk_bi_json_stringify_helper(thr, duk_get_top_index(thr), /*idx_value*/ DUK_INVALID_INDEX, /*idx_replacer*/ DUK_INVALID_INDEX, /*idx_space*/ DUK_JSON_FLAG_EXT_CUSTOM | DUK_JSON_FLAG_ASCII_ONLY | DUK_JSON_FLAG_AVOID_KEY_QUOTES /*flags*/); duk_push_sprintf(thr, "ctx: top=%ld, stack=%s", (long) top, (const char *) duk_safe_to_string(thr, -1)); duk_replace(thr, -3); /* [ ... arr jsonx(arr) res ] -> [ ... res jsonx(arr) ] */ duk_pop(thr); DUK_ASSERT(duk_is_string(thr, -1)); } #else /* DUK_USE_JSON_SUPPORT */ DUK_EXTERNAL void duk_push_context_dump(duk_hthread *thr) { DUK_ASSERT_API_ENTRY(thr); DUK_ERROR_UNSUPPORTED(thr); DUK_WO_NORETURN(return;); } #endif /* DUK_USE_JSON_SUPPORT */ #if defined(DUK_USE_DEBUGGER_SUPPORT) DUK_EXTERNAL void duk_debugger_attach(duk_hthread *thr, duk_debug_read_function read_cb, duk_debug_write_function write_cb, duk_debug_peek_function peek_cb, duk_debug_read_flush_function read_flush_cb, duk_debug_write_flush_function write_flush_cb, duk_debug_request_function request_cb, duk_debug_detached_function detached_cb, void *udata) { duk_heap *heap; const char *str; duk_size_t len; /* XXX: should there be an error or an automatic detach if * already attached? */ DUK_D(DUK_DPRINT("application called duk_debugger_attach()")); DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(read_cb != NULL); DUK_ASSERT(write_cb != NULL); /* Other callbacks are optional. */ heap = thr->heap; heap->dbg_read_cb = read_cb; heap->dbg_write_cb = write_cb; heap->dbg_peek_cb = peek_cb; heap->dbg_read_flush_cb = read_flush_cb; heap->dbg_write_flush_cb = write_flush_cb; heap->dbg_request_cb = request_cb; heap->dbg_detached_cb = detached_cb; heap->dbg_udata = udata; heap->dbg_have_next_byte = 0; /* Start in paused state. */ heap->dbg_processing = 0; heap->dbg_state_dirty = 0; heap->dbg_force_restart = 0; heap->dbg_pause_flags = 0; heap->dbg_pause_act = NULL; heap->dbg_pause_startline = 0; heap->dbg_exec_counter = 0; heap->dbg_last_counter = 0; heap->dbg_last_time = 0.0; duk_debug_set_paused(heap); /* XXX: overlap with fields above */ /* Send version identification and flush right afterwards. Note that * we must write raw, unframed bytes here. */ duk_push_sprintf(thr, "%ld %ld %s %s\n", (long) DUK_DEBUG_PROTOCOL_VERSION, (long) DUK_VERSION, (const char *) DUK_GIT_DESCRIBE, (const char *) DUK_USE_TARGET_INFO); str = duk_get_lstring(thr, -1, &len); DUK_ASSERT(str != NULL); duk_debug_write_bytes(thr, (const duk_uint8_t *) str, len); duk_debug_write_flush(thr); duk_pop(thr); } DUK_EXTERNAL void duk_debugger_detach(duk_hthread *thr) { DUK_D(DUK_DPRINT("application called duk_debugger_detach()")); DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(thr->heap != NULL); /* Can be called multiple times with no harm. */ duk_debug_do_detach(thr->heap); } DUK_EXTERNAL void duk_debugger_cooperate(duk_hthread *thr) { duk_bool_t processed_messages; DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(thr->heap != NULL); if (!duk_debug_is_attached(thr->heap)) { return; } if (thr->callstack_curr != NULL || thr->heap->dbg_processing) { /* Calling duk_debugger_cooperate() while Duktape is being * called into is not supported. This is not a 100% check * but prevents any damage in most cases. */ return; } processed_messages = duk_debug_process_messages(thr, 1 /*no_block*/); DUK_UNREF(processed_messages); } DUK_EXTERNAL duk_bool_t duk_debugger_notify(duk_hthread *thr, duk_idx_t nvalues) { duk_idx_t top; duk_idx_t idx; duk_bool_t ret = 0; DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(thr->heap != NULL); DUK_D(DUK_DPRINT("application called duk_debugger_notify() with nvalues=%ld", (long) nvalues)); top = duk_get_top(thr); if (top < nvalues) { DUK_ERROR_RANGE(thr, "not enough stack values for notify"); DUK_WO_NORETURN(return 0;); } if (duk_debug_is_attached(thr->heap)) { duk_debug_write_notify(thr, DUK_DBG_CMD_APPNOTIFY); for (idx = top - nvalues; idx < top; idx++) { duk_tval *tv = DUK_GET_TVAL_POSIDX(thr, idx); duk_debug_write_tval(thr, tv); } duk_debug_write_eom(thr); /* Return non-zero (true) if we have a good reason to believe * the notify was delivered; if we're still attached at least * a transport error was not indicated by the transport write * callback. This is not a 100% guarantee of course. */ if (duk_debug_is_attached(thr->heap)) { ret = 1; } } duk_pop_n(thr, nvalues); return ret; } DUK_EXTERNAL void duk_debugger_pause(duk_hthread *thr) { DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(thr->heap != NULL); DUK_D(DUK_DPRINT("application called duk_debugger_pause()")); /* Treat like a debugger statement: ignore when not attached. */ if (duk_debug_is_attached(thr->heap)) { if (duk_debug_is_paused(thr->heap)) { DUK_D(DUK_DPRINT("duk_debugger_pause() called when already paused; ignoring")); } else { duk_debug_set_paused(thr->heap); /* Pause on the next opcode executed. This is always safe to do even * inside the debugger message loop: the interrupt counter will be reset * to its proper value when the message loop exits. */ thr->interrupt_init = 1; thr->interrupt_counter = 0; } } } #else /* DUK_USE_DEBUGGER_SUPPORT */ DUK_EXTERNAL void duk_debugger_attach(duk_hthread *thr, duk_debug_read_function read_cb, duk_debug_write_function write_cb, duk_debug_peek_function peek_cb, duk_debug_read_flush_function read_flush_cb, duk_debug_write_flush_function write_flush_cb, duk_debug_request_function request_cb, duk_debug_detached_function detached_cb, void *udata) { DUK_ASSERT_API_ENTRY(thr); DUK_UNREF(read_cb); DUK_UNREF(write_cb); DUK_UNREF(peek_cb); DUK_UNREF(read_flush_cb); DUK_UNREF(write_flush_cb); DUK_UNREF(request_cb); DUK_UNREF(detached_cb); DUK_UNREF(udata); DUK_ERROR_TYPE(thr, "no debugger support"); DUK_WO_NORETURN(return;); } DUK_EXTERNAL void duk_debugger_detach(duk_hthread *thr) { DUK_ASSERT_API_ENTRY(thr); DUK_ERROR_TYPE(thr, "no debugger support"); DUK_WO_NORETURN(return;); } DUK_EXTERNAL void duk_debugger_cooperate(duk_hthread *thr) { /* nop */ DUK_ASSERT_API_ENTRY(thr); DUK_UNREF(thr); } DUK_EXTERNAL duk_bool_t duk_debugger_notify(duk_hthread *thr, duk_idx_t nvalues) { duk_idx_t top; DUK_ASSERT_API_ENTRY(thr); top = duk_get_top(thr); if (top < nvalues) { DUK_ERROR_RANGE_INVALID_COUNT(thr); DUK_WO_NORETURN(return 0;); } /* No debugger support, just pop values. */ duk_pop_n(thr, nvalues); return 0; } DUK_EXTERNAL void duk_debugger_pause(duk_hthread *thr) { /* Treat like debugger statement: nop */ DUK_ASSERT_API_ENTRY(thr); DUK_UNREF(thr); } #endif /* DUK_USE_DEBUGGER_SUPPORT */ #line 1 "duk_api_heap.c" /* * Heap creation and destruction */ /* #include duk_internal.h -> already included */ typedef struct duk_internal_thread_state duk_internal_thread_state; struct duk_internal_thread_state { duk_ljstate lj; duk_bool_t creating_error; duk_hthread *curr_thread; duk_uint8_t thread_state; duk_int_t call_recursion_depth; }; DUK_EXTERNAL duk_hthread *duk_create_heap(duk_alloc_function alloc_func, duk_realloc_function realloc_func, duk_free_function free_func, void *heap_udata, duk_fatal_function fatal_handler) { duk_heap *heap = NULL; duk_hthread *thr; /* Assume that either all memory funcs are NULL or non-NULL, mixed * cases will now be unsafe. */ /* XXX: just assert non-NULL values here and make caller arguments * do the defaulting to the default implementations (smaller code)? */ if (!alloc_func) { DUK_ASSERT(realloc_func == NULL); DUK_ASSERT(free_func == NULL); #if defined(DUK_USE_PROVIDE_DEFAULT_ALLOC_FUNCTIONS) alloc_func = duk_default_alloc_function; realloc_func = duk_default_realloc_function; free_func = duk_default_free_function; #else DUK_D(DUK_DPRINT("no allocation functions given and no default providers")); return NULL; #endif } else { DUK_ASSERT(realloc_func != NULL); DUK_ASSERT(free_func != NULL); } if (!fatal_handler) { fatal_handler = duk_default_fatal_handler; } DUK_ASSERT(alloc_func != NULL); DUK_ASSERT(realloc_func != NULL); DUK_ASSERT(free_func != NULL); DUK_ASSERT(fatal_handler != NULL); heap = duk_heap_alloc(alloc_func, realloc_func, free_func, heap_udata, fatal_handler); if (!heap) { return NULL; } thr = heap->heap_thread; DUK_ASSERT(thr != NULL); DUK_ASSERT(thr->heap != NULL); return thr; } DUK_EXTERNAL void duk_destroy_heap(duk_hthread *thr) { duk_heap *heap; if (!thr) { return; } DUK_ASSERT_API_ENTRY(thr); heap = thr->heap; DUK_ASSERT(heap != NULL); duk_heap_free(heap); } DUK_EXTERNAL void duk_suspend(duk_hthread *thr, duk_thread_state *state) { duk_internal_thread_state *snapshot = (duk_internal_thread_state *) (void *) state; duk_heap *heap; duk_ljstate *lj; DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(thr->heap != NULL); DUK_ASSERT(state != NULL); /* unvalidated */ /* Currently not supported when called from within a finalizer. * If that is done, the finalizer will remain running indefinitely, * preventing other finalizers from executing. The assert is a bit * wider, checking that it would be OK to run pending finalizers. */ DUK_ASSERT(thr->heap->pf_prevent_count == 0); /* Currently not supported to duk_suspend() from an errCreate() * call. */ DUK_ASSERT(thr->heap->creating_error == 0); heap = thr->heap; lj = &heap->lj; duk_push_tval(thr, &lj->value1); duk_push_tval(thr, &lj->value2); /* XXX: creating_error == 0 is asserted above, so no need to store. */ duk_memcpy((void *) &snapshot->lj, (const void *) lj, sizeof(duk_ljstate)); snapshot->creating_error = heap->creating_error; snapshot->curr_thread = heap->curr_thread; snapshot->thread_state = thr->state; snapshot->call_recursion_depth = heap->call_recursion_depth; lj->jmpbuf_ptr = NULL; lj->type = DUK_LJ_TYPE_UNKNOWN; DUK_TVAL_SET_UNDEFINED(&lj->value1); DUK_TVAL_SET_UNDEFINED(&lj->value2); heap->creating_error = 0; heap->curr_thread = NULL; heap->call_recursion_depth = 0; thr->state = DUK_HTHREAD_STATE_INACTIVE; } DUK_EXTERNAL void duk_resume(duk_hthread *thr, const duk_thread_state *state) { const duk_internal_thread_state *snapshot = (const duk_internal_thread_state *) (const void *) state; duk_heap *heap; DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(thr->heap != NULL); DUK_ASSERT(state != NULL); /* unvalidated */ /* Shouldn't be necessary if duk_suspend() is called before * duk_resume(), but assert in case API sequence is incorrect. */ DUK_ASSERT(thr->heap->pf_prevent_count == 0); DUK_ASSERT(thr->heap->creating_error == 0); thr->state = snapshot->thread_state; heap = thr->heap; duk_memcpy((void *) &heap->lj, (const void *) &snapshot->lj, sizeof(duk_ljstate)); heap->creating_error = snapshot->creating_error; heap->curr_thread = snapshot->curr_thread; heap->call_recursion_depth = snapshot->call_recursion_depth; duk_pop_2(thr); } /* XXX: better place for this */ DUK_EXTERNAL void duk_set_global_object(duk_hthread *thr) { duk_hobject *h_glob; duk_hobject *h_prev_glob; duk_hobjenv *h_env; duk_hobject *h_prev_env; DUK_ASSERT_API_ENTRY(thr); DUK_D(DUK_DPRINT("replace global object with: %!T", duk_get_tval(thr, -1))); h_glob = duk_require_hobject(thr, -1); DUK_ASSERT(h_glob != NULL); /* * Replace global object. */ h_prev_glob = thr->builtins[DUK_BIDX_GLOBAL]; DUK_UNREF(h_prev_glob); thr->builtins[DUK_BIDX_GLOBAL] = h_glob; DUK_HOBJECT_INCREF(thr, h_glob); DUK_HOBJECT_DECREF_ALLOWNULL(thr, h_prev_glob); /* side effects, in theory (referenced by global env) */ /* * Replace lexical environment for global scope * * Create a new object environment for the global lexical scope. * We can't just reset the _Target property of the current one, * because the lexical scope is shared by other threads with the * same (initial) built-ins. */ h_env = duk_hobjenv_alloc(thr, DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJENV)); DUK_ASSERT(h_env != NULL); DUK_ASSERT(DUK_HOBJECT_GET_PROTOTYPE(thr->heap, (duk_hobject *) h_env) == NULL); DUK_ASSERT(h_env->target == NULL); DUK_ASSERT(h_glob != NULL); h_env->target = h_glob; DUK_HOBJECT_INCREF(thr, h_glob); DUK_ASSERT(h_env->has_this == 0); /* [ ... new_glob ] */ h_prev_env = thr->builtins[DUK_BIDX_GLOBAL_ENV]; thr->builtins[DUK_BIDX_GLOBAL_ENV] = (duk_hobject *) h_env; DUK_HOBJECT_INCREF(thr, (duk_hobject *) h_env); DUK_HOBJECT_DECREF_ALLOWNULL(thr, h_prev_env); /* side effects */ DUK_UNREF(h_env); /* without refcounts */ DUK_UNREF(h_prev_env); /* [ ... new_glob ] */ duk_pop(thr); /* [ ... ] */ } #line 1 "duk_api_inspect.c" /* * Inspection */ /* #include duk_internal.h -> already included */ /* For footprint efficient multiple value setting: arrays are much better than * varargs, format string with parsing is often better than string pointer arrays. */ DUK_LOCAL void duk__inspect_multiple_uint(duk_hthread *thr, const char *fmt, duk_int_t *vals) { duk_int_t val; const char *p; const char *p_curr; duk_size_t len; for (p = fmt;;) { len = DUK_STRLEN(p); p_curr = p; p += len + 1; if (len == 0) { /* Double NUL (= empty key) terminates. */ break; } val = *vals++; if (val >= 0) { /* Negative values are markers to skip key. */ duk_push_string(thr, p_curr); duk_push_int(thr, val); duk_put_prop(thr, -3); } } } /* Raw helper to extract internal information / statistics about a value. * The return value is an object with properties that are version specific. * The properties must not expose anything that would lead to security * issues (e.g. exposing compiled function 'data' buffer might be an issue). * Currently only counts and sizes and such are given so there shouldn't * be security implications. */ #define DUK__IDX_TYPE 0 #define DUK__IDX_ITAG 1 #define DUK__IDX_REFC 2 #define DUK__IDX_HBYTES 3 #define DUK__IDX_CLASS 4 #define DUK__IDX_PBYTES 5 #define DUK__IDX_ESIZE 6 #define DUK__IDX_ENEXT 7 #define DUK__IDX_ASIZE 8 #define DUK__IDX_HSIZE 9 #define DUK__IDX_BCBYTES 10 #define DUK__IDX_DBYTES 11 #define DUK__IDX_TSTATE 12 #define DUK__IDX_VARIANT 13 DUK_EXTERNAL void duk_inspect_value(duk_hthread *thr, duk_idx_t idx) { duk_tval *tv; duk_heaphdr *h; /* The temporary values should be in an array rather than individual * variables which (in practice) ensures that the compiler won't map * them to registers and emit a lot of unnecessary shuffling code. */ duk_int_t vals[14]; DUK_ASSERT_API_ENTRY(thr); /* Assume two's complement and set everything to -1. */ duk_memset((void *) &vals, (int) 0xff, sizeof(vals)); DUK_ASSERT(vals[DUK__IDX_TYPE] == -1); /* spot check one */ tv = duk_get_tval_or_unused(thr, idx); h = (DUK_TVAL_IS_HEAP_ALLOCATED(tv) ? DUK_TVAL_GET_HEAPHDR(tv) : NULL); vals[DUK__IDX_TYPE] = duk_get_type_tval(tv); vals[DUK__IDX_ITAG] = (duk_int_t) DUK_TVAL_GET_TAG(tv); duk_push_bare_object(thr); /* Invalidates 'tv'. */ tv = NULL; if (h == NULL) { goto finish; } duk_push_pointer(thr, (void *) h); duk_put_prop_literal(thr, -2, "hptr"); #if 0 /* Covers a lot of information, e.g. buffer and string variants. */ duk_push_uint(thr, (duk_uint_t) DUK_HEAPHDR_GET_FLAGS(h)); duk_put_prop_literal(thr, -2, "hflags"); #endif #if defined(DUK_USE_REFERENCE_COUNTING) vals[DUK__IDX_REFC] = (duk_int_t) DUK_HEAPHDR_GET_REFCOUNT(h); #endif vals[DUK__IDX_VARIANT] = 0; /* Heaphdr size and additional allocation size, followed by * type specific stuff (with varying value count). */ switch ((duk_small_int_t) DUK_HEAPHDR_GET_TYPE(h)) { case DUK_HTYPE_STRING: { duk_hstring *h_str = (duk_hstring *) h; vals[DUK__IDX_HBYTES] = (duk_int_t) (sizeof(duk_hstring) + DUK_HSTRING_GET_BYTELEN(h_str) + 1); #if defined(DUK_USE_HSTRING_EXTDATA) if (DUK_HSTRING_HAS_EXTDATA(h_str)) { vals[DUK__IDX_VARIANT] = 1; } #endif break; } case DUK_HTYPE_OBJECT: { duk_hobject *h_obj = (duk_hobject *) h; /* XXX: variants here are maybe pointless; class is enough? */ if (DUK_HOBJECT_IS_ARRAY(h_obj)) { vals[DUK__IDX_HBYTES] = sizeof(duk_harray); } else if (DUK_HOBJECT_IS_COMPFUNC(h_obj)) { vals[DUK__IDX_HBYTES] = sizeof(duk_hcompfunc); } else if (DUK_HOBJECT_IS_NATFUNC(h_obj)) { vals[DUK__IDX_HBYTES] = sizeof(duk_hnatfunc); } else if (DUK_HOBJECT_IS_THREAD(h_obj)) { vals[DUK__IDX_HBYTES] = sizeof(duk_hthread); vals[DUK__IDX_TSTATE] = ((duk_hthread *) h_obj)->state; #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) } else if (DUK_HOBJECT_IS_BUFOBJ(h_obj)) { vals[DUK__IDX_HBYTES] = sizeof(duk_hbufobj); /* XXX: some size information */ #endif } else { vals[DUK__IDX_HBYTES] = (duk_small_uint_t) sizeof(duk_hobject); } vals[DUK__IDX_CLASS] = (duk_int_t) DUK_HOBJECT_GET_CLASS_NUMBER(h_obj); vals[DUK__IDX_PBYTES] = (duk_int_t) DUK_HOBJECT_P_ALLOC_SIZE(h_obj); vals[DUK__IDX_ESIZE] = (duk_int_t) DUK_HOBJECT_GET_ESIZE(h_obj); vals[DUK__IDX_ENEXT] = (duk_int_t) DUK_HOBJECT_GET_ENEXT(h_obj); vals[DUK__IDX_ASIZE] = (duk_int_t) DUK_HOBJECT_GET_ASIZE(h_obj); vals[DUK__IDX_HSIZE] = (duk_int_t) DUK_HOBJECT_GET_HSIZE(h_obj); /* Note: e_next indicates the number of gc-reachable entries * in the entry part, and also indicates the index where the * next new property would be inserted. It does *not* indicate * the number of non-NULL keys present in the object. That * value could be counted separately but requires a pass through * the key list. */ if (DUK_HOBJECT_IS_COMPFUNC(h_obj)) { duk_hbuffer *h_data = (duk_hbuffer *) DUK_HCOMPFUNC_GET_DATA(thr->heap, (duk_hcompfunc *) h_obj); vals[DUK__IDX_BCBYTES] = (duk_int_t) (h_data ? DUK_HBUFFER_GET_SIZE(h_data) : 0); } break; } case DUK_HTYPE_BUFFER: { duk_hbuffer *h_buf = (duk_hbuffer *) h; if (DUK_HBUFFER_HAS_DYNAMIC(h_buf)) { if (DUK_HBUFFER_HAS_EXTERNAL(h_buf)) { vals[DUK__IDX_VARIANT] = 2; /* buffer variant 2: external */ vals[DUK__IDX_HBYTES] = (duk_uint_t) (sizeof(duk_hbuffer_external)); } else { /* When alloc_size == 0 the second allocation may not * actually exist. */ vals[DUK__IDX_VARIANT] = 1; /* buffer variant 1: dynamic */ vals[DUK__IDX_HBYTES] = (duk_uint_t) (sizeof(duk_hbuffer_dynamic)); } vals[DUK__IDX_DBYTES] = (duk_int_t) (DUK_HBUFFER_GET_SIZE(h_buf)); } else { DUK_ASSERT(vals[DUK__IDX_VARIANT] == 0); /* buffer variant 0: fixed */ vals[DUK__IDX_HBYTES] = (duk_int_t) (sizeof(duk_hbuffer_fixed) + DUK_HBUFFER_GET_SIZE(h_buf)); } break; } } finish: duk__inspect_multiple_uint(thr, "type" "\x00" "itag" "\x00" "refc" "\x00" "hbytes" "\x00" "class" "\x00" "pbytes" "\x00" "esize" "\x00" "enext" "\x00" "asize" "\x00" "hsize" "\x00" "bcbytes" "\x00" "dbytes" "\x00" "tstate" "\x00" "variant" "\x00" "\x00", (duk_int_t *) &vals); } DUK_EXTERNAL void duk_inspect_callstack_entry(duk_hthread *thr, duk_int_t level) { duk_activation *act; duk_uint_fast32_t pc; duk_uint_fast32_t line; DUK_ASSERT_API_ENTRY(thr); /* -1 = top callstack entry * -2 = caller of level -1 * etc */ act = duk_hthread_get_activation_for_level(thr, level); if (act == NULL) { duk_push_undefined(thr); return; } duk_push_bare_object(thr); /* Relevant PC is just before current one because PC is * post-incremented. This should match what error augment * code does. */ pc = duk_hthread_get_act_prev_pc(thr, act); duk_push_tval(thr, &act->tv_func); duk_push_uint(thr, (duk_uint_t) pc); duk_put_prop_stridx_short(thr, -3, DUK_STRIDX_PC); #if defined(DUK_USE_PC2LINE) line = duk_hobject_pc2line_query(thr, -1, pc); #else line = 0; #endif duk_push_uint(thr, (duk_uint_t) line); duk_put_prop_stridx_short(thr, -3, DUK_STRIDX_LINE_NUMBER); duk_put_prop_stridx_short(thr, -2, DUK_STRIDX_LC_FUNCTION); /* Providing access to e.g. act->lex_env would be dangerous: these * internal structures must never be accessible to the application. * Duktape relies on them having consistent data, and this consistency * is only asserted for, not checked for. */ } /* automatic undefs */ #undef DUK__IDX_ASIZE #undef DUK__IDX_BCBYTES #undef DUK__IDX_CLASS #undef DUK__IDX_DBYTES #undef DUK__IDX_ENEXT #undef DUK__IDX_ESIZE #undef DUK__IDX_HBYTES #undef DUK__IDX_HSIZE #undef DUK__IDX_ITAG #undef DUK__IDX_PBYTES #undef DUK__IDX_REFC #undef DUK__IDX_TSTATE #undef DUK__IDX_TYPE #undef DUK__IDX_VARIANT #line 1 "duk_api_memory.c" /* * Memory calls. */ /* #include duk_internal.h -> already included */ DUK_EXTERNAL void *duk_alloc_raw(duk_hthread *thr, duk_size_t size) { DUK_ASSERT_API_ENTRY(thr); return DUK_ALLOC_RAW(thr->heap, size); } DUK_EXTERNAL void duk_free_raw(duk_hthread *thr, void *ptr) { DUK_ASSERT_API_ENTRY(thr); DUK_FREE_RAW(thr->heap, ptr); } DUK_EXTERNAL void *duk_realloc_raw(duk_hthread *thr, void *ptr, duk_size_t size) { DUK_ASSERT_API_ENTRY(thr); return DUK_REALLOC_RAW(thr->heap, ptr, size); } DUK_EXTERNAL void *duk_alloc(duk_hthread *thr, duk_size_t size) { DUK_ASSERT_API_ENTRY(thr); return DUK_ALLOC(thr->heap, size); } DUK_EXTERNAL void duk_free(duk_hthread *thr, void *ptr) { DUK_ASSERT_API_ENTRY(thr); DUK_FREE_CHECKED(thr, ptr); } DUK_EXTERNAL void *duk_realloc(duk_hthread *thr, void *ptr, duk_size_t size) { DUK_ASSERT_API_ENTRY(thr); /* * Note: since this is an exposed API call, there should be * no way a mark-and-sweep could have a side effect on the * memory allocation behind 'ptr'; the pointer should never * be something that Duktape wants to change. * * Thus, no need to use DUK_REALLOC_INDIRECT (and we don't * have the storage location here anyway). */ return DUK_REALLOC(thr->heap, ptr, size); } DUK_EXTERNAL void duk_get_memory_functions(duk_hthread *thr, duk_memory_functions *out_funcs) { duk_heap *heap; DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(out_funcs != NULL); DUK_ASSERT(thr != NULL); DUK_ASSERT(thr->heap != NULL); heap = thr->heap; out_funcs->alloc_func = heap->alloc_func; out_funcs->realloc_func = heap->realloc_func; out_funcs->free_func = heap->free_func; out_funcs->udata = heap->heap_udata; } DUK_EXTERNAL void duk_gc(duk_hthread *thr, duk_uint_t flags) { duk_heap *heap; duk_small_uint_t ms_flags; DUK_ASSERT_API_ENTRY(thr); heap = thr->heap; DUK_ASSERT(heap != NULL); DUK_D(DUK_DPRINT("mark-and-sweep requested by application")); DUK_ASSERT(DUK_GC_COMPACT == DUK_MS_FLAG_EMERGENCY); /* Compact flag is 1:1 with emergency flag which forces compaction. */ ms_flags = (duk_small_uint_t) flags; duk_heap_mark_and_sweep(heap, ms_flags); } #line 1 "duk_api_object.c" /* * Object handling: property access and other support functions. */ /* #include duk_internal.h -> already included */ /* * Property handling * * The API exposes only the most common property handling functions. * The caller can invoke ECMAScript built-ins for full control (e.g. * defineProperty, getOwnPropertyDescriptor). */ DUK_EXTERNAL duk_bool_t duk_get_prop(duk_hthread *thr, duk_idx_t obj_idx) { duk_tval *tv_obj; duk_tval *tv_key; duk_bool_t rc; DUK_ASSERT_API_ENTRY(thr); /* Note: copying tv_obj and tv_key to locals to shield against a valstack * resize is not necessary for a property get right now. */ tv_obj = duk_require_tval(thr, obj_idx); tv_key = duk_require_tval(thr, -1); rc = duk_hobject_getprop(thr, tv_obj, tv_key); DUK_ASSERT(rc == 0 || rc == 1); /* a value is left on stack regardless of rc */ duk_remove_m2(thr); /* remove key */ DUK_ASSERT(duk_is_undefined(thr, -1) || rc == 1); return rc; /* 1 if property found, 0 otherwise */ } DUK_EXTERNAL duk_bool_t duk_get_prop_string(duk_hthread *thr, duk_idx_t obj_idx, const char *key) { DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(key != NULL); obj_idx = duk_require_normalize_index(thr, obj_idx); (void) duk_push_string(thr, key); return duk_get_prop(thr, obj_idx); } DUK_EXTERNAL duk_bool_t duk_get_prop_lstring(duk_hthread *thr, duk_idx_t obj_idx, const char *key, duk_size_t key_len) { DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(key != NULL); obj_idx = duk_require_normalize_index(thr, obj_idx); (void) duk_push_lstring(thr, key, key_len); return duk_get_prop(thr, obj_idx); } #if !defined(DUK_USE_PREFER_SIZE) DUK_EXTERNAL duk_bool_t duk_get_prop_literal_raw(duk_hthread *thr, duk_idx_t obj_idx, const char *key, duk_size_t key_len) { DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(key != NULL); DUK_ASSERT(key[key_len] == (char) 0); obj_idx = duk_require_normalize_index(thr, obj_idx); (void) duk_push_literal_raw(thr, key, key_len); return duk_get_prop(thr, obj_idx); } #endif DUK_EXTERNAL duk_bool_t duk_get_prop_index(duk_hthread *thr, duk_idx_t obj_idx, duk_uarridx_t arr_idx) { DUK_ASSERT_API_ENTRY(thr); obj_idx = duk_require_normalize_index(thr, obj_idx); duk_push_uarridx(thr, arr_idx); return duk_get_prop(thr, obj_idx); } DUK_EXTERNAL duk_bool_t duk_get_prop_heapptr(duk_hthread *thr, duk_idx_t obj_idx, void *ptr) { DUK_ASSERT_API_ENTRY(thr); obj_idx = duk_require_normalize_index(thr, obj_idx); (void) duk_push_heapptr(thr, ptr); /* NULL -> 'undefined' */ return duk_get_prop(thr, obj_idx); } DUK_INTERNAL duk_bool_t duk_get_prop_stridx(duk_hthread *thr, duk_idx_t obj_idx, duk_small_uint_t stridx) { DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT_STRIDX_VALID(stridx); obj_idx = duk_require_normalize_index(thr, obj_idx); (void) duk_push_hstring(thr, DUK_HTHREAD_GET_STRING(thr, stridx)); return duk_get_prop(thr, obj_idx); } DUK_INTERNAL duk_bool_t duk_get_prop_stridx_short_raw(duk_hthread *thr, duk_uint_t packed_args) { return duk_get_prop_stridx(thr, (duk_idx_t) (duk_int16_t) (packed_args >> 16), (duk_small_uint_t) (packed_args & 0xffffUL)); } DUK_INTERNAL duk_bool_t duk_get_prop_stridx_boolean(duk_hthread *thr, duk_idx_t obj_idx, duk_small_uint_t stridx, duk_bool_t *out_has_prop) { duk_bool_t rc; DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT_STRIDX_VALID(stridx); rc = duk_get_prop_stridx(thr, obj_idx, stridx); if (out_has_prop) { *out_has_prop = rc; } return duk_to_boolean_top_pop(thr); } /* This get variant is for internal use, it differs from standard * duk_get_prop() in that: * - Object argument must be an object (primitive values not supported). * - Key argument must be a string (no coercion). * - Only own properties are checked (no inheritance). Only "entry part" * properties are checked (not array index properties). * - Property must be a plain data property, not a getter. * - Proxy traps are not triggered. */ DUK_INTERNAL duk_bool_t duk_xget_owndataprop(duk_hthread *thr, duk_idx_t obj_idx) { duk_hobject *h_obj; duk_hstring *h_key; duk_tval *tv_val; DUK_ASSERT_API_ENTRY(thr); /* Note: copying tv_obj and tv_key to locals to shield against a valstack * resize is not necessary for a property get right now. */ h_obj = duk_get_hobject(thr, obj_idx); if (h_obj == NULL) { return 0; } h_key = duk_require_hstring(thr, -1); tv_val = duk_hobject_find_entry_tval_ptr(thr->heap, h_obj, h_key); if (tv_val == NULL) { return 0; } duk_push_tval(thr, tv_val); duk_remove_m2(thr); /* remove key */ return 1; } DUK_INTERNAL duk_bool_t duk_xget_owndataprop_stridx(duk_hthread *thr, duk_idx_t obj_idx, duk_small_uint_t stridx) { DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT_STRIDX_VALID(stridx); obj_idx = duk_require_normalize_index(thr, obj_idx); (void) duk_push_hstring(thr, DUK_HTHREAD_GET_STRING(thr, stridx)); return duk_xget_owndataprop(thr, obj_idx); } DUK_INTERNAL duk_bool_t duk_xget_owndataprop_stridx_short_raw(duk_hthread *thr, duk_uint_t packed_args) { return duk_xget_owndataprop_stridx(thr, (duk_idx_t) (duk_int16_t) (packed_args >> 16), (duk_small_uint_t) (packed_args & 0xffffUL)); } DUK_LOCAL duk_bool_t duk__put_prop_shared(duk_hthread *thr, duk_idx_t obj_idx, duk_idx_t idx_key) { duk_tval *tv_obj; duk_tval *tv_key; duk_tval *tv_val; duk_bool_t throw_flag; duk_bool_t rc; /* Note: copying tv_obj and tv_key to locals to shield against a valstack * resize is not necessary for a property put right now (putprop protects * against it internally). */ /* Key and value indices are either (-2, -1) or (-1, -2). Given idx_key, * idx_val is always (idx_key ^ 0x01). */ DUK_ASSERT((idx_key == -2 && (idx_key ^ 1) == -1) || (idx_key == -1 && (idx_key ^ 1) == -2)); /* XXX: Direct access; faster validation. */ tv_obj = duk_require_tval(thr, obj_idx); tv_key = duk_require_tval(thr, idx_key); tv_val = duk_require_tval(thr, idx_key ^ 1); throw_flag = duk_is_strict_call(thr); rc = duk_hobject_putprop(thr, tv_obj, tv_key, tv_val, throw_flag); DUK_ASSERT(rc == 0 || rc == 1); duk_pop_2(thr); /* remove key and value */ return rc; /* 1 if property found, 0 otherwise */ } DUK_EXTERNAL duk_bool_t duk_put_prop(duk_hthread *thr, duk_idx_t obj_idx) { DUK_ASSERT_API_ENTRY(thr); return duk__put_prop_shared(thr, obj_idx, -2); } DUK_EXTERNAL duk_bool_t duk_put_prop_string(duk_hthread *thr, duk_idx_t obj_idx, const char *key) { DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(key != NULL); /* Careful here and with other duk_put_prop_xxx() helpers: the * target object and the property value may be in the same value * stack slot (unusual, but still conceptually clear). */ obj_idx = duk_normalize_index(thr, obj_idx); (void) duk_push_string(thr, key); return duk__put_prop_shared(thr, obj_idx, -1); } DUK_EXTERNAL duk_bool_t duk_put_prop_lstring(duk_hthread *thr, duk_idx_t obj_idx, const char *key, duk_size_t key_len) { DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(key != NULL); obj_idx = duk_normalize_index(thr, obj_idx); (void) duk_push_lstring(thr, key, key_len); return duk__put_prop_shared(thr, obj_idx, -1); } #if !defined(DUK_USE_PREFER_SIZE) DUK_EXTERNAL duk_bool_t duk_put_prop_literal_raw(duk_hthread *thr, duk_idx_t obj_idx, const char *key, duk_size_t key_len) { DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(key != NULL); DUK_ASSERT(key[key_len] == (char) 0); obj_idx = duk_normalize_index(thr, obj_idx); (void) duk_push_literal_raw(thr, key, key_len); return duk__put_prop_shared(thr, obj_idx, -1); } #endif DUK_EXTERNAL duk_bool_t duk_put_prop_index(duk_hthread *thr, duk_idx_t obj_idx, duk_uarridx_t arr_idx) { DUK_ASSERT_API_ENTRY(thr); obj_idx = duk_require_normalize_index(thr, obj_idx); duk_push_uarridx(thr, arr_idx); return duk__put_prop_shared(thr, obj_idx, -1); } DUK_EXTERNAL duk_bool_t duk_put_prop_heapptr(duk_hthread *thr, duk_idx_t obj_idx, void *ptr) { DUK_ASSERT_API_ENTRY(thr); obj_idx = duk_require_normalize_index(thr, obj_idx); (void) duk_push_heapptr(thr, ptr); /* NULL -> 'undefined' */ return duk__put_prop_shared(thr, obj_idx, -1); } DUK_INTERNAL duk_bool_t duk_put_prop_stridx(duk_hthread *thr, duk_idx_t obj_idx, duk_small_uint_t stridx) { DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT_STRIDX_VALID(stridx); obj_idx = duk_require_normalize_index(thr, obj_idx); duk_push_hstring(thr, DUK_HTHREAD_GET_STRING(thr, stridx)); return duk__put_prop_shared(thr, obj_idx, -1); } DUK_INTERNAL duk_bool_t duk_put_prop_stridx_short_raw(duk_hthread *thr, duk_uint_t packed_args) { return duk_put_prop_stridx(thr, (duk_idx_t) (duk_int16_t) (packed_args >> 16), (duk_small_uint_t) (packed_args & 0xffffUL)); } DUK_EXTERNAL duk_bool_t duk_del_prop(duk_hthread *thr, duk_idx_t obj_idx) { duk_tval *tv_obj; duk_tval *tv_key; duk_bool_t throw_flag; duk_bool_t rc; DUK_ASSERT_API_ENTRY(thr); /* Note: copying tv_obj and tv_key to locals to shield against a valstack * resize is not necessary for a property delete right now. */ tv_obj = duk_require_tval(thr, obj_idx); tv_key = duk_require_tval(thr, -1); throw_flag = duk_is_strict_call(thr); rc = duk_hobject_delprop(thr, tv_obj, tv_key, throw_flag); DUK_ASSERT(rc == 0 || rc == 1); duk_pop(thr); /* remove key */ return rc; } DUK_EXTERNAL duk_bool_t duk_del_prop_string(duk_hthread *thr, duk_idx_t obj_idx, const char *key) { DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(key != NULL); obj_idx = duk_require_normalize_index(thr, obj_idx); (void) duk_push_string(thr, key); return duk_del_prop(thr, obj_idx); } DUK_EXTERNAL duk_bool_t duk_del_prop_lstring(duk_hthread *thr, duk_idx_t obj_idx, const char *key, duk_size_t key_len) { DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(key != NULL); obj_idx = duk_require_normalize_index(thr, obj_idx); (void) duk_push_lstring(thr, key, key_len); return duk_del_prop(thr, obj_idx); } #if !defined(DUK_USE_PREFER_SIZE) DUK_EXTERNAL duk_bool_t duk_del_prop_literal_raw(duk_hthread *thr, duk_idx_t obj_idx, const char *key, duk_size_t key_len) { DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(key != NULL); DUK_ASSERT(key[key_len] == (char) 0); obj_idx = duk_require_normalize_index(thr, obj_idx); (void) duk_push_literal_raw(thr, key, key_len); return duk_del_prop(thr, obj_idx); } #endif DUK_EXTERNAL duk_bool_t duk_del_prop_index(duk_hthread *thr, duk_idx_t obj_idx, duk_uarridx_t arr_idx) { DUK_ASSERT_API_ENTRY(thr); obj_idx = duk_require_normalize_index(thr, obj_idx); duk_push_uarridx(thr, arr_idx); return duk_del_prop(thr, obj_idx); } DUK_EXTERNAL duk_bool_t duk_del_prop_heapptr(duk_hthread *thr, duk_idx_t obj_idx, void *ptr) { DUK_ASSERT_API_ENTRY(thr); obj_idx = duk_require_normalize_index(thr, obj_idx); (void) duk_push_heapptr(thr, ptr); /* NULL -> 'undefined' */ return duk_del_prop(thr, obj_idx); } DUK_INTERNAL duk_bool_t duk_del_prop_stridx(duk_hthread *thr, duk_idx_t obj_idx, duk_small_uint_t stridx) { DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT_STRIDX_VALID(stridx); obj_idx = duk_require_normalize_index(thr, obj_idx); duk_push_hstring(thr, DUK_HTHREAD_GET_STRING(thr, stridx)); return duk_del_prop(thr, obj_idx); } #if 0 DUK_INTERNAL duk_bool_t duk_del_prop_stridx_short_raw(duk_hthread *thr, duk_uint_t packed_args) { return duk_del_prop_stridx(thr, (duk_idx_t) (duk_int16_t) (packed_args >> 16), (duk_small_uint_t) (packed_args & 0xffffUL)); } #endif DUK_EXTERNAL duk_bool_t duk_has_prop(duk_hthread *thr, duk_idx_t obj_idx) { duk_tval *tv_obj; duk_tval *tv_key; duk_bool_t rc; DUK_ASSERT_API_ENTRY(thr); /* Note: copying tv_obj and tv_key to locals to shield against a valstack * resize is not necessary for a property existence check right now. */ tv_obj = duk_require_tval(thr, obj_idx); tv_key = duk_require_tval(thr, -1); rc = duk_hobject_hasprop(thr, tv_obj, tv_key); DUK_ASSERT(rc == 0 || rc == 1); duk_pop(thr); /* remove key */ return rc; /* 1 if property found, 0 otherwise */ } DUK_EXTERNAL duk_bool_t duk_has_prop_string(duk_hthread *thr, duk_idx_t obj_idx, const char *key) { DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(key != NULL); obj_idx = duk_require_normalize_index(thr, obj_idx); (void) duk_push_string(thr, key); return duk_has_prop(thr, obj_idx); } DUK_EXTERNAL duk_bool_t duk_has_prop_lstring(duk_hthread *thr, duk_idx_t obj_idx, const char *key, duk_size_t key_len) { DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(key != NULL); obj_idx = duk_require_normalize_index(thr, obj_idx); (void) duk_push_lstring(thr, key, key_len); return duk_has_prop(thr, obj_idx); } #if !defined(DUK_USE_PREFER_SIZE) DUK_EXTERNAL duk_bool_t duk_has_prop_literal_raw(duk_hthread *thr, duk_idx_t obj_idx, const char *key, duk_size_t key_len) { DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(key != NULL); DUK_ASSERT(key[key_len] == (char) 0); obj_idx = duk_require_normalize_index(thr, obj_idx); (void) duk_push_literal_raw(thr, key, key_len); return duk_has_prop(thr, obj_idx); } #endif DUK_EXTERNAL duk_bool_t duk_has_prop_index(duk_hthread *thr, duk_idx_t obj_idx, duk_uarridx_t arr_idx) { DUK_ASSERT_API_ENTRY(thr); obj_idx = duk_require_normalize_index(thr, obj_idx); duk_push_uarridx(thr, arr_idx); return duk_has_prop(thr, obj_idx); } DUK_EXTERNAL duk_bool_t duk_has_prop_heapptr(duk_hthread *thr, duk_idx_t obj_idx, void *ptr) { DUK_ASSERT_API_ENTRY(thr); obj_idx = duk_require_normalize_index(thr, obj_idx); (void) duk_push_heapptr(thr, ptr); /* NULL -> 'undefined' */ return duk_has_prop(thr, obj_idx); } DUK_INTERNAL duk_bool_t duk_has_prop_stridx(duk_hthread *thr, duk_idx_t obj_idx, duk_small_uint_t stridx) { DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT_STRIDX_VALID(stridx); obj_idx = duk_require_normalize_index(thr, obj_idx); duk_push_hstring(thr, DUK_HTHREAD_GET_STRING(thr, stridx)); return duk_has_prop(thr, obj_idx); } #if 0 DUK_INTERNAL duk_bool_t duk_has_prop_stridx_short_raw(duk_hthread *thr, duk_uint_t packed_args) { return duk_has_prop_stridx(thr, (duk_idx_t) (duk_int16_t) (packed_args >> 16), (duk_small_uint_t) (packed_args & 0xffffUL)); } #endif /* Define own property without inheritance lookups and such. This differs from * [[DefineOwnProperty]] because special behaviors (like Array 'length') are * not invoked by this method. The caller must be careful to invoke any such * behaviors if necessary. */ DUK_INTERNAL void duk_xdef_prop(duk_hthread *thr, duk_idx_t obj_idx, duk_small_uint_t desc_flags) { duk_hobject *obj; duk_hstring *key; DUK_ASSERT_API_ENTRY(thr); obj = duk_require_hobject(thr, obj_idx); DUK_ASSERT(obj != NULL); key = duk_to_property_key_hstring(thr, -2); DUK_ASSERT(key != NULL); DUK_ASSERT(duk_require_tval(thr, -1) != NULL); duk_hobject_define_property_internal(thr, obj, key, desc_flags); duk_pop(thr); /* pop key */ } DUK_INTERNAL void duk_xdef_prop_index(duk_hthread *thr, duk_idx_t obj_idx, duk_uarridx_t arr_idx, duk_small_uint_t desc_flags) { duk_hobject *obj; DUK_ASSERT_API_ENTRY(thr); obj = duk_require_hobject(thr, obj_idx); DUK_ASSERT(obj != NULL); duk_hobject_define_property_internal_arridx(thr, obj, arr_idx, desc_flags); /* value popped by call */ } DUK_INTERNAL void duk_xdef_prop_stridx(duk_hthread *thr, duk_idx_t obj_idx, duk_small_uint_t stridx, duk_small_uint_t desc_flags) { duk_hobject *obj; duk_hstring *key; DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT_STRIDX_VALID(stridx); obj = duk_require_hobject(thr, obj_idx); DUK_ASSERT(obj != NULL); key = DUK_HTHREAD_GET_STRING(thr, stridx); DUK_ASSERT(key != NULL); DUK_ASSERT(duk_require_tval(thr, -1) != NULL); duk_hobject_define_property_internal(thr, obj, key, desc_flags); /* value popped by call */ } DUK_INTERNAL void duk_xdef_prop_stridx_short_raw(duk_hthread *thr, duk_uint_t packed_args) { duk_xdef_prop_stridx(thr, (duk_idx_t) (duk_int8_t) (packed_args >> 24), (duk_small_uint_t) (packed_args >> 8) & 0xffffUL, (duk_small_uint_t) (packed_args & 0xffL)); } #if 0 /*unused*/ DUK_INTERNAL void duk_xdef_prop_stridx_builtin(duk_hthread *thr, duk_idx_t obj_idx, duk_small_uint_t stridx, duk_small_int_t builtin_idx, duk_small_uint_t desc_flags) { duk_hobject *obj; duk_hstring *key; DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT_STRIDX_VALID(stridx); DUK_ASSERT_BIDX_VALID(builtin_idx); obj = duk_require_hobject(thr, obj_idx); DUK_ASSERT(obj != NULL); key = DUK_HTHREAD_GET_STRING(thr, stridx); DUK_ASSERT(key != NULL); duk_push_hobject(thr, thr->builtins[builtin_idx]); duk_hobject_define_property_internal(thr, obj, key, desc_flags); /* value popped by call */ } #endif /* This is a rare property helper; it sets the global thrower (E5 Section 13.2.3) * setter/getter into an object property. This is needed by the 'arguments' * object creation code, function instance creation code, and Function.prototype.bind(). */ DUK_INTERNAL void duk_xdef_prop_stridx_thrower(duk_hthread *thr, duk_idx_t obj_idx, duk_small_uint_t stridx) { DUK_ASSERT_API_ENTRY(thr); obj_idx = duk_require_normalize_index(thr, obj_idx); duk_push_hstring_stridx(thr, stridx); duk_push_hobject_bidx(thr, DUK_BIDX_TYPE_ERROR_THROWER); duk_dup_top(thr); duk_def_prop(thr, obj_idx, DUK_DEFPROP_HAVE_SETTER | DUK_DEFPROP_HAVE_GETTER | DUK_DEFPROP_FORCE); /* attributes always 0 */ } /* Object.getOwnPropertyDescriptor() equivalent C binding. */ DUK_EXTERNAL void duk_get_prop_desc(duk_hthread *thr, duk_idx_t obj_idx, duk_uint_t flags) { DUK_ASSERT_API_ENTRY(thr); DUK_UNREF(flags); /* no flags defined yet */ duk_hobject_object_get_own_property_descriptor(thr, obj_idx); /* [ ... key ] -> [ ... desc ] */ } /* Object.defineProperty() equivalent C binding. */ DUK_EXTERNAL void duk_def_prop(duk_hthread *thr, duk_idx_t obj_idx, duk_uint_t flags) { duk_idx_t idx_base; duk_hobject *obj; duk_hstring *key; duk_idx_t idx_value; duk_hobject *get; duk_hobject *set; duk_uint_t is_data_desc; duk_uint_t is_acc_desc; DUK_ASSERT_API_ENTRY(thr); obj = duk_require_hobject(thr, obj_idx); is_data_desc = flags & (DUK_DEFPROP_HAVE_VALUE | DUK_DEFPROP_HAVE_WRITABLE); is_acc_desc = flags & (DUK_DEFPROP_HAVE_GETTER | DUK_DEFPROP_HAVE_SETTER); if (is_data_desc && is_acc_desc) { /* "Have" flags must not be conflicting so that they would * apply to both a plain property and an accessor at the same * time. */ goto fail_invalid_desc; } idx_base = duk_get_top_index(thr); if (flags & DUK_DEFPROP_HAVE_SETTER) { duk_require_type_mask(thr, idx_base, DUK_TYPE_MASK_UNDEFINED | DUK_TYPE_MASK_OBJECT | DUK_TYPE_MASK_LIGHTFUNC); set = duk_get_hobject_promote_lfunc(thr, idx_base); if (set != NULL && !DUK_HOBJECT_IS_CALLABLE(set)) { goto fail_not_callable; } idx_base--; } else { set = NULL; } if (flags & DUK_DEFPROP_HAVE_GETTER) { duk_require_type_mask(thr, idx_base, DUK_TYPE_MASK_UNDEFINED | DUK_TYPE_MASK_OBJECT | DUK_TYPE_MASK_LIGHTFUNC); get = duk_get_hobject_promote_lfunc(thr, idx_base); if (get != NULL && !DUK_HOBJECT_IS_CALLABLE(get)) { goto fail_not_callable; } idx_base--; } else { get = NULL; } if (flags & DUK_DEFPROP_HAVE_VALUE) { idx_value = idx_base; idx_base--; } else { idx_value = (duk_idx_t) -1; } key = duk_to_property_key_hstring(thr, idx_base); DUK_ASSERT(key != NULL); duk_require_valid_index(thr, idx_base); duk_hobject_define_property_helper(thr, flags /*defprop_flags*/, obj, key, idx_value, get, set, 1 /*throw_flag*/); /* Clean up stack */ duk_set_top(thr, idx_base); /* [ ... obj ... ] */ return; fail_invalid_desc: DUK_ERROR_TYPE(thr, DUK_STR_INVALID_DESCRIPTOR); DUK_WO_NORETURN(return;); fail_not_callable: DUK_ERROR_TYPE(thr, DUK_STR_NOT_CALLABLE); DUK_WO_NORETURN(return;); } /* * Object related */ DUK_EXTERNAL void duk_compact(duk_hthread *thr, duk_idx_t obj_idx) { duk_hobject *obj; DUK_ASSERT_API_ENTRY(thr); obj = duk_get_hobject(thr, obj_idx); if (obj) { /* Note: this may fail, caller should protect the call if necessary */ duk_hobject_compact_props(thr, obj); } } DUK_INTERNAL void duk_compact_m1(duk_hthread *thr) { DUK_ASSERT_API_ENTRY(thr); duk_compact(thr, -1); } /* XXX: the duk_hobject_enum.c stack APIs should be reworked */ DUK_EXTERNAL void duk_enum(duk_hthread *thr, duk_idx_t obj_idx, duk_uint_t enum_flags) { DUK_ASSERT_API_ENTRY(thr); duk_dup(thr, obj_idx); duk_require_hobject_promote_mask(thr, -1, DUK_TYPE_MASK_LIGHTFUNC | DUK_TYPE_MASK_BUFFER); duk_hobject_enumerator_create(thr, enum_flags); /* [target] -> [enum] */ } DUK_EXTERNAL duk_bool_t duk_next(duk_hthread *thr, duk_idx_t enum_index, duk_bool_t get_value) { DUK_ASSERT_API_ENTRY(thr); duk_require_hobject(thr, enum_index); duk_dup(thr, enum_index); return duk_hobject_enumerator_next(thr, get_value); } DUK_INTERNAL void duk_seal_freeze_raw(duk_hthread *thr, duk_idx_t obj_idx, duk_bool_t is_freeze) { duk_tval *tv; duk_hobject *h; DUK_ASSERT_API_ENTRY(thr); tv = duk_require_tval(thr, obj_idx); DUK_ASSERT(tv != NULL); /* Seal/freeze are quite rare in practice so it'd be nice to get the * correct behavior simply via automatic promotion (at the cost of some * memory churn). However, the promoted objects don't behave the same, * e.g. promoted lightfuncs are extensible. */ switch (DUK_TVAL_GET_TAG(tv)) { case DUK_TAG_BUFFER: /* Plain buffer: already sealed, but not frozen (and can't be frozen * because index properties can't be made non-writable. */ if (is_freeze) { goto fail_cannot_freeze; } break; case DUK_TAG_LIGHTFUNC: /* Lightfunc: already sealed and frozen, success. */ break; case DUK_TAG_OBJECT: h = DUK_TVAL_GET_OBJECT(tv); DUK_ASSERT(h != NULL); if (is_freeze && DUK_HOBJECT_IS_BUFOBJ(h)) { /* Buffer objects cannot be frozen because there's no internal * support for making virtual array indices non-writable. */ DUK_DD(DUK_DDPRINT("cannot freeze a buffer object")); goto fail_cannot_freeze; } duk_hobject_object_seal_freeze_helper(thr, h, is_freeze); /* Sealed and frozen objects cannot gain any more properties, * so this is a good time to compact them. */ duk_hobject_compact_props(thr, h); break; default: /* ES2015 Sections 19.1.2.5, 19.1.2.17 */ break; } return; fail_cannot_freeze: DUK_ERROR_TYPE_INVALID_ARGS(thr); /* XXX: proper error message */ DUK_WO_NORETURN(return;); } DUK_EXTERNAL void duk_seal(duk_hthread *thr, duk_idx_t obj_idx) { DUK_ASSERT_API_ENTRY(thr); duk_seal_freeze_raw(thr, obj_idx, 0 /*is_freeze*/); } DUK_EXTERNAL void duk_freeze(duk_hthread *thr, duk_idx_t obj_idx) { DUK_ASSERT_API_ENTRY(thr); duk_seal_freeze_raw(thr, obj_idx, 1 /*is_freeze*/); } /* * Helpers for writing multiple properties */ DUK_EXTERNAL void duk_put_function_list(duk_hthread *thr, duk_idx_t obj_idx, const duk_function_list_entry *funcs) { const duk_function_list_entry *ent = funcs; DUK_ASSERT_API_ENTRY(thr); obj_idx = duk_require_normalize_index(thr, obj_idx); if (ent != NULL) { while (ent->key != NULL) { duk_push_c_function(thr, ent->value, ent->nargs); duk_put_prop_string(thr, obj_idx, ent->key); ent++; } } } DUK_EXTERNAL void duk_put_number_list(duk_hthread *thr, duk_idx_t obj_idx, const duk_number_list_entry *numbers) { const duk_number_list_entry *ent = numbers; duk_tval *tv; DUK_ASSERT_API_ENTRY(thr); obj_idx = duk_require_normalize_index(thr, obj_idx); if (ent != NULL) { while (ent->key != NULL) { tv = thr->valstack_top++; DUK_ASSERT(DUK_TVAL_IS_UNDEFINED(tv)); /* value stack init policy */ DUK_TVAL_SET_NUMBER_CHKFAST_SLOW(tv, ent->value); /* no need for decref/incref */ duk_put_prop_string(thr, obj_idx, ent->key); ent++; } } } /* * Shortcut for accessing global object properties */ DUK_EXTERNAL duk_bool_t duk_get_global_string(duk_hthread *thr, const char *key) { duk_bool_t ret; DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(thr->builtins[DUK_BIDX_GLOBAL] != NULL); /* XXX: direct implementation */ duk_push_hobject(thr, thr->builtins[DUK_BIDX_GLOBAL]); ret = duk_get_prop_string(thr, -1, key); duk_remove_m2(thr); return ret; } DUK_EXTERNAL duk_bool_t duk_get_global_lstring(duk_hthread *thr, const char *key, duk_size_t key_len) { duk_bool_t ret; DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(thr->builtins[DUK_BIDX_GLOBAL] != NULL); /* XXX: direct implementation */ duk_push_hobject(thr, thr->builtins[DUK_BIDX_GLOBAL]); ret = duk_get_prop_lstring(thr, -1, key, key_len); duk_remove_m2(thr); return ret; } #if !defined(DUK_USE_PREFER_SIZE) DUK_EXTERNAL duk_bool_t duk_get_global_literal_raw(duk_hthread *thr, const char *key, duk_size_t key_len) { duk_bool_t ret; DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(thr->builtins[DUK_BIDX_GLOBAL] != NULL); DUK_ASSERT(key[key_len] == (char) 0); /* XXX: direct implementation */ duk_push_hobject(thr, thr->builtins[DUK_BIDX_GLOBAL]); ret = duk_get_prop_literal_raw(thr, -1, key, key_len); duk_remove_m2(thr); return ret; } #endif DUK_EXTERNAL duk_bool_t duk_get_global_heapptr(duk_hthread *thr, void *ptr) { duk_bool_t ret; DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(thr->builtins[DUK_BIDX_GLOBAL] != NULL); /* XXX: direct implementation */ duk_push_hobject(thr, thr->builtins[DUK_BIDX_GLOBAL]); ret = duk_get_prop_heapptr(thr, -1, ptr); duk_remove_m2(thr); return ret; } DUK_EXTERNAL duk_bool_t duk_put_global_string(duk_hthread *thr, const char *key) { duk_bool_t ret; DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(thr->builtins[DUK_BIDX_GLOBAL] != NULL); /* XXX: direct implementation */ duk_push_hobject(thr, thr->builtins[DUK_BIDX_GLOBAL]); duk_insert(thr, -2); ret = duk_put_prop_string(thr, -2, key); /* [ ... global val ] -> [ ... global ] */ duk_pop(thr); return ret; } DUK_EXTERNAL duk_bool_t duk_put_global_lstring(duk_hthread *thr, const char *key, duk_size_t key_len) { duk_bool_t ret; DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(thr->builtins[DUK_BIDX_GLOBAL] != NULL); /* XXX: direct implementation */ duk_push_hobject(thr, thr->builtins[DUK_BIDX_GLOBAL]); duk_insert(thr, -2); ret = duk_put_prop_lstring(thr, -2, key, key_len); /* [ ... global val ] -> [ ... global ] */ duk_pop(thr); return ret; } #if !defined(DUK_USE_PREFER_SIZE) DUK_EXTERNAL duk_bool_t duk_put_global_literal_raw(duk_hthread *thr, const char *key, duk_size_t key_len) { duk_bool_t ret; DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(thr->builtins[DUK_BIDX_GLOBAL] != NULL); DUK_ASSERT(key[key_len] == (char) 0); /* XXX: direct implementation */ duk_push_hobject(thr, thr->builtins[DUK_BIDX_GLOBAL]); duk_insert(thr, -2); ret = duk_put_prop_literal_raw(thr, -2, key, key_len); /* [ ... global val ] -> [ ... global ] */ duk_pop(thr); return ret; } #endif DUK_EXTERNAL duk_bool_t duk_put_global_heapptr(duk_hthread *thr, void *ptr) { duk_bool_t ret; DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(thr->builtins[DUK_BIDX_GLOBAL] != NULL); /* XXX: direct implementation */ duk_push_hobject(thr, thr->builtins[DUK_BIDX_GLOBAL]); duk_insert(thr, -2); ret = duk_put_prop_heapptr(thr, -2, ptr); /* [ ... global val ] -> [ ... global ] */ duk_pop(thr); return ret; } /* * ES2015 GetMethod() */ DUK_INTERNAL duk_bool_t duk_get_method_stridx(duk_hthread *thr, duk_idx_t idx, duk_small_uint_t stridx) { (void) duk_get_prop_stridx(thr, idx, stridx); if (duk_is_null_or_undefined(thr, -1)) { duk_pop_nodecref_unsafe(thr); return 0; } if (!duk_is_callable(thr, -1)) { DUK_ERROR_TYPE(thr, DUK_STR_NOT_CALLABLE); DUK_WO_NORETURN(return 0;); } return 1; } /* * Object prototype */ DUK_EXTERNAL void duk_get_prototype(duk_hthread *thr, duk_idx_t idx) { duk_hobject *obj; duk_hobject *proto; DUK_ASSERT_API_ENTRY(thr); obj = duk_require_hobject(thr, idx); DUK_ASSERT(obj != NULL); /* XXX: shared helper for duk_push_hobject_or_undefined()? */ proto = DUK_HOBJECT_GET_PROTOTYPE(thr->heap, obj); if (proto) { duk_push_hobject(thr, proto); } else { duk_push_undefined(thr); } } DUK_EXTERNAL void duk_set_prototype(duk_hthread *thr, duk_idx_t idx) { duk_hobject *obj; duk_hobject *proto; DUK_ASSERT_API_ENTRY(thr); obj = duk_require_hobject(thr, idx); DUK_ASSERT(obj != NULL); duk_require_type_mask(thr, -1, DUK_TYPE_MASK_UNDEFINED | DUK_TYPE_MASK_OBJECT); proto = duk_get_hobject(thr, -1); /* proto can also be NULL here (allowed explicitly) */ #if defined(DUK_USE_ROM_OBJECTS) if (DUK_HEAPHDR_HAS_READONLY((duk_heaphdr *) obj)) { DUK_ERROR_TYPE(thr, DUK_STR_NOT_CONFIGURABLE); /* XXX: "read only object"? */ DUK_WO_NORETURN(return;); } #endif DUK_HOBJECT_SET_PROTOTYPE_UPDREF(thr, obj, proto); duk_pop(thr); } DUK_INTERNAL void duk_clear_prototype(duk_hthread *thr, duk_idx_t idx) { duk_hobject *obj; DUK_ASSERT_API_ENTRY(thr); obj = duk_require_hobject(thr, idx); DUK_ASSERT(obj != NULL); #if defined(DUK_USE_ROM_OBJECTS) if (DUK_HEAPHDR_HAS_READONLY((duk_heaphdr *) obj)) { DUK_ERROR_TYPE(thr, DUK_STR_NOT_CONFIGURABLE); /* XXX: "read only object"? */ DUK_WO_NORETURN(return;); } #endif DUK_HOBJECT_SET_PROTOTYPE_UPDREF(thr, obj, NULL); } DUK_INTERNAL duk_bool_t duk_is_bare_object(duk_hthread *thr, duk_idx_t idx) { duk_hobject *obj; duk_hobject *proto; DUK_ASSERT_API_ENTRY(thr); obj = duk_require_hobject(thr, idx); DUK_ASSERT(obj != NULL); proto = DUK_HOBJECT_GET_PROTOTYPE(thr->heap, obj); return (proto == NULL); } /* * Object finalizer */ #if defined(DUK_USE_FINALIZER_SUPPORT) /* XXX: these could be implemented as macros calling an internal function * directly. * XXX: same issue as with Duktape.fin: there's no way to delete the property * now (just set it to undefined). */ DUK_EXTERNAL void duk_get_finalizer(duk_hthread *thr, duk_idx_t idx) { DUK_ASSERT_API_ENTRY(thr); /* This get intentionally walks the inheritance chain at present, * which matches how the effective finalizer property is also * looked up in GC. */ duk_get_prop_stridx(thr, idx, DUK_STRIDX_INT_FINALIZER); } DUK_EXTERNAL void duk_set_finalizer(duk_hthread *thr, duk_idx_t idx) { duk_hobject *h; duk_bool_t callable; DUK_ASSERT_API_ENTRY(thr); h = duk_require_hobject(thr, idx); /* Get before 'put' so that 'idx' is correct. */ callable = duk_is_callable(thr, -1); /* At present finalizer is stored as a hidden Symbol, with normal * inheritance and access control. As a result, finalizer cannot * currently be set on a non-extensible (sealed or frozen) object. * It might be useful to allow it. */ duk_put_prop_stridx(thr, idx, DUK_STRIDX_INT_FINALIZER); /* In addition to setting the finalizer property, keep a "have * finalizer" flag in duk_hobject in sync so that refzero can do * a very quick finalizer check by walking the prototype chain * and checking the flag alone. (Note that this means that just * setting _Finalizer on an object won't affect finalizer checks.) * * NOTE: if the argument is a Proxy object, this flag will be set * on the Proxy, not the target. As a result, the target won't get * a finalizer flag and the Proxy also won't be finalized as there's * an explicit Proxy check in finalization now. */ if (callable) { DUK_HOBJECT_SET_HAVE_FINALIZER(h); } else { DUK_HOBJECT_CLEAR_HAVE_FINALIZER(h); } } #else /* DUK_USE_FINALIZER_SUPPORT */ DUK_EXTERNAL void duk_get_finalizer(duk_hthread *thr, duk_idx_t idx) { DUK_ASSERT_API_ENTRY(thr); DUK_UNREF(idx); DUK_ERROR_UNSUPPORTED(thr); DUK_WO_NORETURN(return;); } DUK_EXTERNAL void duk_set_finalizer(duk_hthread *thr, duk_idx_t idx) { DUK_ASSERT_API_ENTRY(thr); DUK_UNREF(idx); DUK_ERROR_UNSUPPORTED(thr); DUK_WO_NORETURN(return;); } #endif /* DUK_USE_FINALIZER_SUPPORT */ #line 1 "duk_api_random.c" /* * Random numbers */ /* #include duk_internal.h -> already included */ DUK_EXTERNAL duk_double_t duk_random(duk_hthread *thr) { return (duk_double_t) duk_util_get_random_double(thr); } #line 1 "duk_api_stack.c" /* * API calls related to general value stack manipulation: resizing the value * stack, pushing and popping values, type checking and reading values, * coercing values, etc. * * Also contains internal functions (such as duk_get_tval()), defined * in duk_api_internal.h, with semantics similar to the public API. */ /* XXX: repetition of stack pre-checks -> helper or macro or inline */ /* XXX: shared api error strings, and perhaps even throw code for rare cases? */ /* #include duk_internal.h -> already included */ /* * Forward declarations */ DUK_LOCAL_DECL duk_idx_t duk__push_c_function_raw(duk_hthread *thr, duk_c_function func, duk_idx_t nargs, duk_uint_t flags, duk_small_uint_t proto_bidx); /* * Global state for working around missing variadic macros */ #if !defined(DUK_USE_VARIADIC_MACROS) DUK_EXTERNAL const char *duk_api_global_filename = NULL; DUK_EXTERNAL duk_int_t duk_api_global_line = 0; #endif /* * Misc helpers */ DUK_LOCAL const char * const duk__symbol_type_strings[4] = { "hidden", "global", "local", "wellknown" }; #if !defined(DUK_USE_PACKED_TVAL) DUK_LOCAL const duk_uint_t duk__type_from_tag[] = { DUK_TYPE_NUMBER, DUK_TYPE_NUMBER, /* fastint */ DUK_TYPE_UNDEFINED, DUK_TYPE_NULL, DUK_TYPE_BOOLEAN, DUK_TYPE_POINTER, DUK_TYPE_LIGHTFUNC, DUK_TYPE_NONE, DUK_TYPE_STRING, DUK_TYPE_OBJECT, DUK_TYPE_BUFFER, }; DUK_LOCAL const duk_uint_t duk__type_mask_from_tag[] = { DUK_TYPE_MASK_NUMBER, DUK_TYPE_MASK_NUMBER, /* fastint */ DUK_TYPE_MASK_UNDEFINED, DUK_TYPE_MASK_NULL, DUK_TYPE_MASK_BOOLEAN, DUK_TYPE_MASK_POINTER, DUK_TYPE_MASK_LIGHTFUNC, DUK_TYPE_MASK_NONE, DUK_TYPE_MASK_STRING, DUK_TYPE_MASK_OBJECT, DUK_TYPE_MASK_BUFFER, }; #endif /* !DUK_USE_PACKED_TVAL */ /* Assert that there's room for one value. */ #define DUK__ASSERT_SPACE() \ do { \ DUK_ASSERT(!(thr->valstack_top >= thr->valstack_end)); \ } while (0) /* Check that there's room to push one value. */ #if defined(DUK_USE_VALSTACK_UNSAFE) /* Faster but value stack overruns are memory unsafe. */ #define DUK__CHECK_SPACE() DUK__ASSERT_SPACE() #else #define DUK__CHECK_SPACE() \ do { \ if (DUK_UNLIKELY(thr->valstack_top >= thr->valstack_end)) { \ DUK_ERROR_RANGE_PUSH_BEYOND(thr); \ } \ } while (0) #endif DUK_LOCAL duk_small_uint_t duk__get_symbol_type(duk_hstring *h) { const duk_uint8_t *data; duk_size_t len; DUK_ASSERT(h != NULL); DUK_ASSERT(DUK_HSTRING_HAS_SYMBOL(h)); DUK_ASSERT(DUK_HSTRING_GET_BYTELEN(h) >= 1); /* always true, symbol prefix */ data = (const duk_uint8_t *) DUK_HSTRING_GET_DATA(h); len = DUK_HSTRING_GET_BYTELEN(h); DUK_ASSERT(len >= 1); /* XXX: differentiate between 0x82 and 0xff (hidden vs. internal?)? */ if (data[0] == 0xffU) { return DUK_SYMBOL_TYPE_HIDDEN; } else if (data[0] == 0x82U) { return DUK_SYMBOL_TYPE_HIDDEN; } else if (data[0] == 0x80U) { return DUK_SYMBOL_TYPE_GLOBAL; } else if (data[len - 1] != 0xffU) { return DUK_SYMBOL_TYPE_LOCAL; } else { return DUK_SYMBOL_TYPE_WELLKNOWN; } } DUK_LOCAL const char *duk__get_symbol_type_string(duk_hstring *h) { duk_small_uint_t idx; idx = duk__get_symbol_type(h); DUK_ASSERT(idx < sizeof(duk__symbol_type_strings)); return duk__symbol_type_strings[idx]; } DUK_LOCAL_DECL duk_heaphdr *duk__get_tagged_heaphdr_raw(duk_hthread *thr, duk_idx_t idx, duk_uint_t tag); DUK_LOCAL duk_int_t duk__api_coerce_d2i(duk_hthread *thr, duk_idx_t idx, duk_int_t def_value, duk_bool_t require) { duk_tval *tv; duk_small_int_t c; duk_double_t d; tv = duk_get_tval_or_unused(thr, idx); DUK_ASSERT(tv != NULL); /* * Special cases like NaN and +/- Infinity are handled explicitly * because a plain C coercion from double to int handles these cases * in undesirable ways. For instance, NaN may coerce to INT_MIN * (not zero), and INT_MAX + 1 may coerce to INT_MIN (not INT_MAX). * * This double-to-int coercion differs from ToInteger() because it * has a finite range (ToInteger() allows e.g. +/- Infinity). It * also differs from ToInt32() because the INT_MIN/INT_MAX clamping * depends on the size of the int type on the platform. In particular, * on platforms with a 64-bit int type, the full range is allowed. */ #if defined(DUK_USE_FASTINT) if (DUK_TVAL_IS_FASTINT(tv)) { duk_int64_t t = DUK_TVAL_GET_FASTINT(tv); #if (DUK_INT_MAX <= 0x7fffffffL) /* Clamping only necessary for 32-bit ints. */ if (t < DUK_INT_MIN) { t = DUK_INT_MIN; } else if (t > DUK_INT_MAX) { t = DUK_INT_MAX; } #endif return (duk_int_t) t; } #endif if (DUK_TVAL_IS_NUMBER(tv)) { d = DUK_TVAL_GET_NUMBER(tv); c = (duk_small_int_t) DUK_FPCLASSIFY(d); if (c == DUK_FP_NAN) { return 0; } else if (d < (duk_double_t) DUK_INT_MIN) { /* covers -Infinity */ return DUK_INT_MIN; } else if (d > (duk_double_t) DUK_INT_MAX) { /* covers +Infinity */ return DUK_INT_MAX; } else { /* coerce towards zero */ return (duk_int_t) d; } } if (require) { DUK_ERROR_REQUIRE_TYPE_INDEX(thr, idx, "number", DUK_STR_NOT_NUMBER); DUK_WO_NORETURN(return 0;); } return def_value; } DUK_LOCAL duk_uint_t duk__api_coerce_d2ui(duk_hthread *thr, duk_idx_t idx, duk_uint_t def_value, duk_bool_t require) { duk_tval *tv; duk_small_int_t c; duk_double_t d; /* Same as above but for unsigned int range. */ tv = duk_get_tval_or_unused(thr, idx); DUK_ASSERT(tv != NULL); #if defined(DUK_USE_FASTINT) if (DUK_TVAL_IS_FASTINT(tv)) { duk_int64_t t = DUK_TVAL_GET_FASTINT(tv); if (t < 0) { t = 0; } #if (DUK_UINT_MAX <= 0xffffffffUL) /* Clamping only necessary for 32-bit ints. */ else if (t > DUK_UINT_MAX) { t = DUK_UINT_MAX; } #endif return (duk_uint_t) t; } #endif if (DUK_TVAL_IS_NUMBER(tv)) { d = DUK_TVAL_GET_NUMBER(tv); c = (duk_small_int_t) DUK_FPCLASSIFY(d); if (c == DUK_FP_NAN) { return 0; } else if (d < 0.0) { /* covers -Infinity */ return (duk_uint_t) 0; } else if (d > (duk_double_t) DUK_UINT_MAX) { /* covers +Infinity */ return (duk_uint_t) DUK_UINT_MAX; } else { /* coerce towards zero */ return (duk_uint_t) d; } } if (require) { DUK_ERROR_REQUIRE_TYPE_INDEX(thr, idx, "number", DUK_STR_NOT_NUMBER); DUK_WO_NORETURN(return 0;); } return def_value; } /* * Stack index validation/normalization and getting a stack duk_tval ptr. * * These are called by many API entrypoints so the implementations must be * fast and "inlined". * * There's some repetition because of this; keep the functions in sync. */ DUK_EXTERNAL duk_idx_t duk_normalize_index(duk_hthread *thr, duk_idx_t idx) { duk_uidx_t vs_size; duk_uidx_t uidx; DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(DUK_INVALID_INDEX < 0); /* Care must be taken to avoid pointer wrapping in the index * validation. For instance, on a 32-bit platform with 8-byte * duk_tval the index 0x20000000UL would wrap the memory space * once. */ /* Assume value stack sizes (in elements) fits into duk_idx_t. */ DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom); vs_size = (duk_uidx_t) (thr->valstack_top - thr->valstack_bottom); DUK_ASSERT_DISABLE(vs_size >= 0); /* unsigned */ if (idx < 0) { uidx = vs_size + (duk_uidx_t) idx; } else { /* since index non-negative */ DUK_ASSERT(idx != DUK_INVALID_INDEX); uidx = (duk_uidx_t) idx; } /* DUK_INVALID_INDEX won't be accepted as a valid index. */ DUK_ASSERT(vs_size + (duk_uidx_t) DUK_INVALID_INDEX >= vs_size); if (DUK_LIKELY(uidx < vs_size)) { return (duk_idx_t) uidx; } return DUK_INVALID_INDEX; } DUK_EXTERNAL duk_idx_t duk_require_normalize_index(duk_hthread *thr, duk_idx_t idx) { duk_uidx_t vs_size; duk_uidx_t uidx; DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(DUK_INVALID_INDEX < 0); DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom); vs_size = (duk_uidx_t) (thr->valstack_top - thr->valstack_bottom); DUK_ASSERT_DISABLE(vs_size >= 0); /* unsigned */ if (idx < 0) { uidx = vs_size + (duk_uidx_t) idx; } else { DUK_ASSERT(idx != DUK_INVALID_INDEX); uidx = (duk_uidx_t) idx; } /* DUK_INVALID_INDEX won't be accepted as a valid index. */ DUK_ASSERT(vs_size + (duk_uidx_t) DUK_INVALID_INDEX >= vs_size); if (DUK_LIKELY(uidx < vs_size)) { return (duk_idx_t) uidx; } DUK_ERROR_RANGE_INDEX(thr, idx); DUK_WO_NORETURN(return 0;); } DUK_INTERNAL duk_tval *duk_get_tval(duk_hthread *thr, duk_idx_t idx) { duk_uidx_t vs_size; duk_uidx_t uidx; DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(DUK_INVALID_INDEX < 0); DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom); vs_size = (duk_uidx_t) (thr->valstack_top - thr->valstack_bottom); DUK_ASSERT_DISABLE(vs_size >= 0); /* unsigned */ if (idx < 0) { uidx = vs_size + (duk_uidx_t) idx; } else { DUK_ASSERT(idx != DUK_INVALID_INDEX); uidx = (duk_uidx_t) idx; } /* DUK_INVALID_INDEX won't be accepted as a valid index. */ DUK_ASSERT(vs_size + (duk_uidx_t) DUK_INVALID_INDEX >= vs_size); if (DUK_LIKELY(uidx < vs_size)) { return thr->valstack_bottom + uidx; } return NULL; } /* Variant of duk_get_tval() which is guaranteed to return a valid duk_tval * pointer. When duk_get_tval() would return NULL, this variant returns a * pointer to a duk_tval with tag DUK_TAG_UNUSED. This allows the call site * to avoid an unnecessary NULL check which sometimes leads to better code. * The return duk_tval is read only (at least for the UNUSED value). */ DUK_LOCAL const duk_tval_unused duk__const_tval_unused = DUK_TVAL_UNUSED_INITIALIZER(); DUK_INTERNAL duk_tval *duk_get_tval_or_unused(duk_hthread *thr, duk_idx_t idx) { duk_tval *tv; DUK_ASSERT_API_ENTRY(thr); tv = duk_get_tval(thr, idx); if (tv != NULL) { return tv; } return (duk_tval *) DUK_LOSE_CONST(&duk__const_tval_unused); } DUK_INTERNAL duk_tval *duk_require_tval(duk_hthread *thr, duk_idx_t idx) { duk_uidx_t vs_size; duk_uidx_t uidx; DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(DUK_INVALID_INDEX < 0); DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom); vs_size = (duk_uidx_t) (thr->valstack_top - thr->valstack_bottom); DUK_ASSERT_DISABLE(vs_size >= 0); /* unsigned */ /* Use unsigned arithmetic to optimize comparison. */ if (idx < 0) { uidx = vs_size + (duk_uidx_t) idx; } else { DUK_ASSERT(idx != DUK_INVALID_INDEX); uidx = (duk_uidx_t) idx; } /* DUK_INVALID_INDEX won't be accepted as a valid index. */ DUK_ASSERT(vs_size + (duk_uidx_t) DUK_INVALID_INDEX >= vs_size); if (DUK_LIKELY(uidx < vs_size)) { return thr->valstack_bottom + uidx; } DUK_ERROR_RANGE_INDEX(thr, idx); DUK_WO_NORETURN(return NULL;); } /* Non-critical. */ DUK_EXTERNAL duk_bool_t duk_is_valid_index(duk_hthread *thr, duk_idx_t idx) { DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(DUK_INVALID_INDEX < 0); return (duk_normalize_index(thr, idx) >= 0); } /* Non-critical. */ DUK_EXTERNAL void duk_require_valid_index(duk_hthread *thr, duk_idx_t idx) { DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(DUK_INVALID_INDEX < 0); if (DUK_UNLIKELY(duk_normalize_index(thr, idx) < 0)) { DUK_ERROR_RANGE_INDEX(thr, idx); DUK_WO_NORETURN(return;); } } /* * Value stack top handling */ DUK_EXTERNAL duk_idx_t duk_get_top(duk_hthread *thr) { DUK_ASSERT_API_ENTRY(thr); return (duk_idx_t) (thr->valstack_top - thr->valstack_bottom); } /* Internal helper to get current top but to require a minimum top value * (TypeError if not met). */ DUK_INTERNAL duk_idx_t duk_get_top_require_min(duk_hthread *thr, duk_idx_t min_top) { duk_idx_t ret; DUK_ASSERT_API_ENTRY(thr); ret = (duk_idx_t) (thr->valstack_top - thr->valstack_bottom); if (DUK_UNLIKELY(ret < min_top)) { DUK_ERROR_TYPE_INVALID_ARGS(thr); DUK_WO_NORETURN(return 0;); } return ret; } /* Set stack top within currently allocated range, but don't reallocate. * This is performance critical especially for call handling, so whenever * changing, profile and look at generated code. */ DUK_EXTERNAL void duk_set_top(duk_hthread *thr, duk_idx_t idx) { duk_uidx_t vs_size; duk_uidx_t vs_limit; duk_uidx_t uidx; duk_tval *tv; DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(DUK_INVALID_INDEX < 0); DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom); DUK_ASSERT(thr->valstack_end >= thr->valstack_bottom); vs_size = (duk_uidx_t) (thr->valstack_top - thr->valstack_bottom); vs_limit = (duk_uidx_t) (thr->valstack_end - thr->valstack_bottom); if (idx < 0) { /* Negative indices are always within allocated stack but * must not go below zero index. */ uidx = vs_size + (duk_uidx_t) idx; } else { /* Positive index can be higher than valstack top but must * not go above allocated stack (equality is OK). */ uidx = (duk_uidx_t) idx; } /* DUK_INVALID_INDEX won't be accepted as a valid index. */ DUK_ASSERT(vs_size + (duk_uidx_t) DUK_INVALID_INDEX >= vs_size); DUK_ASSERT(vs_size + (duk_uidx_t) DUK_INVALID_INDEX >= vs_limit); #if defined(DUK_USE_VALSTACK_UNSAFE) DUK_ASSERT(uidx <= vs_limit); DUK_UNREF(vs_limit); #else if (DUK_UNLIKELY(uidx > vs_limit)) { DUK_ERROR_RANGE_INDEX(thr, idx); DUK_WO_NORETURN(return;); } #endif DUK_ASSERT(uidx <= vs_limit); /* Handle change in value stack top. Respect value stack * initialization policy: 'undefined' above top. Note that * DECREF may cause a side effect that reallocates valstack, * so must relookup after DECREF. */ if (uidx >= vs_size) { /* Stack size increases or stays the same. */ #if defined(DUK_USE_ASSERTIONS) duk_uidx_t count; count = uidx - vs_size; while (count != 0) { count--; tv = thr->valstack_top + count; DUK_ASSERT(DUK_TVAL_IS_UNDEFINED(tv)); } #endif thr->valstack_top = thr->valstack_bottom + uidx; } else { /* Stack size decreases. */ #if defined(DUK_USE_REFERENCE_COUNTING) duk_uidx_t count; duk_tval *tv_end; count = vs_size - uidx; DUK_ASSERT(count > 0); tv = thr->valstack_top; tv_end = tv - count; DUK_ASSERT(tv > tv_end); /* Because count > 0. */ do { tv--; DUK_ASSERT(tv >= thr->valstack_bottom); DUK_TVAL_SET_UNDEFINED_UPDREF_NORZ(thr, tv); } while (tv != tv_end); thr->valstack_top = tv_end; DUK_REFZERO_CHECK_FAST(thr); #else /* DUK_USE_REFERENCE_COUNTING */ duk_uidx_t count; duk_tval *tv_end; count = vs_size - uidx; tv = thr->valstack_top; tv_end = tv - count; DUK_ASSERT(tv > tv_end); do { tv--; DUK_TVAL_SET_UNDEFINED(tv); } while (tv != tv_end); thr->valstack_top = tv_end; #endif /* DUK_USE_REFERENCE_COUNTING */ } } /* Internal variant with a non-negative index and no runtime size checks. */ #if defined(DUK_USE_PREFER_SIZE) DUK_INTERNAL void duk_set_top_unsafe(duk_hthread *thr, duk_idx_t idx) { DUK_ASSERT_API_ENTRY(thr); duk_set_top(thr, idx); } #else /* DUK_USE_PREFER_SIZE */ DUK_INTERNAL void duk_set_top_unsafe(duk_hthread *thr, duk_idx_t idx) { duk_uidx_t uidx; duk_uidx_t vs_size; duk_tval *tv; DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom); DUK_ASSERT(thr->valstack_end >= thr->valstack_bottom); DUK_ASSERT(idx >= 0); DUK_ASSERT(idx <= (duk_idx_t) (thr->valstack_end - thr->valstack_bottom)); /* XXX: byte arithmetic */ uidx = (duk_uidx_t) idx; vs_size = (duk_uidx_t) (thr->valstack_top - thr->valstack_bottom); if (uidx >= vs_size) { /* Stack size increases or stays the same. */ #if defined(DUK_USE_ASSERTIONS) duk_uidx_t count; count = uidx - vs_size; while (count != 0) { count--; tv = thr->valstack_top + count; DUK_ASSERT(DUK_TVAL_IS_UNDEFINED(tv)); } #endif thr->valstack_top = thr->valstack_bottom + uidx; } else { /* Stack size decreases. */ #if defined(DUK_USE_REFERENCE_COUNTING) duk_uidx_t count; duk_tval *tv_end; count = vs_size - uidx; DUK_ASSERT(count > 0); tv = thr->valstack_top; tv_end = tv - count; DUK_ASSERT(tv > tv_end); /* Because count > 0. */ do { tv--; DUK_ASSERT(tv >= thr->valstack_bottom); DUK_TVAL_SET_UNDEFINED_UPDREF_NORZ(thr, tv); } while (tv != tv_end); thr->valstack_top = tv_end; DUK_REFZERO_CHECK_FAST(thr); #else /* DUK_USE_REFERENCE_COUNTING */ duk_uidx_t count; duk_tval *tv_end; count = vs_size - uidx; tv = thr->valstack_top; tv_end = tv - count; DUK_ASSERT(tv > tv_end); do { tv--; DUK_TVAL_SET_UNDEFINED(tv); } while (tv != tv_end); thr->valstack_top = tv_end; #endif /* DUK_USE_REFERENCE_COUNTING */ } } #endif /* DUK_USE_PREFER_SIZE */ /* Internal helper: set top to 'top', and set [idx_wipe_start,top[ to * 'undefined' (doing nothing if idx_wipe_start == top). Indices are * positive and within value stack reserve. This is used by call handling. */ DUK_INTERNAL void duk_set_top_and_wipe(duk_hthread *thr, duk_idx_t top, duk_idx_t idx_wipe_start) { DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(top >= 0); DUK_ASSERT(idx_wipe_start >= 0); DUK_ASSERT(idx_wipe_start <= top); DUK_ASSERT(thr->valstack_bottom + top <= thr->valstack_end); DUK_ASSERT(thr->valstack_bottom + idx_wipe_start <= thr->valstack_end); duk_set_top_unsafe(thr, idx_wipe_start); duk_set_top_unsafe(thr, top); } DUK_EXTERNAL duk_idx_t duk_get_top_index(duk_hthread *thr) { duk_idx_t ret; DUK_ASSERT_API_ENTRY(thr); ret = (duk_idx_t) (thr->valstack_top - thr->valstack_bottom) - 1; if (DUK_UNLIKELY(ret < 0)) { /* Return invalid index; if caller uses this without checking * in another API call, the index won't map to a valid stack * entry. */ return DUK_INVALID_INDEX; } return ret; } /* Internal variant: call assumes there is at least one element on the value * stack frame; this is only asserted for. */ DUK_INTERNAL duk_idx_t duk_get_top_index_unsafe(duk_hthread *thr) { duk_idx_t ret; DUK_ASSERT_API_ENTRY(thr); ret = (duk_idx_t) (thr->valstack_top - thr->valstack_bottom) - 1; return ret; } DUK_EXTERNAL duk_idx_t duk_require_top_index(duk_hthread *thr) { duk_idx_t ret; DUK_ASSERT_API_ENTRY(thr); ret = (duk_idx_t) (thr->valstack_top - thr->valstack_bottom) - 1; if (DUK_UNLIKELY(ret < 0)) { DUK_ERROR_RANGE_INDEX(thr, -1); DUK_WO_NORETURN(return 0;); } return ret; } /* * Value stack resizing. * * This resizing happens above the current "top": the value stack can be * grown or shrunk, but the "top" is not affected. The value stack cannot * be resized to a size below the current reserve. * * The low level reallocation primitive must carefully recompute all value * stack pointers, and must also work if ALL pointers are NULL. The resize * is quite tricky because the valstack realloc may cause a mark-and-sweep, * which may run finalizers. Running finalizers may resize the valstack * recursively (the same value stack we're working on). So, after realloc * returns, we know that the valstack bottom, top, and reserve should still * be the same (there should not be live values above the "top"), but its * underlying size, alloc_end, and base pointer may have changed. * * 'new_size' is known to be <= DUK_USE_VALSTACK_LIMIT, which ensures that * size_t and pointer arithmetic won't wrap in duk__resize_valstack(). */ /* Low level valstack resize primitive, used for both grow and shrink. All * adjustments for slack etc have already been done. Doesn't throw but does * have allocation side effects. */ DUK_LOCAL DUK_COLD DUK_NOINLINE duk_bool_t duk__resize_valstack(duk_hthread *thr, duk_size_t new_size) { duk_tval *pre_valstack; duk_tval *pre_bottom; duk_tval *pre_top; duk_tval *pre_end; duk_tval *pre_alloc_end; duk_ptrdiff_t ptr_diff; duk_tval *new_valstack; duk_size_t new_alloc_size; duk_tval *tv_prev_alloc_end; duk_tval *p; DUK_HTHREAD_ASSERT_VALID(thr); DUK_ASSERT(thr->valstack_bottom >= thr->valstack); DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom); DUK_ASSERT(thr->valstack_end >= thr->valstack_top); DUK_ASSERT(thr->valstack_alloc_end >= thr->valstack_end); DUK_ASSERT((duk_size_t) (thr->valstack_top - thr->valstack) <= new_size); /* can't resize below 'top' */ DUK_ASSERT(new_size <= DUK_USE_VALSTACK_LIMIT); /* valstack limit caller has check, prevents wrapping */ DUK_ASSERT(new_size <= DUK_SIZE_MAX / sizeof(duk_tval)); /* specific assert for wrapping */ /* Pre-realloc pointer copies for asserts and debug logs. */ pre_valstack = thr->valstack; pre_bottom = thr->valstack_bottom; pre_top = thr->valstack_top; pre_end = thr->valstack_end; pre_alloc_end = thr->valstack_alloc_end; DUK_UNREF(pre_valstack); DUK_UNREF(pre_bottom); DUK_UNREF(pre_top); DUK_UNREF(pre_end); DUK_UNREF(pre_alloc_end); /* If finalizer torture enabled, force base pointer change every time * when it would be allowed. */ #if defined(DUK_USE_FINALIZER_TORTURE) if (thr->heap->pf_prevent_count == 0) { duk_hthread_valstack_torture_realloc(thr); } #endif /* Allocate a new valstack using DUK_REALLOC_DIRECT() to deal with * a side effect changing the base pointer. */ new_alloc_size = sizeof(duk_tval) * new_size; new_valstack = (duk_tval *) DUK_REALLOC_INDIRECT(thr->heap, duk_hthread_get_valstack_ptr, (void *) thr, new_alloc_size); if (DUK_UNLIKELY(new_valstack == NULL)) { /* Because new_size != 0, if condition doesn't need to be * (new_valstack != NULL || new_size == 0). */ DUK_ASSERT(new_size != 0); DUK_D(DUK_DPRINT("failed to resize valstack to %lu entries (%lu bytes)", (unsigned long) new_size, (unsigned long) new_alloc_size)); return 0; } /* Debug log any changes in pointer(s) by side effects. These don't * necessarily imply any incorrect behavior, but should be rare in * practice. */ #if defined(DUK_USE_DEBUG) if (thr->valstack != pre_valstack) { DUK_D(DUK_DPRINT("valstack base pointer changed during valstack resize: %p -> %p", (void *) pre_valstack, (void *) thr->valstack)); } if (thr->valstack_bottom != pre_bottom) { DUK_D(DUK_DPRINT("valstack bottom pointer changed during valstack resize: %p -> %p", (void *) pre_bottom, (void *) thr->valstack_bottom)); } if (thr->valstack_top != pre_top) { DUK_D(DUK_DPRINT("valstack top pointer changed during valstack resize: %p -> %p", (void *) pre_top, (void *) thr->valstack_top)); } if (thr->valstack_end != pre_end) { DUK_D(DUK_DPRINT("valstack end pointer changed during valstack resize: %p -> %p", (void *) pre_end, (void *) thr->valstack_end)); } if (thr->valstack_alloc_end != pre_alloc_end) { DUK_D(DUK_DPRINT("valstack alloc_end pointer changed during valstack resize: %p -> %p", (void *) pre_alloc_end, (void *) thr->valstack_alloc_end)); } #endif /* Assertions: offsets for bottom, top, and end (reserve) must not * have changed even with side effects because they are always * restored in unwind. For alloc_end there's no guarantee: it may * have grown or shrunk (but remain above 'end'). */ DUK_ASSERT(thr->valstack_bottom - thr->valstack == pre_bottom - pre_valstack); DUK_ASSERT(thr->valstack_top - thr->valstack == pre_top - pre_valstack); DUK_ASSERT(thr->valstack_end - thr->valstack == pre_end - pre_valstack); DUK_ASSERT(thr->valstack_alloc_end >= thr->valstack_end); /* Write new pointers. Most pointers can be handled as a pointer * difference. */ ptr_diff = (duk_ptrdiff_t) ((duk_uint8_t *) new_valstack - (duk_uint8_t *) thr->valstack); tv_prev_alloc_end = (duk_tval *) (void *) ((duk_uint8_t *) thr->valstack_alloc_end + ptr_diff); thr->valstack = new_valstack; thr->valstack_bottom = (duk_tval *) (void *) ((duk_uint8_t *) thr->valstack_bottom + ptr_diff); thr->valstack_top = (duk_tval *) (void *) ((duk_uint8_t *) thr->valstack_top + ptr_diff); thr->valstack_end = (duk_tval *) (void *) ((duk_uint8_t *) thr->valstack_end + ptr_diff); thr->valstack_alloc_end = (duk_tval *) (void *) ((duk_uint8_t *) new_valstack + new_alloc_size); /* Assertions: pointer sanity after pointer updates. */ DUK_ASSERT(thr->valstack_bottom >= thr->valstack); DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom); DUK_ASSERT(thr->valstack_end >= thr->valstack_top); DUK_ASSERT(thr->valstack_alloc_end >= thr->valstack_end); DUK_D(DUK_DPRINT("resized valstack %lu -> %lu elements (%lu -> %lu bytes): " "base=%p -> %p, bottom=%p -> %p (%ld), top=%p -> %p (%ld), " "end=%p -> %p (%ld), alloc_end=%p -> %p (%ld);" " tv_prev_alloc_end=%p (-> %ld inits; <0 means shrink)", (unsigned long) (pre_alloc_end - pre_valstack), (unsigned long) new_size, (unsigned long) ((duk_uint8_t *) pre_alloc_end - (duk_uint8_t *) pre_valstack), (unsigned long) new_alloc_size, (void *) pre_valstack, (void *) thr->valstack, (void *) pre_bottom, (void *) thr->valstack_bottom, (long) (thr->valstack_bottom - thr->valstack), (void *) pre_top, (void *) thr->valstack_top, (long) (thr->valstack_top - thr->valstack), (void *) pre_end, (void *) thr->valstack_end, (long) (thr->valstack_end - thr->valstack), (void *) pre_alloc_end, (void *) thr->valstack_alloc_end, (long) (thr->valstack_alloc_end - thr->valstack), (void *) tv_prev_alloc_end, (long) (thr->valstack_alloc_end - tv_prev_alloc_end))); /* If allocation grew, init any new slots to 'undefined'. */ p = tv_prev_alloc_end; while (p < thr->valstack_alloc_end) { /* Never executed if new size is smaller. */ DUK_TVAL_SET_UNDEFINED(p); p++; } /* Assert for value stack initialization policy. */ #if defined(DUK_USE_ASSERTIONS) p = thr->valstack_top; while (p < thr->valstack_alloc_end) { DUK_ASSERT(DUK_TVAL_IS_UNDEFINED(p)); p++; } #endif return 1; } DUK_LOCAL DUK_COLD DUK_NOINLINE duk_bool_t duk__valstack_grow(duk_hthread *thr, duk_size_t min_bytes, duk_bool_t throw_on_error) { duk_size_t min_size; duk_size_t new_size; DUK_ASSERT(min_bytes / sizeof(duk_tval) * sizeof(duk_tval) == min_bytes); min_size = min_bytes / sizeof(duk_tval); /* from bytes to slots */ #if defined(DUK_USE_VALSTACK_GROW_SHIFT) /* New size is minimum size plus a proportional slack, e.g. shift of * 2 means a 25% slack. */ new_size = min_size + (min_size >> DUK_USE_VALSTACK_GROW_SHIFT); #else /* New size is tight with no slack. This is sometimes preferred in * low memory environments. */ new_size = min_size; #endif if (DUK_UNLIKELY(new_size > DUK_USE_VALSTACK_LIMIT || new_size < min_size /*wrap*/)) { /* Note: may be triggered even if minimal new_size would not reach the limit, * plan limit accordingly. */ if (throw_on_error) { DUK_ERROR_RANGE(thr, DUK_STR_VALSTACK_LIMIT); DUK_WO_NORETURN(return 0;); } return 0; } if (duk__resize_valstack(thr, new_size) == 0) { if (throw_on_error) { DUK_ERROR_ALLOC_FAILED(thr); DUK_WO_NORETURN(return 0;); } return 0; } thr->valstack_end = thr->valstack + min_size; DUK_ASSERT(thr->valstack_alloc_end >= thr->valstack_end); return 1; } /* Hot, inlined value stack grow check. Because value stack almost never * grows, the actual resize call is in a NOINLINE helper. */ DUK_INTERNAL DUK_INLINE void duk_valstack_grow_check_throw(duk_hthread *thr, duk_size_t min_bytes) { duk_tval *tv; tv = (duk_tval *) (void *) ((duk_uint8_t *) thr->valstack + min_bytes); if (DUK_LIKELY(thr->valstack_end >= tv)) { return; } if (DUK_LIKELY(thr->valstack_alloc_end >= tv)) { /* Values in [valstack_top,valstack_alloc_end[ are initialized * to 'undefined' so we can just move the end pointer. */ thr->valstack_end = tv; return; } (void) duk__valstack_grow(thr, min_bytes, 1 /*throw_on_error*/); } /* Hot, inlined value stack grow check which doesn't throw. */ DUK_INTERNAL DUK_INLINE duk_bool_t duk_valstack_grow_check_nothrow(duk_hthread *thr, duk_size_t min_bytes) { duk_tval *tv; tv = (duk_tval *) (void *) ((duk_uint8_t *) thr->valstack + min_bytes); if (DUK_LIKELY(thr->valstack_end >= tv)) { return 1; } if (DUK_LIKELY(thr->valstack_alloc_end >= tv)) { thr->valstack_end = tv; return 1; } return duk__valstack_grow(thr, min_bytes, 0 /*throw_on_error*/); } /* Value stack shrink check, called from mark-and-sweep. */ DUK_INTERNAL void duk_valstack_shrink_check_nothrow(duk_hthread *thr, duk_bool_t snug) { duk_size_t alloc_bytes; duk_size_t reserve_bytes; duk_size_t shrink_bytes; alloc_bytes = (duk_size_t) ((duk_uint8_t *) thr->valstack_alloc_end - (duk_uint8_t *) thr->valstack); reserve_bytes = (duk_size_t) ((duk_uint8_t *) thr->valstack_end - (duk_uint8_t *) thr->valstack); DUK_ASSERT(alloc_bytes >= reserve_bytes); /* We're free to shrink the value stack allocation down to * reserve_bytes but not more. If 'snug' (emergency GC) * shrink whatever we can. Otherwise only shrink if the new * size would be considerably smaller. */ #if defined(DUK_USE_VALSTACK_SHRINK_CHECK_SHIFT) if (snug) { shrink_bytes = reserve_bytes; } else { duk_size_t proportion, slack; /* Require that value stack shrinks by at least X% of its * current size. For example, shift of 2 means at least * 25%. The proportion is computed as bytes and may not * be a multiple of sizeof(duk_tval); that's OK here. */ proportion = alloc_bytes >> DUK_USE_VALSTACK_SHRINK_CHECK_SHIFT; if (alloc_bytes - reserve_bytes < proportion) { /* Too little would be freed, do nothing. */ return; } /* Keep a slack after shrinking. The slack is again a * proportion of the current size (the proportion should * of course be smaller than the check proportion above). */ #if defined(DUK_USE_VALSTACK_SHRINK_SLACK_SHIFT) DUK_ASSERT(DUK_USE_VALSTACK_SHRINK_SLACK_SHIFT > DUK_USE_VALSTACK_SHRINK_CHECK_SHIFT); slack = alloc_bytes >> DUK_USE_VALSTACK_SHRINK_SLACK_SHIFT; #else slack = 0; #endif shrink_bytes = reserve_bytes + slack / sizeof(duk_tval) * sizeof(duk_tval); /* multiple of duk_tval */ } #else /* DUK_USE_VALSTACK_SHRINK_CHECK_SHIFT */ /* Always snug, useful in some low memory environments. */ DUK_UNREF(snug); shrink_bytes = reserve_bytes; #endif /* DUK_USE_VALSTACK_SHRINK_CHECK_SHIFT */ DUK_D(DUK_DPRINT("valstack shrink check: alloc_bytes=%ld, reserve_bytes=%ld, shrink_bytes=%ld (unvalidated)", (long) alloc_bytes, (long) reserve_bytes, (long) shrink_bytes)); DUK_ASSERT(shrink_bytes >= reserve_bytes); if (shrink_bytes >= alloc_bytes) { /* Skip if shrink target is same as current one (or higher, * though that shouldn't happen in practice). */ return; } DUK_ASSERT(shrink_bytes / sizeof(duk_tval) * sizeof(duk_tval) == shrink_bytes); DUK_D(DUK_DPRINT("valstack shrink check: decided to shrink, snug: %ld", (long) snug)); duk__resize_valstack(thr, shrink_bytes / sizeof(duk_tval)); } DUK_EXTERNAL duk_bool_t duk_check_stack(duk_hthread *thr, duk_idx_t extra) { duk_size_t min_new_bytes; DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(thr != NULL); if (DUK_UNLIKELY(extra < 0 || extra > DUK_USE_VALSTACK_LIMIT)) { if (extra < 0) { /* Clamping to zero makes the API more robust to calling code * calculation errors. */ extra = 0; } else { /* Cause grow check to fail without wrapping arithmetic. */ extra = DUK_USE_VALSTACK_LIMIT; } } min_new_bytes = (duk_size_t) ((duk_uint8_t *) thr->valstack_top - (duk_uint8_t *) thr->valstack) + sizeof(duk_tval) * ((duk_size_t) extra + DUK_VALSTACK_INTERNAL_EXTRA); return duk_valstack_grow_check_nothrow(thr, min_new_bytes); } DUK_EXTERNAL void duk_require_stack(duk_hthread *thr, duk_idx_t extra) { duk_size_t min_new_bytes; DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(thr != NULL); if (DUK_UNLIKELY(extra < 0 || extra > DUK_USE_VALSTACK_LIMIT)) { if (extra < 0) { /* Clamping to zero makes the API more robust to calling code * calculation errors. */ extra = 0; } else { /* Cause grow check to fail without wrapping arithmetic. */ extra = DUK_USE_VALSTACK_LIMIT; } } min_new_bytes = (duk_size_t) ((duk_uint8_t *) thr->valstack_top - (duk_uint8_t *) thr->valstack) + sizeof(duk_tval) * ((duk_size_t) extra + DUK_VALSTACK_INTERNAL_EXTRA); duk_valstack_grow_check_throw(thr, min_new_bytes); } DUK_EXTERNAL duk_bool_t duk_check_stack_top(duk_hthread *thr, duk_idx_t top) { duk_size_t min_new_bytes; DUK_ASSERT_API_ENTRY(thr); if (DUK_UNLIKELY(top < 0 || top > DUK_USE_VALSTACK_LIMIT)) { if (top < 0) { /* Clamping to zero makes the API more robust to calling code * calculation errors. */ top = 0; } else { /* Cause grow check to fail without wrapping arithmetic. */ top = DUK_USE_VALSTACK_LIMIT; } } DUK_ASSERT(top >= 0); min_new_bytes = (duk_size_t) ((duk_uint8_t *) thr->valstack_bottom - (duk_uint8_t *) thr->valstack) + sizeof(duk_tval) * ((duk_size_t) top + DUK_VALSTACK_INTERNAL_EXTRA); return duk_valstack_grow_check_nothrow(thr, min_new_bytes); } DUK_EXTERNAL void duk_require_stack_top(duk_hthread *thr, duk_idx_t top) { duk_size_t min_new_bytes; DUK_ASSERT_API_ENTRY(thr); if (DUK_UNLIKELY(top < 0 || top > DUK_USE_VALSTACK_LIMIT)) { if (top < 0) { /* Clamping to zero makes the API more robust to calling code * calculation errors. */ top = 0; } else { /* Cause grow check to fail without wrapping arithmetic. */ top = DUK_USE_VALSTACK_LIMIT; } } DUK_ASSERT(top >= 0); min_new_bytes = (duk_size_t) ((duk_uint8_t *) thr->valstack_bottom - (duk_uint8_t *) thr->valstack) + sizeof(duk_tval) * ((duk_size_t) top + DUK_VALSTACK_INTERNAL_EXTRA); duk_valstack_grow_check_throw(thr, min_new_bytes); } /* * Basic stack manipulation: swap, dup, insert, replace, etc */ DUK_EXTERNAL void duk_swap(duk_hthread *thr, duk_idx_t idx1, duk_idx_t idx2) { duk_tval *tv1; duk_tval *tv2; duk_tval tv_tmp; DUK_ASSERT_API_ENTRY(thr); tv1 = duk_require_tval(thr, idx1); DUK_ASSERT(tv1 != NULL); tv2 = duk_require_tval(thr, idx2); DUK_ASSERT(tv2 != NULL); /* If tv1==tv2 this is a NOP, no check is needed */ DUK_TVAL_SET_TVAL(&tv_tmp, tv1); DUK_TVAL_SET_TVAL(tv1, tv2); DUK_TVAL_SET_TVAL(tv2, &tv_tmp); } DUK_EXTERNAL void duk_swap_top(duk_hthread *thr, duk_idx_t idx) { DUK_ASSERT_API_ENTRY(thr); duk_swap(thr, idx, -1); } DUK_EXTERNAL void duk_dup(duk_hthread *thr, duk_idx_t from_idx) { duk_tval *tv_from; duk_tval *tv_to; DUK_ASSERT_API_ENTRY(thr); DUK__CHECK_SPACE(); tv_from = duk_require_tval(thr, from_idx); tv_to = thr->valstack_top++; DUK_ASSERT(tv_from != NULL); DUK_ASSERT(tv_to != NULL); DUK_TVAL_SET_TVAL(tv_to, tv_from); DUK_TVAL_INCREF(thr, tv_to); /* no side effects */ } DUK_EXTERNAL void duk_dup_top(duk_hthread *thr) { #if defined(DUK_USE_PREFER_SIZE) duk_dup(thr, -1); #else duk_tval *tv_from; duk_tval *tv_to; DUK_ASSERT_API_ENTRY(thr); DUK__CHECK_SPACE(); if (DUK_UNLIKELY(thr->valstack_top - thr->valstack_bottom <= 0)) { DUK_ERROR_RANGE_INDEX(thr, -1); DUK_WO_NORETURN(return;); } tv_from = thr->valstack_top - 1; tv_to = thr->valstack_top++; DUK_ASSERT(tv_from != NULL); DUK_ASSERT(tv_to != NULL); DUK_TVAL_SET_TVAL(tv_to, tv_from); DUK_TVAL_INCREF(thr, tv_to); /* no side effects */ #endif } DUK_INTERNAL void duk_dup_0(duk_hthread *thr) { DUK_ASSERT_API_ENTRY(thr); duk_dup(thr, 0); } DUK_INTERNAL void duk_dup_1(duk_hthread *thr) { DUK_ASSERT_API_ENTRY(thr); duk_dup(thr, 1); } DUK_INTERNAL void duk_dup_2(duk_hthread *thr) { DUK_ASSERT_API_ENTRY(thr); duk_dup(thr, 2); } DUK_INTERNAL void duk_dup_m2(duk_hthread *thr) { DUK_ASSERT_API_ENTRY(thr); duk_dup(thr, -2); } DUK_INTERNAL void duk_dup_m3(duk_hthread *thr) { DUK_ASSERT_API_ENTRY(thr); duk_dup(thr, -3); } DUK_INTERNAL void duk_dup_m4(duk_hthread *thr) { DUK_ASSERT_API_ENTRY(thr); duk_dup(thr, -4); } DUK_EXTERNAL void duk_insert(duk_hthread *thr, duk_idx_t to_idx) { duk_tval *p; duk_tval *q; duk_tval tv_tmp; duk_size_t nbytes; DUK_ASSERT_API_ENTRY(thr); p = duk_require_tval(thr, to_idx); DUK_ASSERT(p != NULL); q = duk_require_tval(thr, -1); DUK_ASSERT(q != NULL); DUK_ASSERT(q >= p); /* nbytes * <---------> * [ ... | p | x | x | q ] * => [ ... | q | p | x | x ] */ nbytes = (duk_size_t) (((duk_uint8_t *) q) - ((duk_uint8_t *) p)); DUK_DDD(DUK_DDDPRINT("duk_insert: to_idx=%ld, p=%p, q=%p, nbytes=%lu", (long) to_idx, (void *) p, (void *) q, (unsigned long) nbytes)); /* No net refcount changes. No need to special case nbytes == 0 * (p == q). */ DUK_TVAL_SET_TVAL(&tv_tmp, q); duk_memmove((void *) (p + 1), (const void *) p, (size_t) nbytes); DUK_TVAL_SET_TVAL(p, &tv_tmp); } DUK_INTERNAL void duk_insert_undefined(duk_hthread *thr, duk_idx_t idx) { DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(idx >= 0); /* Doesn't support negative indices. */ duk_push_undefined(thr); duk_insert(thr, idx); } DUK_INTERNAL void duk_insert_undefined_n(duk_hthread *thr, duk_idx_t idx, duk_idx_t count) { duk_tval *tv, *tv_end; DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(idx >= 0); /* Doesn't support negative indices or count. */ DUK_ASSERT(count >= 0); tv = duk_reserve_gap(thr, idx, count); tv_end = tv + count; while (tv != tv_end) { DUK_TVAL_SET_UNDEFINED(tv); tv++; } } DUK_EXTERNAL void duk_pull(duk_hthread *thr, duk_idx_t from_idx) { duk_tval *p; duk_tval *q; duk_tval tv_tmp; duk_size_t nbytes; DUK_ASSERT_API_ENTRY(thr); /* nbytes * <---------> * [ ... | x | x | p | y | y | q ] * => [ ... | x | x | y | y | q | p ] */ p = duk_require_tval(thr, from_idx); DUK_ASSERT(p != NULL); q = duk_require_tval(thr, -1); DUK_ASSERT(q != NULL); DUK_ASSERT(q >= p); nbytes = (duk_size_t) (((duk_uint8_t *) q) - ((duk_uint8_t *) p)); DUK_DDD(DUK_DDDPRINT("duk_pull: from_idx=%ld, p=%p, q=%p, nbytes=%lu", (long) from_idx, (void *) p, (void *) q, (unsigned long) nbytes)); /* No net refcount changes. No need to special case nbytes == 0 * (p == q). */ DUK_TVAL_SET_TVAL(&tv_tmp, p); duk_memmove((void *) p, (const void *) (p + 1), (size_t) nbytes); DUK_TVAL_SET_TVAL(q, &tv_tmp); } DUK_EXTERNAL void duk_replace(duk_hthread *thr, duk_idx_t to_idx) { duk_tval *tv1; duk_tval *tv2; duk_tval tv_tmp; DUK_ASSERT_API_ENTRY(thr); tv1 = duk_require_tval(thr, -1); DUK_ASSERT(tv1 != NULL); tv2 = duk_require_tval(thr, to_idx); DUK_ASSERT(tv2 != NULL); /* For tv1 == tv2, both pointing to stack top, the end result * is same as duk_pop(thr). */ DUK_TVAL_SET_TVAL(&tv_tmp, tv2); DUK_TVAL_SET_TVAL(tv2, tv1); DUK_TVAL_SET_UNDEFINED(tv1); thr->valstack_top--; DUK_TVAL_DECREF(thr, &tv_tmp); /* side effects */ } DUK_EXTERNAL void duk_copy(duk_hthread *thr, duk_idx_t from_idx, duk_idx_t to_idx) { duk_tval *tv1; duk_tval *tv2; DUK_ASSERT_API_ENTRY(thr); tv1 = duk_require_tval(thr, from_idx); DUK_ASSERT(tv1 != NULL); tv2 = duk_require_tval(thr, to_idx); DUK_ASSERT(tv2 != NULL); /* For tv1 == tv2, this is a no-op (no explicit check needed). */ DUK_TVAL_SET_TVAL_UPDREF(thr, tv2, tv1); /* side effects */ } DUK_EXTERNAL void duk_remove(duk_hthread *thr, duk_idx_t idx) { duk_tval *p; duk_tval *q; #if defined(DUK_USE_REFERENCE_COUNTING) duk_tval tv_tmp; #endif duk_size_t nbytes; DUK_ASSERT_API_ENTRY(thr); p = duk_require_tval(thr, idx); DUK_ASSERT(p != NULL); q = duk_require_tval(thr, -1); DUK_ASSERT(q != NULL); DUK_ASSERT(q >= p); /* nbytes zero size case * <---------> * [ ... | p | x | x | q ] [ ... | p==q ] * => [ ... | x | x | q ] [ ... ] */ #if defined(DUK_USE_REFERENCE_COUNTING) /* use a temp: decref only when valstack reachable values are correct */ DUK_TVAL_SET_TVAL(&tv_tmp, p); #endif nbytes = (duk_size_t) (((duk_uint8_t *) q) - ((duk_uint8_t *) p)); /* Note: 'q' is top-1 */ duk_memmove((void *) p, (const void *) (p + 1), (size_t) nbytes); DUK_TVAL_SET_UNDEFINED(q); thr->valstack_top--; #if defined(DUK_USE_REFERENCE_COUNTING) DUK_TVAL_DECREF(thr, &tv_tmp); /* side effects */ #endif } DUK_INTERNAL void duk_remove_unsafe(duk_hthread *thr, duk_idx_t idx) { DUK_ASSERT_API_ENTRY(thr); duk_remove(thr, idx); /* XXX: no optimization for now */ } DUK_INTERNAL void duk_remove_m2(duk_hthread *thr) { DUK_ASSERT_API_ENTRY(thr); duk_remove(thr, -2); } DUK_INTERNAL void duk_remove_n(duk_hthread *thr, duk_idx_t idx, duk_idx_t count) { #if defined(DUK_USE_PREFER_SIZE) /* XXX: maybe too slow even when preferring size? */ DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(count >= 0); DUK_ASSERT(idx >= 0); while (count-- > 0) { duk_remove(thr, idx); } #else /* DUK_USE_PREFER_SIZE */ duk_tval *tv_src; duk_tval *tv_dst; duk_tval *tv_newtop; duk_tval *tv; duk_size_t bytes; DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(count >= 0); DUK_ASSERT(idx >= 0); tv_dst = thr->valstack_bottom + idx; DUK_ASSERT(tv_dst <= thr->valstack_top); tv_src = tv_dst + count; DUK_ASSERT(tv_src <= thr->valstack_top); bytes = (duk_size_t) ((duk_uint8_t *) thr->valstack_top - (duk_uint8_t *) tv_src); for (tv = tv_dst; tv < tv_src; tv++) { DUK_TVAL_DECREF_NORZ(thr, tv); } duk_memmove((void *) tv_dst, (const void *) tv_src, bytes); tv_newtop = thr->valstack_top - count; for (tv = tv_newtop; tv < thr->valstack_top; tv++) { DUK_TVAL_SET_UNDEFINED(tv); } thr->valstack_top = tv_newtop; /* When not preferring size, only NORZ macros are used; caller * is expected to DUK_REFZERO_CHECK(). */ #endif /* DUK_USE_PREFER_SIZE */ } DUK_INTERNAL void duk_remove_n_unsafe(duk_hthread *thr, duk_idx_t idx, duk_idx_t count) { DUK_ASSERT_API_ENTRY(thr); duk_remove_n(thr, idx, count); /* XXX: no optimization for now */ } /* * Stack slice primitives */ DUK_EXTERNAL void duk_xcopymove_raw(duk_hthread *to_thr, duk_hthread *from_thr, duk_idx_t count, duk_bool_t is_copy) { void *src; duk_size_t nbytes; duk_tval *p; duk_tval *q; /* XXX: several pointer comparison issues here */ DUK_ASSERT_API_ENTRY(to_thr); DUK_CTX_ASSERT_VALID(to_thr); DUK_CTX_ASSERT_VALID(from_thr); DUK_ASSERT(to_thr->heap == from_thr->heap); if (DUK_UNLIKELY(to_thr == from_thr)) { DUK_ERROR_TYPE(to_thr, DUK_STR_INVALID_CONTEXT); DUK_WO_NORETURN(return;); } if (DUK_UNLIKELY((duk_uidx_t) count > (duk_uidx_t) DUK_USE_VALSTACK_LIMIT)) { /* Maximum value check ensures 'nbytes' won't wrap below. * Also handles negative count. */ DUK_ERROR_RANGE_INVALID_COUNT(to_thr); DUK_WO_NORETURN(return;); } DUK_ASSERT(count >= 0); nbytes = sizeof(duk_tval) * (duk_size_t) count; if (DUK_UNLIKELY(nbytes == 0)) { return; } DUK_ASSERT(to_thr->valstack_top <= to_thr->valstack_end); if (DUK_UNLIKELY((duk_size_t) ((duk_uint8_t *) to_thr->valstack_end - (duk_uint8_t *) to_thr->valstack_top) < nbytes)) { DUK_ERROR_RANGE_PUSH_BEYOND(to_thr); DUK_WO_NORETURN(return;); } src = (void *) ((duk_uint8_t *) from_thr->valstack_top - nbytes); if (DUK_UNLIKELY(src < (void *) from_thr->valstack_bottom)) { DUK_ERROR_RANGE_INVALID_COUNT(to_thr); DUK_WO_NORETURN(return;); } /* Copy values (no overlap even if to_thr == from_thr; that's not * allowed now anyway). */ DUK_ASSERT(nbytes > 0); duk_memcpy((void *) to_thr->valstack_top, (const void *) src, (size_t) nbytes); p = to_thr->valstack_top; to_thr->valstack_top = (duk_tval *) (void *) (((duk_uint8_t *) p) + nbytes); if (is_copy) { /* Incref copies, keep originals. */ q = to_thr->valstack_top; while (p < q) { DUK_TVAL_INCREF(to_thr, p); /* no side effects */ p++; } } else { /* No net refcount change. */ p = from_thr->valstack_top; q = (duk_tval *) (void *) (((duk_uint8_t *) p) - nbytes); from_thr->valstack_top = q; while (p > q) { p--; DUK_TVAL_SET_UNDEFINED(p); /* XXX: fast primitive to set a bunch of values to UNDEFINED */ } } } /* Internal helper: reserve a gap of 'count' elements at 'idx_base' and return a * pointer to the gap. Values in the gap are garbage and MUST be initialized by * the caller before any side effects may occur. The caller must ensure there's * enough stack reserve for 'count' values. */ DUK_INTERNAL duk_tval *duk_reserve_gap(duk_hthread *thr, duk_idx_t idx_base, duk_idx_t count) { duk_tval *tv_src; duk_tval *tv_dst; duk_size_t gap_bytes; duk_size_t copy_bytes; /* Caller is responsible for ensuring there's enough preallocated * value stack. */ DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(count >= 0); DUK_ASSERT((duk_size_t) (thr->valstack_end - thr->valstack_top) >= (duk_size_t) count); tv_src = thr->valstack_bottom + idx_base; gap_bytes = (duk_size_t) count * sizeof(duk_tval); tv_dst = (duk_tval *) (void *) ((duk_uint8_t *) tv_src + gap_bytes); copy_bytes = (duk_size_t) ((duk_uint8_t *) thr->valstack_top - (duk_uint8_t *) tv_src); thr->valstack_top = (duk_tval *) (void *) ((duk_uint8_t *) thr->valstack_top + gap_bytes); duk_memmove((void *) tv_dst, (const void *) tv_src, copy_bytes); /* Values in the gap are left as garbage: caller must fill them in * and INCREF them before any side effects. */ return tv_src; } /* * Get/opt/require */ DUK_EXTERNAL void duk_require_undefined(duk_hthread *thr, duk_idx_t idx) { duk_tval *tv; DUK_ASSERT_API_ENTRY(thr); tv = duk_get_tval_or_unused(thr, idx); DUK_ASSERT(tv != NULL); if (DUK_UNLIKELY(!DUK_TVAL_IS_UNDEFINED(tv))) { DUK_ERROR_REQUIRE_TYPE_INDEX(thr, idx, "undefined", DUK_STR_NOT_UNDEFINED); DUK_WO_NORETURN(return;); } } DUK_EXTERNAL void duk_require_null(duk_hthread *thr, duk_idx_t idx) { duk_tval *tv; DUK_ASSERT_API_ENTRY(thr); tv = duk_get_tval_or_unused(thr, idx); DUK_ASSERT(tv != NULL); if (DUK_UNLIKELY(!DUK_TVAL_IS_NULL(tv))) { DUK_ERROR_REQUIRE_TYPE_INDEX(thr, idx, "null", DUK_STR_NOT_NULL); DUK_WO_NORETURN(return;); } } DUK_LOCAL DUK_ALWAYS_INLINE duk_bool_t duk__get_boolean_raw(duk_hthread *thr, duk_idx_t idx, duk_bool_t def_value) { duk_bool_t ret; duk_tval *tv; DUK_CTX_ASSERT_VALID(thr); tv = duk_get_tval_or_unused(thr, idx); DUK_ASSERT(tv != NULL); if (DUK_TVAL_IS_BOOLEAN(tv)) { ret = DUK_TVAL_GET_BOOLEAN(tv); DUK_ASSERT(ret == 0 || ret == 1); } else { ret = def_value; /* Not guaranteed to be 0 or 1. */ } return ret; } DUK_EXTERNAL duk_bool_t duk_get_boolean(duk_hthread *thr, duk_idx_t idx) { DUK_ASSERT_API_ENTRY(thr); return duk__get_boolean_raw(thr, idx, 0); /* default: false */ } DUK_EXTERNAL duk_bool_t duk_get_boolean_default(duk_hthread *thr, duk_idx_t idx, duk_bool_t def_value) { DUK_ASSERT_API_ENTRY(thr); return duk__get_boolean_raw(thr, idx, def_value); } DUK_EXTERNAL duk_bool_t duk_require_boolean(duk_hthread *thr, duk_idx_t idx) { duk_tval *tv; duk_bool_t ret; DUK_ASSERT_API_ENTRY(thr); tv = duk_get_tval_or_unused(thr, idx); DUK_ASSERT(tv != NULL); if (DUK_LIKELY(DUK_TVAL_IS_BOOLEAN(tv))) { ret = DUK_TVAL_GET_BOOLEAN(tv); DUK_ASSERT(ret == 0 || ret == 1); return ret; } else { DUK_ERROR_REQUIRE_TYPE_INDEX(thr, idx, "boolean", DUK_STR_NOT_BOOLEAN); DUK_WO_NORETURN(return 0;); } } DUK_EXTERNAL duk_bool_t duk_opt_boolean(duk_hthread *thr, duk_idx_t idx, duk_bool_t def_value) { DUK_ASSERT_API_ENTRY(thr); if (duk_check_type_mask(thr, idx, DUK_TYPE_MASK_NONE | DUK_TYPE_MASK_UNDEFINED)) { return def_value; } return duk_require_boolean(thr, idx); } DUK_LOCAL DUK_ALWAYS_INLINE duk_double_t duk__get_number_raw(duk_hthread *thr, duk_idx_t idx, duk_double_t def_value) { duk_double_union ret; duk_tval *tv; DUK_CTX_ASSERT_VALID(thr); tv = duk_get_tval_or_unused(thr, idx); DUK_ASSERT(tv != NULL); #if defined(DUK_USE_FASTINT) if (DUK_TVAL_IS_FASTINT(tv)) { ret.d = (duk_double_t) DUK_TVAL_GET_FASTINT(tv); /* XXX: cast trick */ } else #endif if (DUK_TVAL_IS_DOUBLE(tv)) { /* When using packed duk_tval, number must be in NaN-normalized form * for it to be a duk_tval, so no need to normalize. NOP for unpacked * duk_tval. */ ret.d = DUK_TVAL_GET_DOUBLE(tv); DUK_ASSERT(DUK_DBLUNION_IS_NORMALIZED(&ret)); } else { ret.d = def_value; /* Default value (including NaN) may not be normalized. */ } return ret.d; } DUK_EXTERNAL duk_double_t duk_get_number(duk_hthread *thr, duk_idx_t idx) { DUK_ASSERT_API_ENTRY(thr); return duk__get_number_raw(thr, idx, DUK_DOUBLE_NAN); /* default: NaN */ } DUK_EXTERNAL duk_double_t duk_get_number_default(duk_hthread *thr, duk_idx_t idx, duk_double_t def_value) { DUK_ASSERT_API_ENTRY(thr); return duk__get_number_raw(thr, idx, def_value); } DUK_EXTERNAL duk_double_t duk_require_number(duk_hthread *thr, duk_idx_t idx) { duk_tval *tv; duk_double_union ret; DUK_ASSERT_API_ENTRY(thr); tv = duk_get_tval_or_unused(thr, idx); DUK_ASSERT(tv != NULL); if (DUK_UNLIKELY(!DUK_TVAL_IS_NUMBER(tv))) { DUK_ERROR_REQUIRE_TYPE_INDEX(thr, idx, "number", DUK_STR_NOT_NUMBER); DUK_WO_NORETURN(return 0.0;); } ret.d = DUK_TVAL_GET_NUMBER(tv); /* When using packed duk_tval, number must be in NaN-normalized form * for it to be a duk_tval, so no need to normalize. NOP for unpacked * duk_tval. */ DUK_ASSERT(DUK_DBLUNION_IS_NORMALIZED(&ret)); return ret.d; } DUK_EXTERNAL duk_double_t duk_opt_number(duk_hthread *thr, duk_idx_t idx, duk_double_t def_value) { DUK_ASSERT_API_ENTRY(thr); if (duk_check_type_mask(thr, idx, DUK_TYPE_MASK_NONE | DUK_TYPE_MASK_UNDEFINED)) { /* User provided default is not NaN normalized. */ return def_value; } return duk_require_number(thr, idx); } DUK_EXTERNAL duk_int_t duk_get_int(duk_hthread *thr, duk_idx_t idx) { DUK_ASSERT_API_ENTRY(thr); return (duk_int_t) duk__api_coerce_d2i(thr, idx, 0 /*def_value*/, 0 /*require*/); } DUK_EXTERNAL duk_uint_t duk_get_uint(duk_hthread *thr, duk_idx_t idx) { DUK_ASSERT_API_ENTRY(thr); return (duk_uint_t) duk__api_coerce_d2ui(thr, idx, 0 /*def_value*/, 0 /*require*/); } DUK_EXTERNAL duk_int_t duk_get_int_default(duk_hthread *thr, duk_idx_t idx, duk_int_t def_value) { DUK_ASSERT_API_ENTRY(thr); return (duk_int_t) duk__api_coerce_d2i(thr, idx, def_value, 0 /*require*/); } DUK_EXTERNAL duk_uint_t duk_get_uint_default(duk_hthread *thr, duk_idx_t idx, duk_uint_t def_value) { DUK_ASSERT_API_ENTRY(thr); return (duk_uint_t) duk__api_coerce_d2ui(thr, idx, def_value, 0 /*require*/); } DUK_EXTERNAL duk_int_t duk_require_int(duk_hthread *thr, duk_idx_t idx) { DUK_ASSERT_API_ENTRY(thr); return (duk_int_t) duk__api_coerce_d2i(thr, idx, 0 /*def_value*/, 1 /*require*/); } DUK_EXTERNAL duk_uint_t duk_require_uint(duk_hthread *thr, duk_idx_t idx) { DUK_ASSERT_API_ENTRY(thr); return (duk_uint_t) duk__api_coerce_d2ui(thr, idx, 0 /*def_value*/, 1 /*require*/); } DUK_EXTERNAL duk_int_t duk_opt_int(duk_hthread *thr, duk_idx_t idx, duk_int_t def_value) { DUK_ASSERT_API_ENTRY(thr); if (duk_check_type_mask(thr, idx, DUK_TYPE_MASK_NONE | DUK_TYPE_MASK_UNDEFINED)) { return def_value; } return duk_require_int(thr, idx); } DUK_EXTERNAL duk_uint_t duk_opt_uint(duk_hthread *thr, duk_idx_t idx, duk_uint_t def_value) { DUK_ASSERT_API_ENTRY(thr); if (duk_check_type_mask(thr, idx, DUK_TYPE_MASK_NONE | DUK_TYPE_MASK_UNDEFINED)) { return def_value; } return duk_require_uint(thr, idx); } DUK_EXTERNAL const char *duk_get_lstring(duk_hthread *thr, duk_idx_t idx, duk_size_t *out_len) { duk_hstring *h; const char *ret; duk_size_t len; DUK_ASSERT_API_ENTRY(thr); h = duk_get_hstring(thr, idx); if (h != NULL) { len = DUK_HSTRING_GET_BYTELEN(h); ret = (const char *) DUK_HSTRING_GET_DATA(h); } else { len = 0; ret = NULL; } if (out_len != NULL) { *out_len = len; } return ret; } DUK_EXTERNAL const char *duk_require_lstring(duk_hthread *thr, duk_idx_t idx, duk_size_t *out_len) { duk_hstring *h; DUK_ASSERT_API_ENTRY(thr); h = duk_require_hstring(thr, idx); DUK_ASSERT(h != NULL); if (out_len) { *out_len = DUK_HSTRING_GET_BYTELEN(h); } return (const char *) DUK_HSTRING_GET_DATA(h); } DUK_INTERNAL const char *duk_require_lstring_notsymbol(duk_hthread *thr, duk_idx_t idx, duk_size_t *out_len) { duk_hstring *h; DUK_ASSERT_API_ENTRY(thr); h = duk_require_hstring_notsymbol(thr, idx); DUK_ASSERT(h != NULL); if (out_len) { *out_len = DUK_HSTRING_GET_BYTELEN(h); } return (const char *) DUK_HSTRING_GET_DATA(h); } DUK_EXTERNAL const char *duk_get_string(duk_hthread *thr, duk_idx_t idx) { duk_hstring *h; DUK_ASSERT_API_ENTRY(thr); h = duk_get_hstring(thr, idx); if (h != NULL) { return (const char *) DUK_HSTRING_GET_DATA(h); } else { return NULL; } } DUK_EXTERNAL const char *duk_opt_lstring(duk_hthread *thr, duk_idx_t idx, duk_size_t *out_len, const char *def_ptr, duk_size_t def_len) { DUK_ASSERT_API_ENTRY(thr); if (duk_check_type_mask(thr, idx, DUK_TYPE_MASK_NONE | DUK_TYPE_MASK_UNDEFINED)) { if (out_len != NULL) { *out_len = def_len; } return def_ptr; } return duk_require_lstring(thr, idx, out_len); } DUK_EXTERNAL const char *duk_opt_string(duk_hthread *thr, duk_idx_t idx, const char *def_ptr) { DUK_ASSERT_API_ENTRY(thr); if (duk_check_type_mask(thr, idx, DUK_TYPE_MASK_NONE | DUK_TYPE_MASK_UNDEFINED)) { return def_ptr; } return duk_require_string(thr, idx); } DUK_EXTERNAL const char *duk_get_lstring_default(duk_hthread *thr, duk_idx_t idx, duk_size_t *out_len, const char *def_ptr, duk_size_t def_len) { duk_hstring *h; const char *ret; duk_size_t len; DUK_ASSERT_API_ENTRY(thr); h = duk_get_hstring(thr, idx); if (h != NULL) { len = DUK_HSTRING_GET_BYTELEN(h); ret = (const char *) DUK_HSTRING_GET_DATA(h); } else { len = def_len; ret = def_ptr; } if (out_len != NULL) { *out_len = len; } return ret; } DUK_EXTERNAL const char *duk_get_string_default(duk_hthread *thr, duk_idx_t idx, const char *def_value) { duk_hstring *h; DUK_ASSERT_API_ENTRY(thr); h = duk_get_hstring(thr, idx); if (h != NULL) { return (const char *) DUK_HSTRING_GET_DATA(h); } else { return def_value; } } DUK_INTERNAL const char *duk_get_string_notsymbol(duk_hthread *thr, duk_idx_t idx) { duk_hstring *h; DUK_ASSERT_API_ENTRY(thr); h = duk_get_hstring_notsymbol(thr, idx); if (h) { return (const char *) DUK_HSTRING_GET_DATA(h); } else { return NULL; } } DUK_EXTERNAL const char *duk_require_string(duk_hthread *thr, duk_idx_t idx) { DUK_ASSERT_API_ENTRY(thr); return duk_require_lstring(thr, idx, NULL); } DUK_INTERNAL const char *duk_require_string_notsymbol(duk_hthread *thr, duk_idx_t idx) { duk_hstring *h; DUK_ASSERT_API_ENTRY(thr); h = duk_require_hstring_notsymbol(thr, idx); DUK_ASSERT(h != NULL); return (const char *) DUK_HSTRING_GET_DATA(h); } DUK_EXTERNAL void duk_require_object(duk_hthread *thr, duk_idx_t idx) { duk_tval *tv; DUK_ASSERT_API_ENTRY(thr); tv = duk_get_tval_or_unused(thr, idx); DUK_ASSERT(tv != NULL); if (DUK_UNLIKELY(!DUK_TVAL_IS_OBJECT(tv))) { DUK_ERROR_REQUIRE_TYPE_INDEX(thr, idx, "object", DUK_STR_NOT_OBJECT); DUK_WO_NORETURN(return;); } } DUK_LOCAL void *duk__get_pointer_raw(duk_hthread *thr, duk_idx_t idx, void *def_value) { duk_tval *tv; void *p; DUK_CTX_ASSERT_VALID(thr); tv = duk_get_tval_or_unused(thr, idx); DUK_ASSERT(tv != NULL); if (!DUK_TVAL_IS_POINTER(tv)) { return def_value; } p = DUK_TVAL_GET_POINTER(tv); /* may be NULL */ return p; } DUK_EXTERNAL void *duk_get_pointer(duk_hthread *thr, duk_idx_t idx) { DUK_ASSERT_API_ENTRY(thr); return duk__get_pointer_raw(thr, idx, NULL /*def_value*/); } DUK_EXTERNAL void *duk_opt_pointer(duk_hthread *thr, duk_idx_t idx, void *def_value) { DUK_ASSERT_API_ENTRY(thr); if (duk_check_type_mask(thr, idx, DUK_TYPE_MASK_NONE | DUK_TYPE_MASK_UNDEFINED)) { return def_value; } return duk_require_pointer(thr, idx); } DUK_EXTERNAL void *duk_get_pointer_default(duk_hthread *thr, duk_idx_t idx, void *def_value) { DUK_ASSERT_API_ENTRY(thr); return duk__get_pointer_raw(thr, idx, def_value); } DUK_EXTERNAL void *duk_require_pointer(duk_hthread *thr, duk_idx_t idx) { duk_tval *tv; void *p; DUK_ASSERT_API_ENTRY(thr); /* Note: here we must be wary of the fact that a pointer may be * valid and be a NULL. */ tv = duk_get_tval_or_unused(thr, idx); DUK_ASSERT(tv != NULL); if (DUK_UNLIKELY(!DUK_TVAL_IS_POINTER(tv))) { DUK_ERROR_REQUIRE_TYPE_INDEX(thr, idx, "pointer", DUK_STR_NOT_POINTER); DUK_WO_NORETURN(return NULL;); } p = DUK_TVAL_GET_POINTER(tv); /* may be NULL */ return p; } #if 0 /*unused*/ DUK_INTERNAL void *duk_get_voidptr(duk_hthread *thr, duk_idx_t idx) { duk_tval *tv; duk_heaphdr *h; DUK_ASSERT_API_ENTRY(thr); tv = duk_get_tval_or_unused(thr, idx); DUK_ASSERT(tv != NULL); if (!DUK_TVAL_IS_HEAP_ALLOCATED(tv)) { return NULL; } h = DUK_TVAL_GET_HEAPHDR(tv); DUK_ASSERT(h != NULL); return (void *) h; } #endif DUK_LOCAL void *duk__get_buffer_helper(duk_hthread *thr, duk_idx_t idx, duk_size_t *out_size, void *def_ptr, duk_size_t def_size, duk_bool_t throw_flag) { duk_hbuffer *h; void *ret; duk_size_t len; duk_tval *tv; DUK_CTX_ASSERT_VALID(thr); if (out_size != NULL) { *out_size = 0; } tv = duk_get_tval_or_unused(thr, idx); DUK_ASSERT(tv != NULL); if (DUK_LIKELY(DUK_TVAL_IS_BUFFER(tv))) { h = DUK_TVAL_GET_BUFFER(tv); DUK_ASSERT(h != NULL); len = DUK_HBUFFER_GET_SIZE(h); ret = DUK_HBUFFER_GET_DATA_PTR(thr->heap, h); } else { if (throw_flag) { DUK_ERROR_REQUIRE_TYPE_INDEX(thr, idx, "buffer", DUK_STR_NOT_BUFFER); DUK_WO_NORETURN(return NULL;); } len = def_size; ret = def_ptr; } if (out_size != NULL) { *out_size = len; } return ret; } DUK_EXTERNAL void *duk_get_buffer(duk_hthread *thr, duk_idx_t idx, duk_size_t *out_size) { DUK_ASSERT_API_ENTRY(thr); return duk__get_buffer_helper(thr, idx, out_size, NULL /*def_ptr*/, 0 /*def_size*/, 0 /*throw_flag*/); } DUK_EXTERNAL void *duk_opt_buffer(duk_hthread *thr, duk_idx_t idx, duk_size_t *out_size, void *def_ptr, duk_size_t def_size) { DUK_ASSERT_API_ENTRY(thr); if (duk_check_type_mask(thr, idx, DUK_TYPE_MASK_NONE | DUK_TYPE_MASK_UNDEFINED)) { if (out_size != NULL) { *out_size = def_size; } return def_ptr; } return duk_require_buffer(thr, idx, out_size); } DUK_EXTERNAL void *duk_get_buffer_default(duk_hthread *thr, duk_idx_t idx, duk_size_t *out_size, void *def_ptr, duk_size_t def_len) { DUK_ASSERT_API_ENTRY(thr); return duk__get_buffer_helper(thr, idx, out_size, def_ptr, def_len, 0 /*throw_flag*/); } DUK_EXTERNAL void *duk_require_buffer(duk_hthread *thr, duk_idx_t idx, duk_size_t *out_size) { DUK_ASSERT_API_ENTRY(thr); return duk__get_buffer_helper(thr, idx, out_size, NULL /*def_ptr*/, 0 /*def_size*/, 1 /*throw_flag*/); } /* Get the active buffer data area for a plain buffer or a buffer object. * Return NULL if the the value is not a buffer. Note that a buffer may * have a NULL data pointer when its size is zero, the optional 'out_isbuffer' * argument allows caller to detect this reliably. */ DUK_INTERNAL void *duk_get_buffer_data_raw(duk_hthread *thr, duk_idx_t idx, duk_size_t *out_size, void *def_ptr, duk_size_t def_size, duk_bool_t throw_flag, duk_bool_t *out_isbuffer) { duk_tval *tv; DUK_ASSERT_API_ENTRY(thr); if (out_isbuffer != NULL) { *out_isbuffer = 0; } if (out_size != NULL) { *out_size = def_size; } tv = duk_get_tval_or_unused(thr, idx); DUK_ASSERT(tv != NULL); if (DUK_TVAL_IS_BUFFER(tv)) { duk_hbuffer *h = DUK_TVAL_GET_BUFFER(tv); DUK_ASSERT(h != NULL); if (out_size != NULL) { *out_size = DUK_HBUFFER_GET_SIZE(h); } if (out_isbuffer != NULL) { *out_isbuffer = 1; } return (void *) DUK_HBUFFER_GET_DATA_PTR(thr->heap, h); /* may be NULL (but only if size is 0) */ } #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) else if (DUK_TVAL_IS_OBJECT(tv)) { duk_hobject *h = DUK_TVAL_GET_OBJECT(tv); DUK_ASSERT(h != NULL); if (DUK_HOBJECT_IS_BUFOBJ(h)) { /* XXX: this is probably a useful shared helper: for a * duk_hbufobj, get a validated buffer pointer/length. */ duk_hbufobj *h_bufobj = (duk_hbufobj *) h; DUK_HBUFOBJ_ASSERT_VALID(h_bufobj); if (h_bufobj->buf != NULL && DUK_HBUFOBJ_VALID_SLICE(h_bufobj)) { duk_uint8_t *p; p = (duk_uint8_t *) DUK_HBUFFER_GET_DATA_PTR(thr->heap, h_bufobj->buf); if (out_size != NULL) { *out_size = (duk_size_t) h_bufobj->length; } if (out_isbuffer != NULL) { *out_isbuffer = 1; } return (void *) (p + h_bufobj->offset); } /* if slice not fully valid, treat as error */ } } #endif /* DUK_USE_BUFFEROBJECT_SUPPORT */ if (throw_flag) { DUK_ERROR_REQUIRE_TYPE_INDEX(thr, idx, "buffer", DUK_STR_NOT_BUFFER); DUK_WO_NORETURN(return NULL;); } return def_ptr; } DUK_EXTERNAL void *duk_get_buffer_data(duk_hthread *thr, duk_idx_t idx, duk_size_t *out_size) { DUK_ASSERT_API_ENTRY(thr); return duk_get_buffer_data_raw(thr, idx, out_size, NULL /*def_ptr*/, 0 /*def_size*/, 0 /*throw_flag*/, NULL); } DUK_EXTERNAL void *duk_get_buffer_data_default(duk_hthread *thr, duk_idx_t idx, duk_size_t *out_size, void *def_ptr, duk_size_t def_size) { DUK_ASSERT_API_ENTRY(thr); return duk_get_buffer_data_raw(thr, idx, out_size, def_ptr, def_size, 0 /*throw_flag*/, NULL); } DUK_EXTERNAL void *duk_opt_buffer_data(duk_hthread *thr, duk_idx_t idx, duk_size_t *out_size, void *def_ptr, duk_size_t def_size) { DUK_ASSERT_API_ENTRY(thr); if (duk_check_type_mask(thr, idx, DUK_TYPE_MASK_NONE | DUK_TYPE_MASK_UNDEFINED)) { if (out_size != NULL) { *out_size = def_size; } return def_ptr; } return duk_require_buffer_data(thr, idx, out_size); } DUK_EXTERNAL void *duk_require_buffer_data(duk_hthread *thr, duk_idx_t idx, duk_size_t *out_size) { DUK_ASSERT_API_ENTRY(thr); return duk_get_buffer_data_raw(thr, idx, out_size, NULL /*def_ptr*/, 0 /*def_size*/, 1 /*throw_flag*/, NULL); } /* Raw helper for getting a value from the stack, checking its tag. * The tag cannot be a number because numbers don't have an internal * tag in the packed representation. */ DUK_LOCAL duk_heaphdr *duk__get_tagged_heaphdr_raw(duk_hthread *thr, duk_idx_t idx, duk_uint_t tag) { duk_tval *tv; duk_heaphdr *ret; DUK_CTX_ASSERT_VALID(thr); tv = duk_get_tval_or_unused(thr, idx); DUK_ASSERT(tv != NULL); if (DUK_TVAL_GET_TAG(tv) != tag) { return (duk_heaphdr *) NULL; } ret = DUK_TVAL_GET_HEAPHDR(tv); DUK_ASSERT(ret != NULL); /* tagged null pointers should never occur */ return ret; } DUK_INTERNAL duk_hstring *duk_get_hstring(duk_hthread *thr, duk_idx_t idx) { DUK_ASSERT_API_ENTRY(thr); return (duk_hstring *) duk__get_tagged_heaphdr_raw(thr, idx, DUK_TAG_STRING); } DUK_INTERNAL duk_hstring *duk_get_hstring_notsymbol(duk_hthread *thr, duk_idx_t idx) { duk_hstring *h; DUK_ASSERT_API_ENTRY(thr); h = (duk_hstring *) duk__get_tagged_heaphdr_raw(thr, idx, DUK_TAG_STRING); if (DUK_UNLIKELY(h && DUK_HSTRING_HAS_SYMBOL(h))) { return NULL; } return h; } DUK_INTERNAL duk_hstring *duk_require_hstring(duk_hthread *thr, duk_idx_t idx) { duk_hstring *h; DUK_ASSERT_API_ENTRY(thr); h = (duk_hstring *) duk__get_tagged_heaphdr_raw(thr, idx, DUK_TAG_STRING); if (DUK_UNLIKELY(h == NULL)) { DUK_ERROR_REQUIRE_TYPE_INDEX(thr, idx, "string", DUK_STR_NOT_STRING); DUK_WO_NORETURN(return NULL;); } return h; } DUK_INTERNAL duk_hstring *duk_require_hstring_notsymbol(duk_hthread *thr, duk_idx_t idx) { duk_hstring *h; DUK_ASSERT_API_ENTRY(thr); h = (duk_hstring *) duk__get_tagged_heaphdr_raw(thr, idx, DUK_TAG_STRING); if (DUK_UNLIKELY(h == NULL || DUK_HSTRING_HAS_SYMBOL(h))) { DUK_ERROR_REQUIRE_TYPE_INDEX(thr, idx, "string", DUK_STR_NOT_STRING); DUK_WO_NORETURN(return NULL;); } return h; } DUK_INTERNAL duk_hobject *duk_get_hobject(duk_hthread *thr, duk_idx_t idx) { DUK_ASSERT_API_ENTRY(thr); return (duk_hobject *) duk__get_tagged_heaphdr_raw(thr, idx, DUK_TAG_OBJECT); } DUK_INTERNAL duk_hobject *duk_require_hobject(duk_hthread *thr, duk_idx_t idx) { duk_hobject *h; DUK_ASSERT_API_ENTRY(thr); h = (duk_hobject *) duk__get_tagged_heaphdr_raw(thr, idx, DUK_TAG_OBJECT); if (DUK_UNLIKELY(h == NULL)) { DUK_ERROR_REQUIRE_TYPE_INDEX(thr, idx, "object", DUK_STR_NOT_OBJECT); DUK_WO_NORETURN(return NULL;); } return h; } DUK_INTERNAL duk_hbuffer *duk_get_hbuffer(duk_hthread *thr, duk_idx_t idx) { DUK_ASSERT_API_ENTRY(thr); return (duk_hbuffer *) duk__get_tagged_heaphdr_raw(thr, idx, DUK_TAG_BUFFER); } DUK_INTERNAL duk_hbuffer *duk_require_hbuffer(duk_hthread *thr, duk_idx_t idx) { duk_hbuffer *h; DUK_ASSERT_API_ENTRY(thr); h = (duk_hbuffer *) duk__get_tagged_heaphdr_raw(thr, idx, DUK_TAG_BUFFER); if (DUK_UNLIKELY(h == NULL)) { DUK_ERROR_REQUIRE_TYPE_INDEX(thr, idx, "buffer", DUK_STR_NOT_BUFFER); DUK_WO_NORETURN(return NULL;); } return h; } DUK_INTERNAL duk_hthread *duk_get_hthread(duk_hthread *thr, duk_idx_t idx) { duk_hobject *h; DUK_ASSERT_API_ENTRY(thr); h = (duk_hobject *) duk__get_tagged_heaphdr_raw(thr, idx, DUK_TAG_OBJECT); if (DUK_UNLIKELY(h != NULL && !DUK_HOBJECT_IS_THREAD(h))) { h = NULL; } return (duk_hthread *) h; } DUK_INTERNAL duk_hthread *duk_require_hthread(duk_hthread *thr, duk_idx_t idx) { duk_hobject *h; DUK_ASSERT_API_ENTRY(thr); h = (duk_hobject *) duk__get_tagged_heaphdr_raw(thr, idx, DUK_TAG_OBJECT); if (DUK_UNLIKELY(!(h != NULL && DUK_HOBJECT_IS_THREAD(h)))) { DUK_ERROR_REQUIRE_TYPE_INDEX(thr, idx, "thread", DUK_STR_NOT_THREAD); DUK_WO_NORETURN(return NULL;); } return (duk_hthread *) h; } DUK_INTERNAL duk_hcompfunc *duk_get_hcompfunc(duk_hthread *thr, duk_idx_t idx) { duk_hobject *h; DUK_ASSERT_API_ENTRY(thr); h = (duk_hobject *) duk__get_tagged_heaphdr_raw(thr, idx, DUK_TAG_OBJECT); if (DUK_UNLIKELY(h != NULL && !DUK_HOBJECT_IS_COMPFUNC(h))) { h = NULL; } return (duk_hcompfunc *) h; } DUK_INTERNAL duk_hcompfunc *duk_require_hcompfunc(duk_hthread *thr, duk_idx_t idx) { duk_hobject *h; DUK_ASSERT_API_ENTRY(thr); h = (duk_hobject *) duk__get_tagged_heaphdr_raw(thr, idx, DUK_TAG_OBJECT); if (DUK_UNLIKELY(!(h != NULL && DUK_HOBJECT_IS_COMPFUNC(h)))) { DUK_ERROR_REQUIRE_TYPE_INDEX(thr, idx, "compiledfunction", DUK_STR_NOT_COMPFUNC); DUK_WO_NORETURN(return NULL;); } return (duk_hcompfunc *) h; } DUK_INTERNAL duk_hnatfunc *duk_get_hnatfunc(duk_hthread *thr, duk_idx_t idx) { duk_hobject *h; DUK_ASSERT_API_ENTRY(thr); h = (duk_hobject *) duk__get_tagged_heaphdr_raw(thr, idx, DUK_TAG_OBJECT); if (DUK_UNLIKELY(h != NULL && !DUK_HOBJECT_IS_NATFUNC(h))) { h = NULL; } return (duk_hnatfunc *) h; } DUK_INTERNAL duk_hnatfunc *duk_require_hnatfunc(duk_hthread *thr, duk_idx_t idx) { duk_hobject *h; DUK_ASSERT_API_ENTRY(thr); h = (duk_hobject *) duk__get_tagged_heaphdr_raw(thr, idx, DUK_TAG_OBJECT); if (DUK_UNLIKELY(!(h != NULL && DUK_HOBJECT_IS_NATFUNC(h)))) { DUK_ERROR_REQUIRE_TYPE_INDEX(thr, idx, "nativefunction", DUK_STR_NOT_NATFUNC); DUK_WO_NORETURN(return NULL;); } return (duk_hnatfunc *) h; } DUK_EXTERNAL duk_c_function duk_get_c_function(duk_hthread *thr, duk_idx_t idx) { duk_tval *tv; duk_hobject *h; duk_hnatfunc *f; DUK_ASSERT_API_ENTRY(thr); tv = duk_get_tval_or_unused(thr, idx); DUK_ASSERT(tv != NULL); if (DUK_UNLIKELY(!DUK_TVAL_IS_OBJECT(tv))) { return NULL; } h = DUK_TVAL_GET_OBJECT(tv); DUK_ASSERT(h != NULL); if (DUK_UNLIKELY(!DUK_HOBJECT_IS_NATFUNC(h))) { return NULL; } DUK_ASSERT(DUK_HOBJECT_HAS_NATFUNC(h)); f = (duk_hnatfunc *) h; return f->func; } DUK_EXTERNAL duk_c_function duk_opt_c_function(duk_hthread *thr, duk_idx_t idx, duk_c_function def_value) { DUK_ASSERT_API_ENTRY(thr); if (duk_check_type_mask(thr, idx, DUK_TYPE_MASK_NONE | DUK_TYPE_MASK_UNDEFINED)) { return def_value; } return duk_require_c_function(thr, idx); } DUK_EXTERNAL duk_c_function duk_get_c_function_default(duk_hthread *thr, duk_idx_t idx, duk_c_function def_value) { duk_c_function ret; DUK_ASSERT_API_ENTRY(thr); ret = duk_get_c_function(thr, idx); if (ret != NULL) { return ret; } return def_value; } DUK_EXTERNAL duk_c_function duk_require_c_function(duk_hthread *thr, duk_idx_t idx) { duk_c_function ret; DUK_ASSERT_API_ENTRY(thr); ret = duk_get_c_function(thr, idx); if (DUK_UNLIKELY(!ret)) { DUK_ERROR_REQUIRE_TYPE_INDEX(thr, idx, "nativefunction", DUK_STR_NOT_NATFUNC); DUK_WO_NORETURN(return ret;); } return ret; } DUK_EXTERNAL void duk_require_function(duk_hthread *thr, duk_idx_t idx) { DUK_ASSERT_API_ENTRY(thr); if (DUK_UNLIKELY(!duk_is_function(thr, idx))) { DUK_ERROR_REQUIRE_TYPE_INDEX(thr, idx, "function", DUK_STR_NOT_FUNCTION); DUK_WO_NORETURN(return;); } } DUK_EXTERNAL void duk_require_constructable(duk_hthread *thr, duk_idx_t idx) { duk_hobject *h; DUK_ASSERT_API_ENTRY(thr); h = duk_require_hobject_accept_mask(thr, idx, DUK_TYPE_MASK_LIGHTFUNC); if (DUK_UNLIKELY(h != NULL && !DUK_HOBJECT_HAS_CONSTRUCTABLE(h))) { DUK_ERROR_REQUIRE_TYPE_INDEX(thr, idx, "constructable", DUK_STR_NOT_CONSTRUCTABLE); DUK_WO_NORETURN(return;); } /* Lightfuncs (h == NULL) are constructable. */ } DUK_EXTERNAL duk_hthread *duk_get_context(duk_hthread *thr, duk_idx_t idx) { DUK_ASSERT_API_ENTRY(thr); return duk_get_hthread(thr, idx); } DUK_EXTERNAL duk_hthread *duk_require_context(duk_hthread *thr, duk_idx_t idx) { DUK_ASSERT_API_ENTRY(thr); return duk_require_hthread(thr, idx); } DUK_EXTERNAL duk_hthread *duk_opt_context(duk_hthread *thr, duk_idx_t idx, duk_hthread *def_value) { DUK_ASSERT_API_ENTRY(thr); if (duk_check_type_mask(thr, idx, DUK_TYPE_MASK_NONE | DUK_TYPE_MASK_UNDEFINED)) { return def_value; } return duk_require_context(thr, idx); } DUK_EXTERNAL duk_hthread *duk_get_context_default(duk_hthread *thr, duk_idx_t idx, duk_hthread *def_value) { duk_hthread *ret; DUK_ASSERT_API_ENTRY(thr); ret = duk_get_context(thr, idx); if (ret != NULL) { return ret; } return def_value; } DUK_EXTERNAL void *duk_get_heapptr(duk_hthread *thr, duk_idx_t idx) { duk_tval *tv; void *ret; DUK_ASSERT_API_ENTRY(thr); tv = duk_get_tval_or_unused(thr, idx); DUK_ASSERT(tv != NULL); if (DUK_UNLIKELY(!DUK_TVAL_IS_HEAP_ALLOCATED(tv))) { return (void *) NULL; } ret = (void *) DUK_TVAL_GET_HEAPHDR(tv); DUK_ASSERT(ret != NULL); return ret; } DUK_EXTERNAL void *duk_opt_heapptr(duk_hthread *thr, duk_idx_t idx, void *def_value) { DUK_ASSERT_API_ENTRY(thr); if (duk_check_type_mask(thr, idx, DUK_TYPE_MASK_NONE | DUK_TYPE_MASK_UNDEFINED)) { return def_value; } return duk_require_heapptr(thr, idx); } DUK_EXTERNAL void *duk_get_heapptr_default(duk_hthread *thr, duk_idx_t idx, void *def_value) { void *ret; DUK_ASSERT_API_ENTRY(thr); ret = duk_get_heapptr(thr, idx); if (ret != NULL) { return ret; } return def_value; } DUK_EXTERNAL void *duk_require_heapptr(duk_hthread *thr, duk_idx_t idx) { duk_tval *tv; void *ret; DUK_ASSERT_API_ENTRY(thr); tv = duk_get_tval_or_unused(thr, idx); DUK_ASSERT(tv != NULL); if (DUK_UNLIKELY(!DUK_TVAL_IS_HEAP_ALLOCATED(tv))) { DUK_ERROR_REQUIRE_TYPE_INDEX(thr, idx, "heapobject", DUK_STR_UNEXPECTED_TYPE); DUK_WO_NORETURN(return NULL;); } ret = (void *) DUK_TVAL_GET_HEAPHDR(tv); DUK_ASSERT(ret != NULL); return ret; } /* Internal helper for getting/requiring a duk_hobject with possible promotion. */ DUK_LOCAL duk_hobject *duk__get_hobject_promote_mask_raw(duk_hthread *thr, duk_idx_t idx, duk_uint_t type_mask) { duk_uint_t val_mask; duk_hobject *res; DUK_CTX_ASSERT_VALID(thr); res = duk_get_hobject(thr, idx); /* common case, not promoted */ if (DUK_LIKELY(res != NULL)) { DUK_ASSERT(res != NULL); return res; } val_mask = duk_get_type_mask(thr, idx); if (val_mask & type_mask) { if (type_mask & DUK_TYPE_MASK_PROMOTE) { res = duk_to_hobject(thr, idx); DUK_ASSERT(res != NULL); return res; } else { return NULL; /* accept without promoting */ } } if (type_mask & DUK_TYPE_MASK_THROW) { DUK_ERROR_REQUIRE_TYPE_INDEX(thr, idx, "object", DUK_STR_NOT_OBJECT); DUK_WO_NORETURN(return NULL;); } return NULL; } /* Get a duk_hobject * at 'idx'; if the value is not an object but matches the * supplied 'type_mask', promote it to an object and return the duk_hobject *. * This is useful for call sites which want an object but also accept a plain * buffer and/or a lightfunc which gets automatically promoted to an object. * Return value is NULL if value is neither an object nor a plain type allowed * by the mask. */ DUK_INTERNAL duk_hobject *duk_get_hobject_promote_mask(duk_hthread *thr, duk_idx_t idx, duk_uint_t type_mask) { DUK_ASSERT_API_ENTRY(thr); return duk__get_hobject_promote_mask_raw(thr, idx, type_mask | DUK_TYPE_MASK_PROMOTE); } /* Like duk_get_hobject_promote_mask() but throw a TypeError instead of * returning a NULL. */ DUK_INTERNAL duk_hobject *duk_require_hobject_promote_mask(duk_hthread *thr, duk_idx_t idx, duk_uint_t type_mask) { DUK_ASSERT_API_ENTRY(thr); return duk__get_hobject_promote_mask_raw(thr, idx, type_mask | DUK_TYPE_MASK_THROW | DUK_TYPE_MASK_PROMOTE); } /* Require a duk_hobject * at 'idx'; if the value is not an object but matches the * supplied 'type_mask', return a NULL instead. Otherwise throw a TypeError. */ DUK_INTERNAL duk_hobject *duk_require_hobject_accept_mask(duk_hthread *thr, duk_idx_t idx, duk_uint_t type_mask) { DUK_ASSERT_API_ENTRY(thr); return duk__get_hobject_promote_mask_raw(thr, idx, type_mask | DUK_TYPE_MASK_THROW); } DUK_INTERNAL duk_hobject *duk_get_hobject_with_class(duk_hthread *thr, duk_idx_t idx, duk_small_uint_t classnum) { duk_hobject *h; DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT_DISABLE(classnum >= 0); /* unsigned */ DUK_ASSERT(classnum <= DUK_HOBJECT_CLASS_MAX); h = (duk_hobject *) duk__get_tagged_heaphdr_raw(thr, idx, DUK_TAG_OBJECT); if (DUK_UNLIKELY(h != NULL && DUK_HOBJECT_GET_CLASS_NUMBER(h) != classnum)) { h = NULL; } return h; } DUK_INTERNAL duk_hobject *duk_require_hobject_with_class(duk_hthread *thr, duk_idx_t idx, duk_small_uint_t classnum) { duk_hobject *h; DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT_DISABLE(classnum >= 0); /* unsigned */ DUK_ASSERT(classnum <= DUK_HOBJECT_CLASS_MAX); h = (duk_hobject *) duk__get_tagged_heaphdr_raw(thr, idx, DUK_TAG_OBJECT); if (DUK_UNLIKELY(!(h != NULL && DUK_HOBJECT_GET_CLASS_NUMBER(h) == classnum))) { duk_hstring *h_class; h_class = DUK_HTHREAD_GET_STRING(thr, DUK_HOBJECT_CLASS_NUMBER_TO_STRIDX(classnum)); DUK_UNREF(h_class); DUK_ERROR_REQUIRE_TYPE_INDEX(thr, idx, (const char *) DUK_HSTRING_GET_DATA(h_class), DUK_STR_UNEXPECTED_TYPE); DUK_WO_NORETURN(return NULL;); } return h; } DUK_EXTERNAL duk_size_t duk_get_length(duk_hthread *thr, duk_idx_t idx) { duk_tval *tv; DUK_ASSERT_API_ENTRY(thr); tv = duk_get_tval_or_unused(thr, idx); DUK_ASSERT(tv != NULL); switch (DUK_TVAL_GET_TAG(tv)) { case DUK_TAG_UNDEFINED: case DUK_TAG_NULL: case DUK_TAG_BOOLEAN: case DUK_TAG_POINTER: return 0; #if defined(DUK_USE_PREFER_SIZE) /* String and buffer have a virtual non-configurable .length property * which is within size_t range so it can be looked up without specific * type checks. Lightfuncs inherit from %NativeFunctionPrototype% * which provides an inherited .length accessor; it could be overwritten * to produce unexpected types or values, but just number convert and * duk_size_t cast for now. */ case DUK_TAG_STRING: case DUK_TAG_BUFFER: case DUK_TAG_LIGHTFUNC: { duk_size_t ret; duk_get_prop_stridx(thr, idx, DUK_STRIDX_LENGTH); ret = (duk_size_t) duk_to_number_m1(thr); duk_pop_unsafe(thr); return ret; } #else /* DUK_USE_PREFER_SIZE */ case DUK_TAG_STRING: { duk_hstring *h = DUK_TVAL_GET_STRING(tv); DUK_ASSERT(h != NULL); if (DUK_UNLIKELY(DUK_HSTRING_HAS_SYMBOL(h))) { return 0; } return (duk_size_t) DUK_HSTRING_GET_CHARLEN(h); } case DUK_TAG_BUFFER: { duk_hbuffer *h = DUK_TVAL_GET_BUFFER(tv); DUK_ASSERT(h != NULL); return (duk_size_t) DUK_HBUFFER_GET_SIZE(h); } case DUK_TAG_LIGHTFUNC: { /* We could look up the length from the lightfunc duk_tval, * but since Duktape 2.2 lightfunc .length comes from * %NativeFunctionPrototype% which can be overridden, so * look up the property explicitly. */ duk_size_t ret; duk_get_prop_stridx(thr, idx, DUK_STRIDX_LENGTH); ret = (duk_size_t) duk_to_number_m1(thr); duk_pop_unsafe(thr); return ret; } #endif /* DUK_USE_PREFER_SIZE */ case DUK_TAG_OBJECT: { duk_hobject *h = DUK_TVAL_GET_OBJECT(tv); DUK_ASSERT(h != NULL); return (duk_size_t) duk_hobject_get_length(thr, h); } #if defined(DUK_USE_FASTINT) case DUK_TAG_FASTINT: #endif default: /* number or 'unused' */ DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv) || DUK_TVAL_IS_UNUSED(tv)); return 0; } DUK_UNREACHABLE(); } /* * duk_known_xxx() helpers * * Used internally when we're 100% sure that a certain index is valid and * contains an object of a certain type. For example, if we duk_push_object() * we can then safely duk_known_hobject(thr, -1). These helpers just assert * for the index and type, and if the assumptions are not valid, memory unsafe * behavior happens. */ DUK_LOCAL duk_heaphdr *duk__known_heaphdr(duk_hthread *thr, duk_idx_t idx) { duk_tval *tv; duk_heaphdr *h; DUK_CTX_ASSERT_VALID(thr); if (idx < 0) { tv = thr->valstack_top + idx; } else { tv = thr->valstack_bottom + idx; } DUK_ASSERT(tv >= thr->valstack_bottom); DUK_ASSERT(tv < thr->valstack_top); h = DUK_TVAL_GET_HEAPHDR(tv); DUK_ASSERT(h != NULL); return h; } DUK_INTERNAL duk_hstring *duk_known_hstring(duk_hthread *thr, duk_idx_t idx) { DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(duk_get_hstring(thr, idx) != NULL); return (duk_hstring *) duk__known_heaphdr(thr, idx); } DUK_INTERNAL duk_hobject *duk_known_hobject(duk_hthread *thr, duk_idx_t idx) { DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(duk_get_hobject(thr, idx) != NULL); return (duk_hobject *) duk__known_heaphdr(thr, idx); } DUK_INTERNAL duk_hbuffer *duk_known_hbuffer(duk_hthread *thr, duk_idx_t idx) { DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(duk_get_hbuffer(thr, idx) != NULL); return (duk_hbuffer *) duk__known_heaphdr(thr, idx); } DUK_INTERNAL duk_hcompfunc *duk_known_hcompfunc(duk_hthread *thr, duk_idx_t idx) { DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(duk_get_hcompfunc(thr, idx) != NULL); return (duk_hcompfunc *) duk__known_heaphdr(thr, idx); } DUK_INTERNAL duk_hnatfunc *duk_known_hnatfunc(duk_hthread *thr, duk_idx_t idx) { DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(duk_get_hnatfunc(thr, idx) != NULL); return (duk_hnatfunc *) duk__known_heaphdr(thr, idx); } DUK_EXTERNAL void duk_set_length(duk_hthread *thr, duk_idx_t idx, duk_size_t len) { DUK_ASSERT_API_ENTRY(thr); idx = duk_normalize_index(thr, idx); duk_push_uint(thr, (duk_uint_t) len); duk_put_prop_stridx(thr, idx, DUK_STRIDX_LENGTH); } /* * Conversions and coercions * * The conversion/coercions are in-place operations on the value stack. * Some operations are implemented here directly, while others call a * helper in duk_js_ops.c after validating arguments. */ /* E5 Section 8.12.8 */ DUK_LOCAL duk_bool_t duk__defaultvalue_coerce_attempt(duk_hthread *thr, duk_idx_t idx, duk_small_uint_t func_stridx) { if (duk_get_prop_stridx(thr, idx, func_stridx)) { /* [ ... func ] */ if (duk_is_callable(thr, -1)) { duk_dup(thr, idx); /* -> [ ... func this ] */ duk_call_method(thr, 0); /* -> [ ... retval ] */ if (duk_is_primitive(thr, -1)) { duk_replace(thr, idx); return 1; } /* [ ... retval ]; popped below */ } } duk_pop_unsafe(thr); /* [ ... func/retval ] -> [ ... ] */ return 0; } DUK_EXTERNAL void duk_to_undefined(duk_hthread *thr, duk_idx_t idx) { duk_tval *tv; DUK_ASSERT_API_ENTRY(thr); tv = duk_require_tval(thr, idx); DUK_ASSERT(tv != NULL); DUK_TVAL_SET_UNDEFINED_UPDREF(thr, tv); /* side effects */ } DUK_EXTERNAL void duk_to_null(duk_hthread *thr, duk_idx_t idx) { duk_tval *tv; DUK_ASSERT_API_ENTRY(thr); tv = duk_require_tval(thr, idx); DUK_ASSERT(tv != NULL); DUK_TVAL_SET_NULL_UPDREF(thr, tv); /* side effects */ } /* E5 Section 9.1 */ DUK_LOCAL const char * const duk__toprim_hint_strings[3] = { "default", "string", "number" }; DUK_LOCAL void duk__to_primitive_helper(duk_hthread *thr, duk_idx_t idx, duk_int_t hint, duk_bool_t check_symbol) { /* Inline initializer for coercers[] is not allowed by old compilers like BCC. */ duk_small_uint_t coercers[2]; DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(hint == DUK_HINT_NONE || hint == DUK_HINT_NUMBER || hint == DUK_HINT_STRING); idx = duk_require_normalize_index(thr, idx); /* If already primitive, return as is. */ if (!duk_check_type_mask(thr, idx, DUK_TYPE_MASK_OBJECT | DUK_TYPE_MASK_LIGHTFUNC | DUK_TYPE_MASK_BUFFER)) { DUK_ASSERT(!duk_is_buffer(thr, idx)); /* duk_to_string() relies on this behavior */ return; } /* @@toPrimitive lookup. Also do for plain buffers and lightfuncs * which mimic objects. */ if (check_symbol && duk_get_method_stridx(thr, idx, DUK_STRIDX_WELLKNOWN_SYMBOL_TO_PRIMITIVE)) { DUK_ASSERT(hint >= 0 && (duk_size_t) hint < sizeof(duk__toprim_hint_strings) / sizeof(const char *)); duk_dup(thr, idx); duk_push_string(thr, duk__toprim_hint_strings[hint]); duk_call_method(thr, 1); /* [ ... method value hint ] -> [ ... res] */ if (duk_check_type_mask(thr, -1, DUK_TYPE_MASK_OBJECT | DUK_TYPE_MASK_LIGHTFUNC | DUK_TYPE_MASK_BUFFER)) { goto fail; } duk_replace(thr, idx); return; } /* Objects are coerced based on E5 specification. * Lightfuncs are coerced because they behave like * objects even if they're internally a primitive * type. Same applies to plain buffers, which behave * like ArrayBuffer objects since Duktape 2.x. */ /* Hint magic for Date is unnecessary in ES2015 because of * Date.prototype[@@toPrimitive]. However, it is needed if * symbol support is not enabled. */ #if defined(DUK_USE_SYMBOL_BUILTIN) if (hint == DUK_HINT_NONE) { hint = DUK_HINT_NUMBER; } #else /* DUK_USE_SYMBOL_BUILTIN */ if (hint == DUK_HINT_NONE) { duk_small_uint_t class_number; class_number = duk_get_class_number(thr, idx); if (class_number == DUK_HOBJECT_CLASS_DATE) { hint = DUK_HINT_STRING; } else { hint = DUK_HINT_NUMBER; } } #endif /* DUK_USE_SYMBOL_BUILTIN */ coercers[0] = DUK_STRIDX_VALUE_OF; coercers[1] = DUK_STRIDX_TO_STRING; if (hint == DUK_HINT_STRING) { coercers[0] = DUK_STRIDX_TO_STRING; coercers[1] = DUK_STRIDX_VALUE_OF; } if (duk__defaultvalue_coerce_attempt(thr, idx, coercers[0])) { DUK_ASSERT(!duk_is_buffer(thr, idx)); /* duk_to_string() relies on this behavior */ return; } if (duk__defaultvalue_coerce_attempt(thr, idx, coercers[1])) { DUK_ASSERT(!duk_is_buffer(thr, idx)); /* duk_to_string() relies on this behavior */ return; } fail: DUK_ERROR_TYPE(thr, DUK_STR_TOPRIMITIVE_FAILED); DUK_WO_NORETURN(return;); } DUK_EXTERNAL void duk_to_primitive(duk_hthread *thr, duk_idx_t idx, duk_int_t hint) { duk__to_primitive_helper(thr, idx, hint, 1 /*check_symbol*/); } #if defined(DUK_USE_SYMBOL_BUILTIN) DUK_INTERNAL void duk_to_primitive_ordinary(duk_hthread *thr, duk_idx_t idx, duk_int_t hint) { duk__to_primitive_helper(thr, idx, hint, 0 /*check_symbol*/); } #endif /* E5 Section 9.2 */ DUK_EXTERNAL duk_bool_t duk_to_boolean(duk_hthread *thr, duk_idx_t idx) { duk_tval *tv; duk_bool_t val; DUK_ASSERT_API_ENTRY(thr); idx = duk_require_normalize_index(thr, idx); tv = DUK_GET_TVAL_POSIDX(thr, idx); DUK_ASSERT(tv != NULL); val = duk_js_toboolean(tv); DUK_ASSERT(val == 0 || val == 1); /* Note: no need to re-lookup tv, conversion is side effect free. */ DUK_ASSERT(tv != NULL); DUK_TVAL_SET_BOOLEAN_UPDREF(thr, tv, val); /* side effects */ return val; } DUK_INTERNAL duk_bool_t duk_to_boolean_top_pop(duk_hthread *thr) { duk_tval *tv; duk_bool_t val; DUK_ASSERT_API_ENTRY(thr); tv = duk_require_tval(thr, -1); DUK_ASSERT(tv != NULL); val = duk_js_toboolean(tv); DUK_ASSERT(val == 0 || val == 1); duk_pop_unsafe(thr); return val; } DUK_EXTERNAL duk_double_t duk_to_number(duk_hthread *thr, duk_idx_t idx) { duk_tval *tv; duk_double_t d; DUK_ASSERT_API_ENTRY(thr); /* XXX: No need to normalize; the whole operation could be inlined here to * avoid 'tv' re-lookup. */ idx = duk_require_normalize_index(thr, idx); tv = DUK_GET_TVAL_POSIDX(thr, idx); DUK_ASSERT(tv != NULL); d = duk_js_tonumber(thr, tv); /* XXX: fastint coercion? now result will always be a non-fastint */ /* ToNumber() may have side effects so must relookup 'tv'. */ tv = DUK_GET_TVAL_POSIDX(thr, idx); DUK_TVAL_SET_NUMBER_UPDREF(thr, tv, d); /* side effects */ return d; } DUK_INTERNAL duk_double_t duk_to_number_m1(duk_hthread *thr) { DUK_ASSERT_API_ENTRY(thr); return duk_to_number(thr, -1); } DUK_INTERNAL duk_double_t duk_to_number_m2(duk_hthread *thr) { DUK_ASSERT_API_ENTRY(thr); return duk_to_number(thr, -2); } DUK_INTERNAL duk_double_t duk_to_number_tval(duk_hthread *thr, duk_tval *tv) { #if defined(DUK_USE_PREFER_SIZE) duk_double_t res; DUK_ASSERT_API_ENTRY(thr); duk_push_tval(thr, tv); res = duk_to_number_m1(thr); duk_pop_unsafe(thr); return res; #else duk_double_t res; duk_tval *tv_dst; DUK_ASSERT_API_ENTRY(thr); DUK__ASSERT_SPACE(); tv_dst = thr->valstack_top++; DUK_TVAL_SET_TVAL(tv_dst, tv); DUK_TVAL_INCREF(thr, tv_dst); /* decref not necessary */ res = duk_to_number_m1(thr); /* invalidates tv_dst */ tv_dst = --thr->valstack_top; DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv_dst)); DUK_ASSERT(!DUK_TVAL_NEEDS_REFCOUNT_UPDATE(tv_dst)); /* plain number */ DUK_TVAL_SET_UNDEFINED(tv_dst); /* valstack init policy */ return res; #endif } /* XXX: combine all the integer conversions: they share everything * but the helper function for coercion. */ typedef duk_double_t (*duk__toint_coercer)(duk_hthread *thr, duk_tval *tv); DUK_LOCAL duk_double_t duk__to_int_uint_helper(duk_hthread *thr, duk_idx_t idx, duk__toint_coercer coerce_func) { duk_tval *tv; duk_double_t d; DUK_CTX_ASSERT_VALID(thr); tv = duk_require_tval(thr, idx); DUK_ASSERT(tv != NULL); #if defined(DUK_USE_FASTINT) /* If argument is a fastint, guarantee that it remains one. * There's no downgrade check for other cases. */ if (DUK_TVAL_IS_FASTINT(tv)) { /* XXX: Unnecessary conversion back and forth. */ return (duk_double_t) DUK_TVAL_GET_FASTINT(tv); } #endif d = coerce_func(thr, tv); /* XXX: fastint? */ /* Relookup in case coerce_func() has side effects, e.g. ends up coercing an object */ tv = duk_require_tval(thr, idx); DUK_TVAL_SET_NUMBER_UPDREF(thr, tv, d); /* side effects */ return d; } DUK_EXTERNAL duk_int_t duk_to_int(duk_hthread *thr, duk_idx_t idx) { /* Value coercion (in stack): ToInteger(), E5 Section 9.4, * API return value coercion: custom. */ DUK_ASSERT_API_ENTRY(thr); (void) duk__to_int_uint_helper(thr, idx, duk_js_tointeger); return (duk_int_t) duk__api_coerce_d2i(thr, idx, 0 /*def_value*/, 0 /*require*/); } DUK_EXTERNAL duk_uint_t duk_to_uint(duk_hthread *thr, duk_idx_t idx) { /* Value coercion (in stack): ToInteger(), E5 Section 9.4, * API return value coercion: custom. */ DUK_ASSERT_API_ENTRY(thr); (void) duk__to_int_uint_helper(thr, idx, duk_js_tointeger); return (duk_uint_t) duk__api_coerce_d2ui(thr, idx, 0 /*def_value*/, 0 /*require*/); } DUK_EXTERNAL duk_int32_t duk_to_int32(duk_hthread *thr, duk_idx_t idx) { duk_tval *tv; duk_int32_t ret; DUK_ASSERT_API_ENTRY(thr); tv = duk_require_tval(thr, idx); DUK_ASSERT(tv != NULL); ret = duk_js_toint32(thr, tv); /* Relookup in case coerce_func() has side effects, e.g. ends up coercing an object */ tv = duk_require_tval(thr, idx); DUK_TVAL_SET_I32_UPDREF(thr, tv, ret); /* side effects */ return ret; } DUK_EXTERNAL duk_uint32_t duk_to_uint32(duk_hthread *thr, duk_idx_t idx) { duk_tval *tv; duk_uint32_t ret; DUK_ASSERT_API_ENTRY(thr); tv = duk_require_tval(thr, idx); DUK_ASSERT(tv != NULL); ret = duk_js_touint32(thr, tv); /* Relookup in case coerce_func() has side effects, e.g. ends up coercing an object */ tv = duk_require_tval(thr, idx); DUK_TVAL_SET_U32_UPDREF(thr, tv, ret); /* side effects */ return ret; } DUK_EXTERNAL duk_uint16_t duk_to_uint16(duk_hthread *thr, duk_idx_t idx) { duk_tval *tv; duk_uint16_t ret; DUK_ASSERT_API_ENTRY(thr); tv = duk_require_tval(thr, idx); DUK_ASSERT(tv != NULL); ret = duk_js_touint16(thr, tv); /* Relookup in case coerce_func() has side effects, e.g. ends up coercing an object */ tv = duk_require_tval(thr, idx); DUK_TVAL_SET_U32_UPDREF(thr, tv, ret); /* side effects */ return ret; } #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) /* Special coercion for Uint8ClampedArray. */ DUK_INTERNAL duk_uint8_t duk_to_uint8clamped(duk_hthread *thr, duk_idx_t idx) { duk_double_t d; duk_double_t t; duk_uint8_t ret; DUK_ASSERT_API_ENTRY(thr); /* XXX: Simplify this algorithm, should be possible to come up with * a shorter and faster algorithm by inspecting IEEE representation * directly. */ d = duk_to_number(thr, idx); if (d <= 0.0) { return 0; } else if (d >= 255) { return 255; } else if (DUK_ISNAN(d)) { /* Avoid NaN-to-integer coercion as it is compiler specific. */ return 0; } t = d - DUK_FLOOR(d); if (duk_double_equals(t, 0.5)) { /* Exact halfway, round to even. */ ret = (duk_uint8_t) d; ret = (ret + 1) & 0xfe; /* Example: d=3.5, t=0.5 -> ret = (3 + 1) & 0xfe = 4 & 0xfe = 4 * Example: d=4.5, t=0.5 -> ret = (4 + 1) & 0xfe = 5 & 0xfe = 4 */ } else { /* Not halfway, round to nearest. */ ret = (duk_uint8_t) (d + 0.5); } return ret; } #endif /* DUK_USE_BUFFEROBJECT_SUPPORT */ DUK_EXTERNAL const char *duk_to_lstring(duk_hthread *thr, duk_idx_t idx, duk_size_t *out_len) { DUK_ASSERT_API_ENTRY(thr); (void) duk_to_string(thr, idx); DUK_ASSERT(duk_is_string(thr, idx)); return duk_require_lstring(thr, idx, out_len); } DUK_LOCAL duk_ret_t duk__safe_to_string_raw(duk_hthread *thr, void *udata) { DUK_CTX_ASSERT_VALID(thr); DUK_UNREF(udata); (void) duk_to_string(thr, -1); return 1; } DUK_EXTERNAL const char *duk_safe_to_lstring(duk_hthread *thr, duk_idx_t idx, duk_size_t *out_len) { DUK_ASSERT_API_ENTRY(thr); idx = duk_require_normalize_index(thr, idx); /* We intentionally ignore the duk_safe_call() return value and only * check the output type. This way we don't also need to check that * the returned value is indeed a string in the success case. */ duk_dup(thr, idx); (void) duk_safe_call(thr, duk__safe_to_string_raw, NULL /*udata*/, 1 /*nargs*/, 1 /*nrets*/); if (!duk_is_string(thr, -1)) { /* Error: try coercing error to string once. */ (void) duk_safe_call(thr, duk__safe_to_string_raw, NULL /*udata*/, 1 /*nargs*/, 1 /*nrets*/); if (!duk_is_string(thr, -1)) { /* Double error */ duk_pop_unsafe(thr); duk_push_hstring_stridx(thr, DUK_STRIDX_UC_ERROR); } else { ; } } else { /* String; may be a symbol, accepted. */ ; } DUK_ASSERT(duk_is_string(thr, -1)); duk_replace(thr, idx); DUK_ASSERT(duk_get_string(thr, idx) != NULL); return duk_get_lstring(thr, idx, out_len); } DUK_EXTERNAL const char *duk_to_stacktrace(duk_hthread *thr, duk_idx_t idx) { DUK_ASSERT_API_ENTRY(thr); idx = duk_require_normalize_index(thr, idx); /* The expected argument to the call is an Error object. The stack * trace is extracted without an inheritance-based instanceof check * so that one can also extract the stack trace of a foreign error * created in another Realm. Accept only a string .stack property. */ if (duk_is_object(thr, idx)) { (void) duk_get_prop_string(thr, idx, "stack"); if (duk_is_string(thr, -1)) { duk_replace(thr, idx); } else { duk_pop(thr); } } return duk_to_string(thr, idx); } DUK_LOCAL duk_ret_t duk__safe_to_stacktrace_raw(duk_hthread *thr, void *udata) { DUK_CTX_ASSERT_VALID(thr); DUK_UNREF(udata); (void) duk_to_stacktrace(thr, -1); return 1; } DUK_EXTERNAL const char *duk_safe_to_stacktrace(duk_hthread *thr, duk_idx_t idx) { duk_int_t rc; DUK_ASSERT_API_ENTRY(thr); idx = duk_require_normalize_index(thr, idx); duk_dup(thr, idx); rc = duk_safe_call(thr, duk__safe_to_stacktrace_raw, NULL /*udata*/, 1 /*nargs*/, 1 /*nrets*/); if (rc != 0) { /* Coercion failed. Try to coerce the coercion itself error * to a stack trace once. If that also fails, return a fixed, * preallocated 'Error' string to avoid potential infinite loop. */ rc = duk_safe_call(thr, duk__safe_to_stacktrace_raw, NULL /*udata*/, 1 /*nargs*/, 1 /*nrets*/); if (rc != 0) { duk_pop_unsafe(thr); duk_push_hstring_stridx(thr, DUK_STRIDX_UC_ERROR); } } duk_replace(thr, idx); return duk_get_string(thr, idx); } DUK_INTERNAL duk_hstring *duk_to_property_key_hstring(duk_hthread *thr, duk_idx_t idx) { duk_hstring *h; DUK_ASSERT_API_ENTRY(thr); duk_to_primitive(thr, idx, DUK_HINT_STRING); /* needed for e.g. Symbol objects */ h = duk_get_hstring(thr, idx); if (h == NULL) { /* The "is string?" check may seem unnecessary, but as things * are duk_to_hstring() invokes ToString() which fails for * symbols. But since symbols are already strings for Duktape * C API, we check for that before doing the coercion. */ h = duk_to_hstring(thr, idx); } DUK_ASSERT(h != NULL); return h; } #if defined(DUK_USE_DEBUGGER_SUPPORT) /* only needed by debugger for now */ DUK_INTERNAL duk_hstring *duk_safe_to_hstring(duk_hthread *thr, duk_idx_t idx) { DUK_ASSERT_API_ENTRY(thr); (void) duk_safe_to_string(thr, idx); DUK_ASSERT(duk_is_string(thr, idx)); DUK_ASSERT(duk_get_hstring(thr, idx) != NULL); return duk_known_hstring(thr, idx); } #endif /* Push Object.prototype.toString() output for 'tv'. */ DUK_INTERNAL void duk_push_class_string_tval(duk_hthread *thr, duk_tval *tv, duk_bool_t avoid_side_effects) { duk_hobject *h_obj; duk_small_uint_t classnum; duk_small_uint_t stridx; duk_tval tv_tmp; DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(tv != NULL); /* Stabilize 'tv', duk_push_literal() may trigger side effects. */ DUK_TVAL_SET_TVAL(&tv_tmp, tv); tv = &tv_tmp; /* Conceptually for any non-undefined/null value we should do a * ToObject() coercion and look up @@toStringTag (from the object * prototype) to see if a custom result should be used, with the * exception of Arrays which are handled specially first. * * We'd like to avoid the actual conversion, but even for primitive * types the prototype may have @@toStringTag. What's worse, the * @@toStringTag property may be a getter that must get the object * coerced value (not the prototype) as its 'this' binding. * * For now, do an actual object coercion. This could be avoided by * doing a side effect free lookup to see if a getter would be invoked. * If not, the value can be read directly and the object coercion could * be avoided. This may not be worth it in practice, because * Object.prototype.toString() is usually not performance critical. */ duk_push_literal(thr, "[object "); /* -> [ ... "[object" ] */ switch (DUK_TVAL_GET_TAG(tv)) { case DUK_TAG_UNUSED: /* Treat like 'undefined', shouldn't happen. */ case DUK_TAG_UNDEFINED: { duk_push_hstring_stridx(thr, DUK_STRIDX_UC_UNDEFINED); goto finish; } case DUK_TAG_NULL: { duk_push_hstring_stridx(thr, DUK_STRIDX_UC_NULL); goto finish; } } duk_push_tval(thr, tv); tv = NULL; /* Invalidated by ToObject(). */ h_obj = duk_to_hobject(thr, -1); DUK_ASSERT(h_obj != NULL); if (duk_js_isarray_hobject(h_obj)) { stridx = DUK_STRIDX_UC_ARRAY; } else { /* [ ... "[object" obj ] */ #if defined(DUK_USE_SYMBOL_BUILTIN) /* XXX: better handling with avoid_side_effects == 1; lookup tval * without Proxy or getter side effects, and use it in sanitized * form if it's a string. */ if (!avoid_side_effects) { (void) duk_get_prop_stridx(thr, -1, DUK_STRIDX_WELLKNOWN_SYMBOL_TO_STRING_TAG); if (duk_is_string_notsymbol(thr, -1)) { duk_remove_m2(thr); goto finish; } duk_pop_unsafe(thr); } #else DUK_UNREF(avoid_side_effects); #endif classnum = DUK_HOBJECT_GET_CLASS_NUMBER(h_obj); stridx = DUK_HOBJECT_CLASS_NUMBER_TO_STRIDX(classnum); } duk_pop_unsafe(thr); duk_push_hstring_stridx(thr, stridx); finish: /* [ ... "[object" tag ] */ duk_push_literal(thr, "]"); duk_concat(thr, 3); /* [ ... "[object" tag "]" ] -> [ ... res ] */ } /* XXX: other variants like uint, u32 etc */ DUK_INTERNAL duk_int_t duk_to_int_clamped_raw(duk_hthread *thr, duk_idx_t idx, duk_int_t minval, duk_int_t maxval, duk_bool_t *out_clamped) { duk_tval *tv; duk_tval tv_tmp; duk_double_t d, dmin, dmax; duk_int_t res; duk_bool_t clamped = 0; DUK_ASSERT_API_ENTRY(thr); tv = duk_require_tval(thr, idx); DUK_ASSERT(tv != NULL); d = duk_js_tointeger(thr, tv); /* E5 Section 9.4, ToInteger() */ dmin = (duk_double_t) minval; dmax = (duk_double_t) maxval; if (d < dmin) { clamped = 1; res = minval; d = dmin; } else if (d > dmax) { clamped = 1; res = maxval; d = dmax; } else { res = (duk_int_t) d; } DUK_UNREF(d); /* SCANBUILD: with suitable dmin/dmax limits 'd' is unused */ /* 'd' and 'res' agree here */ /* Relookup in case duk_js_tointeger() ends up e.g. coercing an object. */ tv = duk_get_tval(thr, idx); DUK_ASSERT(tv != NULL); /* not popped by side effect */ DUK_TVAL_SET_TVAL(&tv_tmp, tv); #if defined(DUK_USE_FASTINT) #if (DUK_INT_MAX <= 0x7fffffffL) DUK_TVAL_SET_I32(tv, res); #else /* Clamping needed if duk_int_t is 64 bits. */ if (res >= DUK_FASTINT_MIN && res <= DUK_FASTINT_MAX) { DUK_TVAL_SET_FASTINT(tv, res); } else { DUK_TVAL_SET_NUMBER(tv, d); } #endif #else DUK_TVAL_SET_NUMBER(tv, d); /* no need to incref */ #endif DUK_TVAL_DECREF(thr, &tv_tmp); /* side effects */ if (out_clamped) { *out_clamped = clamped; } else { /* coerced value is updated to value stack even when RangeError thrown */ if (clamped) { DUK_ERROR_RANGE(thr, DUK_STR_NUMBER_OUTSIDE_RANGE); DUK_WO_NORETURN(return 0;); } } return res; } DUK_INTERNAL duk_int_t duk_to_int_clamped(duk_hthread *thr, duk_idx_t idx, duk_idx_t minval, duk_idx_t maxval) { duk_bool_t dummy; DUK_ASSERT_API_ENTRY(thr); return duk_to_int_clamped_raw(thr, idx, minval, maxval, &dummy); } DUK_INTERNAL duk_int_t duk_to_int_check_range(duk_hthread *thr, duk_idx_t idx, duk_int_t minval, duk_int_t maxval) { DUK_ASSERT_API_ENTRY(thr); return duk_to_int_clamped_raw(thr, idx, minval, maxval, NULL); /* out_clamped==NULL -> RangeError if outside range */ } DUK_EXTERNAL const char *duk_to_string(duk_hthread *thr, duk_idx_t idx) { duk_tval *tv; DUK_ASSERT_API_ENTRY(thr); idx = duk_require_normalize_index(thr, idx); tv = DUK_GET_TVAL_POSIDX(thr, idx); DUK_ASSERT(tv != NULL); switch (DUK_TVAL_GET_TAG(tv)) { case DUK_TAG_UNDEFINED: { duk_push_hstring_stridx(thr, DUK_STRIDX_LC_UNDEFINED); break; } case DUK_TAG_NULL: { duk_push_hstring_stridx(thr, DUK_STRIDX_LC_NULL); break; } case DUK_TAG_BOOLEAN: { if (DUK_TVAL_GET_BOOLEAN(tv)) { duk_push_hstring_stridx(thr, DUK_STRIDX_TRUE); } else { duk_push_hstring_stridx(thr, DUK_STRIDX_FALSE); } break; } case DUK_TAG_STRING: { /* Nop for actual strings, TypeError for Symbols. * Because various internals rely on ToString() coercion of * internal strings, -allow- (NOP) string coercion for hidden * symbols. */ #if 1 duk_hstring *h; h = DUK_TVAL_GET_STRING(tv); DUK_ASSERT(h != NULL); if (DUK_UNLIKELY(DUK_HSTRING_HAS_SYMBOL(h))) { DUK_ERROR_TYPE(thr, DUK_STR_CANNOT_STRING_COERCE_SYMBOL); DUK_WO_NORETURN(goto skip_replace;); } else { goto skip_replace; } #else goto skip_replace; #endif break; } case DUK_TAG_BUFFER: /* Go through Uint8Array.prototype.toString() for coercion. */ case DUK_TAG_OBJECT: { /* Plain buffers: go through ArrayBuffer.prototype.toString() * for coercion. * * Symbol objects: duk_to_primitive() results in a plain symbol * value, and duk_to_string() then causes a TypeError. */ duk_to_primitive(thr, idx, DUK_HINT_STRING); DUK_ASSERT(!duk_is_buffer(thr, idx)); /* ToPrimitive() must guarantee */ DUK_ASSERT(!duk_is_object(thr, idx)); return duk_to_string(thr, idx); /* Note: recursive call */ } case DUK_TAG_POINTER: { void *ptr = DUK_TVAL_GET_POINTER(tv); if (ptr != NULL) { duk_push_sprintf(thr, DUK_STR_FMT_PTR, (void *) ptr); } else { /* Represent a null pointer as 'null' to be consistent with * the JX format variant. Native '%p' format for a NULL * pointer may be e.g. '(nil)'. */ duk_push_hstring_stridx(thr, DUK_STRIDX_LC_NULL); } break; } case DUK_TAG_LIGHTFUNC: { /* Should match Function.prototype.toString() */ duk_push_lightfunc_tostring(thr, tv); break; } #if defined(DUK_USE_FASTINT) case DUK_TAG_FASTINT: #endif default: { /* number */ DUK_ASSERT(!DUK_TVAL_IS_UNUSED(tv)); DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv)); duk_push_tval(thr, tv); duk_numconv_stringify(thr, 10 /*radix*/, 0 /*precision:shortest*/, 0 /*force_exponential*/); break; } } duk_replace(thr, idx); skip_replace: DUK_ASSERT(duk_is_string(thr, idx)); return duk_require_string(thr, idx); } DUK_INTERNAL duk_hstring *duk_to_hstring(duk_hthread *thr, duk_idx_t idx) { duk_hstring *ret; DUK_ASSERT_API_ENTRY(thr); duk_to_string(thr, idx); ret = duk_get_hstring(thr, idx); DUK_ASSERT(ret != NULL); return ret; } DUK_INTERNAL duk_hstring *duk_to_hstring_m1(duk_hthread *thr) { DUK_ASSERT_API_ENTRY(thr); return duk_to_hstring(thr, -1); } DUK_INTERNAL duk_hstring *duk_to_hstring_acceptsymbol(duk_hthread *thr, duk_idx_t idx) { duk_hstring *ret; DUK_ASSERT_API_ENTRY(thr); ret = duk_get_hstring(thr, idx); if (DUK_UNLIKELY(ret && DUK_HSTRING_HAS_SYMBOL(ret))) { return ret; } return duk_to_hstring(thr, idx); } /* Convert a plain buffer or any buffer object into a string, using the buffer * bytes 1:1 in the internal string representation. For views the active byte * slice (not element slice interpreted as an initializer) is used. This is * necessary in Duktape 2.x because ToString(plainBuffer) no longer creates a * string with the same bytes as in the buffer but rather (usually) * '[object ArrayBuffer]'. */ DUK_EXTERNAL const char *duk_buffer_to_string(duk_hthread *thr, duk_idx_t idx) { void *ptr_src; duk_size_t len; const char *res; DUK_ASSERT_API_ENTRY(thr); idx = duk_require_normalize_index(thr, idx); ptr_src = duk_require_buffer_data(thr, idx, &len); DUK_ASSERT(ptr_src != NULL || len == 0); res = duk_push_lstring(thr, (const char *) ptr_src, len); duk_replace(thr, idx); return res; } DUK_EXTERNAL void *duk_to_buffer_raw(duk_hthread *thr, duk_idx_t idx, duk_size_t *out_size, duk_uint_t mode) { duk_hbuffer *h_buf; const duk_uint8_t *src_data; duk_size_t src_size; duk_uint8_t *dst_data; DUK_ASSERT_API_ENTRY(thr); idx = duk_require_normalize_index(thr, idx); h_buf = duk_get_hbuffer(thr, idx); if (h_buf != NULL) { /* Buffer is kept as is, with the fixed/dynamic nature of the * buffer only changed if requested. An external buffer * is converted into a non-external dynamic buffer in a * duk_to_dynamic_buffer() call. */ duk_uint_t tmp; duk_uint8_t *tmp_ptr; tmp_ptr = (duk_uint8_t *) DUK_HBUFFER_GET_DATA_PTR(thr->heap, h_buf); src_data = (const duk_uint8_t *) tmp_ptr; src_size = DUK_HBUFFER_GET_SIZE(h_buf); tmp = (DUK_HBUFFER_HAS_DYNAMIC(h_buf) ? DUK_BUF_MODE_DYNAMIC : DUK_BUF_MODE_FIXED); if ((tmp == mode && !DUK_HBUFFER_HAS_EXTERNAL(h_buf)) || mode == DUK_BUF_MODE_DONTCARE) { /* Note: src_data may be NULL if input is a zero-size * dynamic buffer. */ dst_data = tmp_ptr; goto skip_copy; } } else { /* Non-buffer value is first ToString() coerced, then converted * to a buffer (fixed buffer is used unless a dynamic buffer is * explicitly requested). Symbols are rejected with a TypeError. * XXX: C API could maybe allow symbol-to-buffer coercion? */ src_data = (const duk_uint8_t *) duk_to_lstring(thr, idx, &src_size); } dst_data = (duk_uint8_t *) duk_push_buffer(thr, src_size, (mode == DUK_BUF_MODE_DYNAMIC) /*dynamic*/); /* dst_data may be NULL if size is zero. */ duk_memcpy_unsafe((void *) dst_data, (const void *) src_data, (size_t) src_size); duk_replace(thr, idx); skip_copy: if (out_size) { *out_size = src_size; } return dst_data; } DUK_EXTERNAL void *duk_to_pointer(duk_hthread *thr, duk_idx_t idx) { duk_tval *tv; void *res; DUK_ASSERT_API_ENTRY(thr); idx = duk_require_normalize_index(thr, idx); tv = DUK_GET_TVAL_POSIDX(thr, idx); DUK_ASSERT(tv != NULL); switch (DUK_TVAL_GET_TAG(tv)) { case DUK_TAG_UNDEFINED: case DUK_TAG_NULL: case DUK_TAG_BOOLEAN: res = NULL; break; case DUK_TAG_POINTER: res = DUK_TVAL_GET_POINTER(tv); break; case DUK_TAG_STRING: case DUK_TAG_OBJECT: case DUK_TAG_BUFFER: /* Heap allocated: return heap pointer which is NOT useful * for the caller, except for debugging. */ res = (void *) DUK_TVAL_GET_HEAPHDR(tv); break; case DUK_TAG_LIGHTFUNC: /* Function pointers do not always cast correctly to void * * (depends on memory and segmentation model for instance), * so they coerce to NULL. */ res = NULL; break; #if defined(DUK_USE_FASTINT) case DUK_TAG_FASTINT: #endif default: /* number */ DUK_ASSERT(!DUK_TVAL_IS_UNUSED(tv)); DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv)); res = NULL; break; } duk_push_pointer(thr, res); duk_replace(thr, idx); return res; } DUK_LOCAL void duk__push_func_from_lightfunc(duk_hthread *thr, duk_c_function func, duk_small_uint_t lf_flags) { duk_idx_t nargs; duk_uint_t flags = 0; /* shared flags for a subset of types */ duk_small_uint_t lf_len; duk_hnatfunc *nf; nargs = (duk_idx_t) DUK_LFUNC_FLAGS_GET_NARGS(lf_flags); if (nargs == DUK_LFUNC_NARGS_VARARGS) { nargs = (duk_idx_t) DUK_VARARGS; } flags = DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_FLAG_CONSTRUCTABLE | DUK_HOBJECT_FLAG_CALLABLE | DUK_HOBJECT_FLAG_FASTREFS | DUK_HOBJECT_FLAG_NATFUNC | DUK_HOBJECT_FLAG_NEWENV | DUK_HOBJECT_FLAG_STRICT | DUK_HOBJECT_FLAG_NOTAIL | DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_FUNCTION); (void) duk__push_c_function_raw(thr, func, nargs, flags, DUK_BIDX_NATIVE_FUNCTION_PROTOTYPE); lf_len = DUK_LFUNC_FLAGS_GET_LENGTH(lf_flags); if ((duk_idx_t) lf_len != nargs) { /* Explicit length is only needed if it differs from 'nargs'. */ duk_push_int(thr, (duk_int_t) lf_len); duk_xdef_prop_stridx_short(thr, -2, DUK_STRIDX_LENGTH, DUK_PROPDESC_FLAGS_NONE); } #if defined(DUK_USE_FUNC_NAME_PROPERTY) duk_push_lightfunc_name_raw(thr, func, lf_flags); duk_xdef_prop_stridx_short(thr, -2, DUK_STRIDX_NAME, DUK_PROPDESC_FLAGS_C); #endif nf = duk_known_hnatfunc(thr, -1); nf->magic = (duk_int16_t) DUK_LFUNC_FLAGS_GET_MAGIC(lf_flags); } DUK_EXTERNAL void duk_to_object(duk_hthread *thr, duk_idx_t idx) { duk_tval *tv; duk_uint_t flags = 0; /* shared flags for a subset of types */ duk_small_int_t proto = 0; DUK_ASSERT_API_ENTRY(thr); idx = duk_require_normalize_index(thr, idx); tv = DUK_GET_TVAL_POSIDX(thr, idx); DUK_ASSERT(tv != NULL); switch (DUK_TVAL_GET_TAG(tv)) { #if !defined(DUK_USE_BUFFEROBJECT_SUPPORT) case DUK_TAG_BUFFER: /* With no bufferobject support, don't object coerce. */ #endif case DUK_TAG_UNDEFINED: case DUK_TAG_NULL: { DUK_ERROR_TYPE(thr, DUK_STR_NOT_OBJECT_COERCIBLE); DUK_WO_NORETURN(return;); break; } case DUK_TAG_BOOLEAN: { flags = DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_FLAG_FASTREFS | DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_BOOLEAN); proto = DUK_BIDX_BOOLEAN_PROTOTYPE; goto create_object; } case DUK_TAG_STRING: { duk_hstring *h; h = DUK_TVAL_GET_STRING(tv); DUK_ASSERT(h != NULL); if (DUK_UNLIKELY(DUK_HSTRING_HAS_SYMBOL(h))) { flags = DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_FLAG_FASTREFS | DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_SYMBOL); proto = DUK_BIDX_SYMBOL_PROTOTYPE; } else { flags = DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_FLAG_FASTREFS | DUK_HOBJECT_FLAG_EXOTIC_STRINGOBJ | DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_STRING); proto = DUK_BIDX_STRING_PROTOTYPE; } goto create_object; } case DUK_TAG_OBJECT: { /* nop */ break; } #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) case DUK_TAG_BUFFER: { /* A plain buffer object coerces to a full ArrayBuffer which * is not fully transparent behavior (ToObject() should be a * nop for an object). This behavior matches lightfuncs which * also coerce to an equivalent Function object. There are * also downsides to defining ToObject(plainBuffer) as a no-op; * for example duk_to_hobject() could result in a NULL pointer. */ duk_hbuffer *h_buf; h_buf = DUK_TVAL_GET_BUFFER(tv); DUK_ASSERT(h_buf != NULL); duk_hbufobj_push_uint8array_from_plain(thr, h_buf); goto replace_value; } #endif /* DUK_USE_BUFFEROBJECT_SUPPORT */ case DUK_TAG_POINTER: { flags = DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_FLAG_FASTREFS | DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_POINTER); proto = DUK_BIDX_POINTER_PROTOTYPE; goto create_object; } case DUK_TAG_LIGHTFUNC: { /* Lightfunc coerces to a Function instance with concrete * properties. Since 'length' is virtual for Duktape/C * functions, don't need to define that. The result is made * extensible to mimic what happens to strings in object * coercion: * * > Object.isExtensible(Object('foo')) * true */ duk_small_uint_t lf_flags; duk_c_function func; DUK_TVAL_GET_LIGHTFUNC(tv, func, lf_flags); duk__push_func_from_lightfunc(thr, func, lf_flags); goto replace_value; } #if defined(DUK_USE_FASTINT) case DUK_TAG_FASTINT: #endif default: { DUK_ASSERT(!DUK_TVAL_IS_UNUSED(tv)); DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv)); flags = DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_FLAG_FASTREFS | DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_NUMBER); proto = DUK_BIDX_NUMBER_PROTOTYPE; goto create_object; } } DUK_ASSERT(duk_is_object(thr, idx)); return; create_object: (void) duk_push_object_helper(thr, flags, proto); /* Note: Boolean prototype's internal value property is not writable, * but duk_xdef_prop_stridx() disregards the write protection. Boolean * instances are immutable. * * String and buffer special behaviors are already enabled which is not * ideal, but a write to the internal value is not affected by them. */ duk_dup(thr, idx); duk_xdef_prop_stridx_short(thr, -2, DUK_STRIDX_INT_VALUE, DUK_PROPDESC_FLAGS_NONE); replace_value: duk_replace(thr, idx); DUK_ASSERT(duk_is_object(thr, idx)); } DUK_INTERNAL duk_hobject *duk_to_hobject(duk_hthread *thr, duk_idx_t idx) { duk_hobject *ret; DUK_ASSERT_API_ENTRY(thr); duk_to_object(thr, idx); ret = duk_known_hobject(thr, idx); return ret; } /* * Type checking */ DUK_LOCAL duk_bool_t duk__tag_check(duk_hthread *thr, duk_idx_t idx, duk_small_uint_t tag) { duk_tval *tv; tv = duk_get_tval_or_unused(thr, idx); DUK_ASSERT(tv != NULL); return (DUK_TVAL_GET_TAG(tv) == tag); } DUK_LOCAL duk_bool_t duk__obj_flag_any_default_false(duk_hthread *thr, duk_idx_t idx, duk_uint_t flag_mask) { duk_hobject *obj; DUK_ASSERT_API_ENTRY(thr); obj = duk_get_hobject(thr, idx); if (obj) { return (DUK_HEAPHDR_CHECK_FLAG_BITS((duk_heaphdr *) obj, flag_mask) ? 1 : 0); } return 0; } DUK_INTERNAL duk_int_t duk_get_type_tval(duk_tval *tv) { DUK_ASSERT(tv != NULL); #if defined(DUK_USE_PACKED_TVAL) switch (DUK_TVAL_GET_TAG(tv)) { case DUK_TAG_UNUSED: return DUK_TYPE_NONE; case DUK_TAG_UNDEFINED: return DUK_TYPE_UNDEFINED; case DUK_TAG_NULL: return DUK_TYPE_NULL; case DUK_TAG_BOOLEAN: return DUK_TYPE_BOOLEAN; case DUK_TAG_STRING: return DUK_TYPE_STRING; case DUK_TAG_OBJECT: return DUK_TYPE_OBJECT; case DUK_TAG_BUFFER: return DUK_TYPE_BUFFER; case DUK_TAG_POINTER: return DUK_TYPE_POINTER; case DUK_TAG_LIGHTFUNC: return DUK_TYPE_LIGHTFUNC; #if defined(DUK_USE_FASTINT) case DUK_TAG_FASTINT: #endif default: /* Note: number has no explicit tag (in 8-byte representation) */ DUK_ASSERT(!DUK_TVAL_IS_UNUSED(tv)); DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv)); return DUK_TYPE_NUMBER; } #else /* DUK_USE_PACKED_TVAL */ DUK_ASSERT(DUK_TVAL_IS_VALID_TAG(tv)); DUK_ASSERT(sizeof(duk__type_from_tag) / sizeof(duk_uint_t) == DUK_TAG_MAX - DUK_TAG_MIN + 1); return (duk_int_t) duk__type_from_tag[DUK_TVAL_GET_TAG(tv) - DUK_TAG_MIN]; #endif /* DUK_USE_PACKED_TVAL */ } DUK_EXTERNAL duk_int_t duk_get_type(duk_hthread *thr, duk_idx_t idx) { duk_tval *tv; DUK_ASSERT_API_ENTRY(thr); tv = duk_get_tval_or_unused(thr, idx); DUK_ASSERT(tv != NULL); return duk_get_type_tval(tv); } #if defined(DUK_USE_VERBOSE_ERRORS) && defined(DUK_USE_PARANOID_ERRORS) DUK_LOCAL const char * const duk__type_names[] = { "none", "undefined", "null", "boolean", "number", "string", "object", "buffer", "pointer", "lightfunc" }; DUK_INTERNAL const char *duk_get_type_name(duk_hthread *thr, duk_idx_t idx) { duk_int_t type_tag; DUK_ASSERT_API_ENTRY(thr); type_tag = duk_get_type(thr, idx); DUK_ASSERT(type_tag >= DUK_TYPE_MIN && type_tag <= DUK_TYPE_MAX); DUK_ASSERT(DUK_TYPE_MIN == 0 && sizeof(duk__type_names) / sizeof(const char *) == DUK_TYPE_MAX + 1); return duk__type_names[type_tag]; } #endif /* DUK_USE_VERBOSE_ERRORS && DUK_USE_PARANOID_ERRORS */ DUK_INTERNAL duk_small_uint_t duk_get_class_number(duk_hthread *thr, duk_idx_t idx) { duk_tval *tv; duk_hobject *obj; DUK_ASSERT_API_ENTRY(thr); tv = duk_get_tval_or_unused(thr, idx); DUK_ASSERT(tv != NULL); switch (DUK_TVAL_GET_TAG(tv)) { case DUK_TAG_OBJECT: obj = DUK_TVAL_GET_OBJECT(tv); DUK_ASSERT(obj != NULL); return DUK_HOBJECT_GET_CLASS_NUMBER(obj); case DUK_TAG_BUFFER: /* Buffers behave like Uint8Array objects. */ return DUK_HOBJECT_CLASS_UINT8ARRAY; case DUK_TAG_LIGHTFUNC: /* Lightfuncs behave like Function objects. */ return DUK_HOBJECT_CLASS_FUNCTION; default: /* Primitive or UNUSED, no class number. */ return DUK_HOBJECT_CLASS_NONE; } } DUK_EXTERNAL duk_bool_t duk_check_type(duk_hthread *thr, duk_idx_t idx, duk_int_t type) { DUK_ASSERT_API_ENTRY(thr); return (duk_get_type(thr, idx) == type) ? 1 : 0; } DUK_INTERNAL duk_uint_t duk_get_type_mask_tval(duk_tval *tv) { DUK_ASSERT(tv != NULL); #if defined(DUK_USE_PACKED_TVAL) switch (DUK_TVAL_GET_TAG(tv)) { case DUK_TAG_UNUSED: return DUK_TYPE_MASK_NONE; case DUK_TAG_UNDEFINED: return DUK_TYPE_MASK_UNDEFINED; case DUK_TAG_NULL: return DUK_TYPE_MASK_NULL; case DUK_TAG_BOOLEAN: return DUK_TYPE_MASK_BOOLEAN; case DUK_TAG_STRING: return DUK_TYPE_MASK_STRING; case DUK_TAG_OBJECT: return DUK_TYPE_MASK_OBJECT; case DUK_TAG_BUFFER: return DUK_TYPE_MASK_BUFFER; case DUK_TAG_POINTER: return DUK_TYPE_MASK_POINTER; case DUK_TAG_LIGHTFUNC: return DUK_TYPE_MASK_LIGHTFUNC; #if defined(DUK_USE_FASTINT) case DUK_TAG_FASTINT: #endif default: /* Note: number has no explicit tag (in 8-byte representation) */ DUK_ASSERT(!DUK_TVAL_IS_UNUSED(tv)); DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv)); return DUK_TYPE_MASK_NUMBER; } #else /* DUK_USE_PACKED_TVAL */ DUK_ASSERT(DUK_TVAL_IS_VALID_TAG(tv)); DUK_ASSERT(sizeof(duk__type_mask_from_tag) / sizeof(duk_uint_t) == DUK_TAG_MAX - DUK_TAG_MIN + 1); return duk__type_mask_from_tag[DUK_TVAL_GET_TAG(tv) - DUK_TAG_MIN]; #endif /* DUK_USE_PACKED_TVAL */ } DUK_EXTERNAL duk_uint_t duk_get_type_mask(duk_hthread *thr, duk_idx_t idx) { duk_tval *tv; DUK_ASSERT_API_ENTRY(thr); tv = duk_get_tval_or_unused(thr, idx); DUK_ASSERT(tv != NULL); return duk_get_type_mask_tval(tv); } DUK_EXTERNAL duk_bool_t duk_check_type_mask(duk_hthread *thr, duk_idx_t idx, duk_uint_t mask) { DUK_ASSERT_API_ENTRY(thr); if (DUK_LIKELY((duk_get_type_mask(thr, idx) & mask) != 0U)) { return 1; } if (mask & DUK_TYPE_MASK_THROW) { DUK_ERROR_TYPE(thr, DUK_STR_UNEXPECTED_TYPE); DUK_WO_NORETURN(return 0;); } return 0; } DUK_EXTERNAL duk_bool_t duk_is_undefined(duk_hthread *thr, duk_idx_t idx) { DUK_ASSERT_API_ENTRY(thr); return duk__tag_check(thr, idx, DUK_TAG_UNDEFINED); } DUK_EXTERNAL duk_bool_t duk_is_null(duk_hthread *thr, duk_idx_t idx) { DUK_ASSERT_API_ENTRY(thr); return duk__tag_check(thr, idx, DUK_TAG_NULL); } DUK_EXTERNAL duk_bool_t duk_is_boolean(duk_hthread *thr, duk_idx_t idx) { DUK_ASSERT_API_ENTRY(thr); return duk__tag_check(thr, idx, DUK_TAG_BOOLEAN); } DUK_EXTERNAL duk_bool_t duk_is_number(duk_hthread *thr, duk_idx_t idx) { duk_tval *tv; DUK_ASSERT_API_ENTRY(thr); /* * Number is special because it doesn't have a specific * tag in the 8-byte representation. */ /* XXX: shorter version for unpacked representation? */ tv = duk_get_tval_or_unused(thr, idx); DUK_ASSERT(tv != NULL); return DUK_TVAL_IS_NUMBER(tv); } DUK_EXTERNAL duk_bool_t duk_is_nan(duk_hthread *thr, duk_idx_t idx) { /* XXX: This will now return false for non-numbers, even though they would * coerce to NaN (as a general rule). In particular, duk_get_number() * returns a NaN for non-numbers, so should this function also return * true for non-numbers? */ duk_tval *tv; DUK_ASSERT_API_ENTRY(thr); tv = duk_get_tval_or_unused(thr, idx); DUK_ASSERT(tv != NULL); /* XXX: for packed duk_tval an explicit "is number" check is unnecessary */ if (!DUK_TVAL_IS_NUMBER(tv)) { return 0; } return (duk_bool_t) DUK_ISNAN(DUK_TVAL_GET_NUMBER(tv)); } DUK_EXTERNAL duk_bool_t duk_is_string(duk_hthread *thr, duk_idx_t idx) { DUK_ASSERT_API_ENTRY(thr); return duk__tag_check(thr, idx, DUK_TAG_STRING); } DUK_INTERNAL duk_bool_t duk_is_string_notsymbol(duk_hthread *thr, duk_idx_t idx) { DUK_ASSERT_API_ENTRY(thr); return duk_get_hstring_notsymbol(thr, idx) != NULL; } DUK_EXTERNAL duk_bool_t duk_is_object(duk_hthread *thr, duk_idx_t idx) { DUK_ASSERT_API_ENTRY(thr); return duk__tag_check(thr, idx, DUK_TAG_OBJECT); } DUK_EXTERNAL duk_bool_t duk_is_buffer(duk_hthread *thr, duk_idx_t idx) { DUK_ASSERT_API_ENTRY(thr); return duk__tag_check(thr, idx, DUK_TAG_BUFFER); } #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) DUK_EXTERNAL duk_bool_t duk_is_buffer_data(duk_hthread *thr, duk_idx_t idx) { duk_tval *tv; DUK_ASSERT_API_ENTRY(thr); tv = duk_get_tval_or_unused(thr, idx); DUK_ASSERT(tv != NULL); if (DUK_TVAL_IS_BUFFER(tv)) { return 1; } else if (DUK_TVAL_IS_OBJECT(tv)) { duk_hobject *h = DUK_TVAL_GET_OBJECT(tv); DUK_ASSERT(h != NULL); if (DUK_HOBJECT_IS_BUFOBJ(h)) { return 1; } } return 0; } #else /* DUK_USE_BUFFEROBJECT_SUPPORT */ DUK_EXTERNAL duk_bool_t duk_is_buffer_data(duk_hthread *thr, duk_idx_t idx) { DUK_ASSERT_API_ENTRY(thr); return duk_is_buffer(thr, idx); } #endif /* DUK_USE_BUFFEROBJECT_SUPPORT */ DUK_EXTERNAL duk_bool_t duk_is_pointer(duk_hthread *thr, duk_idx_t idx) { DUK_ASSERT_API_ENTRY(thr); return duk__tag_check(thr, idx, DUK_TAG_POINTER); } DUK_EXTERNAL duk_bool_t duk_is_lightfunc(duk_hthread *thr, duk_idx_t idx) { DUK_ASSERT_API_ENTRY(thr); return duk__tag_check(thr, idx, DUK_TAG_LIGHTFUNC); } DUK_EXTERNAL duk_bool_t duk_is_symbol(duk_hthread *thr, duk_idx_t idx) { duk_hstring *h; DUK_ASSERT_API_ENTRY(thr); h = duk_get_hstring(thr, idx); /* Use DUK_LIKELY() here because caller may be more likely to type * check an expected symbol than not. */ if (DUK_LIKELY(h != NULL && DUK_HSTRING_HAS_SYMBOL(h))) { return 1; } return 0; } /* IsArray(), returns true for Array instance or Proxy of Array instance. */ DUK_EXTERNAL duk_bool_t duk_is_array(duk_hthread *thr, duk_idx_t idx) { duk_tval *tv; DUK_ASSERT_API_ENTRY(thr); tv = duk_get_tval(thr, idx); if (tv) { return duk_js_isarray(tv); } return 0; } DUK_EXTERNAL duk_bool_t duk_is_function(duk_hthread *thr, duk_idx_t idx) { duk_tval *tv; DUK_ASSERT_API_ENTRY(thr); tv = duk_get_tval_or_unused(thr, idx); if (DUK_TVAL_IS_OBJECT(tv)) { duk_hobject *h; h = DUK_TVAL_GET_OBJECT(tv); DUK_ASSERT(h != NULL); return DUK_HOBJECT_HAS_CALLABLE(h) ? 1 : 0; } if (DUK_TVAL_IS_LIGHTFUNC(tv)) { return 1; } return 0; } DUK_INTERNAL duk_bool_t duk_is_callable_tval(duk_hthread *thr, duk_tval *tv) { DUK_ASSERT_API_ENTRY(thr); DUK_UNREF(thr); if (DUK_TVAL_IS_OBJECT(tv)) { duk_hobject *h; h = DUK_TVAL_GET_OBJECT(tv); DUK_ASSERT(h != NULL); return DUK_HOBJECT_HAS_CALLABLE(h) ? 1 : 0; } if (DUK_TVAL_IS_LIGHTFUNC(tv)) { return 1; } return 0; } DUK_EXTERNAL duk_bool_t duk_is_constructable(duk_hthread *thr, duk_idx_t idx) { duk_tval *tv; DUK_ASSERT_API_ENTRY(thr); tv = duk_get_tval_or_unused(thr, idx); if (DUK_TVAL_IS_OBJECT(tv)) { duk_hobject *h; h = DUK_TVAL_GET_OBJECT(tv); DUK_ASSERT(h != NULL); return DUK_HOBJECT_HAS_CONSTRUCTABLE(h) ? 1 : 0; } if (DUK_TVAL_IS_LIGHTFUNC(tv)) { return 1; } return 0; } DUK_EXTERNAL duk_bool_t duk_is_c_function(duk_hthread *thr, duk_idx_t idx) { DUK_ASSERT_API_ENTRY(thr); return duk__obj_flag_any_default_false(thr, idx, DUK_HOBJECT_FLAG_NATFUNC); } DUK_EXTERNAL duk_bool_t duk_is_ecmascript_function(duk_hthread *thr, duk_idx_t idx) { DUK_ASSERT_API_ENTRY(thr); return duk__obj_flag_any_default_false(thr, idx, DUK_HOBJECT_FLAG_COMPFUNC); } DUK_EXTERNAL duk_bool_t duk_is_bound_function(duk_hthread *thr, duk_idx_t idx) { DUK_ASSERT_API_ENTRY(thr); return duk__obj_flag_any_default_false(thr, idx, DUK_HOBJECT_FLAG_BOUNDFUNC); } DUK_EXTERNAL duk_bool_t duk_is_thread(duk_hthread *thr, duk_idx_t idx) { duk_hobject *obj; DUK_ASSERT_API_ENTRY(thr); obj = duk_get_hobject(thr, idx); if (obj) { return (DUK_HOBJECT_GET_CLASS_NUMBER(obj) == DUK_HOBJECT_CLASS_THREAD ? 1 : 0); } return 0; } DUK_EXTERNAL duk_bool_t duk_is_fixed_buffer(duk_hthread *thr, duk_idx_t idx) { duk_tval *tv; DUK_ASSERT_API_ENTRY(thr); tv = duk_get_tval_or_unused(thr, idx); DUK_ASSERT(tv != NULL); if (DUK_TVAL_IS_BUFFER(tv)) { duk_hbuffer *h = DUK_TVAL_GET_BUFFER(tv); DUK_ASSERT(h != NULL); return (DUK_HBUFFER_HAS_DYNAMIC(h) ? 0 : 1); } return 0; } DUK_EXTERNAL duk_bool_t duk_is_dynamic_buffer(duk_hthread *thr, duk_idx_t idx) { duk_tval *tv; DUK_ASSERT_API_ENTRY(thr); tv = duk_get_tval_or_unused(thr, idx); DUK_ASSERT(tv != NULL); if (DUK_TVAL_IS_BUFFER(tv)) { duk_hbuffer *h = DUK_TVAL_GET_BUFFER(tv); DUK_ASSERT(h != NULL); return (DUK_HBUFFER_HAS_DYNAMIC(h) && !DUK_HBUFFER_HAS_EXTERNAL(h) ? 1 : 0); } return 0; } DUK_EXTERNAL duk_bool_t duk_is_external_buffer(duk_hthread *thr, duk_idx_t idx) { duk_tval *tv; DUK_ASSERT_API_ENTRY(thr); tv = duk_get_tval_or_unused(thr, idx); DUK_ASSERT(tv != NULL); if (DUK_TVAL_IS_BUFFER(tv)) { duk_hbuffer *h = DUK_TVAL_GET_BUFFER(tv); DUK_ASSERT(h != NULL); return (DUK_HBUFFER_HAS_DYNAMIC(h) && DUK_HBUFFER_HAS_EXTERNAL(h) ? 1 : 0); } return 0; } DUK_EXTERNAL duk_errcode_t duk_get_error_code(duk_hthread *thr, duk_idx_t idx) { duk_hobject *h; duk_uint_t sanity; DUK_ASSERT_API_ENTRY(thr); h = duk_get_hobject(thr, idx); sanity = DUK_HOBJECT_PROTOTYPE_CHAIN_SANITY; do { if (!h) { return DUK_ERR_NONE; } /* XXX: something more convenient? */ if (h == thr->builtins[DUK_BIDX_EVAL_ERROR_PROTOTYPE]) { return DUK_ERR_EVAL_ERROR; } if (h == thr->builtins[DUK_BIDX_RANGE_ERROR_PROTOTYPE]) { return DUK_ERR_RANGE_ERROR; } if (h == thr->builtins[DUK_BIDX_REFERENCE_ERROR_PROTOTYPE]) { return DUK_ERR_REFERENCE_ERROR; } if (h == thr->builtins[DUK_BIDX_SYNTAX_ERROR_PROTOTYPE]) { return DUK_ERR_SYNTAX_ERROR; } if (h == thr->builtins[DUK_BIDX_TYPE_ERROR_PROTOTYPE]) { return DUK_ERR_TYPE_ERROR; } if (h == thr->builtins[DUK_BIDX_URI_ERROR_PROTOTYPE]) { return DUK_ERR_URI_ERROR; } if (h == thr->builtins[DUK_BIDX_ERROR_PROTOTYPE]) { return DUK_ERR_ERROR; } h = DUK_HOBJECT_GET_PROTOTYPE(thr->heap, h); } while (--sanity > 0); return DUK_ERR_NONE; } /* * Pushers */ DUK_INTERNAL void duk_push_tval(duk_hthread *thr, duk_tval *tv) { duk_tval *tv_slot; DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(tv != NULL); DUK__CHECK_SPACE(); tv_slot = thr->valstack_top++; DUK_TVAL_SET_TVAL(tv_slot, tv); DUK_TVAL_INCREF(thr, tv); /* no side effects */ } DUK_EXTERNAL void duk_push_undefined(duk_hthread *thr) { DUK_ASSERT_API_ENTRY(thr); DUK__CHECK_SPACE(); /* Because value stack init policy is 'undefined above top', * we don't need to write, just assert. */ thr->valstack_top++; DUK_ASSERT(DUK_TVAL_IS_UNDEFINED(thr->valstack_top - 1)); } DUK_EXTERNAL void duk_push_null(duk_hthread *thr) { duk_tval *tv_slot; DUK_ASSERT_API_ENTRY(thr); DUK__CHECK_SPACE(); tv_slot = thr->valstack_top++; DUK_TVAL_SET_NULL(tv_slot); } DUK_EXTERNAL void duk_push_boolean(duk_hthread *thr, duk_bool_t val) { duk_tval *tv_slot; duk_small_int_t b; DUK_ASSERT_API_ENTRY(thr); DUK__CHECK_SPACE(); b = (val ? 1 : 0); /* ensure value is 1 or 0 (not other non-zero) */ tv_slot = thr->valstack_top++; DUK_TVAL_SET_BOOLEAN(tv_slot, b); } DUK_EXTERNAL void duk_push_true(duk_hthread *thr) { duk_tval *tv_slot; DUK_ASSERT_API_ENTRY(thr); DUK__CHECK_SPACE(); tv_slot = thr->valstack_top++; DUK_TVAL_SET_BOOLEAN_TRUE(tv_slot); } DUK_EXTERNAL void duk_push_false(duk_hthread *thr) { duk_tval *tv_slot; DUK_ASSERT_API_ENTRY(thr); DUK__CHECK_SPACE(); tv_slot = thr->valstack_top++; DUK_TVAL_SET_BOOLEAN_FALSE(tv_slot); } /* normalize NaN which may not match our canonical internal NaN */ DUK_EXTERNAL void duk_push_number(duk_hthread *thr, duk_double_t val) { duk_tval *tv_slot; duk_double_union du; DUK_ASSERT_API_ENTRY(thr); DUK__CHECK_SPACE(); du.d = val; DUK_DBLUNION_NORMALIZE_NAN_CHECK(&du); tv_slot = thr->valstack_top++; DUK_TVAL_SET_NUMBER(tv_slot, du.d); } DUK_EXTERNAL void duk_push_int(duk_hthread *thr, duk_int_t val) { #if defined(DUK_USE_FASTINT) duk_tval *tv_slot; DUK_ASSERT_API_ENTRY(thr); DUK__CHECK_SPACE(); tv_slot = thr->valstack_top++; #if DUK_INT_MAX <= 0x7fffffffL DUK_TVAL_SET_I32(tv_slot, (duk_int32_t) val); #else if (val >= DUK_FASTINT_MIN && val <= DUK_FASTINT_MAX) { DUK_TVAL_SET_FASTINT(tv_slot, (duk_int64_t) val); } else { duk_double_t = (duk_double_t) val; DUK_TVAL_SET_NUMBER(tv_slot, d); } #endif #else /* DUK_USE_FASTINT */ duk_tval *tv_slot; duk_double_t d; DUK_ASSERT_API_ENTRY(thr); DUK__CHECK_SPACE(); d = (duk_double_t) val; tv_slot = thr->valstack_top++; DUK_TVAL_SET_NUMBER(tv_slot, d); #endif /* DUK_USE_FASTINT */ } DUK_EXTERNAL void duk_push_uint(duk_hthread *thr, duk_uint_t val) { #if defined(DUK_USE_FASTINT) duk_tval *tv_slot; DUK_ASSERT_API_ENTRY(thr); DUK__CHECK_SPACE(); tv_slot = thr->valstack_top++; #if DUK_UINT_MAX <= 0xffffffffUL DUK_TVAL_SET_U32(tv_slot, (duk_uint32_t) val); #else if (val <= DUK_FASTINT_MAX) { /* val is unsigned so >= 0 */ /* XXX: take advantage of val being unsigned, no need to mask */ DUK_TVAL_SET_FASTINT(tv_slot, (duk_int64_t) val); } else { duk_double_t = (duk_double_t) val; DUK_TVAL_SET_NUMBER(tv_slot, d); } #endif #else /* DUK_USE_FASTINT */ duk_tval *tv_slot; duk_double_t d; DUK_ASSERT_API_ENTRY(thr); DUK__CHECK_SPACE(); d = (duk_double_t) val; tv_slot = thr->valstack_top++; DUK_TVAL_SET_NUMBER(tv_slot, d); #endif /* DUK_USE_FASTINT */ } DUK_EXTERNAL void duk_push_nan(duk_hthread *thr) { duk_tval *tv_slot; duk_double_union du; DUK_ASSERT_API_ENTRY(thr); DUK__CHECK_SPACE(); DUK_DBLUNION_SET_NAN(&du); DUK_ASSERT(DUK_DBLUNION_IS_NORMALIZED(&du)); tv_slot = thr->valstack_top++; DUK_TVAL_SET_NUMBER(tv_slot, du.d); } DUK_EXTERNAL const char *duk_push_lstring(duk_hthread *thr, const char *str, duk_size_t len) { duk_hstring *h; duk_tval *tv_slot; DUK_ASSERT_API_ENTRY(thr); /* Check stack before interning (avoid hanging temp). */ DUK__CHECK_SPACE(); /* NULL with zero length represents an empty string; NULL with higher * length is also now treated like an empty string although it is * a bit dubious. This is unlike duk_push_string() which pushes a * 'null' if the input string is a NULL. */ if (DUK_UNLIKELY(str == NULL)) { len = 0U; } /* Check for maximum string length. */ if (DUK_UNLIKELY(len > DUK_HSTRING_MAX_BYTELEN)) { DUK_ERROR_RANGE(thr, DUK_STR_STRING_TOO_LONG); DUK_WO_NORETURN(return NULL;); } h = duk_heap_strtable_intern_checked(thr, (const duk_uint8_t *) str, (duk_uint32_t) len); DUK_ASSERT(h != NULL); tv_slot = thr->valstack_top++; DUK_TVAL_SET_STRING(tv_slot, h); DUK_HSTRING_INCREF(thr, h); /* no side effects */ return (const char *) DUK_HSTRING_GET_DATA(h); } DUK_EXTERNAL const char *duk_push_string(duk_hthread *thr, const char *str) { DUK_ASSERT_API_ENTRY(thr); if (str) { return duk_push_lstring(thr, str, DUK_STRLEN(str)); } else { duk_push_null(thr); return NULL; } } #if !defined(DUK_USE_PREFER_SIZE) #if defined(DUK_USE_LITCACHE_SIZE) DUK_EXTERNAL const char *duk_push_literal_raw(duk_hthread *thr, const char *str, duk_size_t len) { duk_hstring *h; duk_tval *tv_slot; DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(str != NULL); DUK_ASSERT(str[len] == (char) 0); /* Check for maximum string length. */ if (DUK_UNLIKELY(len > DUK_HSTRING_MAX_BYTELEN)) { DUK_ERROR_RANGE(thr, DUK_STR_STRING_TOO_LONG); DUK_WO_NORETURN(return NULL;); } h = duk_heap_strtable_intern_literal_checked(thr, (const duk_uint8_t *) str, (duk_uint32_t) len); DUK_ASSERT(h != NULL); tv_slot = thr->valstack_top++; DUK_TVAL_SET_STRING(tv_slot, h); DUK_HSTRING_INCREF(thr, h); /* no side effects */ return (const char *) DUK_HSTRING_GET_DATA(h); } #else /* DUK_USE_LITCACHE_SIZE */ DUK_EXTERNAL const char *duk_push_literal_raw(duk_hthread *thr, const char *str, duk_size_t len) { DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(str != NULL); DUK_ASSERT(str[len] == (char) 0); return duk_push_lstring(thr, str, len); } #endif /* DUK_USE_LITCACHE_SIZE */ #endif /* !DUK_USE_PREFER_SIZE */ DUK_EXTERNAL void duk_push_pointer(duk_hthread *thr, void *val) { duk_tval *tv_slot; DUK_ASSERT_API_ENTRY(thr); DUK__CHECK_SPACE(); tv_slot = thr->valstack_top++; DUK_TVAL_SET_POINTER(tv_slot, val); } DUK_INTERNAL duk_hstring *duk_push_uint_to_hstring(duk_hthread *thr, duk_uint_t i) { duk_hstring *h_tmp; DUK_ASSERT_API_ENTRY(thr); /* XXX: this could be a direct DUK_SPRINTF to a buffer followed by duk_push_string() */ duk_push_uint(thr, (duk_uint_t) i); h_tmp = duk_to_hstring_m1(thr); DUK_ASSERT(h_tmp != NULL); return h_tmp; } DUK_LOCAL void duk__push_this_helper(duk_hthread *thr, duk_small_uint_t check_object_coercible) { duk_tval *tv_slot; DUK__CHECK_SPACE(); DUK_ASSERT(DUK_TVAL_IS_UNDEFINED(thr->valstack_top)); /* because of valstack init policy */ tv_slot = thr->valstack_top++; if (DUK_UNLIKELY(thr->callstack_curr == NULL)) { if (check_object_coercible) { goto type_error; } /* 'undefined' already on stack top */ } else { duk_tval *tv; /* 'this' binding is just before current activation's bottom */ DUK_ASSERT(thr->valstack_bottom > thr->valstack); tv = thr->valstack_bottom - 1; if (check_object_coercible && (DUK_TVAL_IS_UNDEFINED(tv) || DUK_TVAL_IS_NULL(tv))) { /* XXX: better macro for DUK_TVAL_IS_UNDEFINED_OR_NULL(tv) */ goto type_error; } DUK_TVAL_SET_TVAL(tv_slot, tv); DUK_TVAL_INCREF(thr, tv); } return; type_error: DUK_ERROR_TYPE(thr, DUK_STR_NOT_OBJECT_COERCIBLE); DUK_WO_NORETURN(return;); } DUK_EXTERNAL void duk_push_this(duk_hthread *thr) { DUK_ASSERT_API_ENTRY(thr); duk__push_this_helper(thr, 0 /*check_object_coercible*/); } DUK_INTERNAL void duk_push_this_check_object_coercible(duk_hthread *thr) { DUK_ASSERT_API_ENTRY(thr); duk__push_this_helper(thr, 1 /*check_object_coercible*/); } DUK_INTERNAL duk_hobject *duk_push_this_coercible_to_object(duk_hthread *thr) { duk_hobject *h; DUK_ASSERT_API_ENTRY(thr); duk__push_this_helper(thr, 1 /*check_object_coercible*/); h = duk_to_hobject(thr, -1); DUK_ASSERT(h != NULL); return h; } DUK_INTERNAL duk_hstring *duk_push_this_coercible_to_string(duk_hthread *thr) { DUK_ASSERT_API_ENTRY(thr); duk__push_this_helper(thr, 1 /*check_object_coercible*/); return duk_to_hstring_m1(thr); /* This will reject all Symbol values; accepts Symbol objects. */ } DUK_INTERNAL duk_tval *duk_get_borrowed_this_tval(duk_hthread *thr) { DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(thr->callstack_top > 0); /* caller required to know */ DUK_ASSERT(thr->callstack_curr != NULL); /* caller required to know */ DUK_ASSERT(thr->valstack_bottom > thr->valstack); /* consequence of above */ DUK_ASSERT(thr->valstack_bottom - 1 >= thr->valstack); /* 'this' binding exists */ return thr->valstack_bottom - 1; } DUK_EXTERNAL void duk_push_new_target(duk_hthread *thr) { duk_activation *act; DUK_ASSERT_API_ENTRY(thr); /* https://www.ecma-international.org/ecma-262/6.0/#sec-meta-properties-runtime-semantics-evaluation * https://www.ecma-international.org/ecma-262/6.0/#sec-getnewtarget * * No newTarget support now, so as a first approximation * use the resolved (non-bound) target function. * * Check CONSTRUCT flag from current function, or if running * direct eval, from a non-direct-eval parent (with possibly * more than one nested direct eval). An alternative to this * would be to store [[NewTarget]] as a hidden symbol of the * lexical scope, and then just look up that variable. * * Calls from the application will either be for an empty * call stack, or a Duktape/C function as the top activation. */ act = thr->callstack_curr; for (;;) { if (act == NULL) { break; } if (act->flags & DUK_ACT_FLAG_CONSTRUCT) { duk_push_tval(thr, &act->tv_func); return; } else if (act->flags & DUK_ACT_FLAG_DIRECT_EVAL) { act = act->parent; } else { break; } } duk_push_undefined(thr); } DUK_EXTERNAL void duk_push_current_function(duk_hthread *thr) { duk_activation *act; DUK_ASSERT_API_ENTRY(thr); act = thr->callstack_curr; if (act != NULL) { duk_push_tval(thr, &act->tv_func); } else { duk_push_undefined(thr); } } DUK_EXTERNAL void duk_push_current_thread(duk_hthread *thr) { DUK_ASSERT_API_ENTRY(thr); if (thr->heap->curr_thread) { duk_push_hobject(thr, (duk_hobject *) thr->heap->curr_thread); } else { duk_push_undefined(thr); } } DUK_EXTERNAL void duk_push_global_object(duk_hthread *thr) { DUK_ASSERT_API_ENTRY(thr); duk_push_hobject_bidx(thr, DUK_BIDX_GLOBAL); } /* XXX: size optimize */ DUK_LOCAL void duk__push_stash(duk_hthread *thr) { if (!duk_xget_owndataprop_stridx_short(thr, -1, DUK_STRIDX_INT_VALUE)) { DUK_DDD(DUK_DDDPRINT("creating heap/global/thread stash on first use")); duk_pop_unsafe(thr); duk_push_bare_object(thr); duk_dup_top(thr); duk_xdef_prop_stridx_short(thr, -3, DUK_STRIDX_INT_VALUE, DUK_PROPDESC_FLAGS_C); /* [ ... parent stash stash ] -> [ ... parent stash ] */ } duk_remove_m2(thr); } DUK_EXTERNAL void duk_push_heap_stash(duk_hthread *thr) { duk_heap *heap; DUK_ASSERT_API_ENTRY(thr); heap = thr->heap; DUK_ASSERT(heap->heap_object != NULL); duk_push_hobject(thr, heap->heap_object); duk__push_stash(thr); } DUK_EXTERNAL void duk_push_global_stash(duk_hthread *thr) { DUK_ASSERT_API_ENTRY(thr); duk_push_global_object(thr); duk__push_stash(thr); } DUK_EXTERNAL void duk_push_thread_stash(duk_hthread *thr, duk_hthread *target_thr) { DUK_ASSERT_API_ENTRY(thr); if (DUK_UNLIKELY(target_thr == NULL)) { DUK_ERROR_TYPE_INVALID_ARGS(thr); DUK_WO_NORETURN(return;); } duk_push_hobject(thr, (duk_hobject *) target_thr); duk__push_stash(thr); } /* XXX: duk_ssize_t would be useful here */ DUK_LOCAL duk_int_t duk__try_push_vsprintf(duk_hthread *thr, void *buf, duk_size_t sz, const char *fmt, va_list ap) { duk_int_t len; DUK_CTX_ASSERT_VALID(thr); DUK_UNREF(thr); /* NUL terminator handling doesn't matter here */ len = DUK_VSNPRINTF((char *) buf, sz, fmt, ap); if (len < (duk_int_t) sz) { /* Return value of 'sz' or more indicates output was (potentially) * truncated. */ return (duk_int_t) len; } return -1; } DUK_EXTERNAL const char *duk_push_vsprintf(duk_hthread *thr, const char *fmt, va_list ap) { duk_uint8_t stack_buf[DUK_PUSH_SPRINTF_INITIAL_SIZE]; duk_size_t sz = DUK_PUSH_SPRINTF_INITIAL_SIZE; duk_bool_t pushed_buf = 0; void *buf; duk_int_t len; /* XXX: duk_ssize_t */ const char *res; DUK_ASSERT_API_ENTRY(thr); /* special handling of fmt==NULL */ if (!fmt) { duk_hstring *h_str; duk_push_hstring_empty(thr); h_str = duk_known_hstring(thr, -1); return (const char *) DUK_HSTRING_GET_DATA(h_str); } /* initial estimate based on format string */ sz = DUK_STRLEN(fmt) + 16; /* format plus something to avoid just missing */ if (sz < DUK_PUSH_SPRINTF_INITIAL_SIZE) { sz = DUK_PUSH_SPRINTF_INITIAL_SIZE; } DUK_ASSERT(sz > 0); /* Try to make do with a stack buffer to avoid allocating a temporary buffer. * This works 99% of the time which is quite nice. */ for (;;) { va_list ap_copy; /* copied so that 'ap' can be reused */ if (sz <= sizeof(stack_buf)) { buf = stack_buf; } else if (!pushed_buf) { pushed_buf = 1; buf = duk_push_dynamic_buffer(thr, sz); } else { buf = duk_resize_buffer(thr, -1, sz); } DUK_ASSERT(buf != NULL); DUK_VA_COPY(ap_copy, ap); len = duk__try_push_vsprintf(thr, buf, sz, fmt, ap_copy); va_end(ap_copy); if (len >= 0) { break; } /* failed, resize and try again */ sz = sz * 2; if (DUK_UNLIKELY(sz >= DUK_PUSH_SPRINTF_SANITY_LIMIT)) { DUK_ERROR_RANGE(thr, DUK_STR_RESULT_TOO_LONG); DUK_WO_NORETURN(return NULL;); } } /* Cannot use duk_buffer_to_string() on the buffer because it is * usually larger than 'len'; 'buf' is also usually a stack buffer. */ res = duk_push_lstring(thr, (const char *) buf, (duk_size_t) len); /* [ buf? res ] */ if (pushed_buf) { duk_remove_m2(thr); } return res; } DUK_EXTERNAL const char *duk_push_sprintf(duk_hthread *thr, const char *fmt, ...) { va_list ap; const char *ret; DUK_ASSERT_API_ENTRY(thr); /* allow fmt==NULL */ va_start(ap, fmt); ret = duk_push_vsprintf(thr, fmt, ap); va_end(ap); return ret; } DUK_INTERNAL duk_hobject *duk_push_object_helper(duk_hthread *thr, duk_uint_t hobject_flags_and_class, duk_small_int_t prototype_bidx) { duk_tval *tv_slot; duk_hobject *h; DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(prototype_bidx == -1 || (prototype_bidx >= 0 && prototype_bidx < DUK_NUM_BUILTINS)); DUK__CHECK_SPACE(); h = duk_hobject_alloc(thr, hobject_flags_and_class); DUK_ASSERT(h != NULL); DUK_DDD(DUK_DDDPRINT("created object with flags: 0x%08lx", (unsigned long) h->hdr.h_flags)); tv_slot = thr->valstack_top; DUK_TVAL_SET_OBJECT(tv_slot, h); DUK_HOBJECT_INCREF(thr, h); /* no side effects */ thr->valstack_top++; /* object is now reachable */ if (prototype_bidx >= 0) { DUK_HOBJECT_SET_PROTOTYPE_INIT_INCREF(thr, h, thr->builtins[prototype_bidx]); } else { DUK_ASSERT(prototype_bidx == -1); DUK_ASSERT(DUK_HOBJECT_GET_PROTOTYPE(thr->heap, h) == NULL); } return h; } DUK_INTERNAL duk_hobject *duk_push_object_helper_proto(duk_hthread *thr, duk_uint_t hobject_flags_and_class, duk_hobject *proto) { duk_hobject *h; DUK_ASSERT_API_ENTRY(thr); h = duk_push_object_helper(thr, hobject_flags_and_class, -1); DUK_ASSERT(h != NULL); DUK_HOBJECT_SET_PROTOTYPE_INIT_INCREF(thr, h, proto); return h; } DUK_EXTERNAL duk_idx_t duk_push_object(duk_hthread *thr) { DUK_ASSERT_API_ENTRY(thr); (void) duk_push_object_helper(thr, DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_FLAG_FASTREFS | DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJECT), DUK_BIDX_OBJECT_PROTOTYPE); return duk_get_top_index_unsafe(thr); } DUK_EXTERNAL duk_idx_t duk_push_array(duk_hthread *thr) { duk_uint_t flags; duk_harray *obj; duk_idx_t ret; duk_tval *tv_slot; DUK_ASSERT_API_ENTRY(thr); flags = DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_FLAG_FASTREFS | DUK_HOBJECT_FLAG_ARRAY_PART | DUK_HOBJECT_FLAG_EXOTIC_ARRAY | DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_ARRAY); obj = duk_harray_alloc(thr, flags); DUK_ASSERT(obj != NULL); DUK_HOBJECT_SET_PROTOTYPE_INIT_INCREF(thr, (duk_hobject *) obj, thr->builtins[DUK_BIDX_ARRAY_PROTOTYPE]); tv_slot = thr->valstack_top; DUK_TVAL_SET_OBJECT(tv_slot, (duk_hobject *) obj); DUK_HOBJECT_INCREF(thr, obj); /* XXX: could preallocate with refcount = 1 */ ret = (duk_idx_t) (thr->valstack_top - thr->valstack_bottom); thr->valstack_top++; DUK_ASSERT(obj->length == 0); /* Array .length starts at zero. */ return ret; } DUK_EXTERNAL duk_idx_t duk_push_bare_array(duk_hthread *thr) { duk_uint_t flags; duk_harray *obj; duk_idx_t ret; duk_tval *tv_slot; DUK_ASSERT_API_ENTRY(thr); flags = DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_FLAG_FASTREFS | DUK_HOBJECT_FLAG_ARRAY_PART | DUK_HOBJECT_FLAG_EXOTIC_ARRAY | DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_ARRAY); obj = duk_harray_alloc(thr, flags); DUK_ASSERT(obj != NULL); tv_slot = thr->valstack_top; DUK_TVAL_SET_OBJECT(tv_slot, (duk_hobject *) obj); DUK_HOBJECT_INCREF(thr, obj); /* XXX: could preallocate with refcount = 1 */ ret = (duk_idx_t) (thr->valstack_top - thr->valstack_bottom); thr->valstack_top++; DUK_ASSERT(obj->length == 0); /* Array .length starts at zero. */ return ret; } DUK_INTERNAL duk_harray *duk_push_harray(duk_hthread *thr) { /* XXX: API call could do this directly, cast to void in API macro. */ duk_harray *a; DUK_ASSERT_API_ENTRY(thr); (void) duk_push_array(thr); DUK_ASSERT(DUK_TVAL_IS_OBJECT(thr->valstack_top - 1)); a = (duk_harray *) DUK_TVAL_GET_OBJECT(thr->valstack_top - 1); DUK_ASSERT(a != NULL); return a; } /* Push a duk_harray with preallocated size (.length also set to match size). * Caller may then populate array part of the duk_harray directly. */ DUK_INTERNAL duk_harray *duk_push_harray_with_size(duk_hthread *thr, duk_uint32_t size) { duk_harray *a; DUK_ASSERT_API_ENTRY(thr); a = duk_push_harray(thr); duk_hobject_realloc_props(thr, (duk_hobject *) a, 0, size, 0, 0); a->length = size; return a; } DUK_INTERNAL duk_tval *duk_push_harray_with_size_outptr(duk_hthread *thr, duk_uint32_t size) { duk_harray *a; DUK_ASSERT_API_ENTRY(thr); a = duk_push_harray_with_size(thr, size); DUK_ASSERT(a != NULL); return DUK_HOBJECT_A_GET_BASE(thr->heap, (duk_hobject *) a); } DUK_EXTERNAL duk_idx_t duk_push_thread_raw(duk_hthread *thr, duk_uint_t flags) { duk_hthread *obj; duk_idx_t ret; duk_tval *tv_slot; DUK_ASSERT_API_ENTRY(thr); DUK__CHECK_SPACE(); obj = duk_hthread_alloc(thr, DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_THREAD)); DUK_ASSERT(obj != NULL); obj->state = DUK_HTHREAD_STATE_INACTIVE; #if defined(DUK_USE_ROM_STRINGS) /* Nothing to initialize, strs[] is in ROM. */ #else #if defined(DUK_USE_HEAPPTR16) obj->strs16 = thr->strs16; #else obj->strs = thr->strs; #endif #endif DUK_DDD(DUK_DDDPRINT("created thread object with flags: 0x%08lx", (unsigned long) obj->obj.hdr.h_flags)); /* make the new thread reachable */ tv_slot = thr->valstack_top; DUK_TVAL_SET_OBJECT(tv_slot, (duk_hobject *) obj); DUK_HTHREAD_INCREF(thr, obj); ret = (duk_idx_t) (thr->valstack_top - thr->valstack_bottom); thr->valstack_top++; /* important to do this *after* pushing, to make the thread reachable for gc */ if (DUK_UNLIKELY(!duk_hthread_init_stacks(thr->heap, obj))) { DUK_ERROR_ALLOC_FAILED(thr); DUK_WO_NORETURN(return 0;); } /* initialize built-ins - either by copying or creating new ones */ if (flags & DUK_THREAD_NEW_GLOBAL_ENV) { duk_hthread_create_builtin_objects(obj); } else { duk_hthread_copy_builtin_objects(thr, obj); } /* default prototype */ DUK_HOBJECT_SET_PROTOTYPE_INIT_INCREF(thr, (duk_hobject *) obj, obj->builtins[DUK_BIDX_THREAD_PROTOTYPE]); /* Initial stack size satisfies the stack slack constraints so there * is no need to require stack here. */ DUK_ASSERT(DUK_VALSTACK_INITIAL_SIZE >= DUK_VALSTACK_API_ENTRY_MINIMUM + DUK_VALSTACK_INTERNAL_EXTRA); return ret; } DUK_INTERNAL duk_hcompfunc *duk_push_hcompfunc(duk_hthread *thr) { duk_hcompfunc *obj; duk_tval *tv_slot; DUK_ASSERT_API_ENTRY(thr); DUK__CHECK_SPACE(); /* Template functions are not strictly constructable (they don't * have a "prototype" property for instance), so leave the * DUK_HOBJECT_FLAG_CONSRUCTABLE flag cleared here. */ obj = duk_hcompfunc_alloc(thr, DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_FLAG_CALLABLE | DUK_HOBJECT_FLAG_COMPFUNC | DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_FUNCTION)); if (DUK_UNLIKELY(obj == NULL)) { DUK_ERROR_ALLOC_FAILED(thr); DUK_WO_NORETURN(return NULL;); } DUK_DDD(DUK_DDDPRINT("created compiled function object with flags: 0x%08lx", (unsigned long) obj->obj.hdr.h_flags)); tv_slot = thr->valstack_top; DUK_TVAL_SET_OBJECT(tv_slot, (duk_hobject *) obj); DUK_HOBJECT_INCREF(thr, obj); thr->valstack_top++; /* default prototype */ DUK_ASSERT(DUK_HOBJECT_GET_PROTOTYPE(thr->heap, (duk_hobject *) obj) == NULL); DUK_HOBJECT_SET_PROTOTYPE_INIT_INCREF(thr, (duk_hobject *) obj, thr->builtins[DUK_BIDX_FUNCTION_PROTOTYPE]); return obj; } DUK_INTERNAL duk_hboundfunc *duk_push_hboundfunc(duk_hthread *thr) { duk_hboundfunc *obj; duk_tval *tv_slot; DUK_ASSERT_API_ENTRY(thr); DUK__CHECK_SPACE(); obj = duk_hboundfunc_alloc(thr->heap, DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_FLAG_BOUNDFUNC | DUK_HOBJECT_FLAG_CONSTRUCTABLE | DUK_HOBJECT_FLAG_CALLABLE | DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_FUNCTION)); if (!obj) { DUK_ERROR_ALLOC_FAILED(thr); DUK_WO_NORETURN(return NULL;); } tv_slot = thr->valstack_top++; DUK_TVAL_SET_OBJECT(tv_slot, (duk_hobject *) obj); DUK_HOBJECT_INCREF(thr, obj); /* Prototype is left as NULL because the caller always sets it (and * it depends on the target function). */ DUK_ASSERT(DUK_HOBJECT_GET_PROTOTYPE(thr->heap, (duk_hobject *) obj) == NULL); return obj; } DUK_LOCAL duk_idx_t duk__push_c_function_raw(duk_hthread *thr, duk_c_function func, duk_idx_t nargs, duk_uint_t flags, duk_small_uint_t proto_bidx) { duk_hnatfunc *obj; duk_idx_t ret; duk_tval *tv_slot; duk_int16_t func_nargs; DUK_CTX_ASSERT_VALID(thr); DUK__CHECK_SPACE(); if (DUK_UNLIKELY(func == NULL)) { goto api_error; } if (nargs >= 0 && nargs < DUK_HNATFUNC_NARGS_MAX) { func_nargs = (duk_int16_t) nargs; } else if (nargs == DUK_VARARGS) { func_nargs = DUK_HNATFUNC_NARGS_VARARGS; } else { goto api_error; } obj = duk_hnatfunc_alloc(thr, flags); DUK_ASSERT(obj != NULL); obj->func = func; obj->nargs = func_nargs; DUK_DDD(DUK_DDDPRINT("created native function object with flags: 0x%08lx, nargs=%ld", (unsigned long) obj->obj.hdr.h_flags, (long) obj->nargs)); tv_slot = thr->valstack_top; DUK_TVAL_SET_OBJECT(tv_slot, (duk_hobject *) obj); DUK_HOBJECT_INCREF(thr, obj); ret = (duk_idx_t) (thr->valstack_top - thr->valstack_bottom); thr->valstack_top++; DUK_ASSERT_BIDX_VALID(proto_bidx); DUK_HOBJECT_SET_PROTOTYPE_INIT_INCREF(thr, (duk_hobject *) obj, thr->builtins[proto_bidx]); return ret; api_error: DUK_ERROR_TYPE_INVALID_ARGS(thr); DUK_WO_NORETURN(return 0;); } DUK_EXTERNAL duk_idx_t duk_push_c_function(duk_hthread *thr, duk_c_function func, duk_int_t nargs) { duk_uint_t flags; DUK_ASSERT_API_ENTRY(thr); flags = DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_FLAG_CONSTRUCTABLE | DUK_HOBJECT_FLAG_CALLABLE | DUK_HOBJECT_FLAG_FASTREFS | DUK_HOBJECT_FLAG_NATFUNC | DUK_HOBJECT_FLAG_NEWENV | DUK_HOBJECT_FLAG_STRICT | DUK_HOBJECT_FLAG_NOTAIL | DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_FUNCTION); /* Default prototype is a Duktape specific %NativeFunctionPrototype% * which provides .length and .name getters. */ return duk__push_c_function_raw(thr, func, nargs, flags, DUK_BIDX_NATIVE_FUNCTION_PROTOTYPE); } DUK_INTERNAL void duk_push_c_function_builtin(duk_hthread *thr, duk_c_function func, duk_int_t nargs) { duk_uint_t flags; DUK_ASSERT_API_ENTRY(thr); flags = DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_FLAG_CONSTRUCTABLE | DUK_HOBJECT_FLAG_CALLABLE | DUK_HOBJECT_FLAG_FASTREFS | DUK_HOBJECT_FLAG_NATFUNC | DUK_HOBJECT_FLAG_NEWENV | DUK_HOBJECT_FLAG_STRICT | DUK_HOBJECT_FLAG_NOTAIL | DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_FUNCTION); /* Must use Function.prototype for standard built-in functions. */ (void) duk__push_c_function_raw(thr, func, nargs, flags, DUK_BIDX_FUNCTION_PROTOTYPE); } DUK_INTERNAL void duk_push_c_function_builtin_noconstruct(duk_hthread *thr, duk_c_function func, duk_int_t nargs) { duk_uint_t flags; DUK_ASSERT_API_ENTRY(thr); flags = DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_FLAG_CALLABLE | DUK_HOBJECT_FLAG_FASTREFS | DUK_HOBJECT_FLAG_NATFUNC | DUK_HOBJECT_FLAG_NEWENV | DUK_HOBJECT_FLAG_STRICT | DUK_HOBJECT_FLAG_NOTAIL | DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_FUNCTION); /* Must use Function.prototype for standard built-in functions. */ (void) duk__push_c_function_raw(thr, func, nargs, flags, DUK_BIDX_FUNCTION_PROTOTYPE); } DUK_EXTERNAL duk_idx_t duk_push_c_lightfunc(duk_hthread *thr, duk_c_function func, duk_idx_t nargs, duk_idx_t length, duk_int_t magic) { duk_small_uint_t lf_flags; duk_tval *tv_slot; DUK_ASSERT_API_ENTRY(thr); DUK__CHECK_SPACE(); if (nargs >= DUK_LFUNC_NARGS_MIN && nargs <= DUK_LFUNC_NARGS_MAX) { /* as is */ } else if (nargs == DUK_VARARGS) { nargs = DUK_LFUNC_NARGS_VARARGS; } else { goto api_error; } if (DUK_UNLIKELY(!(length >= DUK_LFUNC_LENGTH_MIN && length <= DUK_LFUNC_LENGTH_MAX))) { goto api_error; } if (DUK_UNLIKELY(!(magic >= DUK_LFUNC_MAGIC_MIN && magic <= DUK_LFUNC_MAGIC_MAX))) { goto api_error; } lf_flags = DUK_LFUNC_FLAGS_PACK((duk_small_int_t) magic, (duk_small_uint_t) length, (duk_small_uint_t) nargs); tv_slot = thr->valstack_top++; DUK_ASSERT(DUK_TVAL_IS_UNDEFINED(tv_slot)); DUK_TVAL_SET_LIGHTFUNC(tv_slot, func, lf_flags); DUK_ASSERT(tv_slot >= thr->valstack_bottom); return (duk_idx_t) (tv_slot - thr->valstack_bottom); api_error: DUK_ERROR_TYPE_INVALID_ARGS(thr); DUK_WO_NORETURN(return 0;); } #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) DUK_INTERNAL duk_hbufobj *duk_push_bufobj_raw(duk_hthread *thr, duk_uint_t hobject_flags_and_class, duk_small_int_t prototype_bidx) { duk_hbufobj *obj; duk_tval *tv_slot; DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(prototype_bidx >= 0); DUK__CHECK_SPACE(); obj = duk_hbufobj_alloc(thr, hobject_flags_and_class); DUK_ASSERT(obj != NULL); DUK_HOBJECT_SET_PROTOTYPE_INIT_INCREF(thr, (duk_hobject *) obj, thr->builtins[prototype_bidx]); DUK_HBUFOBJ_ASSERT_VALID(obj); tv_slot = thr->valstack_top; DUK_TVAL_SET_OBJECT(tv_slot, (duk_hobject *) obj); DUK_HOBJECT_INCREF(thr, obj); thr->valstack_top++; return obj; } #endif /* DUK_USE_BUFFEROBJECT_SUPPORT */ /* XXX: There's quite a bit of overlap with buffer creation handling in * duk_bi_buffer.c. Look for overlap and refactor. */ #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) #define DUK__PACK_ARGS(classnum, protobidx, elemtype, elemshift, istypedarray) \ (((classnum) << 24) | ((protobidx) << 16) | ((elemtype) << 8) | ((elemshift) << 4) | (istypedarray)) static const duk_uint32_t duk__bufobj_flags_lookup[] = { /* Node.js Buffers are Uint8Array instances which inherit from Buffer.prototype. */ DUK__PACK_ARGS(DUK_HOBJECT_CLASS_ARRAYBUFFER, DUK_BIDX_ARRAYBUFFER_PROTOTYPE, DUK_HBUFOBJ_ELEM_UINT8, 0, 0), /* DUK_BUFOBJ_ARRAYBUFFER */ DUK__PACK_ARGS(DUK_HOBJECT_CLASS_UINT8ARRAY, DUK_BIDX_NODEJS_BUFFER_PROTOTYPE, DUK_HBUFOBJ_ELEM_UINT8, 0, 1), /* DUK_BUFOBJ_NODEJS_BUFFER */ DUK__PACK_ARGS(DUK_HOBJECT_CLASS_DATAVIEW, DUK_BIDX_DATAVIEW_PROTOTYPE, DUK_HBUFOBJ_ELEM_UINT8, 0, 0), /* DUK_BUFOBJ_DATAVIEW */ DUK__PACK_ARGS(DUK_HOBJECT_CLASS_INT8ARRAY, DUK_BIDX_INT8ARRAY_PROTOTYPE, DUK_HBUFOBJ_ELEM_INT8, 0, 1), /* DUK_BUFOBJ_INT8ARRAY */ DUK__PACK_ARGS(DUK_HOBJECT_CLASS_UINT8ARRAY, DUK_BIDX_UINT8ARRAY_PROTOTYPE, DUK_HBUFOBJ_ELEM_UINT8, 0, 1), /* DUK_BUFOBJ_UINT8ARRAY */ DUK__PACK_ARGS(DUK_HOBJECT_CLASS_UINT8CLAMPEDARRAY, DUK_BIDX_UINT8CLAMPEDARRAY_PROTOTYPE, DUK_HBUFOBJ_ELEM_UINT8CLAMPED, 0, 1), /* DUK_BUFOBJ_UINT8CLAMPEDARRAY */ DUK__PACK_ARGS(DUK_HOBJECT_CLASS_INT16ARRAY, DUK_BIDX_INT16ARRAY_PROTOTYPE, DUK_HBUFOBJ_ELEM_INT16, 1, 1), /* DUK_BUFOBJ_INT16ARRAY */ DUK__PACK_ARGS(DUK_HOBJECT_CLASS_UINT16ARRAY, DUK_BIDX_UINT16ARRAY_PROTOTYPE, DUK_HBUFOBJ_ELEM_UINT16, 1, 1), /* DUK_BUFOBJ_UINT16ARRAY */ DUK__PACK_ARGS(DUK_HOBJECT_CLASS_INT32ARRAY, DUK_BIDX_INT32ARRAY_PROTOTYPE, DUK_HBUFOBJ_ELEM_INT32, 2, 1), /* DUK_BUFOBJ_INT32ARRAY */ DUK__PACK_ARGS(DUK_HOBJECT_CLASS_UINT32ARRAY, DUK_BIDX_UINT32ARRAY_PROTOTYPE, DUK_HBUFOBJ_ELEM_UINT32, 2, 1), /* DUK_BUFOBJ_UINT32ARRAY */ DUK__PACK_ARGS(DUK_HOBJECT_CLASS_FLOAT32ARRAY, DUK_BIDX_FLOAT32ARRAY_PROTOTYPE, DUK_HBUFOBJ_ELEM_FLOAT32, 2, 1), /* DUK_BUFOBJ_FLOAT32ARRAY */ DUK__PACK_ARGS(DUK_HOBJECT_CLASS_FLOAT64ARRAY, DUK_BIDX_FLOAT64ARRAY_PROTOTYPE, DUK_HBUFOBJ_ELEM_FLOAT64, 3, 1) /* DUK_BUFOBJ_FLOAT64ARRAY */ }; #endif /* DUK_USE_BUFFEROBJECT_SUPPORT */ #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) DUK_EXTERNAL void duk_push_buffer_object(duk_hthread *thr, duk_idx_t idx_buffer, duk_size_t byte_offset, duk_size_t byte_length, duk_uint_t flags) { duk_hbufobj *h_bufobj; duk_hbuffer *h_val; duk_hobject *h_arraybuf; duk_uint32_t tmp; duk_uint_t classnum; duk_uint_t protobidx; duk_uint_t lookupidx; duk_uint_t uint_offset, uint_length, uint_added; DUK_ASSERT_API_ENTRY(thr); /* The underlying types for offset/length in duk_hbufobj is * duk_uint_t; make sure argument values fit. */ uint_offset = (duk_uint_t) byte_offset; uint_length = (duk_uint_t) byte_length; if (sizeof(duk_size_t) != sizeof(duk_uint_t)) { if (DUK_UNLIKELY((duk_size_t) uint_offset != byte_offset || (duk_size_t) uint_length != byte_length)) { goto range_error; } } DUK_ASSERT_DISABLE(flags >= 0); /* flags is unsigned */ lookupidx = flags; if (DUK_UNLIKELY(lookupidx >= sizeof(duk__bufobj_flags_lookup) / sizeof(duk_uint32_t))) { goto arg_error; } tmp = duk__bufobj_flags_lookup[lookupidx]; classnum = tmp >> 24; protobidx = (tmp >> 16) & 0xff; h_arraybuf = duk_get_hobject(thr, idx_buffer); if (h_arraybuf != NULL && /* argument is an object */ flags != DUK_BUFOBJ_ARRAYBUFFER && /* creating a view */ DUK_HOBJECT_GET_CLASS_NUMBER(h_arraybuf) == DUK_HOBJECT_CLASS_ARRAYBUFFER /* argument is ArrayBuffer */) { duk_uint_t tmp_offset; DUK_HBUFOBJ_ASSERT_VALID((duk_hbufobj *) h_arraybuf); h_val = ((duk_hbufobj *) h_arraybuf)->buf; if (DUK_UNLIKELY(h_val == NULL)) { goto arg_error; } tmp_offset = uint_offset + ((duk_hbufobj *) h_arraybuf)->offset; if (DUK_UNLIKELY(tmp_offset < uint_offset)) { goto range_error; } uint_offset = tmp_offset; /* Note intentional difference to new TypedArray(): we allow * caller to create an uncovered typed array (which is memory * safe); new TypedArray() rejects it. */ } else { /* Handle unexpected object arguments here too, for nice error * messages. */ h_arraybuf = NULL; h_val = duk_require_hbuffer(thr, idx_buffer); } /* Wrap check for offset+length. */ uint_added = uint_offset + uint_length; if (DUK_UNLIKELY(uint_added < uint_offset)) { goto range_error; } DUK_ASSERT(uint_added >= uint_offset && uint_added >= uint_length); DUK_ASSERT(h_val != NULL); h_bufobj = duk_push_bufobj_raw(thr, DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_FLAG_BUFOBJ | DUK_HOBJECT_CLASS_AS_FLAGS(classnum), (duk_small_int_t) protobidx); DUK_ASSERT(h_bufobj != NULL); h_bufobj->buf = h_val; DUK_HBUFFER_INCREF(thr, h_val); h_bufobj->buf_prop = h_arraybuf; DUK_HOBJECT_INCREF_ALLOWNULL(thr, h_arraybuf); h_bufobj->offset = uint_offset; h_bufobj->length = uint_length; h_bufobj->shift = (tmp >> 4) & 0x0f; h_bufobj->elem_type = (tmp >> 8) & 0xff; h_bufobj->is_typedarray = tmp & 0x0f; DUK_HBUFOBJ_ASSERT_VALID(h_bufobj); /* TypedArray views need an automatic ArrayBuffer which must be * provided as .buffer property of the view. The ArrayBuffer is * referenced via duk_hbufobj->buf_prop and an inherited .buffer * accessor returns it. The ArrayBuffer is created lazily on first * access if necessary so we don't need to do anything more here. */ return; range_error: DUK_ERROR_RANGE(thr, DUK_STR_INVALID_ARGS); DUK_WO_NORETURN(return;); arg_error: DUK_ERROR_TYPE(thr, DUK_STR_INVALID_ARGS); DUK_WO_NORETURN(return;); } #else /* DUK_USE_BUFFEROBJECT_SUPPORT */ DUK_EXTERNAL void duk_push_buffer_object(duk_hthread *thr, duk_idx_t idx_buffer, duk_size_t byte_offset, duk_size_t byte_length, duk_uint_t flags) { DUK_ASSERT_API_ENTRY(thr); DUK_UNREF(idx_buffer); DUK_UNREF(byte_offset); DUK_UNREF(byte_length); DUK_UNREF(flags); DUK_ERROR_UNSUPPORTED(thr); DUK_WO_NORETURN(return;); } #endif /* DUK_USE_BUFFEROBJECT_SUPPORT */ DUK_EXTERNAL duk_idx_t duk_push_error_object_va_raw(duk_hthread *thr, duk_errcode_t err_code, const char *filename, duk_int_t line, const char *fmt, va_list ap) { duk_hobject *proto; #if defined(DUK_USE_AUGMENT_ERROR_CREATE) duk_small_uint_t augment_flags; #endif DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(thr != NULL); DUK_UNREF(filename); DUK_UNREF(line); /* Error code also packs a tracedata related flag. */ #if defined(DUK_USE_AUGMENT_ERROR_CREATE) augment_flags = 0; if (err_code & DUK_ERRCODE_FLAG_NOBLAME_FILELINE) { augment_flags = DUK_AUGMENT_FLAG_NOBLAME_FILELINE; } #endif err_code = err_code & (~DUK_ERRCODE_FLAG_NOBLAME_FILELINE); /* error gets its 'name' from the prototype */ proto = duk_error_prototype_from_code(thr, err_code); (void) duk_push_object_helper_proto(thr, DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_FLAG_FASTREFS | DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_ERROR), proto); /* ... and its 'message' from an instance property */ if (fmt) { duk_push_vsprintf(thr, fmt, ap); duk_xdef_prop_stridx_short(thr, -2, DUK_STRIDX_MESSAGE, DUK_PROPDESC_FLAGS_WC); } else { /* If no explicit message given, put error code into message field * (as a number). This is not fully in keeping with the ECMAScript * error model because messages are supposed to be strings (Error * constructors use ToString() on their argument). However, it's * probably more useful than having a separate 'code' property. */ duk_push_int(thr, err_code); duk_xdef_prop_stridx_short(thr, -2, DUK_STRIDX_MESSAGE, DUK_PROPDESC_FLAGS_WC); } /* XXX: .code = err_code disabled, not sure if useful */ /* Creation time error augmentation */ #if defined(DUK_USE_AUGMENT_ERROR_CREATE) /* filename may be NULL in which case file/line is not recorded */ duk_err_augment_error_create(thr, thr, filename, line, augment_flags); /* may throw an error */ #endif return duk_get_top_index_unsafe(thr); } DUK_EXTERNAL duk_idx_t duk_push_error_object_raw(duk_hthread *thr, duk_errcode_t err_code, const char *filename, duk_int_t line, const char *fmt, ...) { va_list ap; duk_idx_t ret; DUK_ASSERT_API_ENTRY(thr); va_start(ap, fmt); ret = duk_push_error_object_va_raw(thr, err_code, filename, line, fmt, ap); va_end(ap); return ret; } #if !defined(DUK_USE_VARIADIC_MACROS) DUK_EXTERNAL duk_idx_t duk_push_error_object_stash(duk_hthread *thr, duk_errcode_t err_code, const char *fmt, ...) { const char *filename = duk_api_global_filename; duk_int_t line = duk_api_global_line; va_list ap; duk_idx_t ret; DUK_ASSERT_API_ENTRY(thr); duk_api_global_filename = NULL; duk_api_global_line = 0; va_start(ap, fmt); ret = duk_push_error_object_va_raw(thr, err_code, filename, line, fmt, ap); va_end(ap); return ret; } #endif /* DUK_USE_VARIADIC_MACROS */ DUK_EXTERNAL void *duk_push_buffer_raw(duk_hthread *thr, duk_size_t size, duk_small_uint_t flags) { duk_tval *tv_slot; duk_hbuffer *h; void *buf_data; DUK_ASSERT_API_ENTRY(thr); DUK__CHECK_SPACE(); /* Check for maximum buffer length. */ if (DUK_UNLIKELY(size > DUK_HBUFFER_MAX_BYTELEN)) { DUK_ERROR_RANGE(thr, DUK_STR_BUFFER_TOO_LONG); DUK_WO_NORETURN(return NULL;); } h = duk_hbuffer_alloc(thr->heap, size, flags, &buf_data); if (DUK_UNLIKELY(h == NULL)) { DUK_ERROR_ALLOC_FAILED(thr); DUK_WO_NORETURN(return NULL;); } tv_slot = thr->valstack_top; DUK_TVAL_SET_BUFFER(tv_slot, h); DUK_HBUFFER_INCREF(thr, h); thr->valstack_top++; return (void *) buf_data; } DUK_INTERNAL void *duk_push_fixed_buffer_nozero(duk_hthread *thr, duk_size_t len) { DUK_ASSERT_API_ENTRY(thr); return duk_push_buffer_raw(thr, len, DUK_BUF_FLAG_NOZERO); } DUK_INTERNAL void *duk_push_fixed_buffer_zero(duk_hthread *thr, duk_size_t len) { void *ptr; DUK_ASSERT_API_ENTRY(thr); ptr = duk_push_buffer_raw(thr, len, 0); DUK_ASSERT(ptr != NULL); #if !defined(DUK_USE_ZERO_BUFFER_DATA) /* ES2015 requires zeroing even when DUK_USE_ZERO_BUFFER_DATA * is not set. */ duk_memzero((void *) ptr, (size_t) len); #endif return ptr; } #if defined(DUK_USE_ES6_PROXY) DUK_EXTERNAL duk_idx_t duk_push_proxy(duk_hthread *thr, duk_uint_t proxy_flags) { duk_hobject *h_target; duk_hobject *h_handler; duk_hproxy *h_proxy; duk_tval *tv_slot; duk_uint_t flags; DUK_ASSERT_API_ENTRY(thr); DUK_UNREF(proxy_flags); /* DUK__CHECK_SPACE() unnecessary because the Proxy is written to * value stack in-place. */ #if 0 DUK__CHECK_SPACE(); #endif /* Reject a proxy object as the target because it would need * special handling in property lookups. (ES2015 has no such * restriction.) */ h_target = duk_require_hobject_promote_mask(thr, -2, DUK_TYPE_MASK_LIGHTFUNC | DUK_TYPE_MASK_BUFFER); DUK_ASSERT(h_target != NULL); if (DUK_HOBJECT_IS_PROXY(h_target)) { goto fail_args; } /* Reject a proxy object as the handler because it would cause * potentially unbounded recursion. (ES2015 has no such * restriction.) * * There's little practical reason to use a lightfunc or a plain * buffer as the handler table: one could only provide traps via * their prototype objects (Function.prototype and ArrayBuffer.prototype). * Even so, as lightfuncs and plain buffers mimic their object * counterparts, they're promoted and accepted here. */ h_handler = duk_require_hobject_promote_mask(thr, -1, DUK_TYPE_MASK_LIGHTFUNC | DUK_TYPE_MASK_BUFFER); DUK_ASSERT(h_handler != NULL); if (DUK_HOBJECT_IS_PROXY(h_handler)) { goto fail_args; } /* XXX: Proxy object currently has no prototype, so ToPrimitive() * coercion fails which is a bit confusing. */ /* CALLABLE and CONSTRUCTABLE flags are copied from the (initial) * target, see ES2015 Sections 9.5.15 and 9.5.13. */ flags = DUK_HEAPHDR_GET_FLAGS((duk_heaphdr *) h_target) & (DUK_HOBJECT_FLAG_CALLABLE | DUK_HOBJECT_FLAG_CONSTRUCTABLE); flags |= DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_FLAG_EXOTIC_PROXYOBJ; if (flags & DUK_HOBJECT_FLAG_CALLABLE) { flags |= DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_FUNCTION) | DUK_HOBJECT_FLAG_SPECIAL_CALL; } else { flags |= DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJECT); } h_proxy = duk_hproxy_alloc(thr, flags); DUK_ASSERT(h_proxy != NULL); DUK_ASSERT(DUK_HOBJECT_GET_PROTOTYPE(thr->heap, (duk_hobject *) h_proxy) == NULL); /* Initialize Proxy target and handler references; avoid INCREF * by stealing the value stack refcounts via direct value stack * manipulation. INCREF is needed for the Proxy itself however. */ DUK_ASSERT(h_target != NULL); h_proxy->target = h_target; DUK_ASSERT(h_handler != NULL); h_proxy->handler = h_handler; DUK_HPROXY_ASSERT_VALID(h_proxy); DUK_ASSERT(duk_get_hobject(thr, -2) == h_target); DUK_ASSERT(duk_get_hobject(thr, -1) == h_handler); tv_slot = thr->valstack_top - 2; DUK_ASSERT(tv_slot >= thr->valstack_bottom); DUK_TVAL_SET_OBJECT(tv_slot, (duk_hobject *) h_proxy); DUK_HOBJECT_INCREF(thr, (duk_hobject *) h_proxy); tv_slot++; DUK_TVAL_SET_UNDEFINED(tv_slot); /* [ ... target handler ] -> [ ... proxy undefined ] */ thr->valstack_top = tv_slot; /* -> [ ... proxy ] */ DUK_DD(DUK_DDPRINT("created Proxy: %!iT", duk_get_tval(thr, -1))); return (duk_idx_t) (thr->valstack_top - thr->valstack_bottom - 1); fail_args: DUK_ERROR_TYPE_INVALID_ARGS(thr); DUK_WO_NORETURN(return 0;); } #else /* DUK_USE_ES6_PROXY */ DUK_EXTERNAL duk_idx_t duk_push_proxy(duk_hthread *thr, duk_uint_t proxy_flags) { DUK_ASSERT_API_ENTRY(thr); DUK_UNREF(proxy_flags); DUK_ERROR_UNSUPPORTED(thr); DUK_WO_NORETURN(return 0;); } #endif /* DUK_USE_ES6_PROXY */ #if defined(DUK_USE_ASSERTIONS) DUK_LOCAL void duk__validate_push_heapptr(duk_hthread *thr, void *ptr) { duk_heaphdr *h; duk_heaphdr *curr; duk_bool_t found = 0; h = (duk_heaphdr *) ptr; if (h == NULL) { /* Allowed. */ return; } DUK_ASSERT(h != NULL); DUK_ASSERT(DUK_HEAPHDR_HTYPE_VALID(h)); /* One particular problem case is where an object has been * queued for finalization but the finalizer hasn't yet been * executed. * * Corner case: we're running in a finalizer for object X, and * user code calls duk_push_heapptr() for X itself. In this * case X will be in finalize_list, and we can detect the case * by seeing that X's FINALIZED flag is set (which is done before * the finalizer starts executing). */ #if defined(DUK_USE_FINALIZER_SUPPORT) for (curr = thr->heap->finalize_list; curr != NULL; curr = DUK_HEAPHDR_GET_NEXT(thr->heap, curr)) { /* FINALIZABLE is set for all objects on finalize_list * except for an object being finalized right now. So * can't assert here. */ #if 0 DUK_ASSERT(DUK_HEAPHDR_HAS_FINALIZABLE(curr)); #endif if (curr == h) { if (DUK_HEAPHDR_HAS_FINALIZED((duk_heaphdr *) h)) { /* Object is currently being finalized. */ DUK_ASSERT(found == 0); /* Would indicate corrupted lists. */ found = 1; } else { /* Not being finalized but on finalize_list, * allowed since Duktape 2.1. */ DUK_ASSERT(found == 0); /* Would indicate corrupted lists. */ found = 1; } } } #endif /* DUK_USE_FINALIZER_SUPPORT */ #if defined(DUK_USE_REFERENCE_COUNTING) /* Because refzero_list is now processed to completion inline with * no side effects, it's always empty here. */ DUK_ASSERT(thr->heap->refzero_list == NULL); #endif /* If not present in finalize_list (or refzero_list), it * must be either in heap_allocated or the string table. */ if (DUK_HEAPHDR_IS_STRING(h)) { duk_uint32_t i; duk_hstring *str; duk_heap *heap = thr->heap; DUK_ASSERT(found == 0); for (i = 0; i < heap->st_size; i++) { #if defined(DUK_USE_STRTAB_PTRCOMP) str = DUK_USE_HEAPPTR_DEC16((heap)->heap_udata, heap->strtable16[i]); #else str = heap->strtable[i]; #endif while (str != NULL) { if (str == (duk_hstring *) h) { DUK_ASSERT(found == 0); /* Would indicate corrupted lists. */ found = 1; break; } str = str->hdr.h_next; } } DUK_ASSERT(found != 0); } else { for (curr = thr->heap->heap_allocated; curr != NULL; curr = DUK_HEAPHDR_GET_NEXT(thr->heap, curr)) { if (curr == h) { DUK_ASSERT(found == 0); /* Would indicate corrupted lists. */ found = 1; } } DUK_ASSERT(found != 0); } } #endif /* DUK_USE_ASSERTIONS */ DUK_EXTERNAL duk_idx_t duk_push_heapptr(duk_hthread *thr, void *ptr) { duk_idx_t ret; duk_tval *tv; DUK_ASSERT_API_ENTRY(thr); /* Reviving an object using a heap pointer is a dangerous API * operation: if the application doesn't guarantee that the * pointer target is always reachable, difficult-to-diagnose * problems may ensue. Try to validate the 'ptr' argument to * the extent possible. */ #if defined(DUK_USE_ASSERTIONS) duk__validate_push_heapptr(thr, ptr); #endif DUK__CHECK_SPACE(); ret = (duk_idx_t) (thr->valstack_top - thr->valstack_bottom); tv = thr->valstack_top++; if (ptr == NULL) { DUK_ASSERT(DUK_TVAL_IS_UNDEFINED(tv)); return ret; } DUK_HEAPHDR_ASSERT_VALID((duk_heaphdr *) ptr); /* If the argument is on finalize_list it has technically been * unreachable before duk_push_heapptr() but it's still safe to * push it. Starting from Duktape 2.1 allow application code to * do so. There are two main cases: * * (1) The object is on the finalize_list and we're called by * the finalizer for the object being finalized. In this * case do nothing: finalize_list handling will deal with * the object queueing. This is detected by the object not * having a FINALIZABLE flag despite being on the finalize_list; * the flag is cleared for the object being finalized only. * * (2) The object is on the finalize_list but is not currently * being processed. In this case the object can be queued * back to heap_allocated with a few flags cleared, in effect * cancelling the finalizer. */ if (DUK_UNLIKELY(DUK_HEAPHDR_HAS_FINALIZABLE((duk_heaphdr *) ptr))) { duk_heaphdr *curr; DUK_D(DUK_DPRINT("duk_push_heapptr() with a pointer on finalize_list, autorescue")); curr = (duk_heaphdr *) ptr; DUK_HEAPHDR_CLEAR_FINALIZABLE(curr); /* Because FINALIZED is set prior to finalizer call, it will * be set for the object being currently finalized, but not * for other objects on finalize_list. */ DUK_HEAPHDR_CLEAR_FINALIZED(curr); /* Dequeue object from finalize_list and queue it back to * heap_allocated. */ #if defined(DUK_USE_REFERENCE_COUNTING) DUK_ASSERT(DUK_HEAPHDR_GET_REFCOUNT(curr) >= 1); /* Preincremented on finalize_list insert. */ DUK_HEAPHDR_PREDEC_REFCOUNT(curr); #endif DUK_HEAP_REMOVE_FROM_FINALIZE_LIST(thr->heap, curr); DUK_HEAP_INSERT_INTO_HEAP_ALLOCATED(thr->heap, curr); /* Continue with the rest. */ } switch (DUK_HEAPHDR_GET_TYPE((duk_heaphdr *) ptr)) { case DUK_HTYPE_STRING: DUK_TVAL_SET_STRING(tv, (duk_hstring *) ptr); break; case DUK_HTYPE_OBJECT: DUK_TVAL_SET_OBJECT(tv, (duk_hobject *) ptr); break; default: DUK_ASSERT(DUK_HEAPHDR_GET_TYPE((duk_heaphdr *) ptr) == DUK_HTYPE_BUFFER); DUK_TVAL_SET_BUFFER(tv, (duk_hbuffer *) ptr); break; } DUK_HEAPHDR_INCREF(thr, (duk_heaphdr *) ptr); return ret; } /* Push object with no prototype, i.e. a "bare" object. */ DUK_EXTERNAL duk_idx_t duk_push_bare_object(duk_hthread *thr) { DUK_ASSERT_API_ENTRY(thr); (void) duk_push_object_helper(thr, DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_FLAG_FASTREFS | DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJECT), -1); /* no prototype */ return duk_get_top_index_unsafe(thr); } DUK_INTERNAL void duk_push_hstring(duk_hthread *thr, duk_hstring *h) { duk_tval tv; DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(h != NULL); DUK_TVAL_SET_STRING(&tv, h); duk_push_tval(thr, &tv); } DUK_INTERNAL void duk_push_hstring_stridx(duk_hthread *thr, duk_small_uint_t stridx) { DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT_STRIDX_VALID(stridx); duk_push_hstring(thr, DUK_HTHREAD_GET_STRING(thr, stridx)); } DUK_INTERNAL void duk_push_hstring_empty(duk_hthread *thr) { DUK_ASSERT_API_ENTRY(thr); duk_push_hstring(thr, DUK_HTHREAD_GET_STRING(thr, DUK_STRIDX_EMPTY_STRING)); } DUK_INTERNAL void duk_push_hobject(duk_hthread *thr, duk_hobject *h) { duk_tval tv; DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(h != NULL); DUK_TVAL_SET_OBJECT(&tv, h); duk_push_tval(thr, &tv); } DUK_INTERNAL void duk_push_hbuffer(duk_hthread *thr, duk_hbuffer *h) { duk_tval tv; DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(h != NULL); DUK_TVAL_SET_BUFFER(&tv, h); duk_push_tval(thr, &tv); } DUK_INTERNAL void duk_push_hobject_bidx(duk_hthread *thr, duk_small_int_t builtin_idx) { DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(builtin_idx >= 0 && builtin_idx < DUK_NUM_BUILTINS); DUK_ASSERT(thr->builtins[builtin_idx] != NULL); duk_push_hobject(thr, thr->builtins[builtin_idx]); } /* * Poppers */ DUK_LOCAL DUK_ALWAYS_INLINE void duk__pop_n_unsafe_raw(duk_hthread *thr, duk_idx_t count) { duk_tval *tv; #if defined(DUK_USE_REFERENCE_COUNTING) duk_tval *tv_end; #endif DUK_CTX_ASSERT_VALID(thr); DUK_ASSERT(count >= 0); DUK_ASSERT((duk_size_t) (thr->valstack_top - thr->valstack_bottom) >= (duk_size_t) count); #if defined(DUK_USE_REFERENCE_COUNTING) tv = thr->valstack_top; tv_end = tv - count; while (tv != tv_end) { tv--; DUK_ASSERT(tv >= thr->valstack_bottom); DUK_TVAL_SET_UNDEFINED_UPDREF_NORZ(thr, tv); } thr->valstack_top = tv; DUK_REFZERO_CHECK_FAST(thr); #else tv = thr->valstack_top; while (count > 0) { count--; tv--; DUK_ASSERT(tv >= thr->valstack_bottom); DUK_TVAL_SET_UNDEFINED(tv); } thr->valstack_top = tv; #endif DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom); } DUK_EXTERNAL void duk_pop_n(duk_hthread *thr, duk_idx_t count) { DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom); if (DUK_UNLIKELY((duk_uidx_t) (thr->valstack_top - thr->valstack_bottom) < (duk_uidx_t) count)) { DUK_ERROR_RANGE_INVALID_COUNT(thr); DUK_WO_NORETURN(return;); } DUK_ASSERT(count >= 0); duk__pop_n_unsafe_raw(thr, count); } #if defined(DUK_USE_PREFER_SIZE) DUK_INTERNAL void duk_pop_n_unsafe(duk_hthread *thr, duk_idx_t count) { DUK_ASSERT_API_ENTRY(thr); duk_pop_n(thr, count); } #else /* DUK_USE_PREFER_SIZE */ DUK_INTERNAL void duk_pop_n_unsafe(duk_hthread *thr, duk_idx_t count) { DUK_ASSERT_API_ENTRY(thr); duk__pop_n_unsafe_raw(thr, count); } #endif /* DUK_USE_PREFER_SIZE */ /* Pop N elements without DECREF (in effect "stealing" any actual refcounts). */ #if defined(DUK_USE_REFERENCE_COUNTING) DUK_INTERNAL void duk_pop_n_nodecref_unsafe(duk_hthread *thr, duk_idx_t count) { duk_tval *tv; DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(count >= 0); DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom); DUK_ASSERT((duk_size_t) (thr->valstack_top - thr->valstack_bottom) >= (duk_size_t) count); tv = thr->valstack_top; while (count > 0) { count--; tv--; DUK_ASSERT(tv >= thr->valstack_bottom); DUK_TVAL_SET_UNDEFINED(tv); } thr->valstack_top = tv; DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom); } #else /* DUK_USE_REFERENCE_COUNTING */ DUK_INTERNAL void duk_pop_n_nodecref_unsafe(duk_hthread *thr, duk_idx_t count) { DUK_ASSERT_API_ENTRY(thr); duk_pop_n_unsafe(thr, count); } #endif /* DUK_USE_REFERENCE_COUNTING */ /* Popping one element is called so often that when footprint is not an issue, * compile a specialized function for it. */ #if defined(DUK_USE_PREFER_SIZE) DUK_EXTERNAL void duk_pop(duk_hthread *thr) { DUK_ASSERT_API_ENTRY(thr); duk_pop_n(thr, 1); } DUK_INTERNAL void duk_pop_unsafe(duk_hthread *thr) { DUK_ASSERT_API_ENTRY(thr); duk_pop_n_unsafe(thr, 1); } DUK_INTERNAL void duk_pop_nodecref_unsafe(duk_hthread *thr) { DUK_ASSERT_API_ENTRY(thr); duk_pop_n_nodecref_unsafe(thr, 1); } #else /* DUK_USE_PREFER_SIZE */ DUK_LOCAL DUK_ALWAYS_INLINE void duk__pop_unsafe_raw(duk_hthread *thr) { duk_tval *tv; DUK_CTX_ASSERT_VALID(thr); DUK_ASSERT(thr->valstack_top != thr->valstack_bottom); DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom); DUK_ASSERT((duk_size_t) (thr->valstack_top - thr->valstack_bottom) >= (duk_size_t) 1); tv = --thr->valstack_top; DUK_ASSERT(tv >= thr->valstack_bottom); #if defined(DUK_USE_REFERENCE_COUNTING) DUK_TVAL_SET_UNDEFINED_UPDREF(thr, tv); /* side effects */ #else DUK_TVAL_SET_UNDEFINED(tv); #endif DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom); } DUK_EXTERNAL void duk_pop(duk_hthread *thr) { DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom); if (DUK_UNLIKELY(thr->valstack_top == thr->valstack_bottom)) { DUK_ERROR_RANGE_INVALID_COUNT(thr); DUK_WO_NORETURN(return;); } duk__pop_unsafe_raw(thr); } DUK_INTERNAL void duk_pop_unsafe(duk_hthread *thr) { DUK_ASSERT_API_ENTRY(thr); duk__pop_unsafe_raw(thr); } DUK_INTERNAL void duk_pop_nodecref_unsafe(duk_hthread *thr) { duk_tval *tv; DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(thr->valstack_top != thr->valstack_bottom); DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom); DUK_ASSERT((duk_size_t) (thr->valstack_top - thr->valstack_bottom) >= (duk_size_t) 1); tv = --thr->valstack_top; DUK_ASSERT(tv >= thr->valstack_bottom); DUK_TVAL_SET_UNDEFINED(tv); DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom); } #endif /* !DUK_USE_PREFER_SIZE */ #if defined(DUK_USE_PREFER_SIZE) DUK_INTERNAL void duk_pop_undefined(duk_hthread *thr) { DUK_ASSERT_API_ENTRY(thr); duk_pop_nodecref_unsafe(thr); } #else /* DUK_USE_PREFER_SIZE */ DUK_INTERNAL void duk_pop_undefined(duk_hthread *thr) { DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(thr->valstack_top != thr->valstack_bottom); DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom); DUK_ASSERT((duk_size_t) (thr->valstack_top - thr->valstack_bottom) >= (duk_size_t) 1); DUK_ASSERT(DUK_TVAL_IS_UNDEFINED(thr->valstack_top - 1)); thr->valstack_top--; DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom); } #endif /* !DUK_USE_PREFER_SIZE */ #if defined(DUK_USE_PREFER_SIZE) DUK_EXTERNAL void duk_pop_2(duk_hthread *thr) { DUK_ASSERT_API_ENTRY(thr); duk_pop_n(thr, 2); } DUK_INTERNAL void duk_pop_2_unsafe(duk_hthread *thr) { DUK_ASSERT_API_ENTRY(thr); duk_pop_n_unsafe(thr, 2); } DUK_INTERNAL void duk_pop_2_nodecref_unsafe(duk_hthread *thr) { DUK_ASSERT_API_ENTRY(thr); duk_pop_n_nodecref_unsafe(thr, 2); } #else DUK_LOCAL DUK_ALWAYS_INLINE void duk__pop_2_unsafe_raw(duk_hthread *thr) { duk_tval *tv; DUK_CTX_ASSERT_VALID(thr); DUK_ASSERT(thr->valstack_top != thr->valstack_bottom); DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom); DUK_ASSERT((duk_size_t) (thr->valstack_top - thr->valstack_bottom) >= (duk_size_t) 2); tv = --thr->valstack_top; DUK_ASSERT(tv >= thr->valstack_bottom); #if defined(DUK_USE_REFERENCE_COUNTING) DUK_TVAL_SET_UNDEFINED_UPDREF(thr, tv); /* side effects */ #else DUK_TVAL_SET_UNDEFINED(tv); #endif tv = --thr->valstack_top; DUK_ASSERT(tv >= thr->valstack_bottom); #if defined(DUK_USE_REFERENCE_COUNTING) DUK_TVAL_SET_UNDEFINED_UPDREF(thr, tv); /* side effects */ #else DUK_TVAL_SET_UNDEFINED(tv); #endif DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom); } DUK_EXTERNAL void duk_pop_2(duk_hthread *thr) { DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom); if (DUK_UNLIKELY(thr->valstack_top - 2 < thr->valstack_bottom)) { DUK_ERROR_RANGE_INVALID_COUNT(thr); DUK_WO_NORETURN(return;); } duk__pop_2_unsafe_raw(thr); } DUK_INTERNAL void duk_pop_2_unsafe(duk_hthread *thr) { DUK_ASSERT_API_ENTRY(thr); duk__pop_2_unsafe_raw(thr); } DUK_INTERNAL void duk_pop_2_nodecref_unsafe(duk_hthread *thr) { DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(thr->valstack_top != thr->valstack_bottom); DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom); DUK_ASSERT((duk_size_t) (thr->valstack_top - thr->valstack_bottom) >= (duk_size_t) 2); DUK_ASSERT(DUK_TVAL_IS_UNDEFINED(thr->valstack_top - 1)); DUK_ASSERT(DUK_TVAL_IS_UNDEFINED(thr->valstack_top - 2)); thr->valstack_top -= 2; DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom); } #endif /* !DUK_USE_PREFER_SIZE */ DUK_EXTERNAL void duk_pop_3(duk_hthread *thr) { DUK_ASSERT_API_ENTRY(thr); duk_pop_n(thr, 3); } DUK_INTERNAL void duk_pop_3_unsafe(duk_hthread *thr) { DUK_ASSERT_API_ENTRY(thr); duk_pop_n_unsafe(thr, 3); } DUK_INTERNAL void duk_pop_3_nodecref_unsafe(duk_hthread *thr) { DUK_ASSERT_API_ENTRY(thr); duk_pop_n_nodecref_unsafe(thr, 3); } /* * Pack and unpack (pack value stack entries into an array and vice versa) */ /* XXX: pack index range? array index offset? */ /* XXX: need ability to pack into a bare array? */ DUK_INTERNAL void duk_pack(duk_hthread *thr, duk_idx_t count) { duk_tval *tv_src; duk_tval *tv_dst; duk_tval *tv_curr; duk_tval *tv_limit; duk_idx_t top; DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom); top = (duk_idx_t) (thr->valstack_top - thr->valstack_bottom); DUK_ASSERT(top >= 0); if (DUK_UNLIKELY((duk_uidx_t) count > (duk_uidx_t) top)) { /* Also handles negative count. */ DUK_ERROR_RANGE_INVALID_COUNT(thr); DUK_WO_NORETURN(return;); } DUK_ASSERT(count >= 0); /* Wrapping is controlled by the check above: value stack top can be * at most DUK_USE_VALSTACK_LIMIT which is low enough so that * multiplying with sizeof(duk_tval) won't wrap. */ DUK_ASSERT(count >= 0 && count <= (duk_idx_t) DUK_USE_VALSTACK_LIMIT); DUK_ASSERT((duk_size_t) count <= DUK_SIZE_MAX / sizeof(duk_tval)); /* no wrapping */ tv_dst = duk_push_harray_with_size_outptr(thr, (duk_uint32_t) count); /* XXX: uninitialized would be OK */ DUK_ASSERT(count == 0 || tv_dst != NULL); DUK_ASSERT(!duk_is_bare_object(thr, -1)); /* Copy value stack values directly to the array part without * any refcount updates: net refcount changes are zero. */ tv_src = thr->valstack_top - count - 1; duk_memcpy_unsafe((void *) tv_dst, (const void *) tv_src, (size_t) count * sizeof(duk_tval)); /* Overwrite result array to final value stack location and wipe * the rest; no refcount operations needed. */ tv_dst = tv_src; /* when count == 0, same as tv_src (OK) */ tv_src = thr->valstack_top - 1; DUK_TVAL_SET_TVAL(tv_dst, tv_src); /* XXX: internal helper to wipe a value stack segment? */ tv_curr = tv_dst + 1; tv_limit = thr->valstack_top; while (tv_curr != tv_limit) { /* Wipe policy: keep as 'undefined'. */ DUK_TVAL_SET_UNDEFINED(tv_curr); tv_curr++; } thr->valstack_top = tv_dst + 1; } DUK_INTERNAL duk_idx_t duk_unpack_array_like(duk_hthread *thr, duk_idx_t idx) { duk_tval *tv; DUK_ASSERT_API_ENTRY(thr); tv = duk_require_tval(thr, idx); if (DUK_LIKELY(DUK_TVAL_IS_OBJECT(tv))) { duk_hobject *h; duk_uint32_t len; duk_uint32_t i; h = DUK_TVAL_GET_OBJECT(tv); DUK_ASSERT(h != NULL); DUK_UNREF(h); #if defined(DUK_USE_ARRAY_FASTPATH) /* close enough */ if (DUK_LIKELY(DUK_HOBJECT_IS_ARRAY(h) && ((duk_harray *) h)->length <= DUK_HOBJECT_GET_ASIZE(h))) { duk_harray *h_arr; duk_tval *tv_src; duk_tval *tv_dst; h_arr = (duk_harray *) h; len = h_arr->length; if (DUK_UNLIKELY(len >= 0x80000000UL)) { goto fail_over_2g; } duk_require_stack(thr, (duk_idx_t) len); /* The potential allocation in duk_require_stack() may * run a finalizer which modifies the argArray so that * e.g. becomes sparse. So, we need to recheck that the * array didn't change size and that there's still a * valid backing array part. * * XXX: alternatively, could prevent finalizers for the * duration. */ if (DUK_UNLIKELY(len != h_arr->length || h_arr->length > DUK_HOBJECT_GET_ASIZE((duk_hobject *) h_arr))) { goto skip_fast; } /* Main fast path: arguments array is almost always * an actual array (though it might also be an arguments * object). */ DUK_DDD(DUK_DDDPRINT("fast path for %ld elements", (long) h_arr->length)); tv_src = DUK_HOBJECT_A_GET_BASE(thr->heap, h); tv_dst = thr->valstack_top; while (len-- > 0) { DUK_ASSERT(tv_dst < thr->valstack_end); if (DUK_UNLIKELY(DUK_TVAL_IS_UNUSED(tv_src))) { /* Gaps are very unlikely. Skip over them, * without an ancestor lookup (technically * not compliant). */ DUK_ASSERT(DUK_TVAL_IS_UNDEFINED(tv_dst)); /* valstack policy */ } else { DUK_TVAL_SET_TVAL(tv_dst, tv_src); DUK_TVAL_INCREF(thr, tv_dst); } tv_src++; tv_dst++; } DUK_ASSERT(tv_dst <= thr->valstack_end); thr->valstack_top = tv_dst; return (duk_idx_t) h_arr->length; } skip_fast: #endif /* DUK_USE_ARRAY_FASTPATH */ /* Slow path: actual lookups. The initial 'length' lookup * decides the output length, regardless of side effects that * may resize or change the argArray while we read the * indices. */ idx = duk_normalize_index(thr, idx); duk_get_prop_stridx(thr, idx, DUK_STRIDX_LENGTH); len = duk_to_uint32(thr, -1); /* ToUint32() coercion required */ if (DUK_UNLIKELY(len >= 0x80000000UL)) { goto fail_over_2g; } duk_pop_unsafe(thr); DUK_DDD(DUK_DDDPRINT("slow path for %ld elements", (long) len)); duk_require_stack(thr, (duk_idx_t) len); for (i = 0; i < len; i++) { duk_get_prop_index(thr, idx, (duk_uarridx_t) i); } return (duk_idx_t) len; } else if (DUK_TVAL_IS_UNDEFINED(tv) || DUK_TVAL_IS_NULL(tv)) { return 0; } DUK_ERROR_TYPE_INVALID_ARGS(thr); DUK_WO_NORETURN(return 0;); fail_over_2g: DUK_ERROR_RANGE_INVALID_LENGTH(thr); DUK_WO_NORETURN(return 0;); } /* * Error throwing */ #if defined(DUK_USE_GCC_PRAGMAS) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wsuggest-attribute=noreturn" #elif defined(DUK_USE_CLANG_PRAGMAS) #pragma clang diagnostic push #endif DUK_EXTERNAL void duk_throw_raw(duk_hthread *thr) { duk_tval *tv_val; DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(thr->valstack_bottom >= thr->valstack); DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom); DUK_ASSERT(thr->valstack_end >= thr->valstack_top); if (DUK_UNLIKELY(thr->valstack_top == thr->valstack_bottom)) { DUK_ERROR_TYPE_INVALID_ARGS(thr); DUK_WO_NORETURN(return;); } /* Errors are augmented when they are created, not when they are * thrown or re-thrown. The current error handler, however, runs * just before an error is thrown. */ /* Sync so that augmentation sees up-to-date activations, NULL * thr->ptr_curr_pc so that it's not used if side effects occur * in augmentation or longjmp handling. */ duk_hthread_sync_and_null_currpc(thr); #if defined(DUK_USE_AUGMENT_ERROR_THROW) DUK_DDD(DUK_DDDPRINT("THROW ERROR (API): %!dT (before throw augment)", (duk_tval *) duk_get_tval(thr, -1))); duk_err_augment_error_throw(thr); #endif DUK_DDD(DUK_DDDPRINT("THROW ERROR (API): %!dT (after throw augment)", (duk_tval *) duk_get_tval(thr, -1))); tv_val = DUK_GET_TVAL_NEGIDX(thr, -1); duk_err_setup_ljstate1(thr, DUK_LJ_TYPE_THROW, tv_val); #if defined(DUK_USE_DEBUGGER_SUPPORT) duk_err_check_debugger_integration(thr); #endif /* thr->heap->lj.jmpbuf_ptr is checked by duk_err_longjmp() so we don't * need to check that here. If the value is NULL, a fatal error occurs * because we can't return. */ duk_err_longjmp(thr); DUK_UNREACHABLE(); } DUK_EXTERNAL void duk_fatal_raw(duk_hthread *thr, const char *err_msg) { DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(thr != NULL); DUK_ASSERT(thr->heap != NULL); DUK_ASSERT(thr->heap->fatal_func != NULL); DUK_D(DUK_DPRINT("fatal error occurred: %s", err_msg ? err_msg : "NULL")); /* fatal_func should be noreturn, but noreturn declarations on function * pointers has a very spotty support apparently so it's not currently * done. */ thr->heap->fatal_func(thr->heap->heap_udata, err_msg); /* If the fatal handler returns, all bets are off. It'd be nice to * print something here but since we don't want to depend on stdio, * there's no way to do so portably. */ DUK_D(DUK_DPRINT("fatal error handler returned, all bets are off!")); for (;;) { /* loop forever, don't return (function marked noreturn) */ } } DUK_EXTERNAL void duk_error_va_raw(duk_hthread *thr, duk_errcode_t err_code, const char *filename, duk_int_t line, const char *fmt, va_list ap) { DUK_ASSERT_API_ENTRY(thr); duk_push_error_object_va_raw(thr, err_code, filename, line, fmt, ap); (void) duk_throw(thr); DUK_WO_NORETURN(return;); } DUK_EXTERNAL void duk_error_raw(duk_hthread *thr, duk_errcode_t err_code, const char *filename, duk_int_t line, const char *fmt, ...) { va_list ap; DUK_ASSERT_API_ENTRY(thr); va_start(ap, fmt); duk_push_error_object_va_raw(thr, err_code, filename, line, fmt, ap); va_end(ap); (void) duk_throw(thr); DUK_WO_NORETURN(return;); } #if defined(DUK_USE_GCC_PRAGMAS) #pragma GCC diagnostic pop #elif defined(DUK_USE_CLANG_PRAGMAS) #pragma clang diagnostic pop #endif #if !defined(DUK_USE_VARIADIC_MACROS) DUK_NORETURN( DUK_LOCAL_DECL void duk__throw_error_from_stash(duk_hthread *thr, duk_errcode_t err_code, const char *fmt, va_list ap)); DUK_LOCAL void duk__throw_error_from_stash(duk_hthread *thr, duk_errcode_t err_code, const char *fmt, va_list ap) { const char *filename; duk_int_t line; DUK_CTX_ASSERT_VALID(thr); filename = duk_api_global_filename; line = duk_api_global_line; duk_api_global_filename = NULL; duk_api_global_line = 0; duk_push_error_object_va_raw(thr, err_code, filename, line, fmt, ap); (void) duk_throw(thr); DUK_WO_NORETURN(return;); } #define DUK__ERROR_STASH_SHARED(code) \ do { \ va_list ap; \ va_start(ap, fmt); \ duk__throw_error_from_stash(thr, (code), fmt, ap); \ va_end(ap); \ DUK_WO_NORETURN(return 0;); \ } while (0) DUK_EXTERNAL duk_ret_t duk_error_stash(duk_hthread *thr, duk_errcode_t err_code, const char *fmt, ...) { DUK_ASSERT_API_ENTRY(thr); DUK__ERROR_STASH_SHARED(err_code); } DUK_EXTERNAL duk_ret_t duk_generic_error_stash(duk_hthread *thr, const char *fmt, ...) { DUK_ASSERT_API_ENTRY(thr); DUK__ERROR_STASH_SHARED(DUK_ERR_ERROR); } DUK_EXTERNAL duk_ret_t duk_eval_error_stash(duk_hthread *thr, const char *fmt, ...) { DUK_ASSERT_API_ENTRY(thr); DUK__ERROR_STASH_SHARED(DUK_ERR_EVAL_ERROR); } DUK_EXTERNAL duk_ret_t duk_range_error_stash(duk_hthread *thr, const char *fmt, ...) { DUK_ASSERT_API_ENTRY(thr); DUK__ERROR_STASH_SHARED(DUK_ERR_RANGE_ERROR); } DUK_EXTERNAL duk_ret_t duk_reference_error_stash(duk_hthread *thr, const char *fmt, ...) { DUK_ASSERT_API_ENTRY(thr); DUK__ERROR_STASH_SHARED(DUK_ERR_REFERENCE_ERROR); } DUK_EXTERNAL duk_ret_t duk_syntax_error_stash(duk_hthread *thr, const char *fmt, ...) { DUK_ASSERT_API_ENTRY(thr); DUK__ERROR_STASH_SHARED(DUK_ERR_SYNTAX_ERROR); } DUK_EXTERNAL duk_ret_t duk_type_error_stash(duk_hthread *thr, const char *fmt, ...) { DUK_ASSERT_API_ENTRY(thr); DUK__ERROR_STASH_SHARED(DUK_ERR_TYPE_ERROR); } DUK_EXTERNAL duk_ret_t duk_uri_error_stash(duk_hthread *thr, const char *fmt, ...) { DUK_ASSERT_API_ENTRY(thr); DUK__ERROR_STASH_SHARED(DUK_ERR_URI_ERROR); } #endif /* DUK_USE_VARIADIC_MACROS */ /* * Comparison */ DUK_EXTERNAL duk_bool_t duk_equals(duk_hthread *thr, duk_idx_t idx1, duk_idx_t idx2) { duk_tval *tv1, *tv2; DUK_ASSERT_API_ENTRY(thr); tv1 = duk_get_tval(thr, idx1); tv2 = duk_get_tval(thr, idx2); if ((tv1 == NULL) || (tv2 == NULL)) { return 0; } /* Coercion may be needed, the helper handles that by pushing the * tagged values to the stack. */ return duk_js_equals(thr, tv1, tv2); } DUK_EXTERNAL duk_bool_t duk_strict_equals(duk_hthread *thr, duk_idx_t idx1, duk_idx_t idx2) { duk_tval *tv1, *tv2; DUK_ASSERT_API_ENTRY(thr); tv1 = duk_get_tval(thr, idx1); tv2 = duk_get_tval(thr, idx2); if ((tv1 == NULL) || (tv2 == NULL)) { return 0; } /* No coercions or other side effects, so safe */ return duk_js_strict_equals(tv1, tv2); } DUK_EXTERNAL duk_bool_t duk_samevalue(duk_hthread *thr, duk_idx_t idx1, duk_idx_t idx2) { duk_tval *tv1, *tv2; DUK_ASSERT_API_ENTRY(thr); tv1 = duk_get_tval(thr, idx1); tv2 = duk_get_tval(thr, idx2); if ((tv1 == NULL) || (tv2 == NULL)) { return 0; } /* No coercions or other side effects, so safe */ return duk_js_samevalue(tv1, tv2); } /* * instanceof */ DUK_EXTERNAL duk_bool_t duk_instanceof(duk_hthread *thr, duk_idx_t idx1, duk_idx_t idx2) { duk_tval *tv1, *tv2; DUK_ASSERT_API_ENTRY(thr); /* Index validation is strict, which differs from duk_equals(). * The strict behavior mimics how instanceof itself works, e.g. * it is a TypeError if rval is not a -callable- object. It would * be somewhat inconsistent if rval would be allowed to be * non-existent without a TypeError. */ tv1 = duk_require_tval(thr, idx1); DUK_ASSERT(tv1 != NULL); tv2 = duk_require_tval(thr, idx2); DUK_ASSERT(tv2 != NULL); return duk_js_instanceof(thr, tv1, tv2); } /* * Lightfunc */ DUK_INTERNAL void duk_push_lightfunc_name_raw(duk_hthread *thr, duk_c_function func, duk_small_uint_t lf_flags) { /* Lightfunc name, includes Duktape/C native function pointer, which * can often be used to locate the function from a symbol table. * The name also includes the 16-bit duk_tval flags field because it * includes the magic value. Because a single native function often * provides different functionality depending on the magic value, it * seems reasonably to include it in the name. * * On the other hand, a complicated name increases string table * pressure in low memory environments (but only when function name * is accessed). */ DUK_ASSERT_API_ENTRY(thr); duk_push_literal(thr, "light_"); duk_push_string_funcptr(thr, (duk_uint8_t *) &func, sizeof(func)); duk_push_sprintf(thr, "_%04x", (unsigned int) lf_flags); duk_concat(thr, 3); } DUK_INTERNAL void duk_push_lightfunc_name(duk_hthread *thr, duk_tval *tv) { duk_c_function func; duk_small_uint_t lf_flags; DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(DUK_TVAL_IS_LIGHTFUNC(tv)); DUK_TVAL_GET_LIGHTFUNC(tv, func, lf_flags); duk_push_lightfunc_name_raw(thr, func, lf_flags); } DUK_INTERNAL void duk_push_lightfunc_tostring(duk_hthread *thr, duk_tval *tv) { duk_c_function func; duk_small_uint_t lf_flags; DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(DUK_TVAL_IS_LIGHTFUNC(tv)); DUK_TVAL_GET_LIGHTFUNC(tv, func, lf_flags); /* read before 'tv' potentially invalidated */ duk_push_literal(thr, "function "); duk_push_lightfunc_name_raw(thr, func, lf_flags); duk_push_literal(thr, "() { [lightfunc code] }"); duk_concat(thr, 3); } /* * Function pointers * * Printing function pointers is non-portable, so we do that by hex printing * bytes from memory. */ DUK_INTERNAL void duk_push_string_funcptr(duk_hthread *thr, duk_uint8_t *ptr, duk_size_t sz) { duk_uint8_t buf[32 * 2]; duk_uint8_t *p, *q; duk_small_uint_t i; duk_small_uint_t t; DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(sz <= 32); /* sanity limit for function pointer size */ p = buf; #if defined(DUK_USE_INTEGER_LE) q = ptr + sz; #else q = ptr; #endif for (i = 0; i < sz; i++) { #if defined(DUK_USE_INTEGER_LE) t = *(--q); #else t = *(q++); #endif *p++ = duk_lc_digits[t >> 4]; *p++ = duk_lc_digits[t & 0x0f]; } duk_push_lstring(thr, (const char *) buf, sz * 2); } /* * Push readable string summarizing duk_tval. The operation is side effect * free and will only throw from internal errors (e.g. out of memory). * This is used by e.g. property access code to summarize a key/base safely, * and is not intended to be fast (but small and safe). */ /* String limits for summary strings. */ #define DUK__READABLE_SUMMARY_MAXCHARS 96 /* maximum supported by helper */ #define DUK__READABLE_STRING_MAXCHARS 32 /* for strings/symbols */ #define DUK__READABLE_ERRMSG_MAXCHARS 96 /* for error messages */ /* String sanitizer which escapes ASCII control characters and a few other * ASCII characters, passes Unicode as is, and replaces invalid UTF-8 with * question marks. No errors are thrown for any input string, except in out * of memory situations. */ DUK_LOCAL void duk__push_hstring_readable_unicode(duk_hthread *thr, duk_hstring *h_input, duk_small_uint_t maxchars) { const duk_uint8_t *p, *p_start, *p_end; duk_uint8_t buf[DUK_UNICODE_MAX_XUTF8_LENGTH * DUK__READABLE_SUMMARY_MAXCHARS + 2 /*quotes*/ + 3 /*periods*/]; duk_uint8_t *q; duk_ucodepoint_t cp; duk_small_uint_t nchars; DUK_CTX_ASSERT_VALID(thr); DUK_ASSERT(h_input != NULL); DUK_ASSERT(maxchars <= DUK__READABLE_SUMMARY_MAXCHARS); p_start = (const duk_uint8_t *) DUK_HSTRING_GET_DATA(h_input); p_end = p_start + DUK_HSTRING_GET_BYTELEN(h_input); p = p_start; q = buf; nchars = 0; *q++ = (duk_uint8_t) DUK_ASC_SINGLEQUOTE; for (;;) { if (p >= p_end) { break; } if (nchars == maxchars) { *q++ = (duk_uint8_t) DUK_ASC_PERIOD; *q++ = (duk_uint8_t) DUK_ASC_PERIOD; *q++ = (duk_uint8_t) DUK_ASC_PERIOD; break; } if (duk_unicode_decode_xutf8(thr, &p, p_start, p_end, &cp)) { if (cp < 0x20 || cp == 0x7f || cp == DUK_ASC_SINGLEQUOTE || cp == DUK_ASC_BACKSLASH) { DUK_ASSERT(DUK_UNICODE_MAX_XUTF8_LENGTH >= 4); /* estimate is valid */ DUK_ASSERT((cp >> 4) <= 0x0f); *q++ = (duk_uint8_t) DUK_ASC_BACKSLASH; *q++ = (duk_uint8_t) DUK_ASC_LC_X; *q++ = (duk_uint8_t) duk_lc_digits[cp >> 4]; *q++ = (duk_uint8_t) duk_lc_digits[cp & 0x0f]; } else { q += duk_unicode_encode_xutf8(cp, q); } } else { p++; /* advance manually */ *q++ = (duk_uint8_t) DUK_ASC_QUESTION; } nchars++; } *q++ = (duk_uint8_t) DUK_ASC_SINGLEQUOTE; duk_push_lstring(thr, (const char *) buf, (duk_size_t) (q - buf)); } DUK_LOCAL const char *duk__push_string_tval_readable(duk_hthread *thr, duk_tval *tv, duk_bool_t error_aware) { DUK_CTX_ASSERT_VALID(thr); /* 'tv' may be NULL */ if (tv == NULL) { duk_push_literal(thr, "none"); } else { switch (DUK_TVAL_GET_TAG(tv)) { case DUK_TAG_STRING: { duk_hstring *h = DUK_TVAL_GET_STRING(tv); if (DUK_HSTRING_HAS_SYMBOL(h)) { /* XXX: string summary produces question marks * so this is not very ideal. */ duk_push_literal(thr, "[Symbol "); duk_push_string(thr, duk__get_symbol_type_string(h)); duk_push_literal(thr, " "); duk__push_hstring_readable_unicode(thr, h, DUK__READABLE_STRING_MAXCHARS); duk_push_literal(thr, "]"); duk_concat(thr, 5); break; } duk__push_hstring_readable_unicode(thr, h, DUK__READABLE_STRING_MAXCHARS); break; } case DUK_TAG_OBJECT: { duk_hobject *h = DUK_TVAL_GET_OBJECT(tv); DUK_ASSERT(h != NULL); if (error_aware && duk_hobject_prototype_chain_contains(thr, h, thr->builtins[DUK_BIDX_ERROR_PROTOTYPE], 1 /*ignore_loop*/)) { /* Get error message in a side effect free way if * possible; if not, summarize as a generic object. * Error message currently gets quoted. */ /* XXX: better internal getprop call; get without side effects * but traverse inheritance chain. */ duk_tval *tv_msg; tv_msg = duk_hobject_find_entry_tval_ptr_stridx(thr->heap, h, DUK_STRIDX_MESSAGE); if (tv_msg != NULL && DUK_TVAL_IS_STRING(tv_msg)) { /* It's critical to avoid recursion so * only summarize a string .message. */ duk__push_hstring_readable_unicode(thr, DUK_TVAL_GET_STRING(tv_msg), DUK__READABLE_ERRMSG_MAXCHARS); break; } } duk_push_class_string_tval(thr, tv, 1 /*avoid_side_effects*/); break; } case DUK_TAG_BUFFER: { /* While plain buffers mimic Uint8Arrays, they summarize differently. * This is useful so that the summarized string accurately reflects the * internal type which may matter for figuring out bugs etc. */ /* XXX: Hex encoded, length limited buffer summary here? */ duk_hbuffer *h = DUK_TVAL_GET_BUFFER(tv); DUK_ASSERT(h != NULL); duk_push_sprintf(thr, "[buffer:%ld]", (long) DUK_HBUFFER_GET_SIZE(h)); break; } case DUK_TAG_POINTER: { /* Surround with parentheses like in JX, ensures NULL pointer * is distinguishable from null value ("(null)" vs "null"). */ duk_push_tval(thr, tv); duk_push_sprintf(thr, "(%s)", duk_to_string(thr, -1)); duk_remove_m2(thr); break; } default: { duk_push_tval(thr, tv); break; } } } return duk_to_string(thr, -1); } DUK_INTERNAL const char *duk_push_string_tval_readable(duk_hthread *thr, duk_tval *tv) { DUK_ASSERT_API_ENTRY(thr); return duk__push_string_tval_readable(thr, tv, 0 /*error_aware*/); } DUK_INTERNAL const char *duk_push_string_readable(duk_hthread *thr, duk_idx_t idx) { DUK_ASSERT_API_ENTRY(thr); return duk_push_string_tval_readable(thr, duk_get_tval(thr, idx)); } DUK_INTERNAL const char *duk_push_string_tval_readable_error(duk_hthread *thr, duk_tval *tv) { DUK_ASSERT_API_ENTRY(thr); return duk__push_string_tval_readable(thr, tv, 1 /*error_aware*/); } DUK_INTERNAL void duk_push_symbol_descriptive_string(duk_hthread *thr, duk_hstring *h) { const duk_uint8_t *p; const duk_uint8_t *p_end; const duk_uint8_t *q; DUK_ASSERT_API_ENTRY(thr); /* .toString() */ duk_push_literal(thr, "Symbol("); p = (const duk_uint8_t *) DUK_HSTRING_GET_DATA(h); p_end = p + DUK_HSTRING_GET_BYTELEN(h); DUK_ASSERT(p[0] == 0xff || (p[0] & 0xc0) == 0x80); p++; for (q = p; q < p_end; q++) { if (*q == 0xffU) { /* Terminate either at end-of-string (but NUL MUST * be accepted without terminating description) or * 0xFF, which is used to mark start of unique trailer * (and cannot occur in CESU-8 / extended UTF-8). */ break; } } duk_push_lstring(thr, (const char *) p, (duk_size_t) (q - p)); duk_push_literal(thr, ")"); duk_concat(thr, 3); } /* * Functions */ #if 0 /* not used yet */ DUK_INTERNAL void duk_push_hnatfunc_name(duk_hthread *thr, duk_hnatfunc *h) { duk_c_function func; DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(h != NULL); DUK_ASSERT(DUK_HOBJECT_IS_NATFUNC((duk_hobject *) h)); duk_push_sprintf(thr, "native_"); func = h->func; duk_push_string_funcptr(thr, (duk_uint8_t *) &func, sizeof(func)); duk_push_sprintf(thr, "_%04x_%04x", (unsigned int) (duk_uint16_t) h->nargs, (unsigned int) (duk_uint16_t) h->magic); duk_concat(thr, 3); } #endif /* * duk_tval slice copy */ DUK_INTERNAL void duk_copy_tvals_incref(duk_hthread *thr, duk_tval *tv_dst, duk_tval *tv_src, duk_size_t count) { duk_tval *tv; DUK_ASSERT_API_ENTRY(thr); DUK_UNREF(thr); DUK_ASSERT(count * sizeof(duk_tval) >= count); /* no wrap */ duk_memcpy_unsafe((void *) tv_dst, (const void *) tv_src, count * sizeof(duk_tval)); tv = tv_dst; while (count-- > 0) { DUK_TVAL_INCREF(thr, tv); tv++; } } /* automatic undefs */ #undef DUK__ASSERT_SPACE #undef DUK__CHECK_SPACE #undef DUK__ERROR_STASH_SHARED #undef DUK__PACK_ARGS #undef DUK__READABLE_ERRMSG_MAXCHARS #undef DUK__READABLE_STRING_MAXCHARS #undef DUK__READABLE_SUMMARY_MAXCHARS #line 1 "duk_api_string.c" /* * String manipulation */ /* #include duk_internal.h -> already included */ DUK_LOCAL void duk__concat_and_join_helper(duk_hthread *thr, duk_idx_t count_in, duk_bool_t is_join) { duk_uint_t count; duk_uint_t i; duk_size_t idx; duk_size_t len; duk_hstring *h; duk_uint8_t *buf; DUK_CTX_ASSERT_VALID(thr); if (DUK_UNLIKELY(count_in <= 0)) { if (count_in < 0) { DUK_ERROR_RANGE_INVALID_COUNT(thr); DUK_WO_NORETURN(return;); } DUK_ASSERT(count_in == 0); duk_push_hstring_empty(thr); return; } count = (duk_uint_t) count_in; if (is_join) { duk_size_t t1, t2, limit; h = duk_to_hstring(thr, -((duk_idx_t) count) - 1); DUK_ASSERT(h != NULL); /* A bit tricky overflow test, see doc/code-issues.rst. */ t1 = (duk_size_t) DUK_HSTRING_GET_BYTELEN(h); t2 = (duk_size_t) (count - 1); limit = (duk_size_t) DUK_HSTRING_MAX_BYTELEN; if (DUK_UNLIKELY(t2 != 0 && t1 > limit / t2)) { /* Combined size of separators already overflows. */ goto error_overflow; } len = (duk_size_t) (t1 * t2); } else { len = (duk_size_t) 0; } for (i = count; i >= 1; i--) { duk_size_t new_len; h = duk_to_hstring(thr, -((duk_idx_t) i)); new_len = len + (duk_size_t) DUK_HSTRING_GET_BYTELEN(h); /* Impose a string maximum length, need to handle overflow * correctly. */ if (new_len < len || /* wrapped */ new_len > (duk_size_t) DUK_HSTRING_MAX_BYTELEN) { goto error_overflow; } len = new_len; } DUK_DDD(DUK_DDDPRINT("join/concat %lu strings, total length %lu bytes", (unsigned long) count, (unsigned long) len)); /* Use stack allocated buffer to ensure reachability in errors * (e.g. intern error). */ buf = (duk_uint8_t *) duk_push_fixed_buffer_nozero(thr, len); DUK_ASSERT(buf != NULL); /* [ ... (sep) str1 str2 ... strN buf ] */ idx = 0; for (i = count; i >= 1; i--) { if (is_join && i != count) { h = duk_require_hstring(thr, -((duk_idx_t) count) - 2); /* extra -1 for buffer */ duk_memcpy(buf + idx, DUK_HSTRING_GET_DATA(h), DUK_HSTRING_GET_BYTELEN(h)); idx += DUK_HSTRING_GET_BYTELEN(h); } h = duk_require_hstring(thr, -((duk_idx_t) i) - 1); /* extra -1 for buffer */ duk_memcpy(buf + idx, DUK_HSTRING_GET_DATA(h), DUK_HSTRING_GET_BYTELEN(h)); idx += DUK_HSTRING_GET_BYTELEN(h); } DUK_ASSERT(idx == len); /* [ ... (sep) str1 str2 ... strN buf ] */ /* Get rid of the strings early to minimize memory use before intern. */ if (is_join) { duk_replace(thr, -((duk_idx_t) count) - 2); /* overwrite sep */ duk_pop_n(thr, (duk_idx_t) count); } else { duk_replace(thr, -((duk_idx_t) count) - 1); /* overwrite str1 */ duk_pop_n(thr, (duk_idx_t) (count - 1)); } /* [ ... buf ] */ (void) duk_buffer_to_string(thr, -1); /* Safe if inputs are safe. */ /* [ ... res ] */ return; error_overflow: DUK_ERROR_RANGE(thr, DUK_STR_RESULT_TOO_LONG); DUK_WO_NORETURN(return;); } DUK_EXTERNAL void duk_concat(duk_hthread *thr, duk_idx_t count) { DUK_ASSERT_API_ENTRY(thr); duk__concat_and_join_helper(thr, count, 0 /*is_join*/); } #if defined(DUK_USE_PREFER_SIZE) DUK_INTERNAL void duk_concat_2(duk_hthread *thr) { DUK_ASSERT_API_ENTRY(thr); duk_concat(thr, 2); } #else /* DUK_USE_PREFER_SIZE */ DUK_INTERNAL void duk_concat_2(duk_hthread *thr) { duk_hstring *h1; duk_hstring *h2; duk_uint8_t *buf; duk_size_t len1; duk_size_t len2; duk_size_t len; DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(duk_get_top(thr) >= 2); /* Trusted caller. */ h1 = duk_to_hstring(thr, -2); h2 = duk_to_hstring(thr, -1); len1 = (duk_size_t) DUK_HSTRING_GET_BYTELEN(h1); len2 = (duk_size_t) DUK_HSTRING_GET_BYTELEN(h2); len = len1 + len2; if (DUK_UNLIKELY(len < len1 || /* wrapped */ len > (duk_size_t) DUK_HSTRING_MAX_BYTELEN)) { goto error_overflow; } buf = (duk_uint8_t *) duk_push_fixed_buffer_nozero(thr, len); DUK_ASSERT(buf != NULL); duk_memcpy((void *) buf, (const void *) DUK_HSTRING_GET_DATA(h1), (size_t) len1); duk_memcpy((void *) (buf + len1), (const void *) DUK_HSTRING_GET_DATA(h2), (size_t) len2); (void) duk_buffer_to_string(thr, -1); /* Safe if inputs are safe. */ /* [ ... str1 str2 buf ] */ duk_replace(thr, -3); duk_pop_unsafe(thr); return; error_overflow: DUK_ERROR_RANGE(thr, DUK_STR_RESULT_TOO_LONG); DUK_WO_NORETURN(return;); } #endif /* DUK_USE_PREFER_SIZE */ DUK_EXTERNAL void duk_join(duk_hthread *thr, duk_idx_t count) { DUK_ASSERT_API_ENTRY(thr); duk__concat_and_join_helper(thr, count, 1 /*is_join*/); } /* XXX: could map/decode be unified with duk_unicode_support.c code? * Case conversion needs also the character surroundings though. */ DUK_EXTERNAL void duk_decode_string(duk_hthread *thr, duk_idx_t idx, duk_decode_char_function callback, void *udata) { duk_hstring *h_input; const duk_uint8_t *p, *p_start, *p_end; duk_codepoint_t cp; DUK_ASSERT_API_ENTRY(thr); h_input = duk_require_hstring(thr, idx); /* Accept symbols. */ DUK_ASSERT(h_input != NULL); p_start = (const duk_uint8_t *) DUK_HSTRING_GET_DATA(h_input); p_end = p_start + DUK_HSTRING_GET_BYTELEN(h_input); p = p_start; for (;;) { if (p >= p_end) { break; } cp = (duk_codepoint_t) duk_unicode_decode_xutf8_checked(thr, &p, p_start, p_end); callback(udata, cp); } } DUK_EXTERNAL void duk_map_string(duk_hthread *thr, duk_idx_t idx, duk_map_char_function callback, void *udata) { duk_hstring *h_input; duk_bufwriter_ctx bw_alloc; duk_bufwriter_ctx *bw; const duk_uint8_t *p, *p_start, *p_end; duk_codepoint_t cp; DUK_ASSERT_API_ENTRY(thr); idx = duk_normalize_index(thr, idx); h_input = duk_require_hstring(thr, idx); /* Accept symbols. */ DUK_ASSERT(h_input != NULL); bw = &bw_alloc; DUK_BW_INIT_PUSHBUF(thr, bw, DUK_HSTRING_GET_BYTELEN(h_input)); /* Reasonable output estimate. */ p_start = (const duk_uint8_t *) DUK_HSTRING_GET_DATA(h_input); p_end = p_start + DUK_HSTRING_GET_BYTELEN(h_input); p = p_start; for (;;) { /* XXX: could write output in chunks with fewer ensure calls, * but relative benefit would be small here. */ if (p >= p_end) { break; } cp = (duk_codepoint_t) duk_unicode_decode_xutf8_checked(thr, &p, p_start, p_end); cp = callback(udata, cp); DUK_BW_WRITE_ENSURE_XUTF8(thr, bw, cp); } DUK_BW_COMPACT(thr, bw); (void) duk_buffer_to_string(thr, -1); /* Safe, extended UTF-8 encoded. */ duk_replace(thr, idx); } DUK_EXTERNAL void duk_substring(duk_hthread *thr, duk_idx_t idx, duk_size_t start_offset, duk_size_t end_offset) { duk_hstring *h; duk_hstring *res; duk_size_t start_byte_offset; duk_size_t end_byte_offset; duk_size_t charlen; DUK_ASSERT_API_ENTRY(thr); idx = duk_require_normalize_index(thr, idx); /* Accept symbols. */ h = duk_require_hstring(thr, idx); DUK_ASSERT(h != NULL); charlen = DUK_HSTRING_GET_CHARLEN(h); if (end_offset >= charlen) { end_offset = charlen; } if (start_offset > end_offset) { start_offset = end_offset; } DUK_ASSERT_DISABLE(start_offset >= 0); DUK_ASSERT(start_offset <= end_offset && start_offset <= DUK_HSTRING_GET_CHARLEN(h)); DUK_ASSERT_DISABLE(end_offset >= 0); DUK_ASSERT(end_offset >= start_offset && end_offset <= DUK_HSTRING_GET_CHARLEN(h)); /* Guaranteed by string limits. */ DUK_ASSERT(start_offset <= DUK_UINT32_MAX); DUK_ASSERT(end_offset <= DUK_UINT32_MAX); start_byte_offset = (duk_size_t) duk_heap_strcache_offset_char2byte(thr, h, (duk_uint_fast32_t) start_offset); end_byte_offset = (duk_size_t) duk_heap_strcache_offset_char2byte(thr, h, (duk_uint_fast32_t) end_offset); DUK_ASSERT(end_byte_offset >= start_byte_offset); DUK_ASSERT(end_byte_offset - start_byte_offset <= DUK_UINT32_MAX); /* Guaranteed by string limits. */ /* No size check is necessary. */ res = duk_heap_strtable_intern_checked(thr, DUK_HSTRING_GET_DATA(h) + start_byte_offset, (duk_uint32_t) (end_byte_offset - start_byte_offset)); duk_push_hstring(thr, res); duk_replace(thr, idx); } /* XXX: this is quite clunky. Add Unicode helpers to scan backwards and * forwards with a callback to process codepoints? */ DUK_EXTERNAL void duk_trim(duk_hthread *thr, duk_idx_t idx) { duk_hstring *h; const duk_uint8_t *p, *p_start, *p_end, *p_tmp1, *p_tmp2; /* pointers for scanning */ const duk_uint8_t *q_start, *q_end; /* start (incl) and end (excl) of trimmed part */ duk_codepoint_t cp; DUK_ASSERT_API_ENTRY(thr); idx = duk_require_normalize_index(thr, idx); /* Accept symbols. */ h = duk_require_hstring(thr, idx); DUK_ASSERT(h != NULL); p_start = DUK_HSTRING_GET_DATA(h); p_end = p_start + DUK_HSTRING_GET_BYTELEN(h); p = p_start; while (p < p_end) { p_tmp1 = p; cp = (duk_codepoint_t) duk_unicode_decode_xutf8_checked(thr, &p_tmp1, p_start, p_end); if (!(duk_unicode_is_whitespace(cp) || duk_unicode_is_line_terminator(cp))) { break; } p = p_tmp1; } q_start = p; if (p == p_end) { /* Entire string is whitespace. */ q_end = p; goto scan_done; } p = p_end; while (p > p_start) { p_tmp1 = p; while (p > p_start) { p--; if (((*p) & 0xc0) != 0x80) { break; } } p_tmp2 = p; cp = (duk_codepoint_t) duk_unicode_decode_xutf8_checked(thr, &p_tmp2, p_start, p_end); if (!(duk_unicode_is_whitespace(cp) || duk_unicode_is_line_terminator(cp))) { p = p_tmp1; break; } } q_end = p; scan_done: /* This may happen when forward and backward scanning disagree * (possible for non-extended-UTF-8 strings). */ if (q_end < q_start) { q_end = q_start; } DUK_ASSERT(q_start >= p_start && q_start <= p_end); DUK_ASSERT(q_end >= p_start && q_end <= p_end); DUK_ASSERT(q_end >= q_start); DUK_DDD(DUK_DDDPRINT("trim: p_start=%p, p_end=%p, q_start=%p, q_end=%p", (const void *) p_start, (const void *) p_end, (const void *) q_start, (const void *) q_end)); if (q_start == p_start && q_end == p_end) { DUK_DDD(DUK_DDDPRINT("nothing was trimmed: avoid interning (hashing etc)")); return; } duk_push_lstring(thr, (const char *) q_start, (duk_size_t) (q_end - q_start)); duk_replace(thr, idx); } DUK_EXTERNAL duk_codepoint_t duk_char_code_at(duk_hthread *thr, duk_idx_t idx, duk_size_t char_offset) { duk_hstring *h; duk_ucodepoint_t cp; DUK_ASSERT_API_ENTRY(thr); /* XXX: Share code with String.prototype.charCodeAt? Main difference * is handling of clamped offsets. */ h = duk_require_hstring(thr, idx); /* Accept symbols. */ DUK_ASSERT(h != NULL); DUK_ASSERT_DISABLE(char_offset >= 0); /* Always true, arg is unsigned. */ if (char_offset >= DUK_HSTRING_GET_CHARLEN(h)) { return 0; } DUK_ASSERT(char_offset <= DUK_UINT_MAX); /* Guaranteed by string limits. */ cp = duk_hstring_char_code_at_raw(thr, h, (duk_uint_t) char_offset, 0 /*surrogate_aware*/); return (duk_codepoint_t) cp; } #line 1 "duk_api_time.c" /* * Date/time. */ /* #include duk_internal.h -> already included */ DUK_INTERNAL duk_double_t duk_time_get_ecmascript_time(duk_hthread *thr) { /* ECMAScript time, with millisecond fractions. Exposed via * duk_get_now() for example. */ DUK_UNREF(thr); return (duk_double_t) DUK_USE_DATE_GET_NOW(thr); } DUK_INTERNAL duk_double_t duk_time_get_ecmascript_time_nofrac(duk_hthread *thr) { /* ECMAScript time without millisecond fractions. Exposed via * the Date built-in which doesn't allow fractions. */ DUK_UNREF(thr); return (duk_double_t) DUK_FLOOR(DUK_USE_DATE_GET_NOW(thr)); } DUK_INTERNAL duk_double_t duk_time_get_monotonic_time(duk_hthread *thr) { DUK_UNREF(thr); #if defined(DUK_USE_GET_MONOTONIC_TIME) return (duk_double_t) DUK_USE_GET_MONOTONIC_TIME(thr); #else return (duk_double_t) DUK_USE_DATE_GET_NOW(thr); #endif } DUK_EXTERNAL duk_double_t duk_get_now(duk_hthread *thr) { DUK_ASSERT_API_ENTRY(thr); DUK_UNREF(thr); /* This API intentionally allows millisecond fractions. */ return duk_time_get_ecmascript_time(thr); } #if 0 /* XXX: worth exposing? */ DUK_EXTERNAL duk_double_t duk_get_monotonic_time(duk_hthread *thr) { DUK_ASSERT_API_ENTRY(thr); DUK_UNREF(thr); return duk_time_get_monotonic_time(thr); } #endif DUK_EXTERNAL void duk_time_to_components(duk_hthread *thr, duk_double_t timeval, duk_time_components *comp) { duk_int_t parts[DUK_DATE_IDX_NUM_PARTS]; duk_double_t dparts[DUK_DATE_IDX_NUM_PARTS]; duk_uint_t flags; DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(comp != NULL); /* XXX: or check? */ DUK_UNREF(thr); /* Convert as one-based, but change month to zero-based to match the * ECMAScript Date built-in behavior 1:1. */ flags = DUK_DATE_FLAG_ONEBASED | DUK_DATE_FLAG_NAN_TO_ZERO; duk_bi_date_timeval_to_parts(timeval, parts, dparts, flags); /* XXX: sub-millisecond accuracy for the API */ DUK_ASSERT(dparts[DUK_DATE_IDX_MONTH] >= 1.0 && dparts[DUK_DATE_IDX_MONTH] <= 12.0); comp->year = dparts[DUK_DATE_IDX_YEAR]; comp->month = dparts[DUK_DATE_IDX_MONTH] - 1.0; comp->day = dparts[DUK_DATE_IDX_DAY]; comp->hours = dparts[DUK_DATE_IDX_HOUR]; comp->minutes = dparts[DUK_DATE_IDX_MINUTE]; comp->seconds = dparts[DUK_DATE_IDX_SECOND]; comp->milliseconds = dparts[DUK_DATE_IDX_MILLISECOND]; comp->weekday = dparts[DUK_DATE_IDX_WEEKDAY]; } DUK_EXTERNAL duk_double_t duk_components_to_time(duk_hthread *thr, duk_time_components *comp) { duk_double_t d; duk_double_t dparts[DUK_DATE_IDX_NUM_PARTS]; duk_uint_t flags; DUK_ASSERT_API_ENTRY(thr); DUK_ASSERT(comp != NULL); /* XXX: or check? */ DUK_UNREF(thr); /* Match Date constructor behavior (with UTC time). Month is given * as zero-based. Day-of-month is given as one-based so normalize * it to zero-based as the internal conversion helpers expects all * components to be zero-based. */ flags = 0; /* XXX: expensive conversion; use array format in API instead, or unify * time provider and time API to use same struct? */ dparts[DUK_DATE_IDX_YEAR] = comp->year; dparts[DUK_DATE_IDX_MONTH] = comp->month; dparts[DUK_DATE_IDX_DAY] = comp->day - 1.0; dparts[DUK_DATE_IDX_HOUR] = comp->hours; dparts[DUK_DATE_IDX_MINUTE] = comp->minutes; dparts[DUK_DATE_IDX_SECOND] = comp->seconds; dparts[DUK_DATE_IDX_MILLISECOND] = comp->milliseconds; dparts[DUK_DATE_IDX_WEEKDAY] = 0; /* ignored */ d = duk_bi_date_get_timeval_from_dparts(dparts, flags); return d; } #line 1 "duk_bi_array.c" /* * Array built-ins * * Most Array built-ins are intentionally generic in ECMAScript, and are * intended to work even when the 'this' binding is not an Array instance. * This ECMAScript feature is also used by much real world code. For this * reason the implementations here don't assume exotic Array behavior or * e.g. presence of a .length property. However, some algorithms have a * fast path for duk_harray backed actual Array instances, enabled when * footprint is not a concern. * * XXX: the "Throw" flag should be set for (almost?) all [[Put]] and * [[Delete]] operations, but it's currently false throughout. Go through * all put/delete cases and check throw flag use. Need a new API primitive * which allows throws flag to be specified. * * XXX: array lengths above 2G won't work reliably. There are many places * where one needs a full signed 32-bit range ([-0xffffffff, 0xffffffff], * i.e. -33- bits). Although array 'length' cannot be written to be outside * the unsigned 32-bit range (E5.1 Section 15.4.5.1 throws a RangeError if so) * some intermediate values may be above 0xffffffff and this may not be always * correctly handled now (duk_uint32_t is not enough for all algorithms). * For instance, push() can legitimately write entries beyond length 0xffffffff * and cause a RangeError only at the end. To do this properly, the current * push() implementation tracks the array index using a 'double' instead of a * duk_uint32_t (which is somewhat awkward). See test-bi-array-push-maxlen.js. * * On using "put" vs. "def" prop * ============================= * * Code below must be careful to use the appropriate primitive as it matters * for compliance. When using "put" there may be inherited properties in * Array.prototype which cause side effects when values are written. When * using "define" there are no such side effects, and many test262 test cases * check for this (for real world code, such side effects are very rare). * Both "put" and "define" are used in the E5.1 specification; as a rule, * "put" is used when modifying an existing array (or a non-array 'this' * binding) and "define" for setting values into a fresh result array. */ /* #include duk_internal.h -> already included */ /* Perform an intermediate join when this many elements have been pushed * on the value stack. */ #define DUK__ARRAY_MID_JOIN_LIMIT 4096 #if defined(DUK_USE_ARRAY_BUILTIN) /* * Shared helpers. */ /* Shared entry code for many Array built-ins: the 'this' binding is pushed * on the value stack and object coerced, and the current .length is returned. * Note that length is left on stack (it could be popped, but that's not * usually necessary because call handling will clean it up automatically). */ DUK_LOCAL duk_uint32_t duk__push_this_obj_len_u32(duk_hthread *thr) { duk_uint32_t len; /* XXX: push more directly? */ (void) duk_push_this_coercible_to_object(thr); DUK_HOBJECT_ASSERT_VALID(duk_get_hobject(thr, -1)); duk_get_prop_stridx_short(thr, -1, DUK_STRIDX_LENGTH); len = duk_to_uint32(thr, -1); /* -> [ ... ToObject(this) ToUint32(length) ] */ return len; } DUK_LOCAL duk_uint32_t duk__push_this_obj_len_u32_limited(duk_hthread *thr) { /* Range limited to [0, 0x7fffffff] range, i.e. range that can be * represented with duk_int32_t. Use this when the method doesn't * handle the full 32-bit unsigned range correctly. */ duk_uint32_t ret = duk__push_this_obj_len_u32(thr); if (DUK_UNLIKELY(ret >= 0x80000000UL)) { DUK_ERROR_RANGE_INVALID_LENGTH(thr); DUK_WO_NORETURN(return 0U;); } return ret; } #if defined(DUK_USE_ARRAY_FASTPATH) /* Check if 'this' binding is an Array instance (duk_harray) which satisfies * a few other guarantees for fast path operation. The fast path doesn't * need to handle all operations, even for duk_harrays, but must handle a * significant fraction to improve performance. Return a non-NULL duk_harray * pointer when all fast path criteria are met, NULL otherwise. */ DUK_LOCAL duk_harray *duk__arraypart_fastpath_this(duk_hthread *thr) { duk_tval *tv; duk_hobject *h; duk_uint_t flags_mask, flags_bits, flags_value; DUK_ASSERT(thr->valstack_bottom > thr->valstack); /* because call in progress */ tv = DUK_GET_THIS_TVAL_PTR(thr); /* Fast path requires that 'this' is a duk_harray. Read only arrays * (ROM backed) are also rejected for simplicity. */ if (!DUK_TVAL_IS_OBJECT(tv)) { DUK_DD(DUK_DDPRINT("reject array fast path: not an object")); return NULL; } h = DUK_TVAL_GET_OBJECT(tv); DUK_ASSERT(h != NULL); flags_mask = DUK_HOBJECT_FLAG_ARRAY_PART | DUK_HOBJECT_FLAG_EXOTIC_ARRAY | DUK_HEAPHDR_FLAG_READONLY; flags_bits = DUK_HOBJECT_FLAG_ARRAY_PART | DUK_HOBJECT_FLAG_EXOTIC_ARRAY; flags_value = DUK_HEAPHDR_GET_FLAGS_RAW((duk_heaphdr *) h); if ((flags_value & flags_mask) != flags_bits) { DUK_DD(DUK_DDPRINT("reject array fast path: object flag check failed")); return NULL; } /* In some cases a duk_harray's 'length' may be larger than the * current array part allocation. Avoid the fast path in these * cases, so that all fast path code can safely assume that all * items in the range [0,length[ are backed by the current array * part allocation. */ if (((duk_harray *) h)->length > DUK_HOBJECT_GET_ASIZE(h)) { DUK_DD(DUK_DDPRINT("reject array fast path: length > array part size")); return NULL; } /* Guarantees for fast path. */ DUK_ASSERT(h != NULL); DUK_ASSERT(DUK_HOBJECT_GET_ASIZE(h) == 0 || DUK_HOBJECT_A_GET_BASE(thr->heap, h) != NULL); DUK_ASSERT(((duk_harray *) h)->length <= DUK_HOBJECT_GET_ASIZE(h)); DUK_DD(DUK_DDPRINT("array fast path allowed for: %!O", (duk_heaphdr *) h)); return (duk_harray *) h; } #endif /* DUK_USE_ARRAY_FASTPATH */ /* * Constructor */ DUK_INTERNAL duk_ret_t duk_bi_array_constructor(duk_hthread *thr) { duk_idx_t nargs; duk_harray *a; duk_double_t d; duk_uint32_t len; duk_uint32_t len_prealloc; nargs = duk_get_top(thr); if (nargs == 1 && duk_is_number(thr, 0)) { /* XXX: expensive check (also shared elsewhere - so add a shared internal API call?) */ d = duk_get_number(thr, 0); len = duk_to_uint32(thr, 0); if (!duk_double_equals((duk_double_t) len, d)) { DUK_DCERROR_RANGE_INVALID_LENGTH(thr); } /* For small lengths create a dense preallocated array. * For large arrays preallocate an initial part. */ len_prealloc = len < 64 ? len : 64; a = duk_push_harray_with_size(thr, len_prealloc); DUK_ASSERT(a != NULL); DUK_ASSERT(!duk_is_bare_object(thr, -1)); a->length = len; return 1; } duk_pack(thr, nargs); return 1; } /* * isArray() */ DUK_INTERNAL duk_ret_t duk_bi_array_constructor_is_array(duk_hthread *thr) { DUK_ASSERT_TOP(thr, 1); duk_push_boolean(thr, duk_js_isarray(DUK_GET_TVAL_POSIDX(thr, 0))); return 1; } /* * toString() */ DUK_INTERNAL duk_ret_t duk_bi_array_prototype_to_string(duk_hthread *thr) { (void) duk_push_this_coercible_to_object(thr); duk_get_prop_stridx_short(thr, -1, DUK_STRIDX_JOIN); /* [ ... this func ] */ if (!duk_is_callable(thr, -1)) { /* Fall back to the initial (original) Object.toString(). We don't * currently have pointers to the built-in functions, only the top * level global objects (like "Array") so this is now done in a bit * of a hacky manner. It would be cleaner to push the (original) * function and use duk_call_method(). */ /* XXX: 'this' will be ToObject() coerced twice, which is incorrect * but should have no visible side effects. */ DUK_DDD(DUK_DDDPRINT("this.join is not callable, fall back to (original) Object.toString")); duk_set_top(thr, 0); return duk_bi_object_prototype_to_string(thr); /* has access to 'this' binding */ } /* [ ... this func ] */ duk_insert(thr, -2); /* [ ... func this ] */ DUK_DDD( DUK_DDDPRINT("calling: func=%!iT, this=%!iT", (duk_tval *) duk_get_tval(thr, -2), (duk_tval *) duk_get_tval(thr, -1))); duk_call_method(thr, 0); return 1; } /* * concat() */ DUK_INTERNAL duk_ret_t duk_bi_array_prototype_concat(duk_hthread *thr) { duk_idx_t i, n; duk_uint32_t j, idx, len; duk_hobject *h; duk_size_t tmp_len; /* XXX: In ES2015 Array .length can be up to 2^53-1. The current * implementation is limited to 2^32-1. */ /* XXX: Fast path for array 'this' and array element. */ /* XXX: The insert here is a bit expensive if there are a lot of items. * It could also be special cased in the outermost for loop quite easily * (as the element is dup()'d anyway). */ (void) duk_push_this_coercible_to_object(thr); duk_insert(thr, 0); n = duk_get_top(thr); duk_push_array(thr); /* -> [ ToObject(this) item1 ... itemN arr ] */ /* NOTE: The Array special behaviors are NOT invoked by duk_xdef_prop_index() * (which differs from the official algorithm). If no error is thrown, this * doesn't matter as the length is updated at the end. However, if an error * is thrown, the length will be unset. That shouldn't matter because the * caller won't get a reference to the intermediate value. */ idx = 0; for (i = 0; i < n; i++) { duk_bool_t spreadable; duk_bool_t need_has_check; DUK_ASSERT_TOP(thr, n + 1); /* [ ToObject(this) item1 ... itemN arr ] */ h = duk_get_hobject(thr, i); if (h == NULL) { spreadable = 0; } else { #if defined(DUK_USE_SYMBOL_BUILTIN) duk_get_prop_stridx(thr, i, DUK_STRIDX_WELLKNOWN_SYMBOL_IS_CONCAT_SPREADABLE); if (duk_is_undefined(thr, -1)) { spreadable = duk_js_isarray_hobject(h); } else { spreadable = duk_to_boolean(thr, -1); } duk_pop_nodecref_unsafe(thr); #else spreadable = duk_js_isarray_hobject(h); #endif } if (!spreadable) { duk_dup(thr, i); duk_xdef_prop_index_wec(thr, -2, idx); idx++; if (DUK_UNLIKELY(idx == 0U)) { /* Index after update is 0, and index written * was 0xffffffffUL which is no longer a valid * array index. */ goto fail_wrap; } continue; } DUK_ASSERT(duk_is_object(thr, i)); need_has_check = (DUK_HOBJECT_IS_PROXY(h) != 0); /* Always 0 w/o Proxy support. */ /* [ ToObject(this) item1 ... itemN arr ] */ tmp_len = duk_get_length(thr, i); len = (duk_uint32_t) tmp_len; if (DUK_UNLIKELY(tmp_len != (duk_size_t) len)) { goto fail_wrap; } if (DUK_UNLIKELY(idx + len < idx)) { /* Result length must be at most 0xffffffffUL to be * a valid 32-bit array index. */ goto fail_wrap; } for (j = 0; j < len; j++) { /* For a Proxy element, an explicit 'has' check is * needed to allow the Proxy to present gaps. */ if (need_has_check) { if (duk_has_prop_index(thr, i, j)) { duk_get_prop_index(thr, i, j); duk_xdef_prop_index_wec(thr, -2, idx); } } else { if (duk_get_prop_index(thr, i, j)) { duk_xdef_prop_index_wec(thr, -2, idx); } else { duk_pop_undefined(thr); } } idx++; DUK_ASSERT(idx != 0U); /* Wrap check above. */ } } /* ES5.1 has a specification "bug" in that nonexistent trailing * elements don't affect the result .length. Test262 and other * engines disagree, and the specification bug was fixed in ES2015 * (see NOTE 1 in https://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.concat). */ duk_push_uarridx(thr, idx); duk_xdef_prop_stridx_short(thr, -2, DUK_STRIDX_LENGTH, DUK_PROPDESC_FLAGS_W); DUK_ASSERT_TOP(thr, n + 1); return 1; fail_wrap: DUK_ERROR_RANGE_INVALID_LENGTH(thr); DUK_WO_NORETURN(return 0;); } /* * join(), toLocaleString() * * Note: checking valstack is necessary, but only in the per-element loop. * * Note: the trivial approach of pushing all the elements on the value stack * and then calling duk_join() fails when the array contains a large number * of elements. This problem can't be offloaded to duk_join() because the * elements to join must be handled here and have special handling. Current * approach is to do intermediate joins with very large number of elements. * There is no fancy handling; the prefix gets re-joined multiple times. */ DUK_INTERNAL duk_ret_t duk_bi_array_prototype_join_shared(duk_hthread *thr) { duk_uint32_t len, count; duk_uint32_t idx; duk_small_int_t to_locale_string = duk_get_current_magic(thr); duk_idx_t valstack_required; /* For join(), nargs is 1. For toLocaleString(), nargs is 0 and * setting the top essentially pushes an undefined to the stack, * thus defaulting to a comma separator. */ duk_set_top(thr, 1); if (duk_is_undefined(thr, 0)) { duk_pop_undefined(thr); duk_push_hstring_stridx(thr, DUK_STRIDX_COMMA); } else { duk_to_string(thr, 0); } len = duk__push_this_obj_len_u32(thr); /* [ sep ToObject(this) len ] */ DUK_DDD(DUK_DDDPRINT("sep=%!T, this=%!T, len=%lu", (duk_tval *) duk_get_tval(thr, 0), (duk_tval *) duk_get_tval(thr, 1), (unsigned long) len)); /* The extra (+4) is tight. */ valstack_required = (duk_idx_t) ((len >= DUK__ARRAY_MID_JOIN_LIMIT ? DUK__ARRAY_MID_JOIN_LIMIT : len) + 4); duk_require_stack(thr, valstack_required); duk_dup_0(thr); /* [ sep ToObject(this) len sep ] */ count = 0; idx = 0; for (;;) { DUK_DDD(DUK_DDDPRINT("join idx=%ld", (long) idx)); if (count >= DUK__ARRAY_MID_JOIN_LIMIT || /* intermediate join to avoid valstack overflow */ idx >= len) { /* end of loop (careful with len==0) */ /* [ sep ToObject(this) len sep str0 ... str(count-1) ] */ DUK_DDD(DUK_DDDPRINT("mid/final join, count=%ld, idx=%ld, len=%ld", (long) count, (long) idx, (long) len)); duk_join(thr, (duk_idx_t) count); /* -> [ sep ToObject(this) len str ] */ duk_dup_0(thr); /* -> [ sep ToObject(this) len str sep ] */ duk_insert(thr, -2); /* -> [ sep ToObject(this) len sep str ] */ count = 1; } if (idx >= len) { /* if true, the stack already contains the final result */ break; } duk_get_prop_index(thr, 1, (duk_uarridx_t) idx); if (duk_is_null_or_undefined(thr, -1)) { duk_pop_nodecref_unsafe(thr); duk_push_hstring_empty(thr); } else { if (to_locale_string) { duk_to_object(thr, -1); duk_get_prop_stridx_short(thr, -1, DUK_STRIDX_TO_LOCALE_STRING); duk_insert(thr, -2); /* -> [ ... toLocaleString ToObject(val) ] */ duk_call_method(thr, 0); } duk_to_string(thr, -1); } count++; idx++; } /* [ sep ToObject(this) len sep result ] */ return 1; } /* * pop(), push() */ #if defined(DUK_USE_ARRAY_FASTPATH) DUK_LOCAL duk_ret_t duk__array_pop_fastpath(duk_hthread *thr, duk_harray *h_arr) { duk_tval *tv_arraypart; duk_tval *tv_val; duk_uint32_t len; tv_arraypart = DUK_HOBJECT_A_GET_BASE(thr->heap, (duk_hobject *) h_arr); len = h_arr->length; if (len <= 0) { /* nop, return undefined */ return 0; } len--; h_arr->length = len; /* Fast path doesn't check for an index property inherited from * Array.prototype. This is quite often acceptable; if not, * disable fast path. */ DUK_ASSERT_VS_SPACE(thr); tv_val = tv_arraypart + len; if (DUK_TVAL_IS_UNUSED(tv_val)) { /* No net refcount change. Value stack already has * 'undefined' based on value stack init policy. */ DUK_ASSERT(DUK_TVAL_IS_UNDEFINED(thr->valstack_top)); DUK_ASSERT(DUK_TVAL_IS_UNUSED(tv_val)); } else { /* No net refcount change. */ DUK_TVAL_SET_TVAL(thr->valstack_top, tv_val); DUK_TVAL_SET_UNUSED(tv_val); } thr->valstack_top++; /* XXX: there's no shrink check in the fast path now */ return 1; } #endif /* DUK_USE_ARRAY_FASTPATH */ DUK_INTERNAL duk_ret_t duk_bi_array_prototype_pop(duk_hthread *thr) { duk_uint32_t len; duk_uint32_t idx; #if defined(DUK_USE_ARRAY_FASTPATH) duk_harray *h_arr; #endif DUK_ASSERT_TOP(thr, 0); #if defined(DUK_USE_ARRAY_FASTPATH) h_arr = duk__arraypart_fastpath_this(thr); if (h_arr) { return duk__array_pop_fastpath(thr, h_arr); } #endif /* XXX: Merge fastpath check into a related call (push this, coerce length, etc)? */ len = duk__push_this_obj_len_u32(thr); if (len == 0) { duk_push_int(thr, 0); duk_put_prop_stridx_short(thr, 0, DUK_STRIDX_LENGTH); return 0; } idx = len - 1; duk_get_prop_index(thr, 0, (duk_uarridx_t) idx); duk_del_prop_index(thr, 0, (duk_uarridx_t) idx); duk_push_u32(thr, idx); duk_put_prop_stridx_short(thr, 0, DUK_STRIDX_LENGTH); return 1; } #if defined(DUK_USE_ARRAY_FASTPATH) DUK_LOCAL duk_ret_t duk__array_push_fastpath(duk_hthread *thr, duk_harray *h_arr) { duk_tval *tv_arraypart; duk_tval *tv_src; duk_tval *tv_dst; duk_uint32_t len; duk_idx_t i, n; len = h_arr->length; tv_arraypart = DUK_HOBJECT_A_GET_BASE(thr->heap, (duk_hobject *) h_arr); n = (duk_idx_t) (thr->valstack_top - thr->valstack_bottom); DUK_ASSERT(n >= 0); DUK_ASSERT((duk_uint32_t) n <= DUK_UINT32_MAX); if (DUK_UNLIKELY(len + (duk_uint32_t) n < len)) { DUK_D(DUK_DPRINT("Array.prototype.push() would go beyond 32-bit length, throw")); DUK_DCERROR_RANGE_INVALID_LENGTH(thr); /* != 0 return value returned as is by caller */ } if (len + (duk_uint32_t) n > DUK_HOBJECT_GET_ASIZE((duk_hobject *) h_arr)) { /* Array part would need to be extended. Rely on slow path * for now. * * XXX: Rework hobject code a bit and add extend support. */ return 0; } tv_src = thr->valstack_bottom; tv_dst = tv_arraypart + len; for (i = 0; i < n; i++) { /* No net refcount change; reset value stack values to * undefined to satisfy value stack init policy. */ DUK_TVAL_SET_TVAL(tv_dst, tv_src); DUK_TVAL_SET_UNDEFINED(tv_src); tv_src++; tv_dst++; } thr->valstack_top = thr->valstack_bottom; len += (duk_uint32_t) n; h_arr->length = len; DUK_ASSERT((duk_uint_t) len == len); duk_push_uint(thr, (duk_uint_t) len); return 1; } #endif /* DUK_USE_ARRAY_FASTPATH */ DUK_INTERNAL duk_ret_t duk_bi_array_prototype_push(duk_hthread *thr) { /* Note: 'this' is not necessarily an Array object. The push() * algorithm is supposed to work for other kinds of objects too, * so the algorithm has e.g. an explicit update for the 'length' * property which is normally "magical" in arrays. */ duk_uint32_t len; duk_idx_t i, n; #if defined(DUK_USE_ARRAY_FASTPATH) duk_harray *h_arr; #endif #if defined(DUK_USE_ARRAY_FASTPATH) h_arr = duk__arraypart_fastpath_this(thr); if (h_arr) { duk_ret_t rc; rc = duk__array_push_fastpath(thr, h_arr); if (rc != 0) { return rc; } DUK_DD(DUK_DDPRINT("array push() fast path exited, resize case")); } #endif n = duk_get_top(thr); len = duk__push_this_obj_len_u32(thr); /* [ arg1 ... argN obj length ] */ /* Technically Array.prototype.push() can create an Array with length * longer than 2^32-1, i.e. outside the 32-bit range. The final length * is *not* wrapped to 32 bits in the specification. * * This implementation tracks length with a uint32 because it's much * more practical. * * See: test-bi-array-push-maxlen.js. */ if (len + (duk_uint32_t) n < len) { DUK_D(DUK_DPRINT("Array.prototype.push() would go beyond 32-bit length, throw")); DUK_DCERROR_RANGE_INVALID_LENGTH(thr); } for (i = 0; i < n; i++) { duk_dup(thr, i); duk_put_prop_index(thr, -3, (duk_uarridx_t) (len + (duk_uint32_t) i)); } len += (duk_uint32_t) n; duk_push_u32(thr, len); duk_dup_top(thr); duk_put_prop_stridx_short(thr, -4, DUK_STRIDX_LENGTH); /* [ arg1 ... argN obj length new_length ] */ return 1; } /* * sort() * * Currently qsort with random pivot. This is now really, really slow, * because there is no fast path for array parts. * * Signed indices are used because qsort() leaves and degenerate cases * may use a negative offset. */ DUK_LOCAL duk_small_int_t duk__array_sort_compare(duk_hthread *thr, duk_int_t idx1, duk_int_t idx2) { duk_bool_t have1, have2; duk_bool_t undef1, undef2; duk_small_int_t ret; duk_idx_t idx_obj = 1; /* fixed offsets in valstack */ duk_idx_t idx_fn = 0; duk_hstring *h1, *h2; /* Fast exit if indices are identical. This is valid for a non-existent property, * for an undefined value, and almost always for ToString() coerced comparison of * arbitrary values (corner cases where this is not the case include e.g. a an * object with varying ToString() coercion). * * The specification does not prohibit "caching" of values read from the array, so * assuming equality for comparing an index with itself falls into the category of * "caching". * * Also, compareFn may be inconsistent, so skipping a call to compareFn here may * have an effect on the final result. The specification does not require any * specific behavior for inconsistent compare functions, so again, this fast path * is OK. */ if (idx1 == idx2) { DUK_DDD(DUK_DDDPRINT("duk__array_sort_compare: idx1=%ld, idx2=%ld -> indices identical, quick exit", (long) idx1, (long) idx2)); return 0; } have1 = duk_get_prop_index(thr, idx_obj, (duk_uarridx_t) idx1); have2 = duk_get_prop_index(thr, idx_obj, (duk_uarridx_t) idx2); DUK_DDD(DUK_DDDPRINT("duk__array_sort_compare: idx1=%ld, idx2=%ld, have1=%ld, have2=%ld, val1=%!T, val2=%!T", (long) idx1, (long) idx2, (long) have1, (long) have2, (duk_tval *) duk_get_tval(thr, -2), (duk_tval *) duk_get_tval(thr, -1))); if (have1) { if (have2) { ; } else { ret = -1; goto pop_ret; } } else { if (have2) { ret = 1; goto pop_ret; } else { ret = 0; goto pop_ret; } } undef1 = duk_is_undefined(thr, -2); undef2 = duk_is_undefined(thr, -1); if (undef1) { if (undef2) { ret = 0; goto pop_ret; } else { ret = 1; goto pop_ret; } } else { if (undef2) { ret = -1; goto pop_ret; } else { ; } } if (!duk_is_undefined(thr, idx_fn)) { duk_double_t d; /* No need to check callable; duk_call() will do that. */ duk_dup(thr, idx_fn); /* -> [ ... x y fn ] */ duk_insert(thr, -3); /* -> [ ... fn x y ] */ duk_call(thr, 2); /* -> [ ... res ] */ /* ES5 is a bit vague about what to do if the return value is * not a number. ES2015 provides a concrete description: * http://www.ecma-international.org/ecma-262/6.0/#sec-sortcompare. */ d = duk_to_number_m1(thr); if (d < 0.0) { ret = -1; } else if (d > 0.0) { ret = 1; } else { /* Because NaN compares to false, NaN is handled here * without an explicit check above. */ ret = 0; } duk_pop_nodecref_unsafe(thr); DUK_DDD(DUK_DDDPRINT("-> result %ld (from comparefn, after coercion)", (long) ret)); return ret; } /* string compare is the default (a bit oddly) */ /* XXX: any special handling for plain array; causes repeated coercion now? */ h1 = duk_to_hstring(thr, -2); h2 = duk_to_hstring_m1(thr); DUK_ASSERT(h1 != NULL); DUK_ASSERT(h2 != NULL); ret = duk_js_string_compare(h1, h2); /* retval is directly usable */ goto pop_ret; pop_ret: duk_pop_2_unsafe(thr); DUK_DDD(DUK_DDDPRINT("-> result %ld", (long) ret)); return ret; } DUK_LOCAL void duk__array_sort_swap(duk_hthread *thr, duk_int_t l, duk_int_t r) { duk_bool_t have_l, have_r; duk_idx_t idx_obj = 1; /* fixed offset in valstack */ if (l == r) { return; } /* swap elements; deal with non-existent elements correctly */ have_l = duk_get_prop_index(thr, idx_obj, (duk_uarridx_t) l); have_r = duk_get_prop_index(thr, idx_obj, (duk_uarridx_t) r); if (have_r) { /* right exists, [[Put]] regardless whether or not left exists */ duk_put_prop_index(thr, idx_obj, (duk_uarridx_t) l); } else { duk_del_prop_index(thr, idx_obj, (duk_uarridx_t) l); duk_pop_undefined(thr); } if (have_l) { duk_put_prop_index(thr, idx_obj, (duk_uarridx_t) r); } else { duk_del_prop_index(thr, idx_obj, (duk_uarridx_t) r); duk_pop_undefined(thr); } } #if defined(DUK_USE_DEBUG_LEVEL) && (DUK_USE_DEBUG_LEVEL >= 2) /* Debug print which visualizes the qsort partitioning process. */ DUK_LOCAL void duk__debuglog_qsort_state(duk_hthread *thr, duk_int_t lo, duk_int_t hi, duk_int_t pivot) { char buf[4096]; char *ptr = buf; duk_int_t i, n; n = (duk_int_t) duk_get_length(thr, 1); if (n > 4000) { n = 4000; } *ptr++ = '['; for (i = 0; i < n; i++) { if (i == pivot) { *ptr++ = '|'; } else if (i == lo) { *ptr++ = '<'; } else if (i == hi) { *ptr++ = '>'; } else if (i >= lo && i <= hi) { *ptr++ = '-'; } else { *ptr++ = ' '; } } *ptr++ = ']'; *ptr++ = '\0'; DUK_DDD(DUK_DDDPRINT("%s (lo=%ld, hi=%ld, pivot=%ld)", (const char *) buf, (long) lo, (long) hi, (long) pivot)); } #endif DUK_LOCAL void duk__array_qsort(duk_hthread *thr, duk_int_t lo, duk_int_t hi) { duk_int_t p, l, r; /* The lo/hi indices may be crossed and hi < 0 is possible at entry. */ DUK_DDD(DUK_DDDPRINT("duk__array_qsort: lo=%ld, hi=%ld, obj=%!T", (long) lo, (long) hi, (duk_tval *) duk_get_tval(thr, 1))); DUK_ASSERT_TOP(thr, 3); /* In some cases it may be that lo > hi, or hi < 0; these * degenerate cases happen e.g. for empty arrays, and in * recursion leaves. */ /* trivial cases */ if (hi - lo < 1) { DUK_DDD(DUK_DDDPRINT("degenerate case, return immediately")); return; } DUK_ASSERT(hi > lo); DUK_ASSERT(hi - lo + 1 >= 2); /* randomized pivot selection */ p = lo + (duk_int_t) (duk_util_get_random_double(thr) * (duk_double_t) (hi - lo + 1)); DUK_ASSERT(p >= lo && p <= hi); DUK_DDD(DUK_DDDPRINT("lo=%ld, hi=%ld, chose pivot p=%ld", (long) lo, (long) hi, (long) p)); /* move pivot out of the way */ duk__array_sort_swap(thr, p, lo); p = lo; DUK_DDD(DUK_DDDPRINT("pivot moved out of the way: %!T", (duk_tval *) duk_get_tval(thr, 1))); l = lo + 1; r = hi; for (;;) { /* find elements to swap */ for (;;) { DUK_DDD(DUK_DDDPRINT("left scan: l=%ld, r=%ld, p=%ld", (long) l, (long) r, (long) p)); if (l >= hi) { break; } if (duk__array_sort_compare(thr, l, p) >= 0) { /* !(l < p) */ break; } l++; } for (;;) { DUK_DDD(DUK_DDDPRINT("right scan: l=%ld, r=%ld, p=%ld", (long) l, (long) r, (long) p)); if (r <= lo) { break; } if (duk__array_sort_compare(thr, p, r) >= 0) { /* !(p < r) */ break; } r--; } if (l >= r) { goto done; } DUK_ASSERT(l < r); DUK_DDD(DUK_DDDPRINT("swap %ld and %ld", (long) l, (long) r)); duk__array_sort_swap(thr, l, r); DUK_DDD(DUK_DDDPRINT("after swap: %!T", (duk_tval *) duk_get_tval(thr, 1))); l++; r--; } done: /* Note that 'l' and 'r' may cross, i.e. r < l */ DUK_ASSERT(l >= lo && l <= hi); DUK_ASSERT(r >= lo && r <= hi); /* XXX: there's no explicit recursion bound here now. For the average * qsort recursion depth O(log n) that's not really necessary: e.g. for * 2**32 recursion depth would be about 32 which is OK. However, qsort * worst case recursion depth is O(n) which may be a problem. */ /* move pivot to its final place */ DUK_DDD(DUK_DDDPRINT("before final pivot swap: %!T", (duk_tval *) duk_get_tval(thr, 1))); duk__array_sort_swap(thr, lo, r); #if defined(DUK_USE_DEBUG_LEVEL) && (DUK_USE_DEBUG_LEVEL >= 2) duk__debuglog_qsort_state(thr, lo, hi, r); #endif DUK_DDD(DUK_DDDPRINT("recurse: pivot=%ld, obj=%!T", (long) r, (duk_tval *) duk_get_tval(thr, 1))); duk__array_qsort(thr, lo, r - 1); duk__array_qsort(thr, r + 1, hi); } DUK_INTERNAL duk_ret_t duk_bi_array_prototype_sort(duk_hthread *thr) { duk_uint32_t len; /* XXX: len >= 0x80000000 won't work below because a signed type * is needed by qsort. */ len = duk__push_this_obj_len_u32_limited(thr); /* stack[0] = compareFn * stack[1] = ToObject(this) * stack[2] = ToUint32(length) */ if (len > 0) { /* avoid degenerate cases, so that (len - 1) won't underflow */ duk__array_qsort(thr, (duk_int_t) 0, (duk_int_t) (len - 1)); } DUK_ASSERT_TOP(thr, 3); duk_pop_nodecref_unsafe(thr); return 1; /* return ToObject(this) */ } /* * splice() */ /* XXX: this compiles to over 500 bytes now, even without special handling * for an array part. Uses signed ints so does not handle full array range correctly. */ /* XXX: can shift() / unshift() use the same helper? * shift() is (close to?) <--> splice(0, 1) * unshift is (close to?) <--> splice(0, 0, [items])? */ DUK_INTERNAL duk_ret_t duk_bi_array_prototype_splice(duk_hthread *thr) { duk_idx_t nargs; duk_uint32_t len_u32; duk_int_t len; duk_bool_t have_delcount; duk_int_t item_count; duk_int_t act_start; duk_int_t del_count; duk_int_t i, n; DUK_UNREF(have_delcount); nargs = duk_get_top(thr); if (nargs < 2) { duk_set_top(thr, 2); nargs = 2; have_delcount = 0; } else { have_delcount = 1; } /* XXX: len >= 0x80000000 won't work below because we need to be * able to represent -len. */ len_u32 = duk__push_this_obj_len_u32_limited(thr); len = (duk_int_t) len_u32; DUK_ASSERT(len >= 0); act_start = duk_to_int_clamped(thr, 0, -len, len); if (act_start < 0) { act_start = len + act_start; } DUK_ASSERT(act_start >= 0 && act_start <= len); #if defined(DUK_USE_NONSTD_ARRAY_SPLICE_DELCOUNT) if (have_delcount) { #endif del_count = duk_to_int_clamped(thr, 1, 0, len - act_start); #if defined(DUK_USE_NONSTD_ARRAY_SPLICE_DELCOUNT) } else { /* E5.1 standard behavior when deleteCount is not given would be * to treat it just like if 'undefined' was given, which coerces * ultimately to 0. Real world behavior is to splice to the end * of array, see test-bi-array-proto-splice-no-delcount.js. */ del_count = len - act_start; } #endif DUK_ASSERT(nargs >= 2); item_count = (duk_int_t) (nargs - 2); DUK_ASSERT(del_count >= 0 && del_count <= len - act_start); DUK_ASSERT(del_count + act_start <= len); /* For now, restrict result array into 32-bit length range. */ if (((duk_double_t) len) - ((duk_double_t) del_count) + ((duk_double_t) item_count) > (duk_double_t) DUK_UINT32_MAX) { DUK_D(DUK_DPRINT("Array.prototype.splice() would go beyond 32-bit length, throw")); DUK_DCERROR_RANGE_INVALID_LENGTH(thr); } duk_push_array(thr); /* stack[0] = start * stack[1] = deleteCount * stack[2...nargs-1] = items * stack[nargs] = ToObject(this) -3 * stack[nargs+1] = ToUint32(length) -2 * stack[nargs+2] = result array -1 */ DUK_ASSERT_TOP(thr, nargs + 3); /* Step 9: copy elements-to-be-deleted into the result array */ for (i = 0; i < del_count; i++) { if (duk_get_prop_index(thr, -3, (duk_uarridx_t) (act_start + i))) { duk_xdef_prop_index_wec(thr, -2, (duk_uarridx_t) i); /* throw flag irrelevant (false in std alg) */ } else { duk_pop_undefined(thr); } } duk_push_u32(thr, (duk_uint32_t) del_count); duk_xdef_prop_stridx_short(thr, -2, DUK_STRIDX_LENGTH, DUK_PROPDESC_FLAGS_W); /* Steps 12 and 13: reorganize elements to make room for itemCount elements */ if (item_count < del_count) { /* [ A B C D E F G H ] rel_index = 2, del_count 3, item count 1 * -> [ A B F G H ] (conceptual intermediate step) * -> [ A B . F G H ] (placeholder marked) * [ A B C F G H ] (actual result at this point, C will be replaced) */ DUK_ASSERT_TOP(thr, nargs + 3); n = len - del_count; for (i = act_start; i < n; i++) { if (duk_get_prop_index(thr, -3, (duk_uarridx_t) (i + del_count))) { duk_put_prop_index(thr, -4, (duk_uarridx_t) (i + item_count)); } else { duk_pop_undefined(thr); duk_del_prop_index(thr, -3, (duk_uarridx_t) (i + item_count)); } } DUK_ASSERT_TOP(thr, nargs + 3); /* loop iterator init and limit changed from standard algorithm */ n = len - del_count + item_count; for (i = len - 1; i >= n; i--) { duk_del_prop_index(thr, -3, (duk_uarridx_t) i); } DUK_ASSERT_TOP(thr, nargs + 3); } else if (item_count > del_count) { /* [ A B C D E F G H ] rel_index = 2, del_count 3, item count 4 * -> [ A B F G H ] (conceptual intermediate step) * -> [ A B . . . . F G H ] (placeholder marked) * [ A B C D E F F G H ] (actual result at this point) */ DUK_ASSERT_TOP(thr, nargs + 3); /* loop iterator init and limit changed from standard algorithm */ for (i = len - del_count - 1; i >= act_start; i--) { if (duk_get_prop_index(thr, -3, (duk_uarridx_t) (i + del_count))) { duk_put_prop_index(thr, -4, (duk_uarridx_t) (i + item_count)); } else { duk_pop_undefined(thr); duk_del_prop_index(thr, -3, (duk_uarridx_t) (i + item_count)); } } DUK_ASSERT_TOP(thr, nargs + 3); } else { /* [ A B C D E F G H ] rel_index = 2, del_count 3, item count 3 * -> [ A B F G H ] (conceptual intermediate step) * -> [ A B . . . F G H ] (placeholder marked) * [ A B C D E F G H ] (actual result at this point) */ } DUK_ASSERT_TOP(thr, nargs + 3); /* Step 15: insert itemCount elements into the hole made above */ for (i = 0; i < item_count; i++) { duk_dup(thr, i + 2); /* args start at index 2 */ duk_put_prop_index(thr, -4, (duk_uarridx_t) (act_start + i)); } /* Step 16: update length; note that the final length may be above 32 bit range * (but we checked above that this isn't the case here) */ duk_push_u32(thr, (duk_uint32_t) (len - del_count + item_count)); duk_put_prop_stridx_short(thr, -4, DUK_STRIDX_LENGTH); /* result array is already at the top of stack */ DUK_ASSERT_TOP(thr, nargs + 3); return 1; } /* * reverse() */ DUK_INTERNAL duk_ret_t duk_bi_array_prototype_reverse(duk_hthread *thr) { duk_uint32_t len; duk_uint32_t middle; duk_uint32_t lower, upper; duk_bool_t have_lower, have_upper; len = duk__push_this_obj_len_u32(thr); middle = len / 2; /* If len <= 1, middle will be 0 and for-loop bails out * immediately (0 < 0 -> false). */ for (lower = 0; lower < middle; lower++) { DUK_ASSERT(len >= 2); DUK_ASSERT_TOP(thr, 2); DUK_ASSERT(len >= lower + 1); upper = len - lower - 1; have_lower = duk_get_prop_index(thr, -2, (duk_uarridx_t) lower); have_upper = duk_get_prop_index(thr, -3, (duk_uarridx_t) upper); /* [ ToObject(this) ToUint32(length) lowerValue upperValue ] */ if (have_upper) { duk_put_prop_index(thr, -4, (duk_uarridx_t) lower); } else { duk_del_prop_index(thr, -4, (duk_uarridx_t) lower); duk_pop_undefined(thr); } if (have_lower) { duk_put_prop_index(thr, -3, (duk_uarridx_t) upper); } else { duk_del_prop_index(thr, -3, (duk_uarridx_t) upper); duk_pop_undefined(thr); } DUK_ASSERT_TOP(thr, 2); } DUK_ASSERT_TOP(thr, 2); duk_pop_unsafe(thr); /* -> [ ToObject(this) ] */ return 1; } /* * slice() */ DUK_INTERNAL duk_ret_t duk_bi_array_prototype_slice(duk_hthread *thr) { duk_uint32_t len_u32; duk_int_t len; duk_int_t start, end; duk_int_t i; duk_uarridx_t idx; duk_uint32_t res_length = 0; /* XXX: len >= 0x80000000 won't work below because we need to be * able to represent -len. */ len_u32 = duk__push_this_obj_len_u32_limited(thr); len = (duk_int_t) len_u32; DUK_ASSERT(len >= 0); duk_push_array(thr); /* stack[0] = start * stack[1] = end * stack[2] = ToObject(this) * stack[3] = ToUint32(length) * stack[4] = result array */ start = duk_to_int_clamped(thr, 0, -len, len); if (start < 0) { start = len + start; } /* XXX: could duk_is_undefined() provide defaulting undefined to 'len' * (the upper limit)? */ if (duk_is_undefined(thr, 1)) { end = len; } else { end = duk_to_int_clamped(thr, 1, -len, len); if (end < 0) { end = len + end; } } DUK_ASSERT(start >= 0 && start <= len); DUK_ASSERT(end >= 0 && end <= len); idx = 0; for (i = start; i < end; i++) { DUK_ASSERT_TOP(thr, 5); if (duk_get_prop_index(thr, 2, (duk_uarridx_t) i)) { duk_xdef_prop_index_wec(thr, 4, idx); res_length = idx + 1; } else { duk_pop_undefined(thr); } idx++; DUK_ASSERT_TOP(thr, 5); } duk_push_u32(thr, res_length); duk_xdef_prop_stridx_short(thr, 4, DUK_STRIDX_LENGTH, DUK_PROPDESC_FLAGS_W); DUK_ASSERT_TOP(thr, 5); return 1; } /* * shift() */ DUK_INTERNAL duk_ret_t duk_bi_array_prototype_shift(duk_hthread *thr) { duk_uint32_t len; duk_uint32_t i; len = duk__push_this_obj_len_u32(thr); if (len == 0) { duk_push_int(thr, 0); duk_put_prop_stridx_short(thr, 0, DUK_STRIDX_LENGTH); return 0; } duk_get_prop_index(thr, 0, 0); /* stack[0] = object (this) * stack[1] = ToUint32(length) * stack[2] = elem at index 0 (retval) */ for (i = 1; i < len; i++) { DUK_ASSERT_TOP(thr, 3); if (duk_get_prop_index(thr, 0, (duk_uarridx_t) i)) { /* fromPresent = true */ duk_put_prop_index(thr, 0, (duk_uarridx_t) (i - 1)); } else { /* fromPresent = false */ duk_del_prop_index(thr, 0, (duk_uarridx_t) (i - 1)); duk_pop_undefined(thr); } } duk_del_prop_index(thr, 0, (duk_uarridx_t) (len - 1)); duk_push_u32(thr, (duk_uint32_t) (len - 1)); duk_put_prop_stridx_short(thr, 0, DUK_STRIDX_LENGTH); DUK_ASSERT_TOP(thr, 3); return 1; } /* * unshift() */ DUK_INTERNAL duk_ret_t duk_bi_array_prototype_unshift(duk_hthread *thr) { duk_idx_t nargs; duk_uint32_t len; duk_uint32_t i; nargs = duk_get_top(thr); len = duk__push_this_obj_len_u32(thr); /* stack[0...nargs-1] = unshift args (vararg) * stack[nargs] = ToObject(this) * stack[nargs+1] = ToUint32(length) */ DUK_ASSERT_TOP(thr, nargs + 2); /* Note: unshift() may operate on indices above unsigned 32-bit range * and the final length may be >= 2**32. However, we restrict the * final result to 32-bit range for practicality. */ if (len + (duk_uint32_t) nargs < len) { DUK_D(DUK_DPRINT("Array.prototype.unshift() would go beyond 32-bit length, throw")); DUK_DCERROR_RANGE_INVALID_LENGTH(thr); } i = len; while (i > 0) { DUK_ASSERT_TOP(thr, nargs + 2); i--; /* k+argCount-1; note that may be above 32-bit range */ if (duk_get_prop_index(thr, -2, (duk_uarridx_t) i)) { /* fromPresent = true */ /* [ ... ToObject(this) ToUint32(length) val ] */ duk_put_prop_index( thr, -3, (duk_uarridx_t) (i + (duk_uint32_t) nargs)); /* -> [ ... ToObject(this) ToUint32(length) ] */ } else { /* fromPresent = false */ /* [ ... ToObject(this) ToUint32(length) val ] */ duk_pop_undefined(thr); duk_del_prop_index( thr, -2, (duk_uarridx_t) (i + (duk_uint32_t) nargs)); /* -> [ ... ToObject(this) ToUint32(length) ] */ } DUK_ASSERT_TOP(thr, nargs + 2); } for (i = 0; i < (duk_uint32_t) nargs; i++) { DUK_ASSERT_TOP(thr, nargs + 2); duk_dup(thr, (duk_idx_t) i); /* -> [ ... ToObject(this) ToUint32(length) arg[i] ] */ duk_put_prop_index(thr, -3, (duk_uarridx_t) i); DUK_ASSERT_TOP(thr, nargs + 2); } DUK_ASSERT_TOP(thr, nargs + 2); duk_push_u32(thr, len + (duk_uint32_t) nargs); duk_dup_top(thr); /* -> [ ... ToObject(this) ToUint32(length) final_len final_len ] */ duk_put_prop_stridx_short(thr, -4, DUK_STRIDX_LENGTH); return 1; } /* * indexOf(), lastIndexOf() */ DUK_INTERNAL duk_ret_t duk_bi_array_prototype_indexof_shared(duk_hthread *thr) { duk_idx_t nargs; duk_int_t i, len; duk_int_t from_idx; duk_small_int_t idx_step = duk_get_current_magic(thr); /* idx_step is +1 for indexOf, -1 for lastIndexOf */ /* lastIndexOf() needs to be a vararg function because we must distinguish * between an undefined fromIndex and a "not given" fromIndex; indexOf() is * made vararg for symmetry although it doesn't strictly need to be. */ nargs = duk_get_top(thr); duk_set_top(thr, 2); /* XXX: must be able to represent -len */ len = (duk_int_t) duk__push_this_obj_len_u32_limited(thr); if (len == 0) { goto not_found; } /* Index clamping is a bit tricky, we must ensure that we'll only iterate * through elements that exist and that the specific requirements from E5.1 * Sections 15.4.4.14 and 15.4.4.15 are fulfilled; especially: * * - indexOf: clamp to [-len,len], negative handling -> [0,len], * if clamped result is len, for-loop bails out immediately * * - lastIndexOf: clamp to [-len-1, len-1], negative handling -> [-1, len-1], * if clamped result is -1, for-loop bails out immediately * * If fromIndex is not given, ToInteger(undefined) = 0, which is correct * for indexOf() but incorrect for lastIndexOf(). Hence special handling, * and why lastIndexOf() needs to be a vararg function. */ if (nargs >= 2) { /* indexOf: clamp fromIndex to [-len, len] * (if fromIndex == len, for-loop terminates directly) * * lastIndexOf: clamp fromIndex to [-len - 1, len - 1] * (if clamped to -len-1 -> fromIndex becomes -1, terminates for-loop directly) */ from_idx = duk_to_int_clamped(thr, 1, (idx_step > 0 ? -len : -len - 1), (idx_step > 0 ? len : len - 1)); if (from_idx < 0) { /* for lastIndexOf, result may be -1 (mark immediate termination) */ from_idx = len + from_idx; } } else { /* for indexOf, ToInteger(undefined) would be 0, i.e. correct, but * handle both indexOf and lastIndexOf specially here. */ if (idx_step > 0) { from_idx = 0; } else { from_idx = len - 1; } } /* stack[0] = searchElement * stack[1] = fromIndex * stack[2] = object * stack[3] = length (not needed, but not popped above) */ for (i = from_idx; i >= 0 && i < len; i += idx_step) { DUK_ASSERT_TOP(thr, 4); if (duk_get_prop_index(thr, 2, (duk_uarridx_t) i)) { DUK_ASSERT_TOP(thr, 5); if (duk_strict_equals(thr, 0, 4)) { duk_push_int(thr, i); return 1; } } duk_pop_unsafe(thr); } not_found: duk_push_int(thr, -1); return 1; } /* * every(), some(), forEach(), map(), filter() */ #define DUK__ITER_EVERY 0 #define DUK__ITER_SOME 1 #define DUK__ITER_FOREACH 2 #define DUK__ITER_MAP 3 #define DUK__ITER_FILTER 4 /* XXX: This helper is a bit awkward because the handling for the different iteration * callers is quite different. This now compiles to a bit less than 500 bytes, so with * 5 callers the net result is about 100 bytes / caller. */ DUK_INTERNAL duk_ret_t duk_bi_array_prototype_iter_shared(duk_hthread *thr) { duk_uint32_t len; duk_uint32_t i; duk_uarridx_t k; duk_bool_t bval; duk_small_int_t iter_type = duk_get_current_magic(thr); duk_uint32_t res_length = 0; /* each call this helper serves has nargs==2 */ DUK_ASSERT_TOP(thr, 2); len = duk__push_this_obj_len_u32(thr); duk_require_callable(thr, 0); /* if thisArg not supplied, behave as if undefined was supplied */ if (iter_type == DUK__ITER_MAP || iter_type == DUK__ITER_FILTER) { duk_push_array(thr); } else { duk_push_undefined(thr); } /* stack[0] = callback * stack[1] = thisArg * stack[2] = object * stack[3] = ToUint32(length) (unused, but avoid unnecessary pop) * stack[4] = result array (or undefined) */ k = 0; /* result index for filter() */ for (i = 0; i < len; i++) { DUK_ASSERT_TOP(thr, 5); if (!duk_get_prop_index(thr, 2, (duk_uarridx_t) i)) { /* For 'map' trailing missing elements don't invoke the * callback but count towards the result length. */ if (iter_type == DUK__ITER_MAP) { res_length = i + 1; } duk_pop_undefined(thr); continue; } /* The original value needs to be preserved for filter(), hence * this funny order. We can't re-get the value because of side * effects. */ duk_dup_0(thr); duk_dup_1(thr); duk_dup_m3(thr); duk_push_u32(thr, i); duk_dup_2(thr); /* [ ... val callback thisArg val i obj ] */ duk_call_method(thr, 3); /* -> [ ... val retval ] */ switch (iter_type) { case DUK__ITER_EVERY: bval = duk_to_boolean(thr, -1); if (!bval) { /* stack top contains 'false' */ return 1; } break; case DUK__ITER_SOME: bval = duk_to_boolean(thr, -1); if (bval) { /* stack top contains 'true' */ return 1; } break; case DUK__ITER_FOREACH: /* nop */ break; case DUK__ITER_MAP: duk_dup_top(thr); duk_xdef_prop_index_wec(thr, 4, (duk_uarridx_t) i); /* retval to result[i] */ res_length = i + 1; break; case DUK__ITER_FILTER: bval = duk_to_boolean(thr, -1); if (bval) { duk_dup_m2(thr); /* orig value */ duk_xdef_prop_index_wec(thr, 4, (duk_uarridx_t) k); k++; res_length = k; } break; default: DUK_UNREACHABLE(); break; } duk_pop_2_unsafe(thr); DUK_ASSERT_TOP(thr, 5); } switch (iter_type) { case DUK__ITER_EVERY: duk_push_true(thr); break; case DUK__ITER_SOME: duk_push_false(thr); break; case DUK__ITER_FOREACH: duk_push_undefined(thr); break; case DUK__ITER_MAP: case DUK__ITER_FILTER: DUK_ASSERT_TOP(thr, 5); DUK_ASSERT(duk_is_array(thr, -1)); /* topmost element is the result array already */ duk_push_u32(thr, res_length); duk_xdef_prop_stridx_short(thr, -2, DUK_STRIDX_LENGTH, DUK_PROPDESC_FLAGS_W); break; default: DUK_UNREACHABLE(); break; } return 1; } /* * reduce(), reduceRight() */ DUK_INTERNAL duk_ret_t duk_bi_array_prototype_reduce_shared(duk_hthread *thr) { duk_idx_t nargs; duk_bool_t have_acc; duk_uint32_t i, len; duk_small_int_t idx_step = duk_get_current_magic(thr); /* idx_step is +1 for reduce, -1 for reduceRight */ /* We're a varargs function because we need to detect whether * initialValue was given or not. */ nargs = duk_get_top(thr); DUK_DDD(DUK_DDDPRINT("nargs=%ld", (long) nargs)); duk_set_top(thr, 2); len = duk__push_this_obj_len_u32(thr); duk_require_callable(thr, 0); /* stack[0] = callback fn * stack[1] = initialValue * stack[2] = object (coerced this) * stack[3] = length (not needed, but not popped above) * stack[4] = accumulator */ have_acc = 0; if (nargs >= 2) { duk_dup_1(thr); have_acc = 1; } DUK_DDD(DUK_DDDPRINT("have_acc=%ld, acc=%!T", (long) have_acc, (duk_tval *) duk_get_tval(thr, 3))); /* For len == 0, i is initialized to len - 1 which underflows. * The condition (i < len) will then exit the for-loop on the * first round which is correct. Similarly, loop termination * happens by i underflowing. */ for (i = (idx_step >= 0 ? 0 : len - 1); i < len; /* i >= 0 would always be true */ i += (duk_uint32_t) idx_step) { DUK_DDD(DUK_DDDPRINT("i=%ld, len=%ld, have_acc=%ld, top=%ld, acc=%!T", (long) i, (long) len, (long) have_acc, (long) duk_get_top(thr), (duk_tval *) duk_get_tval(thr, 4))); DUK_ASSERT((have_acc && duk_get_top(thr) == 5) || (!have_acc && duk_get_top(thr) == 4)); if (!duk_has_prop_index(thr, 2, (duk_uarridx_t) i)) { continue; } if (!have_acc) { DUK_ASSERT_TOP(thr, 4); duk_get_prop_index(thr, 2, (duk_uarridx_t) i); have_acc = 1; DUK_ASSERT_TOP(thr, 5); } else { DUK_ASSERT_TOP(thr, 5); duk_dup_0(thr); duk_dup(thr, 4); duk_get_prop_index(thr, 2, (duk_uarridx_t) i); duk_push_u32(thr, i); duk_dup_2(thr); DUK_DDD(DUK_DDDPRINT("calling reduce function: func=%!T, prev=%!T, curr=%!T, idx=%!T, obj=%!T", (duk_tval *) duk_get_tval(thr, -5), (duk_tval *) duk_get_tval(thr, -4), (duk_tval *) duk_get_tval(thr, -3), (duk_tval *) duk_get_tval(thr, -2), (duk_tval *) duk_get_tval(thr, -1))); duk_call(thr, 4); DUK_DDD(DUK_DDDPRINT("-> result: %!T", (duk_tval *) duk_get_tval(thr, -1))); duk_replace(thr, 4); DUK_ASSERT_TOP(thr, 5); } } if (!have_acc) { DUK_DCERROR_TYPE_INVALID_ARGS(thr); } DUK_ASSERT_TOP(thr, 5); return 1; } #endif /* DUK_USE_ARRAY_BUILTIN */ /* automatic undefs */ #undef DUK__ARRAY_MID_JOIN_LIMIT #undef DUK__ITER_EVERY #undef DUK__ITER_FILTER #undef DUK__ITER_FOREACH #undef DUK__ITER_MAP #undef DUK__ITER_SOME #line 1 "duk_bi_boolean.c" /* * Boolean built-ins */ /* #include duk_internal.h -> already included */ #if defined(DUK_USE_BOOLEAN_BUILTIN) /* Shared helper to provide toString() and valueOf(). Checks 'this', gets * the primitive value to stack top, and optionally coerces with ToString(). */ DUK_INTERNAL duk_ret_t duk_bi_boolean_prototype_tostring_shared(duk_hthread *thr) { duk_tval *tv; duk_hobject *h; duk_small_int_t coerce_tostring = duk_get_current_magic(thr); /* XXX: there is room to use a shared helper here, many built-ins * check the 'this' type, and if it's an object, check its class, * then get its internal value, etc. */ duk_push_this(thr); tv = duk_get_tval(thr, -1); DUK_ASSERT(tv != NULL); if (DUK_TVAL_IS_BOOLEAN(tv)) { goto type_ok; } else if (DUK_TVAL_IS_OBJECT(tv)) { h = DUK_TVAL_GET_OBJECT(tv); DUK_ASSERT(h != NULL); if (DUK_HOBJECT_GET_CLASS_NUMBER(h) == DUK_HOBJECT_CLASS_BOOLEAN) { duk_xget_owndataprop_stridx_short(thr, -1, DUK_STRIDX_INT_VALUE); DUK_ASSERT(duk_is_boolean(thr, -1)); goto type_ok; } } DUK_DCERROR_TYPE_INVALID_ARGS(thr); /* never here */ type_ok: if (coerce_tostring) { duk_to_string(thr, -1); } return 1; } DUK_INTERNAL duk_ret_t duk_bi_boolean_constructor(duk_hthread *thr) { duk_hobject *h_this; duk_to_boolean(thr, 0); if (duk_is_constructor_call(thr)) { /* XXX: helper; rely on Boolean.prototype as being non-writable, non-configurable */ duk_push_this(thr); h_this = duk_known_hobject(thr, -1); DUK_ASSERT(DUK_HOBJECT_GET_PROTOTYPE(thr->heap, h_this) == thr->builtins[DUK_BIDX_BOOLEAN_PROTOTYPE]); DUK_HOBJECT_SET_CLASS_NUMBER(h_this, DUK_HOBJECT_CLASS_BOOLEAN); duk_dup_0(thr); /* -> [ val obj val ] */ duk_xdef_prop_stridx_short(thr, -2, DUK_STRIDX_INT_VALUE, DUK_PROPDESC_FLAGS_NONE); /* XXX: proper flags? */ } /* unbalanced stack */ return 1; } #endif /* DUK_USE_BOOLEAN_BUILTIN */ #line 1 "duk_bi_buffer.c" /* * ES2015 TypedArray and Node.js Buffer built-ins */ /* #include duk_internal.h -> already included */ /* * Helpers for buffer handling, enabled with DUK_USE_BUFFEROBJECT_SUPPORT. */ #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) /* Map class number (minus DUK_HOBJECT_CLASS_BUFOBJ_MIN) to a bidx for the * default internal prototype. */ static const duk_uint8_t duk__buffer_proto_from_classnum[] = { DUK_BIDX_ARRAYBUFFER_PROTOTYPE, DUK_BIDX_DATAVIEW_PROTOTYPE, DUK_BIDX_INT8ARRAY_PROTOTYPE, DUK_BIDX_UINT8ARRAY_PROTOTYPE, DUK_BIDX_UINT8CLAMPEDARRAY_PROTOTYPE, DUK_BIDX_INT16ARRAY_PROTOTYPE, DUK_BIDX_UINT16ARRAY_PROTOTYPE, DUK_BIDX_INT32ARRAY_PROTOTYPE, DUK_BIDX_UINT32ARRAY_PROTOTYPE, DUK_BIDX_FLOAT32ARRAY_PROTOTYPE, DUK_BIDX_FLOAT64ARRAY_PROTOTYPE }; /* Map DUK_HBUFOBJ_ELEM_xxx to duk_hobject class number. * Sync with duk_hbufobj.h and duk_hobject.h. */ static const duk_uint8_t duk__buffer_class_from_elemtype[9] = { DUK_HOBJECT_CLASS_UINT8ARRAY, DUK_HOBJECT_CLASS_UINT8CLAMPEDARRAY, DUK_HOBJECT_CLASS_INT8ARRAY, DUK_HOBJECT_CLASS_UINT16ARRAY, DUK_HOBJECT_CLASS_INT16ARRAY, DUK_HOBJECT_CLASS_UINT32ARRAY, DUK_HOBJECT_CLASS_INT32ARRAY, DUK_HOBJECT_CLASS_FLOAT32ARRAY, DUK_HOBJECT_CLASS_FLOAT64ARRAY }; /* Map DUK_HBUFOBJ_ELEM_xxx to prototype object built-in index. * Sync with duk_hbufobj.h. */ static const duk_uint8_t duk__buffer_proto_from_elemtype[9] = { DUK_BIDX_UINT8ARRAY_PROTOTYPE, DUK_BIDX_UINT8CLAMPEDARRAY_PROTOTYPE, DUK_BIDX_INT8ARRAY_PROTOTYPE, DUK_BIDX_UINT16ARRAY_PROTOTYPE, DUK_BIDX_INT16ARRAY_PROTOTYPE, DUK_BIDX_UINT32ARRAY_PROTOTYPE, DUK_BIDX_INT32ARRAY_PROTOTYPE, DUK_BIDX_FLOAT32ARRAY_PROTOTYPE, DUK_BIDX_FLOAT64ARRAY_PROTOTYPE }; /* Map DUK__FLD_xxx to byte size. */ static const duk_uint8_t duk__buffer_nbytes_from_fldtype[6] = { 1, /* DUK__FLD_8BIT */ 2, /* DUK__FLD_16BIT */ 4, /* DUK__FLD_32BIT */ 4, /* DUK__FLD_FLOAT */ 8, /* DUK__FLD_DOUBLE */ 0 /* DUK__FLD_VARINT; not relevant here */ }; /* Bitfield for each DUK_HBUFOBJ_ELEM_xxx indicating which element types * are compatible with a blind byte copy for the TypedArray set() method (also * used for TypedArray constructor). Array index is target buffer elem type, * bitfield indicates compatible source types. The types must have same byte * size and they must be coercion compatible. */ #if !defined(DUK_USE_PREFER_SIZE) static duk_uint16_t duk__buffer_elemtype_copy_compatible[9] = { /* xxx -> DUK_HBUFOBJ_ELEM_UINT8 */ (1U << DUK_HBUFOBJ_ELEM_UINT8) | (1U << DUK_HBUFOBJ_ELEM_UINT8CLAMPED) | (1U << DUK_HBUFOBJ_ELEM_INT8), /* xxx -> DUK_HBUFOBJ_ELEM_UINT8CLAMPED * Note: INT8 is -not- copy compatible, e.g. -1 would coerce to 0x00. */ (1U << DUK_HBUFOBJ_ELEM_UINT8) | (1U << DUK_HBUFOBJ_ELEM_UINT8CLAMPED), /* xxx -> DUK_HBUFOBJ_ELEM_INT8 */ (1U << DUK_HBUFOBJ_ELEM_UINT8) | (1U << DUK_HBUFOBJ_ELEM_UINT8CLAMPED) | (1U << DUK_HBUFOBJ_ELEM_INT8), /* xxx -> DUK_HBUFOBJ_ELEM_UINT16 */ (1U << DUK_HBUFOBJ_ELEM_UINT16) | (1U << DUK_HBUFOBJ_ELEM_INT16), /* xxx -> DUK_HBUFOBJ_ELEM_INT16 */ (1U << DUK_HBUFOBJ_ELEM_UINT16) | (1U << DUK_HBUFOBJ_ELEM_INT16), /* xxx -> DUK_HBUFOBJ_ELEM_UINT32 */ (1U << DUK_HBUFOBJ_ELEM_UINT32) | (1U << DUK_HBUFOBJ_ELEM_INT32), /* xxx -> DUK_HBUFOBJ_ELEM_INT32 */ (1U << DUK_HBUFOBJ_ELEM_UINT32) | (1U << DUK_HBUFOBJ_ELEM_INT32), /* xxx -> DUK_HBUFOBJ_ELEM_FLOAT32 */ (1U << DUK_HBUFOBJ_ELEM_FLOAT32), /* xxx -> DUK_HBUFOBJ_ELEM_FLOAT64 */ (1U << DUK_HBUFOBJ_ELEM_FLOAT64) }; #endif /* !DUK_USE_PREFER_SIZE */ DUK_LOCAL duk_hbufobj *duk__hbufobj_promote_this(duk_hthread *thr) { duk_tval *tv_dst; duk_hbufobj *res; duk_push_this(thr); DUK_ASSERT(duk_is_buffer(thr, -1)); res = (duk_hbufobj *) duk_to_hobject(thr, -1); DUK_HBUFOBJ_ASSERT_VALID(res); DUK_DD(DUK_DDPRINT("promoted 'this' automatically to an ArrayBuffer: %!iT", duk_get_tval(thr, -1))); tv_dst = duk_get_borrowed_this_tval(thr); DUK_TVAL_SET_OBJECT_UPDREF(thr, tv_dst, (duk_hobject *) res); duk_pop(thr); return res; } #define DUK__BUFOBJ_FLAG_THROW (1 << 0) #define DUK__BUFOBJ_FLAG_PROMOTE (1 << 1) /* Shared helper. When DUK__BUFOBJ_FLAG_PROMOTE is given, the return value is * always a duk_hbufobj *. Without the flag the return value can also be a * plain buffer, and the caller must check for it using DUK_HEAPHDR_IS_BUFFER(). */ DUK_LOCAL duk_heaphdr *duk__getrequire_bufobj_this(duk_hthread *thr, duk_small_uint_t flags) { duk_tval *tv; duk_hbufobj *h_this; DUK_ASSERT(thr != NULL); tv = duk_get_borrowed_this_tval(thr); DUK_ASSERT(tv != NULL); if (DUK_TVAL_IS_OBJECT(tv)) { h_this = (duk_hbufobj *) DUK_TVAL_GET_OBJECT(tv); DUK_ASSERT(h_this != NULL); if (DUK_HOBJECT_IS_BUFOBJ((duk_hobject *) h_this)) { DUK_HBUFOBJ_ASSERT_VALID(h_this); return (duk_heaphdr *) h_this; } } else if (DUK_TVAL_IS_BUFFER(tv)) { if (flags & DUK__BUFOBJ_FLAG_PROMOTE) { /* Promote a plain buffer to a Uint8Array. This is very * inefficient but allows plain buffer to be used wherever an * Uint8Array is used with very small cost; hot path functions * like index read/write calls should provide direct buffer * support to avoid promotion. */ /* XXX: make this conditional to a flag if call sites need it? */ h_this = duk__hbufobj_promote_this(thr); DUK_ASSERT(h_this != NULL); DUK_HBUFOBJ_ASSERT_VALID(h_this); return (duk_heaphdr *) h_this; } else { /* XXX: ugly, share return pointer for duk_hbuffer. */ return (duk_heaphdr *) DUK_TVAL_GET_BUFFER(tv); } } if (flags & DUK__BUFOBJ_FLAG_THROW) { DUK_ERROR_TYPE(thr, DUK_STR_NOT_BUFFER); DUK_WO_NORETURN(return NULL;); } return NULL; } /* Check that 'this' is a duk_hbufobj and return a pointer to it. */ DUK_LOCAL duk_hbufobj *duk__get_bufobj_this(duk_hthread *thr) { return (duk_hbufobj *) duk__getrequire_bufobj_this(thr, DUK__BUFOBJ_FLAG_PROMOTE); } /* Check that 'this' is a duk_hbufobj and return a pointer to it * (NULL if not). */ DUK_LOCAL duk_hbufobj *duk__require_bufobj_this(duk_hthread *thr) { return (duk_hbufobj *) duk__getrequire_bufobj_this(thr, DUK__BUFOBJ_FLAG_THROW | DUK__BUFOBJ_FLAG_PROMOTE); } /* Check that value is a duk_hbufobj and return a pointer to it. */ DUK_LOCAL duk_hbufobj *duk__require_bufobj_value(duk_hthread *thr, duk_idx_t idx) { duk_tval *tv; duk_hbufobj *h_obj; /* Don't accept relative indices now. */ DUK_ASSERT(idx >= 0); tv = duk_require_tval(thr, idx); DUK_ASSERT(tv != NULL); if (DUK_TVAL_IS_OBJECT(tv)) { h_obj = (duk_hbufobj *) DUK_TVAL_GET_OBJECT(tv); DUK_ASSERT(h_obj != NULL); if (DUK_HOBJECT_IS_BUFOBJ((duk_hobject *) h_obj)) { DUK_HBUFOBJ_ASSERT_VALID(h_obj); return h_obj; } } else if (DUK_TVAL_IS_BUFFER(tv)) { h_obj = (duk_hbufobj *) duk_to_hobject(thr, idx); DUK_ASSERT(h_obj != NULL); DUK_HBUFOBJ_ASSERT_VALID(h_obj); return h_obj; } DUK_ERROR_TYPE(thr, DUK_STR_NOT_BUFFER); DUK_WO_NORETURN(return NULL;); } DUK_LOCAL void duk__set_bufobj_buffer(duk_hthread *thr, duk_hbufobj *h_bufobj, duk_hbuffer *h_val) { DUK_ASSERT(thr != NULL); DUK_ASSERT(h_bufobj != NULL); DUK_ASSERT(h_bufobj->buf == NULL); /* no need to decref */ DUK_ASSERT(h_val != NULL); DUK_HBUFOBJ_ASSERT_VALID(h_bufobj); DUK_UNREF(thr); h_bufobj->buf = h_val; DUK_HBUFFER_INCREF(thr, h_val); h_bufobj->length = (duk_uint_t) DUK_HBUFFER_GET_SIZE(h_val); DUK_ASSERT(h_bufobj->shift == 0); DUK_ASSERT(h_bufobj->elem_type == DUK_HBUFOBJ_ELEM_UINT8); DUK_ASSERT(h_bufobj->is_typedarray == 0); DUK_HBUFOBJ_ASSERT_VALID(h_bufobj); } /* Shared offset/length coercion helper. */ DUK_LOCAL void duk__resolve_offset_opt_length(duk_hthread *thr, duk_hbufobj *h_bufarg, duk_idx_t idx_offset, duk_idx_t idx_length, duk_uint_t *out_offset, duk_uint_t *out_length, duk_bool_t throw_flag) { duk_int_t offset_signed; duk_int_t length_signed; duk_uint_t offset; duk_uint_t length; offset_signed = duk_to_int(thr, idx_offset); if (offset_signed < 0) { goto fail_range; } offset = (duk_uint_t) offset_signed; if (offset > h_bufarg->length) { goto fail_range; } DUK_ASSERT_DISABLE(offset >= 0); /* unsigned */ DUK_ASSERT(offset <= h_bufarg->length); if (duk_is_undefined(thr, idx_length)) { DUK_ASSERT(h_bufarg->length >= offset); length = h_bufarg->length - offset; /* >= 0 */ } else { length_signed = duk_to_int(thr, idx_length); if (length_signed < 0) { goto fail_range; } length = (duk_uint_t) length_signed; DUK_ASSERT(h_bufarg->length >= offset); if (length > h_bufarg->length - offset) { /* Unlike for negative arguments, some call sites * want length to be clamped if it's positive. */ if (throw_flag) { goto fail_range; } else { length = h_bufarg->length - offset; } } } DUK_ASSERT_DISABLE(length >= 0); /* unsigned */ DUK_ASSERT(offset + length <= h_bufarg->length); *out_offset = offset; *out_length = length; return; fail_range: DUK_ERROR_RANGE(thr, DUK_STR_INVALID_ARGS); DUK_WO_NORETURN(return;); } /* Shared lenient buffer length clamping helper. No negative indices, no * element/byte shifting. */ DUK_LOCAL void duk__clamp_startend_nonegidx_noshift(duk_hthread *thr, duk_int_t buffer_length, duk_idx_t idx_start, duk_idx_t idx_end, duk_int_t *out_start_offset, duk_int_t *out_end_offset) { duk_int_t start_offset; duk_int_t end_offset; DUK_ASSERT(out_start_offset != NULL); DUK_ASSERT(out_end_offset != NULL); /* undefined coerces to zero which is correct */ start_offset = duk_to_int_clamped(thr, idx_start, 0, buffer_length); if (duk_is_undefined(thr, idx_end)) { end_offset = buffer_length; } else { end_offset = duk_to_int_clamped(thr, idx_end, start_offset, buffer_length); } DUK_ASSERT(start_offset >= 0); DUK_ASSERT(start_offset <= buffer_length); DUK_ASSERT(end_offset >= 0); DUK_ASSERT(end_offset <= buffer_length); DUK_ASSERT(start_offset <= end_offset); *out_start_offset = start_offset; *out_end_offset = end_offset; } /* Shared lenient buffer length clamping helper. Indices are treated as * element indices (though output values are byte offsets) which only * really matters for TypedArray views as other buffer object have a zero * shift. Negative indices are counted from end of input slice; crossed * indices are clamped to zero length; and final indices are clamped * against input slice. Used for e.g. ArrayBuffer slice(). */ DUK_LOCAL void duk__clamp_startend_negidx_shifted(duk_hthread *thr, duk_int_t buffer_length, duk_uint8_t buffer_shift, duk_idx_t idx_start, duk_idx_t idx_end, duk_int_t *out_start_offset, duk_int_t *out_end_offset) { duk_int_t start_offset; duk_int_t end_offset; DUK_ASSERT(out_start_offset != NULL); DUK_ASSERT(out_end_offset != NULL); buffer_length >>= buffer_shift; /* as (full) elements */ /* Resolve start/end offset as element indices first; arguments * at idx_start/idx_end are element offsets. Working with element * indices first also avoids potential for wrapping. */ start_offset = duk_to_int(thr, idx_start); if (start_offset < 0) { start_offset = buffer_length + start_offset; } if (duk_is_undefined(thr, idx_end)) { end_offset = buffer_length; } else { end_offset = duk_to_int(thr, idx_end); if (end_offset < 0) { end_offset = buffer_length + end_offset; } } /* Note: start_offset/end_offset can still be < 0 here. */ if (start_offset < 0) { start_offset = 0; } else if (start_offset > buffer_length) { start_offset = buffer_length; } if (end_offset < start_offset) { end_offset = start_offset; } else if (end_offset > buffer_length) { end_offset = buffer_length; } DUK_ASSERT(start_offset >= 0); DUK_ASSERT(start_offset <= buffer_length); DUK_ASSERT(end_offset >= 0); DUK_ASSERT(end_offset <= buffer_length); DUK_ASSERT(start_offset <= end_offset); /* Convert indices to byte offsets. */ start_offset <<= buffer_shift; end_offset <<= buffer_shift; *out_start_offset = start_offset; *out_end_offset = end_offset; } DUK_INTERNAL void duk_hbufobj_promote_plain(duk_hthread *thr, duk_idx_t idx) { if (duk_is_buffer(thr, idx)) { duk_to_object(thr, idx); } } DUK_INTERNAL void duk_hbufobj_push_uint8array_from_plain(duk_hthread *thr, duk_hbuffer *h_buf) { /* Push Uint8Array which will share the same underlying buffer as * the plain buffer argument. Also create an ArrayBuffer with the * same backing for the result .buffer property. */ duk_push_hbuffer(thr, h_buf); duk_push_buffer_object(thr, -1, 0, (duk_size_t) DUK_HBUFFER_GET_SIZE(h_buf), DUK_BUFOBJ_UINT8ARRAY); duk_remove_m2(thr); #if 0 /* More verbose equivalent; maybe useful if e.g. .buffer is omitted. */ h_bufobj = duk_push_bufobj_raw(thr, DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_FLAG_BUFOBJ | DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_UINT8ARRAY), DUK_BIDX_UINT8ARRAY_PROTOTYPE); DUK_ASSERT(h_bufobj != NULL); duk__set_bufobj_buffer(thr, h_bufobj, h_buf); h_bufobj->is_typedarray = 1; DUK_HBUFOBJ_ASSERT_VALID(h_bufobj); h_arrbuf = duk_push_bufobj_raw(thr, DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_FLAG_BUFOBJ | DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_ARRAYBUFFER), DUK_BIDX_ARRAYBUFFER_PROTOTYPE); DUK_ASSERT(h_arrbuf != NULL); duk__set_bufobj_buffer(thr, h_arrbuf, h_buf); DUK_ASSERT(h_arrbuf->is_typedarray == 0); DUK_HBUFOBJ_ASSERT_VALID(h_arrbuf); DUK_ASSERT(h_bufobj->buf_prop == NULL); h_bufobj->buf_prop = (duk_hobject *) h_arrbuf; DUK_ASSERT(h_arrbuf != NULL); DUK_HBUFOBJ_INCREF(thr, h_arrbuf); duk_pop(thr); #endif } /* Indexed read helper for buffer objects, also called from outside this file. */ DUK_INTERNAL void duk_hbufobj_push_validated_read(duk_hthread *thr, duk_hbufobj *h_bufobj, duk_uint8_t *p, duk_small_uint_t elem_size) { duk_double_union du; DUK_ASSERT(elem_size > 0); duk_memcpy((void *) du.uc, (const void *) p, (size_t) elem_size); switch (h_bufobj->elem_type) { case DUK_HBUFOBJ_ELEM_UINT8: case DUK_HBUFOBJ_ELEM_UINT8CLAMPED: duk_push_uint(thr, (duk_uint_t) du.uc[0]); break; case DUK_HBUFOBJ_ELEM_INT8: duk_push_int(thr, (duk_int_t) (duk_int8_t) du.uc[0]); break; case DUK_HBUFOBJ_ELEM_UINT16: duk_push_uint(thr, (duk_uint_t) du.us[0]); break; case DUK_HBUFOBJ_ELEM_INT16: duk_push_int(thr, (duk_int_t) (duk_int16_t) du.us[0]); break; case DUK_HBUFOBJ_ELEM_UINT32: duk_push_uint(thr, (duk_uint_t) du.ui[0]); break; case DUK_HBUFOBJ_ELEM_INT32: duk_push_int(thr, (duk_int_t) (duk_int32_t) du.ui[0]); break; case DUK_HBUFOBJ_ELEM_FLOAT32: duk_push_number(thr, (duk_double_t) du.f[0]); break; case DUK_HBUFOBJ_ELEM_FLOAT64: duk_push_number(thr, (duk_double_t) du.d); break; default: DUK_UNREACHABLE(); } } /* Indexed write helper for buffer objects, also called from outside this file. */ DUK_INTERNAL void duk_hbufobj_validated_write(duk_hthread *thr, duk_hbufobj *h_bufobj, duk_uint8_t *p, duk_small_uint_t elem_size) { duk_double_union du; /* NOTE! Caller must ensure that any side effects from the * coercions below are safe. If that cannot be guaranteed * (which is normally the case), caller must coerce the * argument using duk_to_number() before any pointer * validations; the result of duk_to_number() always coerces * without side effects here. */ switch (h_bufobj->elem_type) { case DUK_HBUFOBJ_ELEM_UINT8: du.uc[0] = (duk_uint8_t) duk_to_uint32(thr, -1); break; case DUK_HBUFOBJ_ELEM_UINT8CLAMPED: du.uc[0] = (duk_uint8_t) duk_to_uint8clamped(thr, -1); break; case DUK_HBUFOBJ_ELEM_INT8: du.uc[0] = (duk_uint8_t) duk_to_int32(thr, -1); break; case DUK_HBUFOBJ_ELEM_UINT16: du.us[0] = (duk_uint16_t) duk_to_uint32(thr, -1); break; case DUK_HBUFOBJ_ELEM_INT16: du.us[0] = (duk_uint16_t) duk_to_int32(thr, -1); break; case DUK_HBUFOBJ_ELEM_UINT32: du.ui[0] = (duk_uint32_t) duk_to_uint32(thr, -1); break; case DUK_HBUFOBJ_ELEM_INT32: du.ui[0] = (duk_uint32_t) duk_to_int32(thr, -1); break; case DUK_HBUFOBJ_ELEM_FLOAT32: /* A double-to-float cast is undefined behavior in C99 if * the cast is out-of-range, so use a helper. Example: * runtime error: value -1e+100 is outside the range of representable values of type 'float' */ du.f[0] = duk_double_to_float_t(duk_to_number_m1(thr)); break; case DUK_HBUFOBJ_ELEM_FLOAT64: du.d = (duk_double_t) duk_to_number_m1(thr); break; default: DUK_UNREACHABLE(); } DUK_ASSERT(elem_size > 0); duk_memcpy((void *) p, (const void *) du.uc, (size_t) elem_size); } /* Helper to create a fixed buffer from argument value at index 0. * Node.js and allocPlain() compatible. */ DUK_LOCAL duk_hbuffer *duk__hbufobj_fixed_from_argvalue(duk_hthread *thr) { duk_int_t len; duk_int_t i; duk_size_t buf_size; duk_uint8_t *buf; switch (duk_get_type(thr, 0)) { case DUK_TYPE_NUMBER: { len = duk_to_int_clamped(thr, 0, 0, DUK_INT_MAX); (void) duk_push_fixed_buffer_zero(thr, (duk_size_t) len); break; } case DUK_TYPE_BUFFER: { /* Treat like Uint8Array. */ goto slow_copy; } case DUK_TYPE_OBJECT: { duk_hobject *h; duk_hbufobj *h_bufobj; /* For Node.js Buffers "Passing an ArrayBuffer returns a Buffer * that shares allocated memory with the given ArrayBuffer." * https://nodejs.org/api/buffer.html#buffer_buffer_from_buffer_alloc_and_buffer_allocunsafe */ h = duk_known_hobject(thr, 0); if (DUK_HOBJECT_GET_CLASS_NUMBER(h) == DUK_HOBJECT_CLASS_ARRAYBUFFER) { DUK_ASSERT(DUK_HOBJECT_IS_BUFOBJ(h)); h_bufobj = (duk_hbufobj *) h; if (DUK_UNLIKELY(h_bufobj->buf == NULL)) { DUK_ERROR_TYPE_INVALID_ARGS(thr); DUK_WO_NORETURN(return NULL;); } if (DUK_UNLIKELY(h_bufobj->offset != 0 || h_bufobj->length != DUK_HBUFFER_GET_SIZE(h_bufobj->buf))) { /* No support for ArrayBuffers with slice * offset/length. */ DUK_ERROR_TYPE_INVALID_ARGS(thr); DUK_WO_NORETURN(return NULL;); } duk_push_hbuffer(thr, h_bufobj->buf); return h_bufobj->buf; } goto slow_copy; } case DUK_TYPE_STRING: { /* ignore encoding for now */ duk_require_hstring_notsymbol(thr, 0); duk_dup_0(thr); (void) duk_to_buffer(thr, -1, &buf_size); break; } default: DUK_ERROR_TYPE_INVALID_ARGS(thr); DUK_WO_NORETURN(return NULL;); } done: DUK_ASSERT(duk_is_buffer(thr, -1)); return duk_known_hbuffer(thr, -1); slow_copy: /* XXX: fast path for typed arrays and other buffer objects? */ (void) duk_get_prop_stridx_short(thr, 0, DUK_STRIDX_LENGTH); len = duk_to_int_clamped(thr, -1, 0, DUK_INT_MAX); duk_pop(thr); buf = (duk_uint8_t *) duk_push_fixed_buffer_nozero(thr, (duk_size_t) len); /* no zeroing, all indices get initialized */ for (i = 0; i < len; i++) { /* XXX: fast path for array or buffer arguments? */ duk_get_prop_index(thr, 0, (duk_uarridx_t) i); buf[i] = (duk_uint8_t) (duk_to_uint32(thr, -1) & 0xffU); duk_pop(thr); } goto done; } #endif /* DUK_USE_BUFFEROBJECT_SUPPORT */ /* * Node.js Buffer constructor * * Node.js Buffers are just Uint8Arrays with internal prototype set to * Buffer.prototype so they're handled otherwise the same as Uint8Array. * However, the constructor arguments are very different so a separate * constructor entry point is used. */ #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) DUK_INTERNAL duk_ret_t duk_bi_nodejs_buffer_constructor(duk_hthread *thr) { duk_hbuffer *h_buf; h_buf = duk__hbufobj_fixed_from_argvalue(thr); DUK_ASSERT(h_buf != NULL); duk_push_buffer_object(thr, -1, 0, DUK_HBUFFER_FIXED_GET_SIZE((duk_hbuffer_fixed *) (void *) h_buf), DUK_BUFOBJ_UINT8ARRAY); duk_push_hobject_bidx(thr, DUK_BIDX_NODEJS_BUFFER_PROTOTYPE); duk_set_prototype(thr, -2); /* XXX: a more direct implementation */ return 1; } #endif /* DUK_USE_BUFFEROBJECT_SUPPORT */ /* * ArrayBuffer, DataView, and TypedArray constructors */ #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) DUK_INTERNAL duk_ret_t duk_bi_arraybuffer_constructor(duk_hthread *thr) { duk_hbufobj *h_bufobj; duk_hbuffer *h_val; duk_int_t len; DUK_CTX_ASSERT_VALID(thr); duk_require_constructor_call(thr); len = duk_to_int(thr, 0); if (len < 0) { goto fail_length; } (void) duk_push_fixed_buffer_zero(thr, (duk_size_t) len); h_val = (duk_hbuffer *) duk_known_hbuffer(thr, -1); h_bufobj = duk_push_bufobj_raw(thr, DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_FLAG_BUFOBJ | DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_ARRAYBUFFER), DUK_BIDX_ARRAYBUFFER_PROTOTYPE); DUK_ASSERT(h_bufobj != NULL); duk__set_bufobj_buffer(thr, h_bufobj, h_val); DUK_HBUFOBJ_ASSERT_VALID(h_bufobj); return 1; fail_length: DUK_DCERROR_RANGE_INVALID_LENGTH(thr); } #endif /* DUK_USE_BUFFEROBJECT_SUPPORT */ /* Format of magic, bits: * 0...1: elem size shift (0-3) * 2...5: elem type (DUK_HBUFOBJ_ELEM_xxx) * * XXX: add prototype bidx explicitly to magic instead of using a mapping? */ #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) DUK_INTERNAL duk_ret_t duk_bi_typedarray_constructor(duk_hthread *thr) { duk_tval *tv; duk_hobject *h_obj; duk_hbufobj *h_bufobj = NULL; duk_hbufobj *h_bufarg = NULL; duk_hbuffer *h_val; duk_small_uint_t magic; duk_small_uint_t shift; duk_small_uint_t elem_type; duk_small_uint_t elem_size; duk_small_uint_t class_num; duk_small_uint_t proto_bidx; duk_uint_t align_mask; duk_uint_t elem_length; duk_int_t elem_length_signed; duk_uint_t byte_length; duk_small_uint_t copy_mode; /* XXX: The same copy helpers could be shared with at least some * buffer functions. */ duk_require_constructor_call(thr); /* We could fit built-in index into magic but that'd make the magic * number dependent on built-in numbering (genbuiltins.py doesn't * handle that yet). So map both class and prototype from the * element type. */ magic = (duk_small_uint_t) duk_get_current_magic(thr); shift = magic & 0x03U; /* bits 0...1: shift */ elem_type = (magic >> 2) & 0x0fU; /* bits 2...5: type */ elem_size = 1U << shift; align_mask = elem_size - 1; DUK_ASSERT(elem_type < sizeof(duk__buffer_proto_from_elemtype) / sizeof(duk_uint8_t)); proto_bidx = duk__buffer_proto_from_elemtype[elem_type]; DUK_ASSERT(proto_bidx < DUK_NUM_BUILTINS); DUK_ASSERT(elem_type < sizeof(duk__buffer_class_from_elemtype) / sizeof(duk_uint8_t)); class_num = duk__buffer_class_from_elemtype[elem_type]; DUK_DD(DUK_DDPRINT("typedarray constructor, magic=%d, shift=%d, elem_type=%d, " "elem_size=%d, proto_bidx=%d, class_num=%d", (int) magic, (int) shift, (int) elem_type, (int) elem_size, (int) proto_bidx, (int) class_num)); /* Argument variants. When the argument is an ArrayBuffer a view to * the same buffer is created; otherwise a new ArrayBuffer is always * created. */ /* XXX: initial iteration to treat a plain buffer like an ArrayBuffer: * coerce to an ArrayBuffer object and use that as .buffer. The underlying * buffer will be the same but result .buffer !== inputPlainBuffer. */ duk_hbufobj_promote_plain(thr, 0); tv = duk_get_tval(thr, 0); DUK_ASSERT(tv != NULL); /* arg count */ if (DUK_TVAL_IS_OBJECT(tv)) { h_obj = DUK_TVAL_GET_OBJECT(tv); DUK_ASSERT(h_obj != NULL); if (DUK_HOBJECT_GET_CLASS_NUMBER(h_obj) == DUK_HOBJECT_CLASS_ARRAYBUFFER) { /* ArrayBuffer: unlike any other argument variant, create * a view into the existing buffer. */ duk_int_t byte_offset_signed; duk_uint_t byte_offset; h_bufarg = (duk_hbufobj *) h_obj; byte_offset_signed = duk_to_int(thr, 1); if (byte_offset_signed < 0) { goto fail_arguments; } byte_offset = (duk_uint_t) byte_offset_signed; if (byte_offset > h_bufarg->length || (byte_offset & align_mask) != 0) { /* Must be >= 0 and multiple of element size. */ goto fail_arguments; } if (duk_is_undefined(thr, 2)) { DUK_ASSERT(h_bufarg->length >= byte_offset); byte_length = h_bufarg->length - byte_offset; if ((byte_length & align_mask) != 0) { /* Must be element size multiple from * start offset to end of buffer. */ goto fail_arguments; } elem_length = (byte_length >> shift); } else { elem_length_signed = duk_to_int(thr, 2); if (elem_length_signed < 0) { goto fail_arguments; } elem_length = (duk_uint_t) elem_length_signed; byte_length = elem_length << shift; if ((byte_length >> shift) != elem_length) { /* Byte length would overflow. */ /* XXX: easier check with less code? */ goto fail_arguments; } DUK_ASSERT(h_bufarg->length >= byte_offset); if (byte_length > h_bufarg->length - byte_offset) { /* Not enough data. */ goto fail_arguments; } } DUK_UNREF(elem_length); DUK_ASSERT_DISABLE(byte_offset >= 0); DUK_ASSERT(byte_offset <= h_bufarg->length); DUK_ASSERT_DISABLE(byte_length >= 0); DUK_ASSERT(byte_offset + byte_length <= h_bufarg->length); DUK_ASSERT((elem_length << shift) == byte_length); h_bufobj = duk_push_bufobj_raw(thr, DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_FLAG_BUFOBJ | DUK_HOBJECT_CLASS_AS_FLAGS(class_num), (duk_small_int_t) proto_bidx); h_val = h_bufarg->buf; if (h_val == NULL) { DUK_DCERROR_TYPE_INVALID_ARGS(thr); } h_bufobj->buf = h_val; DUK_HBUFFER_INCREF(thr, h_val); h_bufobj->offset = h_bufarg->offset + byte_offset; h_bufobj->length = byte_length; h_bufobj->shift = (duk_uint8_t) shift; h_bufobj->elem_type = (duk_uint8_t) elem_type; h_bufobj->is_typedarray = 1; DUK_HBUFOBJ_ASSERT_VALID(h_bufobj); /* Set .buffer to the argument ArrayBuffer. */ DUK_ASSERT(h_bufobj->buf_prop == NULL); h_bufobj->buf_prop = (duk_hobject *) h_bufarg; DUK_ASSERT(h_bufarg != NULL); DUK_HBUFOBJ_INCREF(thr, h_bufarg); return 1; } else if (DUK_HOBJECT_IS_BUFOBJ(h_obj)) { /* TypedArray (or other non-ArrayBuffer duk_hbufobj). * Conceptually same behavior as for an Array-like argument, * with a few fast paths. */ h_bufarg = (duk_hbufobj *) h_obj; DUK_HBUFOBJ_ASSERT_VALID(h_bufarg); elem_length_signed = (duk_int_t) (h_bufarg->length >> h_bufarg->shift); if (h_bufarg->buf == NULL) { DUK_DCERROR_TYPE_INVALID_ARGS(thr); } /* Select copy mode. Must take into account element * compatibility and validity of the underlying source * buffer. */ DUK_DDD(DUK_DDDPRINT("selecting copy mode for bufobj arg, " "src byte_length=%ld, src shift=%d, " "src/dst elem_length=%ld; " "dst shift=%d -> dst byte_length=%ld", (long) h_bufarg->length, (int) h_bufarg->shift, (long) elem_length_signed, (int) shift, (long) (elem_length_signed << shift))); copy_mode = 2; /* default is explicit index read/write copy */ #if !defined(DUK_USE_PREFER_SIZE) /* With a size optimized build copy_mode 2 is enough. * Modes 0 and 1 are faster but conceptually the same. */ DUK_ASSERT(elem_type < sizeof(duk__buffer_elemtype_copy_compatible) / sizeof(duk_uint16_t)); if (DUK_HBUFOBJ_VALID_SLICE(h_bufarg)) { if ((duk__buffer_elemtype_copy_compatible[elem_type] & (1 << h_bufarg->elem_type)) != 0) { DUK_DDD(DUK_DDDPRINT("source/target are copy compatible, memcpy")); DUK_ASSERT(shift == h_bufarg->shift); /* byte sizes will match */ copy_mode = 0; } else { DUK_DDD(DUK_DDDPRINT("source/target not copy compatible but valid, fast copy")); copy_mode = 1; } } #endif /* !DUK_USE_PREFER_SIZE */ } else { /* Array or Array-like */ elem_length_signed = (duk_int_t) duk_get_length(thr, 0); copy_mode = 2; } } else { /* Non-object argument is simply int coerced, matches * V8 behavior (except for "null", which we coerce to * 0 but V8 TypeErrors). */ elem_length_signed = duk_to_int(thr, 0); copy_mode = 3; } if (elem_length_signed < 0) { goto fail_arguments; } elem_length = (duk_uint_t) elem_length_signed; byte_length = (duk_uint_t) (elem_length << shift); if ((byte_length >> shift) != elem_length) { /* Byte length would overflow. */ /* XXX: easier check with less code? */ goto fail_arguments; } DUK_DDD(DUK_DDDPRINT("elem_length=%ld, byte_length=%ld", (long) elem_length, (long) byte_length)); /* ArrayBuffer argument is handled specially above; the rest of the * argument variants are handled by shared code below. * * ArrayBuffer in h_bufobj->buf_prop is intentionally left unset. * It will be automatically created by the .buffer accessor on * first access. */ /* Push the resulting view object on top of a plain fixed buffer. */ (void) duk_push_fixed_buffer(thr, byte_length); h_val = duk_known_hbuffer(thr, -1); DUK_ASSERT(h_val != NULL); h_bufobj = duk_push_bufobj_raw(thr, DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_FLAG_BUFOBJ | DUK_HOBJECT_CLASS_AS_FLAGS(class_num), (duk_small_int_t) proto_bidx); h_bufobj->buf = h_val; DUK_HBUFFER_INCREF(thr, h_val); DUK_ASSERT(h_bufobj->offset == 0); h_bufobj->length = byte_length; h_bufobj->shift = (duk_uint8_t) shift; h_bufobj->elem_type = (duk_uint8_t) elem_type; h_bufobj->is_typedarray = 1; DUK_HBUFOBJ_ASSERT_VALID(h_bufobj); /* Copy values, the copy method depends on the arguments. * * Copy mode decision may depend on the validity of the underlying * buffer of the source argument; there must be no harmful side effects * from there to here for copy_mode to still be valid. */ DUK_DDD(DUK_DDDPRINT("copy mode: %d", (int) copy_mode)); switch (copy_mode) { /* Copy modes 0 and 1 can be omitted in size optimized build, * copy mode 2 handles them (but more slowly). */ #if !defined(DUK_USE_PREFER_SIZE) case 0: { /* Use byte copy. */ duk_uint8_t *p_src; duk_uint8_t *p_dst; DUK_ASSERT(h_bufobj != NULL); DUK_ASSERT(h_bufobj->buf != NULL); DUK_ASSERT(DUK_HBUFOBJ_VALID_SLICE(h_bufobj)); DUK_ASSERT(h_bufarg != NULL); DUK_ASSERT(h_bufarg->buf != NULL); DUK_ASSERT(DUK_HBUFOBJ_VALID_SLICE(h_bufarg)); p_dst = DUK_HBUFOBJ_GET_SLICE_BASE(thr->heap, h_bufobj); p_src = DUK_HBUFOBJ_GET_SLICE_BASE(thr->heap, h_bufarg); DUK_DDD(DUK_DDDPRINT("using memcpy: p_src=%p, p_dst=%p, byte_length=%ld", (void *) p_src, (void *) p_dst, (long) byte_length)); duk_memcpy_unsafe((void *) p_dst, (const void *) p_src, (size_t) byte_length); break; } case 1: { /* Copy values through direct validated reads and writes. */ duk_small_uint_t src_elem_size; duk_small_uint_t dst_elem_size; duk_uint8_t *p_src; duk_uint8_t *p_src_end; duk_uint8_t *p_dst; DUK_ASSERT(h_bufobj != NULL); DUK_ASSERT(h_bufobj->buf != NULL); DUK_ASSERT(DUK_HBUFOBJ_VALID_SLICE(h_bufobj)); DUK_ASSERT(h_bufarg != NULL); DUK_ASSERT(h_bufarg->buf != NULL); DUK_ASSERT(DUK_HBUFOBJ_VALID_SLICE(h_bufarg)); src_elem_size = (duk_small_uint_t) (1U << h_bufarg->shift); dst_elem_size = elem_size; p_src = DUK_HBUFOBJ_GET_SLICE_BASE(thr->heap, h_bufarg); p_dst = DUK_HBUFOBJ_GET_SLICE_BASE(thr->heap, h_bufobj); p_src_end = p_src + h_bufarg->length; DUK_DDD(DUK_DDDPRINT("using fast copy: p_src=%p, p_src_end=%p, p_dst=%p, " "src_elem_size=%d, dst_elem_size=%d", (void *) p_src, (void *) p_src_end, (void *) p_dst, (int) src_elem_size, (int) dst_elem_size)); while (p_src != p_src_end) { DUK_DDD(DUK_DDDPRINT("fast path per element copy loop: " "p_src=%p, p_src_end=%p, p_dst=%p", (void *) p_src, (void *) p_src_end, (void *) p_dst)); /* A validated read() is always a number, so it's write coercion * is always side effect free an won't invalidate pointers etc. */ duk_hbufobj_push_validated_read(thr, h_bufarg, p_src, src_elem_size); duk_hbufobj_validated_write(thr, h_bufobj, p_dst, dst_elem_size); duk_pop(thr); p_src += src_elem_size; p_dst += dst_elem_size; } break; } #endif /* !DUK_USE_PREFER_SIZE */ case 2: { /* Copy values by index reads and writes. Let virtual * property handling take care of coercion. */ duk_uint_t i; DUK_DDD(DUK_DDDPRINT("using slow copy")); for (i = 0; i < elem_length; i++) { duk_get_prop_index(thr, 0, (duk_uarridx_t) i); duk_put_prop_index(thr, -2, (duk_uarridx_t) i); } break; } default: case 3: { /* No copy, leave zero bytes in the buffer. There's no * ambiguity with Float32/Float64 because zero bytes also * represent 0.0. */ DUK_DDD(DUK_DDDPRINT("using no copy")); break; } } return 1; fail_arguments: DUK_DCERROR_RANGE_INVALID_ARGS(thr); } #else /* DUK_USE_BUFFEROBJECT_SUPPORT */ /* When bufferobject support is disabled, new Uint8Array() could still be * supported to create a plain fixed buffer. Disabled for now. */ #if 0 DUK_INTERNAL duk_ret_t duk_bi_typedarray_constructor(duk_hthread *thr) { duk_int_t elem_length_signed; duk_uint_t byte_length; /* XXX: The same copy helpers could be shared with at least some * buffer functions. */ duk_require_constructor_call(thr); elem_length_signed = duk_require_int(thr, 0); if (elem_length_signed < 0) { goto fail_arguments; } byte_length = (duk_uint_t) elem_length_signed; (void) duk_push_fixed_buffer_zero(thr, (duk_size_t) byte_length); return 1; fail_arguments: DUK_DCERROR_RANGE_INVALID_ARGS(thr); } #endif /* 0 */ #endif /* DUK_USE_BUFFEROBJECT_SUPPORT */ #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) DUK_INTERNAL duk_ret_t duk_bi_dataview_constructor(duk_hthread *thr) { duk_hbufobj *h_bufarg; duk_hbufobj *h_bufobj; duk_hbuffer *h_val; duk_uint_t offset; duk_uint_t length; duk_require_constructor_call(thr); h_bufarg = duk__require_bufobj_value(thr, 0); DUK_ASSERT(h_bufarg != NULL); if (DUK_HOBJECT_GET_CLASS_NUMBER((duk_hobject *) h_bufarg) != DUK_HOBJECT_CLASS_ARRAYBUFFER) { DUK_DCERROR_TYPE_INVALID_ARGS(thr); } duk__resolve_offset_opt_length(thr, h_bufarg, 1, 2, &offset, &length, 1 /*throw_flag*/); DUK_ASSERT(offset <= h_bufarg->length); DUK_ASSERT(offset + length <= h_bufarg->length); h_bufobj = duk_push_bufobj_raw(thr, DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_FLAG_BUFOBJ | DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_DATAVIEW), DUK_BIDX_DATAVIEW_PROTOTYPE); h_val = h_bufarg->buf; if (h_val == NULL) { DUK_DCERROR_TYPE_INVALID_ARGS(thr); } h_bufobj->buf = h_val; DUK_HBUFFER_INCREF(thr, h_val); h_bufobj->offset = h_bufarg->offset + offset; h_bufobj->length = length; DUK_ASSERT(h_bufobj->shift == 0); DUK_ASSERT(h_bufobj->elem_type == DUK_HBUFOBJ_ELEM_UINT8); DUK_ASSERT(h_bufobj->is_typedarray == 0); DUK_ASSERT(h_bufobj->buf_prop == NULL); h_bufobj->buf_prop = (duk_hobject *) h_bufarg; DUK_ASSERT(h_bufarg != NULL); DUK_HBUFOBJ_INCREF(thr, h_bufarg); DUK_HBUFOBJ_ASSERT_VALID(h_bufobj); return 1; } #endif /* DUK_USE_BUFFEROBJECT_SUPPORT */ /* * ArrayBuffer.isView() */ #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) DUK_INTERNAL duk_ret_t duk_bi_arraybuffer_isview(duk_hthread *thr) { duk_hobject *h_obj; duk_bool_t ret = 0; if (duk_is_buffer(thr, 0)) { ret = 1; } else { h_obj = duk_get_hobject(thr, 0); if (h_obj != NULL && DUK_HOBJECT_IS_BUFOBJ(h_obj)) { /* DataView needs special casing: ArrayBuffer.isView() is * true, but ->is_typedarray is 0. */ ret = ((duk_hbufobj *) h_obj)->is_typedarray || (DUK_HOBJECT_GET_CLASS_NUMBER(h_obj) == DUK_HOBJECT_CLASS_DATAVIEW); } } duk_push_boolean(thr, ret); return 1; } #endif /* DUK_USE_BUFFEROBJECT_SUPPORT */ /* * Uint8Array.allocPlain() */ #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) DUK_INTERNAL duk_ret_t duk_bi_uint8array_allocplain(duk_hthread *thr) { duk__hbufobj_fixed_from_argvalue(thr); return 1; } #endif /* DUK_USE_BUFFEROBJECT_SUPPORT */ /* * Uint8Array.plainOf() */ #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) DUK_INTERNAL duk_ret_t duk_bi_uint8array_plainof(duk_hthread *thr) { duk_hbufobj *h_bufobj; #if !defined(DUK_USE_PREFER_SIZE) /* Avoid churn if argument is already a plain buffer. */ if (duk_is_buffer(thr, 0)) { return 1; } #endif /* Promotes plain buffers to ArrayBuffers, so for a plain buffer * argument we'll create a pointless temporary (but still work * correctly). */ h_bufobj = duk__require_bufobj_value(thr, 0); if (h_bufobj->buf == NULL) { duk_push_undefined(thr); } else { duk_push_hbuffer(thr, h_bufobj->buf); } return 1; } #endif /* DUK_USE_BUFFEROBJECT_SUPPORT */ /* * Node.js Buffer: toString([encoding], [start], [end]) */ #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) DUK_INTERNAL duk_ret_t duk_bi_nodejs_buffer_tostring(duk_hthread *thr) { duk_hbufobj *h_this; duk_int_t start_offset, end_offset; duk_uint8_t *buf_slice; duk_size_t slice_length; h_this = duk__get_bufobj_this(thr); if (h_this == NULL) { /* XXX: happens e.g. when evaluating: String(Buffer.prototype). */ duk_push_literal(thr, "[object Object]"); return 1; } DUK_HBUFOBJ_ASSERT_VALID(h_this); /* Ignore encoding for now. */ duk__clamp_startend_nonegidx_noshift(thr, (duk_int_t) h_this->length, 1 /*idx_start*/, 2 /*idx_end*/, &start_offset, &end_offset); slice_length = (duk_size_t) (end_offset - start_offset); buf_slice = (duk_uint8_t *) duk_push_fixed_buffer_nozero(thr, slice_length); /* all bytes initialized below */ DUK_ASSERT(buf_slice != NULL); /* Neutered or uncovered, TypeError. */ if (h_this->buf == NULL || !DUK_HBUFOBJ_VALID_BYTEOFFSET_EXCL(h_this, (duk_size_t) start_offset + slice_length)) { DUK_DCERROR_TYPE_INVALID_ARGS(thr); } /* XXX: ideally we wouldn't make a copy but a view into the buffer for the * decoding process. Or the decoding helper could be changed to accept * the slice info (a buffer pointer is NOT a good approach because guaranteeing * its stability is difficult). */ DUK_ASSERT(DUK_HBUFOBJ_VALID_BYTEOFFSET_EXCL(h_this, (duk_size_t) start_offset + slice_length)); duk_memcpy_unsafe((void *) buf_slice, (const void *) (DUK_HBUFOBJ_GET_SLICE_BASE(thr->heap, h_this) + start_offset), (size_t) slice_length); /* Use the equivalent of: new TextEncoder().encode(this) to convert the * string. Result will be valid UTF-8; non-CESU-8 inputs are currently * interpreted loosely. Value stack convention is a bit odd for now. */ duk_replace(thr, 0); duk_set_top(thr, 1); return duk_textdecoder_decode_utf8_nodejs(thr); } #endif /* DUK_USE_BUFFEROBJECT_SUPPORT */ /* * Node.js Buffer.prototype: toJSON() */ #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) DUK_INTERNAL duk_ret_t duk_bi_nodejs_buffer_tojson(duk_hthread *thr) { duk_hbufobj *h_this; duk_uint8_t *buf; duk_uint_t i, n; duk_tval *tv; h_this = duk__require_bufobj_this(thr); DUK_ASSERT(h_this != NULL); if (h_this->buf == NULL || !DUK_HBUFOBJ_VALID_SLICE(h_this)) { /* Serialize uncovered backing buffer as a null; doesn't * really matter as long we're memory safe. */ duk_push_null(thr); return 1; } duk_push_object(thr); duk_push_hstring_stridx(thr, DUK_STRIDX_UC_BUFFER); duk_put_prop_stridx_short(thr, -2, DUK_STRIDX_TYPE); /* XXX: uninitialized would be OK */ DUK_ASSERT_DISABLE((duk_size_t) h_this->length <= (duk_size_t) DUK_UINT32_MAX); tv = duk_push_harray_with_size_outptr(thr, (duk_uint32_t) h_this->length); /* XXX: needs revision with >4G buffers */ DUK_ASSERT(!duk_is_bare_object(thr, -1)); DUK_ASSERT(h_this->buf != NULL); buf = DUK_HBUFOBJ_GET_SLICE_BASE(thr->heap, h_this); for (i = 0, n = h_this->length; i < n; i++) { DUK_TVAL_SET_U32(tv + i, (duk_uint32_t) buf[i]); /* no need for decref or incref */ } duk_put_prop_stridx_short(thr, -2, DUK_STRIDX_DATA); return 1; } #endif /* DUK_USE_BUFFEROBJECT_SUPPORT */ /* * Node.js Buffer.prototype.equals() * Node.js Buffer.prototype.compare() * Node.js Buffer.compare() */ #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) DUK_INTERNAL duk_ret_t duk_bi_buffer_compare_shared(duk_hthread *thr) { duk_small_uint_t magic; duk_hbufobj *h_bufarg1; duk_hbufobj *h_bufarg2; duk_small_int_t comp_res; /* XXX: keep support for plain buffers and non-Node.js buffers? */ magic = (duk_small_uint_t) duk_get_current_magic(thr); if (magic & 0x02U) { /* Static call style. */ h_bufarg1 = duk__require_bufobj_value(thr, 0); h_bufarg2 = duk__require_bufobj_value(thr, 1); } else { h_bufarg1 = duk__require_bufobj_this(thr); h_bufarg2 = duk__require_bufobj_value(thr, 0); } DUK_ASSERT(h_bufarg1 != NULL); DUK_ASSERT(h_bufarg2 != NULL); /* We want to compare the slice/view areas of the arguments. * If either slice/view is invalid (underlying buffer is shorter) * ensure equals() is false, but otherwise the only thing that * matters is to be memory safe. */ if (DUK_HBUFOBJ_VALID_SLICE(h_bufarg1) && DUK_HBUFOBJ_VALID_SLICE(h_bufarg2)) { comp_res = duk_js_data_compare( (const duk_uint8_t *) DUK_HBUFFER_GET_DATA_PTR(thr->heap, h_bufarg1->buf) + h_bufarg1->offset, (const duk_uint8_t *) DUK_HBUFFER_GET_DATA_PTR(thr->heap, h_bufarg2->buf) + h_bufarg2->offset, (duk_size_t) h_bufarg1->length, (duk_size_t) h_bufarg2->length); } else { comp_res = -1; /* either nonzero value is ok */ } if (magic & 0x01U) { /* compare: similar to string comparison but for buffer data. */ duk_push_int(thr, comp_res); } else { /* equals */ duk_push_boolean(thr, (comp_res == 0)); } return 1; } #endif /* DUK_USE_BUFFEROBJECT_SUPPORT */ /* * Node.js Buffer.prototype.fill() */ #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) DUK_INTERNAL duk_ret_t duk_bi_nodejs_buffer_fill(duk_hthread *thr) { duk_hbufobj *h_this; const duk_uint8_t *fill_str_ptr; duk_size_t fill_str_len; duk_uint8_t fill_value; duk_int_t fill_offset; duk_int_t fill_end; duk_size_t fill_length; duk_uint8_t *p; h_this = duk__require_bufobj_this(thr); DUK_ASSERT(h_this != NULL); if (h_this->buf == NULL) { DUK_DCERROR_TYPE_INVALID_ARGS(thr); } /* [ value offset end ] */ if (duk_is_string_notsymbol(thr, 0)) { fill_str_ptr = (const duk_uint8_t *) duk_get_lstring(thr, 0, &fill_str_len); DUK_ASSERT(fill_str_ptr != NULL); } else { /* Symbols get ToNumber() coerced and cause TypeError. */ fill_value = (duk_uint8_t) duk_to_uint32(thr, 0); fill_str_ptr = (const duk_uint8_t *) &fill_value; fill_str_len = 1; } /* Fill offset handling is more lenient than in Node.js. */ duk__clamp_startend_nonegidx_noshift(thr, (duk_int_t) h_this->length, 1 /*idx_start*/, 2 /*idx_end*/, &fill_offset, &fill_end); DUK_DDD(DUK_DDDPRINT("fill: fill_value=%02x, fill_offset=%ld, fill_end=%ld, view length=%ld", (unsigned int) fill_value, (long) fill_offset, (long) fill_end, (long) h_this->length)); DUK_ASSERT(fill_end - fill_offset >= 0); DUK_ASSERT(h_this->buf != NULL); p = (DUK_HBUFOBJ_GET_SLICE_BASE(thr->heap, h_this) + fill_offset); fill_length = (duk_size_t) (fill_end - fill_offset); if (fill_str_len == 1) { /* Handle single character fills as memset() even when * the fill data comes from a one-char argument. */ duk_memset_unsafe((void *) p, (int) fill_str_ptr[0], (size_t) fill_length); } else if (fill_str_len > 1) { duk_size_t i, n, t; for (i = 0, n = (duk_size_t) (fill_end - fill_offset), t = 0; i < n; i++) { p[i] = fill_str_ptr[t++]; if (t >= fill_str_len) { t = 0; } } } else { DUK_DDD(DUK_DDDPRINT("zero size fill pattern, ignore silently")); } /* Return the Buffer to allow chaining: b.fill(0x11).fill(0x22, 3, 5).toString() */ duk_push_this(thr); return 1; } #endif /* DUK_USE_BUFFEROBJECT_SUPPORT */ /* * Node.js Buffer.prototype.write(string, [offset], [length], [encoding]) */ #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) DUK_INTERNAL duk_ret_t duk_bi_nodejs_buffer_write(duk_hthread *thr) { duk_hbufobj *h_this; duk_uint_t offset; duk_uint_t length; const duk_uint8_t *str_data; duk_size_t str_len; /* XXX: very inefficient support for plain buffers */ h_this = duk__require_bufobj_this(thr); DUK_ASSERT(h_this != NULL); /* Argument must be a string, e.g. a buffer is not allowed. */ str_data = (const duk_uint8_t *) duk_require_lstring_notsymbol(thr, 0, &str_len); duk__resolve_offset_opt_length(thr, h_this, 1, 2, &offset, &length, 0 /*throw_flag*/); DUK_ASSERT(offset <= h_this->length); DUK_ASSERT(offset + length <= h_this->length); /* XXX: encoding is ignored now. */ if (length > str_len) { length = (duk_uint_t) str_len; } if (DUK_HBUFOBJ_VALID_SLICE(h_this)) { /* Cannot overlap. */ duk_memcpy_unsafe((void *) (DUK_HBUFOBJ_GET_SLICE_BASE(thr->heap, h_this) + offset), (const void *) str_data, (size_t) length); } else { DUK_DDD(DUK_DDDPRINT("write() target buffer is not covered, silent ignore")); } duk_push_uint(thr, length); return 1; } #endif /* DUK_USE_BUFFEROBJECT_SUPPORT */ /* * Node.js Buffer.prototype.copy() */ #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) DUK_INTERNAL duk_ret_t duk_bi_nodejs_buffer_copy(duk_hthread *thr) { duk_hbufobj *h_this; duk_hbufobj *h_bufarg; duk_int_t source_length; duk_int_t target_length; duk_int_t target_start, source_start, source_end; duk_uint_t target_ustart, source_ustart, source_uend; duk_uint_t copy_size = 0; /* [ targetBuffer targetStart sourceStart sourceEnd ] */ h_this = duk__require_bufobj_this(thr); h_bufarg = duk__require_bufobj_value(thr, 0); DUK_ASSERT(h_this != NULL); DUK_ASSERT(h_bufarg != NULL); source_length = (duk_int_t) h_this->length; target_length = (duk_int_t) h_bufarg->length; target_start = duk_to_int(thr, 1); source_start = duk_to_int(thr, 2); if (duk_is_undefined(thr, 3)) { source_end = source_length; } else { source_end = duk_to_int(thr, 3); } DUK_DDD(DUK_DDDPRINT("checking copy args: target_start=%ld, target_length=%ld, " "source_start=%ld, source_end=%ld, source_length=%ld", (long) target_start, (long) h_bufarg->length, (long) source_start, (long) source_end, (long) source_length)); /* This behavior mostly mimics Node.js now. */ if (source_start < 0 || source_end < 0 || target_start < 0) { /* Negative offsets cause a RangeError. */ goto fail_bounds; } source_ustart = (duk_uint_t) source_start; source_uend = (duk_uint_t) source_end; target_ustart = (duk_uint_t) target_start; if (source_ustart >= source_uend || /* crossed offsets or zero size */ source_ustart >= (duk_uint_t) source_length || /* source out-of-bounds (but positive) */ target_ustart >= (duk_uint_t) target_length) { /* target out-of-bounds (but positive) */ goto silent_ignore; } if (source_uend >= (duk_uint_t) source_length) { /* Source end clamped silently to available length. */ source_uend = (duk_uint_t) source_length; } copy_size = source_uend - source_ustart; if (target_ustart + copy_size > (duk_uint_t) target_length) { /* Clamp to target's end if too long. * * NOTE: there's no overflow possibility in the comparison; * both target_ustart and copy_size are >= 0 and based on * values in duk_int_t range. Adding them as duk_uint_t * values is then guaranteed not to overflow. */ DUK_ASSERT(target_ustart + copy_size >= target_ustart); /* no overflow */ DUK_ASSERT(target_ustart + copy_size >= copy_size); /* no overflow */ copy_size = (duk_uint_t) target_length - target_ustart; } DUK_DDD(DUK_DDDPRINT("making copy: target_ustart=%lu source_ustart=%lu copy_size=%lu", (unsigned long) target_ustart, (unsigned long) source_ustart, (unsigned long) copy_size)); DUK_ASSERT(copy_size >= 1); DUK_ASSERT(source_ustart <= (duk_uint_t) source_length); DUK_ASSERT(source_ustart + copy_size <= (duk_uint_t) source_length); DUK_ASSERT(target_ustart <= (duk_uint_t) target_length); DUK_ASSERT(target_ustart + copy_size <= (duk_uint_t) target_length); /* Ensure copy is covered by underlying buffers. */ DUK_ASSERT(h_bufarg->buf != NULL); /* length check */ DUK_ASSERT(h_this->buf != NULL); /* length check */ if (DUK_HBUFOBJ_VALID_BYTEOFFSET_EXCL(h_bufarg, target_ustart + copy_size) && DUK_HBUFOBJ_VALID_BYTEOFFSET_EXCL(h_this, source_ustart + copy_size)) { /* Must use memmove() because copy area may overlap (source and target * buffer may be the same, or from different slices. */ duk_memmove_unsafe((void *) (DUK_HBUFOBJ_GET_SLICE_BASE(thr->heap, h_bufarg) + target_ustart), (const void *) (DUK_HBUFOBJ_GET_SLICE_BASE(thr->heap, h_this) + source_ustart), (size_t) copy_size); } else { DUK_DDD(DUK_DDDPRINT("buffer copy not covered by underlying buffer(s), ignoring")); } silent_ignore: /* Return value is like write(), number of bytes written. * The return value matters because of code like: * "off += buf.copy(...)". */ duk_push_uint(thr, copy_size); return 1; fail_bounds: DUK_DCERROR_RANGE_INVALID_ARGS(thr); } #endif /* DUK_USE_BUFFEROBJECT_SUPPORT */ /* * TypedArray.prototype.set() * * TypedArray set() is pretty interesting to implement because: * * - The source argument may be a plain array or a typedarray. If the * source is a TypedArray, values are decoded and re-encoded into the * target (not as a plain byte copy). This may happen even when the * element byte size is the same, e.g. integer values may be re-encoded * into floats. * * - Source and target may refer to the same underlying buffer, so that * the set() operation may overlap. The specification requires that this * must work as if a copy was made before the operation. Note that this * is NOT a simple memmove() situation because the source and target * byte sizes may be different -- e.g. a 4-byte source (Int8Array) may * expand to a 16-byte target (Uint32Array) so that the target overlaps * the source both from beginning and the end (unlike in typical memmove). * * - Even if 'buf' pointers of the source and target differ, there's no * guarantee that their memory areas don't overlap. This may be the * case with external buffers. * * Even so, it is nice to optimize for the common case: * * - Source and target separate buffers or non-overlapping. * * - Source and target have a compatible type so that a plain byte copy * is possible. Note that while e.g. uint8 and int8 are compatible * (coercion one way or another doesn't change the byte representation), * e.g. int8 and uint8clamped are NOT compatible when writing int8 * values into uint8clamped typedarray (-1 would clamp to 0 for instance). * * See test-bi-typedarray-proto-set.js. */ #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) DUK_INTERNAL duk_ret_t duk_bi_typedarray_set(duk_hthread *thr) { duk_hbufobj *h_this; duk_hobject *h_obj; duk_uarridx_t i, n; duk_int_t offset_signed; duk_uint_t offset_elems; duk_uint_t offset_bytes; h_this = duk__require_bufobj_this(thr); DUK_ASSERT(h_this != NULL); DUK_HBUFOBJ_ASSERT_VALID(h_this); if (h_this->buf == NULL) { DUK_DDD(DUK_DDDPRINT("source neutered, skip copy")); return 0; } duk_hbufobj_promote_plain(thr, 0); h_obj = duk_require_hobject(thr, 0); /* XXX: V8 throws a TypeError for negative values. Would it * be more useful to interpret negative offsets here from the * end of the buffer too? */ offset_signed = duk_to_int(thr, 1); if (offset_signed < 0) { /* For some reason this is a TypeError (at least in V8). */ DUK_DCERROR_TYPE_INVALID_ARGS(thr); } offset_elems = (duk_uint_t) offset_signed; offset_bytes = offset_elems << h_this->shift; if ((offset_bytes >> h_this->shift) != offset_elems) { /* Byte length would overflow. */ /* XXX: easier check with less code? */ goto fail_args; } if (offset_bytes > h_this->length) { /* Equality may be OK but >length not. Checking * this explicitly avoids some overflow cases * below. */ goto fail_args; } DUK_ASSERT(offset_bytes <= h_this->length); /* Fast path: source is a TypedArray (or any bufobj). */ if (DUK_HOBJECT_IS_BUFOBJ(h_obj)) { duk_hbufobj *h_bufarg; #if !defined(DUK_USE_PREFER_SIZE) duk_uint16_t comp_mask; #endif duk_small_int_t no_overlap = 0; duk_uint_t src_length; duk_uint_t dst_length; duk_uint_t dst_length_elems; duk_uint8_t *p_src_base; duk_uint8_t *p_src_end; duk_uint8_t *p_src; duk_uint8_t *p_dst_base; duk_uint8_t *p_dst; duk_small_uint_t src_elem_size; duk_small_uint_t dst_elem_size; h_bufarg = (duk_hbufobj *) h_obj; DUK_HBUFOBJ_ASSERT_VALID(h_bufarg); if (h_bufarg->buf == NULL) { DUK_DDD(DUK_DDDPRINT("target neutered, skip copy")); return 0; } /* Nominal size check. */ src_length = h_bufarg->length; /* bytes in source */ dst_length_elems = (src_length >> h_bufarg->shift); /* elems in source and dest */ dst_length = dst_length_elems << h_this->shift; /* bytes in dest */ if ((dst_length >> h_this->shift) != dst_length_elems) { /* Byte length would overflow. */ /* XXX: easier check with less code? */ goto fail_args; } DUK_DDD(DUK_DDDPRINT("nominal size check: src_length=%ld, dst_length=%ld", (long) src_length, (long) dst_length)); DUK_ASSERT(offset_bytes <= h_this->length); if (dst_length > h_this->length - offset_bytes) { /* Overflow not an issue because subtraction is used on the right * side and guaranteed to be >= 0. */ DUK_DDD(DUK_DDDPRINT("copy exceeds target buffer nominal length")); goto fail_args; } if (!DUK_HBUFOBJ_VALID_BYTEOFFSET_EXCL(h_this, offset_bytes + dst_length)) { DUK_DDD(DUK_DDDPRINT("copy not covered by underlying target buffer, ignore")); return 0; } p_src_base = DUK_HBUFOBJ_GET_SLICE_BASE(thr->heap, h_bufarg); p_dst_base = DUK_HBUFOBJ_GET_SLICE_BASE(thr->heap, h_this) + offset_bytes; /* Check actual underlying buffers for validity and that they * cover the copy. No side effects are allowed after the check * so that the validity status doesn't change. */ if (!DUK_HBUFOBJ_VALID_SLICE(h_this) || !DUK_HBUFOBJ_VALID_SLICE(h_bufarg)) { /* The condition could be more narrow and check for the * copy area only, but there's no need for fine grained * behavior when the underlying buffer is misconfigured. */ DUK_DDD(DUK_DDDPRINT("source and/or target not covered by underlying buffer, skip copy")); return 0; } /* We want to do a straight memory copy if possible: this is * an important operation because .set() is the TypedArray * way to copy chunks of memory. However, because set() * conceptually works in terms of elements, not all views are * compatible with direct byte copying. * * If we do manage a direct copy, the "overlap issue" handled * below can just be solved using memmove() because the source * and destination element sizes are necessarily equal. */ #if !defined(DUK_USE_PREFER_SIZE) DUK_ASSERT(h_this->elem_type < sizeof(duk__buffer_elemtype_copy_compatible) / sizeof(duk_uint16_t)); comp_mask = duk__buffer_elemtype_copy_compatible[h_this->elem_type]; if (comp_mask & (1 << h_bufarg->elem_type)) { DUK_ASSERT(src_length == dst_length); DUK_DDD(DUK_DDDPRINT("fast path: able to use memmove() because views are compatible")); duk_memmove_unsafe((void *) p_dst_base, (const void *) p_src_base, (size_t) dst_length); return 0; } DUK_DDD(DUK_DDDPRINT("fast path: views are not compatible with a byte copy, copy by item")); #endif /* !DUK_USE_PREFER_SIZE */ /* We want to avoid making a copy to process set() but that's * not always possible: the source and the target may overlap * and because element sizes are different, the overlap cannot * always be handled with a memmove() or choosing the copy * direction in a certain way. For example, if source type is * uint8 and target type is uint32, the target area may exceed * the source area from both ends! * * Note that because external buffers may point to the same * memory areas, we must ultimately make this check using * pointers. * * NOTE: careful with side effects: any side effect may cause * a buffer resize (or external buffer pointer/length update)! */ DUK_DDD(DUK_DDDPRINT("overlap check: p_src_base=%p, src_length=%ld, " "p_dst_base=%p, dst_length=%ld", (void *) p_src_base, (long) src_length, (void *) p_dst_base, (long) dst_length)); if (p_src_base >= p_dst_base + dst_length || /* source starts after dest ends */ p_src_base + src_length <= p_dst_base) { /* source ends before dest starts */ no_overlap = 1; } if (!no_overlap) { /* There's overlap: the desired end result is that * conceptually a copy is made to avoid "trampling" * of source data by destination writes. We make * an actual temporary copy to handle this case. */ duk_uint8_t *p_src_copy; DUK_DDD(DUK_DDDPRINT("there is overlap, make a copy of the source")); p_src_copy = (duk_uint8_t *) duk_push_fixed_buffer_nozero(thr, src_length); DUK_ASSERT(p_src_copy != NULL); duk_memcpy_unsafe((void *) p_src_copy, (const void *) p_src_base, (size_t) src_length); p_src_base = p_src_copy; /* use p_src_base from now on */ } /* Value stack intentionally mixed size here. */ DUK_DDD(DUK_DDDPRINT("after overlap check: p_src_base=%p, src_length=%ld, " "p_dst_base=%p, dst_length=%ld, valstack top=%ld", (void *) p_src_base, (long) src_length, (void *) p_dst_base, (long) dst_length, (long) duk_get_top(thr))); /* Ready to make the copy. We must proceed element by element * and must avoid any side effects that might cause the buffer * validity check above to become invalid. * * Although we work through the value stack here, only plain * numbers are handled which should be side effect safe. */ src_elem_size = (duk_small_uint_t) (1U << h_bufarg->shift); dst_elem_size = (duk_small_uint_t) (1U << h_this->shift); p_src = p_src_base; p_dst = p_dst_base; p_src_end = p_src_base + src_length; while (p_src != p_src_end) { DUK_DDD(DUK_DDDPRINT("fast path per element copy loop: " "p_src=%p, p_src_end=%p, p_dst=%p", (void *) p_src, (void *) p_src_end, (void *) p_dst)); /* A validated read() is always a number, so it's write coercion * is always side effect free an won't invalidate pointers etc. */ duk_hbufobj_push_validated_read(thr, h_bufarg, p_src, src_elem_size); duk_hbufobj_validated_write(thr, h_this, p_dst, dst_elem_size); duk_pop(thr); p_src += src_elem_size; p_dst += dst_elem_size; } return 0; } else { /* Slow path: quite slow, but we save space by using the property code * to write coerce target values. We don't need to worry about overlap * here because the source is not a TypedArray. * * We could use the bufobj write coercion helper but since the * property read may have arbitrary side effects, full validity checks * would be needed for every element anyway. */ n = (duk_uarridx_t) duk_get_length(thr, 0); DUK_ASSERT(offset_bytes <= h_this->length); if ((n << h_this->shift) > h_this->length - offset_bytes) { /* Overflow not an issue because subtraction is used on the right * side and guaranteed to be >= 0. */ DUK_DDD(DUK_DDDPRINT("copy exceeds target buffer nominal length")); goto fail_args; } /* There's no need to check for buffer validity status for the * target here: the property access code will do that for each * element. Moreover, if we did check the validity here, side * effects from reading the source argument might invalidate * the results anyway. */ DUK_ASSERT_TOP(thr, 2); duk_push_this(thr); for (i = 0; i < n; i++) { duk_get_prop_index(thr, 0, i); duk_put_prop_index(thr, 2, offset_elems + i); } } return 0; fail_args: DUK_DCERROR_RANGE_INVALID_ARGS(thr); } #endif /* DUK_USE_BUFFEROBJECT_SUPPORT */ /* * Node.js Buffer.prototype.slice([start], [end]) * ArrayBuffer.prototype.slice(begin, [end]) * TypedArray.prototype.subarray(begin, [end]) * * The API calls are almost identical; negative indices are counted from end * of buffer, and final indices are clamped (allowing crossed indices). Main * differences: * * - Copy/view behavior; Node.js .slice() and TypedArray .subarray() create * views, ArrayBuffer .slice() creates a copy * * - Resulting object has a different class and prototype depending on the * call (or 'this' argument) * * - TypedArray .subarray() arguments are element indices, not byte offsets * * - Plain buffer argument creates a plain buffer slice */ #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) DUK_LOCAL void duk__arraybuffer_plain_slice(duk_hthread *thr, duk_hbuffer *h_val) { duk_int_t start_offset, end_offset; duk_uint_t slice_length; duk_uint8_t *p_copy; duk_size_t copy_length; duk__clamp_startend_negidx_shifted(thr, (duk_int_t) DUK_HBUFFER_GET_SIZE(h_val), 0 /*buffer_shift*/, 0 /*idx_start*/, 1 /*idx_end*/, &start_offset, &end_offset); DUK_ASSERT(end_offset <= (duk_int_t) DUK_HBUFFER_GET_SIZE(h_val)); DUK_ASSERT(start_offset >= 0); DUK_ASSERT(end_offset >= start_offset); slice_length = (duk_uint_t) (end_offset - start_offset); p_copy = (duk_uint8_t *) duk_push_fixed_buffer_nozero(thr, (duk_size_t) slice_length); DUK_ASSERT(p_copy != NULL); copy_length = slice_length; duk_memcpy_unsafe((void *) p_copy, (const void *) ((duk_uint8_t *) DUK_HBUFFER_GET_DATA_PTR(thr->heap, h_val) + start_offset), copy_length); } #endif /* DUK_USE_BUFFEROBJECT_SUPPORT */ #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) /* Shared helper for slice/subarray operation. * Magic: 0x01=isView, 0x02=copy, 0x04=Node.js Buffer special handling. */ DUK_INTERNAL duk_ret_t duk_bi_buffer_slice_shared(duk_hthread *thr) { duk_small_int_t magic; duk_small_uint_t res_class_num; duk_small_int_t res_proto_bidx; duk_hbufobj *h_this; duk_hbufobj *h_bufobj; duk_hbuffer *h_val; duk_int_t start_offset, end_offset; duk_uint_t slice_length; duk_tval *tv; /* [ start end ] */ magic = duk_get_current_magic(thr); tv = duk_get_borrowed_this_tval(thr); DUK_ASSERT(tv != NULL); if (DUK_TVAL_IS_BUFFER(tv)) { /* For plain buffers return a plain buffer slice. */ h_val = DUK_TVAL_GET_BUFFER(tv); DUK_ASSERT(h_val != NULL); if (magic & 0x02) { /* Make copy: ArrayBuffer.prototype.slice() uses this. */ duk__arraybuffer_plain_slice(thr, h_val); return 1; } else { /* View into existing buffer: cannot be done if the * result is a plain buffer because there's no slice * info. So return an ArrayBuffer instance; coerce * the 'this' binding into an object and behave as if * the original call was for an Object-coerced plain * buffer (handled automatically by duk__require_bufobj_this()). */ DUK_DDD(DUK_DDDPRINT("slice() doesn't handle view into plain buffer, coerce 'this' to ArrayBuffer object")); /* fall through */ } } tv = NULL; /* No longer valid nor needed. */ h_this = duk__require_bufobj_this(thr); /* Slice offsets are element (not byte) offsets, which only matters * for TypedArray views, Node.js Buffer and ArrayBuffer have shift * zero so byte and element offsets are the same. Negative indices * are counted from end of slice, crossed indices are allowed (and * result in zero length result), and final values are clamped * against the current slice. There's intentionally no check * against the underlying buffer here. */ duk__clamp_startend_negidx_shifted(thr, (duk_int_t) h_this->length, (duk_uint8_t) h_this->shift, 0 /*idx_start*/, 1 /*idx_end*/, &start_offset, &end_offset); DUK_ASSERT(end_offset >= start_offset); DUK_ASSERT(start_offset >= 0); DUK_ASSERT(end_offset >= 0); slice_length = (duk_uint_t) (end_offset - start_offset); /* The resulting buffer object gets the same class and prototype as * the buffer in 'this', e.g. if the input is a Uint8Array the * result is a Uint8Array; if the input is a Float32Array, the * result is a Float32Array. The result internal prototype should * be the default prototype for the class (e.g. initial value of * Uint8Array.prototype), not copied from the argument (Duktape 1.x * did that). * * Node.js Buffers have special handling: they're Uint8Arrays as far * as the internal class is concerned, so the new Buffer should also * be an Uint8Array but inherit from Buffer.prototype. */ res_class_num = DUK_HOBJECT_GET_CLASS_NUMBER((duk_hobject *) h_this); DUK_ASSERT(res_class_num >= DUK_HOBJECT_CLASS_BUFOBJ_MIN); /* type check guarantees */ DUK_ASSERT(res_class_num <= DUK_HOBJECT_CLASS_BUFOBJ_MAX); res_proto_bidx = duk__buffer_proto_from_classnum[res_class_num - DUK_HOBJECT_CLASS_BUFOBJ_MIN]; if (magic & 0x04) { res_proto_bidx = DUK_BIDX_NODEJS_BUFFER_PROTOTYPE; } h_bufobj = duk_push_bufobj_raw(thr, DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_FLAG_BUFOBJ | DUK_HOBJECT_CLASS_AS_FLAGS(res_class_num), res_proto_bidx); DUK_ASSERT(h_bufobj != NULL); DUK_ASSERT(h_bufobj->length == 0); h_bufobj->shift = h_this->shift; /* inherit */ h_bufobj->elem_type = h_this->elem_type; /* inherit */ h_bufobj->is_typedarray = magic & 0x01; DUK_ASSERT(h_bufobj->is_typedarray == 0 || h_bufobj->is_typedarray == 1); h_val = h_this->buf; if (h_val == NULL) { DUK_DCERROR_TYPE_INVALID_ARGS(thr); } if (magic & 0x02) { /* non-zero: make copy */ duk_uint8_t *p_copy; duk_size_t copy_length; p_copy = (duk_uint8_t *) duk_push_fixed_buffer_zero( thr, (duk_size_t) slice_length); /* must be zeroed, not all bytes always copied */ DUK_ASSERT(p_copy != NULL); /* Copy slice, respecting underlying buffer limits; remainder * is left as zero. */ copy_length = DUK_HBUFOBJ_CLAMP_BYTELENGTH(h_this, slice_length); duk_memcpy_unsafe((void *) p_copy, (const void *) (DUK_HBUFOBJ_GET_SLICE_BASE(thr->heap, h_this) + start_offset), copy_length); h_val = duk_known_hbuffer(thr, -1); h_bufobj->buf = h_val; DUK_HBUFFER_INCREF(thr, h_val); h_bufobj->length = slice_length; DUK_ASSERT(h_bufobj->offset == 0); duk_pop(thr); /* reachable so pop OK */ } else { h_bufobj->buf = h_val; DUK_HBUFFER_INCREF(thr, h_val); h_bufobj->length = slice_length; h_bufobj->offset = h_this->offset + (duk_uint_t) start_offset; /* Copy the .buffer property, needed for TypedArray.prototype.subarray(). * * XXX: limit copy only for TypedArray classes specifically? */ DUK_ASSERT(h_bufobj->buf_prop == NULL); h_bufobj->buf_prop = h_this->buf_prop; /* may be NULL */ DUK_HOBJECT_INCREF_ALLOWNULL(thr, (duk_hobject *) h_bufobj->buf_prop); } /* unbalanced stack on purpose */ DUK_HBUFOBJ_ASSERT_VALID(h_bufobj); return 1; } #endif /* DUK_USE_BUFFEROBJECT_SUPPORT */ /* * Node.js Buffer.isEncoding() */ #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) DUK_INTERNAL duk_ret_t duk_bi_nodejs_buffer_is_encoding(duk_hthread *thr) { const char *encoding; /* only accept lowercase 'utf8' now. */ encoding = duk_to_string(thr, 0); DUK_ASSERT(duk_is_string(thr, 0)); /* guaranteed by duk_to_string() */ duk_push_boolean(thr, DUK_STRCMP(encoding, "utf8") == 0); return 1; } #endif /* DUK_USE_BUFFEROBJECT_SUPPORT */ /* * Node.js Buffer.isBuffer() */ #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) DUK_INTERNAL duk_ret_t duk_bi_nodejs_buffer_is_buffer(duk_hthread *thr) { duk_hobject *h; duk_hobject *h_proto; duk_bool_t ret = 0; DUK_ASSERT(duk_get_top(thr) >= 1); /* nargs */ h = duk_get_hobject(thr, 0); if (h != NULL) { h_proto = thr->builtins[DUK_BIDX_NODEJS_BUFFER_PROTOTYPE]; DUK_ASSERT(h_proto != NULL); h = DUK_HOBJECT_GET_PROTOTYPE(thr->heap, h); if (h != NULL) { ret = duk_hobject_prototype_chain_contains(thr, h, h_proto, 0 /*ignore_loop*/); } } duk_push_boolean(thr, ret); return 1; } #endif /* DUK_USE_BUFFEROBJECT_SUPPORT */ /* * Node.js Buffer.byteLength() */ #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) DUK_INTERNAL duk_ret_t duk_bi_nodejs_buffer_byte_length(duk_hthread *thr) { const char *str; duk_size_t len; /* At the moment Buffer(<str>) will just use the string bytes as * is (ignoring encoding), so we return the string length here * unconditionally. */ /* XXX: to be revised; Old Node.js behavior just coerces any buffer * values to string: * $ node * > Buffer.byteLength(new Uint32Array(10)) * 20 * > Buffer.byteLength(new Uint32Array(100)) * 20 * (The 20 comes from '[object Uint32Array]'.length */ str = duk_to_lstring(thr, 0, &len); DUK_UNREF(str); duk_push_size_t(thr, len); return 1; } #endif /* DUK_USE_BUFFEROBJECT_SUPPORT */ /* * Node.js Buffer.concat() */ #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) DUK_INTERNAL duk_ret_t duk_bi_nodejs_buffer_concat(duk_hthread *thr) { duk_hobject *h_arg; duk_uint_t total_length; duk_hbufobj *h_bufobj; duk_hbufobj *h_bufres; duk_hbuffer *h_val; duk_uint_t i, n; duk_uint8_t *p; duk_size_t space_left; duk_size_t copy_size; /* Node.js accepts only actual Arrays. */ h_arg = duk_require_hobject(thr, 0); if (DUK_HOBJECT_GET_CLASS_NUMBER(h_arg) != DUK_HOBJECT_CLASS_ARRAY) { DUK_DCERROR_TYPE_INVALID_ARGS(thr); } /* Compute result length and validate argument buffers. */ n = (duk_uint_t) duk_get_length(thr, 0); total_length = 0; for (i = 0; i < n; i++) { /* Neutered checks not necessary here: neutered buffers have * zero 'length' so we'll effectively skip them. */ DUK_ASSERT_TOP(thr, 2); /* [ array totalLength ] */ duk_get_prop_index(thr, 0, (duk_uarridx_t) i); /* -> [ array totalLength buf ] */ h_bufobj = duk__require_bufobj_value(thr, 2); DUK_ASSERT(h_bufobj != NULL); total_length += h_bufobj->length; if (DUK_UNLIKELY(total_length < h_bufobj->length)) { DUK_DCERROR_RANGE_INVALID_ARGS(thr); /* Wrapped. */ } duk_pop(thr); } /* In Node.js v0.12.1 a 1-element array is special and won't create a * copy, this was fixed later so an explicit check no longer needed. */ /* User totalLength overrides a computed length, but we'll check * every copy in the copy loop. Note that duk_to_int() can * technically have arbitrary side effects so we need to recheck * the buffers in the copy loop. */ if (!duk_is_undefined(thr, 1) && n > 0) { /* For n == 0, Node.js ignores totalLength argument and * returns a zero length buffer. */ duk_int_t total_length_signed; total_length_signed = duk_to_int(thr, 1); if (total_length_signed < 0) { DUK_DCERROR_RANGE_INVALID_ARGS(thr); } total_length = (duk_uint_t) total_length_signed; } h_bufres = duk_push_bufobj_raw(thr, DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_FLAG_BUFOBJ | DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_UINT8ARRAY), DUK_BIDX_NODEJS_BUFFER_PROTOTYPE); DUK_ASSERT(h_bufres != NULL); p = (duk_uint8_t *) duk_push_fixed_buffer_zero(thr, total_length); /* must be zeroed, all bytes not necessarily written over */ DUK_ASSERT(p != NULL); space_left = (duk_size_t) total_length; for (i = 0; i < n; i++) { DUK_ASSERT_TOP(thr, 4); /* [ array totalLength bufres buf ] */ duk_get_prop_index(thr, 0, (duk_uarridx_t) i); h_bufobj = duk__require_bufobj_value(thr, 4); DUK_ASSERT(h_bufobj != NULL); copy_size = h_bufobj->length; if (copy_size > space_left) { copy_size = space_left; } if (h_bufobj->buf != NULL && DUK_HBUFOBJ_VALID_SLICE(h_bufobj)) { duk_memcpy_unsafe((void *) p, (const void *) DUK_HBUFOBJ_GET_SLICE_BASE(thr->heap, h_bufobj), copy_size); } else { /* Just skip, leaving zeroes in the result. */ ; } p += copy_size; space_left -= copy_size; duk_pop(thr); } h_val = duk_known_hbuffer(thr, -1); duk__set_bufobj_buffer(thr, h_bufres, h_val); h_bufres->is_typedarray = 1; DUK_HBUFOBJ_ASSERT_VALID(h_bufres); duk_pop(thr); /* pop plain buffer, now reachable through h_bufres */ return 1; /* return h_bufres */ } #endif /* DUK_USE_BUFFEROBJECT_SUPPORT */ /* * Shared readfield and writefield methods * * The readfield/writefield methods need support for endianness and field * types. All offsets are byte based so no offset shifting is needed. */ #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) /* Format of magic, bits: * 0...1: field type; 0=uint8, 1=uint16, 2=uint32, 3=float, 4=double, 5=unused, 6=unused, 7=unused * 3: endianness: 0=little, 1=big * 4: signed: 1=yes, 0=no * 5: typedarray: 1=yes, 0=no */ #define DUK__FLD_8BIT 0 #define DUK__FLD_16BIT 1 #define DUK__FLD_32BIT 2 #define DUK__FLD_FLOAT 3 #define DUK__FLD_DOUBLE 4 #define DUK__FLD_VARINT 5 #define DUK__FLD_BIGENDIAN (1 << 3) #define DUK__FLD_SIGNED (1 << 4) #define DUK__FLD_TYPEDARRAY (1 << 5) /* XXX: split into separate functions for each field type? */ DUK_INTERNAL duk_ret_t duk_bi_buffer_readfield(duk_hthread *thr) { duk_small_uint_t magic = (duk_small_uint_t) duk_get_current_magic(thr); duk_small_uint_t magic_ftype; duk_small_uint_t magic_bigendian; duk_small_uint_t magic_signed; duk_small_uint_t magic_typedarray; duk_small_uint_t endswap; duk_hbufobj *h_this; duk_bool_t no_assert; duk_int_t offset_signed; duk_uint_t offset; duk_uint_t buffer_length; duk_uint_t check_length; duk_uint8_t *buf; duk_double_union du; magic_ftype = magic & 0x0007U; magic_bigendian = magic & 0x0008U; magic_signed = magic & 0x0010U; magic_typedarray = magic & 0x0020U; h_this = duk__require_bufobj_this(thr); /* XXX: very inefficient for plain buffers */ DUK_ASSERT(h_this != NULL); buffer_length = h_this->length; /* [ offset noAssert ], when ftype != DUK__FLD_VARINT */ /* [ offset fieldByteLength noAssert ], when ftype == DUK__FLD_VARINT */ /* [ offset littleEndian ], when DUK__FLD_TYPEDARRAY (regardless of ftype) */ /* Handle TypedArray vs. Node.js Buffer arg differences */ if (magic_typedarray) { no_assert = 0; #if defined(DUK_USE_INTEGER_LE) endswap = !duk_to_boolean(thr, 1); /* 1=little endian */ #else endswap = duk_to_boolean(thr, 1); /* 1=little endian */ #endif } else { no_assert = duk_to_boolean(thr, (magic_ftype == DUK__FLD_VARINT) ? 2 : 1); #if defined(DUK_USE_INTEGER_LE) endswap = magic_bigendian; #else endswap = !magic_bigendian; #endif } /* Offset is coerced first to signed integer range and then to unsigned. * This ensures we can add a small byte length (1-8) to the offset in * bound checks and not wrap. */ offset_signed = duk_to_int(thr, 0); offset = (duk_uint_t) offset_signed; if (offset_signed < 0) { goto fail_bounds; } DUK_DDD(DUK_DDDPRINT("readfield, buffer_length=%ld, offset=%ld, no_assert=%d, " "magic=%04x, magic_fieldtype=%d, magic_bigendian=%d, magic_signed=%d, " "endswap=%u", (long) buffer_length, (long) offset, (int) no_assert, (unsigned int) magic, (int) magic_ftype, (int) (magic_bigendian >> 3), (int) (magic_signed >> 4), (int) endswap)); /* Update 'buffer_length' to be the effective, safe limit which * takes into account the underlying buffer. This value will be * potentially invalidated by any side effect. */ check_length = DUK_HBUFOBJ_CLAMP_BYTELENGTH(h_this, buffer_length); DUK_DDD(DUK_DDDPRINT("buffer_length=%ld, check_length=%ld", (long) buffer_length, (long) check_length)); if (h_this->buf) { buf = DUK_HBUFOBJ_GET_SLICE_BASE(thr->heap, h_this); } else { /* Neutered. We could go into the switch-case safely with * buf == NULL because check_length == 0. To avoid scanbuild * warnings, fail directly instead. */ DUK_ASSERT(check_length == 0); goto fail_neutered; } DUK_ASSERT(buf != NULL); switch (magic_ftype) { case DUK__FLD_8BIT: { duk_uint8_t tmp; if (offset + 1U > check_length) { goto fail_bounds; } tmp = buf[offset]; if (magic_signed) { duk_push_int(thr, (duk_int_t) ((duk_int8_t) tmp)); } else { duk_push_uint(thr, (duk_uint_t) tmp); } break; } case DUK__FLD_16BIT: { duk_uint16_t tmp; if (offset + 2U > check_length) { goto fail_bounds; } duk_memcpy((void *) du.uc, (const void *) (buf + offset), 2); tmp = du.us[0]; if (endswap) { tmp = DUK_BSWAP16(tmp); } if (magic_signed) { duk_push_int(thr, (duk_int_t) ((duk_int16_t) tmp)); } else { duk_push_uint(thr, (duk_uint_t) tmp); } break; } case DUK__FLD_32BIT: { duk_uint32_t tmp; if (offset + 4U > check_length) { goto fail_bounds; } duk_memcpy((void *) du.uc, (const void *) (buf + offset), 4); tmp = du.ui[0]; if (endswap) { tmp = DUK_BSWAP32(tmp); } if (magic_signed) { duk_push_int(thr, (duk_int_t) ((duk_int32_t) tmp)); } else { duk_push_uint(thr, (duk_uint_t) tmp); } break; } case DUK__FLD_FLOAT: { duk_uint32_t tmp; if (offset + 4U > check_length) { goto fail_bounds; } duk_memcpy((void *) du.uc, (const void *) (buf + offset), 4); if (endswap) { tmp = du.ui[0]; tmp = DUK_BSWAP32(tmp); du.ui[0] = tmp; } duk_push_number(thr, (duk_double_t) du.f[0]); break; } case DUK__FLD_DOUBLE: { if (offset + 8U > check_length) { goto fail_bounds; } duk_memcpy((void *) du.uc, (const void *) (buf + offset), 8); if (endswap) { DUK_DBLUNION_BSWAP64(&du); } duk_push_number(thr, (duk_double_t) du.d); break; } case DUK__FLD_VARINT: { /* Node.js Buffer variable width integer field. We don't really * care about speed here, so aim for shortest algorithm. */ duk_int_t field_bytelen; duk_int_t i, i_step, i_end; #if defined(DUK_USE_64BIT_OPS) duk_int64_t tmp; duk_small_uint_t shift_tmp; #else duk_double_t tmp; duk_small_int_t highbyte; #endif const duk_uint8_t *p; field_bytelen = duk_get_int(thr, 1); /* avoid side effects! */ if (field_bytelen < 1 || field_bytelen > 6) { goto fail_field_length; } if (offset + (duk_uint_t) field_bytelen > check_length) { goto fail_bounds; } p = (const duk_uint8_t *) (buf + offset); /* Slow gathering of value using either 64-bit arithmetic * or IEEE doubles if 64-bit types not available. Handling * of negative numbers is a bit non-obvious in both cases. */ if (magic_bigendian) { /* Gather in big endian */ i = 0; i_step = 1; i_end = field_bytelen; /* one i_step over */ } else { /* Gather in little endian */ i = field_bytelen - 1; i_step = -1; i_end = -1; /* one i_step over */ } #if defined(DUK_USE_64BIT_OPS) tmp = 0; do { DUK_ASSERT(i >= 0 && i < field_bytelen); tmp = (tmp << 8) + (duk_int64_t) p[i]; i += i_step; } while (i != i_end); if (magic_signed) { /* Shift to sign extend. Left shift must be unsigned * to avoid undefined behavior; right shift must be * signed to sign extend properly. */ shift_tmp = (duk_small_uint_t) (64U - (duk_small_uint_t) field_bytelen * 8U); tmp = (duk_int64_t) ((duk_uint64_t) tmp << shift_tmp) >> shift_tmp; } duk_push_i64(thr, tmp); #else highbyte = p[i]; if (magic_signed && (highbyte & 0x80) != 0) { /* 0xff => 255 - 256 = -1; 0x80 => 128 - 256 = -128 */ tmp = (duk_double_t) (highbyte - 256); } else { tmp = (duk_double_t) highbyte; } for (;;) { i += i_step; if (i == i_end) { break; } DUK_ASSERT(i >= 0 && i < field_bytelen); tmp = (tmp * 256.0) + (duk_double_t) p[i]; } duk_push_number(thr, tmp); #endif break; } default: { /* should never happen but default here */ goto fail_bounds; } } return 1; fail_neutered: fail_field_length: fail_bounds: if (no_assert) { /* Node.js return value for noAssert out-of-bounds reads is * usually (but not always) NaN. Return NaN consistently. */ duk_push_nan(thr); return 1; } DUK_DCERROR_RANGE_INVALID_ARGS(thr); } #endif /* DUK_USE_BUFFEROBJECT_SUPPORT */ #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) /* XXX: split into separate functions for each field type? */ DUK_INTERNAL duk_ret_t duk_bi_buffer_writefield(duk_hthread *thr) { duk_small_uint_t magic = (duk_small_uint_t) duk_get_current_magic(thr); duk_small_uint_t magic_ftype; duk_small_uint_t magic_bigendian; duk_small_uint_t magic_signed; duk_small_uint_t magic_typedarray; duk_small_uint_t endswap; duk_hbufobj *h_this; duk_bool_t no_assert; duk_int_t offset_signed; duk_uint_t offset; duk_uint_t buffer_length; duk_uint_t check_length; duk_uint8_t *buf; duk_double_union du; duk_int_t nbytes = 0; magic_ftype = magic & 0x0007U; magic_bigendian = magic & 0x0008U; magic_signed = magic & 0x0010U; magic_typedarray = magic & 0x0020U; DUK_UNREF(magic_signed); h_this = duk__require_bufobj_this(thr); /* XXX: very inefficient for plain buffers */ DUK_ASSERT(h_this != NULL); buffer_length = h_this->length; /* [ value offset noAssert ], when ftype != DUK__FLD_VARINT */ /* [ value offset fieldByteLength noAssert ], when ftype == DUK__FLD_VARINT */ /* [ offset value littleEndian ], when DUK__FLD_TYPEDARRAY (regardless of ftype) */ /* Handle TypedArray vs. Node.js Buffer arg differences */ if (magic_typedarray) { no_assert = 0; #if defined(DUK_USE_INTEGER_LE) endswap = !duk_to_boolean(thr, 2); /* 1=little endian */ #else endswap = duk_to_boolean(thr, 2); /* 1=little endian */ #endif duk_swap(thr, 0, 1); /* offset/value order different from Node.js */ } else { no_assert = duk_to_boolean(thr, (magic_ftype == DUK__FLD_VARINT) ? 3 : 2); #if defined(DUK_USE_INTEGER_LE) endswap = magic_bigendian; #else endswap = !magic_bigendian; #endif } /* Offset is coerced first to signed integer range and then to unsigned. * This ensures we can add a small byte length (1-8) to the offset in * bound checks and not wrap. */ offset_signed = duk_to_int(thr, 1); offset = (duk_uint_t) offset_signed; /* We need 'nbytes' even for a failed offset; return value must be * (offset + nbytes) even when write fails due to invalid offset. */ if (magic_ftype != DUK__FLD_VARINT) { DUK_ASSERT(magic_ftype < (duk_small_uint_t) (sizeof(duk__buffer_nbytes_from_fldtype) / sizeof(duk_uint8_t))); nbytes = duk__buffer_nbytes_from_fldtype[magic_ftype]; } else { nbytes = duk_get_int(thr, 2); if (nbytes < 1 || nbytes > 6) { goto fail_field_length; } } DUK_ASSERT(nbytes >= 1 && nbytes <= 8); /* Now we can check offset validity. */ if (offset_signed < 0) { goto fail_bounds; } DUK_DDD(DUK_DDDPRINT("writefield, value=%!T, buffer_length=%ld, offset=%ld, no_assert=%d, " "magic=%04x, magic_fieldtype=%d, magic_bigendian=%d, magic_signed=%d, " "endswap=%u", duk_get_tval(thr, 0), (long) buffer_length, (long) offset, (int) no_assert, (unsigned int) magic, (int) magic_ftype, (int) (magic_bigendian >> 3), (int) (magic_signed >> 4), (int) endswap)); /* Coerce value to a number before computing check_length, so that * the field type specific coercion below can't have side effects * that would invalidate check_length. */ duk_to_number(thr, 0); /* Update 'buffer_length' to be the effective, safe limit which * takes into account the underlying buffer. This value will be * potentially invalidated by any side effect. */ check_length = DUK_HBUFOBJ_CLAMP_BYTELENGTH(h_this, buffer_length); DUK_DDD(DUK_DDDPRINT("buffer_length=%ld, check_length=%ld", (long) buffer_length, (long) check_length)); if (h_this->buf) { buf = DUK_HBUFOBJ_GET_SLICE_BASE(thr->heap, h_this); } else { /* Neutered. We could go into the switch-case safely with * buf == NULL because check_length == 0. To avoid scanbuild * warnings, fail directly instead. */ DUK_ASSERT(check_length == 0); goto fail_neutered; } DUK_ASSERT(buf != NULL); switch (magic_ftype) { case DUK__FLD_8BIT: { if (offset + 1U > check_length) { goto fail_bounds; } /* sign doesn't matter when writing */ buf[offset] = (duk_uint8_t) duk_to_uint32(thr, 0); break; } case DUK__FLD_16BIT: { duk_uint16_t tmp; if (offset + 2U > check_length) { goto fail_bounds; } tmp = (duk_uint16_t) duk_to_uint32(thr, 0); if (endswap) { tmp = DUK_BSWAP16(tmp); } du.us[0] = tmp; /* sign doesn't matter when writing */ duk_memcpy((void *) (buf + offset), (const void *) du.uc, 2); break; } case DUK__FLD_32BIT: { duk_uint32_t tmp; if (offset + 4U > check_length) { goto fail_bounds; } tmp = (duk_uint32_t) duk_to_uint32(thr, 0); if (endswap) { tmp = DUK_BSWAP32(tmp); } du.ui[0] = tmp; /* sign doesn't matter when writing */ duk_memcpy((void *) (buf + offset), (const void *) du.uc, 4); break; } case DUK__FLD_FLOAT: { duk_uint32_t tmp; if (offset + 4U > check_length) { goto fail_bounds; } du.f[0] = (duk_float_t) duk_to_number(thr, 0); if (endswap) { tmp = du.ui[0]; tmp = DUK_BSWAP32(tmp); du.ui[0] = tmp; } /* sign doesn't matter when writing */ duk_memcpy((void *) (buf + offset), (const void *) du.uc, 4); break; } case DUK__FLD_DOUBLE: { if (offset + 8U > check_length) { goto fail_bounds; } du.d = (duk_double_t) duk_to_number(thr, 0); if (endswap) { DUK_DBLUNION_BSWAP64(&du); } /* sign doesn't matter when writing */ duk_memcpy((void *) (buf + offset), (const void *) du.uc, 8); break; } case DUK__FLD_VARINT: { /* Node.js Buffer variable width integer field. We don't really * care about speed here, so aim for shortest algorithm. */ duk_int_t field_bytelen; duk_int_t i, i_step, i_end; #if defined(DUK_USE_64BIT_OPS) duk_int64_t tmp; #else duk_double_t tmp; #endif duk_uint8_t *p; field_bytelen = (duk_int_t) nbytes; if (offset + (duk_uint_t) field_bytelen > check_length) { goto fail_bounds; } /* Slow writing of value using either 64-bit arithmetic * or IEEE doubles if 64-bit types not available. There's * no special sign handling when writing varints. */ if (magic_bigendian) { /* Write in big endian */ i = field_bytelen; /* one i_step added at top of loop */ i_step = -1; i_end = 0; } else { /* Write in little endian */ i = -1; /* one i_step added at top of loop */ i_step = 1; i_end = field_bytelen - 1; } /* XXX: The duk_to_number() cast followed by integer coercion * is platform specific so NaN, +/- Infinity, and out-of-bounds * values result in platform specific output now. * See: test-bi-nodejs-buffer-proto-varint-special.js */ #if defined(DUK_USE_64BIT_OPS) tmp = (duk_int64_t) duk_to_number(thr, 0); p = (duk_uint8_t *) (buf + offset); do { i += i_step; DUK_ASSERT(i >= 0 && i < field_bytelen); p[i] = (duk_uint8_t) (tmp & 0xff); tmp = tmp >> 8; /* unnecessary shift for last byte */ } while (i != i_end); #else tmp = duk_to_number(thr, 0); p = (duk_uint8_t *) (buf + offset); do { i += i_step; tmp = DUK_FLOOR(tmp); DUK_ASSERT(i >= 0 && i < field_bytelen); p[i] = (duk_uint8_t) (DUK_FMOD(tmp, 256.0)); tmp = tmp / 256.0; /* unnecessary div for last byte */ } while (i != i_end); #endif break; } default: { /* should never happen but default here */ goto fail_bounds; } } /* Node.js Buffer: return offset + #bytes written (i.e. next * write offset). */ if (magic_typedarray) { /* For TypedArrays 'undefined' return value is specified * by ES2015 (matches V8). */ return 0; } duk_push_uint(thr, offset + (duk_uint_t) nbytes); return 1; fail_neutered: fail_field_length: fail_bounds: if (no_assert) { /* Node.js return value for failed writes is offset + #bytes * that would have been written. */ /* XXX: for negative input offsets, 'offset' will be a large * positive value so the result here is confusing. */ if (magic_typedarray) { return 0; } duk_push_uint(thr, offset + (duk_uint_t) nbytes); return 1; } DUK_DCERROR_RANGE_INVALID_ARGS(thr); } #endif /* DUK_USE_BUFFEROBJECT_SUPPORT */ /* * Accessors for .buffer, .byteLength, .byteOffset */ #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) DUK_LOCAL duk_hbufobj *duk__autospawn_arraybuffer(duk_hthread *thr, duk_hbuffer *h_buf) { duk_hbufobj *h_res; h_res = duk_push_bufobj_raw(thr, DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_FLAG_BUFOBJ | DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_ARRAYBUFFER), DUK_BIDX_ARRAYBUFFER_PROTOTYPE); DUK_ASSERT(h_res != NULL); DUK_UNREF(h_res); duk__set_bufobj_buffer(thr, h_res, h_buf); DUK_HBUFOBJ_ASSERT_VALID(h_res); DUK_ASSERT(h_res->buf_prop == NULL); return h_res; } DUK_INTERNAL duk_ret_t duk_bi_typedarray_buffer_getter(duk_hthread *thr) { duk_hbufobj *h_bufobj; h_bufobj = (duk_hbufobj *) duk__getrequire_bufobj_this(thr, DUK__BUFOBJ_FLAG_THROW /*flags*/); DUK_ASSERT(h_bufobj != NULL); if (DUK_HEAPHDR_IS_BUFFER((duk_heaphdr *) h_bufobj)) { DUK_DD(DUK_DDPRINT("autospawn ArrayBuffer for plain buffer")); (void) duk__autospawn_arraybuffer(thr, (duk_hbuffer *) h_bufobj); return 1; } else { if (h_bufobj->buf_prop == NULL && DUK_HOBJECT_GET_CLASS_NUMBER((duk_hobject *) h_bufobj) != DUK_HOBJECT_CLASS_ARRAYBUFFER && h_bufobj->buf != NULL) { duk_hbufobj *h_arrbuf; DUK_DD(DUK_DDPRINT("autospawn ArrayBuffer for typed array or DataView")); h_arrbuf = duk__autospawn_arraybuffer(thr, h_bufobj->buf); if (h_bufobj->buf_prop == NULL) { /* Must recheck buf_prop, in case ArrayBuffer * alloc had a side effect which already filled * it! */ /* Set ArrayBuffer's .byteOffset and .byteLength based * on the view so that Arraybuffer[view.byteOffset] * matches view[0]. */ h_arrbuf->offset = 0; DUK_ASSERT(h_bufobj->offset + h_bufobj->length >= h_bufobj->offset); /* Wrap check on creation. */ h_arrbuf->length = h_bufobj->offset + h_bufobj->length; DUK_ASSERT(h_arrbuf->buf_prop == NULL); DUK_ASSERT(h_bufobj->buf_prop == NULL); h_bufobj->buf_prop = (duk_hobject *) h_arrbuf; DUK_HBUFOBJ_INCREF(thr, h_arrbuf); /* Now reachable and accounted for. */ } /* Left on stack; pushed for the second time below (OK). */ } if (h_bufobj->buf_prop) { duk_push_hobject(thr, h_bufobj->buf_prop); return 1; } } return 0; } DUK_INTERNAL duk_ret_t duk_bi_typedarray_byteoffset_getter(duk_hthread *thr) { duk_hbufobj *h_bufobj; h_bufobj = (duk_hbufobj *) duk__getrequire_bufobj_this(thr, DUK__BUFOBJ_FLAG_THROW /*flags*/); DUK_ASSERT(h_bufobj != NULL); if (DUK_HEAPHDR_IS_BUFFER((duk_heaphdr *) h_bufobj)) { duk_push_uint(thr, 0); } else { /* If neutered must return 0; offset is zeroed during * neutering. */ duk_push_uint(thr, h_bufobj->offset); } return 1; } DUK_INTERNAL duk_ret_t duk_bi_typedarray_bytelength_getter(duk_hthread *thr) { duk_hbufobj *h_bufobj; h_bufobj = (duk_hbufobj *) duk__getrequire_bufobj_this(thr, DUK__BUFOBJ_FLAG_THROW /*flags*/); DUK_ASSERT(h_bufobj != NULL); if (DUK_HEAPHDR_IS_BUFFER((duk_heaphdr *) h_bufobj)) { duk_hbuffer *h_buf; h_buf = (duk_hbuffer *) h_bufobj; DUK_ASSERT(DUK_HBUFFER_GET_SIZE(h_buf) <= DUK_UINT_MAX); /* Buffer limits. */ duk_push_uint(thr, (duk_uint_t) DUK_HBUFFER_GET_SIZE(h_buf)); } else { /* If neutered must return 0; length is zeroed during * neutering. */ duk_push_uint(thr, h_bufobj->length); } return 1; } #else /* DUK_USE_BUFFEROBJECT_SUPPORT */ /* No .buffer getter without ArrayBuffer support. */ #if 0 DUK_INTERNAL duk_ret_t duk_bi_typedarray_buffer_getter(duk_hthread *thr) { return 0; } #endif DUK_INTERNAL duk_ret_t duk_bi_typedarray_byteoffset_getter(duk_hthread *thr) { duk_push_uint(thr, 0); return 1; } DUK_INTERNAL duk_ret_t duk_bi_typedarray_bytelength_getter(duk_hthread *thr) { duk_hbuffer *h_buf; /* XXX: helper? */ duk_push_this(thr); h_buf = duk_require_hbuffer(thr, -1); duk_push_uint(thr, DUK_HBUFFER_GET_SIZE(h_buf)); return 1; } #endif /* DUK_USE_BUFFEROBJECT_SUPPORT */ /* automatic undefs */ #undef DUK__BUFOBJ_FLAG_PROMOTE #undef DUK__BUFOBJ_FLAG_THROW #undef DUK__FLD_16BIT #undef DUK__FLD_32BIT #undef DUK__FLD_8BIT #undef DUK__FLD_BIGENDIAN #undef DUK__FLD_DOUBLE #undef DUK__FLD_FLOAT #undef DUK__FLD_SIGNED #undef DUK__FLD_TYPEDARRAY #undef DUK__FLD_VARINT #line 1 "duk_bi_cbor.c" /* * CBOR bindings. * * http://cbor.io/ * https://tools.ietf.org/html/rfc7049 */ /* #include duk_internal.h -> already included */ #if defined(DUK_USE_CBOR_SUPPORT) /* #define DUK_CBOR_STRESS */ /* Default behavior for encoding strings: use CBOR text string if string * is UTF-8 compatible, otherwise use CBOR byte string. These defines * can be used to force either type for all strings. Using text strings * for non-UTF-8 data is technically invalid CBOR. */ /* #define DUK_CBOR_TEXT_STRINGS */ /* #define DUK_CBOR_BYTE_STRINGS */ /* Misc. defines. */ /* #define DUK_CBOR_PREFER_SIZE */ /* #define DUK_CBOR_DOUBLE_AS_IS */ /* #define DUK_CBOR_DECODE_FASTPATH */ typedef struct { duk_hthread *thr; duk_uint8_t *ptr; duk_uint8_t *buf; duk_uint8_t *buf_end; duk_size_t len; duk_idx_t idx_buf; duk_uint_t recursion_depth; duk_uint_t recursion_limit; } duk_cbor_encode_context; typedef struct { duk_hthread *thr; const duk_uint8_t *buf; duk_size_t off; duk_size_t len; duk_uint_t recursion_depth; duk_uint_t recursion_limit; } duk_cbor_decode_context; DUK_LOCAL void duk__cbor_encode_value(duk_cbor_encode_context *enc_ctx); DUK_LOCAL void duk__cbor_decode_value(duk_cbor_decode_context *dec_ctx); /* * Misc */ DUK_LOCAL duk_uint32_t duk__cbor_double_to_uint32(double d) { /* Out of range casts are undefined behavior, so caller must avoid. */ DUK_ASSERT(d >= 0.0 && d <= 4294967295.0); return (duk_uint32_t) d; } /* * Encoding */ DUK_LOCAL void duk__cbor_encode_error(duk_cbor_encode_context *enc_ctx) { (void) duk_type_error(enc_ctx->thr, "cbor encode error"); } DUK_LOCAL void duk__cbor_encode_req_stack(duk_cbor_encode_context *enc_ctx) { duk_require_stack(enc_ctx->thr, 4); } DUK_LOCAL void duk__cbor_encode_objarr_entry(duk_cbor_encode_context *enc_ctx) { duk_hthread *thr = enc_ctx->thr; /* Native stack check in object/array recursion. */ duk_native_stack_check(thr); /* When working with deeply recursive structures, this is important * to ensure there's no effective depth limit. */ duk__cbor_encode_req_stack(enc_ctx); DUK_ASSERT(enc_ctx->recursion_depth <= enc_ctx->recursion_limit); if (enc_ctx->recursion_depth >= enc_ctx->recursion_limit) { DUK_ERROR_RANGE(thr, DUK_STR_ENC_RECLIMIT); DUK_WO_NORETURN(return;); } enc_ctx->recursion_depth++; } DUK_LOCAL void duk__cbor_encode_objarr_exit(duk_cbor_encode_context *enc_ctx) { DUK_ASSERT(enc_ctx->recursion_depth > 0); enc_ctx->recursion_depth--; } /* Check that a size_t is in uint32 range to avoid out-of-range casts. */ DUK_LOCAL void duk__cbor_encode_sizet_uint32_check(duk_cbor_encode_context *enc_ctx, duk_size_t len) { if (DUK_UNLIKELY(sizeof(duk_size_t) > sizeof(duk_uint32_t) && len > (duk_size_t) DUK_UINT32_MAX)) { duk__cbor_encode_error(enc_ctx); } } DUK_LOCAL DUK_NOINLINE void duk__cbor_encode_ensure_slowpath(duk_cbor_encode_context *enc_ctx, duk_size_t len) { duk_size_t oldlen; duk_size_t minlen; duk_size_t newlen; duk_uint8_t *p_new; duk_size_t old_data_len; DUK_ASSERT(enc_ctx->ptr >= enc_ctx->buf); DUK_ASSERT(enc_ctx->buf_end >= enc_ctx->ptr); DUK_ASSERT(enc_ctx->buf_end >= enc_ctx->buf); /* Overflow check. * * Limit example: 0xffffffffUL / 2U = 0x7fffffffUL, we reject >= 0x80000000UL. */ oldlen = enc_ctx->len; minlen = oldlen + len; if (DUK_UNLIKELY(oldlen > DUK_SIZE_MAX / 2U || minlen < oldlen)) { duk__cbor_encode_error(enc_ctx); } #if defined(DUK_CBOR_STRESS) newlen = oldlen + 1U; #else newlen = oldlen * 2U; #endif DUK_ASSERT(newlen >= oldlen); if (minlen > newlen) { newlen = minlen; } DUK_ASSERT(newlen >= oldlen); DUK_ASSERT(newlen >= minlen); DUK_ASSERT(newlen > 0U); DUK_DD(DUK_DDPRINT("cbor encode buffer resized to %ld", (long) newlen)); p_new = (duk_uint8_t *) duk_resize_buffer(enc_ctx->thr, enc_ctx->idx_buf, newlen); DUK_ASSERT(p_new != NULL); old_data_len = (duk_size_t) (enc_ctx->ptr - enc_ctx->buf); enc_ctx->buf = p_new; enc_ctx->buf_end = p_new + newlen; enc_ctx->ptr = p_new + old_data_len; enc_ctx->len = newlen; } DUK_LOCAL DUK_INLINE void duk__cbor_encode_ensure(duk_cbor_encode_context *enc_ctx, duk_size_t len) { if (DUK_LIKELY((duk_size_t) (enc_ctx->buf_end - enc_ctx->ptr) >= len)) { return; } duk__cbor_encode_ensure_slowpath(enc_ctx, len); } DUK_LOCAL duk_size_t duk__cbor_get_reserve(duk_cbor_encode_context *enc_ctx) { DUK_ASSERT(enc_ctx->ptr >= enc_ctx->buf); DUK_ASSERT(enc_ctx->ptr <= enc_ctx->buf_end); return (duk_size_t) (enc_ctx->buf_end - enc_ctx->ptr); } DUK_LOCAL void duk__cbor_encode_uint32(duk_cbor_encode_context *enc_ctx, duk_uint32_t u, duk_uint8_t base) { duk_uint8_t *p; /* Caller must ensure space. */ DUK_ASSERT(duk__cbor_get_reserve(enc_ctx) >= 1 + 4); p = enc_ctx->ptr; if (DUK_LIKELY(u <= 23U)) { *p++ = (duk_uint8_t) (base + (duk_uint8_t) u); } else if (u <= 0xffUL) { *p++ = base + 0x18U; *p++ = (duk_uint8_t) u; } else if (u <= 0xffffUL) { *p++ = base + 0x19U; DUK_RAW_WRITEINC_U16_BE(p, (duk_uint16_t) u); } else { *p++ = base + 0x1aU; DUK_RAW_WRITEINC_U32_BE(p, u); } enc_ctx->ptr = p; } #if defined(DUK_CBOR_DOUBLE_AS_IS) DUK_LOCAL void duk__cbor_encode_double(duk_cbor_encode_context *enc_ctx, double d) { duk_uint8_t *p; /* Caller must ensure space. */ DUK_ASSERT(duk__cbor_get_reserve(enc_ctx) >= 1 + 8); p = enc_ctx->ptr; *p++ = 0xfbU; DUK_RAW_WRITEINC_DOUBLE_BE(p, d); p += 8; enc_ctx->ptr = p; } #else /* DUK_CBOR_DOUBLE_AS_IS */ DUK_LOCAL void duk__cbor_encode_double_fp(duk_cbor_encode_context *enc_ctx, double d) { duk_double_union u; duk_uint16_t u16; duk_int16_t expt; duk_uint8_t *p; DUK_ASSERT(DUK_FPCLASSIFY(d) != DUK_FP_ZERO); /* Caller must ensure space. */ DUK_ASSERT(duk__cbor_get_reserve(enc_ctx) >= 1 + 8); /* Organize into little endian (no-op if platform is little endian). */ u.d = d; duk_dblunion_host_to_little(&u); /* Check if 'd' can represented as a normal half-float. * Denormal half-floats could also be used, but that check * isn't done now (denormal half-floats are decoded of course). * So just check exponent range and that at most 10 significant * bits (excluding implicit leading 1) are used in 'd'. */ u16 = (((duk_uint16_t) u.uc[7]) << 8) | ((duk_uint16_t) u.uc[6]); expt = (duk_int16_t) ((u16 & 0x7ff0U) >> 4) - 1023; if (expt >= -14 && expt <= 15) { /* Half-float normal exponents (excl. denormals). * * 7 6 5 4 3 2 1 0 (LE index) * double: seeeeeee eeeemmmm mmmmmmmm mmmmmmmm mmmmmmmm mmmmmmmm mmmmmmmm mmmmmmmm * half: seeeee mmmm mmmmmm00 00000000 00000000 00000000 00000000 00000000 */ duk_bool_t use_half_float; use_half_float = (u.uc[0] == 0 && u.uc[1] == 0 && u.uc[2] == 0 && u.uc[3] == 0 && u.uc[4] == 0 && (u.uc[5] & 0x03U) == 0); if (use_half_float) { duk_uint32_t t; expt += 15; t = (duk_uint32_t) (u.uc[7] & 0x80U) << 8; t += (duk_uint32_t) expt << 10; t += ((duk_uint32_t) u.uc[6] & 0x0fU) << 6; t += ((duk_uint32_t) u.uc[5]) >> 2; /* seeeeemm mmmmmmmm */ p = enc_ctx->ptr; *p++ = 0xf9U; DUK_RAW_WRITEINC_U16_BE(p, (duk_uint16_t) t); enc_ctx->ptr = p; return; } } /* Same check for plain float. Also no denormal support here. */ if (expt >= -126 && expt <= 127) { /* Float normal exponents (excl. denormals). * * double: seeeeeee eeeemmmm mmmmmmmm mmmmmmmm mmmmmmmm mmmmmmmm mmmmmmmm mmmmmmmm * float: seeee eeeemmmm mmmmmmmm mmmmmmmm mmm00000 00000000 00000000 00000000 */ duk_bool_t use_float; duk_float_t d_float; /* We could do this explicit mantissa check, but doing * a double-float-double cast is fine because we've * already verified that the exponent is in range so * that the narrower cast is not undefined behavior. */ #if 0 use_float = (u.uc[0] == 0 && u.uc[1] == 0 && u.uc[2] == 0 && (u.uc[3] & 0xe0U) == 0); #endif d_float = (duk_float_t) d; use_float = duk_double_equals((duk_double_t) d_float, d); if (use_float) { p = enc_ctx->ptr; *p++ = 0xfaU; DUK_RAW_WRITEINC_FLOAT_BE(p, d_float); enc_ctx->ptr = p; return; } } /* Special handling for NaN and Inf which we want to encode as * half-floats. They share the same (maximum) exponent. */ if (expt == 1024) { DUK_ASSERT(DUK_ISNAN(d) || DUK_ISINF(d)); p = enc_ctx->ptr; *p++ = 0xf9U; if (DUK_ISNAN(d)) { /* Shortest NaN encoding is using a half-float. Lose the * exact NaN bits in the process. IEEE double would be * 7ff8 0000 0000 0000, i.e. a quiet NaN in most architectures * (https://en.wikipedia.org/wiki/NaN#Encoding). The * equivalent half float is 7e00. */ *p++ = 0x7eU; } else { /* Shortest +/- Infinity encoding is using a half-float. */ if (DUK_SIGNBIT(d)) { *p++ = 0xfcU; } else { *p++ = 0x7cU; } } *p++ = 0x00U; enc_ctx->ptr = p; return; } /* Cannot use half-float or float, encode as full IEEE double. */ p = enc_ctx->ptr; *p++ = 0xfbU; DUK_RAW_WRITEINC_DOUBLE_BE(p, d); enc_ctx->ptr = p; } DUK_LOCAL void duk__cbor_encode_double(duk_cbor_encode_context *enc_ctx, double d) { duk_uint8_t *p; double d_floor; /* Integers and floating point values of all types are conceptually * equivalent in CBOR. Try to always choose the shortest encoding * which is not always immediately obvious. For example, NaN and Inf * can be most compactly represented as a half-float (assuming NaN * bits are not preserved), and 0x1'0000'0000 as a single precision * float. Shortest forms in preference order (prefer integer over * float when equal length): * * uint 1 byte [0,23] (not -0) * sint 1 byte [-24,-1] * uint+1 2 bytes [24,255] * sint+1 2 bytes [-256,-25] * uint+2 3 bytes [256,65535] * sint+2 3 bytes [-65536,-257] * half-float 3 bytes -0, NaN, +/- Infinity, range [-65504,65504] * uint+4 5 bytes [65536,4294967295] * sint+4 5 bytes [-4294967296,-258] * float 5 bytes range [-(1 - 2^(-24)) * 2^128, (1 - 2^(-24)) * 2^128] * uint+8 9 bytes [4294967296,18446744073709551615] * sint+8 9 bytes [-18446744073709551616,-4294967297] * double 9 bytes * * For whole numbers (compatible with integers): * - 1-byte or 2-byte uint/sint representation is preferred for * [-256,255]. * - 3-byte uint/sint is preferred for [-65536,65535]. Half floats * are never preferred because they have the same length. * - 5-byte uint/sint is preferred for [-4294967296,4294967295]. * Single precision floats are never preferred, and half-floats * don't reach above the 3-byte uint/sint range so they're never * preferred. * - So, for all integers up to signed/unsigned 32-bit range the * preferred encoding is always an integer uint/sint. * - For integers above 32 bits the situation is more complicated. * Half-floats are never useful for them because of their limited * range, but IEEE single precision floats (5 bytes encoded) can * represent some integers between the 32-bit and 64-bit ranges * which require 9 bytes as a uint/sint. * * For floating point values not compatible with integers, the * preferred encoding is quite clear: * - For +Inf/-Inf use half-float. * - For NaN use a half-float, assuming NaN bits ("payload") is * not worth preserving. Duktape doesn't in general guarantee * preservation of the NaN payload so using a half-float seems * consistent with that. * - For remaining values, prefer the shortest form which doesn't * lose any precision. For normal half-floats and single precision * floats this is simple: just check exponent and mantissa bits * using a fixed mask. For denormal half-floats and single * precision floats the check is a bit more complicated: a normal * IEEE double can sometimes be represented as a denormal * half-float or single precision float. * * https://en.wikipedia.org/wiki/Half-precision_floating-point_format#IEEE_754_half-precision_binary_floating-point_format:_binary16 */ /* Caller must ensure space. */ DUK_ASSERT(duk__cbor_get_reserve(enc_ctx) >= 1 + 8); /* Most important path is integers. The floor() test will be true * for Inf too (but not NaN). */ d_floor = DUK_FLOOR(d); /* identity if d is +/- 0.0, NaN, or +/- Infinity */ if (DUK_LIKELY(duk_double_equals(d_floor, d) != 0)) { DUK_ASSERT(!DUK_ISNAN(d)); /* NaN == NaN compares false. */ if (DUK_SIGNBIT(d)) { if (d >= -4294967296.0) { d = -1.0 - d; if (d >= 0.0) { DUK_ASSERT(d >= 0.0); duk__cbor_encode_uint32(enc_ctx, duk__cbor_double_to_uint32(d), 0x20U); return; } /* Input was negative zero, d == -1.0 < 0.0. * Shortest -0 is using half-float. */ p = enc_ctx->ptr; *p++ = 0xf9U; *p++ = 0x80U; *p++ = 0x00U; enc_ctx->ptr = p; return; } } else { if (d <= 4294967295.0) { /* Positive zero needs no special handling. */ DUK_ASSERT(d >= 0.0); duk__cbor_encode_uint32(enc_ctx, duk__cbor_double_to_uint32(d), 0x00U); return; } } } /* 64-bit integers are not supported at present. So * we also don't need to deal with choosing between a * 64-bit uint/sint representation vs. IEEE double or * float. */ DUK_ASSERT(DUK_FPCLASSIFY(d) != DUK_FP_ZERO); duk__cbor_encode_double_fp(enc_ctx, d); } #endif /* DUK_CBOR_DOUBLE_AS_IS */ DUK_LOCAL void duk__cbor_encode_string_top(duk_cbor_encode_context *enc_ctx) { const duk_uint8_t *str; duk_size_t len; duk_uint8_t *p; /* CBOR differentiates between UTF-8 text strings and byte strings. * Text strings MUST be valid UTF-8, so not all Duktape strings can * be encoded as valid CBOR text strings. Possible behaviors: * * 1. Use text string when input is valid UTF-8, otherwise use * byte string (maybe tagged to indicate it was an extended * UTF-8 string). * 2. Always use text strings, but sanitize input string so that * invalid UTF-8 is replaced with U+FFFD for example. Combine * surrogates whenever possible. * 3. Always use byte strings. This is simple and produces valid * CBOR, but isn't ideal for interoperability. * 4. Always use text strings, even for invalid UTF-8 such as * codepoints in the surrogate pair range. This is simple but * produces technically invalid CBOR for non-UTF-8 strings which * may affect interoperability. * * Current default is 1; can be changed with defines. */ /* Caller must ensure space. */ DUK_ASSERT(duk__cbor_get_reserve(enc_ctx) >= 1 + 8); str = (const duk_uint8_t *) duk_require_lstring(enc_ctx->thr, -1, &len); if (duk_is_symbol(enc_ctx->thr, -1)) { /* Symbols, encode as an empty table for now. This matches * the behavior of cbor-js. * * XXX: Maybe encode String() coercion with a tag? * XXX: Option to keep enough information to recover * Symbols when decoding (this is not always desirable). */ p = enc_ctx->ptr; *p++ = 0xa0U; enc_ctx->ptr = p; return; } duk__cbor_encode_sizet_uint32_check(enc_ctx, len); #if defined(DUK_CBOR_TEXT_STRINGS) duk__cbor_encode_uint32(enc_ctx, (duk_uint32_t) len, 0x60U); #elif defined(DUK_CBOR_BYTE_STRINGS) duk__cbor_encode_uint32(enc_ctx, (duk_uint32_t) len, 0x40U); #else duk__cbor_encode_uint32(enc_ctx, (duk_uint32_t) len, (DUK_LIKELY(duk_unicode_is_utf8_compatible(str, len) != 0) ? 0x60U : 0x40U)); #endif duk__cbor_encode_ensure(enc_ctx, len); p = enc_ctx->ptr; duk_memcpy((void *) p, (const void *) str, len); p += len; enc_ctx->ptr = p; } DUK_LOCAL void duk__cbor_encode_object(duk_cbor_encode_context *enc_ctx) { duk_uint8_t *buf; duk_size_t len; duk_uint8_t *p; duk_size_t i; duk_size_t off_ib; duk_uint32_t count; /* Caller must ensure space. */ DUK_ASSERT(duk__cbor_get_reserve(enc_ctx) >= 1 + 8); duk__cbor_encode_objarr_entry(enc_ctx); /* XXX: Support for specific built-ins like Date and RegExp. */ if (duk_is_array(enc_ctx->thr, -1)) { /* Shortest encoding for arrays >= 256 in length is actually * the indefinite length one (3 or more bytes vs. 2 bytes). * We still use the definite length version because it is * more decoding friendly. */ len = duk_get_length(enc_ctx->thr, -1); duk__cbor_encode_sizet_uint32_check(enc_ctx, len); duk__cbor_encode_uint32(enc_ctx, (duk_uint32_t) len, 0x80U); for (i = 0; i < len; i++) { duk_get_prop_index(enc_ctx->thr, -1, (duk_uarridx_t) i); duk__cbor_encode_value(enc_ctx); } } else if (duk_is_buffer_data(enc_ctx->thr, -1)) { /* XXX: Tag buffer data? * XXX: Encode typed arrays as integer arrays rather * than buffer data as is? */ buf = (duk_uint8_t *) duk_require_buffer_data(enc_ctx->thr, -1, &len); duk__cbor_encode_sizet_uint32_check(enc_ctx, len); duk__cbor_encode_uint32(enc_ctx, (duk_uint32_t) len, 0x40U); duk__cbor_encode_ensure(enc_ctx, len); p = enc_ctx->ptr; duk_memcpy_unsafe((void *) p, (const void *) buf, len); p += len; enc_ctx->ptr = p; } else { /* We don't know the number of properties in advance * but would still like to encode at least small * objects without indefinite length. Emit an * indefinite length byte initially, and if the final * property count is small enough to also fit in one * byte, backpatch it later. Otherwise keep the * indefinite length. This works well up to 23 * properties which is practical and good enough. */ off_ib = (duk_size_t) (enc_ctx->ptr - enc_ctx->buf); /* XXX: get_offset? */ count = 0U; p = enc_ctx->ptr; *p++ = 0xa0U + 0x1fU; /* indefinite length */ enc_ctx->ptr = p; duk_enum(enc_ctx->thr, -1, DUK_ENUM_OWN_PROPERTIES_ONLY); while (duk_next(enc_ctx->thr, -1, 1 /*get_value*/)) { duk_insert(enc_ctx->thr, -2); /* [ ... key value ] -> [ ... value key ] */ duk__cbor_encode_value(enc_ctx); duk__cbor_encode_value(enc_ctx); count++; if (count == 0U) { duk__cbor_encode_error(enc_ctx); } } duk_pop(enc_ctx->thr); if (count <= 0x17U) { DUK_ASSERT(off_ib < enc_ctx->len); enc_ctx->buf[off_ib] = 0xa0U + (duk_uint8_t) count; } else { duk__cbor_encode_ensure(enc_ctx, 1); p = enc_ctx->ptr; *p++ = 0xffU; /* break */ enc_ctx->ptr = p; } } duk__cbor_encode_objarr_exit(enc_ctx); } DUK_LOCAL void duk__cbor_encode_buffer(duk_cbor_encode_context *enc_ctx) { duk_uint8_t *buf; duk_size_t len; duk_uint8_t *p; /* Caller must ensure space. */ DUK_ASSERT(duk__cbor_get_reserve(enc_ctx) >= 1 + 8); /* Tag buffer data? */ buf = (duk_uint8_t *) duk_require_buffer(enc_ctx->thr, -1, &len); duk__cbor_encode_sizet_uint32_check(enc_ctx, len); duk__cbor_encode_uint32(enc_ctx, (duk_uint32_t) len, 0x40U); duk__cbor_encode_ensure(enc_ctx, len); p = enc_ctx->ptr; duk_memcpy_unsafe((void *) p, (const void *) buf, len); p += len; enc_ctx->ptr = p; } DUK_LOCAL void duk__cbor_encode_pointer(duk_cbor_encode_context *enc_ctx) { /* Pointers (void *) are challenging to encode. They can't * be relied to be even 64-bit integer compatible (there are * pointer models larger than that), nor can floats encode * them. They could be encoded as strings (%p format) but * that's not portable. They could be encoded as direct memory * representations. Recovering pointers is non-portable in any * case but it would be nice to be able to detect and recover * compatible pointers. * * For now, encode as "(%p)" string, matching JX. There doesn't * seem to be an appropriate tag, so pointers don't currently * survive a CBOR encode/decode roundtrip intact. */ const char *ptr; ptr = duk_to_string(enc_ctx->thr, -1); DUK_ASSERT(ptr != NULL); duk_push_sprintf(enc_ctx->thr, "(%s)", ptr); duk_remove(enc_ctx->thr, -2); duk__cbor_encode_string_top(enc_ctx); } DUK_LOCAL void duk__cbor_encode_lightfunc(duk_cbor_encode_context *enc_ctx) { duk_uint8_t *p; /* Caller must ensure space. */ DUK_ASSERT(duk__cbor_get_reserve(enc_ctx) >= 1 + 8); /* For now encode as an empty object. */ p = enc_ctx->ptr; *p++ = 0xa0U; enc_ctx->ptr = p; } DUK_LOCAL void duk__cbor_encode_value(duk_cbor_encode_context *enc_ctx) { duk_uint8_t *p; /* Encode/decode cycle currently loses some type information. * This can be improved by registering custom tags with IANA. */ /* Reserve space for up to 64-bit types (1 initial byte + 8 * followup bytes). This allows encoding of integers, floats, * string/buffer length fields, etc without separate checks * in each code path. */ duk__cbor_encode_ensure(enc_ctx, 1 + 8); switch (duk_get_type(enc_ctx->thr, -1)) { case DUK_TYPE_UNDEFINED: { p = enc_ctx->ptr; *p++ = 0xf7; enc_ctx->ptr = p; break; } case DUK_TYPE_NULL: { p = enc_ctx->ptr; *p++ = 0xf6; enc_ctx->ptr = p; break; } case DUK_TYPE_BOOLEAN: { duk_uint8_t u8 = duk_get_boolean(enc_ctx->thr, -1) ? 0xf5U : 0xf4U; p = enc_ctx->ptr; *p++ = u8; enc_ctx->ptr = p; break; } case DUK_TYPE_NUMBER: { duk__cbor_encode_double(enc_ctx, duk_get_number(enc_ctx->thr, -1)); break; } case DUK_TYPE_STRING: { duk__cbor_encode_string_top(enc_ctx); break; } case DUK_TYPE_OBJECT: { duk__cbor_encode_object(enc_ctx); break; } case DUK_TYPE_BUFFER: { duk__cbor_encode_buffer(enc_ctx); break; } case DUK_TYPE_POINTER: { duk__cbor_encode_pointer(enc_ctx); break; } case DUK_TYPE_LIGHTFUNC: { duk__cbor_encode_lightfunc(enc_ctx); break; } case DUK_TYPE_NONE: default: goto fail; } duk_pop(enc_ctx->thr); return; fail: duk__cbor_encode_error(enc_ctx); } /* * Decoding */ DUK_LOCAL void duk__cbor_decode_error(duk_cbor_decode_context *dec_ctx) { (void) duk_type_error(dec_ctx->thr, "cbor decode error"); } DUK_LOCAL void duk__cbor_decode_req_stack(duk_cbor_decode_context *dec_ctx) { duk_require_stack(dec_ctx->thr, 4); } DUK_LOCAL void duk__cbor_decode_objarr_entry(duk_cbor_decode_context *dec_ctx) { duk_hthread *thr = dec_ctx->thr; /* Native stack check in object/array recursion. */ duk_native_stack_check(thr); duk__cbor_decode_req_stack(dec_ctx); DUK_ASSERT(dec_ctx->recursion_depth <= dec_ctx->recursion_limit); if (dec_ctx->recursion_depth >= dec_ctx->recursion_limit) { DUK_ERROR_RANGE(thr, DUK_STR_DEC_RECLIMIT); DUK_WO_NORETURN(return;); } dec_ctx->recursion_depth++; } DUK_LOCAL void duk__cbor_decode_objarr_exit(duk_cbor_decode_context *dec_ctx) { DUK_ASSERT(dec_ctx->recursion_depth > 0); dec_ctx->recursion_depth--; } DUK_LOCAL duk_uint8_t duk__cbor_decode_readbyte(duk_cbor_decode_context *dec_ctx) { DUK_ASSERT(dec_ctx->off <= dec_ctx->len); if (DUK_UNLIKELY(dec_ctx->len - dec_ctx->off < 1U)) { duk__cbor_decode_error(dec_ctx); } return dec_ctx->buf[dec_ctx->off++]; } DUK_LOCAL duk_uint16_t duk__cbor_decode_read_u16(duk_cbor_decode_context *dec_ctx) { duk_uint16_t res; DUK_ASSERT(dec_ctx->off <= dec_ctx->len); if (DUK_UNLIKELY(dec_ctx->len - dec_ctx->off < 2U)) { duk__cbor_decode_error(dec_ctx); } res = DUK_RAW_READ_U16_BE(dec_ctx->buf + dec_ctx->off); dec_ctx->off += 2; return res; } DUK_LOCAL duk_uint32_t duk__cbor_decode_read_u32(duk_cbor_decode_context *dec_ctx) { duk_uint32_t res; DUK_ASSERT(dec_ctx->off <= dec_ctx->len); if (DUK_UNLIKELY(dec_ctx->len - dec_ctx->off < 4U)) { duk__cbor_decode_error(dec_ctx); } res = DUK_RAW_READ_U32_BE(dec_ctx->buf + dec_ctx->off); dec_ctx->off += 4; return res; } DUK_LOCAL duk_uint8_t duk__cbor_decode_peekbyte(duk_cbor_decode_context *dec_ctx) { if (DUK_UNLIKELY(dec_ctx->off >= dec_ctx->len)) { duk__cbor_decode_error(dec_ctx); } return dec_ctx->buf[dec_ctx->off]; } DUK_LOCAL void duk__cbor_decode_rewind(duk_cbor_decode_context *dec_ctx, duk_size_t len) { DUK_ASSERT(len <= dec_ctx->off); /* Caller must ensure. */ dec_ctx->off -= len; } #if 0 DUK_LOCAL void duk__cbor_decode_ensure(duk_cbor_decode_context *dec_ctx, duk_size_t len) { if (dec_ctx->off + len > dec_ctx->len) { duk__cbor_decode_error(dec_ctx); } } #endif DUK_LOCAL const duk_uint8_t *duk__cbor_decode_consume(duk_cbor_decode_context *dec_ctx, duk_size_t len) { DUK_ASSERT(dec_ctx->off <= dec_ctx->len); if (DUK_LIKELY(dec_ctx->len - dec_ctx->off >= len)) { const duk_uint8_t *res = dec_ctx->buf + dec_ctx->off; dec_ctx->off += len; return res; } duk__cbor_decode_error(dec_ctx); /* Not enough input. */ return NULL; } DUK_LOCAL int duk__cbor_decode_checkbreak(duk_cbor_decode_context *dec_ctx) { if (duk__cbor_decode_peekbyte(dec_ctx) == 0xffU) { DUK_ASSERT(dec_ctx->off < dec_ctx->len); dec_ctx->off++; #if 0 (void) duk__cbor_decode_readbyte(dec_ctx); #endif return 1; } return 0; } DUK_LOCAL void duk__cbor_decode_push_aival_int(duk_cbor_decode_context *dec_ctx, duk_uint8_t ib, duk_bool_t negative) { duk_uint8_t ai; duk_uint32_t t, t1, t2; #if 0 duk_uint64_t t3; #endif duk_double_t d1, d2; duk_double_t d; ai = ib & 0x1fU; if (ai <= 0x17U) { t = ai; goto shared_exit; } switch (ai) { case 0x18U: /* 1 byte */ t = (duk_uint32_t) duk__cbor_decode_readbyte(dec_ctx); goto shared_exit; case 0x19U: /* 2 byte */ t = (duk_uint32_t) duk__cbor_decode_read_u16(dec_ctx); goto shared_exit; case 0x1aU: /* 4 byte */ t = (duk_uint32_t) duk__cbor_decode_read_u32(dec_ctx); goto shared_exit; case 0x1bU: /* 8 byte */ /* For uint64 it's important to handle the -1.0 part before * casting to double: otherwise the adjustment might be lost * in the cast. Uses: -1.0 - d <=> -(d + 1.0). */ t = (duk_uint32_t) duk__cbor_decode_read_u32(dec_ctx); t2 = t; t = (duk_uint32_t) duk__cbor_decode_read_u32(dec_ctx); t1 = t; #if 0 t3 = (duk_uint64_t) t2 * DUK_U64_CONSTANT(0x100000000) + (duk_uint64_t) t1; if (negative) { if (t3 == DUK_UINT64_MAX) { /* -(0xffff'ffff'ffff'ffffULL + 1) = * -0x1'0000'0000'0000'0000 * * >>> -0x10000000000000000 * -18446744073709551616L */ return -18446744073709551616.0; } else { return -((duk_double_t) (t3 + DUK_U64_CONSTANT(1))); } } else { return (duk_double_t) t3; /* XXX: cast helper */ } #endif #if 0 t3 = (duk_uint64_t) t2 * DUK_U64_CONSTANT(0x100000000) + (duk_uint64_t) t1; if (negative) { /* Simpler version: take advantage of the fact that * 0xffff'ffff'ffff'ffff and 0x1'0000'0000'0000'0000 * both round to 0x1'0000'0000'0000'0000: * > (0xffffffffffffffff).toString(16) * '10000000000000000' * > (0x10000000000000000).toString(16) * '10000000000000000' * * For the DUK_UINT64_MAX case we just skip the +1 * increment to avoid wrapping; the result still * comes out right for an IEEE double cast. */ if (t3 != DUK_UINT64_MAX) { t3++; } return -((duk_double_t) t3); } else { return (duk_double_t) t3; /* XXX: cast helper */ } #endif #if 1 /* Use two double parts, avoids dependency on 64-bit type. * Avoid precision loss carefully, especially when dealing * with the required +1 for negative values. * * No fastint check for this path at present. */ d1 = (duk_double_t) t1; /* XXX: cast helpers */ d2 = (duk_double_t) t2 * 4294967296.0; if (negative) { d1 += 1.0; } d = d2 + d1; if (negative) { d = -d; } #endif /* XXX: a push and check for fastint API would be nice */ duk_push_number(dec_ctx->thr, d); return; } duk__cbor_decode_error(dec_ctx); return; shared_exit: if (negative) { /* XXX: a push and check for fastint API would be nice */ if ((duk_uint_t) t <= (duk_uint_t) - (DUK_INT_MIN + 1)) { duk_push_int(dec_ctx->thr, -1 - ((duk_int_t) t)); } else { duk_push_number(dec_ctx->thr, -1.0 - (duk_double_t) t); } } else { duk_push_uint(dec_ctx->thr, (duk_uint_t) t); } } DUK_LOCAL void duk__cbor_decode_skip_aival_int(duk_cbor_decode_context *dec_ctx, duk_uint8_t ib) { const duk_int8_t skips[32] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 4, 8, -1, -1, -1, -1 }; duk_uint8_t ai; duk_int8_t skip; ai = ib & 0x1fU; skip = skips[ai]; if (DUK_UNLIKELY(skip < 0)) { duk__cbor_decode_error(dec_ctx); } duk__cbor_decode_consume(dec_ctx, (duk_size_t) skip); return; } DUK_LOCAL duk_uint32_t duk__cbor_decode_aival_uint32(duk_cbor_decode_context *dec_ctx, duk_uint8_t ib) { duk_uint8_t ai; duk_uint32_t t; ai = ib & 0x1fU; if (ai <= 0x17U) { return (duk_uint32_t) ai; } switch (ai) { case 0x18U: /* 1 byte */ t = (duk_uint32_t) duk__cbor_decode_readbyte(dec_ctx); return t; case 0x19U: /* 2 byte */ t = (duk_uint32_t) duk__cbor_decode_read_u16(dec_ctx); return t; case 0x1aU: /* 4 byte */ t = (duk_uint32_t) duk__cbor_decode_read_u32(dec_ctx); return t; case 0x1bU: /* 8 byte */ t = (duk_uint32_t) duk__cbor_decode_read_u32(dec_ctx); if (t != 0U) { break; } t = (duk_uint32_t) duk__cbor_decode_read_u32(dec_ctx); return t; } duk__cbor_decode_error(dec_ctx); return 0U; } DUK_LOCAL void duk__cbor_decode_buffer(duk_cbor_decode_context *dec_ctx, duk_uint8_t expected_base) { duk_uint32_t len; duk_uint8_t *buf; const duk_uint8_t *inp; duk_uint8_t ib; ib = duk__cbor_decode_readbyte(dec_ctx); if ((ib & 0xe0U) != expected_base) { duk__cbor_decode_error(dec_ctx); } /* Indefinite format is rejected by the following on purpose. */ len = duk__cbor_decode_aival_uint32(dec_ctx, ib); inp = duk__cbor_decode_consume(dec_ctx, len); /* XXX: duk_push_fixed_buffer_with_data() would be a nice API addition. */ buf = (duk_uint8_t *) duk_push_fixed_buffer(dec_ctx->thr, (duk_size_t) len); duk_memcpy((void *) buf, (const void *) inp, (size_t) len); } DUK_LOCAL void duk__cbor_decode_join_buffers(duk_cbor_decode_context *dec_ctx, duk_idx_t count) { duk_size_t total_size = 0; duk_idx_t top = duk_get_top(dec_ctx->thr); duk_idx_t base = top - count; /* count is >= 1 */ duk_idx_t idx; duk_uint8_t *p = NULL; DUK_ASSERT(count >= 1); DUK_ASSERT(top >= count); for (;;) { /* First round: compute total size. * Second round: copy into place. */ for (idx = base; idx < top; idx++) { duk_uint8_t *buf_data; duk_size_t buf_size; buf_data = (duk_uint8_t *) duk_require_buffer(dec_ctx->thr, idx, &buf_size); if (p != NULL) { duk_memcpy_unsafe((void *) p, (const void *) buf_data, buf_size); p += buf_size; } else { total_size += buf_size; if (DUK_UNLIKELY(total_size < buf_size)) { /* Wrap check. */ duk__cbor_decode_error(dec_ctx); } } } if (p != NULL) { break; } else { p = (duk_uint8_t *) duk_push_fixed_buffer(dec_ctx->thr, total_size); DUK_ASSERT(p != NULL); } } duk_replace(dec_ctx->thr, base); duk_pop_n(dec_ctx->thr, count - 1); } DUK_LOCAL void duk__cbor_decode_and_join_strbuf(duk_cbor_decode_context *dec_ctx, duk_uint8_t expected_base) { duk_idx_t count = 0; for (;;) { if (duk__cbor_decode_checkbreak(dec_ctx)) { break; } duk_require_stack(dec_ctx->thr, 1); duk__cbor_decode_buffer(dec_ctx, expected_base); count++; if (DUK_UNLIKELY(count <= 0)) { /* Wrap check. */ duk__cbor_decode_error(dec_ctx); } } if (count == 0) { (void) duk_push_fixed_buffer(dec_ctx->thr, 0); } else if (count > 1) { duk__cbor_decode_join_buffers(dec_ctx, count); } } DUK_LOCAL duk_double_t duk__cbor_decode_half_float(duk_cbor_decode_context *dec_ctx) { duk_double_union u; const duk_uint8_t *inp; duk_int_t expt; duk_uint_t u16; duk_uint_t tmp; duk_double_t res; inp = duk__cbor_decode_consume(dec_ctx, 2); u16 = ((duk_uint_t) inp[0] << 8) + (duk_uint_t) inp[1]; expt = (duk_int_t) ((u16 >> 10) & 0x1fU) - 15; /* Reconstruct IEEE double into little endian order first, then convert * to host order. */ duk_memzero((void *) &u, sizeof(u)); if (expt == -15) { /* Zero or denormal; but note that half float * denormals become double normals. */ if ((u16 & 0x03ffU) == 0) { u.uc[7] = inp[0] & 0x80U; } else { /* Create denormal by first creating a double that * contains the denormal bits and a leading implicit * 1-bit. Then subtract away the implicit 1-bit. * * 0.mmmmmmmmmm * 2^-14 * 1.mmmmmmmmmm 0.... * 2^-14 * -1.0000000000 0.... * 2^-14 * * Double exponent: -14 + 1023 = 0x3f1 */ u.uc[7] = 0x3fU; u.uc[6] = 0x10U + (duk_uint8_t) ((u16 >> 6) & 0x0fU); u.uc[5] = (duk_uint8_t) ((u16 << 2) & 0xffU); /* Mask is really 0xfcU */ duk_dblunion_little_to_host(&u); res = u.d - 0.00006103515625; /* 2^(-14) */ if (u16 & 0x8000U) { res = -res; } return res; } } else if (expt == 16) { /* +/- Inf or NaN. */ if ((u16 & 0x03ffU) == 0) { u.uc[7] = (inp[0] & 0x80U) + 0x7fU; u.uc[6] = 0xf0U; } else { /* Create a 'quiet NaN' with highest * bit set (there are some platforms * where the NaN payload convention is * the opposite). Keep sign. */ u.uc[7] = (inp[0] & 0x80U) + 0x7fU; u.uc[6] = 0xf8U; } } else { /* Normal. */ tmp = (inp[0] & 0x80U) ? 0x80000000UL : 0UL; tmp += (duk_uint_t) (expt + 1023) << 20; tmp += (duk_uint_t) (inp[0] & 0x03U) << 18; tmp += (duk_uint_t) (inp[1] & 0xffU) << 10; u.uc[7] = (tmp >> 24) & 0xffU; u.uc[6] = (tmp >> 16) & 0xffU; u.uc[5] = (tmp >> 8) & 0xffU; u.uc[4] = (tmp >> 0) & 0xffU; } duk_dblunion_little_to_host(&u); return u.d; } DUK_LOCAL void duk__cbor_decode_string(duk_cbor_decode_context *dec_ctx, duk_uint8_t ib, duk_uint8_t ai) { /* If the CBOR string data is not valid UTF-8 it is technically * invalid CBOR. Possible behaviors at least: * * 1. Reject the input, i.e. throw TypeError. * * 2. Accept the input, but sanitize non-UTF-8 data into UTF-8 * using U+FFFD replacements. Also it might make sense to * decode non-BMP codepoints into surrogates for better * ECMAScript compatibility. * * 3. Accept the input as a Duktape string (which are not always * valid UTF-8), but reject any input that would create a * Symbol representation. * * Current behavior is 3. */ if (ai == 0x1fU) { duk_uint8_t *buf_data; duk_size_t buf_size; duk__cbor_decode_and_join_strbuf(dec_ctx, 0x60U); buf_data = (duk_uint8_t *) duk_require_buffer(dec_ctx->thr, -1, &buf_size); (void) duk_push_lstring(dec_ctx->thr, (const char *) buf_data, buf_size); duk_remove(dec_ctx->thr, -2); } else { duk_uint32_t len; const duk_uint8_t *inp; len = duk__cbor_decode_aival_uint32(dec_ctx, ib); inp = duk__cbor_decode_consume(dec_ctx, len); (void) duk_push_lstring(dec_ctx->thr, (const char *) inp, (duk_size_t) len); } if (duk_is_symbol(dec_ctx->thr, -1)) { /* Refuse to create Symbols when decoding. */ duk__cbor_decode_error(dec_ctx); } /* XXX: Here a Duktape API call to convert input -> utf-8 with * replacements would be nice. */ } DUK_LOCAL duk_bool_t duk__cbor_decode_array(duk_cbor_decode_context *dec_ctx, duk_uint8_t ib, duk_uint8_t ai) { duk_uint32_t idx, len; duk__cbor_decode_objarr_entry(dec_ctx); /* Support arrays up to 0xfffffffeU in length. 0xffffffff is * used as an indefinite length marker. */ if (ai == 0x1fU) { len = 0xffffffffUL; } else { len = duk__cbor_decode_aival_uint32(dec_ctx, ib); if (len == 0xffffffffUL) { goto failure; } } /* XXX: use bare array? */ duk_push_array(dec_ctx->thr); for (idx = 0U;;) { if (len == 0xffffffffUL && duk__cbor_decode_checkbreak(dec_ctx)) { break; } if (idx == len) { if (ai == 0x1fU) { goto failure; } break; } duk__cbor_decode_value(dec_ctx); duk_put_prop_index(dec_ctx->thr, -2, (duk_uarridx_t) idx); idx++; if (idx == 0U) { goto failure; /* wrapped */ } } #if 0 success: #endif duk__cbor_decode_objarr_exit(dec_ctx); return 1; failure: /* No need to unwind recursion checks, caller will throw. */ return 0; } DUK_LOCAL duk_bool_t duk__cbor_decode_map(duk_cbor_decode_context *dec_ctx, duk_uint8_t ib, duk_uint8_t ai) { duk_uint32_t count; duk__cbor_decode_objarr_entry(dec_ctx); if (ai == 0x1fU) { count = 0xffffffffUL; } else { count = duk__cbor_decode_aival_uint32(dec_ctx, ib); if (count == 0xffffffffUL) { goto failure; } } /* XXX: use bare object? */ duk_push_object(dec_ctx->thr); for (;;) { if (count == 0xffffffffUL) { if (duk__cbor_decode_checkbreak(dec_ctx)) { break; } } else { if (count == 0UL) { break; } count--; } /* Non-string keys are coerced to strings, * possibly leading to overwriting previous * keys. Last key of a certain coerced name * wins. If key is an object, it will coerce * to '[object Object]' which is consistent * but potentially misleading. One alternative * would be to skip non-string keys. */ duk__cbor_decode_value(dec_ctx); duk__cbor_decode_value(dec_ctx); duk_put_prop(dec_ctx->thr, -3); } #if 0 success: #endif duk__cbor_decode_objarr_exit(dec_ctx); return 1; failure: /* No need to unwind recursion checks, caller will throw. */ return 0; } DUK_LOCAL duk_double_t duk__cbor_decode_float(duk_cbor_decode_context *dec_ctx) { duk_float_union u; const duk_uint8_t *inp; inp = duk__cbor_decode_consume(dec_ctx, 4); duk_memcpy((void *) u.uc, (const void *) inp, 4); duk_fltunion_big_to_host(&u); return (duk_double_t) u.f; } DUK_LOCAL duk_double_t duk__cbor_decode_double(duk_cbor_decode_context *dec_ctx) { duk_double_union u; const duk_uint8_t *inp; inp = duk__cbor_decode_consume(dec_ctx, 8); duk_memcpy((void *) u.uc, (const void *) inp, 8); duk_dblunion_big_to_host(&u); return u.d; } #if defined(DUK_CBOR_DECODE_FASTPATH) #define DUK__CBOR_AI (ib & 0x1fU) DUK_LOCAL void duk__cbor_decode_value(duk_cbor_decode_context *dec_ctx) { duk_uint8_t ib; /* Any paths potentially recursing back to duk__cbor_decode_value() * must perform a Duktape value stack growth check. Avoid the check * here for simple paths like primitive values. */ reread_initial_byte: DUK_DDD(DUK_DDDPRINT("cbor decode off=%ld len=%ld", (long) dec_ctx->off, (long) dec_ctx->len)); ib = duk__cbor_decode_readbyte(dec_ctx); /* Full initial byte switch, footprint cost over baseline is ~+1kB. */ /* XXX: Force full switch with no range check. */ switch (ib) { case 0x00U: case 0x01U: case 0x02U: case 0x03U: case 0x04U: case 0x05U: case 0x06U: case 0x07U: case 0x08U: case 0x09U: case 0x0aU: case 0x0bU: case 0x0cU: case 0x0dU: case 0x0eU: case 0x0fU: case 0x10U: case 0x11U: case 0x12U: case 0x13U: case 0x14U: case 0x15U: case 0x16U: case 0x17U: duk_push_uint(dec_ctx->thr, ib); break; case 0x18U: case 0x19U: case 0x1aU: case 0x1bU: duk__cbor_decode_push_aival_int(dec_ctx, ib, 0 /*negative*/); break; case 0x1cU: case 0x1dU: case 0x1eU: case 0x1fU: goto format_error; case 0x20U: case 0x21U: case 0x22U: case 0x23U: case 0x24U: case 0x25U: case 0x26U: case 0x27U: case 0x28U: case 0x29U: case 0x2aU: case 0x2bU: case 0x2cU: case 0x2dU: case 0x2eU: case 0x2fU: case 0x30U: case 0x31U: case 0x32U: case 0x33U: case 0x34U: case 0x35U: case 0x36U: case 0x37U: duk_push_int(dec_ctx->thr, -((duk_int_t) ((ib - 0x20U) + 1U))); break; case 0x38U: case 0x39U: case 0x3aU: case 0x3bU: duk__cbor_decode_push_aival_int(dec_ctx, ib, 1 /*negative*/); break; case 0x3cU: case 0x3dU: case 0x3eU: case 0x3fU: goto format_error; case 0x40U: case 0x41U: case 0x42U: case 0x43U: case 0x44U: case 0x45U: case 0x46U: case 0x47U: case 0x48U: case 0x49U: case 0x4aU: case 0x4bU: case 0x4cU: case 0x4dU: case 0x4eU: case 0x4fU: case 0x50U: case 0x51U: case 0x52U: case 0x53U: case 0x54U: case 0x55U: case 0x56U: case 0x57U: /* XXX: Avoid rewind, we know the length already. */ DUK_ASSERT(dec_ctx->off > 0U); dec_ctx->off--; duk__cbor_decode_buffer(dec_ctx, 0x40U); break; case 0x58U: case 0x59U: case 0x5aU: case 0x5bU: /* XXX: Avoid rewind, decode length inline. */ DUK_ASSERT(dec_ctx->off > 0U); dec_ctx->off--; duk__cbor_decode_buffer(dec_ctx, 0x40U); break; case 0x5cU: case 0x5dU: case 0x5eU: goto format_error; case 0x5fU: duk__cbor_decode_and_join_strbuf(dec_ctx, 0x40U); break; case 0x60U: case 0x61U: case 0x62U: case 0x63U: case 0x64U: case 0x65U: case 0x66U: case 0x67U: case 0x68U: case 0x69U: case 0x6aU: case 0x6bU: case 0x6cU: case 0x6dU: case 0x6eU: case 0x6fU: case 0x70U: case 0x71U: case 0x72U: case 0x73U: case 0x74U: case 0x75U: case 0x76U: case 0x77U: /* XXX: Avoid double decode of length. */ duk__cbor_decode_string(dec_ctx, ib, DUK__CBOR_AI); break; case 0x78U: case 0x79U: case 0x7aU: case 0x7bU: /* XXX: Avoid double decode of length. */ duk__cbor_decode_string(dec_ctx, ib, DUK__CBOR_AI); break; case 0x7cU: case 0x7dU: case 0x7eU: goto format_error; case 0x7fU: duk__cbor_decode_string(dec_ctx, ib, DUK__CBOR_AI); break; case 0x80U: case 0x81U: case 0x82U: case 0x83U: case 0x84U: case 0x85U: case 0x86U: case 0x87U: case 0x88U: case 0x89U: case 0x8aU: case 0x8bU: case 0x8cU: case 0x8dU: case 0x8eU: case 0x8fU: case 0x90U: case 0x91U: case 0x92U: case 0x93U: case 0x94U: case 0x95U: case 0x96U: case 0x97U: if (DUK_UNLIKELY(duk__cbor_decode_array(dec_ctx, ib, DUK__CBOR_AI) == 0)) { goto format_error; } break; case 0x98U: case 0x99U: case 0x9aU: case 0x9bU: if (DUK_UNLIKELY(duk__cbor_decode_array(dec_ctx, ib, DUK__CBOR_AI) == 0)) { goto format_error; } break; case 0x9cU: case 0x9dU: case 0x9eU: goto format_error; case 0x9fU: if (DUK_UNLIKELY(duk__cbor_decode_array(dec_ctx, ib, DUK__CBOR_AI) == 0)) { goto format_error; } break; case 0xa0U: case 0xa1U: case 0xa2U: case 0xa3U: case 0xa4U: case 0xa5U: case 0xa6U: case 0xa7U: case 0xa8U: case 0xa9U: case 0xaaU: case 0xabU: case 0xacU: case 0xadU: case 0xaeU: case 0xafU: case 0xb0U: case 0xb1U: case 0xb2U: case 0xb3U: case 0xb4U: case 0xb5U: case 0xb6U: case 0xb7U: if (DUK_UNLIKELY(duk__cbor_decode_map(dec_ctx, ib, DUK__CBOR_AI) == 0)) { goto format_error; } break; case 0xb8U: case 0xb9U: case 0xbaU: case 0xbbU: if (DUK_UNLIKELY(duk__cbor_decode_map(dec_ctx, ib, DUK__CBOR_AI) == 0)) { goto format_error; } break; case 0xbcU: case 0xbdU: case 0xbeU: goto format_error; case 0xbfU: if (DUK_UNLIKELY(duk__cbor_decode_map(dec_ctx, ib, DUK__CBOR_AI) == 0)) { goto format_error; } break; case 0xc0U: case 0xc1U: case 0xc2U: case 0xc3U: case 0xc4U: case 0xc5U: case 0xc6U: case 0xc7U: case 0xc8U: case 0xc9U: case 0xcaU: case 0xcbU: case 0xccU: case 0xcdU: case 0xceU: case 0xcfU: case 0xd0U: case 0xd1U: case 0xd2U: case 0xd3U: case 0xd4U: case 0xd5U: case 0xd6U: case 0xd7U: /* Tag 0-23: drop. */ goto reread_initial_byte; case 0xd8U: case 0xd9U: case 0xdaU: case 0xdbU: duk__cbor_decode_skip_aival_int(dec_ctx, ib); goto reread_initial_byte; case 0xdcU: case 0xddU: case 0xdeU: case 0xdfU: goto format_error; case 0xe0U: goto format_error; case 0xe1U: goto format_error; case 0xe2U: goto format_error; case 0xe3U: goto format_error; case 0xe4U: goto format_error; case 0xe5U: goto format_error; case 0xe6U: goto format_error; case 0xe7U: goto format_error; case 0xe8U: goto format_error; case 0xe9U: goto format_error; case 0xeaU: goto format_error; case 0xebU: goto format_error; case 0xecU: goto format_error; case 0xedU: goto format_error; case 0xeeU: goto format_error; case 0xefU: goto format_error; case 0xf0U: goto format_error; case 0xf1U: goto format_error; case 0xf2U: goto format_error; case 0xf3U: goto format_error; case 0xf4U: duk_push_false(dec_ctx->thr); break; case 0xf5U: duk_push_true(dec_ctx->thr); break; case 0xf6U: duk_push_null(dec_ctx->thr); break; case 0xf7U: duk_push_undefined(dec_ctx->thr); break; case 0xf8U: /* Simple value 32-255, nothing defined yet, so reject. */ goto format_error; case 0xf9U: { duk_double_t d; d = duk__cbor_decode_half_float(dec_ctx); duk_push_number(dec_ctx->thr, d); break; } case 0xfaU: { duk_double_t d; d = duk__cbor_decode_float(dec_ctx); duk_push_number(dec_ctx->thr, d); break; } case 0xfbU: { duk_double_t d; d = duk__cbor_decode_double(dec_ctx); duk_push_number(dec_ctx->thr, d); break; } case 0xfcU: case 0xfdU: case 0xfeU: case 0xffU: goto format_error; } /* end switch */ return; format_error: duk__cbor_decode_error(dec_ctx); } #else /* DUK_CBOR_DECODE_FASTPATH */ DUK_LOCAL void duk__cbor_decode_value(duk_cbor_decode_context *dec_ctx) { duk_uint8_t ib, mt, ai; /* Any paths potentially recursing back to duk__cbor_decode_value() * must perform a Duktape value stack growth check. Avoid the check * here for simple paths like primitive values. */ reread_initial_byte: DUK_DDD(DUK_DDDPRINT("cbor decode off=%ld len=%ld", (long) dec_ctx->off, (long) dec_ctx->len)); ib = duk__cbor_decode_readbyte(dec_ctx); mt = ib >> 5U; ai = ib & 0x1fU; /* Additional information in [24,27] = [0x18,0x1b] has relatively * uniform handling for all major types: read 1/2/4/8 additional * bytes. For major type 7 the 1-byte value is a 'simple type', and * 2/4/8-byte values are floats. For other major types the 1/2/4/8 * byte values are integers. The lengths are uniform, but the typing * is not. */ switch (mt) { case 0U: { /* unsigned integer */ duk__cbor_decode_push_aival_int(dec_ctx, ib, 0 /*negative*/); break; } case 1U: { /* negative integer */ duk__cbor_decode_push_aival_int(dec_ctx, ib, 1 /*negative*/); break; } case 2U: { /* byte string */ if (ai == 0x1fU) { duk__cbor_decode_and_join_strbuf(dec_ctx, 0x40U); } else { duk__cbor_decode_rewind(dec_ctx, 1U); duk__cbor_decode_buffer(dec_ctx, 0x40U); } break; } case 3U: { /* text string */ duk__cbor_decode_string(dec_ctx, ib, ai); break; } case 4U: { /* array of data items */ if (DUK_UNLIKELY(duk__cbor_decode_array(dec_ctx, ib, ai) == 0)) { goto format_error; } break; } case 5U: { /* map of pairs of data items */ if (DUK_UNLIKELY(duk__cbor_decode_map(dec_ctx, ib, ai) == 0)) { goto format_error; } break; } case 6U: { /* semantic tagging */ /* Tags are ignored now, re-read initial byte. A tagged * value may itself be tagged (an unlimited number of times) * so keep on peeling away tags. */ duk__cbor_decode_skip_aival_int(dec_ctx, ib); goto reread_initial_byte; } case 7U: { /* floating point numbers, simple data types, break; other */ switch (ai) { case 0x14U: { duk_push_false(dec_ctx->thr); break; } case 0x15U: { duk_push_true(dec_ctx->thr); break; } case 0x16U: { duk_push_null(dec_ctx->thr); break; } case 0x17U: { duk_push_undefined(dec_ctx->thr); break; } case 0x18U: { /* more simple values (1 byte) */ /* Simple value encoded in additional byte (none * are defined so far). RFC 7049 states that the * follow-up byte must be 32-255 to minimize * confusion. So, a non-shortest encoding like * f815 (= true, shortest encoding f5) must be * rejected. cbor.me tester rejects f815, but * e.g. Python CBOR binding decodes it as true. */ goto format_error; } case 0x19U: { /* half-float (2 bytes) */ duk_double_t d; d = duk__cbor_decode_half_float(dec_ctx); duk_push_number(dec_ctx->thr, d); break; } case 0x1aU: { /* float (4 bytes) */ duk_double_t d; d = duk__cbor_decode_float(dec_ctx); duk_push_number(dec_ctx->thr, d); break; } case 0x1bU: { /* double (8 bytes) */ duk_double_t d; d = duk__cbor_decode_double(dec_ctx); duk_push_number(dec_ctx->thr, d); break; } case 0xffU: /* unexpected break */ default: { goto format_error; } } /* end switch */ break; } default: { goto format_error; /* will never actually occur */ } } /* end switch */ return; format_error: duk__cbor_decode_error(dec_ctx); } #endif /* DUK_CBOR_DECODE_FASTPATH */ DUK_LOCAL void duk__cbor_encode(duk_hthread *thr, duk_idx_t idx, duk_uint_t encode_flags) { duk_cbor_encode_context enc_ctx; duk_uint8_t *buf; DUK_UNREF(encode_flags); idx = duk_require_normalize_index(thr, idx); enc_ctx.thr = thr; enc_ctx.idx_buf = duk_get_top(thr); enc_ctx.len = 64; buf = (duk_uint8_t *) duk_push_dynamic_buffer(thr, enc_ctx.len); enc_ctx.ptr = buf; enc_ctx.buf = buf; enc_ctx.buf_end = buf + enc_ctx.len; enc_ctx.recursion_depth = 0; enc_ctx.recursion_limit = DUK_USE_CBOR_ENC_RECLIMIT; duk_dup(thr, idx); duk__cbor_encode_req_stack(&enc_ctx); duk__cbor_encode_value(&enc_ctx); DUK_ASSERT(enc_ctx.recursion_depth == 0); duk_resize_buffer(enc_ctx.thr, enc_ctx.idx_buf, (duk_size_t) (enc_ctx.ptr - enc_ctx.buf)); duk_replace(thr, idx); } DUK_LOCAL void duk__cbor_decode(duk_hthread *thr, duk_idx_t idx, duk_uint_t decode_flags) { duk_cbor_decode_context dec_ctx; DUK_UNREF(decode_flags); /* Suppress compile warnings for functions only needed with e.g. * asserts enabled. */ DUK_UNREF(duk__cbor_get_reserve); idx = duk_require_normalize_index(thr, idx); dec_ctx.thr = thr; dec_ctx.buf = (const duk_uint8_t *) duk_require_buffer_data(thr, idx, &dec_ctx.len); dec_ctx.off = 0; /* dec_ctx.len: set above */ dec_ctx.recursion_depth = 0; dec_ctx.recursion_limit = DUK_USE_CBOR_DEC_RECLIMIT; duk__cbor_decode_req_stack(&dec_ctx); duk__cbor_decode_value(&dec_ctx); DUK_ASSERT(dec_ctx.recursion_depth == 0); if (dec_ctx.off != dec_ctx.len) { (void) duk_type_error(thr, "trailing garbage"); } duk_replace(thr, idx); } #else /* DUK_USE_CBOR_SUPPORT */ DUK_LOCAL void duk__cbor_encode(duk_hthread *thr, duk_idx_t idx, duk_uint_t encode_flags) { DUK_UNREF(idx); DUK_UNREF(encode_flags); DUK_ERROR_UNSUPPORTED(thr); } DUK_LOCAL void duk__cbor_decode(duk_hthread *thr, duk_idx_t idx, duk_uint_t decode_flags) { DUK_UNREF(idx); DUK_UNREF(decode_flags); DUK_ERROR_UNSUPPORTED(thr); } #endif /* DUK_USE_CBOR_SUPPORT */ /* * Public APIs */ DUK_EXTERNAL void duk_cbor_encode(duk_hthread *thr, duk_idx_t idx, duk_uint_t encode_flags) { DUK_ASSERT_API_ENTRY(thr); duk__cbor_encode(thr, idx, encode_flags); } DUK_EXTERNAL void duk_cbor_decode(duk_hthread *thr, duk_idx_t idx, duk_uint_t decode_flags) { DUK_ASSERT_API_ENTRY(thr); duk__cbor_decode(thr, idx, decode_flags); } #if defined(DUK_USE_CBOR_BUILTIN) #if defined(DUK_USE_CBOR_SUPPORT) DUK_INTERNAL duk_ret_t duk_bi_cbor_encode(duk_hthread *thr) { DUK_ASSERT_TOP(thr, 1); duk__cbor_encode(thr, -1, 0 /*flags*/); /* Produce an ArrayBuffer by first decoding into a plain buffer which * mimics a Uint8Array and gettings its .buffer property. */ /* XXX: shortcut */ (void) duk_get_prop_stridx(thr, -1, DUK_STRIDX_LC_BUFFER); return 1; } DUK_INTERNAL duk_ret_t duk_bi_cbor_decode(duk_hthread *thr) { DUK_ASSERT_TOP(thr, 1); duk__cbor_decode(thr, -1, 0 /*flags*/); return 1; } #else /* DUK_USE_CBOR_SUPPORT */ DUK_INTERNAL duk_ret_t duk_bi_cbor_encode(duk_hthread *thr) { DUK_ERROR_UNSUPPORTED(thr); DUK_WO_NORETURN(return 0;); } DUK_INTERNAL duk_ret_t duk_bi_cbor_decode(duk_hthread *thr) { DUK_ERROR_UNSUPPORTED(thr); DUK_WO_NORETURN(return 0;); } #endif /* DUK_USE_CBOR_SUPPORT */ #endif /* DUK_USE_CBOR_BUILTIN */ /* automatic undefs */ #undef DUK__CBOR_AI #line 1 "duk_bi_date.c" /* * Date built-ins * * Unlike most built-ins, Date has some platform dependencies for getting * UTC time, converting between UTC and local time, and parsing and * formatting time values. These are all abstracted behind DUK_USE_xxx * config options. There are built-in platform specific providers for * POSIX and Windows, but external providers can also be used. * * See doc/datetime.rst. * */ /* #include duk_internal.h -> already included */ /* XXX: currently defines unnecessary symbols when DUK_USE_DATE_BUILTIN is disabled. */ /* * Forward declarations */ DUK_LOCAL_DECL duk_double_t duk__push_this_get_timeval_tzoffset(duk_hthread *thr, duk_small_uint_t flags, duk_int_t *out_tzoffset); DUK_LOCAL_DECL duk_double_t duk__push_this_get_timeval(duk_hthread *thr, duk_small_uint_t flags); DUK_LOCAL_DECL void duk__twodigit_year_fixup(duk_hthread *thr, duk_idx_t idx_val); DUK_LOCAL_DECL duk_ret_t duk__set_this_timeval_from_dparts(duk_hthread *thr, duk_double_t *dparts, duk_small_uint_t flags); /* * Other file level defines */ /* Debug macro to print all parts and dparts (used manually because of debug level). */ #define DUK__DPRINT_PARTS_AND_DPARTS(parts, dparts) \ do { \ DUK_D(DUK_DPRINT("parts: %ld %ld %ld %ld %ld %ld %ld %ld, dparts: %lf %lf %lf %lf %lf %lf %lf %lf", \ (long) (parts)[0], \ (long) (parts)[1], \ (long) (parts)[2], \ (long) (parts)[3], \ (long) (parts)[4], \ (long) (parts)[5], \ (long) (parts)[6], \ (long) (parts)[7], \ (double) (dparts)[0], \ (double) (dparts)[1], \ (double) (dparts)[2], \ (double) (dparts)[3], \ (double) (dparts)[4], \ (double) (dparts)[5], \ (double) (dparts)[6], \ (double) (dparts)[7])); \ } while (0) #define DUK__DPRINT_PARTS(parts) \ do { \ DUK_D(DUK_DPRINT("parts: %ld %ld %ld %ld %ld %ld %ld %ld", \ (long) (parts)[0], \ (long) (parts)[1], \ (long) (parts)[2], \ (long) (parts)[3], \ (long) (parts)[4], \ (long) (parts)[5], \ (long) (parts)[6], \ (long) (parts)[7])); \ } while (0) #define DUK__DPRINT_DPARTS(dparts) \ do { \ DUK_D(DUK_DPRINT("dparts: %lf %lf %lf %lf %lf %lf %lf %lf", \ (double) (dparts)[0], \ (double) (dparts)[1], \ (double) (dparts)[2], \ (double) (dparts)[3], \ (double) (dparts)[4], \ (double) (dparts)[5], \ (double) (dparts)[6], \ (double) (dparts)[7])); \ } while (0) /* Equivalent year for DST calculations outside [1970,2038[ range, see * E5 Section 15.9.1.8. Equivalent year has the same leap-year-ness and * starts with the same weekday on Jan 1. * https://bugzilla.mozilla.org/show_bug.cgi?id=351066 */ #define DUK__YEAR(x) ((duk_uint8_t) ((x) -1970)) DUK_LOCAL duk_uint8_t duk__date_equivyear[14] = { #if 1 /* This is based on V8 EquivalentYear() algorithm (see util/genequivyear.py): * http://code.google.com/p/v8/source/browse/trunk/src/date.h#146 */ /* non-leap year: sunday, monday, ... */ DUK__YEAR(2023), DUK__YEAR(2035), DUK__YEAR(2019), DUK__YEAR(2031), DUK__YEAR(2015), DUK__YEAR(2027), DUK__YEAR(2011), /* leap year: sunday, monday, ... */ DUK__YEAR(2012), DUK__YEAR(2024), DUK__YEAR(2008), DUK__YEAR(2020), DUK__YEAR(2032), DUK__YEAR(2016), DUK__YEAR(2028) #endif #if 0 /* This is based on Rhino EquivalentYear() algorithm: * https://github.com/mozilla/rhino/blob/f99cc11d616f0cdda2c42bde72b3484df6182947/src/org/mozilla/javascript/NativeDate.java */ /* non-leap year: sunday, monday, ... */ DUK__YEAR(1978), DUK__YEAR(1973), DUK__YEAR(1985), DUK__YEAR(1986), DUK__YEAR(1981), DUK__YEAR(1971), DUK__YEAR(1977), /* leap year: sunday, monday, ... */ DUK__YEAR(1984), DUK__YEAR(1996), DUK__YEAR(1980), DUK__YEAR(1992), DUK__YEAR(1976), DUK__YEAR(1988), DUK__YEAR(1972) #endif }; /* * ISO 8601 subset parser. */ /* Parser part count. */ #define DUK__NUM_ISO8601_PARSER_PARTS 9 /* Parser part indices. */ #define DUK__PI_YEAR 0 #define DUK__PI_MONTH 1 #define DUK__PI_DAY 2 #define DUK__PI_HOUR 3 #define DUK__PI_MINUTE 4 #define DUK__PI_SECOND 5 #define DUK__PI_MILLISECOND 6 #define DUK__PI_TZHOUR 7 #define DUK__PI_TZMINUTE 8 /* Parser part masks. */ #define DUK__PM_YEAR (1 << DUK__PI_YEAR) #define DUK__PM_MONTH (1 << DUK__PI_MONTH) #define DUK__PM_DAY (1 << DUK__PI_DAY) #define DUK__PM_HOUR (1 << DUK__PI_HOUR) #define DUK__PM_MINUTE (1 << DUK__PI_MINUTE) #define DUK__PM_SECOND (1 << DUK__PI_SECOND) #define DUK__PM_MILLISECOND (1 << DUK__PI_MILLISECOND) #define DUK__PM_TZHOUR (1 << DUK__PI_TZHOUR) #define DUK__PM_TZMINUTE (1 << DUK__PI_TZMINUTE) /* Parser separator indices. */ #define DUK__SI_PLUS 0 #define DUK__SI_MINUS 1 #define DUK__SI_T 2 #define DUK__SI_SPACE 3 #define DUK__SI_COLON 4 #define DUK__SI_PERIOD 5 #define DUK__SI_Z 6 #define DUK__SI_NUL 7 /* Parser separator masks. */ #define DUK__SM_PLUS (1 << DUK__SI_PLUS) #define DUK__SM_MINUS (1 << DUK__SI_MINUS) #define DUK__SM_T (1 << DUK__SI_T) #define DUK__SM_SPACE (1 << DUK__SI_SPACE) #define DUK__SM_COLON (1 << DUK__SI_COLON) #define DUK__SM_PERIOD (1 << DUK__SI_PERIOD) #define DUK__SM_Z (1 << DUK__SI_Z) #define DUK__SM_NUL (1 << DUK__SI_NUL) /* Rule control flags. */ #define DUK__CF_NEG (1 << 0) /* continue matching, set neg_tzoffset flag */ #define DUK__CF_ACCEPT (1 << 1) /* accept string */ #define DUK__CF_ACCEPT_NUL (1 << 2) /* accept string if next char is NUL (otherwise reject) */ #define DUK__PACK_RULE(partmask, sepmask, nextpart, flags) \ ((duk_uint32_t) (partmask) + (((duk_uint32_t) (sepmask)) << 9) + (((duk_uint32_t) (nextpart)) << 17) + \ (((duk_uint32_t) (flags)) << 21)) #define DUK__UNPACK_RULE(rule, var_nextidx, var_flags) \ do { \ (var_nextidx) = (duk_small_uint_t) (((rule) >> 17) & 0x0f); \ (var_flags) = (duk_small_uint_t) ((rule) >> 21); \ } while (0) #define DUK__RULE_MASK_PART_SEP 0x1ffffUL /* Matching separator index is used in the control table */ DUK_LOCAL const duk_uint8_t duk__parse_iso8601_seps[] = { DUK_ASC_PLUS /*0*/, DUK_ASC_MINUS /*1*/, DUK_ASC_UC_T /*2*/, DUK_ASC_SPACE /*3*/, DUK_ASC_COLON /*4*/, DUK_ASC_PERIOD /*5*/, DUK_ASC_UC_Z /*6*/, DUK_ASC_NUL /*7*/ }; /* Rule table: first matching rule is used to determine what to do next. */ DUK_LOCAL const duk_uint32_t duk__parse_iso8601_control[] = { DUK__PACK_RULE(DUK__PM_YEAR, DUK__SM_MINUS, DUK__PI_MONTH, 0), DUK__PACK_RULE(DUK__PM_MONTH, DUK__SM_MINUS, DUK__PI_DAY, 0), DUK__PACK_RULE(DUK__PM_YEAR | DUK__PM_MONTH | DUK__PM_DAY, DUK__SM_T | DUK__SM_SPACE, DUK__PI_HOUR, 0), DUK__PACK_RULE(DUK__PM_HOUR, DUK__SM_COLON, DUK__PI_MINUTE, 0), DUK__PACK_RULE(DUK__PM_MINUTE, DUK__SM_COLON, DUK__PI_SECOND, 0), DUK__PACK_RULE(DUK__PM_SECOND, DUK__SM_PERIOD, DUK__PI_MILLISECOND, 0), DUK__PACK_RULE(DUK__PM_TZHOUR, DUK__SM_COLON, DUK__PI_TZMINUTE, 0), DUK__PACK_RULE(DUK__PM_YEAR | DUK__PM_MONTH | DUK__PM_DAY | DUK__PM_HOUR /*Note1*/ | DUK__PM_MINUTE | DUK__PM_SECOND | DUK__PM_MILLISECOND, DUK__SM_PLUS, DUK__PI_TZHOUR, 0), DUK__PACK_RULE(DUK__PM_YEAR | DUK__PM_MONTH | DUK__PM_DAY | DUK__PM_HOUR /*Note1*/ | DUK__PM_MINUTE | DUK__PM_SECOND | DUK__PM_MILLISECOND, DUK__SM_MINUS, DUK__PI_TZHOUR, DUK__CF_NEG), DUK__PACK_RULE(DUK__PM_YEAR | DUK__PM_MONTH | DUK__PM_DAY | DUK__PM_HOUR /*Note1*/ | DUK__PM_MINUTE | DUK__PM_SECOND | DUK__PM_MILLISECOND, DUK__SM_Z, 0, DUK__CF_ACCEPT_NUL), DUK__PACK_RULE(DUK__PM_YEAR | DUK__PM_MONTH | DUK__PM_DAY | DUK__PM_HOUR /*Note1*/ | DUK__PM_MINUTE | DUK__PM_SECOND | DUK__PM_MILLISECOND | DUK__PM_TZHOUR /*Note2*/ | DUK__PM_TZMINUTE, DUK__SM_NUL, 0, DUK__CF_ACCEPT) /* Note1: the specification doesn't require matching a time form with * just hours ("HH"), but we accept it here, e.g. "2012-01-02T12Z". * * Note2: the specification doesn't require matching a timezone offset * with just hours ("HH"), but accept it here, e.g. "2012-01-02T03:04:05+02" */ }; DUK_LOCAL duk_bool_t duk__parse_string_iso8601_subset(duk_hthread *thr, const char *str) { duk_int_t parts[DUK__NUM_ISO8601_PARSER_PARTS]; duk_double_t dparts[DUK_DATE_IDX_NUM_PARTS]; duk_double_t d; const duk_uint8_t *p; duk_small_uint_t part_idx = 0; duk_int_t accum = 0; duk_small_uint_t ndigits = 0; duk_bool_t neg_year = 0; duk_bool_t neg_tzoffset = 0; duk_uint_fast8_t ch; duk_small_uint_t i; /* During parsing, month and day are one-based; set defaults here. */ duk_memzero(parts, sizeof(parts)); DUK_ASSERT(parts[DUK_DATE_IDX_YEAR] == 0); /* don't care value, year is mandatory */ parts[DUK_DATE_IDX_MONTH] = 1; parts[DUK_DATE_IDX_DAY] = 1; /* Special handling for year sign. */ p = (const duk_uint8_t *) str; ch = p[0]; if (ch == DUK_ASC_PLUS) { p++; } else if (ch == DUK_ASC_MINUS) { neg_year = 1; p++; } for (;;) { ch = *p++; DUK_DDD(DUK_DDDPRINT("parsing, part_idx=%ld, char=%ld ('%c')", (long) part_idx, (long) ch, (int) ((ch >= 0x20 && ch <= 0x7e) ? ch : DUK_ASC_QUESTION))); if (ch >= DUK_ASC_0 && ch <= DUK_ASC_9) { if (ndigits >= 9) { DUK_DDD(DUK_DDDPRINT("too many digits -> reject")); goto reject; } if (part_idx == DUK__PI_MILLISECOND && ndigits >= 3) { /* ignore millisecond fractions after 3 */ } else { accum = accum * 10 + ((duk_int_t) ch) - ((duk_int_t) DUK_ASC_0) + 0x00; ndigits++; } } else { duk_uint_fast32_t match_val; duk_small_uint_t sep_idx; if (ndigits <= 0) { goto reject; } if (part_idx == DUK__PI_MILLISECOND) { /* complete the millisecond field */ while (ndigits < 3) { accum *= 10; ndigits++; } } parts[part_idx] = accum; DUK_DDD(DUK_DDDPRINT("wrote part %ld -> value %ld", (long) part_idx, (long) accum)); accum = 0; ndigits = 0; for (i = 0; i < (duk_small_uint_t) (sizeof(duk__parse_iso8601_seps) / sizeof(duk_uint8_t)); i++) { if (duk__parse_iso8601_seps[i] == ch) { break; } } if (i == (duk_small_uint_t) (sizeof(duk__parse_iso8601_seps) / sizeof(duk_uint8_t))) { DUK_DDD(DUK_DDDPRINT("separator character doesn't match -> reject")); goto reject; } sep_idx = i; match_val = (1UL << part_idx) + (1UL << (sep_idx + 9)); /* match against rule part/sep bits */ for (i = 0; i < (duk_small_uint_t) (sizeof(duk__parse_iso8601_control) / sizeof(duk_uint32_t)); i++) { duk_uint_fast32_t rule = duk__parse_iso8601_control[i]; duk_small_uint_t nextpart; duk_small_uint_t cflags; DUK_DDD(DUK_DDDPRINT("part_idx=%ld, sep_idx=%ld, match_val=0x%08lx, considering rule=0x%08lx", (long) part_idx, (long) sep_idx, (unsigned long) match_val, (unsigned long) rule)); if ((rule & match_val) != match_val) { continue; } DUK__UNPACK_RULE(rule, nextpart, cflags); DUK_DDD(DUK_DDDPRINT("rule match -> part_idx=%ld, sep_idx=%ld, match_val=0x%08lx, " "rule=0x%08lx -> nextpart=%ld, cflags=0x%02lx", (long) part_idx, (long) sep_idx, (unsigned long) match_val, (unsigned long) rule, (long) nextpart, (unsigned long) cflags)); if (cflags & DUK__CF_NEG) { neg_tzoffset = 1; } if (cflags & DUK__CF_ACCEPT) { goto accept; } if (cflags & DUK__CF_ACCEPT_NUL) { DUK_ASSERT(*(p - 1) != (char) 0); if (*p == DUK_ASC_NUL) { goto accept; } goto reject; } part_idx = nextpart; break; } /* rule match */ if (i == (duk_small_uint_t) (sizeof(duk__parse_iso8601_control) / sizeof(duk_uint32_t))) { DUK_DDD(DUK_DDDPRINT("no rule matches -> reject")); goto reject; } if (ch == 0) { /* This shouldn't be necessary, but check just in case * to avoid any chance of overruns. */ DUK_DDD(DUK_DDDPRINT("NUL after rule matching (should not happen) -> reject")); goto reject; } } /* if-digit-else-ctrl */ } /* char loop */ /* We should never exit the loop above. */ DUK_UNREACHABLE(); reject: DUK_DDD(DUK_DDDPRINT("reject")); return 0; accept: DUK_DDD(DUK_DDDPRINT("accept")); /* Apply timezone offset to get the main parts in UTC */ if (neg_year) { parts[DUK__PI_YEAR] = -parts[DUK__PI_YEAR]; } if (neg_tzoffset) { parts[DUK__PI_HOUR] += parts[DUK__PI_TZHOUR]; parts[DUK__PI_MINUTE] += parts[DUK__PI_TZMINUTE]; } else { parts[DUK__PI_HOUR] -= parts[DUK__PI_TZHOUR]; parts[DUK__PI_MINUTE] -= parts[DUK__PI_TZMINUTE]; } parts[DUK__PI_MONTH] -= 1; /* zero-based month */ parts[DUK__PI_DAY] -= 1; /* zero-based day */ /* Use double parts, they tolerate unnormalized time. * * Note: DUK_DATE_IDX_WEEKDAY is initialized with a bogus value (DUK__PI_TZHOUR) * on purpose. It won't be actually used by duk_bi_date_get_timeval_from_dparts(), * but will make the value initialized just in case, and avoid any * potential for Valgrind issues. */ for (i = 0; i < DUK_DATE_IDX_NUM_PARTS; i++) { DUK_DDD(DUK_DDDPRINT("part[%ld] = %ld", (long) i, (long) parts[i])); dparts[i] = parts[i]; } d = duk_bi_date_get_timeval_from_dparts(dparts, 0 /*flags*/); duk_push_number(thr, d); return 1; } /* * Date/time parsing helper. * * Parse a datetime string into a time value. We must first try to parse * the input according to the standard format in E5.1 Section 15.9.1.15. * If that fails, we can try to parse using custom parsing, which can * either be platform neutral (custom code) or platform specific (using * existing platform API calls). * * Note in particular that we must parse whatever toString(), toUTCString(), * and toISOString() can produce; see E5.1 Section 15.9.4.2. * * Returns 1 to allow tail calling. * * There is much room for improvement here with respect to supporting * alternative datetime formats. For instance, V8 parses '2012-01-01' as * UTC and '2012/01/01' as local time. */ DUK_LOCAL duk_ret_t duk__parse_string(duk_hthread *thr, const char *str) { /* XXX: there is a small risk here: because the ISO 8601 parser is * very loose, it may end up parsing some datetime values which * would be better parsed with a platform specific parser. */ DUK_ASSERT(str != NULL); DUK_DDD(DUK_DDDPRINT("parse datetime from string '%s'", (const char *) str)); if (duk__parse_string_iso8601_subset(thr, str) != 0) { return 1; } #if defined(DUK_USE_DATE_PARSE_STRING) /* Contract, either: * - Push value on stack and return 1 * - Don't push anything on stack and return 0 */ if (DUK_USE_DATE_PARSE_STRING(thr, str) != 0) { return 1; } #else /* No platform-specific parsing, this is not an error. */ #endif duk_push_nan(thr); return 1; } /* * Calendar helpers * * Some helpers are used for getters and can operate on normalized values * which can be represented with 32-bit signed integers. Other helpers are * needed by setters and operate on un-normalized double values, must watch * out for non-finite numbers etc. */ DUK_LOCAL duk_uint8_t duk__days_in_month[12] = { (duk_uint8_t) 31, (duk_uint8_t) 28, (duk_uint8_t) 31, (duk_uint8_t) 30, (duk_uint8_t) 31, (duk_uint8_t) 30, (duk_uint8_t) 31, (duk_uint8_t) 31, (duk_uint8_t) 30, (duk_uint8_t) 31, (duk_uint8_t) 30, (duk_uint8_t) 31 }; /* Maximum iteration count for computing UTC-to-local time offset when * creating an ECMAScript time value from local parts. */ #define DUK__LOCAL_TZOFFSET_MAXITER 4 /* Because 'day since epoch' can be negative and is used to compute weekday * using a modulo operation, add this multiple of 7 to avoid negative values * when year is below 1970 epoch. ECMAScript time values are restricted to * +/- 100 million days from epoch, so this adder fits nicely into 32 bits. * Round to a multiple of 7 (= floor(100000000 / 7) * 7) and add margin. */ #define DUK__WEEKDAY_MOD_ADDER (20000000 * 7) /* 0x08583b00 */ DUK_INTERNAL duk_bool_t duk_bi_date_is_leap_year(duk_int_t year) { if ((year % 4) != 0) { return 0; } if ((year % 100) != 0) { return 1; } if ((year % 400) != 0) { return 0; } return 1; } DUK_INTERNAL duk_bool_t duk_bi_date_timeval_in_valid_range(duk_double_t x) { return (x >= -DUK_DATE_MSEC_100M_DAYS && x <= DUK_DATE_MSEC_100M_DAYS); } DUK_INTERNAL duk_bool_t duk_bi_date_timeval_in_leeway_range(duk_double_t x) { return (x >= -DUK_DATE_MSEC_100M_DAYS_LEEWAY && x <= DUK_DATE_MSEC_100M_DAYS_LEEWAY); } DUK_INTERNAL duk_bool_t duk_bi_date_year_in_valid_range(duk_double_t x) { return (x >= DUK_DATE_MIN_ECMA_YEAR && x <= DUK_DATE_MAX_ECMA_YEAR); } DUK_LOCAL duk_double_t duk__timeclip(duk_double_t x) { if (!DUK_ISFINITE(x)) { return DUK_DOUBLE_NAN; } if (!duk_bi_date_timeval_in_valid_range(x)) { return DUK_DOUBLE_NAN; } x = duk_js_tointeger_number(x); /* Here we'd have the option to normalize -0 to +0. */ return x; } /* Integer division which floors also negative values correctly. */ DUK_LOCAL duk_int_t duk__div_floor(duk_int_t a, duk_int_t b) { DUK_ASSERT(b > 0); if (a >= 0) { return a / b; } else { /* e.g. a = -4, b = 5 --> -4 - 5 + 1 / 5 --> -8 / 5 --> -1 * a = -5, b = 5 --> -5 - 5 + 1 / 5 --> -9 / 5 --> -1 * a = -6, b = 5 --> -6 - 5 + 1 / 5 --> -10 / 5 --> -2 */ return (a - b + 1) / b; } } /* Compute day number of the first day of a given year. */ DUK_LOCAL duk_int_t duk__day_from_year(duk_int_t year) { /* Note: in integer arithmetic, (x / 4) is same as floor(x / 4) for non-negative * values, but is incorrect for negative ones. */ return 365 * (year - 1970) + duk__div_floor(year - 1969, 4) - duk__div_floor(year - 1901, 100) + duk__div_floor(year - 1601, 400); } /* Given a day number, determine year and day-within-year. */ DUK_LOCAL duk_int_t duk__year_from_day(duk_int_t day, duk_small_int_t *out_day_within_year) { duk_int_t year; duk_int_t diff_days; /* estimate year upwards (towards positive infinity), then back down; * two iterations should be enough */ if (day >= 0) { year = 1970 + day / 365; } else { year = 1970 + day / 366; } for (;;) { diff_days = duk__day_from_year(year) - day; DUK_DDD(DUK_DDDPRINT("year=%ld day=%ld, diff_days=%ld", (long) year, (long) day, (long) diff_days)); if (diff_days <= 0) { DUK_ASSERT(-diff_days < 366); /* fits into duk_small_int_t */ *out_day_within_year = -diff_days; DUK_DDD(DUK_DDDPRINT("--> year=%ld, day-within-year=%ld", (long) year, (long) *out_day_within_year)); DUK_ASSERT(*out_day_within_year >= 0); DUK_ASSERT(*out_day_within_year < (duk_bi_date_is_leap_year(year) ? 366 : 365)); return year; } /* Note: this is very tricky; we must never 'overshoot' the * correction downwards. */ year -= 1 + (diff_days - 1) / 366; /* conservative */ } } /* Given a (year, month, day-within-month) triple, compute day number. * The input triple is un-normalized and may contain non-finite values. */ DUK_LOCAL duk_double_t duk__make_day(duk_double_t year, duk_double_t month, duk_double_t day) { duk_int_t day_num; duk_bool_t is_leap; duk_small_int_t i, n; /* Assume that year, month, day are all coerced to whole numbers. * They may also be NaN or infinity, in which case this function * must return NaN or infinity to ensure time value becomes NaN. * If 'day' is NaN, the final return will end up returning a NaN, * so it doesn't need to be checked here. */ if (!DUK_ISFINITE(year) || !DUK_ISFINITE(month)) { return DUK_DOUBLE_NAN; } year += DUK_FLOOR(month / 12.0); month = DUK_FMOD(month, 12.0); if (month < 0.0) { /* handle negative values */ month += 12.0; } /* The algorithm in E5.1 Section 15.9.1.12 normalizes month, but * does not normalize the day-of-month (nor check whether or not * it is finite) because it's not necessary for finding the day * number which matches the (year,month) pair. * * We assume that duk__day_from_year() is exact here. * * Without an explicit infinity / NaN check in the beginning, * day_num would be a bogus integer here. * * It's possible for 'year' to be out of integer range here. * If so, we need to return NaN without integer overflow. * This fixes test-bug-setyear-overflow.js. */ if (!duk_bi_date_year_in_valid_range(year)) { DUK_DD(DUK_DDPRINT("year not in ecmascript valid range, avoid integer overflow: %lf", (double) year)); return DUK_DOUBLE_NAN; } day_num = duk__day_from_year((duk_int_t) year); is_leap = duk_bi_date_is_leap_year((duk_int_t) year); n = (duk_small_int_t) month; for (i = 0; i < n; i++) { day_num += duk__days_in_month[i]; if (i == 1 && is_leap) { day_num++; } } /* If 'day' is NaN, returns NaN. */ return (duk_double_t) day_num + day; } /* Split time value into parts. The time value may contain fractions (it may * come from duk_time_to_components() API call) which are truncated. Possible * local time adjustment has already been applied when reading the time value. */ DUK_INTERNAL void duk_bi_date_timeval_to_parts(duk_double_t d, duk_int_t *parts, duk_double_t *dparts, duk_small_uint_t flags) { duk_double_t d1, d2; duk_int_t t1, t2; duk_int_t day_since_epoch; duk_int_t year; /* does not fit into 16 bits */ duk_small_int_t day_in_year; duk_small_int_t month; duk_small_int_t day; duk_small_int_t dim; duk_int_t jan1_since_epoch; duk_small_int_t jan1_weekday; duk_int_t equiv_year; duk_small_uint_t i; duk_bool_t is_leap; duk_small_int_t arridx; DUK_ASSERT(DUK_ISFINITE(d)); /* caller checks */ d = DUK_FLOOR(d); /* remove fractions if present */ DUK_ASSERT(duk_double_equals(DUK_FLOOR(d), d)); /* The timevalue must be in valid ECMAScript range, but since a local * time offset can be applied, we need to allow a +/- 24h leeway to * the value. In other words, although the UTC time is within the * ECMAScript range, the local part values can be just outside of it. */ DUK_UNREF(duk_bi_date_timeval_in_leeway_range); DUK_ASSERT(duk_bi_date_timeval_in_leeway_range(d)); /* These computations are guaranteed to be exact for the valid * E5 time value range, assuming milliseconds without fractions. */ d1 = (duk_double_t) DUK_FMOD(d, (double) DUK_DATE_MSEC_DAY); if (d1 < 0.0) { /* deal with negative values */ d1 += (duk_double_t) DUK_DATE_MSEC_DAY; } d2 = DUK_FLOOR((double) (d / (duk_double_t) DUK_DATE_MSEC_DAY)); DUK_ASSERT(duk_double_equals(d2 * ((duk_double_t) DUK_DATE_MSEC_DAY) + d1, d)); /* now expected to fit into a 32-bit integer */ t1 = (duk_int_t) d1; t2 = (duk_int_t) d2; day_since_epoch = t2; DUK_ASSERT(duk_double_equals((duk_double_t) t1, d1)); DUK_ASSERT(duk_double_equals((duk_double_t) t2, d2)); /* t1 = milliseconds within day (fits 32 bit) * t2 = day number from epoch (fits 32 bit, may be negative) */ parts[DUK_DATE_IDX_MILLISECOND] = t1 % 1000; t1 /= 1000; parts[DUK_DATE_IDX_SECOND] = t1 % 60; t1 /= 60; parts[DUK_DATE_IDX_MINUTE] = t1 % 60; t1 /= 60; parts[DUK_DATE_IDX_HOUR] = t1; DUK_ASSERT(parts[DUK_DATE_IDX_MILLISECOND] >= 0 && parts[DUK_DATE_IDX_MILLISECOND] <= 999); DUK_ASSERT(parts[DUK_DATE_IDX_SECOND] >= 0 && parts[DUK_DATE_IDX_SECOND] <= 59); DUK_ASSERT(parts[DUK_DATE_IDX_MINUTE] >= 0 && parts[DUK_DATE_IDX_MINUTE] <= 59); DUK_ASSERT(parts[DUK_DATE_IDX_HOUR] >= 0 && parts[DUK_DATE_IDX_HOUR] <= 23); DUK_DDD(DUK_DDDPRINT("d=%lf, d1=%lf, d2=%lf, t1=%ld, t2=%ld, parts: hour=%ld min=%ld sec=%ld msec=%ld", (double) d, (double) d1, (double) d2, (long) t1, (long) t2, (long) parts[DUK_DATE_IDX_HOUR], (long) parts[DUK_DATE_IDX_MINUTE], (long) parts[DUK_DATE_IDX_SECOND], (long) parts[DUK_DATE_IDX_MILLISECOND])); /* This assert depends on the input parts representing time inside * the ECMAScript range. */ DUK_ASSERT(t2 + DUK__WEEKDAY_MOD_ADDER >= 0); parts[DUK_DATE_IDX_WEEKDAY] = (t2 + 4 + DUK__WEEKDAY_MOD_ADDER) % 7; /* E5.1 Section 15.9.1.6 */ DUK_ASSERT(parts[DUK_DATE_IDX_WEEKDAY] >= 0 && parts[DUK_DATE_IDX_WEEKDAY] <= 6); year = duk__year_from_day(t2, &day_in_year); day = day_in_year; is_leap = duk_bi_date_is_leap_year(year); for (month = 0; month < 12; month++) { dim = duk__days_in_month[month]; if (month == 1 && is_leap) { dim++; } DUK_DDD(DUK_DDDPRINT("month=%ld, dim=%ld, day=%ld", (long) month, (long) dim, (long) day)); if (day < dim) { break; } day -= dim; } DUK_DDD(DUK_DDDPRINT("final month=%ld", (long) month)); DUK_ASSERT(month >= 0 && month <= 11); DUK_ASSERT(day >= 0 && day <= 31); /* Equivalent year mapping, used to avoid DST trouble when platform * may fail to provide reasonable DST answers for dates outside the * ordinary range (e.g. 1970-2038). An equivalent year has the same * leap-year-ness as the original year and begins on the same weekday * (Jan 1). * * The year 2038 is avoided because there seem to be problems with it * on some platforms. The year 1970 is also avoided as there were * practical problems with it; an equivalent year is used for it too, * which breaks some DST computations for 1970 right now, see e.g. * test-bi-date-tzoffset-brute-fi.js. */ if ((flags & DUK_DATE_FLAG_EQUIVYEAR) && (year < 1971 || year > 2037)) { DUK_ASSERT(is_leap == 0 || is_leap == 1); jan1_since_epoch = day_since_epoch - day_in_year; /* day number for Jan 1 since epoch */ DUK_ASSERT(jan1_since_epoch + DUK__WEEKDAY_MOD_ADDER >= 0); jan1_weekday = (jan1_since_epoch + 4 + DUK__WEEKDAY_MOD_ADDER) % 7; /* E5.1 Section 15.9.1.6 */ DUK_ASSERT(jan1_weekday >= 0 && jan1_weekday <= 6); arridx = jan1_weekday; if (is_leap) { arridx += 7; } DUK_ASSERT(arridx >= 0 && arridx < (duk_small_int_t) (sizeof(duk__date_equivyear) / sizeof(duk_uint8_t))); equiv_year = (duk_int_t) duk__date_equivyear[arridx] + 1970; year = equiv_year; DUK_DDD(DUK_DDDPRINT("equiv year mapping, year=%ld, day_in_year=%ld, day_since_epoch=%ld, " "jan1_since_epoch=%ld, jan1_weekday=%ld -> equiv year %ld", (long) year, (long) day_in_year, (long) day_since_epoch, (long) jan1_since_epoch, (long) jan1_weekday, (long) equiv_year)); } parts[DUK_DATE_IDX_YEAR] = year; parts[DUK_DATE_IDX_MONTH] = month; parts[DUK_DATE_IDX_DAY] = day; if (flags & DUK_DATE_FLAG_ONEBASED) { parts[DUK_DATE_IDX_MONTH]++; /* zero-based -> one-based */ parts[DUK_DATE_IDX_DAY]++; /* -""- */ } if (dparts != NULL) { for (i = 0; i < DUK_DATE_IDX_NUM_PARTS; i++) { dparts[i] = (duk_double_t) parts[i]; } } } /* Compute time value from (double) parts. The parts can be either UTC * or local time; if local, they need to be (conceptually) converted into * UTC time. The parts may represent valid or invalid time, and may be * wildly out of range (but may cancel each other and still come out in * the valid Date range). */ DUK_INTERNAL duk_double_t duk_bi_date_get_timeval_from_dparts(duk_double_t *dparts, duk_small_uint_t flags) { #if defined(DUK_USE_PARANOID_DATE_COMPUTATION) /* See comments below on MakeTime why these are volatile. */ volatile duk_double_t tmp_time; volatile duk_double_t tmp_day; volatile duk_double_t d; #else duk_double_t tmp_time; duk_double_t tmp_day; duk_double_t d; #endif duk_small_uint_t i; duk_int_t tzoff, tzoffprev1, tzoffprev2; /* Expects 'this' at top of stack on entry. */ /* Coerce all finite parts with ToInteger(). ToInteger() must not * be called for NaN/Infinity because it will convert e.g. NaN to * zero. If ToInteger() has already been called, this has no side * effects and is idempotent. * * Don't read dparts[DUK_DATE_IDX_WEEKDAY]; it will cause Valgrind * issues if the value is uninitialized. */ for (i = 0; i <= DUK_DATE_IDX_MILLISECOND; i++) { /* SCANBUILD: scan-build complains here about assigned value * being garbage or undefined. This is correct but operating * on undefined values has no ill effect and is ignored by the * caller in the case where this happens. */ d = dparts[i]; if (DUK_ISFINITE(d)) { dparts[i] = duk_js_tointeger_number(d); } } /* Use explicit steps in computation to try to ensure that * computation happens with intermediate results coerced to * double values (instead of using something more accurate). * E.g. E5.1 Section 15.9.1.11 requires use of IEEE 754 * rules (= ECMAScript '+' and '*' operators). * * Without 'volatile' even this approach fails on some platform * and compiler combinations. For instance, gcc 4.8.1 on Ubuntu * 64-bit, with -m32 and without -std=c99, test-bi-date-canceling.js * would fail because of some optimizations when computing tmp_time * (MakeTime below). Adding 'volatile' to tmp_time solved this * particular problem (annoyingly, also adding debug prints or * running the executable under valgrind hides it). */ /* MakeTime */ tmp_time = 0.0; tmp_time += dparts[DUK_DATE_IDX_HOUR] * ((duk_double_t) DUK_DATE_MSEC_HOUR); tmp_time += dparts[DUK_DATE_IDX_MINUTE] * ((duk_double_t) DUK_DATE_MSEC_MINUTE); tmp_time += dparts[DUK_DATE_IDX_SECOND] * ((duk_double_t) DUK_DATE_MSEC_SECOND); tmp_time += dparts[DUK_DATE_IDX_MILLISECOND]; /* MakeDay */ tmp_day = duk__make_day(dparts[DUK_DATE_IDX_YEAR], dparts[DUK_DATE_IDX_MONTH], dparts[DUK_DATE_IDX_DAY]); /* MakeDate */ d = tmp_day * ((duk_double_t) DUK_DATE_MSEC_DAY) + tmp_time; DUK_DDD(DUK_DDDPRINT("time=%lf day=%lf --> timeval=%lf", (double) tmp_time, (double) tmp_day, (double) d)); /* Optional UTC conversion. */ if (flags & DUK_DATE_FLAG_LOCALTIME) { /* DUK_USE_DATE_GET_LOCAL_TZOFFSET() needs to be called with a * time value computed from UTC parts. At this point we only * have 'd' which is a time value computed from local parts, so * it is off by the UTC-to-local time offset which we don't know * yet. The current solution for computing the UTC-to-local * time offset is to iterate a few times and detect a fixed * point or a two-cycle loop (or a sanity iteration limit), * see test-bi-date-local-parts.js and test-bi-date-tzoffset-basic-fi.js. * * E5.1 Section 15.9.1.9: * UTC(t) = t - LocalTZA - DaylightSavingTA(t - LocalTZA) * * For NaN/inf, DUK_USE_DATE_GET_LOCAL_TZOFFSET() returns 0. */ #if 0 /* Old solution: don't iterate, incorrect */ tzoff = DUK_USE_DATE_GET_LOCAL_TZOFFSET(d); DUK_DDD(DUK_DDDPRINT("tzoffset w/o iteration, tzoff=%ld", (long) tzoff)); d -= tzoff * 1000L; DUK_UNREF(tzoffprev1); DUK_UNREF(tzoffprev2); #endif /* Iteration solution */ tzoff = 0; tzoffprev1 = 999999999L; /* invalid value which never matches */ for (i = 0; i < DUK__LOCAL_TZOFFSET_MAXITER; i++) { tzoffprev2 = tzoffprev1; tzoffprev1 = tzoff; tzoff = DUK_USE_DATE_GET_LOCAL_TZOFFSET(d - tzoff * 1000L); DUK_DDD(DUK_DDDPRINT("tzoffset iteration, i=%d, tzoff=%ld, tzoffprev1=%ld tzoffprev2=%ld", (int) i, (long) tzoff, (long) tzoffprev1, (long) tzoffprev2)); if (tzoff == tzoffprev1) { DUK_DDD(DUK_DDDPRINT("tzoffset iteration finished, i=%d, tzoff=%ld, tzoffprev1=%ld, tzoffprev2=%ld", (int) i, (long) tzoff, (long) tzoffprev1, (long) tzoffprev2)); break; } else if (tzoff == tzoffprev2) { /* Two value cycle, see e.g. test-bi-date-tzoffset-basic-fi.js. * In these cases, favor a higher tzoffset to get a consistent * result which is independent of iteration count. Not sure if * this is a generically correct solution. */ DUK_DDD(DUK_DDDPRINT( "tzoffset iteration two-value cycle, i=%d, tzoff=%ld, tzoffprev1=%ld, tzoffprev2=%ld", (int) i, (long) tzoff, (long) tzoffprev1, (long) tzoffprev2)); if (tzoffprev1 > tzoff) { tzoff = tzoffprev1; } break; } } DUK_DDD(DUK_DDDPRINT("tzoffset iteration, tzoff=%ld", (long) tzoff)); d -= tzoff * 1000L; } /* TimeClip(), which also handles Infinity -> NaN conversion */ d = duk__timeclip(d); return d; } /* * API oriented helpers */ /* Push 'this' binding, check that it is a Date object; then push the * internal time value. At the end, stack is: [ ... this timeval ]. * Returns the time value. Local time adjustment is done if requested. */ DUK_LOCAL duk_double_t duk__push_this_get_timeval_tzoffset(duk_hthread *thr, duk_small_uint_t flags, duk_int_t *out_tzoffset) { duk_hobject *h; duk_double_t d; duk_int_t tzoffset = 0; duk_push_this(thr); h = duk_get_hobject(thr, -1); /* XXX: getter with class check, useful in built-ins */ if (h == NULL || DUK_HOBJECT_GET_CLASS_NUMBER(h) != DUK_HOBJECT_CLASS_DATE) { DUK_ERROR_TYPE(thr, "expected Date"); DUK_WO_NORETURN(return 0.0;); } duk_xget_owndataprop_stridx_short(thr, -1, DUK_STRIDX_INT_VALUE); d = duk_to_number_m1(thr); duk_pop(thr); if (DUK_ISNAN(d)) { if (flags & DUK_DATE_FLAG_NAN_TO_ZERO) { d = 0.0; } if (flags & DUK_DATE_FLAG_NAN_TO_RANGE_ERROR) { DUK_ERROR_RANGE(thr, "Invalid Date"); DUK_WO_NORETURN(return 0.0;); } } /* if no NaN handling flag, may still be NaN here, but not Inf */ DUK_ASSERT(!DUK_ISINF(d)); if (flags & DUK_DATE_FLAG_LOCALTIME) { /* Note: DST adjustment is determined using UTC time. * If 'd' is NaN, tzoffset will be 0. */ tzoffset = DUK_USE_DATE_GET_LOCAL_TZOFFSET(d); /* seconds */ d += tzoffset * 1000L; } if (out_tzoffset) { *out_tzoffset = tzoffset; } /* [ ... this ] */ return d; } DUK_LOCAL duk_double_t duk__push_this_get_timeval(duk_hthread *thr, duk_small_uint_t flags) { return duk__push_this_get_timeval_tzoffset(thr, flags, NULL); } /* Set timeval to 'this' from dparts, push the new time value onto the * value stack and return 1 (caller can then tail call us). Expects * the value stack to contain 'this' on the stack top. */ DUK_LOCAL duk_ret_t duk__set_this_timeval_from_dparts(duk_hthread *thr, duk_double_t *dparts, duk_small_uint_t flags) { duk_double_t d; /* [ ... this ] */ d = duk_bi_date_get_timeval_from_dparts(dparts, flags); duk_push_number(thr, d); /* -> [ ... this timeval_new ] */ duk_dup_top(thr); /* -> [ ... this timeval_new timeval_new ] */ /* Must force write because e.g. .setYear() must work even when * the Date instance is frozen. */ duk_xdef_prop_stridx_short(thr, -3, DUK_STRIDX_INT_VALUE, DUK_PROPDESC_FLAGS_W); /* Stack top: new time value, return 1 to allow tail calls. */ return 1; } /* 'out_buf' must be at least DUK_BI_DATE_ISO8601_BUFSIZE long. */ DUK_LOCAL void duk__format_parts_iso8601(duk_int_t *parts, duk_int_t tzoffset, duk_small_uint_t flags, duk_uint8_t *out_buf) { char yearstr[8]; /* "-123456\0" */ char tzstr[8]; /* "+11:22\0" */ char sep = (flags & DUK_DATE_FLAG_SEP_T) ? DUK_ASC_UC_T : DUK_ASC_SPACE; DUK_ASSERT(parts[DUK_DATE_IDX_MONTH] >= 1 && parts[DUK_DATE_IDX_MONTH] <= 12); DUK_ASSERT(parts[DUK_DATE_IDX_DAY] >= 1 && parts[DUK_DATE_IDX_DAY] <= 31); DUK_ASSERT(parts[DUK_DATE_IDX_YEAR] >= -999999 && parts[DUK_DATE_IDX_YEAR] <= 999999); /* Note: %06d for positive value, %07d for negative value to include * sign and 6 digits. */ DUK_SNPRINTF(yearstr, sizeof(yearstr), (parts[DUK_DATE_IDX_YEAR] >= 0 && parts[DUK_DATE_IDX_YEAR] <= 9999) ? "%04ld" : ((parts[DUK_DATE_IDX_YEAR] >= 0) ? "+%06ld" : "%07ld"), (long) parts[DUK_DATE_IDX_YEAR]); yearstr[sizeof(yearstr) - 1] = (char) 0; if (flags & DUK_DATE_FLAG_LOCALTIME) { /* tzoffset seconds are dropped; 16 bits suffice for * time offset in minutes */ const char *fmt; duk_small_int_t tmp, arg_hours, arg_minutes; if (tzoffset >= 0) { tmp = tzoffset; fmt = "+%02d:%02d"; } else { tmp = -tzoffset; fmt = "-%02d:%02d"; } tmp = tmp / 60; arg_hours = tmp / 60; arg_minutes = tmp % 60; DUK_ASSERT(arg_hours <= 24); /* Even less is actually guaranteed for a valid tzoffset. */ arg_hours = arg_hours & 0x3f; /* For [0,24] this is a no-op, but fixes GCC 7 warning, see https://github.com/svaarala/duktape/issues/1602. */ DUK_SNPRINTF(tzstr, sizeof(tzstr), fmt, (int) arg_hours, (int) arg_minutes); tzstr[sizeof(tzstr) - 1] = (char) 0; } else { tzstr[0] = DUK_ASC_UC_Z; tzstr[1] = (char) 0; } /* Unlike year, the other parts fit into 16 bits so %d format * is portable. */ if ((flags & DUK_DATE_FLAG_TOSTRING_DATE) && (flags & DUK_DATE_FLAG_TOSTRING_TIME)) { DUK_SPRINTF((char *) out_buf, "%s-%02d-%02d%c%02d:%02d:%02d.%03d%s", (const char *) yearstr, (int) parts[DUK_DATE_IDX_MONTH], (int) parts[DUK_DATE_IDX_DAY], (int) sep, (int) parts[DUK_DATE_IDX_HOUR], (int) parts[DUK_DATE_IDX_MINUTE], (int) parts[DUK_DATE_IDX_SECOND], (int) parts[DUK_DATE_IDX_MILLISECOND], (const char *) tzstr); } else if (flags & DUK_DATE_FLAG_TOSTRING_DATE) { DUK_SPRINTF((char *) out_buf, "%s-%02d-%02d", (const char *) yearstr, (int) parts[DUK_DATE_IDX_MONTH], (int) parts[DUK_DATE_IDX_DAY]); } else { DUK_ASSERT(flags & DUK_DATE_FLAG_TOSTRING_TIME); DUK_SPRINTF((char *) out_buf, "%02d:%02d:%02d.%03d%s", (int) parts[DUK_DATE_IDX_HOUR], (int) parts[DUK_DATE_IDX_MINUTE], (int) parts[DUK_DATE_IDX_SECOND], (int) parts[DUK_DATE_IDX_MILLISECOND], (const char *) tzstr); } } /* Helper for string conversion calls: check 'this' binding, get the * internal time value, and format date and/or time in a few formats. * Return value allows tail calls. */ DUK_LOCAL duk_ret_t duk__to_string_helper(duk_hthread *thr, duk_small_uint_t flags) { duk_double_t d; duk_int_t parts[DUK_DATE_IDX_NUM_PARTS]; duk_int_t tzoffset; /* seconds, doesn't fit into 16 bits */ duk_bool_t rc; duk_uint8_t buf[DUK_BI_DATE_ISO8601_BUFSIZE]; DUK_UNREF(rc); /* unreferenced with some options */ d = duk__push_this_get_timeval_tzoffset(thr, flags, &tzoffset); if (DUK_ISNAN(d)) { duk_push_hstring_stridx(thr, DUK_STRIDX_INVALID_DATE); return 1; } DUK_ASSERT(DUK_ISFINITE(d)); /* formatters always get one-based month/day-of-month */ duk_bi_date_timeval_to_parts(d, parts, NULL, DUK_DATE_FLAG_ONEBASED); DUK_ASSERT(parts[DUK_DATE_IDX_MONTH] >= 1 && parts[DUK_DATE_IDX_MONTH] <= 12); DUK_ASSERT(parts[DUK_DATE_IDX_DAY] >= 1 && parts[DUK_DATE_IDX_DAY] <= 31); if (flags & DUK_DATE_FLAG_TOSTRING_LOCALE) { /* try locale specific formatter; if it refuses to format the * string, fall back to an ISO 8601 formatted value in local * time. */ #if defined(DUK_USE_DATE_FORMAT_STRING) /* Contract, either: * - Push string to value stack and return 1 * - Don't push anything and return 0 */ rc = DUK_USE_DATE_FORMAT_STRING(thr, parts, tzoffset, flags); if (rc != 0) { return 1; } #else /* No locale specific formatter; this is OK, we fall back * to ISO 8601. */ #endif } /* Different calling convention than above used because the helper * is shared. */ duk__format_parts_iso8601(parts, tzoffset, flags, buf); duk_push_string(thr, (const char *) buf); return 1; } /* Helper for component getter calls: check 'this' binding, get the * internal time value, split it into parts (either as UTC time or * local time), push a specified component as a return value to the * value stack and return 1 (caller can then tail call us). */ DUK_LOCAL duk_ret_t duk__get_part_helper(duk_hthread *thr, duk_small_uint_t flags_and_idx) { duk_double_t d; duk_int_t parts[DUK_DATE_IDX_NUM_PARTS]; duk_small_uint_t idx_part = (duk_small_uint_t) (flags_and_idx >> DUK_DATE_FLAG_VALUE_SHIFT); /* unpack args */ DUK_ASSERT_DISABLE(idx_part >= 0); /* unsigned */ DUK_ASSERT(idx_part < DUK_DATE_IDX_NUM_PARTS); d = duk__push_this_get_timeval(thr, flags_and_idx); if (DUK_ISNAN(d)) { duk_push_nan(thr); return 1; } DUK_ASSERT(DUK_ISFINITE(d)); duk_bi_date_timeval_to_parts(d, parts, NULL, flags_and_idx); /* no need to mask idx portion */ /* Setter APIs detect special year numbers (0...99) and apply a +1900 * only in certain cases. The legacy getYear() getter applies -1900 * unconditionally. */ duk_push_int(thr, (flags_and_idx & DUK_DATE_FLAG_SUB1900) ? parts[idx_part] - 1900 : parts[idx_part]); return 1; } /* Helper for component setter calls: check 'this' binding, get the * internal time value, split it into parts (either as UTC time or * local time), modify one or more components as specified, recompute * the time value, set it as the internal value. Finally, push the * new time value as a return value to the value stack and return 1 * (caller can then tail call us). */ DUK_LOCAL duk_ret_t duk__set_part_helper(duk_hthread *thr, duk_small_uint_t flags_and_maxnargs) { duk_double_t d; duk_int_t parts[DUK_DATE_IDX_NUM_PARTS]; duk_double_t dparts[DUK_DATE_IDX_NUM_PARTS]; duk_idx_t nargs; duk_small_uint_t maxnargs = (duk_small_uint_t) (flags_and_maxnargs >> DUK_DATE_FLAG_VALUE_SHIFT); /* unpack args */ duk_small_uint_t idx_first, idx; duk_small_uint_t i; nargs = duk_get_top(thr); d = duk__push_this_get_timeval(thr, flags_and_maxnargs); DUK_ASSERT(DUK_ISFINITE(d) || DUK_ISNAN(d)); if (DUK_ISFINITE(d)) { duk_bi_date_timeval_to_parts(d, parts, dparts, flags_and_maxnargs); } else { /* NaN timevalue: we need to coerce the arguments, but * the resulting internal timestamp needs to remain NaN. * This works but is not pretty: parts and dparts will * be partially uninitialized, but we only write to them. */ } /* * Determining which datetime components to overwrite based on * stack arguments is a bit complicated, but important to factor * out from setters themselves for compactness. * * If DUK_DATE_FLAG_TIMESETTER, maxnargs indicates setter type: * * 1 -> millisecond * 2 -> second, [millisecond] * 3 -> minute, [second], [millisecond] * 4 -> hour, [minute], [second], [millisecond] * * Else: * * 1 -> date * 2 -> month, [date] * 3 -> year, [month], [date] * * By comparing nargs and maxnargs (and flags) we know which * components to override. We rely on part index ordering. */ if (flags_and_maxnargs & DUK_DATE_FLAG_TIMESETTER) { DUK_ASSERT(maxnargs >= 1 && maxnargs <= 4); idx_first = DUK_DATE_IDX_MILLISECOND - (maxnargs - 1); } else { DUK_ASSERT(maxnargs >= 1 && maxnargs <= 3); idx_first = DUK_DATE_IDX_DAY - (maxnargs - 1); } DUK_ASSERT_DISABLE(idx_first >= 0); /* unsigned */ DUK_ASSERT(idx_first < DUK_DATE_IDX_NUM_PARTS); for (i = 0; i < maxnargs; i++) { if ((duk_idx_t) i >= nargs) { /* no argument given -> leave components untouched */ break; } idx = idx_first + i; DUK_ASSERT_DISABLE(idx >= 0); /* unsigned */ DUK_ASSERT(idx < DUK_DATE_IDX_NUM_PARTS); if (idx == DUK_DATE_IDX_YEAR && (flags_and_maxnargs & DUK_DATE_FLAG_YEAR_FIXUP)) { duk__twodigit_year_fixup(thr, (duk_idx_t) i); } dparts[idx] = duk_to_number(thr, (duk_idx_t) i); if (idx == DUK_DATE_IDX_DAY) { /* Day-of-month is one-based in the API, but zero-based * internally, so fix here. Note that month is zero-based * both in the API and internally. */ /* SCANBUILD: complains about use of uninitialized values. * The complaint is correct, but operating in undefined * values here is intentional in some cases and the caller * ignores the results. */ dparts[idx] -= 1.0; } } /* Leaves new timevalue on stack top and returns 1, which is correct * for part setters. */ if (DUK_ISFINITE(d)) { return duk__set_this_timeval_from_dparts(thr, dparts, flags_and_maxnargs); } else { /* Internal timevalue is already NaN, so don't touch it. */ duk_push_nan(thr); return 1; } } /* Apply ToNumber() to specified index; if ToInteger(val) in [0,99], add * 1900 and replace value at idx_val. */ DUK_LOCAL void duk__twodigit_year_fixup(duk_hthread *thr, duk_idx_t idx_val) { duk_double_t d; /* XXX: idx_val would fit into 16 bits, but using duk_small_uint_t * might not generate better code due to casting. */ /* E5 Sections 15.9.3.1, B.2.4, B.2.5 */ duk_to_number(thr, idx_val); if (duk_is_nan(thr, idx_val)) { return; } duk_dup(thr, idx_val); duk_to_int(thr, -1); d = duk_get_number(thr, -1); /* get as double to handle huge numbers correctly */ if (d >= 0.0 && d <= 99.0) { d += 1900.0; duk_push_number(thr, d); duk_replace(thr, idx_val); } duk_pop(thr); } /* Set datetime parts from stack arguments, defaulting any missing values. * Day-of-week is not set; it is not required when setting the time value. */ DUK_LOCAL void duk__set_parts_from_args(duk_hthread *thr, duk_double_t *dparts, duk_idx_t nargs) { duk_double_t d; duk_small_uint_t i; duk_small_uint_t idx; /* Causes a ToNumber() coercion, but doesn't break coercion order since * year is coerced first anyway. */ duk__twodigit_year_fixup(thr, 0); /* There are at most 7 args, but we use 8 here so that also * DUK_DATE_IDX_WEEKDAY gets initialized (to zero) to avoid the potential * for any Valgrind gripes later. */ for (i = 0; i < 8; i++) { /* Note: rely on index ordering */ idx = DUK_DATE_IDX_YEAR + i; if ((duk_idx_t) i < nargs) { d = duk_to_number(thr, (duk_idx_t) i); if (idx == DUK_DATE_IDX_DAY) { /* Convert day from one-based to zero-based (internal). This may * cause the day part to be negative, which is OK. */ d -= 1.0; } } else { /* All components default to 0 except day-of-month which defaults * to 1. However, because our internal day-of-month is zero-based, * it also defaults to zero here. */ d = 0.0; } dparts[idx] = d; } DUK_DDD(DUK_DDDPRINT("parts from args -> %lf %lf %lf %lf %lf %lf %lf %lf", (double) dparts[0], (double) dparts[1], (double) dparts[2], (double) dparts[3], (double) dparts[4], (double) dparts[5], (double) dparts[6], (double) dparts[7])); } /* * Indirect magic value lookup for Date methods. * * Date methods don't put their control flags into the function magic value * because they wouldn't fit into a LIGHTFUNC's magic field. Instead, the * magic value is set to an index pointing to the array of control flags * below. * * This must be kept in strict sync with genbuiltins.py! */ static duk_uint16_t duk__date_magics[] = { /* 0: toString */ DUK_DATE_FLAG_TOSTRING_DATE + DUK_DATE_FLAG_TOSTRING_TIME + DUK_DATE_FLAG_LOCALTIME, /* 1: toDateString */ DUK_DATE_FLAG_TOSTRING_DATE + DUK_DATE_FLAG_LOCALTIME, /* 2: toTimeString */ DUK_DATE_FLAG_TOSTRING_TIME + DUK_DATE_FLAG_LOCALTIME, /* 3: toLocaleString */ DUK_DATE_FLAG_TOSTRING_DATE + DUK_DATE_FLAG_TOSTRING_TIME + DUK_DATE_FLAG_TOSTRING_LOCALE + DUK_DATE_FLAG_LOCALTIME, /* 4: toLocaleDateString */ DUK_DATE_FLAG_TOSTRING_DATE + DUK_DATE_FLAG_TOSTRING_LOCALE + DUK_DATE_FLAG_LOCALTIME, /* 5: toLocaleTimeString */ DUK_DATE_FLAG_TOSTRING_TIME + DUK_DATE_FLAG_TOSTRING_LOCALE + DUK_DATE_FLAG_LOCALTIME, /* 6: toUTCString */ DUK_DATE_FLAG_TOSTRING_DATE + DUK_DATE_FLAG_TOSTRING_TIME, /* 7: toISOString */ DUK_DATE_FLAG_TOSTRING_DATE + DUK_DATE_FLAG_TOSTRING_TIME + DUK_DATE_FLAG_NAN_TO_RANGE_ERROR + DUK_DATE_FLAG_SEP_T, /* 8: getFullYear */ DUK_DATE_FLAG_LOCALTIME + (DUK_DATE_IDX_YEAR << DUK_DATE_FLAG_VALUE_SHIFT), /* 9: getUTCFullYear */ 0 + (DUK_DATE_IDX_YEAR << DUK_DATE_FLAG_VALUE_SHIFT), /* 10: getMonth */ DUK_DATE_FLAG_LOCALTIME + (DUK_DATE_IDX_MONTH << DUK_DATE_FLAG_VALUE_SHIFT), /* 11: getUTCMonth */ 0 + (DUK_DATE_IDX_MONTH << DUK_DATE_FLAG_VALUE_SHIFT), /* 12: getDate */ DUK_DATE_FLAG_ONEBASED + DUK_DATE_FLAG_LOCALTIME + (DUK_DATE_IDX_DAY << DUK_DATE_FLAG_VALUE_SHIFT), /* 13: getUTCDate */ DUK_DATE_FLAG_ONEBASED + (DUK_DATE_IDX_DAY << DUK_DATE_FLAG_VALUE_SHIFT), /* 14: getDay */ DUK_DATE_FLAG_LOCALTIME + (DUK_DATE_IDX_WEEKDAY << DUK_DATE_FLAG_VALUE_SHIFT), /* 15: getUTCDay */ 0 + (DUK_DATE_IDX_WEEKDAY << DUK_DATE_FLAG_VALUE_SHIFT), /* 16: getHours */ DUK_DATE_FLAG_LOCALTIME + (DUK_DATE_IDX_HOUR << DUK_DATE_FLAG_VALUE_SHIFT), /* 17: getUTCHours */ 0 + (DUK_DATE_IDX_HOUR << DUK_DATE_FLAG_VALUE_SHIFT), /* 18: getMinutes */ DUK_DATE_FLAG_LOCALTIME + (DUK_DATE_IDX_MINUTE << DUK_DATE_FLAG_VALUE_SHIFT), /* 19: getUTCMinutes */ 0 + (DUK_DATE_IDX_MINUTE << DUK_DATE_FLAG_VALUE_SHIFT), /* 20: getSeconds */ DUK_DATE_FLAG_LOCALTIME + (DUK_DATE_IDX_SECOND << DUK_DATE_FLAG_VALUE_SHIFT), /* 21: getUTCSeconds */ 0 + (DUK_DATE_IDX_SECOND << DUK_DATE_FLAG_VALUE_SHIFT), /* 22: getMilliseconds */ DUK_DATE_FLAG_LOCALTIME + (DUK_DATE_IDX_MILLISECOND << DUK_DATE_FLAG_VALUE_SHIFT), /* 23: getUTCMilliseconds */ 0 + (DUK_DATE_IDX_MILLISECOND << DUK_DATE_FLAG_VALUE_SHIFT), /* 24: setMilliseconds */ DUK_DATE_FLAG_TIMESETTER + DUK_DATE_FLAG_LOCALTIME + (1 << DUK_DATE_FLAG_VALUE_SHIFT), /* 25: setUTCMilliseconds */ DUK_DATE_FLAG_TIMESETTER + (1 << DUK_DATE_FLAG_VALUE_SHIFT), /* 26: setSeconds */ DUK_DATE_FLAG_TIMESETTER + DUK_DATE_FLAG_LOCALTIME + (2 << DUK_DATE_FLAG_VALUE_SHIFT), /* 27: setUTCSeconds */ DUK_DATE_FLAG_TIMESETTER + (2 << DUK_DATE_FLAG_VALUE_SHIFT), /* 28: setMinutes */ DUK_DATE_FLAG_TIMESETTER + DUK_DATE_FLAG_LOCALTIME + (3 << DUK_DATE_FLAG_VALUE_SHIFT), /* 29: setUTCMinutes */ DUK_DATE_FLAG_TIMESETTER + (3 << DUK_DATE_FLAG_VALUE_SHIFT), /* 30: setHours */ DUK_DATE_FLAG_TIMESETTER + DUK_DATE_FLAG_LOCALTIME + (4 << DUK_DATE_FLAG_VALUE_SHIFT), /* 31: setUTCHours */ DUK_DATE_FLAG_TIMESETTER + (4 << DUK_DATE_FLAG_VALUE_SHIFT), /* 32: setDate */ DUK_DATE_FLAG_LOCALTIME + (1 << DUK_DATE_FLAG_VALUE_SHIFT), /* 33: setUTCDate */ 0 + (1 << DUK_DATE_FLAG_VALUE_SHIFT), /* 34: setMonth */ DUK_DATE_FLAG_LOCALTIME + (2 << DUK_DATE_FLAG_VALUE_SHIFT), /* 35: setUTCMonth */ 0 + (2 << DUK_DATE_FLAG_VALUE_SHIFT), /* 36: setFullYear */ DUK_DATE_FLAG_NAN_TO_ZERO + DUK_DATE_FLAG_LOCALTIME + (3 << DUK_DATE_FLAG_VALUE_SHIFT), /* 37: setUTCFullYear */ DUK_DATE_FLAG_NAN_TO_ZERO + (3 << DUK_DATE_FLAG_VALUE_SHIFT), /* 38: getYear */ DUK_DATE_FLAG_LOCALTIME + DUK_DATE_FLAG_SUB1900 + (DUK_DATE_IDX_YEAR << DUK_DATE_FLAG_VALUE_SHIFT), /* 39: setYear */ DUK_DATE_FLAG_NAN_TO_ZERO + DUK_DATE_FLAG_YEAR_FIXUP + (3 << DUK_DATE_FLAG_VALUE_SHIFT), }; DUK_LOCAL duk_small_uint_t duk__date_get_indirect_magic(duk_hthread *thr) { duk_small_uint_t magicidx = (duk_small_uint_t) duk_get_current_magic(thr); DUK_ASSERT(magicidx < (duk_small_int_t) (sizeof(duk__date_magics) / sizeof(duk_uint16_t))); return (duk_small_uint_t) duk__date_magics[magicidx]; } #if defined(DUK_USE_DATE_BUILTIN) /* * Constructor calls */ DUK_INTERNAL duk_ret_t duk_bi_date_constructor(duk_hthread *thr) { duk_idx_t nargs = duk_get_top(thr); duk_bool_t is_cons = duk_is_constructor_call(thr); duk_double_t dparts[DUK_DATE_IDX_NUM_PARTS]; duk_double_t d; DUK_DDD(DUK_DDDPRINT("Date constructor, nargs=%ld, is_cons=%ld", (long) nargs, (long) is_cons)); (void) duk_push_object_helper(thr, DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_FLAG_FASTREFS | DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_DATE), DUK_BIDX_DATE_PROTOTYPE); /* Unlike most built-ins, the internal [[PrimitiveValue]] of a Date * is mutable. */ if (nargs == 0 || !is_cons) { d = duk__timeclip(duk_time_get_ecmascript_time_nofrac(thr)); duk_push_number(thr, d); duk_xdef_prop_stridx_short(thr, -2, DUK_STRIDX_INT_VALUE, DUK_PROPDESC_FLAGS_W); if (!is_cons) { /* called as a normal function: return new Date().toString() */ duk_to_string(thr, -1); } return 1; } else if (nargs == 1) { const char *str; duk_to_primitive(thr, 0, DUK_HINT_NONE); str = duk_get_string_notsymbol(thr, 0); if (str) { duk__parse_string(thr, str); duk_replace(thr, 0); /* may be NaN */ } d = duk__timeclip(duk_to_number(thr, 0)); /* symbols fail here */ duk_push_number(thr, d); duk_xdef_prop_stridx_short(thr, -2, DUK_STRIDX_INT_VALUE, DUK_PROPDESC_FLAGS_W); return 1; } duk__set_parts_from_args(thr, dparts, nargs); /* Parts are in local time, convert when setting. */ (void) duk__set_this_timeval_from_dparts(thr, dparts, DUK_DATE_FLAG_LOCALTIME /*flags*/); /* -> [ ... this timeval ] */ duk_pop(thr); /* -> [ ... this ] */ return 1; } DUK_INTERNAL duk_ret_t duk_bi_date_constructor_parse(duk_hthread *thr) { return duk__parse_string(thr, duk_to_string(thr, 0)); } DUK_INTERNAL duk_ret_t duk_bi_date_constructor_utc(duk_hthread *thr) { duk_idx_t nargs = duk_get_top(thr); duk_double_t dparts[DUK_DATE_IDX_NUM_PARTS]; duk_double_t d; /* Behavior for nargs < 2 is implementation dependent: currently we'll * set a NaN time value (matching V8 behavior) in this case. */ if (nargs < 2) { duk_push_nan(thr); } else { duk__set_parts_from_args(thr, dparts, nargs); d = duk_bi_date_get_timeval_from_dparts(dparts, 0 /*flags*/); duk_push_number(thr, d); } return 1; } DUK_INTERNAL duk_ret_t duk_bi_date_constructor_now(duk_hthread *thr) { duk_double_t d; d = duk_time_get_ecmascript_time_nofrac(thr); DUK_ASSERT(duk_double_equals(duk__timeclip(d), d)); /* TimeClip() should never be necessary */ duk_push_number(thr, d); return 1; } /* * String/JSON conversions * * Human readable conversions are now basically ISO 8601 with a space * (instead of 'T') as the date/time separator. This is a good baseline * and is platform independent. * * A shared native helper to provide many conversions. Magic value contains * a set of flags. The helper provides: * * toString() * toDateString() * toTimeString() * toLocaleString() * toLocaleDateString() * toLocaleTimeString() * toUTCString() * toISOString() * * Notes: * * - Date.prototype.toGMTString() and Date.prototype.toUTCString() are * required to be the same ECMAScript function object (!), so it is * omitted from here. * * - Date.prototype.toUTCString(): E5.1 specification does not require a * specific format, but result should be human readable. The * specification suggests using ISO 8601 format with a space (instead * of 'T') separator if a more human readable format is not available. * * - Date.prototype.toISOString(): unlike other conversion functions, * toISOString() requires a RangeError for invalid date values. */ DUK_INTERNAL duk_ret_t duk_bi_date_prototype_tostring_shared(duk_hthread *thr) { duk_small_uint_t flags = duk__date_get_indirect_magic(thr); return duk__to_string_helper(thr, flags); } DUK_INTERNAL duk_ret_t duk_bi_date_prototype_value_of(duk_hthread *thr) { /* This native function is also used for Date.prototype.getTime() * as their behavior is identical. */ duk_double_t d = duk__push_this_get_timeval(thr, 0 /*flags*/); /* -> [ this ] */ DUK_ASSERT(DUK_ISFINITE(d) || DUK_ISNAN(d)); duk_push_number(thr, d); return 1; } DUK_INTERNAL duk_ret_t duk_bi_date_prototype_to_json(duk_hthread *thr) { /* Note: toJSON() is a generic function which works even if 'this' * is not a Date. The sole argument is ignored. */ duk_push_this(thr); duk_to_object(thr, -1); duk_dup_top(thr); duk_to_primitive(thr, -1, DUK_HINT_NUMBER); if (duk_is_number(thr, -1)) { duk_double_t d = duk_get_number(thr, -1); if (!DUK_ISFINITE(d)) { duk_push_null(thr); return 1; } } duk_pop(thr); duk_get_prop_stridx_short(thr, -1, DUK_STRIDX_TO_ISO_STRING); duk_dup_m2(thr); /* -> [ O toIsoString O ] */ duk_call_method(thr, 0); return 1; } /* * Getters. * * Implementing getters is quite easy. The internal time value is either * NaN, or represents milliseconds (without fractions) from Jan 1, 1970. * The internal time value can be converted to integer parts, and each * part will be normalized and will fit into a 32-bit signed integer. * * A shared native helper to provide all getters. Magic value contains * a set of flags and also packs the date component index argument. The * helper provides: * * getFullYear() * getUTCFullYear() * getMonth() * getUTCMonth() * getDate() * getUTCDate() * getDay() * getUTCDay() * getHours() * getUTCHours() * getMinutes() * getUTCMinutes() * getSeconds() * getUTCSeconds() * getMilliseconds() * getUTCMilliseconds() * getYear() * * Notes: * * - Date.prototype.getDate(): 'date' means day-of-month, and is * zero-based in internal calculations but public API expects it to * be one-based. * * - Date.prototype.getTime() and Date.prototype.valueOf() have identical * behavior. They have separate function objects, but share the same C * function (duk_bi_date_prototype_value_of). */ DUK_INTERNAL duk_ret_t duk_bi_date_prototype_get_shared(duk_hthread *thr) { duk_small_uint_t flags_and_idx = duk__date_get_indirect_magic(thr); return duk__get_part_helper(thr, flags_and_idx); } DUK_INTERNAL duk_ret_t duk_bi_date_prototype_get_timezone_offset(duk_hthread *thr) { /* * Return (t - LocalTime(t)) in minutes: * * t - LocalTime(t) = t - (t + LocalTZA + DaylightSavingTA(t)) * = -(LocalTZA + DaylightSavingTA(t)) * * where DaylightSavingTA() is checked for time 't'. * * Note that the sign of the result is opposite to common usage, * e.g. for EE(S)T which normally is +2h or +3h from UTC, this * function returns -120 or -180. * */ duk_double_t d; duk_int_t tzoffset; /* Note: DST adjustment is determined using UTC time. */ d = duk__push_this_get_timeval(thr, 0 /*flags*/); DUK_ASSERT(DUK_ISFINITE(d) || DUK_ISNAN(d)); if (DUK_ISNAN(d)) { duk_push_nan(thr); } else { DUK_ASSERT(DUK_ISFINITE(d)); tzoffset = DUK_USE_DATE_GET_LOCAL_TZOFFSET(d); duk_push_int(thr, -tzoffset / 60); } return 1; } /* * Setters. * * Setters are a bit more complicated than getters. Component setters * break down the current time value into its (normalized) component * parts, replace one or more components with -unnormalized- new values, * and the components are then converted back into a time value. As an * example of using unnormalized values: * * var d = new Date(1234567890); * * is equivalent to: * * var d = new Date(0); * d.setUTCMilliseconds(1234567890); * * A shared native helper to provide almost all setters. Magic value * contains a set of flags and also packs the "maxnargs" argument. The * helper provides: * * setMilliseconds() * setUTCMilliseconds() * setSeconds() * setUTCSeconds() * setMinutes() * setUTCMinutes() * setHours() * setUTCHours() * setDate() * setUTCDate() * setMonth() * setUTCMonth() * setFullYear() * setUTCFullYear() * setYear() * * Notes: * * - Date.prototype.setYear() (Section B addition): special year check * is omitted. NaN / Infinity will just flow through and ultimately * result in a NaN internal time value. * * - Date.prototype.setYear() does not have optional arguments for * setting month and day-in-month (like setFullYear()), but we indicate * 'maxnargs' to be 3 to get the year written to the correct component * index in duk__set_part_helper(). The function has nargs == 1, so only * the year will be set regardless of actual argument count. */ DUK_INTERNAL duk_ret_t duk_bi_date_prototype_set_shared(duk_hthread *thr) { duk_small_uint_t flags_and_maxnargs = duk__date_get_indirect_magic(thr); return duk__set_part_helper(thr, flags_and_maxnargs); } DUK_INTERNAL duk_ret_t duk_bi_date_prototype_set_time(duk_hthread *thr) { duk_double_t d; (void) duk__push_this_get_timeval(thr, 0 /*flags*/); /* -> [ timeval this ] */ d = duk__timeclip(duk_to_number(thr, 0)); duk_push_number(thr, d); duk_dup_top(thr); /* Must force write because .setTime() must work even when * the Date instance is frozen. */ duk_xdef_prop_stridx_short(thr, -3, DUK_STRIDX_INT_VALUE, DUK_PROPDESC_FLAGS_W); /* -> [ timeval this timeval ] */ return 1; } /* * Misc. */ #if defined(DUK_USE_SYMBOL_BUILTIN) DUK_INTERNAL duk_ret_t duk_bi_date_prototype_toprimitive(duk_hthread *thr) { duk_size_t hintlen; const char *hintstr; duk_int_t hint; /* Invokes OrdinaryToPrimitive() with suitable hint. Note that the * method is generic, and works on non-Date arguments too. * * https://www.ecma-international.org/ecma-262/6.0/#sec-date.prototype-@@toprimitive */ duk_push_this(thr); duk_require_object(thr, -1); DUK_ASSERT_TOP(thr, 2); hintstr = duk_require_lstring(thr, 0, &hintlen); if ((hintlen == 6 && DUK_STRCMP(hintstr, "string") == 0) || (hintlen == 7 && DUK_STRCMP(hintstr, "default") == 0)) { hint = DUK_HINT_STRING; } else if (hintlen == 6 && DUK_STRCMP(hintstr, "number") == 0) { hint = DUK_HINT_NUMBER; } else { DUK_DCERROR_TYPE_INVALID_ARGS(thr); } duk_to_primitive_ordinary(thr, -1, hint); return 1; } #endif /* DUK_USE_SYMBOL_BUILTIN */ #endif /* DUK_USE_DATE_BUILTIN */ /* automatic undefs */ #undef DUK__CF_ACCEPT #undef DUK__CF_ACCEPT_NUL #undef DUK__CF_NEG #undef DUK__DPRINT_DPARTS #undef DUK__DPRINT_PARTS #undef DUK__DPRINT_PARTS_AND_DPARTS #undef DUK__LOCAL_TZOFFSET_MAXITER #undef DUK__NUM_ISO8601_PARSER_PARTS #undef DUK__PACK_RULE #undef DUK__PI_DAY #undef DUK__PI_HOUR #undef DUK__PI_MILLISECOND #undef DUK__PI_MINUTE #undef DUK__PI_MONTH #undef DUK__PI_SECOND #undef DUK__PI_TZHOUR #undef DUK__PI_TZMINUTE #undef DUK__PI_YEAR #undef DUK__PM_DAY #undef DUK__PM_HOUR #undef DUK__PM_MILLISECOND #undef DUK__PM_MINUTE #undef DUK__PM_MONTH #undef DUK__PM_SECOND #undef DUK__PM_TZHOUR #undef DUK__PM_TZMINUTE #undef DUK__PM_YEAR #undef DUK__RULE_MASK_PART_SEP #undef DUK__SI_COLON #undef DUK__SI_MINUS #undef DUK__SI_NUL #undef DUK__SI_PERIOD #undef DUK__SI_PLUS #undef DUK__SI_SPACE #undef DUK__SI_T #undef DUK__SI_Z #undef DUK__SM_COLON #undef DUK__SM_MINUS #undef DUK__SM_NUL #undef DUK__SM_PERIOD #undef DUK__SM_PLUS #undef DUK__SM_SPACE #undef DUK__SM_T #undef DUK__SM_Z #undef DUK__UNPACK_RULE #undef DUK__WEEKDAY_MOD_ADDER #undef DUK__YEAR #line 1 "duk_bi_date_unix.c" /* * Unix-like Date providers * * Generally useful Unix / POSIX / ANSI Date providers. */ /* #include duk_internal.h -> already included */ /* The necessary #includes are in place in duk_config.h. */ /* Buffer sizes for some UNIX calls. Larger than strictly necessary * to avoid Valgrind errors. */ #define DUK__STRPTIME_BUF_SIZE 64 #define DUK__STRFTIME_BUF_SIZE 64 #if defined(DUK_USE_DATE_NOW_GETTIMEOFDAY) /* Get current ECMAScript time (= UNIX/Posix time, but in milliseconds). */ DUK_INTERNAL duk_double_t duk_bi_date_get_now_gettimeofday(void) { struct timeval tv; duk_double_t d; if (gettimeofday(&tv, NULL) != 0) { DUK_D(DUK_DPRINT("gettimeofday() failed")); return 0.0; } /* As of Duktape 2.2.0 allow fractions. */ d = ((duk_double_t) tv.tv_sec) * 1000.0 + ((duk_double_t) tv.tv_usec) / 1000.0; return d; } #endif /* DUK_USE_DATE_NOW_GETTIMEOFDAY */ #if defined(DUK_USE_DATE_NOW_TIME) /* Not a very good provider: only full seconds are available. */ DUK_INTERNAL duk_double_t duk_bi_date_get_now_time(void) { time_t t; t = time(NULL); if (t == (time_t) -1) { DUK_D(DUK_DPRINT("time() failed")); return 0.0; } return ((duk_double_t) t) * 1000.0; } #endif /* DUK_USE_DATE_NOW_TIME */ #if defined(DUK_USE_DATE_TZO_GMTIME) || defined(DUK_USE_DATE_TZO_GMTIME_R) || defined(DUK_USE_DATE_TZO_GMTIME_S) /* Get local time offset (in seconds) for a certain (UTC) instant 'd'. */ DUK_INTERNAL duk_int_t duk_bi_date_get_local_tzoffset_gmtime(duk_double_t d) { time_t t, t1, t2; duk_int_t parts[DUK_DATE_IDX_NUM_PARTS]; duk_double_t dparts[DUK_DATE_IDX_NUM_PARTS]; struct tm tms[2]; #if defined(DUK_USE_DATE_TZO_GMTIME) struct tm *tm_ptr; #endif /* For NaN/inf, the return value doesn't matter. */ if (!DUK_ISFINITE(d)) { return 0; } /* If not within ECMAScript range, some integer time calculations * won't work correctly (and some asserts will fail), so bail out * if so. This fixes test-bug-date-insane-setyear.js. There is * a +/- 24h leeway in this range check to avoid a test262 corner * case documented in test-bug-date-timeval-edges.js. */ if (!duk_bi_date_timeval_in_leeway_range(d)) { DUK_DD(DUK_DDPRINT("timeval not within valid range, skip tzoffset computation to avoid integer overflows")); return 0; } /* * This is a bit tricky to implement portably. The result depends * on the timestamp (specifically, DST depends on the timestamp). * If e.g. UNIX APIs are used, they'll have portability issues with * very small and very large years. * * Current approach: * * - Stay within portable UNIX limits by using equivalent year mapping. * Avoid year 1970 and 2038 as some conversions start to fail, at * least on some platforms. Avoiding 1970 means that there are * currently DST discrepancies for 1970. * * - Create a UTC and local time breakdowns from 't'. Then create * a time_t using gmtime() and localtime() and compute the time * difference between the two. * * Equivalent year mapping (E5 Section 15.9.1.8): * * If the host environment provides functionality for determining * daylight saving time, the implementation of ECMAScript is free * to map the year in question to an equivalent year (same * leap-year-ness and same starting week day for the year) for which * the host environment provides daylight saving time information. * The only restriction is that all equivalent years should produce * the same result. * * This approach is quite reasonable but not entirely correct, e.g. * the specification also states (E5 Section 15.9.1.8): * * The implementation of ECMAScript should not try to determine * whether the exact time was subject to daylight saving time, but * just whether daylight saving time would have been in effect if * the _current daylight saving time algorithm_ had been used at the * time. This avoids complications such as taking into account the * years that the locale observed daylight saving time year round. * * Since we rely on the platform APIs for conversions between local * time and UTC, we can't guarantee the above. Rather, if the platform * has historical DST rules they will be applied. This seems to be the * general preferred direction in ECMAScript standardization (or at least * implementations) anyway, and even the equivalent year mapping should * be disabled if the platform is known to handle DST properly for the * full ECMAScript range. * * The following has useful discussion and links: * * https://bugzilla.mozilla.org/show_bug.cgi?id=351066 */ duk_bi_date_timeval_to_parts(d, parts, dparts, DUK_DATE_FLAG_EQUIVYEAR /*flags*/); DUK_ASSERT(parts[DUK_DATE_IDX_YEAR] >= 1970 && parts[DUK_DATE_IDX_YEAR] <= 2038); d = duk_bi_date_get_timeval_from_dparts(dparts, 0 /*flags*/); DUK_ASSERT(d >= 0 && d < 2147483648.0 * 1000.0); /* unsigned 31-bit range */ t = (time_t) (d / 1000.0); DUK_DDD(DUK_DDDPRINT("timeval: %lf -> time_t %ld", (double) d, (long) t)); duk_memzero((void *) tms, sizeof(struct tm) * 2); #if defined(DUK_USE_DATE_TZO_GMTIME_R) (void) gmtime_r(&t, &tms[0]); (void) localtime_r(&t, &tms[1]); #elif defined(DUK_USE_DATE_TZO_GMTIME_S) (void) gmtime_s(&t, &tms[0]); (void) localtime_s(&t, &tms[1]); #elif defined(DUK_USE_DATE_TZO_GMTIME) tm_ptr = gmtime(&t); duk_memcpy((void *) &tms[0], tm_ptr, sizeof(struct tm)); tm_ptr = localtime(&t); duk_memcpy((void *) &tms[1], tm_ptr, sizeof(struct tm)); #else #error internal error #endif DUK_DDD(DUK_DDDPRINT("gmtime result: tm={sec:%ld,min:%ld,hour:%ld,mday:%ld,mon:%ld,year:%ld," "wday:%ld,yday:%ld,isdst:%ld}", (long) tms[0].tm_sec, (long) tms[0].tm_min, (long) tms[0].tm_hour, (long) tms[0].tm_mday, (long) tms[0].tm_mon, (long) tms[0].tm_year, (long) tms[0].tm_wday, (long) tms[0].tm_yday, (long) tms[0].tm_isdst)); DUK_DDD(DUK_DDDPRINT("localtime result: tm={sec:%ld,min:%ld,hour:%ld,mday:%ld,mon:%ld,year:%ld," "wday:%ld,yday:%ld,isdst:%ld}", (long) tms[1].tm_sec, (long) tms[1].tm_min, (long) tms[1].tm_hour, (long) tms[1].tm_mday, (long) tms[1].tm_mon, (long) tms[1].tm_year, (long) tms[1].tm_wday, (long) tms[1].tm_yday, (long) tms[1].tm_isdst)); /* tm_isdst is both an input and an output to mktime(), use 0 to * avoid DST handling in mktime(): * - https://github.com/svaarala/duktape/issues/406 * - http://stackoverflow.com/questions/8558919/mktime-and-tm-isdst */ tms[0].tm_isdst = 0; tms[1].tm_isdst = 0; t1 = mktime(&tms[0]); /* UTC */ t2 = mktime(&tms[1]); /* local */ if (t1 == (time_t) -1 || t2 == (time_t) -1) { /* This check used to be for (t < 0) but on some platforms * time_t is unsigned and apparently the proper way to detect * an mktime() error return is the cast above. See e.g.: * http://pubs.opengroup.org/onlinepubs/009695299/functions/mktime.html */ goto mktime_error; } DUK_DDD(DUK_DDDPRINT("t1=%ld (utc), t2=%ld (local)", (long) t1, (long) t2)); /* Compute final offset in seconds, positive if local time ahead of * UTC (returned value is UTC-to-local offset). * * difftime() returns a double, so coercion to int generates quite * a lot of code. Direct subtraction is not portable, however. * XXX: allow direct subtraction on known platforms. */ #if 0 return (duk_int_t) (t2 - t1); #endif return (duk_int_t) difftime(t2, t1); mktime_error: /* XXX: return something more useful, so that caller can throw? */ DUK_D(DUK_DPRINT("mktime() failed, d=%lf", (double) d)); return 0; } #endif /* DUK_USE_DATE_TZO_GMTIME */ #if defined(DUK_USE_DATE_PRS_STRPTIME) DUK_INTERNAL duk_bool_t duk_bi_date_parse_string_strptime(duk_hthread *thr, const char *str) { struct tm tm; time_t t; char buf[DUK__STRPTIME_BUF_SIZE]; /* Copy to buffer with slack to avoid Valgrind gripes from strptime. */ DUK_ASSERT(str != NULL); duk_memzero(buf, sizeof(buf)); /* valgrind whine without this */ DUK_SNPRINTF(buf, sizeof(buf), "%s", (const char *) str); buf[sizeof(buf) - 1] = (char) 0; DUK_DDD(DUK_DDDPRINT("parsing: '%s'", (const char *) buf)); duk_memzero(&tm, sizeof(tm)); if (strptime((const char *) buf, "%c", &tm) != NULL) { DUK_DDD(DUK_DDDPRINT("before mktime: tm={sec:%ld,min:%ld,hour:%ld,mday:%ld,mon:%ld,year:%ld," "wday:%ld,yday:%ld,isdst:%ld}", (long) tm.tm_sec, (long) tm.tm_min, (long) tm.tm_hour, (long) tm.tm_mday, (long) tm.tm_mon, (long) tm.tm_year, (long) tm.tm_wday, (long) tm.tm_yday, (long) tm.tm_isdst)); tm.tm_isdst = -1; /* negative: dst info not available */ t = mktime(&tm); DUK_DDD(DUK_DDDPRINT("mktime() -> %ld", (long) t)); if (t >= 0) { duk_push_number(thr, ((duk_double_t) t) * 1000.0); return 1; } } return 0; } #endif /* DUK_USE_DATE_PRS_STRPTIME */ #if defined(DUK_USE_DATE_PRS_GETDATE) DUK_INTERNAL duk_bool_t duk_bi_date_parse_string_getdate(duk_hthread *thr, const char *str) { struct tm tm; duk_small_int_t rc; time_t t; /* For this to work, DATEMSK must be set, so this is not very * convenient for an embeddable interpreter. */ duk_memzero(&tm, sizeof(struct tm)); rc = (duk_small_int_t) getdate_r(str, &tm); DUK_DDD(DUK_DDDPRINT("getdate_r() -> %ld", (long) rc)); if (rc == 0) { t = mktime(&tm); DUK_DDD(DUK_DDDPRINT("mktime() -> %ld", (long) t)); if (t >= 0) { duk_push_number(thr, (duk_double_t) t); return 1; } } return 0; } #endif /* DUK_USE_DATE_PRS_GETDATE */ #if defined(DUK_USE_DATE_FMT_STRFTIME) DUK_INTERNAL duk_bool_t duk_bi_date_format_parts_strftime(duk_hthread *thr, duk_int_t *parts, duk_int_t tzoffset, duk_small_uint_t flags) { char buf[DUK__STRFTIME_BUF_SIZE]; struct tm tm; const char *fmt; DUK_UNREF(tzoffset); /* If the platform doesn't support the entire ECMAScript range, we need * to return 0 so that the caller can fall back to the default formatter. * * For now, assume that if time_t is 8 bytes or more, the whole ECMAScript * range is supported. For smaller time_t values (4 bytes in practice), * assumes that the signed 32-bit range is supported. * * XXX: detect this more correctly per platform. The size of time_t is * probably not an accurate guarantee of strftime() supporting or not * supporting a large time range (the full ECMAScript range). */ if (sizeof(time_t) < 8 && (parts[DUK_DATE_IDX_YEAR] < 1970 || parts[DUK_DATE_IDX_YEAR] > 2037)) { /* be paranoid for 32-bit time values (even avoiding negative ones) */ return 0; } duk_memzero(&tm, sizeof(tm)); tm.tm_sec = parts[DUK_DATE_IDX_SECOND]; tm.tm_min = parts[DUK_DATE_IDX_MINUTE]; tm.tm_hour = parts[DUK_DATE_IDX_HOUR]; tm.tm_mday = parts[DUK_DATE_IDX_DAY]; /* already one-based */ tm.tm_mon = parts[DUK_DATE_IDX_MONTH] - 1; /* one-based -> zero-based */ tm.tm_year = parts[DUK_DATE_IDX_YEAR] - 1900; tm.tm_wday = parts[DUK_DATE_IDX_WEEKDAY]; tm.tm_isdst = 0; duk_memzero(buf, sizeof(buf)); if ((flags & DUK_DATE_FLAG_TOSTRING_DATE) && (flags & DUK_DATE_FLAG_TOSTRING_TIME)) { fmt = "%c"; } else if (flags & DUK_DATE_FLAG_TOSTRING_DATE) { fmt = "%x"; } else { DUK_ASSERT(flags & DUK_DATE_FLAG_TOSTRING_TIME); fmt = "%X"; } (void) strftime(buf, sizeof(buf) - 1, fmt, &tm); DUK_ASSERT(buf[sizeof(buf) - 1] == 0); duk_push_string(thr, buf); return 1; } #endif /* DUK_USE_DATE_FMT_STRFTIME */ #if defined(DUK_USE_GET_MONOTONIC_TIME_CLOCK_GETTIME) DUK_INTERNAL duk_double_t duk_bi_date_get_monotonic_time_clock_gettime(void) { struct timespec ts; if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) { return (duk_double_t) ts.tv_sec * 1000.0 + (duk_double_t) ts.tv_nsec / 1000000.0; } else { DUK_D(DUK_DPRINT("clock_gettime(CLOCK_MONOTONIC) failed")); return 0.0; } } #endif /* automatic undefs */ #undef DUK__STRFTIME_BUF_SIZE #undef DUK__STRPTIME_BUF_SIZE #line 1 "duk_bi_date_windows.c" /* * Windows Date providers * * Platform specific links: * * - http://msdn.microsoft.com/en-us/library/windows/desktop/ms725473(v=vs.85).aspx */ /* #include duk_internal.h -> already included */ /* The necessary #includes are in place in duk_config.h. */ #if defined(DUK_USE_DATE_NOW_WINDOWS) || defined(DUK_USE_DATE_TZO_WINDOWS) /* Shared Windows helpers. */ DUK_LOCAL void duk__convert_systime_to_ularge(const SYSTEMTIME *st, ULARGE_INTEGER *res) { FILETIME ft; if (SystemTimeToFileTime(st, &ft) == 0) { DUK_D(DUK_DPRINT("SystemTimeToFileTime() failed, returning 0")); res->QuadPart = 0; } else { res->LowPart = ft.dwLowDateTime; res->HighPart = ft.dwHighDateTime; } } #if defined(DUK_USE_DATE_NOW_WINDOWS_SUBMS) DUK_LOCAL void duk__convert_filetime_to_ularge(const FILETIME *ft, ULARGE_INTEGER *res) { res->LowPart = ft->dwLowDateTime; res->HighPart = ft->dwHighDateTime; } #endif /* DUK_USE_DATE_NOW_WINDOWS_SUBMS */ DUK_LOCAL void duk__set_systime_jan1970(SYSTEMTIME *st) { duk_memzero((void *) st, sizeof(*st)); st->wYear = 1970; st->wMonth = 1; st->wDayOfWeek = 4; /* not sure whether or not needed; Thursday */ st->wDay = 1; DUK_ASSERT(st->wHour == 0); DUK_ASSERT(st->wMinute == 0); DUK_ASSERT(st->wSecond == 0); DUK_ASSERT(st->wMilliseconds == 0); } #endif /* defined(DUK_USE_DATE_NOW_WINDOWS) || defined(DUK_USE_DATE_TZO_WINDOWS) */ #if defined(DUK_USE_DATE_NOW_WINDOWS) DUK_INTERNAL duk_double_t duk_bi_date_get_now_windows(void) { /* Suggested step-by-step method from documentation of RtlTimeToSecondsSince1970: * http://msdn.microsoft.com/en-us/library/windows/desktop/ms724928(v=vs.85).aspx */ SYSTEMTIME st1, st2; ULARGE_INTEGER tmp1, tmp2; GetSystemTime(&st1); duk__convert_systime_to_ularge((const SYSTEMTIME *) &st1, &tmp1); duk__set_systime_jan1970(&st2); duk__convert_systime_to_ularge((const SYSTEMTIME *) &st2, &tmp2); /* Difference is in 100ns units, convert to milliseconds, keeping * fractions since Duktape 2.2.0. This is only theoretical because * SYSTEMTIME is limited to milliseconds. */ return (duk_double_t) ((LONGLONG) tmp1.QuadPart - (LONGLONG) tmp2.QuadPart) / 10000.0; } #endif /* DUK_USE_DATE_NOW_WINDOWS */ #if defined(DUK_USE_DATE_NOW_WINDOWS_SUBMS) DUK_INTERNAL duk_double_t duk_bi_date_get_now_windows_subms(void) { /* Variant of the basic algorithm using GetSystemTimePreciseAsFileTime() * for more accuracy. */ FILETIME ft1; SYSTEMTIME st2; ULARGE_INTEGER tmp1, tmp2; GetSystemTimePreciseAsFileTime(&ft1); duk__convert_filetime_to_ularge((const FILETIME *) &ft1, &tmp1); duk__set_systime_jan1970(&st2); duk__convert_systime_to_ularge((const SYSTEMTIME *) &st2, &tmp2); /* Difference is in 100ns units, convert to milliseconds, keeping * fractions since Duktape 2.2.0. */ return (duk_double_t) ((LONGLONG) tmp1.QuadPart - (LONGLONG) tmp2.QuadPart) / 10000.0; } #endif /* DUK_USE_DATE_NOW_WINDOWS */ #if defined(DUK_USE_DATE_TZO_WINDOWS) DUK_INTERNAL duk_int_t duk_bi_date_get_local_tzoffset_windows(duk_double_t d) { SYSTEMTIME st1; SYSTEMTIME st2; SYSTEMTIME st3; ULARGE_INTEGER tmp1; ULARGE_INTEGER tmp2; ULARGE_INTEGER tmp3; FILETIME ft1; /* XXX: handling of timestamps outside Windows supported range. * How does Windows deal with dates before 1600? Does windows * support all ECMAScript years (like -200000 and +200000)? * Should equivalent year mapping be used here too? If so, use * a shared helper (currently integrated into timeval-to-parts). */ /* Use the approach described in "Remarks" of FileTimeToLocalFileTime: * http://msdn.microsoft.com/en-us/library/windows/desktop/ms724277(v=vs.85).aspx */ duk__set_systime_jan1970(&st1); duk__convert_systime_to_ularge((const SYSTEMTIME *) &st1, &tmp1); tmp2.QuadPart = (ULONGLONG) (d * 10000.0); /* millisec -> 100ns units since jan 1, 1970 */ tmp2.QuadPart += tmp1.QuadPart; /* input 'd' in Windows UTC, 100ns units */ ft1.dwLowDateTime = tmp2.LowPart; ft1.dwHighDateTime = tmp2.HighPart; if (FileTimeToSystemTime((const FILETIME *) &ft1, &st2) == 0) { DUK_D(DUK_DPRINT("FileTimeToSystemTime() failed, return tzoffset 0")); return 0; } if (SystemTimeToTzSpecificLocalTime((LPTIME_ZONE_INFORMATION) NULL, &st2, &st3) == 0) { DUK_D(DUK_DPRINT("SystemTimeToTzSpecificLocalTime() failed, return tzoffset 0")); return 0; } duk__convert_systime_to_ularge((const SYSTEMTIME *) &st3, &tmp3); /* Positive if local time ahead of UTC. */ return (duk_int_t) (((LONGLONG) tmp3.QuadPart - (LONGLONG) tmp2.QuadPart) / DUK_I64_CONSTANT(10000000)); /* seconds */ } #endif /* DUK_USE_DATE_TZO_WINDOWS */ #if defined(DUK_USE_DATE_TZO_WINDOWS_NO_DST) DUK_INTERNAL duk_int_t duk_bi_date_get_local_tzoffset_windows_no_dst(duk_double_t d) { SYSTEMTIME st1; SYSTEMTIME st2; FILETIME ft1; FILETIME ft2; ULARGE_INTEGER tmp1; ULARGE_INTEGER tmp2; /* Do a similar computation to duk_bi_date_get_local_tzoffset_windows * but without accounting for daylight savings time. Use this on * Windows platforms (like Durango) that don't support the * SystemTimeToTzSpecificLocalTime() call. */ /* current time not needed for this computation */ DUK_UNREF(d); duk__set_systime_jan1970(&st1); duk__convert_systime_to_ularge((const SYSTEMTIME *) &st1, &tmp1); ft1.dwLowDateTime = tmp1.LowPart; ft1.dwHighDateTime = tmp1.HighPart; if (FileTimeToLocalFileTime((const FILETIME *) &ft1, &ft2) == 0) { DUK_D(DUK_DPRINT("FileTimeToLocalFileTime() failed, return tzoffset 0")); return 0; } if (FileTimeToSystemTime((const FILETIME *) &ft2, &st2) == 0) { DUK_D(DUK_DPRINT("FileTimeToSystemTime() failed, return tzoffset 0")); return 0; } duk__convert_systime_to_ularge((const SYSTEMTIME *) &st2, &tmp2); return (duk_int_t) (((LONGLONG) tmp2.QuadPart - (LONGLONG) tmp1.QuadPart) / DUK_I64_CONSTANT(10000000)); /* seconds */ } #endif /* DUK_USE_DATE_TZO_WINDOWS_NO_DST */ #if defined(DUK_USE_GET_MONOTONIC_TIME_WINDOWS_QPC) DUK_INTERNAL duk_double_t duk_bi_date_get_monotonic_time_windows_qpc(void) { LARGE_INTEGER count, freq; /* There are legacy issues with QueryPerformanceCounter(): * - Potential jumps: * https://support.microsoft.com/en-us/help/274323/performance-counter-value-may-unexpectedly-leap-forward * - Differences between cores (XP): * https://msdn.microsoft.com/en-us/library/windows/desktop/dn553408(v=vs.85).aspx#qpc_support_in_windows_versions * * We avoid these by enabling QPC by default only for Vista or later. */ if (QueryPerformanceCounter(&count) && QueryPerformanceFrequency(&freq)) { /* XXX: QueryPerformanceFrequency() can be cached */ return (duk_double_t) count.QuadPart / (duk_double_t) freq.QuadPart * 1000.0; } else { /* MSDN: "On systems that run Windows XP or later, the function * will always succeed and will thus never return zero." * Provide minimal error path just in case user enables this * feature in pre-XP Windows. */ return 0.0; } } #endif /* DUK_USE_GET_MONOTONIC_TIME_WINDOWS_QPC */ #line 1 "duk_bi_duktape.c" /* * Duktape built-ins * * Size optimization note: it might seem that vararg multipurpose functions * like fin(), enc(), and dec() are not very size optimal, but using a single * user-visible ECMAScript function saves a lot of run-time footprint; each * Function instance takes >100 bytes. Using a shared native helper and a * 'magic' value won't save much if there are multiple Function instances * anyway. */ /* #include duk_internal.h -> already included */ #if defined(DUK_USE_DUKTAPE_BUILTIN) DUK_INTERNAL duk_ret_t duk_bi_duktape_object_info(duk_hthread *thr) { duk_inspect_value(thr, -1); return 1; } DUK_INTERNAL duk_ret_t duk_bi_duktape_object_act(duk_hthread *thr) { duk_int_t level; level = duk_to_int(thr, 0); duk_inspect_callstack_entry(thr, level); return 1; } DUK_INTERNAL duk_ret_t duk_bi_duktape_object_gc(duk_hthread *thr) { duk_small_uint_t flags; flags = (duk_small_uint_t) duk_get_uint(thr, 0); duk_heap_mark_and_sweep(thr->heap, flags); /* XXX: Not sure what the best return value would be in the API. * Return true for now. */ duk_push_true(thr); return 1; } #if defined(DUK_USE_FINALIZER_SUPPORT) DUK_INTERNAL duk_ret_t duk_bi_duktape_object_fin(duk_hthread *thr) { (void) duk_require_hobject(thr, 0); if (duk_get_top(thr) >= 2) { /* Set: currently a finalizer is disabled by setting it to * undefined; this does not remove the property at the moment. * The value could be type checked to be either a function * or something else; if something else, the property could * be deleted. Must use duk_set_finalizer() to keep * DUK_HOBJECT_FLAG_HAVE_FINALIZER in sync. */ duk_set_top(thr, 2); duk_set_finalizer(thr, 0); return 0; } else { /* Get. */ DUK_ASSERT(duk_get_top(thr) == 1); duk_get_finalizer(thr, 0); return 1; } } #endif /* DUK_USE_FINALIZER_SUPPORT */ DUK_INTERNAL duk_ret_t duk_bi_duktape_object_enc(duk_hthread *thr) { duk_hstring *h_str; /* Vararg function: must be careful to check/require arguments. * The JSON helpers accept invalid indices and treat them like * non-existent optional parameters. */ h_str = duk_require_hstring(thr, 0); /* Could reject symbols, but no point: won't match comparisons. */ duk_require_valid_index(thr, 1); if (h_str == DUK_HTHREAD_STRING_HEX(thr)) { duk_set_top(thr, 2); duk_hex_encode(thr, 1); DUK_ASSERT_TOP(thr, 2); } else if (h_str == DUK_HTHREAD_STRING_BASE64(thr)) { duk_set_top(thr, 2); duk_base64_encode(thr, 1); DUK_ASSERT_TOP(thr, 2); #if defined(DUK_USE_JSON_SUPPORT) && defined(DUK_USE_JX) } else if (h_str == DUK_HTHREAD_STRING_JX(thr)) { duk_bi_json_stringify_helper(thr, 1 /*idx_value*/, 2 /*idx_replacer*/, 3 /*idx_space*/, DUK_JSON_FLAG_EXT_CUSTOM | DUK_JSON_FLAG_ASCII_ONLY | DUK_JSON_FLAG_AVOID_KEY_QUOTES /*flags*/); #endif #if defined(DUK_USE_JSON_SUPPORT) && defined(DUK_USE_JC) } else if (h_str == DUK_HTHREAD_STRING_JC(thr)) { duk_bi_json_stringify_helper(thr, 1 /*idx_value*/, 2 /*idx_replacer*/, 3 /*idx_space*/, DUK_JSON_FLAG_EXT_COMPATIBLE | DUK_JSON_FLAG_ASCII_ONLY /*flags*/); #endif } else { DUK_DCERROR_TYPE_INVALID_ARGS(thr); } return 1; } DUK_INTERNAL duk_ret_t duk_bi_duktape_object_dec(duk_hthread *thr) { duk_hstring *h_str; /* Vararg function: must be careful to check/require arguments. * The JSON helpers accept invalid indices and treat them like * non-existent optional parameters. */ h_str = duk_require_hstring(thr, 0); /* Could reject symbols, but no point: won't match comparisons */ duk_require_valid_index(thr, 1); if (h_str == DUK_HTHREAD_STRING_HEX(thr)) { duk_set_top(thr, 2); duk_hex_decode(thr, 1); DUK_ASSERT_TOP(thr, 2); } else if (h_str == DUK_HTHREAD_STRING_BASE64(thr)) { duk_set_top(thr, 2); duk_base64_decode(thr, 1); DUK_ASSERT_TOP(thr, 2); #if defined(DUK_USE_JSON_SUPPORT) && defined(DUK_USE_JX) } else if (h_str == DUK_HTHREAD_STRING_JX(thr)) { duk_bi_json_parse_helper(thr, 1 /*idx_value*/, 2 /*idx_replacer*/, DUK_JSON_FLAG_EXT_CUSTOM /*flags*/); #endif #if defined(DUK_USE_JSON_SUPPORT) && defined(DUK_USE_JC) } else if (h_str == DUK_HTHREAD_STRING_JC(thr)) { duk_bi_json_parse_helper(thr, 1 /*idx_value*/, 2 /*idx_replacer*/, DUK_JSON_FLAG_EXT_COMPATIBLE /*flags*/); #endif } else { DUK_DCERROR_TYPE_INVALID_ARGS(thr); } return 1; } /* * Compact an object */ DUK_INTERNAL duk_ret_t duk_bi_duktape_object_compact(duk_hthread *thr) { DUK_ASSERT_TOP(thr, 1); duk_compact(thr, 0); return 1; /* return the argument object */ } #endif /* DUK_USE_DUKTAPE_BUILTIN */ #line 1 "duk_bi_encoding.c" /* * WHATWG Encoding API built-ins * * API specification: https://encoding.spec.whatwg.org/#api * Web IDL: https://www.w3.org/TR/WebIDL/ */ /* #include duk_internal.h -> already included */ /* * Data structures for encoding/decoding */ typedef struct { duk_uint8_t *out; /* where to write next byte(s) */ duk_codepoint_t lead; /* lead surrogate */ } duk__encode_context; typedef struct { /* UTF-8 decoding state */ duk_codepoint_t codepoint; /* built up incrementally */ duk_uint8_t upper; /* max value of next byte (decode error otherwise) */ duk_uint8_t lower; /* min value of next byte (ditto) */ duk_uint8_t needed; /* how many more bytes we need */ duk_uint8_t bom_handled; /* BOM seen or no longer expected */ /* Decoder configuration */ duk_uint8_t fatal; duk_uint8_t ignore_bom; } duk__decode_context; /* The signed duk_codepoint_t type is used to signal a decoded codepoint * (>= 0) or various other states using negative values. */ #define DUK__CP_CONTINUE (-1) /* continue to next byte, no completed codepoint */ #define DUK__CP_ERROR (-2) /* decoding error */ #define DUK__CP_RETRY (-3) /* decoding error; retry last byte */ /* * Raw helpers for encoding/decoding */ /* Emit UTF-8 (= CESU-8) encoded U+FFFD (replacement char), i.e. ef bf bd. */ DUK_LOCAL duk_uint8_t *duk__utf8_emit_repl(duk_uint8_t *ptr) { *ptr++ = 0xef; *ptr++ = 0xbf; *ptr++ = 0xbd; return ptr; } DUK_LOCAL void duk__utf8_decode_init(duk__decode_context *dec_ctx) { /* (Re)init the decoding state of 'dec_ctx' but leave decoder * configuration fields untouched. */ dec_ctx->codepoint = 0x0000L; dec_ctx->upper = 0xbf; dec_ctx->lower = 0x80; dec_ctx->needed = 0; dec_ctx->bom_handled = 0; } DUK_LOCAL duk_codepoint_t duk__utf8_decode_next(duk__decode_context *dec_ctx, duk_uint8_t x) { /* * UTF-8 algorithm based on the Encoding specification: * https://encoding.spec.whatwg.org/#utf-8-decoder * * Two main states: decoding initial byte vs. decoding continuation * bytes. Shortest length encoding is validated by restricting the * allowed range of first continuation byte using 'lower' and 'upper'. */ if (dec_ctx->needed == 0) { /* process initial byte */ if (x <= 0x7f) { /* U+0000-U+007F, 1 byte (ASCII) */ return (duk_codepoint_t) x; } else if (x >= 0xc2 && x <= 0xdf) { /* U+0080-U+07FF, 2 bytes */ dec_ctx->needed = 1; dec_ctx->codepoint = x & 0x1f; DUK_ASSERT(dec_ctx->lower == 0x80); DUK_ASSERT(dec_ctx->upper == 0xbf); return DUK__CP_CONTINUE; } else if (x >= 0xe0 && x <= 0xef) { /* U+0800-U+FFFF, 3 bytes */ if (x == 0xe0) { dec_ctx->lower = 0xa0; DUK_ASSERT(dec_ctx->upper == 0xbf); } else if (x == 0xed) { DUK_ASSERT(dec_ctx->lower == 0x80); dec_ctx->upper = 0x9f; } dec_ctx->needed = 2; dec_ctx->codepoint = x & 0x0f; return DUK__CP_CONTINUE; } else if (x >= 0xf0 && x <= 0xf4) { /* U+010000-U+10FFFF, 4 bytes */ if (x == 0xf0) { dec_ctx->lower = 0x90; DUK_ASSERT(dec_ctx->upper == 0xbf); } else if (x == 0xf4) { DUK_ASSERT(dec_ctx->lower == 0x80); dec_ctx->upper = 0x8f; } dec_ctx->needed = 3; dec_ctx->codepoint = x & 0x07; return DUK__CP_CONTINUE; } else { /* not a legal initial byte */ return DUK__CP_ERROR; } } else { /* process continuation byte */ if (x >= dec_ctx->lower && x <= dec_ctx->upper) { dec_ctx->lower = 0x80; dec_ctx->upper = 0xbf; dec_ctx->codepoint = (dec_ctx->codepoint << 6) | (x & 0x3f); if (--dec_ctx->needed > 0) { /* need more bytes */ return DUK__CP_CONTINUE; } else { /* got a codepoint */ duk_codepoint_t ret; DUK_ASSERT(dec_ctx->codepoint <= 0x10ffffL); /* Decoding rules guarantee. */ ret = dec_ctx->codepoint; dec_ctx->codepoint = 0x0000L; dec_ctx->needed = 0; return ret; } } else { /* We just encountered an illegal UTF-8 continuation byte. This might * be the initial byte of the next character; if we return a plain * error status and the decoder is in replacement mode, the character * will be masked. We still need to alert the caller to the error * though. */ dec_ctx->codepoint = 0x0000L; dec_ctx->needed = 0; dec_ctx->lower = 0x80; dec_ctx->upper = 0xbf; return DUK__CP_RETRY; } } } #if defined(DUK_USE_ENCODING_BUILTINS) DUK_LOCAL void duk__utf8_encode_char(void *udata, duk_codepoint_t codepoint) { duk__encode_context *enc_ctx; DUK_ASSERT(codepoint >= 0); enc_ctx = (duk__encode_context *) udata; DUK_ASSERT(enc_ctx != NULL); #if !defined(DUK_USE_PREFER_SIZE) if (codepoint <= 0x7f && enc_ctx->lead == 0x0000L) { /* Fast path for ASCII. */ *enc_ctx->out++ = (duk_uint8_t) codepoint; return; } #endif if (DUK_UNLIKELY(codepoint > 0x10ffffL)) { /* cannot legally encode in UTF-8 */ codepoint = DUK_UNICODE_CP_REPLACEMENT_CHARACTER; } else if (codepoint >= 0xd800L && codepoint <= 0xdfffL) { if (codepoint <= 0xdbffL) { /* high surrogate */ duk_codepoint_t prev_lead = enc_ctx->lead; enc_ctx->lead = codepoint; if (prev_lead == 0x0000L) { /* high surrogate, no output */ return; } else { /* consecutive high surrogates, consider first one unpaired */ codepoint = DUK_UNICODE_CP_REPLACEMENT_CHARACTER; } } else { /* low surrogate */ if (enc_ctx->lead != 0x0000L) { codepoint = (duk_codepoint_t) (0x010000L + ((enc_ctx->lead - 0xd800L) << 10) + (codepoint - 0xdc00L)); enc_ctx->lead = 0x0000L; } else { /* unpaired low surrogate */ DUK_ASSERT(enc_ctx->lead == 0x0000L); codepoint = DUK_UNICODE_CP_REPLACEMENT_CHARACTER; } } } else { if (enc_ctx->lead != 0x0000L) { /* unpaired high surrogate: emit replacement character and the input codepoint */ enc_ctx->lead = 0x0000L; enc_ctx->out = duk__utf8_emit_repl(enc_ctx->out); } } /* Codepoint may be original input, a decoded surrogate pair, or may * have been replaced with U+FFFD. */ enc_ctx->out += duk_unicode_encode_xutf8((duk_ucodepoint_t) codepoint, enc_ctx->out); } #endif /* DUK_USE_ENCODING_BUILTINS */ /* Shared helper for buffer-to-string using a TextDecoder() compatible UTF-8 * decoder. */ DUK_LOCAL duk_ret_t duk__decode_helper(duk_hthread *thr, duk__decode_context *dec_ctx) { const duk_uint8_t *input; duk_size_t len = 0; duk_size_t len_tmp; duk_bool_t stream = 0; duk_codepoint_t codepoint; duk_uint8_t *output; const duk_uint8_t *in; duk_uint8_t *out; DUK_ASSERT(dec_ctx != NULL); /* Careful with input buffer pointer: any side effects involving * code execution (e.g. getters, coercion calls, and finalizers) * may cause a resize and invalidate a pointer we've read. This * is why the pointer is actually looked up at the last minute. * Argument validation must still happen first to match WHATWG * required side effect order. */ if (duk_is_undefined(thr, 0)) { duk_push_fixed_buffer_nozero(thr, 0); duk_replace(thr, 0); } (void) duk_require_buffer_data(thr, 0, &len); /* Need 'len', avoid pointer. */ if (duk_check_type_mask(thr, 1, DUK_TYPE_MASK_UNDEFINED | DUK_TYPE_MASK_NULL | DUK_TYPE_MASK_NONE)) { /* Use defaults, treat missing value like undefined. */ } else { duk_require_type_mask(thr, 1, DUK_TYPE_MASK_UNDEFINED | DUK_TYPE_MASK_NULL | DUK_TYPE_MASK_LIGHTFUNC | DUK_TYPE_MASK_BUFFER | DUK_TYPE_MASK_OBJECT); if (duk_get_prop_literal(thr, 1, "stream")) { stream = duk_to_boolean(thr, -1); } } /* Allowance is 3*len in the general case because all bytes may potentially * become U+FFFD. If the first byte completes a non-BMP codepoint it will * decode to a CESU-8 surrogate pair (6 bytes) so we allow 3 extra bytes to * compensate: (1*3)+3 = 6. Non-BMP codepoints are safe otherwise because * the 4->6 expansion is well under the 3x allowance. * * XXX: As with TextEncoder, need a better buffer allocation strategy here. */ if (len >= (DUK_HBUFFER_MAX_BYTELEN / 3) - 3) { DUK_ERROR_TYPE(thr, DUK_STR_RESULT_TOO_LONG); DUK_WO_NORETURN(return 0;); } output = (duk_uint8_t *) duk_push_fixed_buffer_nozero(thr, 3 + (3 * len)); /* used parts will be always manually written over */ input = (const duk_uint8_t *) duk_get_buffer_data(thr, 0, &len_tmp); DUK_ASSERT(input != NULL || len == 0); if (DUK_UNLIKELY(len != len_tmp)) { /* Very unlikely but possible: source buffer was resized by * a side effect when fixed buffer was pushed. Output buffer * may not be large enough to hold output, so just fail if * length has changed. */ DUK_D(DUK_DPRINT("input buffer resized by side effect, fail")); goto fail_type; } /* From this point onwards it's critical that no side effect occur * which may disturb 'input': finalizer execution, property accesses, * active coercions, etc. Even an allocation related mark-and-sweep * may affect the pointer because it may trigger a pending finalizer. */ in = input; out = output; while (in < input + len) { codepoint = duk__utf8_decode_next(dec_ctx, *in++); if (codepoint < 0) { if (codepoint == DUK__CP_CONTINUE) { continue; } /* Decoding error with or without retry. */ DUK_ASSERT(codepoint == DUK__CP_ERROR || codepoint == DUK__CP_RETRY); if (codepoint == DUK__CP_RETRY) { --in; /* retry last byte */ } /* replacement mode: replace with U+FFFD */ codepoint = DUK_UNICODE_CP_REPLACEMENT_CHARACTER; if (dec_ctx->fatal) { /* fatal mode: throw a TypeError */ goto fail_type; } /* Continue with 'codepoint', Unicode replacement. */ } DUK_ASSERT(codepoint >= 0x0000L && codepoint <= 0x10ffffL); if (!dec_ctx->bom_handled) { dec_ctx->bom_handled = 1; if (codepoint == 0xfeffL && !dec_ctx->ignore_bom) { continue; } } out += duk_unicode_encode_cesu8((duk_ucodepoint_t) codepoint, out); DUK_ASSERT(out <= output + (3 + (3 * len))); } if (!stream) { if (dec_ctx->needed != 0) { /* truncated sequence at end of buffer */ if (dec_ctx->fatal) { goto fail_type; } else { out += duk_unicode_encode_cesu8(DUK_UNICODE_CP_REPLACEMENT_CHARACTER, out); DUK_ASSERT(out <= output + (3 + (3 * len))); } } duk__utf8_decode_init(dec_ctx); /* Initialize decoding state for potential reuse. */ } /* Output buffer is fixed and thus stable even if there had been * side effects (which there shouldn't be). */ duk_push_lstring(thr, (const char *) output, (duk_size_t) (out - output)); return 1; fail_type: DUK_ERROR_TYPE(thr, DUK_STR_UTF8_DECODE_FAILED); DUK_WO_NORETURN(return 0;); } /* * Built-in bindings */ #if defined(DUK_USE_ENCODING_BUILTINS) DUK_INTERNAL duk_ret_t duk_bi_textencoder_constructor(duk_hthread *thr) { /* TextEncoder currently requires no persistent state, so the constructor * does nothing on purpose. */ duk_require_constructor_call(thr); return 0; } DUK_INTERNAL duk_ret_t duk_bi_textencoder_prototype_encoding_getter(duk_hthread *thr) { duk_push_literal(thr, "utf-8"); return 1; } DUK_INTERNAL duk_ret_t duk_bi_textencoder_prototype_encode(duk_hthread *thr) { duk__encode_context enc_ctx; duk_size_t len; duk_size_t final_len; duk_uint8_t *output; DUK_ASSERT_TOP(thr, 1); if (duk_is_undefined(thr, 0)) { len = 0; } else { duk_hstring *h_input; h_input = duk_to_hstring(thr, 0); DUK_ASSERT(h_input != NULL); len = (duk_size_t) DUK_HSTRING_GET_CHARLEN(h_input); if (len >= DUK_HBUFFER_MAX_BYTELEN / 3) { DUK_ERROR_TYPE(thr, DUK_STR_RESULT_TOO_LONG); DUK_WO_NORETURN(return 0;); } } /* Allowance is 3*len because all bytes can potentially be replaced with * U+FFFD -- which rather inconveniently encodes to 3 bytes in UTF-8. * Rely on dynamic buffer data pointer stability: no other code has * access to the data pointer. * * XXX: The buffer allocation strategy used here is rather inefficient. * Maybe switch to a chunk-based strategy, or preprocess the string to * figure out the space needed ahead of time? */ DUK_ASSERT(3 * len >= len); output = (duk_uint8_t *) duk_push_dynamic_buffer(thr, 3 * len); if (len > 0) { DUK_ASSERT(duk_is_string(thr, 0)); /* True if len > 0. */ /* XXX: duk_decode_string() is used to process the input * string. For standard ECMAScript strings, represented * internally as CESU-8, this is fine. However, behavior * beyond CESU-8 is not very strict: codepoints using an * extended form of UTF-8 are also accepted, and invalid * codepoint sequences (which are allowed in Duktape strings) * are not handled as well as they could (e.g. invalid * continuation bytes may mask following codepoints). * This is how ECMAScript code would also see such strings. * Maybe replace duk_decode_string() with an explicit strict * CESU-8 decoder here? */ enc_ctx.lead = 0x0000L; enc_ctx.out = output; duk_decode_string(thr, 0, duk__utf8_encode_char, (void *) &enc_ctx); if (enc_ctx.lead != 0x0000L) { /* unpaired high surrogate at end of string */ enc_ctx.out = duk__utf8_emit_repl(enc_ctx.out); DUK_ASSERT(enc_ctx.out <= output + (3 * len)); } /* The output buffer is usually very much oversized, so shrink it to * actually needed size. Pointer stability assumed up to this point. */ DUK_ASSERT_TOP(thr, 2); DUK_ASSERT(output == (duk_uint8_t *) duk_get_buffer_data(thr, -1, NULL)); final_len = (duk_size_t) (enc_ctx.out - output); duk_resize_buffer(thr, -1, final_len); /* 'output' and 'enc_ctx.out' are potentially invalidated by the resize. */ } else { final_len = 0; } /* Standard WHATWG output is a Uint8Array. Here the Uint8Array will * be backed by a dynamic buffer which differs from e.g. Uint8Arrays * created as 'new Uint8Array(N)'. ECMAScript code won't see the * difference but C code will. When bufferobjects are not supported, * returns a plain dynamic buffer. */ #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) duk_push_buffer_object(thr, -1, 0, final_len, DUK_BUFOBJ_UINT8ARRAY); #endif return 1; } DUK_INTERNAL duk_ret_t duk_bi_textdecoder_constructor(duk_hthread *thr) { duk__decode_context *dec_ctx; duk_bool_t fatal = 0; duk_bool_t ignore_bom = 0; DUK_ASSERT_TOP(thr, 2); duk_require_constructor_call(thr); if (!duk_is_undefined(thr, 0)) { /* XXX: For now ignore 'label' (encoding identifier). */ duk_to_string(thr, 0); } if (!duk_is_null_or_undefined(thr, 1)) { if (duk_get_prop_literal(thr, 1, "fatal")) { fatal = duk_to_boolean(thr, -1); } if (duk_get_prop_literal(thr, 1, "ignoreBOM")) { ignore_bom = duk_to_boolean(thr, -1); } } duk_push_this(thr); /* The decode context is not assumed to be zeroed; all fields are * initialized explicitly. */ dec_ctx = (duk__decode_context *) duk_push_fixed_buffer(thr, sizeof(duk__decode_context)); dec_ctx->fatal = (duk_uint8_t) fatal; dec_ctx->ignore_bom = (duk_uint8_t) ignore_bom; duk__utf8_decode_init(dec_ctx); /* Initializes remaining fields. */ duk_put_prop_literal(thr, -2, DUK_INTERNAL_SYMBOL("Context")); return 0; } /* Get TextDecoder context from 'this'; leaves garbage on stack. */ DUK_LOCAL duk__decode_context *duk__get_textdecoder_context(duk_hthread *thr) { duk__decode_context *dec_ctx; duk_push_this(thr); duk_get_prop_literal(thr, -1, DUK_INTERNAL_SYMBOL("Context")); dec_ctx = (duk__decode_context *) duk_require_buffer(thr, -1, NULL); DUK_ASSERT(dec_ctx != NULL); return dec_ctx; } DUK_INTERNAL duk_ret_t duk_bi_textdecoder_prototype_shared_getter(duk_hthread *thr) { duk__decode_context *dec_ctx; duk_int_t magic; dec_ctx = duk__get_textdecoder_context(thr); magic = duk_get_current_magic(thr); switch (magic) { case 0: /* Encoding is now fixed, so _Context lookup is only needed to * validate the 'this' binding (TypeError if not TextDecoder-like). */ duk_push_literal(thr, "utf-8"); break; case 1: duk_push_boolean(thr, dec_ctx->fatal); break; default: duk_push_boolean(thr, dec_ctx->ignore_bom); break; } return 1; } DUK_INTERNAL duk_ret_t duk_bi_textdecoder_prototype_decode(duk_hthread *thr) { duk__decode_context *dec_ctx; dec_ctx = duk__get_textdecoder_context(thr); return duk__decode_helper(thr, dec_ctx); } #endif /* DUK_USE_ENCODING_BUILTINS */ /* * Internal helper for Node.js Buffer */ /* Internal helper used for Node.js Buffer .toString(). Value stack convention * is currently odd: it mimics TextDecoder .decode() so that argument must be at * index 0, and decode options (not present for Buffer) at index 1. Return value * is a Duktape/C function return value. */ DUK_INTERNAL duk_ret_t duk_textdecoder_decode_utf8_nodejs(duk_hthread *thr) { duk__decode_context dec_ctx; dec_ctx.fatal = 0; /* use replacement chars */ dec_ctx.ignore_bom = 1; /* ignore BOMs (matches Node.js Buffer .toString()) */ duk__utf8_decode_init(&dec_ctx); return duk__decode_helper(thr, &dec_ctx); } /* automatic undefs */ #undef DUK__CP_CONTINUE #undef DUK__CP_ERROR #undef DUK__CP_RETRY #line 1 "duk_bi_error.c" /* * Error built-ins */ /* #include duk_internal.h -> already included */ DUK_INTERNAL duk_ret_t duk_bi_error_constructor_shared(duk_hthread *thr) { /* Behavior for constructor and non-constructor call is * the same except for augmenting the created error. When * called as a constructor, the caller (duk_new()) will handle * augmentation; when called as normal function, we need to do * it here. */ duk_small_int_t bidx_prototype = duk_get_current_magic(thr); /* same for both error and each subclass like TypeError */ duk_uint_t flags_and_class = DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_FLAG_FASTREFS | DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_ERROR); (void) duk_push_object_helper(thr, flags_and_class, bidx_prototype); /* If message is undefined, the own property 'message' is not set at * all to save property space. An empty message is inherited anyway. */ if (!duk_is_undefined(thr, 0)) { duk_to_string(thr, 0); duk_dup_0(thr); /* [ message error message ] */ duk_xdef_prop_stridx_short(thr, -2, DUK_STRIDX_MESSAGE, DUK_PROPDESC_FLAGS_WC); } /* Augment the error if called as a normal function. __FILE__ and __LINE__ * are not desirable in this case. */ #if defined(DUK_USE_AUGMENT_ERROR_CREATE) if (!duk_is_constructor_call(thr)) { duk_err_augment_error_create(thr, thr, NULL, 0, DUK_AUGMENT_FLAG_NOBLAME_FILELINE); } #endif return 1; } DUK_INTERNAL duk_ret_t duk_bi_error_prototype_to_string(duk_hthread *thr) { /* XXX: optimize with more direct internal access */ duk_push_this(thr); (void) duk_require_hobject_promote_mask(thr, -1, DUK_TYPE_MASK_LIGHTFUNC | DUK_TYPE_MASK_BUFFER); /* [ ... this ] */ duk_get_prop_stridx_short(thr, -1, DUK_STRIDX_NAME); if (duk_is_undefined(thr, -1)) { duk_pop(thr); duk_push_literal(thr, "Error"); } else { duk_to_string(thr, -1); } /* [ ... this name ] */ /* XXX: Are steps 6 and 7 in E5 Section 15.11.4.4 duplicated by * accident or are they actually needed? The first ToString() * could conceivably return 'undefined'. */ duk_get_prop_stridx_short(thr, -2, DUK_STRIDX_MESSAGE); if (duk_is_undefined(thr, -1)) { duk_pop(thr); duk_push_hstring_empty(thr); } else { duk_to_string(thr, -1); } /* [ ... this name message ] */ if (duk_get_length(thr, -2) == 0) { /* name is empty -> return message */ return 1; } if (duk_get_length(thr, -1) == 0) { /* message is empty -> return name */ duk_pop(thr); return 1; } duk_push_literal(thr, ": "); duk_insert(thr, -2); /* ... name ': ' message */ duk_concat(thr, 3); return 1; } #if defined(DUK_USE_TRACEBACKS) /* * Traceback handling * * The unified helper decodes the traceback and produces various requested * outputs. It should be optimized for size, and may leave garbage on stack, * only the topmost return value matters. For instance, traceback separator * and decoded strings are pushed even when looking for filename only. * * NOTE: although _Tracedata is an internal property, user code can currently * write to the array (or replace it with something other than an array). * The code below must tolerate arbitrary _Tracedata. It can throw errors * etc, but cannot cause a segfault or memory unsafe behavior. */ /* constants arbitrary, chosen for small loads */ #define DUK__OUTPUT_TYPE_TRACEBACK (-1) #define DUK__OUTPUT_TYPE_FILENAME 0 #define DUK__OUTPUT_TYPE_LINENUMBER 1 DUK_LOCAL duk_ret_t duk__error_getter_helper(duk_hthread *thr, duk_small_int_t output_type) { duk_idx_t idx_td; duk_small_int_t i; /* traceback depth fits into 16 bits */ duk_small_int_t t; /* stack type fits into 16 bits */ duk_small_int_t count_func = 0; /* traceback depth ensures fits into 16 bits */ const char *str_tailcall = " tailcall"; const char *str_strict = " strict"; const char *str_construct = " construct"; const char *str_prevyield = " preventsyield"; const char *str_directeval = " directeval"; const char *str_empty = ""; DUK_ASSERT_TOP(thr, 0); /* fixed arg count */ duk_push_this(thr); duk_xget_owndataprop_stridx_short(thr, -1, DUK_STRIDX_INT_TRACEDATA); idx_td = duk_get_top_index(thr); duk_push_hstring_stridx(thr, DUK_STRIDX_NEWLINE_4SPACE); duk_push_this(thr); /* [ ... this tracedata sep this ] */ /* XXX: skip null filename? */ if (duk_check_type(thr, idx_td, DUK_TYPE_OBJECT)) { /* Current tracedata contains 2 entries per callstack entry. */ for (i = 0;; i += 2) { duk_int_t pc; duk_uint_t line; duk_uint_t flags; duk_double_t d; const char *funcname; const char *filename; duk_hobject *h_func; duk_hstring *h_name; duk_require_stack(thr, 5); duk_get_prop_index(thr, idx_td, (duk_uarridx_t) i); duk_get_prop_index(thr, idx_td, (duk_uarridx_t) (i + 1)); d = duk_to_number_m1(thr); pc = duk_double_to_int_t(DUK_FMOD(d, DUK_DOUBLE_2TO32)); flags = duk_double_to_uint_t(DUK_FLOOR(d / DUK_DOUBLE_2TO32)); t = (duk_small_int_t) duk_get_type(thr, -2); if (t == DUK_TYPE_OBJECT || t == DUK_TYPE_LIGHTFUNC) { /* * ECMAScript/native function call or lightfunc call */ count_func++; /* [ ... v1(func) v2(pc+flags) ] */ /* These may be systematically omitted by Duktape * with certain config options, but allow user to * set them on a case-by-case basis. */ duk_get_prop_stridx_short(thr, -2, DUK_STRIDX_NAME); duk_get_prop_stridx_short(thr, -3, DUK_STRIDX_FILE_NAME); #if defined(DUK_USE_PC2LINE) line = (duk_uint_t) duk_hobject_pc2line_query(thr, -4, (duk_uint_fast32_t) pc); #else line = 0; #endif /* [ ... v1 v2 name filename ] */ /* When looking for .fileName/.lineNumber, blame first * function which has a .fileName. */ if (duk_is_string_notsymbol(thr, -1)) { if (output_type == DUK__OUTPUT_TYPE_FILENAME) { return 1; } else if (output_type == DUK__OUTPUT_TYPE_LINENUMBER) { duk_push_uint(thr, line); return 1; } } /* XXX: Change 'anon' handling here too, to use empty string for anonymous functions? */ /* XXX: Could be improved by coercing to a readable duk_tval (especially string escaping) */ h_name = duk_get_hstring_notsymbol(thr, -2); /* may be NULL */ funcname = (h_name == NULL || h_name == DUK_HTHREAD_STRING_EMPTY_STRING(thr)) ? "[anon]" : (const char *) DUK_HSTRING_GET_DATA(h_name); filename = duk_get_string_notsymbol(thr, -1); filename = filename ? filename : ""; DUK_ASSERT(funcname != NULL); DUK_ASSERT(filename != NULL); h_func = duk_get_hobject(thr, -4); /* NULL for lightfunc */ if (h_func == NULL) { duk_push_sprintf( thr, "at %s light%s%s%s%s%s", (const char *) funcname, (const char *) ((flags & DUK_ACT_FLAG_STRICT) ? str_strict : str_empty), (const char *) ((flags & DUK_ACT_FLAG_TAILCALLED) ? str_tailcall : str_empty), (const char *) ((flags & DUK_ACT_FLAG_CONSTRUCT) ? str_construct : str_empty), (const char *) ((flags & DUK_ACT_FLAG_DIRECT_EVAL) ? str_directeval : str_empty), (const char *) ((flags & DUK_ACT_FLAG_PREVENT_YIELD) ? str_prevyield : str_empty)); } else if (DUK_HOBJECT_HAS_NATFUNC(h_func)) { duk_push_sprintf( thr, "at %s (%s) native%s%s%s%s%s", (const char *) funcname, (const char *) filename, (const char *) ((flags & DUK_ACT_FLAG_STRICT) ? str_strict : str_empty), (const char *) ((flags & DUK_ACT_FLAG_TAILCALLED) ? str_tailcall : str_empty), (const char *) ((flags & DUK_ACT_FLAG_CONSTRUCT) ? str_construct : str_empty), (const char *) ((flags & DUK_ACT_FLAG_DIRECT_EVAL) ? str_directeval : str_empty), (const char *) ((flags & DUK_ACT_FLAG_PREVENT_YIELD) ? str_prevyield : str_empty)); } else { duk_push_sprintf( thr, "at %s (%s:%lu)%s%s%s%s%s", (const char *) funcname, (const char *) filename, (unsigned long) line, (const char *) ((flags & DUK_ACT_FLAG_STRICT) ? str_strict : str_empty), (const char *) ((flags & DUK_ACT_FLAG_TAILCALLED) ? str_tailcall : str_empty), (const char *) ((flags & DUK_ACT_FLAG_CONSTRUCT) ? str_construct : str_empty), (const char *) ((flags & DUK_ACT_FLAG_DIRECT_EVAL) ? str_directeval : str_empty), (const char *) ((flags & DUK_ACT_FLAG_PREVENT_YIELD) ? str_prevyield : str_empty)); } duk_replace(thr, -5); /* [ ... v1 v2 name filename str ] -> [ ... str v2 name filename ] */ duk_pop_3(thr); /* -> [ ... str ] */ } else if (t == DUK_TYPE_STRING) { const char *str_file; /* * __FILE__ / __LINE__ entry, here 'pc' is line number directly. * Sometimes __FILE__ / __LINE__ is reported as the source for * the error (fileName, lineNumber), sometimes not. */ /* [ ... v1(filename) v2(line+flags) ] */ /* When looking for .fileName/.lineNumber, blame compilation * or C call site unless flagged not to do so. */ if (!(flags & DUK_TB_FLAG_NOBLAME_FILELINE)) { if (output_type == DUK__OUTPUT_TYPE_FILENAME) { duk_pop(thr); return 1; } else if (output_type == DUK__OUTPUT_TYPE_LINENUMBER) { duk_push_int(thr, pc); return 1; } } /* Tracedata is trusted but avoid any risk of using a NULL * for %s format because it has undefined behavior. Symbols * don't need to be explicitly rejected as they pose no memory * safety issues. */ str_file = (const char *) duk_get_string(thr, -2); duk_push_sprintf(thr, "at [anon] (%s:%ld) internal", (const char *) (str_file ? str_file : "null"), (long) pc); duk_replace(thr, -3); /* [ ... v1 v2 str ] -> [ ... str v2 ] */ duk_pop(thr); /* -> [ ... str ] */ } else { /* unknown, ignore */ duk_pop_2(thr); break; } } if (count_func >= DUK_USE_TRACEBACK_DEPTH) { /* Possibly truncated; there is no explicit truncation * marker so this is the best we can do. */ duk_push_hstring_stridx(thr, DUK_STRIDX_BRACKETED_ELLIPSIS); } } /* [ ... this tracedata sep this str1 ... strN ] */ if (output_type != DUK__OUTPUT_TYPE_TRACEBACK) { return 0; } else { /* The 'this' after 'sep' will get ToString() coerced by * duk_join() automatically. We don't want to do that * coercion when providing .fileName or .lineNumber (GH-254). */ duk_join(thr, duk_get_top(thr) - (idx_td + 2) /*count, not including sep*/); return 1; } } /* XXX: Output type could be encoded into native function 'magic' value to * save space. For setters the stridx could be encoded into 'magic'. */ DUK_INTERNAL duk_ret_t duk_bi_error_prototype_stack_getter(duk_hthread *thr) { return duk__error_getter_helper(thr, DUK__OUTPUT_TYPE_TRACEBACK); } DUK_INTERNAL duk_ret_t duk_bi_error_prototype_filename_getter(duk_hthread *thr) { return duk__error_getter_helper(thr, DUK__OUTPUT_TYPE_FILENAME); } DUK_INTERNAL duk_ret_t duk_bi_error_prototype_linenumber_getter(duk_hthread *thr) { return duk__error_getter_helper(thr, DUK__OUTPUT_TYPE_LINENUMBER); } #else /* DUK_USE_TRACEBACKS */ /* * Traceback handling when tracebacks disabled. * * The fileName / lineNumber stubs are now necessary because built-in * data will include the accessor properties in Error.prototype. If those * are removed for builds without tracebacks, these can also be removed. * 'stack' should still be present and produce a ToString() equivalent: * this is useful for user code which prints a stacktrace and expects to * see something useful. A normal stacktrace also begins with a ToString() * of the error so this makes sense. */ DUK_INTERNAL duk_ret_t duk_bi_error_prototype_stack_getter(duk_hthread *thr) { /* XXX: remove this native function and map 'stack' accessor * to the toString() implementation directly. */ return duk_bi_error_prototype_to_string(thr); } DUK_INTERNAL duk_ret_t duk_bi_error_prototype_filename_getter(duk_hthread *thr) { DUK_UNREF(thr); return 0; } DUK_INTERNAL duk_ret_t duk_bi_error_prototype_linenumber_getter(duk_hthread *thr) { DUK_UNREF(thr); return 0; } #endif /* DUK_USE_TRACEBACKS */ DUK_LOCAL duk_ret_t duk__error_setter_helper(duk_hthread *thr, duk_small_uint_t stridx_key) { /* Attempt to write 'stack', 'fileName', 'lineNumber' works as if * user code called Object.defineProperty() to create an overriding * own property. This allows user code to overwrite .fileName etc * intuitively as e.g. "err.fileName = 'dummy'" as one might expect. * See https://github.com/svaarala/duktape/issues/387. */ DUK_ASSERT_TOP(thr, 1); /* fixed arg count: value */ duk_push_this(thr); duk_push_hstring_stridx(thr, stridx_key); duk_dup_0(thr); /* [ ... obj key value ] */ DUK_DD(DUK_DDPRINT("error setter: %!T %!T %!T", duk_get_tval(thr, -3), duk_get_tval(thr, -2), duk_get_tval(thr, -1))); duk_def_prop(thr, -3, DUK_DEFPROP_HAVE_VALUE | DUK_DEFPROP_HAVE_WRITABLE | DUK_DEFPROP_WRITABLE | DUK_DEFPROP_HAVE_ENUMERABLE | /*not enumerable*/ DUK_DEFPROP_HAVE_CONFIGURABLE | DUK_DEFPROP_CONFIGURABLE); return 0; } DUK_INTERNAL duk_ret_t duk_bi_error_prototype_stack_setter(duk_hthread *thr) { return duk__error_setter_helper(thr, DUK_STRIDX_STACK); } DUK_INTERNAL duk_ret_t duk_bi_error_prototype_filename_setter(duk_hthread *thr) { return duk__error_setter_helper(thr, DUK_STRIDX_FILE_NAME); } DUK_INTERNAL duk_ret_t duk_bi_error_prototype_linenumber_setter(duk_hthread *thr) { return duk__error_setter_helper(thr, DUK_STRIDX_LINE_NUMBER); } /* automatic undefs */ #undef DUK__OUTPUT_TYPE_FILENAME #undef DUK__OUTPUT_TYPE_LINENUMBER #undef DUK__OUTPUT_TYPE_TRACEBACK #line 1 "duk_bi_function.c" /* * Function built-ins */ /* #include duk_internal.h -> already included */ /* Needed even when Function built-in is disabled. */ DUK_INTERNAL duk_ret_t duk_bi_function_prototype(duk_hthread *thr) { /* ignore arguments, return undefined (E5 Section 15.3.4) */ DUK_UNREF(thr); return 0; } #if defined(DUK_USE_FUNCTION_BUILTIN) DUK_INTERNAL duk_ret_t duk_bi_function_constructor(duk_hthread *thr) { duk_hstring *h_sourcecode; duk_idx_t nargs; duk_idx_t i; duk_small_uint_t comp_flags; duk_hcompfunc *func; duk_hobject *outer_lex_env; duk_hobject *outer_var_env; /* normal and constructor calls have identical semantics */ nargs = duk_get_top(thr); for (i = 0; i < nargs; i++) { duk_to_string(thr, i); /* Rejects Symbols during coercion. */ } if (nargs == 0) { duk_push_hstring_empty(thr); duk_push_hstring_empty(thr); } else if (nargs == 1) { /* XXX: cover this with the generic >1 case? */ duk_push_hstring_empty(thr); } else { duk_insert(thr, 0); /* [ arg1 ... argN-1 body] -> [body arg1 ... argN-1] */ duk_push_literal(thr, ","); duk_insert(thr, 1); duk_join(thr, nargs - 1); } /* [ body formals ], formals is comma separated list that needs to be parsed */ DUK_ASSERT_TOP(thr, 2); /* XXX: this placeholder is not always correct, but use for now. * It will fail in corner cases; see test-dev-func-cons-args.js. */ duk_push_literal(thr, "function("); duk_dup_1(thr); duk_push_literal(thr, "){"); duk_dup_0(thr); duk_push_literal(thr, "\n}"); /* Newline is important to handle trailing // comment. */ duk_concat(thr, 5); /* [ body formals source ] */ DUK_ASSERT_TOP(thr, 3); /* strictness is not inherited, intentional */ comp_flags = DUK_COMPILE_FUNCEXPR; duk_push_hstring_stridx(thr, DUK_STRIDX_COMPILE); /* XXX: copy from caller? */ /* XXX: ignored now */ h_sourcecode = duk_require_hstring(thr, -2); /* no symbol check needed; -2 is concat'd code */ duk_js_compile(thr, (const duk_uint8_t *) DUK_HSTRING_GET_DATA(h_sourcecode), (duk_size_t) DUK_HSTRING_GET_BYTELEN(h_sourcecode), comp_flags); /* Force .name to 'anonymous' (ES2015). */ duk_push_literal(thr, "anonymous"); duk_xdef_prop_stridx_short(thr, -2, DUK_STRIDX_NAME, DUK_PROPDESC_FLAGS_C); func = (duk_hcompfunc *) duk_known_hobject(thr, -1); DUK_ASSERT(DUK_HOBJECT_IS_COMPFUNC((duk_hobject *) func)); DUK_ASSERT(DUK_HOBJECT_HAS_CONSTRUCTABLE((duk_hobject *) func)); /* [ body formals source template ] */ /* only outer_lex_env matters, as functions always get a new * variable declaration environment. */ outer_lex_env = thr->builtins[DUK_BIDX_GLOBAL_ENV]; outer_var_env = thr->builtins[DUK_BIDX_GLOBAL_ENV]; duk_js_push_closure(thr, func, outer_var_env, outer_lex_env, 1 /*add_auto_proto*/); /* [ body formals source template closure ] */ return 1; } #endif /* DUK_USE_FUNCTION_BUILTIN */ #if defined(DUK_USE_FUNCTION_BUILTIN) DUK_INTERNAL duk_ret_t duk_bi_function_prototype_to_string(duk_hthread *thr) { duk_tval *tv; /* * E5 Section 15.3.4.2 places few requirements on the output of * this function: the result is implementation dependent, must * follow FunctionDeclaration syntax (in particular, must have a * name even for anonymous functions or functions with empty name). * The output does NOT need to compile into anything useful. * * E6 Section 19.2.3.5 changes the requirements completely: the * result must either eval() to a functionally equivalent object * OR eval() to a SyntaxError. * * We opt for the SyntaxError approach for now, with a syntax that * mimics V8's native function syntax: * * 'function cos() { [native code] }' * * but extended with [ecmascript code], [bound code], and * [lightfunc code]. */ duk_push_this(thr); tv = DUK_GET_TVAL_NEGIDX(thr, -1); DUK_ASSERT(tv != NULL); if (DUK_TVAL_IS_OBJECT(tv)) { duk_hobject *obj = DUK_TVAL_GET_OBJECT(tv); const char *func_name; /* Function name: missing/undefined is mapped to empty string, * otherwise coerce to string. No handling for invalid identifier * characters or e.g. '{' in the function name. This doesn't * really matter as long as a SyntaxError results. Technically * if the name contained a suitable prefix followed by '//' it * might cause the result to parse without error. */ duk_get_prop_stridx_short(thr, -1, DUK_STRIDX_NAME); if (duk_is_undefined(thr, -1)) { func_name = ""; } else { func_name = duk_to_string(thr, -1); DUK_ASSERT(func_name != NULL); } if (DUK_HOBJECT_IS_COMPFUNC(obj)) { duk_push_sprintf(thr, "function %s() { [ecmascript code] }", (const char *) func_name); } else if (DUK_HOBJECT_IS_NATFUNC(obj)) { duk_push_sprintf(thr, "function %s() { [native code] }", (const char *) func_name); } else if (DUK_HOBJECT_IS_BOUNDFUNC(obj)) { duk_push_sprintf(thr, "function %s() { [bound code] }", (const char *) func_name); } else { goto type_error; } } else if (DUK_TVAL_IS_LIGHTFUNC(tv)) { duk_push_lightfunc_tostring(thr, tv); } else { goto type_error; } return 1; type_error: DUK_DCERROR_TYPE_INVALID_ARGS(thr); } #endif /* Always present because the native function pointer is needed in call * handling. */ DUK_INTERNAL duk_ret_t duk_bi_function_prototype_call(duk_hthread *thr) { /* .call() is dealt with in call handling by simulating its * effects so this function is actually never called. */ DUK_UNREF(thr); return DUK_RET_TYPE_ERROR; } DUK_INTERNAL duk_ret_t duk_bi_function_prototype_apply(duk_hthread *thr) { /* Like .call(), never actually called. */ DUK_UNREF(thr); return DUK_RET_TYPE_ERROR; } DUK_INTERNAL duk_ret_t duk_bi_reflect_apply(duk_hthread *thr) { /* Like .call(), never actually called. */ DUK_UNREF(thr); return DUK_RET_TYPE_ERROR; } DUK_INTERNAL duk_ret_t duk_bi_reflect_construct(duk_hthread *thr) { /* Like .call(), never actually called. */ DUK_UNREF(thr); return DUK_RET_TYPE_ERROR; } #if defined(DUK_USE_FUNCTION_BUILTIN) /* Create a bound function which points to a target function which may * be bound or non-bound. If the target is bound, the argument lists * and 'this' binding of the functions are merged and the resulting * function points directly to the non-bound target. */ DUK_INTERNAL duk_ret_t duk_bi_function_prototype_bind(duk_hthread *thr) { duk_hboundfunc *h_bound; duk_idx_t nargs; /* bound args, not counting 'this' binding */ duk_idx_t bound_nargs; duk_int_t bound_len; duk_tval *tv_prevbound; duk_idx_t n_prevbound; duk_tval *tv_res; duk_tval *tv_tmp; /* XXX: C API call, e.g. duk_push_bound_function(thr, target_idx, nargs); */ /* Vararg function, careful arg handling, e.g. thisArg may not * be present. */ nargs = duk_get_top(thr) - 1; /* actual args, not counting 'this' binding */ if (nargs < 0) { nargs++; duk_push_undefined(thr); } DUK_ASSERT(nargs >= 0); /* Limit 'nargs' for bound functions to guarantee arithmetic * below will never wrap. */ if (nargs > (duk_idx_t) DUK_HBOUNDFUNC_MAX_ARGS) { DUK_DCERROR_RANGE_INVALID_COUNT(thr); } duk_push_this(thr); duk_require_callable(thr, -1); /* [ thisArg arg1 ... argN func ] (thisArg+args == nargs+1 total) */ DUK_ASSERT_TOP(thr, nargs + 2); /* Create bound function object. */ h_bound = duk_push_hboundfunc(thr); DUK_ASSERT(DUK_TVAL_IS_UNDEFINED(&h_bound->target)); DUK_ASSERT(DUK_TVAL_IS_UNDEFINED(&h_bound->this_binding)); DUK_ASSERT(h_bound->args == NULL); DUK_ASSERT(h_bound->nargs == 0); DUK_ASSERT(DUK_HOBJECT_GET_PROTOTYPE(thr->heap, (duk_hobject *) h_bound) == NULL); /* [ thisArg arg1 ... argN func boundFunc ] */ /* If the target is a bound function, argument lists must be * merged. The 'this' binding closest to the target function * wins because in call handling the 'this' gets replaced over * and over again until we call the non-bound function. */ tv_prevbound = NULL; n_prevbound = 0; tv_tmp = DUK_GET_TVAL_POSIDX(thr, 0); DUK_TVAL_SET_TVAL(&h_bound->this_binding, tv_tmp); tv_tmp = DUK_GET_TVAL_NEGIDX(thr, -2); DUK_TVAL_SET_TVAL(&h_bound->target, tv_tmp); if (DUK_TVAL_IS_OBJECT(tv_tmp)) { duk_hobject *h_target; duk_hobject *bound_proto; h_target = DUK_TVAL_GET_OBJECT(tv_tmp); DUK_ASSERT(DUK_HOBJECT_IS_CALLABLE(h_target)); /* Internal prototype must be copied from the target. * For lightfuncs Function.prototype is used and is already * in place. */ bound_proto = DUK_HOBJECT_GET_PROTOTYPE(thr->heap, h_target); DUK_HOBJECT_SET_PROTOTYPE_INIT_INCREF(thr, (duk_hobject *) h_bound, bound_proto); /* The 'strict' flag is copied to get the special [[Get]] of E5.1 * Section 15.3.5.4 to apply when a 'caller' value is a strict bound * function. Not sure if this is correct, because the specification * is a bit ambiguous on this point but it would make sense. */ /* Strictness is inherited from target. */ if (DUK_HOBJECT_HAS_STRICT(h_target)) { DUK_HOBJECT_SET_STRICT((duk_hobject *) h_bound); } if (DUK_HOBJECT_HAS_BOUNDFUNC(h_target)) { duk_hboundfunc *h_boundtarget; h_boundtarget = (duk_hboundfunc *) (void *) h_target; /* The final function should always be non-bound, unless * there's a bug in the internals. Assert for it. */ DUK_ASSERT(DUK_TVAL_IS_LIGHTFUNC(&h_boundtarget->target) || (DUK_TVAL_IS_OBJECT(&h_boundtarget->target) && DUK_HOBJECT_IS_CALLABLE(DUK_TVAL_GET_OBJECT(&h_boundtarget->target)) && !DUK_HOBJECT_IS_BOUNDFUNC(DUK_TVAL_GET_OBJECT(&h_boundtarget->target)))); DUK_TVAL_SET_TVAL(&h_bound->target, &h_boundtarget->target); DUK_TVAL_SET_TVAL(&h_bound->this_binding, &h_boundtarget->this_binding); tv_prevbound = h_boundtarget->args; n_prevbound = h_boundtarget->nargs; } } else { /* Lightfuncs are always strict. */ duk_hobject *bound_proto; DUK_ASSERT(DUK_TVAL_IS_LIGHTFUNC(tv_tmp)); DUK_HOBJECT_SET_STRICT((duk_hobject *) h_bound); bound_proto = thr->builtins[DUK_BIDX_FUNCTION_PROTOTYPE]; DUK_HOBJECT_SET_PROTOTYPE_INIT_INCREF(thr, (duk_hobject *) h_bound, bound_proto); } DUK_TVAL_INCREF(thr, &h_bound->target); /* old values undefined, no decref needed */ DUK_TVAL_INCREF(thr, &h_bound->this_binding); bound_nargs = n_prevbound + nargs; if (bound_nargs > (duk_idx_t) DUK_HBOUNDFUNC_MAX_ARGS) { DUK_DCERROR_RANGE_INVALID_COUNT(thr); } tv_res = (duk_tval *) DUK_ALLOC_CHECKED(thr, ((duk_size_t) bound_nargs) * sizeof(duk_tval)); DUK_ASSERT(tv_res != NULL || bound_nargs == 0); DUK_ASSERT(h_bound->args == NULL); DUK_ASSERT(h_bound->nargs == 0); h_bound->args = tv_res; h_bound->nargs = bound_nargs; DUK_ASSERT(n_prevbound >= 0); duk_copy_tvals_incref(thr, tv_res, tv_prevbound, (duk_size_t) n_prevbound); DUK_ASSERT(nargs >= 0); duk_copy_tvals_incref(thr, tv_res + n_prevbound, DUK_GET_TVAL_POSIDX(thr, 1), (duk_size_t) nargs); /* [ thisArg arg1 ... argN func boundFunc ] */ /* Bound function 'length' property is interesting. * For lightfuncs, simply read the virtual property. */ duk_get_prop_stridx_short(thr, -2, DUK_STRIDX_LENGTH); bound_len = duk_get_int(thr, -1); /* ES2015: no coercion */ if (bound_len < nargs) { bound_len = 0; } else { bound_len -= nargs; } if (sizeof(duk_int_t) > 4 && bound_len > (duk_int_t) DUK_UINT32_MAX) { bound_len = (duk_int_t) DUK_UINT32_MAX; } duk_pop(thr); DUK_ASSERT(bound_len >= 0); tv_tmp = thr->valstack_top++; DUK_ASSERT(DUK_TVAL_IS_UNDEFINED(tv_tmp)); DUK_ASSERT(!DUK_TVAL_NEEDS_REFCOUNT_UPDATE(tv_tmp)); DUK_TVAL_SET_U32(tv_tmp, (duk_uint32_t) bound_len); /* in-place update, fastint */ duk_xdef_prop_stridx_short(thr, -2, DUK_STRIDX_LENGTH, DUK_PROPDESC_FLAGS_C); /* attrs in E6 Section 9.2.4 */ /* XXX: could these be virtual? */ /* Caller and arguments must use the same thrower, [[ThrowTypeError]]. */ duk_xdef_prop_stridx_thrower(thr, -1, DUK_STRIDX_CALLER); duk_xdef_prop_stridx_thrower(thr, -1, DUK_STRIDX_LC_ARGUMENTS); /* Function name and fileName (non-standard). */ duk_push_literal(thr, "bound "); /* ES2015 19.2.3.2. */ duk_get_prop_stridx(thr, -3, DUK_STRIDX_NAME); if (!duk_is_string_notsymbol(thr, -1)) { /* ES2015 has requirement to check that .name of target is a string * (also must check for Symbol); if not, targetName should be the * empty string. ES2015 19.2.3.2. */ duk_pop(thr); duk_push_hstring_empty(thr); } duk_concat(thr, 2); duk_xdef_prop_stridx_short(thr, -2, DUK_STRIDX_NAME, DUK_PROPDESC_FLAGS_C); #if defined(DUK_USE_FUNC_FILENAME_PROPERTY) duk_get_prop_stridx_short(thr, -2, DUK_STRIDX_FILE_NAME); duk_xdef_prop_stridx_short(thr, -2, DUK_STRIDX_FILE_NAME, DUK_PROPDESC_FLAGS_C); #endif DUK_DDD(DUK_DDDPRINT("created bound function: %!iT", (duk_tval *) duk_get_tval(thr, -1))); return 1; } #endif /* DUK_USE_FUNCTION_BUILTIN */ /* %NativeFunctionPrototype% .length getter. */ DUK_INTERNAL duk_ret_t duk_bi_native_function_length(duk_hthread *thr) { duk_tval *tv; duk_hnatfunc *h; duk_int16_t func_nargs; tv = duk_get_borrowed_this_tval(thr); DUK_ASSERT(tv != NULL); if (DUK_TVAL_IS_OBJECT(tv)) { h = (duk_hnatfunc *) DUK_TVAL_GET_OBJECT(tv); DUK_ASSERT(h != NULL); if (!DUK_HOBJECT_IS_NATFUNC((duk_hobject *) h)) { goto fail_type; } func_nargs = h->nargs; duk_push_int(thr, func_nargs == DUK_HNATFUNC_NARGS_VARARGS ? 0 : func_nargs); } else if (DUK_TVAL_IS_LIGHTFUNC(tv)) { duk_small_uint_t lf_flags; duk_small_uint_t lf_len; lf_flags = DUK_TVAL_GET_LIGHTFUNC_FLAGS(tv); lf_len = DUK_LFUNC_FLAGS_GET_LENGTH(lf_flags); duk_push_uint(thr, lf_len); } else { goto fail_type; } return 1; fail_type: DUK_DCERROR_TYPE_INVALID_ARGS(thr); } /* %NativeFunctionPrototype% .name getter. */ DUK_INTERNAL duk_ret_t duk_bi_native_function_name(duk_hthread *thr) { duk_tval *tv; duk_hnatfunc *h; tv = duk_get_borrowed_this_tval(thr); DUK_ASSERT(tv != NULL); if (DUK_TVAL_IS_OBJECT(tv)) { h = (duk_hnatfunc *) DUK_TVAL_GET_OBJECT(tv); DUK_ASSERT(h != NULL); if (!DUK_HOBJECT_IS_NATFUNC((duk_hobject *) h)) { goto fail_type; } #if 0 duk_push_hnatfunc_name(thr, h); #endif duk_push_hstring_empty(thr); } else if (DUK_TVAL_IS_LIGHTFUNC(tv)) { duk_push_lightfunc_name(thr, tv); } else { goto fail_type; } return 1; fail_type: DUK_DCERROR_TYPE_INVALID_ARGS(thr); } #if defined(DUK_USE_SYMBOL_BUILTIN) DUK_INTERNAL duk_ret_t duk_bi_function_prototype_hasinstance(duk_hthread *thr) { /* This binding: RHS, stack index 0: LHS. */ duk_bool_t ret; ret = duk_js_instanceof_ordinary(thr, DUK_GET_TVAL_POSIDX(thr, 0), DUK_GET_THIS_TVAL_PTR(thr)); duk_push_boolean(thr, ret); return 1; } #endif /* DUK_USE_SYMBOL_BUILTIN */ #line 1 "duk_bi_global.c" /* * Global object built-ins */ /* #include duk_internal.h -> already included */ /* * Encoding/decoding helpers */ /* XXX: Could add fast path (for each transform callback) with direct byte * lookups (no shifting) and no explicit check for x < 0x80 before table * lookup. */ /* Macros for creating and checking bitmasks for character encoding. * Bit number is a bit counterintuitive, but minimizes code size. */ #define DUK__MKBITS(a, b, c, d, e, f, g, h) \ ((duk_uint8_t) (((a) << 0) | ((b) << 1) | ((c) << 2) | ((d) << 3) | ((e) << 4) | ((f) << 5) | ((g) << 6) | ((h) << 7))) #define DUK__CHECK_BITMASK(table, cp) ((table)[(cp) >> 3] & (1 << ((cp) &0x07))) /* E5.1 Section 15.1.3.3: uriReserved + uriUnescaped + '#' */ DUK_LOCAL const duk_uint8_t duk__encode_uriunescaped_table[16] = { DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), /* 0x00-0x0f */ DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), /* 0x10-0x1f */ DUK__MKBITS(0, 1, 0, 1, 1, 0, 1, 1), DUK__MKBITS(1, 1, 1, 1, 1, 1, 1, 1), /* 0x20-0x2f */ DUK__MKBITS(1, 1, 1, 1, 1, 1, 1, 1), DUK__MKBITS(1, 1, 1, 1, 0, 1, 0, 1), /* 0x30-0x3f */ DUK__MKBITS(1, 1, 1, 1, 1, 1, 1, 1), DUK__MKBITS(1, 1, 1, 1, 1, 1, 1, 1), /* 0x40-0x4f */ DUK__MKBITS(1, 1, 1, 1, 1, 1, 1, 1), DUK__MKBITS(1, 1, 1, 0, 0, 0, 0, 1), /* 0x50-0x5f */ DUK__MKBITS(0, 1, 1, 1, 1, 1, 1, 1), DUK__MKBITS(1, 1, 1, 1, 1, 1, 1, 1), /* 0x60-0x6f */ DUK__MKBITS(1, 1, 1, 1, 1, 1, 1, 1), DUK__MKBITS(1, 1, 1, 0, 0, 0, 1, 0), /* 0x70-0x7f */ }; /* E5.1 Section 15.1.3.4: uriUnescaped */ DUK_LOCAL const duk_uint8_t duk__encode_uricomponent_unescaped_table[16] = { DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), /* 0x00-0x0f */ DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), /* 0x10-0x1f */ DUK__MKBITS(0, 1, 0, 0, 0, 0, 0, 1), DUK__MKBITS(1, 1, 1, 0, 0, 1, 1, 0), /* 0x20-0x2f */ DUK__MKBITS(1, 1, 1, 1, 1, 1, 1, 1), DUK__MKBITS(1, 1, 0, 0, 0, 0, 0, 0), /* 0x30-0x3f */ DUK__MKBITS(0, 1, 1, 1, 1, 1, 1, 1), DUK__MKBITS(1, 1, 1, 1, 1, 1, 1, 1), /* 0x40-0x4f */ DUK__MKBITS(1, 1, 1, 1, 1, 1, 1, 1), DUK__MKBITS(1, 1, 1, 0, 0, 0, 0, 1), /* 0x50-0x5f */ DUK__MKBITS(0, 1, 1, 1, 1, 1, 1, 1), DUK__MKBITS(1, 1, 1, 1, 1, 1, 1, 1), /* 0x60-0x6f */ DUK__MKBITS(1, 1, 1, 1, 1, 1, 1, 1), DUK__MKBITS(1, 1, 1, 0, 0, 0, 1, 0), /* 0x70-0x7f */ }; /* E5.1 Section 15.1.3.1: uriReserved + '#' */ DUK_LOCAL const duk_uint8_t duk__decode_uri_reserved_table[16] = { DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), /* 0x00-0x0f */ DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), /* 0x10-0x1f */ DUK__MKBITS(0, 0, 0, 1, 1, 0, 1, 0), DUK__MKBITS(0, 0, 0, 1, 1, 0, 0, 1), /* 0x20-0x2f */ DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), DUK__MKBITS(0, 0, 1, 1, 0, 1, 0, 1), /* 0x30-0x3f */ DUK__MKBITS(1, 0, 0, 0, 0, 0, 0, 0), DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), /* 0x40-0x4f */ DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), /* 0x50-0x5f */ DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), /* 0x60-0x6f */ DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), /* 0x70-0x7f */ }; /* E5.1 Section 15.1.3.2: empty */ DUK_LOCAL const duk_uint8_t duk__decode_uri_component_reserved_table[16] = { DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), /* 0x00-0x0f */ DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), /* 0x10-0x1f */ DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), /* 0x20-0x2f */ DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), /* 0x30-0x3f */ DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), /* 0x40-0x4f */ DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), /* 0x50-0x5f */ DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), /* 0x60-0x6f */ DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), /* 0x70-0x7f */ }; #if defined(DUK_USE_SECTION_B) /* E5.1 Section B.2.2, step 7. */ DUK_LOCAL const duk_uint8_t duk__escape_unescaped_table[16] = { DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), /* 0x00-0x0f */ DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), /* 0x10-0x1f */ DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), DUK__MKBITS(0, 0, 1, 1, 0, 1, 1, 1), /* 0x20-0x2f */ DUK__MKBITS(1, 1, 1, 1, 1, 1, 1, 1), DUK__MKBITS(1, 1, 0, 0, 0, 0, 0, 0), /* 0x30-0x3f */ DUK__MKBITS(1, 1, 1, 1, 1, 1, 1, 1), DUK__MKBITS(1, 1, 1, 1, 1, 1, 1, 1), /* 0x40-0x4f */ DUK__MKBITS(1, 1, 1, 1, 1, 1, 1, 1), DUK__MKBITS(1, 1, 1, 0, 0, 0, 0, 1), /* 0x50-0x5f */ DUK__MKBITS(0, 1, 1, 1, 1, 1, 1, 1), DUK__MKBITS(1, 1, 1, 1, 1, 1, 1, 1), /* 0x60-0x6f */ DUK__MKBITS(1, 1, 1, 1, 1, 1, 1, 1), DUK__MKBITS(1, 1, 1, 0, 0, 0, 0, 0) /* 0x70-0x7f */ }; #endif /* DUK_USE_SECTION_B */ typedef struct { duk_hthread *thr; duk_hstring *h_str; duk_bufwriter_ctx bw; const duk_uint8_t *p; const duk_uint8_t *p_start; const duk_uint8_t *p_end; } duk__transform_context; typedef void (*duk__transform_callback)(duk__transform_context *tfm_ctx, const void *udata, duk_codepoint_t cp); /* XXX: refactor and share with other code */ DUK_LOCAL duk_small_int_t duk__decode_hex_escape(const duk_uint8_t *p, duk_small_int_t n) { duk_small_int_t ch; duk_small_int_t t = 0; while (n > 0) { t = t * 16; ch = (duk_small_int_t) duk_hex_dectab[*p++]; if (DUK_LIKELY(ch >= 0)) { t += ch; } else { return -1; } n--; } return t; } DUK_LOCAL int duk__transform_helper(duk_hthread *thr, duk__transform_callback callback, const void *udata) { duk__transform_context tfm_ctx_alloc; duk__transform_context *tfm_ctx = &tfm_ctx_alloc; duk_codepoint_t cp; tfm_ctx->thr = thr; tfm_ctx->h_str = duk_to_hstring(thr, 0); DUK_ASSERT(tfm_ctx->h_str != NULL); DUK_BW_INIT_PUSHBUF(thr, &tfm_ctx->bw, DUK_HSTRING_GET_BYTELEN(tfm_ctx->h_str)); /* initial size guess */ tfm_ctx->p_start = DUK_HSTRING_GET_DATA(tfm_ctx->h_str); tfm_ctx->p_end = tfm_ctx->p_start + DUK_HSTRING_GET_BYTELEN(tfm_ctx->h_str); tfm_ctx->p = tfm_ctx->p_start; while (tfm_ctx->p < tfm_ctx->p_end) { cp = (duk_codepoint_t) duk_unicode_decode_xutf8_checked(thr, &tfm_ctx->p, tfm_ctx->p_start, tfm_ctx->p_end); callback(tfm_ctx, udata, cp); } DUK_BW_COMPACT(thr, &tfm_ctx->bw); (void) duk_buffer_to_string(thr, -1); /* Safe if transform is safe. */ return 1; } DUK_LOCAL void duk__transform_callback_encode_uri(duk__transform_context *tfm_ctx, const void *udata, duk_codepoint_t cp) { duk_uint8_t xutf8_buf[DUK_UNICODE_MAX_XUTF8_LENGTH]; duk_small_int_t len; duk_codepoint_t cp1, cp2; duk_small_int_t i, t; const duk_uint8_t *unescaped_table = (const duk_uint8_t *) udata; /* UTF-8 encoded bytes escaped as %xx%xx%xx... -> 3 * nbytes. * Codepoint range is restricted so this is a slightly too large * but doesn't matter. */ DUK_BW_ENSURE(tfm_ctx->thr, &tfm_ctx->bw, 3 * DUK_UNICODE_MAX_XUTF8_LENGTH); if (cp < 0) { goto uri_error; } else if ((cp < 0x80L) && DUK__CHECK_BITMASK(unescaped_table, cp)) { DUK_BW_WRITE_RAW_U8(tfm_ctx->thr, &tfm_ctx->bw, (duk_uint8_t) cp); return; } else if (cp >= 0xdc00L && cp <= 0xdfffL) { goto uri_error; } else if (cp >= 0xd800L && cp <= 0xdbffL) { /* Needs lookahead */ if (duk_unicode_decode_xutf8(tfm_ctx->thr, &tfm_ctx->p, tfm_ctx->p_start, tfm_ctx->p_end, (duk_ucodepoint_t *) &cp2) == 0) { goto uri_error; } if (!(cp2 >= 0xdc00L && cp2 <= 0xdfffL)) { goto uri_error; } cp1 = cp; cp = (duk_codepoint_t) (((cp1 - 0xd800L) << 10) + (cp2 - 0xdc00L) + 0x10000L); } else if (cp > 0x10ffffL) { /* Although we can allow non-BMP characters (they'll decode * back into surrogate pairs), we don't allow extended UTF-8 * characters; they would encode to URIs which won't decode * back because of strict UTF-8 checks in URI decoding. * (However, we could just as well allow them here.) */ goto uri_error; } else { /* Non-BMP characters within valid UTF-8 range: encode as is. * They'll decode back into surrogate pairs if the escaped * output is decoded. */ ; } len = duk_unicode_encode_xutf8((duk_ucodepoint_t) cp, xutf8_buf); for (i = 0; i < len; i++) { t = (duk_small_int_t) xutf8_buf[i]; DUK_BW_WRITE_RAW_U8_3(tfm_ctx->thr, &tfm_ctx->bw, DUK_ASC_PERCENT, (duk_uint8_t) duk_uc_nybbles[t >> 4], (duk_uint8_t) duk_uc_nybbles[t & 0x0f]); } return; uri_error: DUK_ERROR_URI(tfm_ctx->thr, DUK_STR_INVALID_INPUT); DUK_WO_NORETURN(return;); } DUK_LOCAL void duk__transform_callback_decode_uri(duk__transform_context *tfm_ctx, const void *udata, duk_codepoint_t cp) { const duk_uint8_t *reserved_table = (const duk_uint8_t *) udata; duk_small_uint_t utf8_blen; duk_codepoint_t min_cp; duk_small_int_t t; /* must be signed */ duk_small_uint_t i; /* Maximum write size: XUTF8 path writes max DUK_UNICODE_MAX_XUTF8_LENGTH, * percent escape path writes max two times CESU-8 encoded BMP length. */ DUK_BW_ENSURE(tfm_ctx->thr, &tfm_ctx->bw, (DUK_UNICODE_MAX_XUTF8_LENGTH >= 2 * DUK_UNICODE_MAX_CESU8_BMP_LENGTH ? DUK_UNICODE_MAX_XUTF8_LENGTH : DUK_UNICODE_MAX_CESU8_BMP_LENGTH)); if (cp == (duk_codepoint_t) '%') { const duk_uint8_t *p = tfm_ctx->p; duk_size_t left = (duk_size_t) (tfm_ctx->p_end - p); /* bytes left */ DUK_DDD(DUK_DDDPRINT("percent encoding, left=%ld", (long) left)); if (left < 2) { goto uri_error; } t = duk__decode_hex_escape(p, 2); DUK_DDD(DUK_DDDPRINT("first byte: %ld", (long) t)); if (t < 0) { goto uri_error; } if (t < 0x80) { if (DUK__CHECK_BITMASK(reserved_table, t)) { /* decode '%xx' to '%xx' if decoded char in reserved set */ DUK_ASSERT(tfm_ctx->p - 1 >= tfm_ctx->p_start); DUK_BW_WRITE_RAW_U8_3(tfm_ctx->thr, &tfm_ctx->bw, DUK_ASC_PERCENT, p[0], p[1]); } else { DUK_BW_WRITE_RAW_U8(tfm_ctx->thr, &tfm_ctx->bw, (duk_uint8_t) t); } tfm_ctx->p += 2; return; } /* Decode UTF-8 codepoint from a sequence of hex escapes. The * first byte of the sequence has been decoded to 't'. * * Note that UTF-8 validation must be strict according to the * specification: E5.1 Section 15.1.3, decode algorithm step * 4.d.vii.8. URIError from non-shortest encodings is also * specifically noted in the spec. */ DUK_ASSERT(t >= 0x80); if (t < 0xc0) { /* continuation byte */ goto uri_error; } else if (t < 0xe0) { /* 110x xxxx; 2 bytes */ utf8_blen = 2; min_cp = 0x80L; cp = t & 0x1f; } else if (t < 0xf0) { /* 1110 xxxx; 3 bytes */ utf8_blen = 3; min_cp = 0x800L; cp = t & 0x0f; } else if (t < 0xf8) { /* 1111 0xxx; 4 bytes */ utf8_blen = 4; min_cp = 0x10000L; cp = t & 0x07; } else { /* extended utf-8 not allowed for URIs */ goto uri_error; } if (left < utf8_blen * 3 - 1) { /* '%xx%xx...%xx', p points to char after first '%' */ goto uri_error; } p += 3; for (i = 1; i < utf8_blen; i++) { /* p points to digit part ('%xy', p points to 'x') */ t = duk__decode_hex_escape(p, 2); DUK_DDD(DUK_DDDPRINT("i=%ld utf8_blen=%ld cp=%ld t=0x%02lx", (long) i, (long) utf8_blen, (long) cp, (unsigned long) t)); if (t < 0) { goto uri_error; } if ((t & 0xc0) != 0x80) { goto uri_error; } cp = (cp << 6) + (t & 0x3f); p += 3; } p--; /* p overshoots */ tfm_ctx->p = p; DUK_DDD(DUK_DDDPRINT("final cp=%ld, min_cp=%ld", (long) cp, (long) min_cp)); if (cp < min_cp || cp > 0x10ffffL || (cp >= 0xd800L && cp <= 0xdfffL)) { goto uri_error; } /* The E5.1 algorithm checks whether or not a decoded codepoint * is below 0x80 and perhaps may be in the "reserved" set. * This seems pointless because the single byte UTF-8 case is * handled separately, and non-shortest encodings are rejected. * So, 'cp' cannot be below 0x80 here, and thus cannot be in * the reserved set. */ /* utf-8 validation ensures these */ DUK_ASSERT(cp >= 0x80L && cp <= 0x10ffffL); if (cp >= 0x10000L) { cp -= 0x10000L; DUK_ASSERT(cp < 0x100000L); DUK_BW_WRITE_RAW_XUTF8(tfm_ctx->thr, &tfm_ctx->bw, ((cp >> 10) + 0xd800L)); DUK_BW_WRITE_RAW_XUTF8(tfm_ctx->thr, &tfm_ctx->bw, ((cp & 0x03ffL) + 0xdc00L)); } else { DUK_BW_WRITE_RAW_XUTF8(tfm_ctx->thr, &tfm_ctx->bw, cp); } } else { DUK_BW_WRITE_RAW_XUTF8(tfm_ctx->thr, &tfm_ctx->bw, cp); } return; uri_error: DUK_ERROR_URI(tfm_ctx->thr, DUK_STR_INVALID_INPUT); DUK_WO_NORETURN(return;); } #if defined(DUK_USE_SECTION_B) DUK_LOCAL void duk__transform_callback_escape(duk__transform_context *tfm_ctx, const void *udata, duk_codepoint_t cp) { DUK_UNREF(udata); DUK_BW_ENSURE(tfm_ctx->thr, &tfm_ctx->bw, 6); if (cp < 0) { goto esc_error; } else if ((cp < 0x80L) && DUK__CHECK_BITMASK(duk__escape_unescaped_table, cp)) { DUK_BW_WRITE_RAW_U8(tfm_ctx->thr, &tfm_ctx->bw, (duk_uint8_t) cp); } else if (cp < 0x100L) { DUK_BW_WRITE_RAW_U8_3(tfm_ctx->thr, &tfm_ctx->bw, (duk_uint8_t) DUK_ASC_PERCENT, (duk_uint8_t) duk_uc_nybbles[cp >> 4], (duk_uint8_t) duk_uc_nybbles[cp & 0x0f]); } else if (cp < 0x10000L) { DUK_BW_WRITE_RAW_U8_6(tfm_ctx->thr, &tfm_ctx->bw, (duk_uint8_t) DUK_ASC_PERCENT, (duk_uint8_t) DUK_ASC_LC_U, (duk_uint8_t) duk_uc_nybbles[cp >> 12], (duk_uint8_t) duk_uc_nybbles[(cp >> 8) & 0x0f], (duk_uint8_t) duk_uc_nybbles[(cp >> 4) & 0x0f], (duk_uint8_t) duk_uc_nybbles[cp & 0x0f]); } else { /* Characters outside BMP cannot be escape()'d. We could * encode them as surrogate pairs (for codepoints inside * valid UTF-8 range, but not extended UTF-8). Because * escape() and unescape() are legacy functions, we don't. */ goto esc_error; } return; esc_error: DUK_ERROR_TYPE(tfm_ctx->thr, DUK_STR_INVALID_INPUT); DUK_WO_NORETURN(return;); } DUK_LOCAL void duk__transform_callback_unescape(duk__transform_context *tfm_ctx, const void *udata, duk_codepoint_t cp) { duk_small_int_t t; DUK_UNREF(udata); if (cp == (duk_codepoint_t) '%') { const duk_uint8_t *p = tfm_ctx->p; duk_size_t left = (duk_size_t) (tfm_ctx->p_end - p); /* bytes left */ if (left >= 5 && p[0] == 'u' && ((t = duk__decode_hex_escape(p + 1, 4)) >= 0)) { cp = (duk_codepoint_t) t; tfm_ctx->p += 5; } else if (left >= 2 && ((t = duk__decode_hex_escape(p, 2)) >= 0)) { cp = (duk_codepoint_t) t; tfm_ctx->p += 2; } } DUK_BW_WRITE_ENSURE_XUTF8(tfm_ctx->thr, &tfm_ctx->bw, cp); } #endif /* DUK_USE_SECTION_B */ /* * Eval * * Eval needs to handle both a "direct eval" and an "indirect eval". * Direct eval handling needs access to the caller's activation so that its * lexical environment can be accessed. A direct eval is only possible from * ECMAScript code; an indirect eval call is possible also from C code. * When an indirect eval call is made from C code, there may not be a * calling activation at all which needs careful handling. */ DUK_INTERNAL duk_ret_t duk_bi_global_object_eval(duk_hthread *thr) { duk_hstring *h; duk_activation *act_caller; duk_activation *act_eval; duk_hcompfunc *func; duk_hobject *outer_lex_env; duk_hobject *outer_var_env; duk_bool_t this_to_global = 1; duk_small_uint_t comp_flags; duk_int_t level = -2; duk_small_uint_t call_flags; DUK_ASSERT(duk_get_top(thr) == 1 || duk_get_top(thr) == 2); /* 2 when called by debugger */ DUK_ASSERT(thr->callstack_top >= 1); /* at least this function exists */ DUK_ASSERT(thr->callstack_curr != NULL); DUK_ASSERT((thr->callstack_curr->flags & DUK_ACT_FLAG_DIRECT_EVAL) == 0 || /* indirect eval */ (thr->callstack_top >= 2)); /* if direct eval, calling activation must exist */ /* * callstack_top - 1 --> this function * callstack_top - 2 --> caller (may not exist) * * If called directly from C, callstack_top might be 1. If calling * activation doesn't exist, call must be indirect. */ h = duk_get_hstring_notsymbol(thr, 0); if (!h) { /* Symbol must be returned as is, like any non-string values. */ return 1; /* return arg as-is */ } #if defined(DUK_USE_DEBUGGER_SUPPORT) /* NOTE: level is used only by the debugger and should never be present * for an ECMAScript eval(). */ DUK_ASSERT(level == -2); /* by default, use caller's environment */ if (duk_get_top(thr) >= 2 && duk_is_number(thr, 1)) { level = duk_get_int(thr, 1); } DUK_ASSERT(level <= -2); /* This is guaranteed by debugger code. */ #endif /* [ source ] */ comp_flags = DUK_COMPILE_EVAL; act_eval = thr->callstack_curr; /* this function */ DUK_ASSERT(act_eval != NULL); act_caller = duk_hthread_get_activation_for_level(thr, level); if (act_caller != NULL) { /* Have a calling activation, check for direct eval (otherwise * assume indirect eval. */ if ((act_caller->flags & DUK_ACT_FLAG_STRICT) && (act_eval->flags & DUK_ACT_FLAG_DIRECT_EVAL)) { /* Only direct eval inherits strictness from calling code * (E5.1 Section 10.1.1). */ comp_flags |= DUK_COMPILE_STRICT; } } else { DUK_ASSERT((act_eval->flags & DUK_ACT_FLAG_DIRECT_EVAL) == 0); } duk_push_hstring_stridx(thr, DUK_STRIDX_INPUT); /* XXX: copy from caller? */ duk_js_compile(thr, (const duk_uint8_t *) DUK_HSTRING_GET_DATA(h), (duk_size_t) DUK_HSTRING_GET_BYTELEN(h), comp_flags); func = (duk_hcompfunc *) duk_known_hobject(thr, -1); DUK_ASSERT(DUK_HOBJECT_IS_COMPFUNC((duk_hobject *) func)); /* [ source template ] */ /* E5 Section 10.4.2 */ if (act_eval->flags & DUK_ACT_FLAG_DIRECT_EVAL) { DUK_ASSERT(thr->callstack_top >= 2); DUK_ASSERT(act_caller != NULL); if (act_caller->lex_env == NULL) { DUK_ASSERT(act_caller->var_env == NULL); DUK_DDD(DUK_DDDPRINT("delayed environment initialization")); /* this may have side effects, so re-lookup act */ duk_js_init_activation_environment_records_delayed(thr, act_caller); } DUK_ASSERT(act_caller->lex_env != NULL); DUK_ASSERT(act_caller->var_env != NULL); this_to_global = 0; if (DUK_HOBJECT_HAS_STRICT((duk_hobject *) func)) { duk_hdecenv *new_env; duk_hobject *act_lex_env; DUK_DDD(DUK_DDDPRINT("direct eval call to a strict function -> " "var_env and lex_env to a fresh env, " "this_binding to caller's this_binding")); act_lex_env = act_caller->lex_env; new_env = duk_hdecenv_alloc(thr, DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_DECENV)); DUK_ASSERT(new_env != NULL); duk_push_hobject(thr, (duk_hobject *) new_env); DUK_ASSERT(DUK_HOBJECT_GET_PROTOTYPE(thr->heap, (duk_hobject *) new_env) == NULL); DUK_HOBJECT_SET_PROTOTYPE(thr->heap, (duk_hobject *) new_env, act_lex_env); DUK_HOBJECT_INCREF_ALLOWNULL(thr, act_lex_env); DUK_DDD(DUK_DDDPRINT("new_env allocated: %!iO", (duk_heaphdr *) new_env)); outer_lex_env = (duk_hobject *) new_env; outer_var_env = (duk_hobject *) new_env; duk_insert(thr, 0); /* stash to bottom of value stack to keep new_env reachable for duration of eval */ /* compiler's responsibility */ DUK_ASSERT(DUK_HOBJECT_HAS_NEWENV((duk_hobject *) func)); } else { DUK_DDD(DUK_DDDPRINT("direct eval call to a non-strict function -> " "var_env and lex_env to caller's envs, " "this_binding to caller's this_binding")); outer_lex_env = act_caller->lex_env; outer_var_env = act_caller->var_env; /* compiler's responsibility */ DUK_ASSERT(!DUK_HOBJECT_HAS_NEWENV((duk_hobject *) func)); } } else { DUK_DDD(DUK_DDDPRINT("indirect eval call -> var_env and lex_env to " "global object, this_binding to global object")); this_to_global = 1; outer_lex_env = thr->builtins[DUK_BIDX_GLOBAL_ENV]; outer_var_env = thr->builtins[DUK_BIDX_GLOBAL_ENV]; } /* Eval code doesn't need an automatic .prototype object. */ duk_js_push_closure(thr, func, outer_var_env, outer_lex_env, 0 /*add_auto_proto*/); /* [ env? source template closure ] */ if (this_to_global) { DUK_ASSERT(thr->builtins[DUK_BIDX_GLOBAL] != NULL); duk_push_hobject_bidx(thr, DUK_BIDX_GLOBAL); } else { duk_tval *tv; DUK_ASSERT(thr->callstack_top >= 2); DUK_ASSERT(act_caller != NULL); tv = (duk_tval *) (void *) ((duk_uint8_t *) thr->valstack + act_caller->bottom_byteoff - sizeof(duk_tval)); /* this is just beneath bottom */ DUK_ASSERT(tv >= thr->valstack); duk_push_tval(thr, tv); } DUK_DDD(DUK_DDDPRINT("eval -> lex_env=%!iO, var_env=%!iO, this_binding=%!T", (duk_heaphdr *) outer_lex_env, (duk_heaphdr *) outer_var_env, duk_get_tval(thr, -1))); /* [ env? source template closure this ] */ call_flags = 0; if (act_eval->flags & DUK_ACT_FLAG_DIRECT_EVAL) { /* Set DIRECT_EVAL flag for the call; it's not strictly * needed for the 'inner' eval call (the eval body) but * current new.target implementation expects to find it * so it can traverse direct eval chains up to the real * calling function. */ call_flags |= DUK_CALL_FLAG_DIRECT_EVAL; } duk_handle_call_unprotected_nargs(thr, 0, call_flags); /* [ env? source template result ] */ return 1; } /* * Parsing of ints and floats */ #if defined(DUK_USE_GLOBAL_BUILTIN) DUK_INTERNAL duk_ret_t duk_bi_global_object_parse_int(duk_hthread *thr) { duk_int32_t radix; duk_small_uint_t s2n_flags; DUK_ASSERT_TOP(thr, 2); duk_to_string(thr, 0); /* Reject symbols. */ radix = duk_to_int32(thr, 1); /* While parseInt() recognizes 0xdeadbeef, it doesn't recognize * ES2015 0o123 or 0b10001. */ s2n_flags = DUK_S2N_FLAG_TRIM_WHITE | DUK_S2N_FLAG_ALLOW_GARBAGE | DUK_S2N_FLAG_ALLOW_PLUS | DUK_S2N_FLAG_ALLOW_MINUS | DUK_S2N_FLAG_ALLOW_LEADING_ZERO | DUK_S2N_FLAG_ALLOW_AUTO_HEX_INT; /* Specification stripPrefix maps to DUK_S2N_FLAG_ALLOW_AUTO_HEX_INT. * * Don't autodetect octals (from leading zeroes), require user code to * provide an explicit radix 8 for parsing octal. See write-up from Mozilla: * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt#ECMAScript_5_Removes_Octal_Interpretation */ if (radix != 0) { if (radix < 2 || radix > 36) { goto ret_nan; } if (radix != 16) { s2n_flags &= ~DUK_S2N_FLAG_ALLOW_AUTO_HEX_INT; } } else { radix = 10; } duk_dup_0(thr); duk_numconv_parse(thr, (duk_small_int_t) radix, s2n_flags); return 1; ret_nan: duk_push_nan(thr); return 1; } #endif /* DUK_USE_GLOBAL_BUILTIN */ #if defined(DUK_USE_GLOBAL_BUILTIN) DUK_INTERNAL duk_ret_t duk_bi_global_object_parse_float(duk_hthread *thr) { duk_small_uint_t s2n_flags; DUK_ASSERT_TOP(thr, 1); duk_to_string(thr, 0); /* Reject symbols. */ /* XXX: check flags */ s2n_flags = DUK_S2N_FLAG_TRIM_WHITE | DUK_S2N_FLAG_ALLOW_EXP | DUK_S2N_FLAG_ALLOW_GARBAGE | DUK_S2N_FLAG_ALLOW_PLUS | DUK_S2N_FLAG_ALLOW_MINUS | DUK_S2N_FLAG_ALLOW_INF | DUK_S2N_FLAG_ALLOW_FRAC | DUK_S2N_FLAG_ALLOW_NAKED_FRAC | DUK_S2N_FLAG_ALLOW_EMPTY_FRAC | DUK_S2N_FLAG_ALLOW_LEADING_ZERO; duk_numconv_parse(thr, 10 /*radix*/, s2n_flags); return 1; } #endif /* DUK_USE_GLOBAL_BUILTIN */ /* * Number checkers */ #if defined(DUK_USE_GLOBAL_BUILTIN) DUK_INTERNAL duk_ret_t duk_bi_global_object_is_nan(duk_hthread *thr) { duk_double_t d = duk_to_number(thr, 0); duk_push_boolean(thr, (duk_bool_t) DUK_ISNAN(d)); return 1; } #endif /* DUK_USE_GLOBAL_BUILTIN */ #if defined(DUK_USE_GLOBAL_BUILTIN) DUK_INTERNAL duk_ret_t duk_bi_global_object_is_finite(duk_hthread *thr) { duk_double_t d = duk_to_number(thr, 0); duk_push_boolean(thr, (duk_bool_t) DUK_ISFINITE(d)); return 1; } #endif /* DUK_USE_GLOBAL_BUILTIN */ /* * URI handling */ #if defined(DUK_USE_GLOBAL_BUILTIN) DUK_INTERNAL duk_ret_t duk_bi_global_object_decode_uri(duk_hthread *thr) { return duk__transform_helper(thr, duk__transform_callback_decode_uri, (const void *) duk__decode_uri_reserved_table); } DUK_INTERNAL duk_ret_t duk_bi_global_object_decode_uri_component(duk_hthread *thr) { return duk__transform_helper(thr, duk__transform_callback_decode_uri, (const void *) duk__decode_uri_component_reserved_table); } DUK_INTERNAL duk_ret_t duk_bi_global_object_encode_uri(duk_hthread *thr) { return duk__transform_helper(thr, duk__transform_callback_encode_uri, (const void *) duk__encode_uriunescaped_table); } DUK_INTERNAL duk_ret_t duk_bi_global_object_encode_uri_component(duk_hthread *thr) { return duk__transform_helper(thr, duk__transform_callback_encode_uri, (const void *) duk__encode_uricomponent_unescaped_table); } #if defined(DUK_USE_SECTION_B) DUK_INTERNAL duk_ret_t duk_bi_global_object_escape(duk_hthread *thr) { return duk__transform_helper(thr, duk__transform_callback_escape, (const void *) NULL); } DUK_INTERNAL duk_ret_t duk_bi_global_object_unescape(duk_hthread *thr) { return duk__transform_helper(thr, duk__transform_callback_unescape, (const void *) NULL); } #endif /* DUK_USE_SECTION_B */ #endif /* DUK_USE_GLOBAL_BUILTIN */ /* automatic undefs */ #undef DUK__CHECK_BITMASK #undef DUK__MKBITS #line 1 "duk_bi_json.c" /* * JSON built-ins. * * See doc/json.rst. * * Codepoints are handled as duk_uint_fast32_t to ensure that the full * unsigned 32-bit range is supported. This matters to e.g. JX. * * Input parsing doesn't do an explicit end-of-input check at all. This is * safe: input string data is always NUL-terminated (0x00) and valid JSON * inputs never contain plain NUL characters, so that as long as syntax checks * are correct, we'll never read past the NUL. This approach reduces code size * and improves parsing performance, but it's critical that syntax checks are * indeed correct! */ /* #include duk_internal.h -> already included */ #if defined(DUK_USE_JSON_SUPPORT) /* * Local defines and forward declarations. */ #define DUK__JSON_DECSTR_BUFSIZE 128 #define DUK__JSON_DECSTR_CHUNKSIZE 64 #define DUK__JSON_ENCSTR_CHUNKSIZE 64 #define DUK__JSON_STRINGIFY_BUFSIZE 128 #define DUK__JSON_MAX_ESC_LEN 10 /* '\Udeadbeef' */ DUK_LOCAL_DECL void duk__json_dec_syntax_error(duk_json_dec_ctx *js_ctx); DUK_LOCAL_DECL void duk__json_dec_eat_white(duk_json_dec_ctx *js_ctx); #if defined(DUK_USE_JX) DUK_LOCAL_DECL duk_uint8_t duk__json_dec_peek(duk_json_dec_ctx *js_ctx); #endif DUK_LOCAL_DECL duk_uint8_t duk__json_dec_get(duk_json_dec_ctx *js_ctx); DUK_LOCAL_DECL duk_uint8_t duk__json_dec_get_nonwhite(duk_json_dec_ctx *js_ctx); DUK_LOCAL_DECL duk_uint_fast32_t duk__json_dec_decode_hex_escape(duk_json_dec_ctx *js_ctx, duk_small_uint_t n); DUK_LOCAL_DECL void duk__json_dec_req_stridx(duk_json_dec_ctx *js_ctx, duk_small_uint_t stridx); DUK_LOCAL_DECL void duk__json_dec_string(duk_json_dec_ctx *js_ctx); #if defined(DUK_USE_JX) DUK_LOCAL_DECL void duk__json_dec_plain_string(duk_json_dec_ctx *js_ctx); DUK_LOCAL_DECL void duk__json_dec_pointer(duk_json_dec_ctx *js_ctx); DUK_LOCAL_DECL void duk__json_dec_buffer(duk_json_dec_ctx *js_ctx); #endif DUK_LOCAL_DECL void duk__json_dec_number(duk_json_dec_ctx *js_ctx); DUK_LOCAL_DECL void duk__json_dec_objarr_entry(duk_json_dec_ctx *js_ctx); DUK_LOCAL_DECL void duk__json_dec_objarr_exit(duk_json_dec_ctx *js_ctx); DUK_LOCAL_DECL void duk__json_dec_object(duk_json_dec_ctx *js_ctx); DUK_LOCAL_DECL void duk__json_dec_array(duk_json_dec_ctx *js_ctx); DUK_LOCAL_DECL void duk__json_dec_value(duk_json_dec_ctx *js_ctx); DUK_LOCAL_DECL void duk__json_dec_reviver_walk(duk_json_dec_ctx *js_ctx); DUK_LOCAL_DECL void duk__emit_1(duk_json_enc_ctx *js_ctx, duk_uint_fast8_t ch); DUK_LOCAL_DECL void duk__emit_2(duk_json_enc_ctx *js_ctx, duk_uint_fast8_t ch1, duk_uint_fast8_t ch2); DUK_LOCAL_DECL void duk__unemit_1(duk_json_enc_ctx *js_ctx); DUK_LOCAL_DECL void duk__emit_hstring(duk_json_enc_ctx *js_ctx, duk_hstring *h); #if defined(DUK_USE_FASTINT) DUK_LOCAL_DECL void duk__emit_cstring(duk_json_enc_ctx *js_ctx, const char *p); #endif DUK_LOCAL_DECL void duk__emit_stridx(duk_json_enc_ctx *js_ctx, duk_small_uint_t stridx); DUK_LOCAL_DECL duk_uint8_t *duk__emit_esc_auto_fast(duk_json_enc_ctx *js_ctx, duk_uint_fast32_t cp, duk_uint8_t *q); DUK_LOCAL_DECL void duk__json_enc_key_autoquote(duk_json_enc_ctx *js_ctx, duk_hstring *k); DUK_LOCAL_DECL void duk__json_enc_quote_string(duk_json_enc_ctx *js_ctx, duk_hstring *h_str); DUK_LOCAL_DECL void duk__json_enc_objarr_entry(duk_json_enc_ctx *js_ctx, duk_idx_t *entry_top); DUK_LOCAL_DECL void duk__json_enc_objarr_exit(duk_json_enc_ctx *js_ctx, duk_idx_t *entry_top); DUK_LOCAL_DECL void duk__json_enc_object(duk_json_enc_ctx *js_ctx); DUK_LOCAL_DECL void duk__json_enc_array(duk_json_enc_ctx *js_ctx); DUK_LOCAL_DECL duk_bool_t duk__json_enc_value(duk_json_enc_ctx *js_ctx, duk_idx_t idx_holder); DUK_LOCAL_DECL duk_bool_t duk__json_enc_allow_into_proplist(duk_tval *tv); DUK_LOCAL_DECL void duk__json_enc_double(duk_json_enc_ctx *js_ctx); #if defined(DUK_USE_FASTINT) DUK_LOCAL_DECL void duk__json_enc_fastint_tval(duk_json_enc_ctx *js_ctx, duk_tval *tv); #endif #if defined(DUK_USE_JX) || defined(DUK_USE_JC) DUK_LOCAL_DECL void duk__json_enc_buffer_jx_jc(duk_json_enc_ctx *js_ctx, duk_hbuffer *h); DUK_LOCAL_DECL void duk__json_enc_pointer(duk_json_enc_ctx *js_ctx, void *ptr); #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) DUK_LOCAL_DECL void duk__json_enc_bufobj(duk_json_enc_ctx *js_ctx, duk_hbufobj *h_bufobj); #endif #endif #if defined(DUK_USE_JSON_STRINGIFY_FASTPATH) DUK_LOCAL_DECL void duk__json_enc_buffer_json_fastpath(duk_json_enc_ctx *js_ctx, duk_hbuffer *h); #endif DUK_LOCAL_DECL void duk__json_enc_newline_indent(duk_json_enc_ctx *js_ctx, duk_uint_t depth); /* * Helper tables */ #if defined(DUK_USE_JSON_QUOTESTRING_FASTPATH) DUK_LOCAL const duk_uint8_t duk__json_quotestr_lookup[256] = { /* 0x00 ... 0x7f: as is * 0x80: escape generically * 0x81: slow path * 0xa0 ... 0xff: backslash + one char */ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xe2, 0xf4, 0xee, 0x80, 0xe6, 0xf2, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x20, 0x21, 0xa2, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0xdc, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81 }; #else /* DUK_USE_JSON_QUOTESTRING_FASTPATH */ DUK_LOCAL const duk_uint8_t duk__json_quotestr_esc[14] = { DUK_ASC_NUL, DUK_ASC_NUL, DUK_ASC_NUL, DUK_ASC_NUL, DUK_ASC_NUL, DUK_ASC_NUL, DUK_ASC_NUL, DUK_ASC_NUL, DUK_ASC_LC_B, DUK_ASC_LC_T, DUK_ASC_LC_N, DUK_ASC_NUL, DUK_ASC_LC_F, DUK_ASC_LC_R }; #endif /* DUK_USE_JSON_QUOTESTRING_FASTPATH */ #if defined(DUK_USE_JSON_DECSTRING_FASTPATH) DUK_LOCAL const duk_uint8_t duk__json_decstr_lookup[256] = { /* 0x00: slow path * other: as is */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x21, 0x00, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x00, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff }; #endif /* DUK_USE_JSON_DECSTRING_FASTPATH */ #if defined(DUK_USE_JSON_EATWHITE_FASTPATH) DUK_LOCAL const duk_uint8_t duk__json_eatwhite_lookup[256] = { /* 0x00: finish (non-white) * 0x01: continue */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; #endif /* DUK_USE_JSON_EATWHITE_FASTPATH */ #if defined(DUK_USE_JSON_DECNUMBER_FASTPATH) DUK_LOCAL const duk_uint8_t duk__json_decnumber_lookup[256] = { /* 0x00: finish (not part of number) * 0x01: continue */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; #endif /* DUK_USE_JSON_DECNUMBER_FASTPATH */ /* * Parsing implementation. * * JSON lexer is now separate from duk_lexer.c because there are numerous * small differences making it difficult to share the lexer. * * The parser here works with raw bytes directly; this works because all * JSON delimiters are ASCII characters. Invalid xUTF-8 encoded values * inside strings will be passed on without normalization; this is not a * compliance concern because compliant inputs will always be valid * CESU-8 encodings. */ DUK_LOCAL void duk__json_dec_syntax_error(duk_json_dec_ctx *js_ctx) { /* Shared handler to minimize parser size. Cause will be * hidden, unfortunately, but we'll have an offset which * is often quite enough. */ DUK_ERROR_FMT1(js_ctx->thr, DUK_ERR_SYNTAX_ERROR, DUK_STR_FMT_INVALID_JSON, (long) (js_ctx->p - js_ctx->p_start)); DUK_WO_NORETURN(return;); } DUK_LOCAL void duk__json_dec_eat_white(duk_json_dec_ctx *js_ctx) { const duk_uint8_t *p; duk_uint8_t t; p = js_ctx->p; for (;;) { DUK_ASSERT(p <= js_ctx->p_end); t = *p; #if defined(DUK_USE_JSON_EATWHITE_FASTPATH) /* This fast path is pretty marginal in practice. * XXX: candidate for removal. */ DUK_ASSERT(duk__json_eatwhite_lookup[0x00] == 0x00); /* end-of-input breaks */ if (duk__json_eatwhite_lookup[t] == 0) { break; } #else /* DUK_USE_JSON_EATWHITE_FASTPATH */ if (!(t == 0x20 || t == 0x0a || t == 0x0d || t == 0x09)) { /* NUL also comes here. Comparison order matters, 0x20 * is most common whitespace. */ break; } #endif /* DUK_USE_JSON_EATWHITE_FASTPATH */ p++; } js_ctx->p = p; } #if defined(DUK_USE_JX) DUK_LOCAL duk_uint8_t duk__json_dec_peek(duk_json_dec_ctx *js_ctx) { DUK_ASSERT(js_ctx->p <= js_ctx->p_end); return *js_ctx->p; } #endif DUK_LOCAL duk_uint8_t duk__json_dec_get(duk_json_dec_ctx *js_ctx) { DUK_ASSERT(js_ctx->p <= js_ctx->p_end); return *js_ctx->p++; } DUK_LOCAL duk_uint8_t duk__json_dec_get_nonwhite(duk_json_dec_ctx *js_ctx) { duk__json_dec_eat_white(js_ctx); return duk__json_dec_get(js_ctx); } /* For JX, expressing the whole unsigned 32-bit range matters. */ DUK_LOCAL duk_uint_fast32_t duk__json_dec_decode_hex_escape(duk_json_dec_ctx *js_ctx, duk_small_uint_t n) { duk_small_uint_t i; duk_uint_fast32_t res = 0; duk_uint8_t x; duk_small_int_t t; for (i = 0; i < n; i++) { /* XXX: share helper from lexer; duk_lexer.c / hexval(). */ x = duk__json_dec_get(js_ctx); DUK_DDD(DUK_DDDPRINT("decode_hex_escape: i=%ld, n=%ld, res=%ld, x=%ld", (long) i, (long) n, (long) res, (long) x)); /* x == 0x00 (EOF) causes syntax_error */ DUK_ASSERT(duk_hex_dectab[0] == -1); t = duk_hex_dectab[x & 0xff]; if (DUK_LIKELY(t >= 0)) { res = (res * 16) + (duk_uint_fast32_t) t; } else { /* catches EOF and invalid digits */ goto syntax_error; } } DUK_DDD(DUK_DDDPRINT("final hex decoded value: %ld", (long) res)); return res; syntax_error: duk__json_dec_syntax_error(js_ctx); DUK_UNREACHABLE(); return 0; } DUK_LOCAL void duk__json_dec_req_stridx(duk_json_dec_ctx *js_ctx, duk_small_uint_t stridx) { duk_hstring *h; const duk_uint8_t *p; duk_uint8_t x, y; /* First character has already been eaten and checked by the caller. * We can scan until a NUL in stridx string because no built-in strings * have internal NULs. */ DUK_ASSERT_STRIDX_VALID(stridx); h = DUK_HTHREAD_GET_STRING(js_ctx->thr, stridx); DUK_ASSERT(h != NULL); p = (const duk_uint8_t *) DUK_HSTRING_GET_DATA(h) + 1; DUK_ASSERT(*(js_ctx->p - 1) == *(p - 1)); /* first character has been matched */ for (;;) { x = *p; if (x == 0) { break; } y = duk__json_dec_get(js_ctx); if (x != y) { /* Catches EOF of JSON input. */ goto syntax_error; } p++; } return; syntax_error: duk__json_dec_syntax_error(js_ctx); DUK_UNREACHABLE(); } DUK_LOCAL duk_small_int_t duk__json_dec_string_escape(duk_json_dec_ctx *js_ctx, duk_uint8_t **ext_p) { duk_uint_fast32_t cp; /* EOF (-1) will be cast to an unsigned value first * and then re-cast for the switch. In any case, it * will match the default case (syntax error). */ cp = (duk_uint_fast32_t) duk__json_dec_get(js_ctx); switch (cp) { case DUK_ASC_BACKSLASH: break; case DUK_ASC_DOUBLEQUOTE: break; case DUK_ASC_SLASH: break; case DUK_ASC_LC_T: cp = 0x09; break; case DUK_ASC_LC_N: cp = 0x0a; break; case DUK_ASC_LC_R: cp = 0x0d; break; case DUK_ASC_LC_F: cp = 0x0c; break; case DUK_ASC_LC_B: cp = 0x08; break; case DUK_ASC_LC_U: { cp = duk__json_dec_decode_hex_escape(js_ctx, 4); break; } #if defined(DUK_USE_JX) case DUK_ASC_UC_U: { if (js_ctx->flag_ext_custom) { cp = duk__json_dec_decode_hex_escape(js_ctx, 8); } else { return 1; /* syntax error */ } break; } case DUK_ASC_LC_X: { if (js_ctx->flag_ext_custom) { cp = duk__json_dec_decode_hex_escape(js_ctx, 2); } else { return 1; /* syntax error */ } break; } #endif /* DUK_USE_JX */ default: /* catches EOF (0x00) */ return 1; /* syntax error */ } DUK_RAW_WRITEINC_XUTF8(*ext_p, cp); return 0; } DUK_LOCAL void duk__json_dec_string(duk_json_dec_ctx *js_ctx) { duk_hthread *thr = js_ctx->thr; duk_bufwriter_ctx bw_alloc; duk_bufwriter_ctx *bw; duk_uint8_t *q; /* '"' was eaten by caller */ /* Note that we currently parse -bytes-, not codepoints. * All non-ASCII extended UTF-8 will encode to bytes >= 0x80, * so they'll simply pass through (valid UTF-8 or not). */ bw = &bw_alloc; DUK_BW_INIT_PUSHBUF(js_ctx->thr, bw, DUK__JSON_DECSTR_BUFSIZE); q = DUK_BW_GET_PTR(js_ctx->thr, bw); #if defined(DUK_USE_JSON_DECSTRING_FASTPATH) for (;;) { duk_small_uint_t safe; duk_uint8_t b, x; const duk_uint8_t *p; /* Select a safe loop count where no output checks are * needed assuming we won't encounter escapes. Input * bound checks are not necessary as a NUL (guaranteed) * will cause a SyntaxError before we read out of bounds. */ safe = DUK__JSON_DECSTR_CHUNKSIZE; /* Ensure space for 1:1 output plus one escape. */ q = DUK_BW_ENSURE_RAW(js_ctx->thr, bw, safe + DUK_UNICODE_MAX_XUTF8_LENGTH, q); p = js_ctx->p; /* temp copy, write back for next loop */ for (;;) { if (safe == 0) { js_ctx->p = p; break; } safe--; /* End of input (NUL) goes through slow path and causes SyntaxError. */ DUK_ASSERT(duk__json_decstr_lookup[0] == 0x00); b = *p++; x = (duk_small_int_t) duk__json_decstr_lookup[b]; if (DUK_LIKELY(x != 0)) { /* Fast path, decode as is. */ *q++ = b; } else if (b == DUK_ASC_DOUBLEQUOTE) { js_ctx->p = p; goto found_quote; } else if (b == DUK_ASC_BACKSLASH) { /* We've ensured space for one escaped input; then * bail out and recheck (this makes escape handling * quite slow but it's uncommon). */ js_ctx->p = p; if (duk__json_dec_string_escape(js_ctx, &q) != 0) { goto syntax_error; } break; } else { js_ctx->p = p; goto syntax_error; } } } found_quote: #else /* DUK_USE_JSON_DECSTRING_FASTPATH */ for (;;) { duk_uint8_t x; q = DUK_BW_ENSURE_RAW(js_ctx->thr, bw, DUK_UNICODE_MAX_XUTF8_LENGTH, q); x = duk__json_dec_get(js_ctx); if (x == DUK_ASC_DOUBLEQUOTE) { break; } else if (x == DUK_ASC_BACKSLASH) { if (duk__json_dec_string_escape(js_ctx, &q) != 0) { goto syntax_error; } } else if (x < 0x20) { /* catches EOF (NUL) */ goto syntax_error; } else { *q++ = (duk_uint8_t) x; } } #endif /* DUK_USE_JSON_DECSTRING_FASTPATH */ DUK_BW_SETPTR_AND_COMPACT(js_ctx->thr, bw, q); (void) duk_buffer_to_string(thr, -1); /* Safe if input string is safe. */ /* [ ... str ] */ return; syntax_error: duk__json_dec_syntax_error(js_ctx); DUK_UNREACHABLE(); } #if defined(DUK_USE_JX) /* Decode a plain string consisting entirely of identifier characters. * Used to parse plain keys (e.g. "foo: 123"). */ DUK_LOCAL void duk__json_dec_plain_string(duk_json_dec_ctx *js_ctx) { duk_hthread *thr = js_ctx->thr; const duk_uint8_t *p; duk_small_int_t x; /* Caller has already eaten the first char so backtrack one byte. */ js_ctx->p--; /* safe */ p = js_ctx->p; /* Here again we parse bytes, and non-ASCII UTF-8 will cause end of * parsing (which is correct except if there are non-shortest encodings). * There is also no need to check explicitly for end of input buffer as * the input is NUL padded and NUL will exit the parsing loop. * * Because no unescaping takes place, we can just scan to the end of the * plain string and intern from the input buffer. */ for (;;) { x = *p; /* There is no need to check the first character specially here * (i.e. reject digits): the caller only accepts valid initial * characters and won't call us if the first character is a digit. * This also ensures that the plain string won't be empty. */ if (!duk_unicode_is_identifier_part((duk_codepoint_t) x)) { break; } p++; } duk_push_lstring(thr, (const char *) js_ctx->p, (duk_size_t) (p - js_ctx->p)); js_ctx->p = p; /* [ ... str ] */ } #endif /* DUK_USE_JX */ #if defined(DUK_USE_JX) DUK_LOCAL void duk__json_dec_pointer(duk_json_dec_ctx *js_ctx) { duk_hthread *thr = js_ctx->thr; const duk_uint8_t *p; duk_small_int_t x; void *voidptr; /* Caller has already eaten the first character ('(') which we don't need. */ p = js_ctx->p; for (;;) { x = *p; /* Assume that the native representation never contains a closing * parenthesis. */ if (x == DUK_ASC_RPAREN) { break; } else if (x <= 0) { /* NUL term or -1 (EOF), NUL check would suffice */ goto syntax_error; } p++; } /* There is no need to NUL delimit the sscanf() call: trailing garbage is * ignored and there is always a NUL terminator which will force an error * if no error is encountered before it. It's possible that the scan * would scan further than between [js_ctx->p,p[ though and we'd advance * by less than the scanned value. * * Because pointers are platform specific, a failure to scan a pointer * results in a null pointer which is a better placeholder than a missing * value or an error. */ voidptr = NULL; (void) DUK_SSCANF((const char *) js_ctx->p, DUK_STR_FMT_PTR, &voidptr); duk_push_pointer(thr, voidptr); js_ctx->p = p + 1; /* skip ')' */ /* [ ... ptr ] */ return; syntax_error: duk__json_dec_syntax_error(js_ctx); DUK_UNREACHABLE(); } #endif /* DUK_USE_JX */ #if defined(DUK_USE_JX) DUK_LOCAL void duk__json_dec_buffer(duk_json_dec_ctx *js_ctx) { duk_hthread *thr = js_ctx->thr; const duk_uint8_t *p; duk_uint8_t *buf; duk_size_t src_len; duk_small_int_t x; /* Caller has already eaten the first character ('|') which we don't need. */ p = js_ctx->p; /* XXX: Would be nice to share the fast path loop from duk_hex_decode() * and avoid creating a temporary buffer. However, there are some * differences which prevent trivial sharing: * * - Pipe char detection * - EOF detection * - Unknown length of input and output * * The best approach here would be a bufwriter and a reasonaly sized * safe inner loop (e.g. 64 output bytes at a time). */ for (;;) { x = *p; /* This loop intentionally does not ensure characters are valid * ([0-9a-fA-F]) because the hex decode call below will do that. */ if (x == DUK_ASC_PIPE) { break; } else if (x <= 0) { /* NUL term or -1 (EOF), NUL check would suffice */ goto syntax_error; } p++; } /* XXX: this is not very nice; unnecessary copy is made. */ src_len = (duk_size_t) (p - js_ctx->p); buf = (duk_uint8_t *) duk_push_fixed_buffer_nozero(thr, src_len); DUK_ASSERT(buf != NULL); duk_memcpy((void *) buf, (const void *) js_ctx->p, src_len); duk_hex_decode(thr, -1); js_ctx->p = p + 1; /* skip '|' */ /* [ ... buf ] */ return; syntax_error: duk__json_dec_syntax_error(js_ctx); DUK_UNREACHABLE(); } #endif /* DUK_USE_JX */ /* Parse a number, other than NaN or +/- Infinity */ DUK_LOCAL void duk__json_dec_number(duk_json_dec_ctx *js_ctx) { duk_hthread *thr = js_ctx->thr; const duk_uint8_t *p_start; const duk_uint8_t *p; duk_uint8_t x; duk_small_uint_t s2n_flags; DUK_DDD(DUK_DDDPRINT("parse_number")); p_start = js_ctx->p; /* First pass parse is very lenient (e.g. allows '1.2.3') and extracts a * string for strict number parsing. */ p = js_ctx->p; for (;;) { x = *p; DUK_DDD(DUK_DDDPRINT("parse_number: p_start=%p, p=%p, p_end=%p, x=%ld", (const void *) p_start, (const void *) p, (const void *) js_ctx->p_end, (long) x)); #if defined(DUK_USE_JSON_DECNUMBER_FASTPATH) /* This fast path is pretty marginal in practice. * XXX: candidate for removal. */ DUK_ASSERT(duk__json_decnumber_lookup[0x00] == 0x00); /* end-of-input breaks */ if (duk__json_decnumber_lookup[x] == 0) { break; } #else /* DUK_USE_JSON_DECNUMBER_FASTPATH */ if (!((x >= DUK_ASC_0 && x <= DUK_ASC_9) || (x == DUK_ASC_PERIOD || x == DUK_ASC_LC_E || x == DUK_ASC_UC_E || x == DUK_ASC_MINUS || x == DUK_ASC_PLUS))) { /* Plus sign must be accepted for positive exponents * (e.g. '1.5e+2'). This clause catches NULs. */ break; } #endif /* DUK_USE_JSON_DECNUMBER_FASTPATH */ p++; /* safe, because matched (NUL causes a break) */ } js_ctx->p = p; DUK_ASSERT(js_ctx->p > p_start); duk_push_lstring(thr, (const char *) p_start, (duk_size_t) (p - p_start)); s2n_flags = DUK_S2N_FLAG_ALLOW_EXP | DUK_S2N_FLAG_ALLOW_MINUS | /* but don't allow leading plus */ DUK_S2N_FLAG_ALLOW_FRAC; DUK_DDD(DUK_DDDPRINT("parse_number: string before parsing: %!T", (duk_tval *) duk_get_tval(thr, -1))); duk_numconv_parse(thr, 10 /*radix*/, s2n_flags); if (duk_is_nan(thr, -1)) { duk__json_dec_syntax_error(js_ctx); } DUK_ASSERT(duk_is_number(thr, -1)); DUK_DDD(DUK_DDDPRINT("parse_number: final number: %!T", (duk_tval *) duk_get_tval(thr, -1))); /* [ ... num ] */ } DUK_LOCAL void duk__json_dec_objarr_entry(duk_json_dec_ctx *js_ctx) { duk_hthread *thr = js_ctx->thr; duk_require_stack(thr, DUK_JSON_DEC_REQSTACK); /* c recursion check */ duk_native_stack_check(thr); DUK_ASSERT_DISABLE(js_ctx->recursion_depth >= 0); /* unsigned */ DUK_ASSERT(js_ctx->recursion_depth <= js_ctx->recursion_limit); if (js_ctx->recursion_depth >= js_ctx->recursion_limit) { DUK_ERROR_RANGE(thr, DUK_STR_DEC_RECLIMIT); DUK_WO_NORETURN(return;); } js_ctx->recursion_depth++; } DUK_LOCAL void duk__json_dec_objarr_exit(duk_json_dec_ctx *js_ctx) { /* c recursion check */ DUK_ASSERT(js_ctx->recursion_depth > 0); DUK_ASSERT(js_ctx->recursion_depth <= js_ctx->recursion_limit); js_ctx->recursion_depth--; } DUK_LOCAL void duk__json_dec_object(duk_json_dec_ctx *js_ctx) { duk_hthread *thr = js_ctx->thr; duk_int_t key_count; /* XXX: a "first" flag would suffice */ duk_uint8_t x; DUK_DDD(DUK_DDDPRINT("parse_object")); duk__json_dec_objarr_entry(js_ctx); duk_push_object(thr); /* Initial '{' has been checked and eaten by caller. */ key_count = 0; for (;;) { x = duk__json_dec_get_nonwhite(js_ctx); DUK_DDD(DUK_DDDPRINT("parse_object: obj=%!T, x=%ld, key_count=%ld", (duk_tval *) duk_get_tval(thr, -1), (long) x, (long) key_count)); /* handle comma and closing brace */ if (x == DUK_ASC_COMMA && key_count > 0) { /* accept comma, expect new value */ x = duk__json_dec_get_nonwhite(js_ctx); } else if (x == DUK_ASC_RCURLY) { /* eat closing brace */ break; } else if (key_count == 0) { /* accept anything, expect first value (EOF will be * caught by key parsing below. */ ; } else { /* catches EOF (NUL) and initial comma */ goto syntax_error; } /* parse key and value */ if (x == DUK_ASC_DOUBLEQUOTE) { duk__json_dec_string(js_ctx); #if defined(DUK_USE_JX) } else if (js_ctx->flag_ext_custom && duk_unicode_is_identifier_start((duk_codepoint_t) x)) { duk__json_dec_plain_string(js_ctx); #endif } else { goto syntax_error; } /* [ ... obj key ] */ x = duk__json_dec_get_nonwhite(js_ctx); if (x != DUK_ASC_COLON) { goto syntax_error; } duk__json_dec_value(js_ctx); /* [ ... obj key val ] */ duk_xdef_prop_wec(thr, -3); /* [ ... obj ] */ key_count++; } /* [ ... obj ] */ DUK_DDD(DUK_DDDPRINT("parse_object: final object is %!T", (duk_tval *) duk_get_tval(thr, -1))); duk__json_dec_objarr_exit(js_ctx); return; syntax_error: duk__json_dec_syntax_error(js_ctx); DUK_UNREACHABLE(); } DUK_LOCAL void duk__json_dec_array(duk_json_dec_ctx *js_ctx) { duk_hthread *thr = js_ctx->thr; duk_uarridx_t arr_idx; duk_uint8_t x; DUK_DDD(DUK_DDDPRINT("parse_array")); duk__json_dec_objarr_entry(js_ctx); duk_push_array(thr); /* Initial '[' has been checked and eaten by caller. */ arr_idx = 0; for (;;) { x = duk__json_dec_get_nonwhite(js_ctx); DUK_DDD(DUK_DDDPRINT("parse_array: arr=%!T, x=%ld, arr_idx=%ld", (duk_tval *) duk_get_tval(thr, -1), (long) x, (long) arr_idx)); /* handle comma and closing bracket */ if ((x == DUK_ASC_COMMA) && (arr_idx != 0)) { /* accept comma, expect new value */ ; } else if (x == DUK_ASC_RBRACKET) { /* eat closing bracket */ break; } else if (arr_idx == 0) { /* accept anything, expect first value (EOF will be * caught by duk__json_dec_value() below. */ js_ctx->p--; /* backtrack (safe) */ } else { /* catches EOF (NUL) and initial comma */ goto syntax_error; } /* parse value */ duk__json_dec_value(js_ctx); /* [ ... arr val ] */ duk_xdef_prop_index_wec(thr, -2, arr_idx); arr_idx++; } /* Must set 'length' explicitly when using duk_xdef_prop_xxx() to * set the values. */ duk_set_length(thr, -1, arr_idx); /* [ ... arr ] */ DUK_DDD(DUK_DDDPRINT("parse_array: final array is %!T", (duk_tval *) duk_get_tval(thr, -1))); duk__json_dec_objarr_exit(js_ctx); return; syntax_error: duk__json_dec_syntax_error(js_ctx); DUK_UNREACHABLE(); } DUK_LOCAL void duk__json_dec_value(duk_json_dec_ctx *js_ctx) { duk_hthread *thr = js_ctx->thr; duk_uint8_t x; x = duk__json_dec_get_nonwhite(js_ctx); DUK_DDD(DUK_DDDPRINT("parse_value: initial x=%ld", (long) x)); /* Note: duk__json_dec_req_stridx() backtracks one char */ if (x == DUK_ASC_DOUBLEQUOTE) { duk__json_dec_string(js_ctx); } else if ((x >= DUK_ASC_0 && x <= DUK_ASC_9) || (x == DUK_ASC_MINUS)) { #if defined(DUK_USE_JX) if (js_ctx->flag_ext_custom && x == DUK_ASC_MINUS && duk__json_dec_peek(js_ctx) == DUK_ASC_UC_I) { duk__json_dec_req_stridx(js_ctx, DUK_STRIDX_MINUS_INFINITY); /* "-Infinity", '-' has been eaten */ duk_push_number(thr, -DUK_DOUBLE_INFINITY); } else { #else { /* unconditional block */ #endif /* We already ate 'x', so backup one byte. */ js_ctx->p--; /* safe */ duk__json_dec_number(js_ctx); } } else if (x == DUK_ASC_LC_T) { duk__json_dec_req_stridx(js_ctx, DUK_STRIDX_TRUE); duk_push_true(thr); } else if (x == DUK_ASC_LC_F) { duk__json_dec_req_stridx(js_ctx, DUK_STRIDX_FALSE); duk_push_false(thr); } else if (x == DUK_ASC_LC_N) { duk__json_dec_req_stridx(js_ctx, DUK_STRIDX_LC_NULL); duk_push_null(thr); #if defined(DUK_USE_JX) } else if (js_ctx->flag_ext_custom && x == DUK_ASC_LC_U) { duk__json_dec_req_stridx(js_ctx, DUK_STRIDX_LC_UNDEFINED); duk_push_undefined(thr); } else if (js_ctx->flag_ext_custom && x == DUK_ASC_UC_N) { duk__json_dec_req_stridx(js_ctx, DUK_STRIDX_NAN); duk_push_nan(thr); } else if (js_ctx->flag_ext_custom && x == DUK_ASC_UC_I) { duk__json_dec_req_stridx(js_ctx, DUK_STRIDX_INFINITY); duk_push_number(thr, DUK_DOUBLE_INFINITY); } else if (js_ctx->flag_ext_custom && x == DUK_ASC_LPAREN) { duk__json_dec_pointer(js_ctx); } else if (js_ctx->flag_ext_custom && x == DUK_ASC_PIPE) { duk__json_dec_buffer(js_ctx); #endif } else if (x == DUK_ASC_LCURLY) { duk__json_dec_object(js_ctx); } else if (x == DUK_ASC_LBRACKET) { duk__json_dec_array(js_ctx); } else { /* catches EOF (NUL) */ goto syntax_error; } duk__json_dec_eat_white(js_ctx); /* [ ... val ] */ return; syntax_error: duk__json_dec_syntax_error(js_ctx); DUK_UNREACHABLE(); } /* Recursive value reviver, implements the Walk() algorithm. The parsing * step ensures there is a reasonable depth limit to the input. However, * the reviver may create more depth by editing object or array entries, so * we have both C recursion limit and native stack checks here. */ DUK_LOCAL void duk__json_dec_reviver_walk(duk_json_dec_ctx *js_ctx) { duk_hthread *thr = js_ctx->thr; duk_hobject *h; duk_uarridx_t i, arr_len; duk__json_dec_objarr_entry(js_ctx); DUK_DDD(DUK_DDDPRINT("walk: top=%ld, holder=%!T, name=%!T", (long) duk_get_top(thr), (duk_tval *) duk_get_tval(thr, -2), (duk_tval *) duk_get_tval(thr, -1))); duk_dup_top(thr); duk_get_prop(thr, -3); /* -> [ ... holder name val ] */ h = duk_get_hobject(thr, -1); if (h != NULL) { if (duk_js_isarray_hobject(h)) { arr_len = (duk_uarridx_t) duk_get_length(thr, -1); for (i = 0; i < arr_len; i++) { /* [ ... holder name val ] */ DUK_DDD(DUK_DDDPRINT("walk: array, top=%ld, i=%ld, arr_len=%ld, holder=%!T, name=%!T, val=%!T", (long) duk_get_top(thr), (long) i, (long) arr_len, (duk_tval *) duk_get_tval(thr, -3), (duk_tval *) duk_get_tval(thr, -2), (duk_tval *) duk_get_tval(thr, -1))); duk_dup_top(thr); (void) duk_push_uint_to_hstring(thr, (duk_uint_t) i); /* -> [ ... holder name val val ToString(i) ] */ duk__json_dec_reviver_walk(js_ctx); /* -> [ ... holder name val new_elem ] */ if (duk_is_undefined(thr, -1)) { duk_pop(thr); duk_del_prop_index(thr, -1, i); } else { /* XXX: duk_xdef_prop_index_wec() would be more appropriate * here but it currently makes some assumptions that might * not hold (e.g. that previous property is not an accessor). */ duk_put_prop_index(thr, -2, i); } } } else { /* [ ... holder name val ] */ duk_enum(thr, -1, DUK_ENUM_OWN_PROPERTIES_ONLY /*flags*/); while (duk_next(thr, -1 /*enum_index*/, 0 /*get_value*/)) { DUK_DDD(DUK_DDDPRINT("walk: object, top=%ld, holder=%!T, name=%!T, val=%!T, enum=%!iT, obj_key=%!T", (long) duk_get_top(thr), (duk_tval *) duk_get_tval(thr, -5), (duk_tval *) duk_get_tval(thr, -4), (duk_tval *) duk_get_tval(thr, -3), (duk_tval *) duk_get_tval(thr, -2), (duk_tval *) duk_get_tval(thr, -1))); /* [ ... holder name val enum obj_key ] */ duk_dup_m3(thr); duk_dup_m2(thr); /* [ ... holder name val enum obj_key val obj_key ] */ duk__json_dec_reviver_walk(js_ctx); /* [ ... holder name val enum obj_key new_elem ] */ if (duk_is_undefined(thr, -1)) { duk_pop(thr); duk_del_prop(thr, -3); } else { /* XXX: duk_xdef_prop_index_wec() would be more appropriate * here but it currently makes some assumptions that might * not hold (e.g. that previous property is not an accessor). * * Using duk_put_prop() works incorrectly with '__proto__' * if the own property with that name has been deleted. This * does not happen normally, but a clever reviver can trigger * that, see complex reviver case in: test-bug-json-parse-__proto__.js. */ duk_put_prop(thr, -4); } } duk_pop(thr); /* pop enum */ } } /* [ ... holder name val ] */ duk_dup(thr, js_ctx->idx_reviver); duk_insert(thr, -4); /* -> [ ... reviver holder name val ] */ duk_call_method(thr, 2); /* -> [ ... res ] */ duk__json_dec_objarr_exit(js_ctx); DUK_DDD(DUK_DDDPRINT("walk: top=%ld, result=%!T", (long) duk_get_top(thr), (duk_tval *) duk_get_tval(thr, -1))); } /* * Stringify implementation. */ #define DUK__EMIT_1(js_ctx, ch) duk__emit_1((js_ctx), (duk_uint_fast8_t) (ch)) #define DUK__EMIT_2(js_ctx, ch1, ch2) duk__emit_2((js_ctx), (duk_uint_fast8_t) (ch1), (duk_uint_fast8_t) (ch2)) #define DUK__EMIT_HSTR(js_ctx, h) duk__emit_hstring((js_ctx), (h)) #if defined(DUK_USE_FASTINT) || defined(DUK_USE_JX) || defined(DUK_USE_JC) #define DUK__EMIT_CSTR(js_ctx, p) duk__emit_cstring((js_ctx), (p)) #endif #define DUK__EMIT_STRIDX(js_ctx, i) duk__emit_stridx((js_ctx), (i)) #define DUK__UNEMIT_1(js_ctx) duk__unemit_1((js_ctx)) DUK_LOCAL void duk__emit_1(duk_json_enc_ctx *js_ctx, duk_uint_fast8_t ch) { DUK_BW_WRITE_ENSURE_U8(js_ctx->thr, &js_ctx->bw, ch); } DUK_LOCAL void duk__emit_2(duk_json_enc_ctx *js_ctx, duk_uint_fast8_t ch1, duk_uint_fast8_t ch2) { DUK_BW_WRITE_ENSURE_U8_2(js_ctx->thr, &js_ctx->bw, ch1, ch2); } DUK_LOCAL void duk__emit_hstring(duk_json_enc_ctx *js_ctx, duk_hstring *h) { DUK_BW_WRITE_ENSURE_HSTRING(js_ctx->thr, &js_ctx->bw, h); } #if defined(DUK_USE_FASTINT) || defined(DUK_USE_JX) || defined(DUK_USE_JC) DUK_LOCAL void duk__emit_cstring(duk_json_enc_ctx *js_ctx, const char *str) { DUK_BW_WRITE_ENSURE_CSTRING(js_ctx->thr, &js_ctx->bw, str); } #endif DUK_LOCAL void duk__emit_stridx(duk_json_enc_ctx *js_ctx, duk_small_uint_t stridx) { duk_hstring *h; DUK_ASSERT_STRIDX_VALID(stridx); h = DUK_HTHREAD_GET_STRING(js_ctx->thr, stridx); DUK_ASSERT(h != NULL); DUK_BW_WRITE_ENSURE_HSTRING(js_ctx->thr, &js_ctx->bw, h); } DUK_LOCAL void duk__unemit_1(duk_json_enc_ctx *js_ctx) { DUK_ASSERT(DUK_BW_GET_SIZE(js_ctx->thr, &js_ctx->bw) >= 1); DUK_BW_ADD_PTR(js_ctx->thr, &js_ctx->bw, -1); } #define DUK__MKESC(nybbles, esc1, esc2) \ (((duk_uint_fast32_t) (nybbles)) << 16) | (((duk_uint_fast32_t) (esc1)) << 8) | ((duk_uint_fast32_t) (esc2)) DUK_LOCAL duk_uint8_t *duk__emit_esc_auto_fast(duk_json_enc_ctx *js_ctx, duk_uint_fast32_t cp, duk_uint8_t *q) { duk_uint_fast32_t tmp; duk_small_uint_t dig; DUK_UNREF(js_ctx); /* Caller ensures space for at least DUK__JSON_MAX_ESC_LEN. */ /* Select appropriate escape format automatically, and set 'tmp' to a * value encoding both the escape format character and the nybble count: * * (nybble_count << 16) | (escape_char1) | (escape_char2) */ #if defined(DUK_USE_JX) if (DUK_LIKELY(cp < 0x100UL)) { if (DUK_UNLIKELY(js_ctx->flag_ext_custom != 0U)) { tmp = DUK__MKESC(2, DUK_ASC_BACKSLASH, DUK_ASC_LC_X); } else { tmp = DUK__MKESC(4, DUK_ASC_BACKSLASH, DUK_ASC_LC_U); } } else #endif if (DUK_LIKELY(cp < 0x10000UL)) { tmp = DUK__MKESC(4, DUK_ASC_BACKSLASH, DUK_ASC_LC_U); } else { #if defined(DUK_USE_JX) if (DUK_LIKELY(js_ctx->flag_ext_custom != 0U)) { tmp = DUK__MKESC(8, DUK_ASC_BACKSLASH, DUK_ASC_UC_U); } else #endif { /* In compatible mode and standard JSON mode, output * something useful for non-BMP characters. This won't * roundtrip but will still be more or less readable and * more useful than an error. */ tmp = DUK__MKESC(8, DUK_ASC_UC_U, DUK_ASC_PLUS); } } *q++ = (duk_uint8_t) ((tmp >> 8) & 0xff); *q++ = (duk_uint8_t) (tmp & 0xff); tmp = tmp >> 16; while (tmp > 0) { tmp--; dig = (duk_small_uint_t) ((cp >> (4 * tmp)) & 0x0f); *q++ = duk_lc_digits[dig]; } return q; } DUK_LOCAL void duk__json_enc_key_autoquote(duk_json_enc_ctx *js_ctx, duk_hstring *k) { const duk_int8_t *p, *p_start, *p_end; /* Note: intentionally signed. */ duk_size_t k_len; duk_codepoint_t cp; DUK_ASSERT(k != NULL); /* Accept ASCII strings which conform to identifier requirements * as being emitted without key quotes. Since we only accept ASCII * there's no need for actual decoding: 'p' is intentionally signed * so that bytes >= 0x80 extend to negative values and are rejected * as invalid identifier codepoints. */ if (js_ctx->flag_avoid_key_quotes) { k_len = DUK_HSTRING_GET_BYTELEN(k); p_start = (const duk_int8_t *) DUK_HSTRING_GET_DATA(k); p_end = p_start + k_len; p = p_start; if (p == p_end) { /* Zero length string is not accepted without quotes */ goto quote_normally; } cp = (duk_codepoint_t) (*p++); if (DUK_UNLIKELY(!duk_unicode_is_identifier_start(cp))) { goto quote_normally; } while (p < p_end) { cp = (duk_codepoint_t) (*p++); if (DUK_UNLIKELY(!duk_unicode_is_identifier_part(cp))) { goto quote_normally; } } /* This seems faster than emitting bytes one at a time and * then potentially rewinding. */ DUK__EMIT_HSTR(js_ctx, k); return; } quote_normally: duk__json_enc_quote_string(js_ctx, k); } /* The Quote(value) operation: quote a string. * * Stack policy: [ ] -> [ ]. */ DUK_LOCAL void duk__json_enc_quote_string(duk_json_enc_ctx *js_ctx, duk_hstring *h_str) { duk_hthread *thr = js_ctx->thr; const duk_uint8_t *p, *p_start, *p_end, *p_now, *p_tmp; duk_uint8_t *q; duk_ucodepoint_t cp; /* typed for duk_unicode_decode_xutf8() */ DUK_DDD(DUK_DDDPRINT("duk__json_enc_quote_string: h_str=%!O", (duk_heaphdr *) h_str)); DUK_ASSERT(h_str != NULL); p_start = DUK_HSTRING_GET_DATA(h_str); p_end = p_start + DUK_HSTRING_GET_BYTELEN(h_str); p = p_start; DUK__EMIT_1(js_ctx, DUK_ASC_DOUBLEQUOTE); /* Encode string in small chunks, estimating the maximum expansion so that * there's no need to ensure space while processing the chunk. */ while (p < p_end) { duk_size_t left, now, space; left = (duk_size_t) (p_end - p); now = (left > DUK__JSON_ENCSTR_CHUNKSIZE ? DUK__JSON_ENCSTR_CHUNKSIZE : left); /* Maximum expansion per input byte is 6: * - invalid UTF-8 byte causes "\uXXXX" to be emitted (6/1 = 6). * - 2-byte UTF-8 encodes as "\uXXXX" (6/2 = 3). * - 4-byte UTF-8 encodes as "\Uxxxxxxxx" (10/4 = 2.5). */ space = now * 6; q = DUK_BW_ENSURE_GETPTR(thr, &js_ctx->bw, space); p_now = p + now; while (p < p_now) { #if defined(DUK_USE_JSON_QUOTESTRING_FASTPATH) duk_uint8_t b; b = duk__json_quotestr_lookup[*p++]; if (DUK_LIKELY(b < 0x80)) { /* Most input bytes go through here. */ *q++ = b; } else if (b >= 0xa0) { *q++ = DUK_ASC_BACKSLASH; *q++ = (duk_uint8_t) (b - 0x80); } else if (b == 0x80) { cp = (duk_ucodepoint_t) (*(p - 1)); q = duk__emit_esc_auto_fast(js_ctx, cp, q); } else if (b == 0x7f && js_ctx->flag_ascii_only) { /* 0x7F is special */ DUK_ASSERT(b == 0x81); cp = (duk_ucodepoint_t) 0x7f; q = duk__emit_esc_auto_fast(js_ctx, cp, q); } else { DUK_ASSERT(b == 0x81); p--; /* slow path is shared */ #else /* DUK_USE_JSON_QUOTESTRING_FASTPATH */ cp = *p; if (DUK_LIKELY(cp <= 0x7f)) { /* ascii fast path: avoid decoding utf-8 */ p++; if (cp == 0x22 || cp == 0x5c) { /* double quote or backslash */ *q++ = DUK_ASC_BACKSLASH; *q++ = (duk_uint8_t) cp; } else if (cp < 0x20) { duk_uint_fast8_t esc_char; /* This approach is a bit shorter than a straight * if-else-ladder and also a bit faster. */ if (cp < (sizeof(duk__json_quotestr_esc) / sizeof(duk_uint8_t)) && (esc_char = duk__json_quotestr_esc[cp]) != 0) { *q++ = DUK_ASC_BACKSLASH; *q++ = (duk_uint8_t) esc_char; } else { q = duk__emit_esc_auto_fast(js_ctx, cp, q); } } else if (cp == 0x7f && js_ctx->flag_ascii_only) { q = duk__emit_esc_auto_fast(js_ctx, cp, q); } else { /* any other printable -> as is */ *q++ = (duk_uint8_t) cp; } } else { /* slow path is shared */ #endif /* DUK_USE_JSON_QUOTESTRING_FASTPATH */ /* slow path decode */ /* If XUTF-8 decoding fails, treat the offending byte as a codepoint directly * and go forward one byte. This is of course very lossy, but allows some kind * of output to be produced even for internal strings which don't conform to * XUTF-8. All standard ECMAScript strings are always CESU-8, so this behavior * does not violate the ECMAScript specification. The behavior is applied to * all modes, including ECMAScript standard JSON. Because the current XUTF-8 * decoding is not very strict, this behavior only really affects initial bytes * and truncated codepoints. * * Another alternative would be to scan forwards to start of next codepoint * (or end of input) and emit just one replacement codepoint. */ p_tmp = p; if (!duk_unicode_decode_xutf8(thr, &p, p_start, p_end, &cp)) { /* Decode failed. */ cp = *p_tmp; p = p_tmp + 1; } #if defined(DUK_USE_NONSTD_JSON_ESC_U2028_U2029) if (js_ctx->flag_ascii_only || cp == 0x2028 || cp == 0x2029) { #else if (js_ctx->flag_ascii_only) { #endif q = duk__emit_esc_auto_fast(js_ctx, cp, q); } else { /* as is */ DUK_RAW_WRITEINC_XUTF8(q, cp); } } } DUK_BW_SET_PTR(thr, &js_ctx->bw, q); } DUK__EMIT_1(js_ctx, DUK_ASC_DOUBLEQUOTE); } /* Encode a double (checked by caller) from stack top. Stack top may be * replaced by serialized string but is not popped (caller does that). */ DUK_LOCAL void duk__json_enc_double(duk_json_enc_ctx *js_ctx) { duk_hthread *thr; duk_tval *tv; duk_double_t d; duk_small_int_t c; duk_small_int_t s; duk_small_uint_t stridx; duk_small_uint_t n2s_flags; duk_hstring *h_str; DUK_ASSERT(js_ctx != NULL); thr = js_ctx->thr; DUK_ASSERT(thr != NULL); /* Caller must ensure 'tv' is indeed a double and not a fastint! */ tv = DUK_GET_TVAL_NEGIDX(thr, -1); DUK_ASSERT(DUK_TVAL_IS_DOUBLE(tv)); d = DUK_TVAL_GET_DOUBLE(tv); c = (duk_small_int_t) DUK_FPCLASSIFY(d); s = (duk_small_int_t) DUK_SIGNBIT(d); DUK_UNREF(s); if (DUK_LIKELY(!(c == DUK_FP_INFINITE || c == DUK_FP_NAN))) { DUK_ASSERT(DUK_ISFINITE(d)); #if defined(DUK_USE_JX) || defined(DUK_USE_JC) /* Negative zero needs special handling in JX/JC because * it would otherwise serialize to '0', not '-0'. */ if (DUK_UNLIKELY(c == DUK_FP_ZERO && s != 0 && (js_ctx->flag_ext_custom_or_compatible))) { duk_push_hstring_stridx(thr, DUK_STRIDX_MINUS_ZERO); /* '-0' */ } else #endif /* DUK_USE_JX || DUK_USE_JC */ { n2s_flags = 0; /* [ ... number ] -> [ ... string ] */ duk_numconv_stringify(thr, 10 /*radix*/, 0 /*digits*/, n2s_flags); } h_str = duk_known_hstring(thr, -1); DUK__EMIT_HSTR(js_ctx, h_str); return; } #if defined(DUK_USE_JX) || defined(DUK_USE_JC) if (!(js_ctx->flags & (DUK_JSON_FLAG_EXT_CUSTOM | DUK_JSON_FLAG_EXT_COMPATIBLE))) { stridx = DUK_STRIDX_LC_NULL; } else if (c == DUK_FP_NAN) { stridx = js_ctx->stridx_custom_nan; } else if (s == 0) { stridx = js_ctx->stridx_custom_posinf; } else { stridx = js_ctx->stridx_custom_neginf; } #else stridx = DUK_STRIDX_LC_NULL; #endif DUK__EMIT_STRIDX(js_ctx, stridx); } #if defined(DUK_USE_FASTINT) /* Encode a fastint from duk_tval ptr, no value stack effects. */ DUK_LOCAL void duk__json_enc_fastint_tval(duk_json_enc_ctx *js_ctx, duk_tval *tv) { duk_int64_t v; /* Fastint range is signed 48-bit so longest value is -2^47 = -140737488355328 * (16 chars long), longest signed 64-bit value is -2^63 = -9223372036854775808 * (20 chars long). Alloc space for 64-bit range to be safe. */ duk_uint8_t buf[20 + 1]; /* Caller must ensure 'tv' is indeed a fastint! */ DUK_ASSERT(DUK_TVAL_IS_FASTINT(tv)); v = DUK_TVAL_GET_FASTINT(tv); /* XXX: There are no format strings in duk_config.h yet, could add * one for formatting duk_int64_t. For now, assumes "%lld" and that * "long long" type exists. Could also rely on C99 directly but that * won't work for older MSVC. */ DUK_SPRINTF((char *) buf, "%lld", (long long) v); DUK__EMIT_CSTR(js_ctx, (const char *) buf); } #endif #if defined(DUK_USE_JX) || defined(DUK_USE_JC) #if defined(DUK_USE_HEX_FASTPATH) DUK_LOCAL duk_uint8_t *duk__json_enc_buffer_data_hex(const duk_uint8_t *src, duk_size_t src_len, duk_uint8_t *dst) { duk_uint8_t *q; duk_uint16_t *q16; duk_small_uint_t x; duk_size_t i, len_safe; #if !defined(DUK_USE_UNALIGNED_ACCESSES_POSSIBLE) duk_bool_t shift_dst; #endif /* Unlike in duk_hex_encode() 'dst' is not necessarily aligned by 2. * For platforms where unaligned accesses are not allowed, shift 'dst' * ahead by 1 byte to get alignment and then duk_memmove() the result * in place. The faster encoding loop makes up the difference. * There's always space for one extra byte because a terminator always * follows the hex data and that's been accounted for by the caller. */ #if defined(DUK_USE_UNALIGNED_ACCESSES_POSSIBLE) q16 = (duk_uint16_t *) (void *) dst; #else shift_dst = (duk_bool_t) (((duk_size_t) dst) & 0x01U); if (shift_dst) { DUK_DD(DUK_DDPRINT("unaligned accesses not possible, dst not aligned -> step to dst + 1")); q16 = (duk_uint16_t *) (void *) (dst + 1); } else { DUK_DD(DUK_DDPRINT("unaligned accesses not possible, dst is aligned")); q16 = (duk_uint16_t *) (void *) dst; } DUK_ASSERT((((duk_size_t) q16) & 0x01U) == 0); #endif len_safe = src_len & ~0x03U; for (i = 0; i < len_safe; i += 4) { q16[0] = duk_hex_enctab[src[i]]; q16[1] = duk_hex_enctab[src[i + 1]]; q16[2] = duk_hex_enctab[src[i + 2]]; q16[3] = duk_hex_enctab[src[i + 3]]; q16 += 4; } q = (duk_uint8_t *) q16; #if !defined(DUK_USE_UNALIGNED_ACCESSES_POSSIBLE) if (shift_dst) { q--; duk_memmove((void *) dst, (const void *) (dst + 1), 2 * len_safe); DUK_ASSERT(dst + 2 * len_safe == q); } #endif for (; i < src_len; i++) { x = src[i]; *q++ = duk_lc_digits[x >> 4]; *q++ = duk_lc_digits[x & 0x0f]; } return q; } #else /* DUK_USE_HEX_FASTPATH */ DUK_LOCAL duk_uint8_t *duk__json_enc_buffer_data_hex(const duk_uint8_t *src, duk_size_t src_len, duk_uint8_t *dst) { const duk_uint8_t *p; const duk_uint8_t *p_end; duk_uint8_t *q; duk_small_uint_t x; p = src; p_end = src + src_len; q = dst; while (p != p_end) { x = *p++; *q++ = duk_lc_digits[x >> 4]; *q++ = duk_lc_digits[x & 0x0f]; } return q; } #endif /* DUK_USE_HEX_FASTPATH */ DUK_LOCAL void duk__json_enc_buffer_data(duk_json_enc_ctx *js_ctx, duk_uint8_t *buf_data, duk_size_t buf_len) { duk_hthread *thr; duk_uint8_t *q; duk_size_t space; thr = js_ctx->thr; DUK_ASSERT(js_ctx->flag_ext_custom || js_ctx->flag_ext_compatible); /* caller checks */ DUK_ASSERT(js_ctx->flag_ext_custom_or_compatible); /* Buffer values are encoded in (lowercase) hex to make the * binary data readable. Base64 or similar would be more * compact but less readable, and the point of JX/JC * variants is to be as useful to a programmer as possible. */ /* The #if defined() clutter here needs to handle the three * cases: (1) JX+JC, (2) JX only, (3) JC only. */ /* Note: space must cater for both JX and JC. */ space = 9 + buf_len * 2 + 2; DUK_ASSERT(DUK_HBUFFER_MAX_BYTELEN <= 0x7ffffffeUL); DUK_ASSERT((space - 2) / 2 >= buf_len); /* overflow not possible, buffer limits */ q = DUK_BW_ENSURE_GETPTR(thr, &js_ctx->bw, space); #if defined(DUK_USE_JX) && defined(DUK_USE_JC) if (js_ctx->flag_ext_custom) #endif #if defined(DUK_USE_JX) { *q++ = DUK_ASC_PIPE; q = duk__json_enc_buffer_data_hex(buf_data, buf_len, q); *q++ = DUK_ASC_PIPE; } #endif #if defined(DUK_USE_JX) && defined(DUK_USE_JC) else #endif #if defined(DUK_USE_JC) { DUK_ASSERT(js_ctx->flag_ext_compatible); duk_memcpy((void *) q, (const void *) "{\"_buf\":\"", 9); /* len: 9 */ q += 9; q = duk__json_enc_buffer_data_hex(buf_data, buf_len, q); *q++ = DUK_ASC_DOUBLEQUOTE; *q++ = DUK_ASC_RCURLY; } #endif DUK_BW_SET_PTR(thr, &js_ctx->bw, q); } DUK_LOCAL void duk__json_enc_buffer_jx_jc(duk_json_enc_ctx *js_ctx, duk_hbuffer *h) { duk__json_enc_buffer_data(js_ctx, (duk_uint8_t *) DUK_HBUFFER_GET_DATA_PTR(js_ctx->thr->heap, h), (duk_size_t) DUK_HBUFFER_GET_SIZE(h)); } #endif /* DUK_USE_JX || DUK_USE_JC */ #if defined(DUK_USE_JSON_STRINGIFY_FASTPATH) DUK_LOCAL void duk__json_enc_buffer_json_fastpath(duk_json_enc_ctx *js_ctx, duk_hbuffer *h) { duk_size_t i, n; const duk_uint8_t *buf; duk_uint8_t *q; n = DUK_HBUFFER_GET_SIZE(h); if (n == 0) { DUK__EMIT_2(js_ctx, DUK_ASC_LCURLY, DUK_ASC_RCURLY); return; } DUK__EMIT_1(js_ctx, DUK_ASC_LCURLY); /* Maximum encoded length with 32-bit index: 1 + 10 + 2 + 3 + 1 + 1 = 18, * with 64-bit index: 1 + 20 + 2 + 3 + 1 + 1 = 28. 32 has some slack. * * Note that because the output buffer is reallocated from time to time, * side effects (such as finalizers) affecting the buffer 'h' must be * disabled. This is the case in the JSON.stringify() fast path. */ buf = (const duk_uint8_t *) DUK_HBUFFER_GET_DATA_PTR(js_ctx->thr->heap, h); if (DUK_UNLIKELY(js_ctx->h_gap != NULL)) { for (i = 0; i < n; i++) { duk__json_enc_newline_indent(js_ctx, js_ctx->recursion_depth + 1); q = DUK_BW_ENSURE_GETPTR(js_ctx->thr, &js_ctx->bw, 32); q += DUK_SPRINTF((char *) q, "\"%lu\": %u,", (unsigned long) i, (unsigned int) buf[i]); DUK_BW_SET_PTR(js_ctx->thr, &js_ctx->bw, q); } } else { q = DUK_BW_GET_PTR(js_ctx->thr, &js_ctx->bw); for (i = 0; i < n; i++) { q = DUK_BW_ENSURE_RAW(js_ctx->thr, &js_ctx->bw, 32, q); q += DUK_SPRINTF((char *) q, "\"%lu\":%u,", (unsigned long) i, (unsigned int) buf[i]); } DUK_BW_SET_PTR(js_ctx->thr, &js_ctx->bw, q); } DUK__UNEMIT_1(js_ctx); /* eat trailing comma */ if (DUK_UNLIKELY(js_ctx->h_gap != NULL)) { duk__json_enc_newline_indent(js_ctx, js_ctx->recursion_depth); } DUK__EMIT_1(js_ctx, DUK_ASC_RCURLY); } #endif /* DUK_USE_JSON_STRINGIFY_FASTPATH */ #if defined(DUK_USE_JX) || defined(DUK_USE_JC) DUK_LOCAL void duk__json_enc_pointer(duk_json_enc_ctx *js_ctx, void *ptr) { char buf[64]; /* XXX: how to figure correct size? */ const char *fmt; DUK_ASSERT(js_ctx->flag_ext_custom || js_ctx->flag_ext_compatible); /* caller checks */ DUK_ASSERT(js_ctx->flag_ext_custom_or_compatible); duk_memzero(buf, sizeof(buf)); /* The #if defined() clutter here needs to handle the three * cases: (1) JX+JC, (2) JX only, (3) JC only. */ #if defined(DUK_USE_JX) && defined(DUK_USE_JC) if (js_ctx->flag_ext_custom) #endif #if defined(DUK_USE_JX) { fmt = ptr ? "(%p)" : "(null)"; } #endif #if defined(DUK_USE_JX) && defined(DUK_USE_JC) else #endif #if defined(DUK_USE_JC) { DUK_ASSERT(js_ctx->flag_ext_compatible); fmt = ptr ? "{\"_ptr\":\"%p\"}" : "{\"_ptr\":\"null\"}"; } #endif /* When ptr == NULL, the format argument is unused. */ DUK_SNPRINTF(buf, sizeof(buf) - 1, fmt, ptr); /* must not truncate */ DUK__EMIT_CSTR(js_ctx, buf); } #endif /* DUK_USE_JX || DUK_USE_JC */ #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) #if defined(DUK_USE_JX) || defined(DUK_USE_JC) DUK_LOCAL void duk__json_enc_bufobj(duk_json_enc_ctx *js_ctx, duk_hbufobj *h_bufobj) { DUK_HBUFOBJ_ASSERT_VALID(h_bufobj); if (h_bufobj->buf == NULL || !DUK_HBUFOBJ_VALID_SLICE(h_bufobj)) { DUK__EMIT_STRIDX(js_ctx, DUK_STRIDX_LC_NULL); } else { /* Handle both full and partial slice (as long as covered). */ duk__json_enc_buffer_data(js_ctx, (duk_uint8_t *) DUK_HBUFOBJ_GET_SLICE_BASE(js_ctx->thr->heap, h_bufobj), (duk_size_t) h_bufobj->length); } } #endif /* DUK_USE_JX || DUK_USE_JC */ #endif /* DUK_USE_BUFFEROBJECT_SUPPORT */ /* Indent helper. Calling code relies on js_ctx->recursion_depth also being * directly related to indent depth. */ #if defined(DUK_USE_PREFER_SIZE) DUK_LOCAL void duk__json_enc_newline_indent(duk_json_enc_ctx *js_ctx, duk_uint_t depth) { DUK_ASSERT(js_ctx->h_gap != NULL); DUK_ASSERT(DUK_HSTRING_GET_BYTELEN(js_ctx->h_gap) > 0); /* caller guarantees */ DUK__EMIT_1(js_ctx, 0x0a); while (depth-- > 0) { DUK__EMIT_HSTR(js_ctx, js_ctx->h_gap); } } #else /* DUK_USE_PREFER_SIZE */ DUK_LOCAL void duk__json_enc_newline_indent(duk_json_enc_ctx *js_ctx, duk_uint_t depth) { const duk_uint8_t *gap_data; duk_size_t gap_len; duk_size_t avail_bytes; /* bytes of indent available for copying */ duk_size_t need_bytes; /* bytes of indent still needed */ duk_uint8_t *p_start; duk_uint8_t *p; DUK_ASSERT(js_ctx->h_gap != NULL); DUK_ASSERT(DUK_HSTRING_GET_BYTELEN(js_ctx->h_gap) > 0); /* caller guarantees */ DUK__EMIT_1(js_ctx, 0x0a); if (DUK_UNLIKELY(depth == 0)) { return; } /* To handle deeper indents efficiently, make use of copies we've * already emitted. In effect we can emit a sequence of 1, 2, 4, * 8, etc copies, and then finish the last run. Byte counters * avoid multiply with gap_len on every loop. */ gap_data = (const duk_uint8_t *) DUK_HSTRING_GET_DATA(js_ctx->h_gap); gap_len = (duk_size_t) DUK_HSTRING_GET_BYTELEN(js_ctx->h_gap); DUK_ASSERT(gap_len > 0); need_bytes = gap_len * depth; p = DUK_BW_ENSURE_GETPTR(js_ctx->thr, &js_ctx->bw, need_bytes); p_start = p; duk_memcpy((void *) p, (const void *) gap_data, (size_t) gap_len); p += gap_len; avail_bytes = gap_len; DUK_ASSERT(need_bytes >= gap_len); need_bytes -= gap_len; while (need_bytes >= avail_bytes) { duk_memcpy((void *) p, (const void *) p_start, (size_t) avail_bytes); p += avail_bytes; need_bytes -= avail_bytes; avail_bytes <<= 1; } DUK_ASSERT(need_bytes < avail_bytes); /* need_bytes may be zero */ duk_memcpy((void *) p, (const void *) p_start, (size_t) need_bytes); p += need_bytes; /*avail_bytes += need_bytes*/ DUK_BW_SET_PTR(js_ctx->thr, &js_ctx->bw, p); } #endif /* DUK_USE_PREFER_SIZE */ /* Shared entry handling for object/array serialization. */ DUK_LOCAL void duk__json_enc_objarr_entry(duk_json_enc_ctx *js_ctx, duk_idx_t *entry_top) { duk_hthread *thr = js_ctx->thr; duk_hobject *h_target; duk_uint_fast32_t i, n; *entry_top = duk_get_top(thr); duk_native_stack_check(thr); duk_require_stack(thr, DUK_JSON_ENC_REQSTACK); /* Loop check using a hybrid approach: a fixed-size visited[] array * with overflow in a loop check object. */ h_target = duk_known_hobject(thr, -1); /* object or array */ n = js_ctx->recursion_depth; if (DUK_UNLIKELY(n > DUK_JSON_ENC_LOOPARRAY)) { n = DUK_JSON_ENC_LOOPARRAY; } for (i = 0; i < n; i++) { if (DUK_UNLIKELY(js_ctx->visiting[i] == h_target)) { DUK_DD(DUK_DDPRINT("slow path loop detect")); DUK_ERROR_TYPE(thr, DUK_STR_CYCLIC_INPUT); DUK_WO_NORETURN(return;); } } if (js_ctx->recursion_depth < DUK_JSON_ENC_LOOPARRAY) { js_ctx->visiting[js_ctx->recursion_depth] = h_target; } else { duk_push_sprintf(thr, DUK_STR_FMT_PTR, (void *) h_target); duk_dup_top(thr); /* -> [ ... voidp voidp ] */ if (duk_has_prop(thr, js_ctx->idx_loop)) { DUK_ERROR_TYPE(thr, DUK_STR_CYCLIC_INPUT); DUK_WO_NORETURN(return;); } duk_push_true(thr); /* -> [ ... voidp true ] */ duk_put_prop(thr, js_ctx->idx_loop); /* -> [ ... ] */ } /* C recursion check. */ DUK_ASSERT_DISABLE(js_ctx->recursion_depth >= 0); /* unsigned */ DUK_ASSERT(js_ctx->recursion_depth <= js_ctx->recursion_limit); if (js_ctx->recursion_depth >= js_ctx->recursion_limit) { DUK_ERROR_RANGE(thr, DUK_STR_ENC_RECLIMIT); DUK_WO_NORETURN(return;); } js_ctx->recursion_depth++; DUK_DDD(DUK_DDDPRINT("shared entry finished: top=%ld, loop=%!T", (long) duk_get_top(thr), (duk_tval *) duk_get_tval(thr, js_ctx->idx_loop))); } /* Shared exit handling for object/array serialization. */ DUK_LOCAL void duk__json_enc_objarr_exit(duk_json_enc_ctx *js_ctx, duk_idx_t *entry_top) { duk_hthread *thr = js_ctx->thr; duk_hobject *h_target; /* C recursion check. */ DUK_ASSERT(js_ctx->recursion_depth > 0); DUK_ASSERT(js_ctx->recursion_depth <= js_ctx->recursion_limit); js_ctx->recursion_depth--; /* Loop check. */ h_target = duk_known_hobject(thr, *entry_top - 1); /* original target at entry_top - 1 */ if (js_ctx->recursion_depth < DUK_JSON_ENC_LOOPARRAY) { /* Previous entry was inside visited[], nothing to do. */ } else { duk_push_sprintf(thr, DUK_STR_FMT_PTR, (void *) h_target); duk_del_prop(thr, js_ctx->idx_loop); /* -> [ ... ] */ } /* Restore stack top after unbalanced code paths. */ duk_set_top(thr, *entry_top); DUK_DDD(DUK_DDDPRINT("shared entry finished: top=%ld, loop=%!T", (long) duk_get_top(thr), (duk_tval *) duk_get_tval(thr, js_ctx->idx_loop))); } /* The JO(value) operation: encode object. * * Stack policy: [ object ] -> [ object ]. */ DUK_LOCAL void duk__json_enc_object(duk_json_enc_ctx *js_ctx) { duk_hthread *thr = js_ctx->thr; duk_hstring *h_key; duk_idx_t entry_top; duk_idx_t idx_obj; duk_idx_t idx_keys; duk_bool_t emitted; duk_uarridx_t arr_len, i; duk_size_t prev_size; DUK_DDD(DUK_DDDPRINT("duk__json_enc_object: obj=%!T", (duk_tval *) duk_get_tval(thr, -1))); duk__json_enc_objarr_entry(js_ctx, &entry_top); idx_obj = entry_top - 1; if (js_ctx->idx_proplist >= 0) { idx_keys = js_ctx->idx_proplist; } else { /* XXX: would be nice to enumerate an object at specified index */ duk_dup(thr, idx_obj); (void) duk_hobject_get_enumerated_keys( thr, DUK_ENUM_OWN_PROPERTIES_ONLY /*flags*/); /* [ ... target ] -> [ ... target keys ] */ idx_keys = duk_require_normalize_index(thr, -1); /* leave stack unbalanced on purpose */ } DUK_DDD(DUK_DDDPRINT("idx_keys=%ld, h_keys=%!T", (long) idx_keys, (duk_tval *) duk_get_tval(thr, idx_keys))); /* Steps 8-10 have been merged to avoid a "partial" variable. */ DUK__EMIT_1(js_ctx, DUK_ASC_LCURLY); /* XXX: keys is an internal object with all keys to be processed * in its (gapless) array part. Because nobody can touch the keys * object, we could iterate its array part directly (keeping in mind * that it can be reallocated). */ arr_len = (duk_uarridx_t) duk_get_length(thr, idx_keys); emitted = 0; for (i = 0; i < arr_len; i++) { duk_get_prop_index(thr, idx_keys, i); /* -> [ ... key ] */ DUK_DDD(DUK_DDDPRINT("object property loop: holder=%!T, key=%!T", (duk_tval *) duk_get_tval(thr, idx_obj), (duk_tval *) duk_get_tval(thr, -1))); h_key = duk_known_hstring(thr, -1); DUK_ASSERT(h_key != NULL); DUK_ASSERT(!DUK_HSTRING_HAS_SYMBOL(h_key)); /* proplist filtering; enum options */ prev_size = DUK_BW_GET_SIZE(js_ctx->thr, &js_ctx->bw); if (DUK_UNLIKELY(js_ctx->h_gap != NULL)) { duk__json_enc_newline_indent(js_ctx, js_ctx->recursion_depth); duk__json_enc_key_autoquote(js_ctx, h_key); DUK__EMIT_2(js_ctx, DUK_ASC_COLON, DUK_ASC_SPACE); } else { duk__json_enc_key_autoquote(js_ctx, h_key); DUK__EMIT_1(js_ctx, DUK_ASC_COLON); } /* [ ... key ] */ if (DUK_UNLIKELY(duk__json_enc_value(js_ctx, idx_obj) == 0)) { /* Value would yield 'undefined', so skip key altogether. * Side effects have already happened. */ DUK_BW_SET_SIZE(js_ctx->thr, &js_ctx->bw, prev_size); } else { DUK__EMIT_1(js_ctx, DUK_ASC_COMMA); emitted = 1; } /* [ ... ] */ } if (emitted) { DUK_ASSERT(*((duk_uint8_t *) DUK_BW_GET_PTR(js_ctx->thr, &js_ctx->bw) - 1) == DUK_ASC_COMMA); DUK__UNEMIT_1(js_ctx); /* eat trailing comma */ if (DUK_UNLIKELY(js_ctx->h_gap != NULL)) { DUK_ASSERT(js_ctx->recursion_depth >= 1); duk__json_enc_newline_indent(js_ctx, js_ctx->recursion_depth - 1U); } } DUK__EMIT_1(js_ctx, DUK_ASC_RCURLY); duk__json_enc_objarr_exit(js_ctx, &entry_top); DUK_ASSERT_TOP(thr, entry_top); } /* The JA(value) operation: encode array. * * Stack policy: [ array ] -> [ array ]. */ DUK_LOCAL void duk__json_enc_array(duk_json_enc_ctx *js_ctx) { duk_hthread *thr = js_ctx->thr; duk_idx_t entry_top; duk_idx_t idx_arr; duk_bool_t emitted; duk_uarridx_t i, arr_len; DUK_DDD(DUK_DDDPRINT("duk__json_enc_array: array=%!T", (duk_tval *) duk_get_tval(thr, -1))); duk__json_enc_objarr_entry(js_ctx, &entry_top); idx_arr = entry_top - 1; /* Steps 8-10 have been merged to avoid a "partial" variable. */ DUK__EMIT_1(js_ctx, DUK_ASC_LBRACKET); arr_len = (duk_uarridx_t) duk_get_length(thr, idx_arr); emitted = 0; for (i = 0; i < arr_len; i++) { DUK_DDD(DUK_DDDPRINT("array entry loop: array=%!T, index=%ld, arr_len=%ld", (duk_tval *) duk_get_tval(thr, idx_arr), (long) i, (long) arr_len)); if (DUK_UNLIKELY(js_ctx->h_gap != NULL)) { DUK_ASSERT(js_ctx->recursion_depth >= 1); duk__json_enc_newline_indent(js_ctx, js_ctx->recursion_depth); } (void) duk_push_uint_to_hstring(thr, (duk_uint_t) i); /* -> [ ... key ] */ /* [ ... key ] */ if (DUK_UNLIKELY(duk__json_enc_value(js_ctx, idx_arr) == 0)) { /* Value would normally be omitted, replace with 'null'. */ DUK__EMIT_STRIDX(js_ctx, DUK_STRIDX_LC_NULL); } else { ; } /* [ ... ] */ DUK__EMIT_1(js_ctx, DUK_ASC_COMMA); emitted = 1; } if (emitted) { DUK_ASSERT(*((duk_uint8_t *) DUK_BW_GET_PTR(js_ctx->thr, &js_ctx->bw) - 1) == DUK_ASC_COMMA); DUK__UNEMIT_1(js_ctx); /* eat trailing comma */ if (DUK_UNLIKELY(js_ctx->h_gap != NULL)) { DUK_ASSERT(js_ctx->recursion_depth >= 1); duk__json_enc_newline_indent(js_ctx, js_ctx->recursion_depth - 1U); } } DUK__EMIT_1(js_ctx, DUK_ASC_RBRACKET); duk__json_enc_objarr_exit(js_ctx, &entry_top); DUK_ASSERT_TOP(thr, entry_top); } /* The Str(key, holder) operation. * * Stack policy: [ ... key ] -> [ ... ] */ DUK_LOCAL duk_bool_t duk__json_enc_value(duk_json_enc_ctx *js_ctx, duk_idx_t idx_holder) { duk_hthread *thr = js_ctx->thr; duk_tval *tv; duk_tval *tv_holder; duk_tval *tv_key; duk_small_int_t c; DUK_DDD(DUK_DDDPRINT("duk__json_enc_value: idx_holder=%ld, holder=%!T, key=%!T", (long) idx_holder, (duk_tval *) duk_get_tval(thr, idx_holder), (duk_tval *) duk_get_tval(thr, -1))); tv_holder = DUK_GET_TVAL_POSIDX(thr, idx_holder); DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv_holder)); tv_key = DUK_GET_TVAL_NEGIDX(thr, -1); DUK_ASSERT(DUK_TVAL_IS_STRING(tv_key)); DUK_ASSERT(!DUK_HSTRING_HAS_SYMBOL(DUK_TVAL_GET_STRING(tv_key))); /* Caller responsible. */ (void) duk_hobject_getprop(thr, tv_holder, tv_key); /* -> [ ... key val ] */ DUK_DDD(DUK_DDDPRINT("value=%!T", (duk_tval *) duk_get_tval(thr, -1))); /* Standard JSON checks for .toJSON() only for actual objects; for * example, setting Number.prototype.toJSON and then serializing a * number won't invoke the .toJSON() method. However, lightfuncs and * plain buffers mimic objects so we check for their .toJSON() method. */ if (duk_check_type_mask(thr, -1, DUK_TYPE_MASK_OBJECT | DUK_TYPE_MASK_LIGHTFUNC | DUK_TYPE_MASK_BUFFER)) { duk_get_prop_stridx_short(thr, -1, DUK_STRIDX_TO_JSON); if (duk_is_callable(thr, -1)) { /* toJSON() can also be a lightfunc */ DUK_DDD(DUK_DDDPRINT("value is object, has callable toJSON() -> call it")); /* XXX: duk_dup_unvalidated(thr, -2) etc. */ duk_dup_m2(thr); /* -> [ ... key val toJSON val ] */ duk_dup_m4(thr); /* -> [ ... key val toJSON val key ] */ duk_call_method(thr, 1); /* -> [ ... key val val' ] */ duk_remove_m2(thr); /* -> [ ... key val' ] */ } else { duk_pop(thr); /* -> [ ... key val ] */ } } /* [ ... key val ] */ DUK_DDD(DUK_DDDPRINT("value=%!T", (duk_tval *) duk_get_tval(thr, -1))); if (js_ctx->h_replacer) { /* XXX: Here a "slice copy" would be useful. */ DUK_DDD(DUK_DDDPRINT("replacer is set, call replacer")); duk_push_hobject(thr, js_ctx->h_replacer); /* -> [ ... key val replacer ] */ duk_dup(thr, idx_holder); /* -> [ ... key val replacer holder ] */ duk_dup_m4(thr); /* -> [ ... key val replacer holder key ] */ duk_dup_m4(thr); /* -> [ ... key val replacer holder key val ] */ duk_call_method(thr, 2); /* -> [ ... key val val' ] */ duk_remove_m2(thr); /* -> [ ... key val' ] */ } /* [ ... key val ] */ DUK_DDD(DUK_DDDPRINT("value=%!T", (duk_tval *) duk_get_tval(thr, -1))); tv = DUK_GET_TVAL_NEGIDX(thr, -1); if (DUK_TVAL_IS_OBJECT(tv)) { duk_hobject *h; h = DUK_TVAL_GET_OBJECT(tv); DUK_ASSERT(h != NULL); #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) #if defined(DUK_USE_JX) || defined(DUK_USE_JC) if (DUK_HOBJECT_IS_BUFOBJ(h) && js_ctx->flags & (DUK_JSON_FLAG_EXT_CUSTOM | DUK_JSON_FLAG_EXT_COMPATIBLE)) { /* With JX/JC a bufferobject gets serialized specially. */ duk_hbufobj *h_bufobj; h_bufobj = (duk_hbufobj *) h; DUK_HBUFOBJ_ASSERT_VALID(h_bufobj); duk__json_enc_bufobj(js_ctx, h_bufobj); goto pop2_emitted; } /* Otherwise bufferobjects get serialized as normal objects. */ #endif /* JX || JC */ #endif /* DUK_USE_BUFFEROBJECT_SUPPORT */ c = (duk_small_int_t) DUK_HOBJECT_GET_CLASS_NUMBER(h); switch (c) { case DUK_HOBJECT_CLASS_NUMBER: { DUK_DDD(DUK_DDDPRINT("value is a Number object -> coerce with ToNumber()")); duk_to_number_m1(thr); /* The coercion potentially invokes user .valueOf() and .toString() * but can't result in a function value because ToPrimitive() would * reject such a result: test-dev-json-stringify-coercion-1.js. */ DUK_ASSERT(!duk_is_callable(thr, -1)); break; } case DUK_HOBJECT_CLASS_STRING: { DUK_DDD(DUK_DDDPRINT("value is a String object -> coerce with ToString()")); duk_to_string(thr, -1); /* Same coercion behavior as for Number. */ DUK_ASSERT(!duk_is_callable(thr, -1)); break; } #if defined(DUK_USE_JX) || defined(DUK_USE_JC) case DUK_HOBJECT_CLASS_POINTER: #endif case DUK_HOBJECT_CLASS_BOOLEAN: { DUK_DDD(DUK_DDDPRINT("value is a Boolean/Buffer/Pointer object -> get internal value")); duk_xget_owndataprop_stridx_short(thr, -1, DUK_STRIDX_INT_VALUE); duk_remove_m2(thr); break; } default: { /* Normal object which doesn't get automatically coerced to a * primitive value. Functions are checked for specially. The * primitive value coercions for Number, String, Pointer, and * Boolean can't result in functions so suffices to check here. * Symbol objects are handled like plain objects (their primitive * value is NOT looked up like for e.g. String objects). */ DUK_ASSERT(h != NULL); if (DUK_HOBJECT_IS_CALLABLE(h)) { #if defined(DUK_USE_JX) || defined(DUK_USE_JC) if (js_ctx->flags & (DUK_JSON_FLAG_EXT_CUSTOM | DUK_JSON_FLAG_EXT_COMPATIBLE)) { /* We only get here when doing non-standard JSON encoding */ DUK_DDD(DUK_DDDPRINT("-> function allowed, serialize to custom format")); DUK_ASSERT(js_ctx->flag_ext_custom || js_ctx->flag_ext_compatible); DUK__EMIT_STRIDX(js_ctx, js_ctx->stridx_custom_function); goto pop2_emitted; } else { DUK_DDD(DUK_DDDPRINT("-> will result in undefined (function)")); goto pop2_undef; } #else /* DUK_USE_JX || DUK_USE_JC */ DUK_DDD(DUK_DDDPRINT("-> will result in undefined (function)")); goto pop2_undef; #endif /* DUK_USE_JX || DUK_USE_JC */ } } } /* end switch */ } /* [ ... key val ] */ DUK_DDD(DUK_DDDPRINT("value=%!T", (duk_tval *) duk_get_tval(thr, -1))); if (duk_check_type_mask(thr, -1, js_ctx->mask_for_undefined)) { /* will result in undefined */ DUK_DDD(DUK_DDDPRINT("-> will result in undefined (type mask check)")); goto pop2_undef; } tv = DUK_GET_TVAL_NEGIDX(thr, -1); switch (DUK_TVAL_GET_TAG(tv)) { #if defined(DUK_USE_JX) || defined(DUK_USE_JC) /* When JX/JC not in use, the type mask above will avoid this case if needed. */ case DUK_TAG_UNDEFINED: { DUK__EMIT_STRIDX(js_ctx, js_ctx->stridx_custom_undefined); break; } #endif case DUK_TAG_NULL: { DUK__EMIT_STRIDX(js_ctx, DUK_STRIDX_LC_NULL); break; } case DUK_TAG_BOOLEAN: { DUK__EMIT_STRIDX(js_ctx, DUK_TVAL_GET_BOOLEAN(tv) ? DUK_STRIDX_TRUE : DUK_STRIDX_FALSE); break; } #if defined(DUK_USE_JX) || defined(DUK_USE_JC) /* When JX/JC not in use, the type mask above will avoid this case if needed. */ case DUK_TAG_POINTER: { duk__json_enc_pointer(js_ctx, DUK_TVAL_GET_POINTER(tv)); break; } #endif /* DUK_USE_JX || DUK_USE_JC */ case DUK_TAG_STRING: { duk_hstring *h = DUK_TVAL_GET_STRING(tv); DUK_ASSERT(h != NULL); if (DUK_UNLIKELY(DUK_HSTRING_HAS_SYMBOL(h))) { goto pop2_undef; } duk__json_enc_quote_string(js_ctx, h); break; } case DUK_TAG_OBJECT: { duk_hobject *h = DUK_TVAL_GET_OBJECT(tv); DUK_ASSERT(h != NULL); /* Function values are handled completely above (including * coercion results): */ DUK_ASSERT(!DUK_HOBJECT_IS_CALLABLE(h)); if (duk_js_isarray_hobject(h)) { duk__json_enc_array(js_ctx); } else { duk__json_enc_object(js_ctx); } break; } /* Because plain buffers mimics Uint8Array, they have enumerable * index properties [0,byteLength[. Because JSON only serializes * enumerable own properties, no properties can be serialized for * plain buffers (all virtual properties are non-enumerable). However, * there may be a .toJSON() method which was already handled above. */ case DUK_TAG_BUFFER: { #if defined(DUK_USE_JX) || defined(DUK_USE_JC) if (js_ctx->flag_ext_custom_or_compatible) { duk__json_enc_buffer_jx_jc(js_ctx, DUK_TVAL_GET_BUFFER(tv)); break; } #endif /* Could implement a fastpath, but the fast path would need * to handle realloc side effects correctly. */ duk_to_object(thr, -1); duk__json_enc_object(js_ctx); break; } case DUK_TAG_LIGHTFUNC: { #if defined(DUK_USE_JX) || defined(DUK_USE_JC) /* We only get here when doing non-standard JSON encoding */ DUK_ASSERT(js_ctx->flag_ext_custom || js_ctx->flag_ext_compatible); DUK__EMIT_STRIDX(js_ctx, js_ctx->stridx_custom_function); #else /* Standard JSON omits functions */ DUK_UNREACHABLE(); #endif break; } #if defined(DUK_USE_FASTINT) case DUK_TAG_FASTINT: /* Number serialization has a significant impact relative to * other fast path code, so careful fast path for fastints. */ duk__json_enc_fastint_tval(js_ctx, tv); break; #endif default: { /* number */ DUK_ASSERT(!DUK_TVAL_IS_UNUSED(tv)); DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv)); /* XXX: A fast path for usual integers would be useful when * fastint support is not enabled. */ duk__json_enc_double(js_ctx); break; } } #if defined(DUK_USE_JX) || defined(DUK_USE_JC) pop2_emitted: #endif duk_pop_2(thr); /* [ ... key val ] -> [ ... ] */ return 1; /* emitted */ pop2_undef: duk_pop_2(thr); /* [ ... key val ] -> [ ... ] */ return 0; /* not emitted */ } /* E5 Section 15.12.3, main algorithm, step 4.b.ii steps 1-4. */ DUK_LOCAL duk_bool_t duk__json_enc_allow_into_proplist(duk_tval *tv) { duk_small_int_t c; /* XXX: some kind of external internal type checker? * - type mask; symbol flag; class mask */ DUK_ASSERT(tv != NULL); if (DUK_TVAL_IS_STRING(tv)) { duk_hstring *h; h = DUK_TVAL_GET_STRING(tv); DUK_ASSERT(h != NULL); if (DUK_UNLIKELY(DUK_HSTRING_HAS_SYMBOL(h))) { return 0; } return 1; } else if (DUK_TVAL_IS_NUMBER(tv)) { return 1; } else if (DUK_TVAL_IS_OBJECT(tv)) { duk_hobject *h; h = DUK_TVAL_GET_OBJECT(tv); DUK_ASSERT(h != NULL); c = (duk_small_int_t) DUK_HOBJECT_GET_CLASS_NUMBER(h); if (c == DUK_HOBJECT_CLASS_STRING || c == DUK_HOBJECT_CLASS_NUMBER) { return 1; } } return 0; } /* * JSON.stringify() fast path * * Otherwise supports full JSON, JX, and JC features, but bails out on any * possible side effect which might change the value being serialized. The * fast path can take advantage of the fact that the value being serialized * is unchanged so that we can walk directly through property tables etc. */ #if defined(DUK_USE_JSON_STRINGIFY_FASTPATH) DUK_LOCAL duk_bool_t duk__json_stringify_fast_value(duk_json_enc_ctx *js_ctx, duk_tval *tv) { duk_uint_fast32_t i, n; DUK_DDD(DUK_DDDPRINT("stringify fast: %!T", tv)); DUK_ASSERT(js_ctx != NULL); DUK_ASSERT(js_ctx->thr != NULL); #if 0 /* disabled for now */ restart_match: #endif DUK_ASSERT(tv != NULL); switch (DUK_TVAL_GET_TAG(tv)) { case DUK_TAG_UNDEFINED: { #if defined(DUK_USE_JX) || defined(DUK_USE_JC) if (js_ctx->flag_ext_custom || js_ctx->flag_ext_compatible) { DUK__EMIT_STRIDX(js_ctx, js_ctx->stridx_custom_undefined); break; } else { goto emit_undefined; } #else goto emit_undefined; #endif } case DUK_TAG_NULL: { DUK__EMIT_STRIDX(js_ctx, DUK_STRIDX_LC_NULL); break; } case DUK_TAG_BOOLEAN: { DUK__EMIT_STRIDX(js_ctx, DUK_TVAL_GET_BOOLEAN(tv) ? DUK_STRIDX_TRUE : DUK_STRIDX_FALSE); break; } case DUK_TAG_STRING: { duk_hstring *h; h = DUK_TVAL_GET_STRING(tv); DUK_ASSERT(h != NULL); if (DUK_UNLIKELY(DUK_HSTRING_HAS_SYMBOL(h))) { goto emit_undefined; } duk__json_enc_quote_string(js_ctx, h); break; } case DUK_TAG_OBJECT: { duk_hobject *obj; duk_tval *tv_val; duk_bool_t emitted = 0; duk_uint32_t c_bit, c_all, c_array, c_unbox, c_undef, c_func, c_bufobj, c_object, c_abort; /* For objects JSON.stringify() only looks for own, enumerable * properties which is nice for the fast path here. * * For arrays JSON.stringify() uses [[Get]] so it will actually * inherit properties during serialization! This fast path * supports gappy arrays as long as there's no actual inherited * property (which might be a getter etc). * * Since recursion only happens for objects, we can have both * recursion and loop checks here. We use a simple, depth-limited * loop check in the fast path because the object-based tracking * is very slow (when tested, it accounted for 50% of fast path * execution time for input data with a lot of small objects!). */ /* XXX: for real world code, could just ignore array inheritance * and only look at array own properties. */ /* We rely on a few object flag / class number relationships here, * assert for them. */ obj = DUK_TVAL_GET_OBJECT(tv); DUK_ASSERT(obj != NULL); DUK_HOBJECT_ASSERT_VALID(obj); /* Once recursion depth is increased, exit path must decrease * it (though it's OK to abort the fast path). */ DUK_ASSERT_DISABLE(js_ctx->recursion_depth >= 0); /* unsigned */ DUK_ASSERT(js_ctx->recursion_depth <= js_ctx->recursion_limit); if (js_ctx->recursion_depth >= js_ctx->recursion_limit) { DUK_DD(DUK_DDPRINT("fast path recursion limit")); DUK_ERROR_RANGE(js_ctx->thr, DUK_STR_DEC_RECLIMIT); DUK_WO_NORETURN(return 0;); } for (i = 0, n = (duk_uint_fast32_t) js_ctx->recursion_depth; i < n; i++) { if (DUK_UNLIKELY(js_ctx->visiting[i] == obj)) { DUK_DD(DUK_DDPRINT("fast path loop detect")); DUK_ERROR_TYPE(js_ctx->thr, DUK_STR_CYCLIC_INPUT); DUK_WO_NORETURN(return 0;); } } /* Guaranteed by recursion_limit setup so we don't have to * check twice. */ DUK_ASSERT(js_ctx->recursion_depth < DUK_JSON_ENC_LOOPARRAY); js_ctx->visiting[js_ctx->recursion_depth] = obj; js_ctx->recursion_depth++; /* If object has a .toJSON() property, we can't be certain * that it wouldn't mutate any value arbitrarily, so bail * out of the fast path. * * If an object is a Proxy we also can't avoid side effects * so abandon. */ /* XXX: non-callable .toJSON() doesn't need to cause an abort * but does at the moment, probably not worth fixing. */ if (duk_hobject_hasprop_raw(js_ctx->thr, obj, DUK_HTHREAD_STRING_TO_JSON(js_ctx->thr)) || DUK_HOBJECT_IS_PROXY(obj)) { DUK_DD(DUK_DDPRINT("object has a .toJSON property or object is a Proxy, abort fast path")); goto abort_fastpath; } /* We could use a switch-case for the class number but it turns out * a small if-else ladder on class masks is better. The if-ladder * should be in order of relevancy. */ /* XXX: move masks to js_ctx? they don't change during one * fast path invocation. */ DUK_ASSERT(DUK_HOBJECT_CLASS_MAX <= 31); #if defined(DUK_USE_JX) || defined(DUK_USE_JC) if (js_ctx->flag_ext_custom_or_compatible) { c_all = DUK_HOBJECT_CMASK_ALL; c_array = DUK_HOBJECT_CMASK_ARRAY; c_unbox = DUK_HOBJECT_CMASK_NUMBER | DUK_HOBJECT_CMASK_STRING | DUK_HOBJECT_CMASK_BOOLEAN | DUK_HOBJECT_CMASK_POINTER; /* Symbols are not unboxed. */ c_func = DUK_HOBJECT_CMASK_FUNCTION; c_bufobj = DUK_HOBJECT_CMASK_ALL_BUFOBJS; c_undef = 0; c_abort = 0; c_object = c_all & ~(c_array | c_unbox | c_func | c_bufobj | c_undef | c_abort); } else #endif { c_all = DUK_HOBJECT_CMASK_ALL; c_array = DUK_HOBJECT_CMASK_ARRAY; c_unbox = DUK_HOBJECT_CMASK_NUMBER | DUK_HOBJECT_CMASK_STRING | DUK_HOBJECT_CMASK_BOOLEAN; /* Symbols are not unboxed. */ c_func = 0; c_bufobj = 0; c_undef = DUK_HOBJECT_CMASK_FUNCTION | DUK_HOBJECT_CMASK_POINTER; /* As the fast path doesn't currently properly support * duk_hbufobj virtual properties, abort fast path if * we encounter them in plain JSON mode. */ c_abort = DUK_HOBJECT_CMASK_ALL_BUFOBJS; c_object = c_all & ~(c_array | c_unbox | c_func | c_bufobj | c_undef | c_abort); } c_bit = (duk_uint32_t) DUK_HOBJECT_GET_CLASS_MASK(obj); if (c_bit & c_object) { /* All other object types. */ DUK__EMIT_1(js_ctx, DUK_ASC_LCURLY); /* A non-Array object should not have an array part in practice. * But since it is supported internally (and perhaps used at some * point), check and abandon if that's the case. */ if (DUK_HOBJECT_HAS_ARRAY_PART(obj)) { DUK_DD(DUK_DDPRINT("non-Array object has array part, abort fast path")); goto abort_fastpath; } for (i = 0; i < (duk_uint_fast32_t) DUK_HOBJECT_GET_ENEXT(obj); i++) { duk_hstring *k; duk_size_t prev_size; k = DUK_HOBJECT_E_GET_KEY(js_ctx->thr->heap, obj, i); if (!k) { continue; } if (DUK_HSTRING_HAS_ARRIDX(k)) { /* If an object has array index keys we would need * to sort them into the ES2015 enumeration order to * be consistent with the slow path. Abort the fast * path and handle in the slow path for now. */ DUK_DD(DUK_DDPRINT("property key is an array index, abort fast path")); goto abort_fastpath; } if (!DUK_HOBJECT_E_SLOT_IS_ENUMERABLE(js_ctx->thr->heap, obj, i)) { continue; } if (DUK_HOBJECT_E_SLOT_IS_ACCESSOR(js_ctx->thr->heap, obj, i)) { /* Getter might have arbitrary side effects, * so bail out. */ DUK_DD(DUK_DDPRINT("property is an accessor, abort fast path")); goto abort_fastpath; } if (DUK_UNLIKELY(DUK_HSTRING_HAS_SYMBOL(k))) { continue; } tv_val = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(js_ctx->thr->heap, obj, i); prev_size = DUK_BW_GET_SIZE(js_ctx->thr, &js_ctx->bw); if (DUK_UNLIKELY(js_ctx->h_gap != NULL)) { duk__json_enc_newline_indent(js_ctx, js_ctx->recursion_depth); duk__json_enc_key_autoquote(js_ctx, k); DUK__EMIT_2(js_ctx, DUK_ASC_COLON, DUK_ASC_SPACE); } else { duk__json_enc_key_autoquote(js_ctx, k); DUK__EMIT_1(js_ctx, DUK_ASC_COLON); } if (duk__json_stringify_fast_value(js_ctx, tv_val) == 0) { DUK_DD(DUK_DDPRINT("prop value not supported, rewind key and colon")); DUK_BW_SET_SIZE(js_ctx->thr, &js_ctx->bw, prev_size); } else { DUK__EMIT_1(js_ctx, DUK_ASC_COMMA); emitted = 1; } } /* If any non-Array value had enumerable virtual own * properties, they should be serialized here (actually, * before the explicit properties). Standard types don't. */ if (emitted) { DUK_ASSERT(*((duk_uint8_t *) DUK_BW_GET_PTR(js_ctx->thr, &js_ctx->bw) - 1) == DUK_ASC_COMMA); DUK__UNEMIT_1(js_ctx); /* eat trailing comma */ if (DUK_UNLIKELY(js_ctx->h_gap != NULL)) { DUK_ASSERT(js_ctx->recursion_depth >= 1); duk__json_enc_newline_indent(js_ctx, js_ctx->recursion_depth - 1U); } } DUK__EMIT_1(js_ctx, DUK_ASC_RCURLY); } else if (c_bit & c_array) { duk_uint_fast32_t arr_len; duk_uint_fast32_t asize; DUK__EMIT_1(js_ctx, DUK_ASC_LBRACKET); /* Assume arrays are dense in the fast path. */ if (!DUK_HOBJECT_HAS_ARRAY_PART(obj)) { DUK_DD(DUK_DDPRINT("Array object is sparse, abort fast path")); goto abort_fastpath; } arr_len = (duk_uint_fast32_t) ((duk_harray *) obj)->length; asize = (duk_uint_fast32_t) DUK_HOBJECT_GET_ASIZE(obj); /* Array part may be larger than 'length'; if so, iterate * only up to array 'length'. Array part may also be smaller * than 'length' in some cases. */ for (i = 0; i < arr_len; i++) { duk_tval *tv_arrval; duk_hstring *h_tmp; duk_bool_t has_inherited; if (DUK_UNLIKELY(js_ctx->h_gap != NULL)) { duk__json_enc_newline_indent(js_ctx, js_ctx->recursion_depth); } if (DUK_LIKELY(i < asize)) { tv_arrval = DUK_HOBJECT_A_GET_VALUE_PTR(js_ctx->thr->heap, obj, i); if (DUK_LIKELY(!DUK_TVAL_IS_UNUSED(tv_arrval))) { /* Expected case: element is present. */ if (duk__json_stringify_fast_value(js_ctx, tv_arrval) == 0) { DUK__EMIT_STRIDX(js_ctx, DUK_STRIDX_LC_NULL); } goto elem_done; } } /* Gap in array; check for inherited property, * bail out if one exists. This should be enough * to support gappy arrays for all practical code. */ h_tmp = duk_push_uint_to_hstring(js_ctx->thr, (duk_uint_t) i); has_inherited = duk_hobject_hasprop_raw(js_ctx->thr, obj, h_tmp); duk_pop(js_ctx->thr); if (has_inherited) { DUK_D(DUK_DPRINT("gap in array, conflicting inherited property, abort fast path")); goto abort_fastpath; } /* Ordinary gap, undefined encodes to 'null' in * standard JSON, but JX/JC use their form for * undefined to better preserve the typing. */ DUK_D(DUK_DPRINT("gap in array, no conflicting inherited property, remain on fast path")); #if defined(DUK_USE_JX) DUK__EMIT_STRIDX(js_ctx, js_ctx->stridx_custom_undefined); #else DUK__EMIT_STRIDX(js_ctx, DUK_STRIDX_LC_NULL); #endif /* fall through */ elem_done: DUK__EMIT_1(js_ctx, DUK_ASC_COMMA); emitted = 1; } if (emitted) { DUK_ASSERT(*((duk_uint8_t *) DUK_BW_GET_PTR(js_ctx->thr, &js_ctx->bw) - 1) == DUK_ASC_COMMA); DUK__UNEMIT_1(js_ctx); /* eat trailing comma */ if (DUK_UNLIKELY(js_ctx->h_gap != NULL)) { DUK_ASSERT(js_ctx->recursion_depth >= 1); duk__json_enc_newline_indent(js_ctx, js_ctx->recursion_depth - 1U); } } DUK__EMIT_1(js_ctx, DUK_ASC_RBRACKET); } else if (c_bit & c_unbox) { /* Certain boxed types are required to go through * automatic unboxing. Rely on internal value being * sane (to avoid infinite recursion). */ DUK_ASSERT((c_bit & DUK_HOBJECT_CMASK_SYMBOL) == 0); /* Symbols are not unboxed. */ #if 1 /* The code below is incorrect if .toString() or .valueOf() have * have been overridden. The correct approach would be to look up * the method(s) and if they resolve to the built-in function we * can safely bypass it and look up the internal value directly. * Unimplemented for now, abort fast path for boxed values. */ goto abort_fastpath; #else /* disabled */ /* Disabled until fixed, see above. */ duk_tval *tv_internal; DUK_DD(DUK_DDPRINT("auto unboxing in fast path")); tv_internal = duk_hobject_get_internal_value_tval_ptr(js_ctx->thr->heap, obj); DUK_ASSERT(tv_internal != NULL); DUK_ASSERT(DUK_TVAL_IS_STRING(tv_internal) || DUK_TVAL_IS_NUMBER(tv_internal) || DUK_TVAL_IS_BOOLEAN(tv_internal) || DUK_TVAL_IS_POINTER(tv_internal)); tv = tv_internal; DUK_ASSERT(js_ctx->recursion_depth > 0); js_ctx->recursion_depth--; /* required to keep recursion depth correct */ goto restart_match; #endif /* disabled */ #if defined(DUK_USE_JX) || defined(DUK_USE_JC) } else if (c_bit & c_func) { DUK__EMIT_STRIDX(js_ctx, js_ctx->stridx_custom_function); #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) } else if (c_bit & c_bufobj) { duk__json_enc_bufobj(js_ctx, (duk_hbufobj *) obj); #endif #endif } else if (c_bit & c_abort) { DUK_DD(DUK_DDPRINT("abort fast path for unsupported type")); goto abort_fastpath; } else { DUK_ASSERT((c_bit & c_undef) != 0); /* Must decrease recursion depth before returning. */ DUK_ASSERT(js_ctx->recursion_depth > 0); DUK_ASSERT(js_ctx->recursion_depth <= js_ctx->recursion_limit); js_ctx->recursion_depth--; goto emit_undefined; } DUK_ASSERT(js_ctx->recursion_depth > 0); DUK_ASSERT(js_ctx->recursion_depth <= js_ctx->recursion_limit); js_ctx->recursion_depth--; break; } case DUK_TAG_BUFFER: { /* Plain buffers are treated like Uint8Arrays: they have * enumerable indices. Other virtual properties are not * enumerable, and inherited properties are not serialized. * However, there can be a replacer (not relevant here) or * a .toJSON() method (which we need to check for explicitly). */ #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) if (duk_hobject_hasprop_raw(js_ctx->thr, js_ctx->thr->builtins[DUK_BIDX_UINT8ARRAY_PROTOTYPE], DUK_HTHREAD_STRING_TO_JSON(js_ctx->thr))) { DUK_DD(DUK_DDPRINT("value is a plain buffer and there's an inherited .toJSON, abort fast path")); goto abort_fastpath; } #endif #if defined(DUK_USE_JX) || defined(DUK_USE_JC) if (js_ctx->flag_ext_custom_or_compatible) { duk__json_enc_buffer_jx_jc(js_ctx, DUK_TVAL_GET_BUFFER(tv)); break; } #endif /* Plain buffers mimic Uint8Arrays, and have enumerable index * properties. */ duk__json_enc_buffer_json_fastpath(js_ctx, DUK_TVAL_GET_BUFFER(tv)); break; } case DUK_TAG_POINTER: { #if defined(DUK_USE_JX) || defined(DUK_USE_JC) if (js_ctx->flag_ext_custom_or_compatible) { duk__json_enc_pointer(js_ctx, DUK_TVAL_GET_POINTER(tv)); break; } else { goto emit_undefined; } #else goto emit_undefined; #endif } case DUK_TAG_LIGHTFUNC: { /* A lightfunc might also inherit a .toJSON() so just bail out. */ /* XXX: Could just lookup .toJSON() and continue in fast path, * as it would almost never be defined. */ DUK_DD(DUK_DDPRINT("value is a lightfunc, abort fast path")); goto abort_fastpath; } #if defined(DUK_USE_FASTINT) case DUK_TAG_FASTINT: { /* Number serialization has a significant impact relative to * other fast path code, so careful fast path for fastints. */ duk__json_enc_fastint_tval(js_ctx, tv); break; } #endif default: { /* XXX: A fast path for usual integers would be useful when * fastint support is not enabled. */ DUK_ASSERT(!DUK_TVAL_IS_UNUSED(tv)); DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv)); /* XXX: Stack discipline is annoying, could be changed in numconv. */ duk_push_tval(js_ctx->thr, tv); duk__json_enc_double(js_ctx); duk_pop(js_ctx->thr); #if 0 /* Could also rely on native sprintf(), but it will handle * values like NaN, Infinity, -0, exponent notation etc in * a JSON-incompatible way. */ duk_double_t d; char buf[64]; DUK_ASSERT(DUK_TVAL_IS_DOUBLE(tv)); d = DUK_TVAL_GET_DOUBLE(tv); DUK_SPRINTF(buf, "%lg", d); DUK__EMIT_CSTR(js_ctx, buf); #endif } } return 1; /* not undefined */ emit_undefined: return 0; /* value was undefined/unsupported */ abort_fastpath: /* Error message doesn't matter: the error is ignored anyway. */ DUK_DD(DUK_DDPRINT("aborting fast path")); DUK_ERROR_INTERNAL(js_ctx->thr); DUK_WO_NORETURN(return 0;); } DUK_LOCAL duk_ret_t duk__json_stringify_fast(duk_hthread *thr, void *udata) { duk_json_enc_ctx *js_ctx; duk_tval *tv; DUK_ASSERT(thr != NULL); DUK_ASSERT(udata != NULL); js_ctx = (duk_json_enc_ctx *) udata; DUK_ASSERT(js_ctx != NULL); tv = DUK_GET_TVAL_NEGIDX(thr, -1); if (duk__json_stringify_fast_value(js_ctx, tv) == 0) { DUK_DD(DUK_DDPRINT("top level value not supported, fail fast path")); DUK_DCERROR_TYPE_INVALID_ARGS(thr); /* Error message is ignored, so doesn't matter. */ } return 0; } #endif /* DUK_USE_JSON_STRINGIFY_FASTPATH */ /* * Top level wrappers */ DUK_INTERNAL void duk_bi_json_parse_helper(duk_hthread *thr, duk_idx_t idx_value, duk_idx_t idx_reviver, duk_small_uint_t flags) { duk_json_dec_ctx js_ctx_alloc; duk_json_dec_ctx *js_ctx = &js_ctx_alloc; duk_hstring *h_text; #if defined(DUK_USE_ASSERTIONS) duk_idx_t entry_top = duk_get_top(thr); #endif /* negative top-relative indices not allowed now */ DUK_ASSERT(idx_value == DUK_INVALID_INDEX || idx_value >= 0); DUK_ASSERT(idx_reviver == DUK_INVALID_INDEX || idx_reviver >= 0); DUK_DDD(DUK_DDDPRINT("JSON parse start: text=%!T, reviver=%!T, flags=0x%08lx, stack_top=%ld", (duk_tval *) duk_get_tval(thr, idx_value), (duk_tval *) duk_get_tval(thr, idx_reviver), (unsigned long) flags, (long) duk_get_top(thr))); duk_memzero(&js_ctx_alloc, sizeof(js_ctx_alloc)); js_ctx->thr = thr; #if defined(DUK_USE_EXPLICIT_NULL_INIT) /* nothing now */ #endif js_ctx->recursion_limit = DUK_USE_JSON_DEC_RECLIMIT; DUK_ASSERT(js_ctx->recursion_depth == 0); /* Flag handling currently assumes that flags are consistent. This is OK * because the call sites are now strictly controlled. */ js_ctx->flags = flags; #if defined(DUK_USE_JX) js_ctx->flag_ext_custom = flags & DUK_JSON_FLAG_EXT_CUSTOM; #endif #if defined(DUK_USE_JC) js_ctx->flag_ext_compatible = flags & DUK_JSON_FLAG_EXT_COMPATIBLE; #endif #if defined(DUK_USE_JX) || defined(DUK_USE_JC) js_ctx->flag_ext_custom_or_compatible = flags & (DUK_JSON_FLAG_EXT_CUSTOM | DUK_JSON_FLAG_EXT_COMPATIBLE); #endif h_text = duk_to_hstring(thr, idx_value); /* coerce in-place; rejects Symbols */ DUK_ASSERT(h_text != NULL); /* JSON parsing code is allowed to read [p_start,p_end]: p_end is * valid and points to the string NUL terminator (which is always * guaranteed for duk_hstrings. */ js_ctx->p_start = (const duk_uint8_t *) DUK_HSTRING_GET_DATA(h_text); js_ctx->p = js_ctx->p_start; js_ctx->p_end = ((const duk_uint8_t *) DUK_HSTRING_GET_DATA(h_text)) + DUK_HSTRING_GET_BYTELEN(h_text); DUK_ASSERT(*(js_ctx->p_end) == 0x00); duk__json_dec_value(js_ctx); /* -> [ ... value ] */ DUK_ASSERT(js_ctx->recursion_depth == 0); /* Trailing whitespace has been eaten by duk__json_dec_value(), so if * we're not at end of input here, it's a SyntaxError. */ if (js_ctx->p != js_ctx->p_end) { duk__json_dec_syntax_error(js_ctx); } if (duk_is_callable(thr, idx_reviver)) { DUK_DDD(DUK_DDDPRINT("applying reviver: %!T", (duk_tval *) duk_get_tval(thr, idx_reviver))); js_ctx->idx_reviver = idx_reviver; duk_push_object(thr); duk_dup_m2(thr); /* -> [ ... val root val ] */ duk_put_prop_stridx_short(thr, -2, DUK_STRIDX_EMPTY_STRING); /* default attrs ok */ duk_push_hstring_stridx(thr, DUK_STRIDX_EMPTY_STRING); /* -> [ ... val root "" ] */ DUK_DDD(DUK_DDDPRINT("start reviver walk, root=%!T, name=%!T", (duk_tval *) duk_get_tval(thr, -2), (duk_tval *) duk_get_tval(thr, -1))); DUK_ASSERT(js_ctx->recursion_depth == 0); duk__json_dec_reviver_walk(js_ctx); /* [ ... val root "" ] -> [ ... val val' ] */ DUK_ASSERT(js_ctx->recursion_depth == 0); duk_remove_m2(thr); /* -> [ ... val' ] */ } else { DUK_DDD( DUK_DDDPRINT("reviver does not exist or is not callable: %!T", (duk_tval *) duk_get_tval(thr, idx_reviver))); } /* Final result is at stack top. */ DUK_DDD(DUK_DDDPRINT("JSON parse end: text=%!T, reviver=%!T, flags=0x%08lx, result=%!T, stack_top=%ld", (duk_tval *) duk_get_tval(thr, idx_value), (duk_tval *) duk_get_tval(thr, idx_reviver), (unsigned long) flags, (duk_tval *) duk_get_tval(thr, -1), (long) duk_get_top(thr))); DUK_ASSERT(duk_get_top(thr) == entry_top + 1); } DUK_INTERNAL void duk_bi_json_stringify_helper(duk_hthread *thr, duk_idx_t idx_value, duk_idx_t idx_replacer, duk_idx_t idx_space, duk_small_uint_t flags) { duk_json_enc_ctx js_ctx_alloc; duk_json_enc_ctx *js_ctx = &js_ctx_alloc; duk_hobject *h; duk_idx_t idx_holder; duk_idx_t entry_top; /* negative top-relative indices not allowed now */ DUK_ASSERT(idx_value == DUK_INVALID_INDEX || idx_value >= 0); DUK_ASSERT(idx_replacer == DUK_INVALID_INDEX || idx_replacer >= 0); DUK_ASSERT(idx_space == DUK_INVALID_INDEX || idx_space >= 0); DUK_DDD(DUK_DDDPRINT("JSON stringify start: value=%!T, replacer=%!T, space=%!T, flags=0x%08lx, stack_top=%ld", (duk_tval *) duk_get_tval(thr, idx_value), (duk_tval *) duk_get_tval(thr, idx_replacer), (duk_tval *) duk_get_tval(thr, idx_space), (unsigned long) flags, (long) duk_get_top(thr))); entry_top = duk_get_top(thr); /* * Context init */ duk_memzero(&js_ctx_alloc, sizeof(js_ctx_alloc)); js_ctx->thr = thr; #if defined(DUK_USE_EXPLICIT_NULL_INIT) js_ctx->h_replacer = NULL; js_ctx->h_gap = NULL; #endif js_ctx->idx_proplist = -1; /* Flag handling currently assumes that flags are consistent. This is OK * because the call sites are now strictly controlled. */ js_ctx->flags = flags; js_ctx->flag_ascii_only = flags & DUK_JSON_FLAG_ASCII_ONLY; js_ctx->flag_avoid_key_quotes = flags & DUK_JSON_FLAG_AVOID_KEY_QUOTES; #if defined(DUK_USE_JX) js_ctx->flag_ext_custom = flags & DUK_JSON_FLAG_EXT_CUSTOM; #endif #if defined(DUK_USE_JC) js_ctx->flag_ext_compatible = flags & DUK_JSON_FLAG_EXT_COMPATIBLE; #endif #if defined(DUK_USE_JX) || defined(DUK_USE_JC) js_ctx->flag_ext_custom_or_compatible = flags & (DUK_JSON_FLAG_EXT_CUSTOM | DUK_JSON_FLAG_EXT_COMPATIBLE); #endif /* The #if defined() clutter here handles the JX/JC enable/disable * combinations properly. */ #if defined(DUK_USE_JX) || defined(DUK_USE_JC) js_ctx->stridx_custom_undefined = DUK_STRIDX_LC_NULL; /* standard JSON; array gaps */ #if defined(DUK_USE_JX) if (flags & DUK_JSON_FLAG_EXT_CUSTOM) { js_ctx->stridx_custom_undefined = DUK_STRIDX_LC_UNDEFINED; js_ctx->stridx_custom_nan = DUK_STRIDX_NAN; js_ctx->stridx_custom_neginf = DUK_STRIDX_MINUS_INFINITY; js_ctx->stridx_custom_posinf = DUK_STRIDX_INFINITY; js_ctx->stridx_custom_function = (flags & DUK_JSON_FLAG_AVOID_KEY_QUOTES) ? DUK_STRIDX_JSON_EXT_FUNCTION2 : DUK_STRIDX_JSON_EXT_FUNCTION1; } #endif /* DUK_USE_JX */ #if defined(DUK_USE_JX) && defined(DUK_USE_JC) else #endif /* DUK_USE_JX && DUK_USE_JC */ #if defined(DUK_USE_JC) if (js_ctx->flags & DUK_JSON_FLAG_EXT_COMPATIBLE) { js_ctx->stridx_custom_undefined = DUK_STRIDX_JSON_EXT_UNDEFINED; js_ctx->stridx_custom_nan = DUK_STRIDX_JSON_EXT_NAN; js_ctx->stridx_custom_neginf = DUK_STRIDX_JSON_EXT_NEGINF; js_ctx->stridx_custom_posinf = DUK_STRIDX_JSON_EXT_POSINF; js_ctx->stridx_custom_function = DUK_STRIDX_JSON_EXT_FUNCTION1; } #endif /* DUK_USE_JC */ #endif /* DUK_USE_JX || DUK_USE_JC */ #if defined(DUK_USE_JX) || defined(DUK_USE_JC) if (js_ctx->flags & (DUK_JSON_FLAG_EXT_CUSTOM | DUK_JSON_FLAG_EXT_COMPATIBLE)) { DUK_ASSERT(js_ctx->mask_for_undefined == 0); /* already zero */ } else #endif /* DUK_USE_JX || DUK_USE_JC */ { /* Plain buffer is treated like ArrayBuffer and serialized. * Lightfuncs are treated like objects, but JSON explicitly * skips serializing Function objects so we can just reject * lightfuncs here. */ js_ctx->mask_for_undefined = DUK_TYPE_MASK_UNDEFINED | DUK_TYPE_MASK_POINTER | DUK_TYPE_MASK_LIGHTFUNC; } DUK_BW_INIT_PUSHBUF(thr, &js_ctx->bw, DUK__JSON_STRINGIFY_BUFSIZE); js_ctx->idx_loop = duk_push_bare_object(thr); DUK_ASSERT(js_ctx->idx_loop >= 0); /* [ ... buf loop ] */ /* * Process replacer/proplist (2nd argument to JSON.stringify) */ h = duk_get_hobject(thr, idx_replacer); if (h != NULL) { if (DUK_HOBJECT_IS_CALLABLE(h)) { js_ctx->h_replacer = h; } else if (duk_js_isarray_hobject(h)) { /* Here the specification requires correct array index enumeration * which is a bit tricky for sparse arrays (it is handled by the * enum setup code). We now enumerate ancestors too, although the * specification is not very clear on whether that is required. */ duk_uarridx_t plist_idx = 0; duk_small_uint_t enum_flags; js_ctx->idx_proplist = duk_push_bare_array(thr); enum_flags = DUK_ENUM_ARRAY_INDICES_ONLY | DUK_ENUM_SORT_ARRAY_INDICES; /* expensive flag */ duk_enum(thr, idx_replacer, enum_flags); while (duk_next(thr, -1 /*enum_index*/, 1 /*get_value*/)) { /* [ ... proplist enum_obj key val ] */ if (duk__json_enc_allow_into_proplist(duk_get_tval(thr, -1))) { /* XXX: duplicates should be eliminated here */ DUK_DDD(DUK_DDDPRINT("proplist enum: key=%!T, val=%!T --> accept", (duk_tval *) duk_get_tval(thr, -2), (duk_tval *) duk_get_tval(thr, -1))); duk_to_string(thr, -1); /* extra coercion of strings is OK */ duk_put_prop_index(thr, -4, plist_idx); /* -> [ ... proplist enum_obj key ] */ plist_idx++; duk_pop(thr); } else { DUK_DDD(DUK_DDDPRINT("proplist enum: key=%!T, val=%!T --> reject", (duk_tval *) duk_get_tval(thr, -2), (duk_tval *) duk_get_tval(thr, -1))); duk_pop_2(thr); } } duk_pop(thr); /* pop enum */ /* [ ... proplist ] */ } } /* [ ... buf loop (proplist) ] */ /* * Process space (3rd argument to JSON.stringify) */ h = duk_get_hobject(thr, idx_space); if (h != NULL) { duk_small_uint_t c = DUK_HOBJECT_GET_CLASS_NUMBER(h); if (c == DUK_HOBJECT_CLASS_NUMBER) { duk_to_number(thr, idx_space); } else if (c == DUK_HOBJECT_CLASS_STRING) { duk_to_string(thr, idx_space); } } if (duk_is_number(thr, idx_space)) { duk_small_int_t nspace; /* spaces[] must be static to allow initializer with old compilers like BCC */ static const char spaces[10] = { DUK_ASC_SPACE, DUK_ASC_SPACE, DUK_ASC_SPACE, DUK_ASC_SPACE, DUK_ASC_SPACE, DUK_ASC_SPACE, DUK_ASC_SPACE, DUK_ASC_SPACE, DUK_ASC_SPACE, DUK_ASC_SPACE }; /* XXX: helper */ /* ToInteger() coercion; NaN -> 0, infinities are clamped to 0 and 10 */ nspace = (duk_small_int_t) duk_to_int_clamped(thr, idx_space, 0 /*minval*/, 10 /*maxval*/); DUK_ASSERT(nspace >= 0 && nspace <= 10); duk_push_lstring(thr, spaces, (duk_size_t) nspace); js_ctx->h_gap = duk_known_hstring(thr, -1); DUK_ASSERT(js_ctx->h_gap != NULL); } else if (duk_is_string_notsymbol(thr, idx_space)) { duk_dup(thr, idx_space); duk_substring(thr, -1, 0, 10); /* clamp to 10 chars */ js_ctx->h_gap = duk_known_hstring(thr, -1); } else { /* nop */ } if (js_ctx->h_gap != NULL) { /* If gap is empty, behave as if not given at all. Check * against byte length because character length is more * expensive. */ if (DUK_HSTRING_GET_BYTELEN(js_ctx->h_gap) == 0) { js_ctx->h_gap = NULL; } } /* [ ... buf loop (proplist) (gap) ] */ /* * Fast path: assume no mutation, iterate object property tables * directly; bail out if that assumption doesn't hold. */ #if defined(DUK_USE_JSON_STRINGIFY_FASTPATH) if (js_ctx->h_replacer == NULL && /* replacer is a mutation risk */ js_ctx->idx_proplist == -1) { /* proplist is very rare */ duk_int_t pcall_rc; duk_small_uint_t prev_ms_base_flags; DUK_DD(DUK_DDPRINT("try JSON.stringify() fast path")); /* Use recursion_limit to ensure we don't overwrite js_ctx->visiting[] * array so we don't need two counter checks in the fast path. The * slow path has a much larger recursion limit which we'll use if * necessary. */ DUK_ASSERT(DUK_USE_JSON_ENC_RECLIMIT >= DUK_JSON_ENC_LOOPARRAY); js_ctx->recursion_limit = DUK_JSON_ENC_LOOPARRAY; DUK_ASSERT(js_ctx->recursion_depth == 0); /* Execute the fast path in a protected call. If any error is thrown, * fall back to the slow path. This includes e.g. recursion limit * because the fast path has a smaller recursion limit (and simpler, * limited loop detection). */ duk_dup(thr, idx_value); /* Must prevent finalizers which may have arbitrary side effects. */ prev_ms_base_flags = thr->heap->ms_base_flags; thr->heap->ms_base_flags |= DUK_MS_FLAG_NO_OBJECT_COMPACTION; /* Avoid attempt to compact any objects. */ thr->heap->pf_prevent_count++; /* Prevent finalizers. */ DUK_ASSERT(thr->heap->pf_prevent_count != 0); /* Wrap. */ pcall_rc = duk_safe_call(thr, duk__json_stringify_fast, (void *) js_ctx /*udata*/, 1 /*nargs*/, 0 /*nret*/); DUK_ASSERT(thr->heap->pf_prevent_count > 0); thr->heap->pf_prevent_count--; thr->heap->ms_base_flags = prev_ms_base_flags; if (pcall_rc == DUK_EXEC_SUCCESS) { DUK_DD(DUK_DDPRINT("fast path successful")); DUK_BW_PUSH_AS_STRING(thr, &js_ctx->bw); goto replace_finished; } /* We come here for actual aborts (like encountering .toJSON()) * but also for recursion/loop errors. Bufwriter size can be * kept because we'll probably need at least as much as we've * allocated so far. */ DUK_D(DUK_DPRINT("fast path failed, serialize using slow path instead")); DUK_BW_RESET_SIZE(thr, &js_ctx->bw); js_ctx->recursion_depth = 0; } #endif /* * Create wrapper object and serialize */ idx_holder = duk_push_object(thr); duk_dup(thr, idx_value); duk_put_prop_stridx_short(thr, -2, DUK_STRIDX_EMPTY_STRING); DUK_DDD(DUK_DDDPRINT("before: flags=0x%08lx, loop=%!T, replacer=%!O, " "proplist=%!T, gap=%!O, holder=%!T", (unsigned long) js_ctx->flags, (duk_tval *) duk_get_tval(thr, js_ctx->idx_loop), (duk_heaphdr *) js_ctx->h_replacer, (duk_tval *) (js_ctx->idx_proplist >= 0 ? duk_get_tval(thr, js_ctx->idx_proplist) : NULL), (duk_heaphdr *) js_ctx->h_gap, (duk_tval *) duk_get_tval(thr, -1))); /* serialize the wrapper with empty string key */ duk_push_hstring_empty(thr); /* [ ... buf loop (proplist) (gap) holder "" ] */ js_ctx->recursion_limit = DUK_USE_JSON_ENC_RECLIMIT; DUK_ASSERT(js_ctx->recursion_depth == 0); if (DUK_UNLIKELY(duk__json_enc_value(js_ctx, idx_holder) == 0)) { /* [ ... holder key ] -> [ ... holder ] */ /* Result is undefined. */ duk_push_undefined(thr); } else { /* Convert buffer to result string. */ DUK_BW_PUSH_AS_STRING(thr, &js_ctx->bw); } DUK_DDD(DUK_DDDPRINT("after: flags=0x%08lx, loop=%!T, replacer=%!O, " "proplist=%!T, gap=%!O, holder=%!T", (unsigned long) js_ctx->flags, (duk_tval *) duk_get_tval(thr, js_ctx->idx_loop), (duk_heaphdr *) js_ctx->h_replacer, (duk_tval *) (js_ctx->idx_proplist >= 0 ? duk_get_tval(thr, js_ctx->idx_proplist) : NULL), (duk_heaphdr *) js_ctx->h_gap, (duk_tval *) duk_get_tval(thr, idx_holder))); /* The stack has a variable shape here, so force it to the * desired one explicitly. */ #if defined(DUK_USE_JSON_STRINGIFY_FASTPATH) replace_finished: #endif duk_replace(thr, entry_top); duk_set_top(thr, entry_top + 1); DUK_DDD(DUK_DDDPRINT("JSON stringify end: value=%!T, replacer=%!T, space=%!T, " "flags=0x%08lx, result=%!T, stack_top=%ld", (duk_tval *) duk_get_tval(thr, idx_value), (duk_tval *) duk_get_tval(thr, idx_replacer), (duk_tval *) duk_get_tval(thr, idx_space), (unsigned long) flags, (duk_tval *) duk_get_tval(thr, -1), (long) duk_get_top(thr))); DUK_ASSERT(duk_get_top(thr) == entry_top + 1); } #if defined(DUK_USE_JSON_BUILTIN) /* * Entry points */ DUK_INTERNAL duk_ret_t duk_bi_json_object_parse(duk_hthread *thr) { duk_bi_json_parse_helper(thr, 0 /*idx_value*/, 1 /*idx_replacer*/, 0 /*flags*/); return 1; } DUK_INTERNAL duk_ret_t duk_bi_json_object_stringify(duk_hthread *thr) { duk_bi_json_stringify_helper(thr, 0 /*idx_value*/, 1 /*idx_replacer*/, 2 /*idx_space*/, 0 /*flags*/); return 1; } #endif /* DUK_USE_JSON_BUILTIN */ #endif /* DUK_USE_JSON_SUPPORT */ /* automatic undefs */ #undef DUK__EMIT_1 #undef DUK__EMIT_2 #undef DUK__EMIT_CSTR #undef DUK__EMIT_HSTR #undef DUK__EMIT_STRIDX #undef DUK__JSON_DECSTR_BUFSIZE #undef DUK__JSON_DECSTR_CHUNKSIZE #undef DUK__JSON_ENCSTR_CHUNKSIZE #undef DUK__JSON_MAX_ESC_LEN #undef DUK__JSON_STRINGIFY_BUFSIZE #undef DUK__MKESC #undef DUK__UNEMIT_1 #line 1 "duk_bi_math.c" /* * Math built-ins */ /* #include duk_internal.h -> already included */ #if defined(DUK_USE_MATH_BUILTIN) /* * Use static helpers which can work with math.h functions matching * the following signatures. This is not portable if any of these math * functions is actually a macro. * * Typing here is intentionally 'double' wherever values interact with * the standard library APIs. */ typedef double (*duk__one_arg_func)(double); typedef double (*duk__two_arg_func)(double, double); DUK_LOCAL duk_ret_t duk__math_minmax(duk_hthread *thr, duk_double_t initial, duk__two_arg_func min_max) { duk_idx_t n = duk_get_top(thr); duk_idx_t i; duk_double_t res = initial; duk_double_t t; /* * Note: fmax() does not match the E5 semantics. E5 requires * that if -any- input to Math.max() is a NaN, the result is a * NaN. fmax() will return a NaN only if -both- inputs are NaN. * Same applies to fmin(). * * Note: every input value must be coerced with ToNumber(), even * if we know the result will be a NaN anyway: ToNumber() may have * side effects for which even order of evaluation matters. */ for (i = 0; i < n; i++) { t = duk_to_number(thr, i); if (DUK_FPCLASSIFY(t) == DUK_FP_NAN || DUK_FPCLASSIFY(res) == DUK_FP_NAN) { /* Note: not normalized, but duk_push_number() will normalize */ res = (duk_double_t) DUK_DOUBLE_NAN; } else { res = (duk_double_t) min_max(res, (double) t); } } duk_push_number(thr, res); return 1; } DUK_LOCAL double duk__fmin_fixed(double x, double y) { /* fmin() with args -0 and +0 is not guaranteed to return * -0 as ECMAScript requires. */ if (duk_double_equals(x, 0.0) && duk_double_equals(y, 0.0)) { duk_double_union du1, du2; du1.d = x; du2.d = y; /* Already checked to be zero so these must hold, and allow us * to check for "x is -0 or y is -0" by ORing the high parts * for comparison. */ DUK_ASSERT(du1.ui[DUK_DBL_IDX_UI0] == 0 || du1.ui[DUK_DBL_IDX_UI0] == 0x80000000UL); DUK_ASSERT(du2.ui[DUK_DBL_IDX_UI0] == 0 || du2.ui[DUK_DBL_IDX_UI0] == 0x80000000UL); /* XXX: what's the safest way of creating a negative zero? */ if ((du1.ui[DUK_DBL_IDX_UI0] | du2.ui[DUK_DBL_IDX_UI0]) != 0) { /* Enter here if either x or y (or both) is -0. */ return -0.0; } else { return +0.0; } } return duk_double_fmin(x, y); } DUK_LOCAL double duk__fmax_fixed(double x, double y) { /* fmax() with args -0 and +0 is not guaranteed to return * +0 as ECMAScript requires. */ if (duk_double_equals(x, 0.0) && duk_double_equals(y, 0.0)) { if (DUK_SIGNBIT(x) == 0 || DUK_SIGNBIT(y) == 0) { return +0.0; } else { return -0.0; } } return duk_double_fmax(x, y); } #if defined(DUK_USE_ES6) DUK_LOCAL double duk__cbrt(double x) { /* cbrt() is C99. To avoid hassling embedders with the need to provide a * cube root function, we can get by with pow(). The result is not * identical, but that's OK: ES2015 says it's implementation-dependent. */ #if defined(DUK_CBRT) /* cbrt() matches ES2015 requirements. */ return DUK_CBRT(x); #else duk_small_int_t c = (duk_small_int_t) DUK_FPCLASSIFY(x); /* pow() does not, however. */ if (c == DUK_FP_NAN || c == DUK_FP_INFINITE || c == DUK_FP_ZERO) { return x; } if (DUK_SIGNBIT(x)) { return -DUK_POW(-x, 1.0 / 3.0); } else { return DUK_POW(x, 1.0 / 3.0); } #endif } DUK_LOCAL double duk__log2(double x) { #if defined(DUK_LOG2) return DUK_LOG2(x); #else return DUK_LOG(x) * DUK_DOUBLE_LOG2E; #endif } DUK_LOCAL double duk__log10(double x) { #if defined(DUK_LOG10) return DUK_LOG10(x); #else return DUK_LOG(x) * DUK_DOUBLE_LOG10E; #endif } DUK_LOCAL double duk__trunc(double x) { #if defined(DUK_TRUNC) return DUK_TRUNC(x); #else /* Handles -0 correctly: -0.0 matches 'x >= 0.0' but floor() * is required to return -0 when the argument is -0. */ return x >= 0.0 ? DUK_FLOOR(x) : DUK_CEIL(x); #endif } #endif /* DUK_USE_ES6 */ DUK_LOCAL double duk__round_fixed(double x) { /* Numbers half-way between integers must be rounded towards +Infinity, * e.g. -3.5 must be rounded to -3 (not -4). When rounded to zero, zero * sign must be set appropriately. E5.1 Section 15.8.2.15. * * Note that ANSI C round() is "round to nearest integer, away from zero", * which is incorrect for negative values. Here we make do with floor(). */ duk_small_int_t c = (duk_small_int_t) DUK_FPCLASSIFY(x); if (c == DUK_FP_NAN || c == DUK_FP_INFINITE || c == DUK_FP_ZERO) { return x; } /* * x is finite and non-zero * * -1.6 -> floor(-1.1) -> -2 * -1.5 -> floor(-1.0) -> -1 (towards +Inf) * -1.4 -> floor(-0.9) -> -1 * -0.5 -> -0.0 (special case) * -0.1 -> -0.0 (special case) * +0.1 -> +0.0 (special case) * +0.5 -> floor(+1.0) -> 1 (towards +Inf) * +1.4 -> floor(+1.9) -> 1 * +1.5 -> floor(+2.0) -> 2 (towards +Inf) * +1.6 -> floor(+2.1) -> 2 */ if (x >= -0.5 && x < 0.5) { /* +0.5 is handled by floor, this is on purpose */ if (x < 0.0) { return -0.0; } else { return +0.0; } } return DUK_FLOOR(x + 0.5); } /* Wrappers for calling standard math library methods. These may be required * on platforms where one or more of the math built-ins are defined as macros * or inline functions and are thus not suitable to be used as function pointers. */ #if defined(DUK_USE_AVOID_PLATFORM_FUNCPTRS) DUK_LOCAL double duk__fabs(double x) { return DUK_FABS(x); } DUK_LOCAL double duk__acos(double x) { return DUK_ACOS(x); } DUK_LOCAL double duk__asin(double x) { return DUK_ASIN(x); } DUK_LOCAL double duk__atan(double x) { return DUK_ATAN(x); } DUK_LOCAL double duk__ceil(double x) { return DUK_CEIL(x); } DUK_LOCAL double duk__cos(double x) { return DUK_COS(x); } DUK_LOCAL double duk__exp(double x) { return DUK_EXP(x); } DUK_LOCAL double duk__floor(double x) { return DUK_FLOOR(x); } DUK_LOCAL double duk__log(double x) { return DUK_LOG(x); } DUK_LOCAL double duk__sin(double x) { return DUK_SIN(x); } DUK_LOCAL double duk__sqrt(double x) { return DUK_SQRT(x); } DUK_LOCAL double duk__tan(double x) { return DUK_TAN(x); } DUK_LOCAL double duk__atan2_fixed(double x, double y) { #if defined(DUK_USE_ATAN2_WORKAROUNDS) /* Specific fixes to common atan2() implementation issues: * - test-bug-mingw-math-issues.js */ if (DUK_ISINF(x) && DUK_ISINF(y)) { if (DUK_SIGNBIT(x)) { if (DUK_SIGNBIT(y)) { return -2.356194490192345; } else { return -0.7853981633974483; } } else { if (DUK_SIGNBIT(y)) { return 2.356194490192345; } else { return 0.7853981633974483; } } } #else /* Some ISO C assumptions. */ DUK_ASSERT(duk_double_equals(DUK_ATAN2(DUK_DOUBLE_INFINITY, DUK_DOUBLE_INFINITY), 0.7853981633974483)); DUK_ASSERT(duk_double_equals(DUK_ATAN2(-DUK_DOUBLE_INFINITY, DUK_DOUBLE_INFINITY), -0.7853981633974483)); DUK_ASSERT(duk_double_equals(DUK_ATAN2(DUK_DOUBLE_INFINITY, -DUK_DOUBLE_INFINITY), 2.356194490192345)); DUK_ASSERT(duk_double_equals(DUK_ATAN2(-DUK_DOUBLE_INFINITY, -DUK_DOUBLE_INFINITY), -2.356194490192345)); #endif return DUK_ATAN2(x, y); } #endif /* DUK_USE_AVOID_PLATFORM_FUNCPTRS */ /* order must match constants in genbuiltins.py */ DUK_LOCAL const duk__one_arg_func duk__one_arg_funcs[] = { #if defined(DUK_USE_AVOID_PLATFORM_FUNCPTRS) duk__fabs, duk__acos, duk__asin, duk__atan, duk__ceil, duk__cos, duk__exp, duk__floor, duk__log, duk__round_fixed, duk__sin, duk__sqrt, duk__tan, #if defined(DUK_USE_ES6) duk__cbrt, duk__log2, duk__log10, duk__trunc #endif #else /* DUK_USE_AVOID_PLATFORM_FUNCPTRS */ DUK_FABS, DUK_ACOS, DUK_ASIN, DUK_ATAN, DUK_CEIL, DUK_COS, DUK_EXP, DUK_FLOOR, DUK_LOG, duk__round_fixed, DUK_SIN, DUK_SQRT, DUK_TAN, #if defined(DUK_USE_ES6) duk__cbrt, duk__log2, duk__log10, duk__trunc #endif #endif /* DUK_USE_AVOID_PLATFORM_FUNCPTRS */ }; /* order must match constants in genbuiltins.py */ DUK_LOCAL const duk__two_arg_func duk__two_arg_funcs[] = { #if defined(DUK_USE_AVOID_PLATFORM_FUNCPTRS) duk__atan2_fixed, duk_js_arith_pow #else duk__atan2_fixed, duk_js_arith_pow #endif }; DUK_INTERNAL duk_ret_t duk_bi_math_object_onearg_shared(duk_hthread *thr) { duk_small_int_t fun_idx = duk_get_current_magic(thr); duk__one_arg_func fun; duk_double_t arg1; DUK_ASSERT(fun_idx >= 0); DUK_ASSERT(fun_idx < (duk_small_int_t) (sizeof(duk__one_arg_funcs) / sizeof(duk__one_arg_func))); arg1 = duk_to_number(thr, 0); fun = duk__one_arg_funcs[fun_idx]; duk_push_number(thr, (duk_double_t) fun((double) arg1)); return 1; } DUK_INTERNAL duk_ret_t duk_bi_math_object_twoarg_shared(duk_hthread *thr) { duk_small_int_t fun_idx = duk_get_current_magic(thr); duk__two_arg_func fun; duk_double_t arg1; duk_double_t arg2; DUK_ASSERT(fun_idx >= 0); DUK_ASSERT(fun_idx < (duk_small_int_t) (sizeof(duk__two_arg_funcs) / sizeof(duk__two_arg_func))); arg1 = duk_to_number(thr, 0); /* explicit ordered evaluation to match coercion semantics */ arg2 = duk_to_number(thr, 1); fun = duk__two_arg_funcs[fun_idx]; duk_push_number(thr, (duk_double_t) fun((double) arg1, (double) arg2)); return 1; } DUK_INTERNAL duk_ret_t duk_bi_math_object_max(duk_hthread *thr) { return duk__math_minmax(thr, -DUK_DOUBLE_INFINITY, duk__fmax_fixed); } DUK_INTERNAL duk_ret_t duk_bi_math_object_min(duk_hthread *thr) { return duk__math_minmax(thr, DUK_DOUBLE_INFINITY, duk__fmin_fixed); } DUK_INTERNAL duk_ret_t duk_bi_math_object_random(duk_hthread *thr) { duk_push_number(thr, (duk_double_t) duk_util_get_random_double(thr)); return 1; } #if defined(DUK_USE_ES6) DUK_INTERNAL duk_ret_t duk_bi_math_object_hypot(duk_hthread *thr) { /* * E6 Section 20.2.2.18: Math.hypot * * - If no arguments are passed, the result is +0. * - If any argument is +inf, the result is +inf. * - If any argument is -inf, the result is +inf. * - If no argument is +inf or -inf, and any argument is NaN, the result is * NaN. * - If all arguments are either +0 or -0, the result is +0. */ duk_idx_t nargs; duk_idx_t i; duk_bool_t found_nan; duk_double_t max; duk_double_t sum, summand; duk_double_t comp, prelim; duk_double_t t; nargs = duk_get_top(thr); /* Find the highest value. Also ToNumber() coerces. */ max = 0.0; found_nan = 0; for (i = 0; i < nargs; i++) { t = DUK_FABS(duk_to_number(thr, i)); if (DUK_FPCLASSIFY(t) == DUK_FP_NAN) { found_nan = 1; } else { max = duk_double_fmax(max, t); } } /* Early return cases. */ if (duk_double_equals(max, DUK_DOUBLE_INFINITY)) { duk_push_number(thr, DUK_DOUBLE_INFINITY); return 1; } else if (found_nan) { duk_push_number(thr, DUK_DOUBLE_NAN); return 1; } else if (duk_double_equals(max, 0.0)) { duk_push_number(thr, 0.0); /* Otherwise we'd divide by zero. */ return 1; } /* Use Kahan summation and normalize to the highest value to minimize * floating point rounding error and avoid overflow. * * https://en.wikipedia.org/wiki/Kahan_summation_algorithm */ sum = 0.0; comp = 0.0; for (i = 0; i < nargs; i++) { t = DUK_FABS(duk_get_number(thr, i)) / max; summand = (t * t) - comp; prelim = sum + summand; comp = (prelim - sum) - summand; sum = prelim; } duk_push_number(thr, (duk_double_t) DUK_SQRT(sum) * max); return 1; } #endif /* DUK_USE_ES6 */ #if defined(DUK_USE_ES6) DUK_INTERNAL duk_ret_t duk_bi_math_object_sign(duk_hthread *thr) { duk_double_t d; d = duk_to_number(thr, 0); if (duk_double_is_nan(d)) { DUK_ASSERT(duk_is_nan(thr, -1)); return 1; /* NaN input -> return NaN */ } if (duk_double_equals(d, 0.0)) { /* Zero sign kept, i.e. -0 -> -0, +0 -> +0. */ return 1; } duk_push_int(thr, (d > 0.0 ? 1 : -1)); return 1; } #endif /* DUK_USE_ES6 */ #if defined(DUK_USE_ES6) DUK_INTERNAL duk_ret_t duk_bi_math_object_clz32(duk_hthread *thr) { duk_uint32_t x; duk_small_uint_t i; #if defined(DUK_USE_PREFER_SIZE) duk_uint32_t mask; x = duk_to_uint32(thr, 0); for (i = 0, mask = 0x80000000UL; mask != 0; mask >>= 1) { if (x & mask) { break; } i++; } DUK_ASSERT(i <= 32); duk_push_uint(thr, i); return 1; #else /* DUK_USE_PREFER_SIZE */ i = 0; x = duk_to_uint32(thr, 0); if (x & 0xffff0000UL) { x >>= 16; } else { i += 16; } if (x & 0x0000ff00UL) { x >>= 8; } else { i += 8; } if (x & 0x000000f0UL) { x >>= 4; } else { i += 4; } if (x & 0x0000000cUL) { x >>= 2; } else { i += 2; } if (x & 0x00000002UL) { x >>= 1; } else { i += 1; } if (x & 0x00000001UL) { ; } else { i += 1; } DUK_ASSERT(i <= 32); duk_push_uint(thr, i); return 1; #endif /* DUK_USE_PREFER_SIZE */ } #endif /* DUK_USE_ES6 */ #if defined(DUK_USE_ES6) DUK_INTERNAL duk_ret_t duk_bi_math_object_imul(duk_hthread *thr) { duk_uint32_t x, y, z; x = duk_to_uint32(thr, 0); y = duk_to_uint32(thr, 1); z = x * y; /* While arguments are ToUint32() coerced and the multiplication * is unsigned as such, the final result is curiously interpreted * as a signed 32-bit value. */ duk_push_i32(thr, (duk_int32_t) z); return 1; } #endif /* DUK_USE_ES6 */ #endif /* DUK_USE_MATH_BUILTIN */ #line 1 "duk_bi_number.c" /* * Number built-ins */ /* #include duk_internal.h -> already included */ #if defined(DUK_USE_NUMBER_BUILTIN) DUK_LOCAL duk_double_t duk__push_this_number_plain(duk_hthread *thr) { duk_hobject *h; /* Number built-in accepts a plain number or a Number object (whose * internal value is operated on). Other types cause TypeError. */ duk_push_this(thr); if (duk_is_number(thr, -1)) { DUK_DDD(DUK_DDDPRINT("plain number value: %!T", (duk_tval *) duk_get_tval(thr, -1))); goto done; } h = duk_get_hobject(thr, -1); if (!h || (DUK_HOBJECT_GET_CLASS_NUMBER(h) != DUK_HOBJECT_CLASS_NUMBER)) { DUK_DDD(DUK_DDDPRINT("unacceptable this value: %!T", (duk_tval *) duk_get_tval(thr, -1))); DUK_ERROR_TYPE(thr, "number expected"); DUK_WO_NORETURN(return 0.0;); } duk_xget_owndataprop_stridx_short(thr, -1, DUK_STRIDX_INT_VALUE); DUK_ASSERT(duk_is_number(thr, -1)); DUK_DDD(DUK_DDDPRINT("number object: %!T, internal value: %!T", (duk_tval *) duk_get_tval(thr, -2), (duk_tval *) duk_get_tval(thr, -1))); duk_remove_m2(thr); done: return duk_get_number(thr, -1); } DUK_INTERNAL duk_ret_t duk_bi_number_constructor(duk_hthread *thr) { duk_idx_t nargs; duk_hobject *h_this; /* * The Number constructor uses ToNumber(arg) for number coercion * (coercing an undefined argument to NaN). However, if the * argument is not given at all, +0 must be used instead. To do * this, a vararg function is used. */ nargs = duk_get_top(thr); if (nargs == 0) { duk_push_int(thr, 0); } duk_to_number(thr, 0); duk_set_top(thr, 1); DUK_ASSERT_TOP(thr, 1); if (!duk_is_constructor_call(thr)) { return 1; } /* * E5 Section 15.7.2.1 requires that the constructed object * must have the original Number.prototype as its internal * prototype. However, since Number.prototype is non-writable * and non-configurable, this doesn't have to be enforced here: * The default object (bound to 'this') is OK, though we have * to change its class. * * Internal value set to ToNumber(arg) or +0; if no arg given, * ToNumber(undefined) = NaN, so special treatment is needed * (above). String internal value is immutable. */ /* XXX: helper */ duk_push_this(thr); h_this = duk_known_hobject(thr, -1); DUK_HOBJECT_SET_CLASS_NUMBER(h_this, DUK_HOBJECT_CLASS_NUMBER); DUK_ASSERT(DUK_HOBJECT_GET_PROTOTYPE(thr->heap, h_this) == thr->builtins[DUK_BIDX_NUMBER_PROTOTYPE]); DUK_ASSERT(DUK_HOBJECT_GET_CLASS_NUMBER(h_this) == DUK_HOBJECT_CLASS_NUMBER); DUK_ASSERT(DUK_HOBJECT_HAS_EXTENSIBLE(h_this)); duk_dup_0(thr); /* -> [ val obj val ] */ duk_xdef_prop_stridx_short(thr, -2, DUK_STRIDX_INT_VALUE, DUK_PROPDESC_FLAGS_NONE); return 0; /* no return value -> don't replace created value */ } DUK_INTERNAL duk_ret_t duk_bi_number_prototype_value_of(duk_hthread *thr) { (void) duk__push_this_number_plain(thr); return 1; } DUK_INTERNAL duk_ret_t duk_bi_number_prototype_to_string(duk_hthread *thr) { duk_small_int_t radix; duk_small_uint_t n2s_flags; (void) duk__push_this_number_plain(thr); if (duk_is_undefined(thr, 0)) { radix = 10; } else { radix = (duk_small_int_t) duk_to_int_check_range(thr, 0, 2, 36); } DUK_DDD(DUK_DDDPRINT("radix=%ld", (long) radix)); n2s_flags = 0; duk_numconv_stringify(thr, radix /*radix*/, 0 /*digits*/, n2s_flags /*flags*/); return 1; } DUK_INTERNAL duk_ret_t duk_bi_number_prototype_to_locale_string(duk_hthread *thr) { /* XXX: just use toString() for now; permitted although not recommended. * nargs==1, so radix is passed to toString(). */ return duk_bi_number_prototype_to_string(thr); } /* * toFixed(), toExponential(), toPrecision() */ /* XXX: shared helper for toFixed(), toExponential(), toPrecision()? */ DUK_INTERNAL duk_ret_t duk_bi_number_prototype_to_fixed(duk_hthread *thr) { duk_small_int_t frac_digits; duk_double_t d; duk_small_int_t c; duk_small_uint_t n2s_flags; /* In ES5.1 frac_digits is coerced first; in ES2015 the 'this number * value' check is done first. */ d = duk__push_this_number_plain(thr); frac_digits = (duk_small_int_t) duk_to_int_check_range(thr, 0, 0, 20); c = (duk_small_int_t) DUK_FPCLASSIFY(d); if (c == DUK_FP_NAN || c == DUK_FP_INFINITE) { goto use_to_string; } if (d >= 1.0e21 || d <= -1.0e21) { goto use_to_string; } n2s_flags = DUK_N2S_FLAG_FIXED_FORMAT | DUK_N2S_FLAG_FRACTION_DIGITS; duk_numconv_stringify(thr, 10 /*radix*/, frac_digits /*digits*/, n2s_flags /*flags*/); return 1; use_to_string: DUK_ASSERT_TOP(thr, 2); duk_to_string(thr, -1); return 1; } DUK_INTERNAL duk_ret_t duk_bi_number_prototype_to_exponential(duk_hthread *thr) { duk_bool_t frac_undefined; duk_small_int_t frac_digits; duk_double_t d; duk_small_int_t c; duk_small_uint_t n2s_flags; d = duk__push_this_number_plain(thr); frac_undefined = duk_is_undefined(thr, 0); duk_to_int(thr, 0); /* for side effects */ c = (duk_small_int_t) DUK_FPCLASSIFY(d); if (c == DUK_FP_NAN || c == DUK_FP_INFINITE) { goto use_to_string; } frac_digits = (duk_small_int_t) duk_to_int_check_range(thr, 0, 0, 20); n2s_flags = DUK_N2S_FLAG_FORCE_EXP | (frac_undefined ? 0 : DUK_N2S_FLAG_FIXED_FORMAT); duk_numconv_stringify(thr, 10 /*radix*/, frac_digits + 1 /*leading digit + fractions*/, n2s_flags /*flags*/); return 1; use_to_string: DUK_ASSERT_TOP(thr, 2); duk_to_string(thr, -1); return 1; } DUK_INTERNAL duk_ret_t duk_bi_number_prototype_to_precision(duk_hthread *thr) { /* The specification has quite awkward order of coercion and * checks for toPrecision(). The operations below are a bit * reordered, within constraints of observable side effects. */ duk_double_t d; duk_small_int_t prec; duk_small_int_t c; duk_small_uint_t n2s_flags; DUK_ASSERT_TOP(thr, 1); d = duk__push_this_number_plain(thr); if (duk_is_undefined(thr, 0)) { goto use_to_string; } DUK_ASSERT_TOP(thr, 2); duk_to_int(thr, 0); /* for side effects */ c = (duk_small_int_t) DUK_FPCLASSIFY(d); if (c == DUK_FP_NAN || c == DUK_FP_INFINITE) { goto use_to_string; } prec = (duk_small_int_t) duk_to_int_check_range(thr, 0, 1, 21); n2s_flags = DUK_N2S_FLAG_FIXED_FORMAT | DUK_N2S_FLAG_NO_ZERO_PAD; duk_numconv_stringify(thr, 10 /*radix*/, prec /*digits*/, n2s_flags /*flags*/); return 1; use_to_string: /* Used when precision is undefined; also used for NaN (-> "NaN"), * and +/- infinity (-> "Infinity", "-Infinity"). */ DUK_ASSERT_TOP(thr, 2); duk_to_string(thr, -1); return 1; } /* * ES2015 isFinite() etc */ #if defined(DUK_USE_ES6) DUK_INTERNAL duk_ret_t duk_bi_number_check_shared(duk_hthread *thr) { duk_int_t magic; duk_bool_t ret = 0; if (duk_is_number(thr, 0)) { duk_double_t d; magic = duk_get_current_magic(thr); d = duk_get_number(thr, 0); switch (magic) { case 0: /* isFinite() */ ret = duk_double_is_finite(d); break; case 1: /* isInteger() */ ret = duk_double_is_integer(d); break; case 2: /* isNaN() */ ret = duk_double_is_nan(d); break; default: /* isSafeInteger() */ DUK_ASSERT(magic == 3); ret = duk_double_is_safe_integer(d); } } duk_push_boolean(thr, ret); return 1; } #endif /* DUK_USE_ES6 */ #endif /* DUK_USE_NUMBER_BUILTIN */ #line 1 "duk_bi_object.c" /* * Object built-ins */ /* #include duk_internal.h -> already included */ /* Needed even when Object built-in disabled. */ DUK_INTERNAL duk_ret_t duk_bi_object_prototype_to_string(duk_hthread *thr) { duk_tval *tv; tv = DUK_HTHREAD_THIS_PTR(thr); duk_push_class_string_tval(thr, tv, 0 /*avoid_side_effects*/); return 1; } #if defined(DUK_USE_OBJECT_BUILTIN) DUK_INTERNAL duk_ret_t duk_bi_object_constructor(duk_hthread *thr) { duk_uint_t arg_mask; arg_mask = duk_get_type_mask(thr, 0); if (!duk_is_constructor_call(thr) && /* not a constructor call */ ((arg_mask & (DUK_TYPE_MASK_NULL | DUK_TYPE_MASK_UNDEFINED)) == 0)) { /* and argument not null or undefined */ duk_to_object(thr, 0); return 1; } /* Pointer and buffer primitive values are treated like other * primitives values which have a fully fledged object counterpart: * promote to an object value. Lightfuncs and plain buffers are * coerced with ToObject() even they could also be returned as is. */ if (arg_mask & (DUK_TYPE_MASK_OBJECT | DUK_TYPE_MASK_STRING | DUK_TYPE_MASK_BOOLEAN | DUK_TYPE_MASK_NUMBER | DUK_TYPE_MASK_POINTER | DUK_TYPE_MASK_BUFFER | DUK_TYPE_MASK_LIGHTFUNC)) { /* For DUK_TYPE_OBJECT the coercion is a no-op and could * be checked for explicitly, but Object(obj) calls are * not very common so opt for minimal footprint. */ duk_to_object(thr, 0); return 1; } (void) duk_push_object_helper(thr, DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_FLAG_FASTREFS | DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJECT), DUK_BIDX_OBJECT_PROTOTYPE); return 1; } #endif /* DUK_USE_OBJECT_BUILTIN */ #if defined(DUK_USE_OBJECT_BUILTIN) && defined(DUK_USE_ES6) DUK_INTERNAL duk_ret_t duk_bi_object_constructor_assign(duk_hthread *thr) { duk_idx_t nargs; duk_int_t idx; nargs = duk_get_top_require_min(thr, 1 /*min_top*/); duk_to_object(thr, 0); for (idx = 1; idx < nargs; idx++) { /* E7 19.1.2.1 (step 4a) */ if (duk_is_null_or_undefined(thr, idx)) { continue; } /* duk_enum() respects ES2015+ [[OwnPropertyKeys]] ordering, which is * convenient here. */ duk_to_object(thr, idx); duk_enum(thr, idx, DUK_ENUM_OWN_PROPERTIES_ONLY); while (duk_next(thr, -1, 1 /*get_value*/)) { /* [ target ... enum key value ] */ duk_put_prop(thr, 0); /* [ target ... enum ] */ } /* Could pop enumerator, but unnecessary because of duk_set_top() * below. */ } duk_set_top(thr, 1); return 1; } #endif #if defined(DUK_USE_OBJECT_BUILTIN) && defined(DUK_USE_ES6) DUK_INTERNAL duk_ret_t duk_bi_object_constructor_is(duk_hthread *thr) { DUK_ASSERT_TOP(thr, 2); duk_push_boolean(thr, duk_samevalue(thr, 0, 1)); return 1; } #endif #if defined(DUK_USE_OBJECT_BUILTIN) DUK_INTERNAL duk_ret_t duk_bi_object_constructor_create(duk_hthread *thr) { duk_hobject *proto; DUK_ASSERT_TOP(thr, 2); #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) duk_hbufobj_promote_plain(thr, 0); #endif proto = duk_require_hobject_accept_mask(thr, 0, DUK_TYPE_MASK_NULL); DUK_ASSERT(proto != NULL || duk_is_null(thr, 0)); (void) duk_push_object_helper_proto(thr, DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_FLAG_FASTREFS | DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJECT), proto); if (!duk_is_undefined(thr, 1)) { /* [ O Properties obj ] */ duk_replace(thr, 0); /* [ obj Properties ] */ /* Just call the "original" Object.defineProperties() to * finish up. */ return duk_bi_object_constructor_define_properties(thr); } /* [ O Properties obj ] */ return 1; } #endif /* DUK_USE_OBJECT_BUILTIN */ #if defined(DUK_USE_OBJECT_BUILTIN) DUK_INTERNAL duk_ret_t duk_bi_object_constructor_define_properties(duk_hthread *thr) { duk_small_uint_t pass; duk_uint_t defprop_flags; duk_hobject *obj; duk_idx_t idx_value; duk_hobject *get; duk_hobject *set; /* Lightfunc and plain buffer handling by ToObject() coercion. */ obj = duk_require_hobject_promote_mask(thr, 0, DUK_TYPE_MASK_LIGHTFUNC | DUK_TYPE_MASK_BUFFER); DUK_ASSERT(obj != NULL); duk_to_object(thr, 1); /* properties object */ DUK_DDD(DUK_DDDPRINT("target=%!iT, properties=%!iT", (duk_tval *) duk_get_tval(thr, 0), (duk_tval *) duk_get_tval(thr, 1))); /* * Two pass approach to processing the property descriptors. * On first pass validate and normalize all descriptors before * any changes are made to the target object. On second pass * make the actual modifications to the target object. * * Right now we'll just use the same normalize/validate helper * on both passes, ignoring its outputs on the first pass. */ for (pass = 0; pass < 2; pass++) { duk_set_top(thr, 2); /* -> [ hobject props ] */ duk_enum(thr, 1, DUK_ENUM_OWN_PROPERTIES_ONLY | DUK_ENUM_INCLUDE_SYMBOLS /*enum_flags*/); for (;;) { duk_hstring *key; /* [ hobject props enum(props) ] */ duk_set_top(thr, 3); if (!duk_next(thr, 2, 1 /*get_value*/)) { break; } DUK_DDD(DUK_DDDPRINT("-> key=%!iT, desc=%!iT", (duk_tval *) duk_get_tval(thr, -2), (duk_tval *) duk_get_tval(thr, -1))); /* [ hobject props enum(props) key desc ] */ duk_hobject_prepare_property_descriptor(thr, 4 /*idx_desc*/, &defprop_flags, &idx_value, &get, &set); /* [ hobject props enum(props) key desc [multiple values] ] */ if (pass == 0) { continue; } /* This allows symbols on purpose. */ key = duk_known_hstring(thr, 3); DUK_ASSERT(key != NULL); duk_hobject_define_property_helper(thr, defprop_flags, obj, key, idx_value, get, set, 1 /*throw_flag*/); } } /* * Return target object */ duk_dup_0(thr); return 1; } #endif /* DUK_USE_OBJECT_BUILTIN */ #if defined(DUK_USE_OBJECT_BUILTIN) DUK_INTERNAL duk_ret_t duk_bi_object_constructor_seal_freeze_shared(duk_hthread *thr) { DUK_ASSERT_TOP(thr, 1); duk_seal_freeze_raw(thr, 0, (duk_bool_t) duk_get_current_magic(thr) /*is_freeze*/); return 1; } #endif /* DUK_USE_OBJECT_BUILTIN */ #if defined(DUK_USE_OBJECT_BUILTIN) DUK_INTERNAL duk_ret_t duk_bi_object_constructor_is_sealed_frozen_shared(duk_hthread *thr) { duk_hobject *h; duk_bool_t is_frozen; duk_uint_t mask; is_frozen = (duk_bool_t) duk_get_current_magic(thr); mask = duk_get_type_mask(thr, 0); if (mask & (DUK_TYPE_MASK_LIGHTFUNC | DUK_TYPE_MASK_BUFFER)) { DUK_ASSERT(is_frozen == 0 || is_frozen == 1); duk_push_boolean(thr, (mask & DUK_TYPE_MASK_LIGHTFUNC) ? 1 : /* lightfunc always frozen and sealed */ (is_frozen ^ 1)); /* buffer sealed but not frozen (index props writable) */ } else { /* ES2015 Sections 19.1.2.12, 19.1.2.13: anything other than an object * is considered to be already sealed and frozen. */ h = duk_get_hobject(thr, 0); duk_push_boolean(thr, (h == NULL) || duk_hobject_object_is_sealed_frozen_helper(thr, h, is_frozen /*is_frozen*/)); } return 1; } #endif /* DUK_USE_OBJECT_BUILTIN */ #if defined(DUK_USE_OBJECT_BUILTIN) DUK_INTERNAL duk_ret_t duk_bi_object_prototype_to_locale_string(duk_hthread *thr) { DUK_ASSERT_TOP(thr, 0); (void) duk_push_this_coercible_to_object(thr); duk_get_prop_stridx_short(thr, 0, DUK_STRIDX_TO_STRING); #if 0 /* This is mentioned explicitly in the E5.1 spec, but duk_call_method() checks for it in practice. */ duk_require_callable(thr, 1); #endif duk_dup_0(thr); /* -> [ O toString O ] */ duk_call_method(thr, 0); /* XXX: call method tail call? */ return 1; } #endif /* DUK_USE_OBJECT_BUILTIN */ #if defined(DUK_USE_OBJECT_BUILTIN) DUK_INTERNAL duk_ret_t duk_bi_object_prototype_value_of(duk_hthread *thr) { /* For lightfuncs and plain buffers, returns Object() coerced. */ (void) duk_push_this_coercible_to_object(thr); return 1; } #endif /* DUK_USE_OBJECT_BUILTIN */ #if defined(DUK_USE_OBJECT_BUILTIN) DUK_INTERNAL duk_ret_t duk_bi_object_prototype_is_prototype_of(duk_hthread *thr) { duk_hobject *h_v; duk_hobject *h_obj; DUK_ASSERT_TOP(thr, 1); h_v = duk_get_hobject(thr, 0); if (!h_v) { duk_push_false(thr); /* XXX: tail call: return duk_push_false(thr) */ return 1; } h_obj = duk_push_this_coercible_to_object(thr); DUK_ASSERT(h_obj != NULL); /* E5.1 Section 15.2.4.6, step 3.a, lookup proto once before compare. * Prototype loops should cause an error to be thrown. */ duk_push_boolean( thr, duk_hobject_prototype_chain_contains(thr, DUK_HOBJECT_GET_PROTOTYPE(thr->heap, h_v), h_obj, 0 /*ignore_loop*/)); return 1; } #endif /* DUK_USE_OBJECT_BUILTIN */ #if defined(DUK_USE_OBJECT_BUILTIN) DUK_INTERNAL duk_ret_t duk_bi_object_prototype_has_own_property(duk_hthread *thr) { return (duk_ret_t) duk_hobject_object_ownprop_helper(thr, 0 /*required_desc_flags*/); } #endif /* DUK_USE_OBJECT_BUILTIN */ #if defined(DUK_USE_OBJECT_BUILTIN) DUK_INTERNAL duk_ret_t duk_bi_object_prototype_property_is_enumerable(duk_hthread *thr) { return (duk_ret_t) duk_hobject_object_ownprop_helper(thr, DUK_PROPDESC_FLAG_ENUMERABLE /*required_desc_flags*/); } #endif /* DUK_USE_OBJECT_BUILTIN */ #if defined(DUK_USE_OBJECT_BUILTIN) || defined(DUK_USE_REFLECT_BUILTIN) /* Shared helper to implement Object.getPrototypeOf, * Object.prototype.__proto__ getter, and Reflect.getPrototypeOf. * * http://www.ecma-international.org/ecma-262/6.0/index.html#sec-get-object.prototype.__proto__ */ DUK_INTERNAL duk_ret_t duk_bi_object_getprototype_shared(duk_hthread *thr) { /* * magic = 0: __proto__ getter * magic = 1: Object.getPrototypeOf() * magic = 2: Reflect.getPrototypeOf() */ duk_hobject *h; duk_hobject *proto; duk_tval *tv; duk_int_t magic; magic = duk_get_current_magic(thr); if (magic == 0) { DUK_ASSERT_TOP(thr, 0); duk_push_this_coercible_to_object(thr); } DUK_ASSERT(duk_get_top(thr) >= 1); if (magic < 2) { /* ES2015 Section 19.1.2.9, step 1 */ duk_to_object(thr, 0); } tv = DUK_GET_TVAL_POSIDX(thr, 0); switch (DUK_TVAL_GET_TAG(tv)) { case DUK_TAG_BUFFER: proto = thr->builtins[DUK_BIDX_UINT8ARRAY_PROTOTYPE]; break; case DUK_TAG_LIGHTFUNC: proto = thr->builtins[DUK_BIDX_FUNCTION_PROTOTYPE]; break; case DUK_TAG_OBJECT: h = DUK_TVAL_GET_OBJECT(tv); proto = DUK_HOBJECT_GET_PROTOTYPE(thr->heap, h); break; default: /* This implicitly handles CheckObjectCoercible() caused * TypeError. */ DUK_DCERROR_TYPE_INVALID_ARGS(thr); } if (proto != NULL) { duk_push_hobject(thr, proto); } else { duk_push_null(thr); } return 1; } #endif /* DUK_USE_OBJECT_BUILTIN || DUK_USE_REFLECT_BUILTIN */ #if defined(DUK_USE_OBJECT_BUILTIN) || defined(DUK_USE_REFLECT_BUILTIN) /* Shared helper to implement ES2015 Object.setPrototypeOf, * Object.prototype.__proto__ setter, and Reflect.setPrototypeOf. * * http://www.ecma-international.org/ecma-262/6.0/index.html#sec-get-object.prototype.__proto__ * http://www.ecma-international.org/ecma-262/6.0/index.html#sec-object.setprototypeof */ DUK_INTERNAL duk_ret_t duk_bi_object_setprototype_shared(duk_hthread *thr) { /* * magic = 0: __proto__ setter * magic = 1: Object.setPrototypeOf() * magic = 2: Reflect.setPrototypeOf() */ duk_hobject *h_obj; duk_hobject *h_new_proto; duk_hobject *h_curr; duk_ret_t ret_success = 1; /* retval for success path */ duk_uint_t mask; duk_int_t magic; /* Preliminaries for __proto__ and setPrototypeOf (E6 19.1.2.18 steps 1-4). */ magic = duk_get_current_magic(thr); if (magic == 0) { duk_push_this_check_object_coercible(thr); duk_insert(thr, 0); if (!duk_check_type_mask(thr, 1, DUK_TYPE_MASK_NULL | DUK_TYPE_MASK_OBJECT)) { return 0; } /* __proto__ setter returns 'undefined' on success unlike the * setPrototypeOf() call which returns the target object. */ ret_success = 0; } else { if (magic == 1) { duk_require_object_coercible(thr, 0); } else { duk_require_hobject_accept_mask(thr, 0, DUK_TYPE_MASK_LIGHTFUNC | DUK_TYPE_MASK_BUFFER); } duk_require_type_mask(thr, 1, DUK_TYPE_MASK_NULL | DUK_TYPE_MASK_OBJECT); } h_new_proto = duk_get_hobject(thr, 1); /* h_new_proto may be NULL */ mask = duk_get_type_mask(thr, 0); if (mask & (DUK_TYPE_MASK_LIGHTFUNC | DUK_TYPE_MASK_BUFFER)) { duk_hobject *curr_proto; curr_proto = thr->builtins[(mask & DUK_TYPE_MASK_LIGHTFUNC) ? DUK_BIDX_FUNCTION_PROTOTYPE : DUK_BIDX_UINT8ARRAY_PROTOTYPE]; if (h_new_proto == curr_proto) { goto skip; } goto fail_nonextensible; } h_obj = duk_get_hobject(thr, 0); if (h_obj == NULL) { goto skip; } DUK_ASSERT(h_obj != NULL); /* [[SetPrototypeOf]] standard behavior, E6 9.1.2. */ /* TODO: implement Proxy object support here */ if (h_new_proto == DUK_HOBJECT_GET_PROTOTYPE(thr->heap, h_obj)) { goto skip; } if (!DUK_HOBJECT_HAS_EXTENSIBLE(h_obj)) { goto fail_nonextensible; } for (h_curr = h_new_proto; h_curr != NULL; h_curr = DUK_HOBJECT_GET_PROTOTYPE(thr->heap, h_curr)) { /* Loop prevention. */ if (h_curr == h_obj) { goto fail_loop; } } DUK_HOBJECT_SET_PROTOTYPE_UPDREF(thr, h_obj, h_new_proto); /* fall thru */ skip: duk_set_top(thr, 1); if (magic == 2) { duk_push_true(thr); } return ret_success; fail_nonextensible: fail_loop: if (magic != 2) { DUK_DCERROR_TYPE_INVALID_ARGS(thr); } else { duk_push_false(thr); return 1; } } #endif /* DUK_USE_OBJECT_BUILTIN || DUK_USE_REFLECT_BUILTIN */ #if defined(DUK_USE_OBJECT_BUILTIN) || defined(DUK_USE_REFLECT_BUILTIN) DUK_INTERNAL duk_ret_t duk_bi_object_constructor_define_property(duk_hthread *thr) { /* * magic = 0: Object.defineProperty() * magic = 1: Reflect.defineProperty() */ duk_hobject *obj; duk_hstring *key; duk_hobject *get; duk_hobject *set; duk_idx_t idx_value; duk_uint_t defprop_flags; duk_small_uint_t magic; duk_bool_t throw_flag; duk_bool_t ret; DUK_ASSERT(thr != NULL); DUK_DDD(DUK_DDDPRINT("Object.defineProperty(): ctx=%p obj=%!T key=%!T desc=%!T", (void *) thr, (duk_tval *) duk_get_tval(thr, 0), (duk_tval *) duk_get_tval(thr, 1), (duk_tval *) duk_get_tval(thr, 2))); /* [ obj key desc ] */ magic = (duk_small_uint_t) duk_get_current_magic(thr); /* Lightfuncs are currently supported by coercing to a temporary * Function object; changes will be allowed (the coerced value is * extensible) but will be lost. Same for plain buffers. */ obj = duk_require_hobject_promote_mask(thr, 0, DUK_TYPE_MASK_LIGHTFUNC | DUK_TYPE_MASK_BUFFER); DUK_ASSERT(obj != NULL); key = duk_to_property_key_hstring(thr, 1); (void) duk_require_hobject(thr, 2); DUK_ASSERT(obj != NULL); DUK_ASSERT(key != NULL); DUK_ASSERT(duk_get_hobject(thr, 2) != NULL); /* * Validate and convert argument property descriptor (an ECMAScript * object) into a set of defprop_flags and possibly property value, * getter, and/or setter values on the value stack. * * Lightfunc set/get values are coerced to full Functions. */ duk_hobject_prepare_property_descriptor(thr, 2 /*idx_desc*/, &defprop_flags, &idx_value, &get, &set); /* * Use Object.defineProperty() helper for the actual operation. */ DUK_ASSERT(magic == 0U || magic == 1U); throw_flag = magic ^ 1U; ret = duk_hobject_define_property_helper(thr, defprop_flags, obj, key, idx_value, get, set, throw_flag); /* Ignore the normalize/validate helper outputs on the value stack, * they're popped automatically. */ if (magic == 0U) { /* Object.defineProperty(): return target object. */ duk_push_hobject(thr, obj); } else { /* Reflect.defineProperty(): return success/fail. */ duk_push_boolean(thr, ret); } return 1; } #endif /* DUK_USE_OBJECT_BUILTIN || DUK_USE_REFLECT_BUILTIN */ #if defined(DUK_USE_OBJECT_BUILTIN) || defined(DUK_USE_REFLECT_BUILTIN) DUK_INTERNAL duk_ret_t duk_bi_object_constructor_get_own_property_descriptor(duk_hthread *thr) { DUK_ASSERT_TOP(thr, 2); /* ES2015 Section 19.1.2.6, step 1 */ if (duk_get_current_magic(thr) == 0) { duk_to_object(thr, 0); } /* [ obj key ] */ duk_hobject_object_get_own_property_descriptor(thr, -2); return 1; } #endif /* DUK_USE_OBJECT_BUILTIN || DUK_USE_REFLECT_BUILTIN */ #if defined(DUK_USE_OBJECT_BUILTIN) || defined(DUK_USE_REFLECT_BUILTIN) DUK_INTERNAL duk_ret_t duk_bi_object_constructor_is_extensible(duk_hthread *thr) { /* * magic = 0: Object.isExtensible() * magic = 1: Reflect.isExtensible() */ duk_hobject *h; if (duk_get_current_magic(thr) == 0) { h = duk_get_hobject(thr, 0); } else { /* Reflect.isExtensible(): throw if non-object, but we accept lightfuncs * and plain buffers here because they pretend to be objects. */ h = duk_require_hobject_accept_mask(thr, 0, DUK_TYPE_MASK_LIGHTFUNC | DUK_TYPE_MASK_BUFFER); } duk_push_boolean(thr, (h != NULL) && DUK_HOBJECT_HAS_EXTENSIBLE(h)); return 1; } #endif /* DUK_USE_OBJECT_BUILTIN || DUK_USE_REFLECT_BUILTIN */ #if defined(DUK_USE_OBJECT_BUILTIN) || defined(DUK_USE_REFLECT_BUILTIN) /* Shared helper for various key/symbol listings, magic: * 0=Object.keys() * 1=Object.getOwnPropertyNames(), * 2=Object.getOwnPropertySymbols(), * 3=Reflect.ownKeys() */ DUK_LOCAL const duk_small_uint_t duk__object_keys_enum_flags[4] = { /* Object.keys() */ DUK_ENUM_OWN_PROPERTIES_ONLY | DUK_ENUM_NO_PROXY_BEHAVIOR, /* Object.getOwnPropertyNames() */ DUK_ENUM_INCLUDE_NONENUMERABLE | DUK_ENUM_OWN_PROPERTIES_ONLY | DUK_ENUM_NO_PROXY_BEHAVIOR, /* Object.getOwnPropertySymbols() */ DUK_ENUM_INCLUDE_SYMBOLS | DUK_ENUM_OWN_PROPERTIES_ONLY | DUK_ENUM_EXCLUDE_STRINGS | DUK_ENUM_INCLUDE_NONENUMERABLE | DUK_ENUM_NO_PROXY_BEHAVIOR, /* Reflect.ownKeys() */ DUK_ENUM_INCLUDE_SYMBOLS | DUK_ENUM_OWN_PROPERTIES_ONLY | DUK_ENUM_INCLUDE_NONENUMERABLE | DUK_ENUM_NO_PROXY_BEHAVIOR }; DUK_INTERNAL duk_ret_t duk_bi_object_constructor_keys_shared(duk_hthread *thr) { duk_hobject *obj; #if defined(DUK_USE_ES6_PROXY) duk_hobject *h_proxy_target; duk_hobject *h_proxy_handler; duk_hobject *h_trap_result; #endif duk_small_uint_t enum_flags; duk_int_t magic; DUK_ASSERT_TOP(thr, 1); magic = duk_get_current_magic(thr); if (magic == 3) { /* ES2015 Section 26.1.11 requires a TypeError for non-objects. Lightfuncs * and plain buffers pretend to be objects, so accept those too. */ obj = duk_require_hobject_promote_mask(thr, 0, DUK_TYPE_MASK_LIGHTFUNC | DUK_TYPE_MASK_BUFFER); } else { /* ES2015: ToObject coerce. */ obj = duk_to_hobject(thr, 0); } DUK_ASSERT(obj != NULL); DUK_UNREF(obj); /* XXX: proxy chains */ #if defined(DUK_USE_ES6_PROXY) /* XXX: better sharing of code between proxy target call sites */ if (DUK_LIKELY(!duk_hobject_proxy_check(obj, &h_proxy_target, &h_proxy_handler))) { goto skip_proxy; } duk_push_hobject(thr, h_proxy_handler); if (!duk_get_prop_stridx_short(thr, -1, DUK_STRIDX_OWN_KEYS)) { /* Careful with reachability here: don't pop 'obj' before pushing * proxy target. */ DUK_DDD(DUK_DDDPRINT("no ownKeys trap, get keys of target instead")); duk_pop_2(thr); duk_push_hobject(thr, h_proxy_target); duk_replace(thr, 0); DUK_ASSERT_TOP(thr, 1); goto skip_proxy; } /* [ obj handler trap ] */ duk_insert(thr, -2); duk_push_hobject(thr, h_proxy_target); /* -> [ obj trap handler target ] */ duk_call_method(thr, 1 /*nargs*/); /* -> [ obj trap_result ] */ h_trap_result = duk_require_hobject(thr, -1); DUK_UNREF(h_trap_result); magic = duk_get_current_magic(thr); DUK_ASSERT(magic >= 0 && magic < (duk_int_t) (sizeof(duk__object_keys_enum_flags) / sizeof(duk_small_uint_t))); enum_flags = duk__object_keys_enum_flags[magic]; duk_proxy_ownkeys_postprocess(thr, h_proxy_target, enum_flags); return 1; skip_proxy: #endif /* DUK_USE_ES6_PROXY */ DUK_ASSERT_TOP(thr, 1); magic = duk_get_current_magic(thr); DUK_ASSERT(magic >= 0 && magic < (duk_int_t) (sizeof(duk__object_keys_enum_flags) / sizeof(duk_small_uint_t))); enum_flags = duk__object_keys_enum_flags[magic]; return duk_hobject_get_enumerated_keys(thr, enum_flags); } #endif /* DUK_USE_OBJECT_BUILTIN || DUK_USE_REFLECT_BUILTIN */ #if defined(DUK_USE_OBJECT_BUILTIN) || defined(DUK_USE_REFLECT_BUILTIN) DUK_INTERNAL duk_ret_t duk_bi_object_constructor_prevent_extensions(duk_hthread *thr) { /* * magic = 0: Object.preventExtensions() * magic = 1: Reflect.preventExtensions() */ duk_hobject *h; duk_uint_t mask; duk_int_t magic; magic = duk_get_current_magic(thr); /* Silent success for lightfuncs and plain buffers always. */ mask = DUK_TYPE_MASK_LIGHTFUNC | DUK_TYPE_MASK_BUFFER; /* Object.preventExtensions() silent success for non-object. */ if (magic == 0) { mask |= DUK_TYPE_MASK_UNDEFINED | DUK_TYPE_MASK_NULL | DUK_TYPE_MASK_BOOLEAN | DUK_TYPE_MASK_NUMBER | DUK_TYPE_MASK_STRING | DUK_TYPE_MASK_POINTER; } if (duk_check_type_mask(thr, 0, mask)) { /* Not an object, already non-extensible so always success. */ goto done; } h = duk_require_hobject(thr, 0); DUK_ASSERT(h != NULL); DUK_HOBJECT_CLEAR_EXTENSIBLE(h); /* A non-extensible object cannot gain any more properties, * so this is a good time to compact. */ duk_hobject_compact_props(thr, h); done: if (magic == 1) { duk_push_true(thr); } return 1; } #endif /* DUK_USE_OBJECT_BUILTIN || DUK_USE_REFLECT_BUILTIN */ /* * __defineGetter__, __defineSetter__, __lookupGetter__, __lookupSetter__ */ #if defined(DUK_USE_ES8) DUK_INTERNAL duk_ret_t duk_bi_object_prototype_defineaccessor(duk_hthread *thr) { duk_push_this(thr); duk_insert(thr, 0); duk_to_object(thr, 0); duk_require_callable(thr, 2); /* [ ToObject(this) key getter/setter ] */ /* ToPropertyKey() coercion is not needed, duk_def_prop() does it. */ duk_def_prop(thr, 0, DUK_DEFPROP_SET_ENUMERABLE | DUK_DEFPROP_SET_CONFIGURABLE | (duk_get_current_magic(thr) ? DUK_DEFPROP_HAVE_SETTER : DUK_DEFPROP_HAVE_GETTER)); return 0; } DUK_INTERNAL duk_ret_t duk_bi_object_prototype_lookupaccessor(duk_hthread *thr) { duk_uint_t sanity; duk_push_this(thr); duk_to_object(thr, -1); /* XXX: Prototype walk (with sanity) should be a core property * operation, could add a flag to e.g. duk_get_prop_desc(). */ /* ToPropertyKey() coercion is not needed, duk_get_prop_desc() does it. */ sanity = DUK_HOBJECT_PROTOTYPE_CHAIN_SANITY; while (!duk_is_undefined(thr, -1)) { /* [ key obj ] */ duk_dup(thr, 0); duk_get_prop_desc(thr, 1, 0 /*flags*/); if (!duk_is_undefined(thr, -1)) { duk_get_prop_stridx(thr, -1, (duk_get_current_magic(thr) != 0 ? DUK_STRIDX_SET : DUK_STRIDX_GET)); return 1; } duk_pop(thr); if (DUK_UNLIKELY(sanity-- == 0)) { DUK_ERROR_RANGE(thr, DUK_STR_PROTOTYPE_CHAIN_LIMIT); DUK_WO_NORETURN(return 0;); } duk_get_prototype(thr, -1); duk_remove(thr, -2); } return 1; } #endif /* DUK_USE_ES8 */ #line 1 "duk_bi_performance.c" /* * High resolution time API (performance.now() et al) * * API specification: https://encoding.spec.whatwg.org/#ap://www.w3.org/TR/hr-time/ */ /* #include duk_internal.h -> already included */ #if defined(DUK_USE_PERFORMANCE_BUILTIN) DUK_INTERNAL duk_ret_t duk_bi_performance_now(duk_hthread *thr) { /* From API spec: * The DOMHighResTimeStamp type is used to store a time value in * milliseconds, measured relative from the time origin, global * monotonic clock, or a time value that represents a duration * between two DOMHighResTimeStamp's. */ duk_push_number(thr, duk_time_get_monotonic_time(thr)); return 1; } #if 0 /* Missing until semantics decided. */ DUK_INTERNAL duk_ret_t duk_bi_performance_timeorigin_getter(duk_hthread *thr) { /* No decision yet how to handle timeOrigins, e.g. should one be * initialized per heap, or per global object set. See * https://www.w3.org/TR/hr-time/#time-origin. */ duk_push_uint(thr, 0); return 1; } #endif /* 0 */ #endif /* DUK_USE_PERFORMANCE_BUILTIN */ #line 1 "duk_bi_pointer.c" /* * Pointer built-ins */ /* #include duk_internal.h -> already included */ /* * Constructor */ DUK_INTERNAL duk_ret_t duk_bi_pointer_constructor(duk_hthread *thr) { /* XXX: this behavior is quite useless now; it would be nice to be able * to create pointer values from e.g. numbers or strings. Numbers are * problematic on 64-bit platforms though. Hex encoded strings? */ if (duk_get_top(thr) == 0) { duk_push_pointer(thr, NULL); } else { duk_to_pointer(thr, 0); } DUK_ASSERT(duk_is_pointer(thr, 0)); duk_set_top(thr, 1); if (duk_is_constructor_call(thr)) { (void) duk_push_object_helper(thr, DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_FLAG_FASTREFS | DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_POINTER), DUK_BIDX_POINTER_PROTOTYPE); /* Pointer object internal value is immutable. */ duk_dup_0(thr); duk_xdef_prop_stridx_short(thr, -2, DUK_STRIDX_INT_VALUE, DUK_PROPDESC_FLAGS_NONE); } /* Note: unbalanced stack on purpose */ return 1; } /* * toString(), valueOf() */ DUK_INTERNAL duk_ret_t duk_bi_pointer_prototype_tostring_shared(duk_hthread *thr) { duk_tval *tv; duk_small_int_t to_string = duk_get_current_magic(thr); duk_push_this(thr); tv = duk_require_tval(thr, -1); DUK_ASSERT(tv != NULL); if (DUK_TVAL_IS_POINTER(tv)) { /* nop */ } else if (DUK_TVAL_IS_OBJECT(tv)) { duk_hobject *h = DUK_TVAL_GET_OBJECT(tv); DUK_ASSERT(h != NULL); /* Must be a "pointer object", i.e. class "Pointer" */ if (DUK_HOBJECT_GET_CLASS_NUMBER(h) != DUK_HOBJECT_CLASS_POINTER) { goto type_error; } duk_xget_owndataprop_stridx_short(thr, -1, DUK_STRIDX_INT_VALUE); } else { goto type_error; } if (to_string) { duk_to_string(thr, -1); } return 1; type_error: DUK_DCERROR_TYPE_INVALID_ARGS(thr); } #line 1 "duk_bi_promise.c" /* * Promise built-in */ /* #include duk_internal.h -> already included */ #if defined(DUK_USE_PROMISE_BUILTIN) DUK_INTERNAL duk_ret_t duk_bi_promise_constructor(duk_hthread *thr) { DUK_ERROR_TYPE(thr, "unimplemented"); DUK_WO_NORETURN(return 0;); } DUK_INTERNAL duk_ret_t duk_bi_promise_all(duk_hthread *thr) { DUK_ERROR_TYPE(thr, "unimplemented"); DUK_WO_NORETURN(return 0;); } DUK_INTERNAL duk_ret_t duk_bi_promise_race(duk_hthread *thr) { DUK_ERROR_TYPE(thr, "unimplemented"); DUK_WO_NORETURN(return 0;); } DUK_INTERNAL duk_ret_t duk_bi_promise_reject(duk_hthread *thr) { DUK_ERROR_TYPE(thr, "unimplemented"); DUK_WO_NORETURN(return 0;); } DUK_INTERNAL duk_ret_t duk_bi_promise_resolve(duk_hthread *thr) { DUK_ERROR_TYPE(thr, "unimplemented"); DUK_WO_NORETURN(return 0;); } DUK_INTERNAL duk_ret_t duk_bi_promise_catch(duk_hthread *thr) { DUK_ERROR_TYPE(thr, "unimplemented"); DUK_WO_NORETURN(return 0;); } DUK_INTERNAL duk_ret_t duk_bi_promise_then(duk_hthread *thr) { DUK_ERROR_TYPE(thr, "unimplemented"); DUK_WO_NORETURN(return 0;); } #endif /* DUK_USE_PROMISE_BUILTIN */ #line 1 "duk_bi_proxy.c" /* * Proxy built-in (ES2015) */ /* #include duk_internal.h -> already included */ #if defined(DUK_USE_ES6_PROXY) /* Post-process a Proxy ownKeys() result at stack top. Push a cleaned up * array of valid result keys (strings or symbols). TypeError for invalid * values. Flags are shared with duk_enum(). */ DUK_INTERNAL void duk_proxy_ownkeys_postprocess(duk_hthread *thr, duk_hobject *h_proxy_target, duk_uint_t flags) { duk_uarridx_t i, len, idx; duk_propdesc desc; DUK_CTX_ASSERT_VALID(thr); DUK_ASSERT(h_proxy_target != NULL); len = (duk_uarridx_t) duk_get_length(thr, -1); idx = 0; duk_push_array(thr); /* XXX: preallocated dense array, fill in directly */ for (i = 0; i < len; i++) { duk_hstring *h; /* [ obj trap_result res_arr ] */ (void) duk_get_prop_index(thr, -2, i); h = duk_get_hstring(thr, -1); if (h == NULL) { DUK_ERROR_TYPE_INVALID_TRAP_RESULT(thr); DUK_WO_NORETURN(return;); } if (!(flags & DUK_ENUM_INCLUDE_NONENUMERABLE)) { /* No support for 'getOwnPropertyDescriptor' trap yet, * so check enumerability always from target object * descriptor. */ if (duk_hobject_get_own_propdesc(thr, h_proxy_target, duk_known_hstring(thr, -1), &desc, 0 /*flags*/)) { if ((desc.flags & DUK_PROPDESC_FLAG_ENUMERABLE) == 0) { DUK_DDD(DUK_DDDPRINT("ignore non-enumerable property: %!T", duk_get_tval(thr, -1))); goto skip_key; } } else { DUK_DDD(DUK_DDDPRINT("ignore non-existent property: %!T", duk_get_tval(thr, -1))); goto skip_key; } } if (DUK_UNLIKELY(DUK_HSTRING_HAS_SYMBOL(h))) { if (!(flags & DUK_ENUM_INCLUDE_SYMBOLS)) { DUK_DDD(DUK_DDDPRINT("ignore symbol property: %!T", duk_get_tval(thr, -1))); goto skip_key; } if (DUK_HSTRING_HAS_HIDDEN(h) && !(flags & DUK_ENUM_INCLUDE_HIDDEN)) { DUK_DDD(DUK_DDDPRINT("ignore hidden symbol property: %!T", duk_get_tval(thr, -1))); goto skip_key; } } else { if (flags & DUK_ENUM_EXCLUDE_STRINGS) { DUK_DDD(DUK_DDDPRINT("ignore string property: %!T", duk_get_tval(thr, -1))); goto skip_key; } } /* [ obj trap_result res_arr propname ] */ duk_push_uarridx(thr, idx++); duk_insert(thr, -2); duk_def_prop(thr, -3, DUK_DEFPROP_HAVE_VALUE | DUK_DEFPROP_SET_WEC); continue; skip_key: duk_pop(thr); continue; } /* XXX: Missing trap result validation for non-configurable target keys * (must be present), for non-extensible target all target keys must be * present and no extra keys can be present. * http://www.ecma-international.org/ecma-262/6.0/#sec-proxy-object-internal-methods-and-internal-slots-ownpropertykeys */ /* XXX: The key enumerability check should trigger the "getOwnPropertyDescriptor" * trap which has not yet been implemented. In the absence of such a trap, * the enumerability should be checked from the target object; this is * handled above. */ } #endif /* DUK_USE_ES6_PROXY */ #if defined(DUK_USE_ES6_PROXY) DUK_INTERNAL duk_ret_t duk_bi_proxy_constructor(duk_hthread *thr) { DUK_ASSERT_TOP(thr, 2); /* [ target handler ] */ duk_require_constructor_call(thr); duk_push_proxy(thr, 0 /*flags*/); /* [ target handler ] -> [ proxy ] */ return 1; /* replacement */ } #endif /* DUK_USE_ES6_PROXY */ #line 1 "duk_bi_reflect.c" /* * 'Reflect' built-in (ES2016 Section 26.1) * http://www.ecma-international.org/ecma-262/7.0/#sec-reflect-object * * Many Reflect built-in functions are provided by shared helpers in * duk_bi_object.c or duk_bi_function.c. */ /* #include duk_internal.h -> already included */ #if defined(DUK_USE_REFLECT_BUILTIN) DUK_INTERNAL duk_ret_t duk_bi_reflect_object_delete_property(duk_hthread *thr) { duk_tval *tv_obj; duk_tval *tv_key; duk_bool_t ret; DUK_ASSERT_TOP(thr, 2); (void) duk_require_hobject(thr, 0); (void) duk_to_string(thr, 1); /* [ target key ] */ DUK_ASSERT(thr != NULL); tv_obj = DUK_GET_TVAL_POSIDX(thr, 0); tv_key = DUK_GET_TVAL_POSIDX(thr, 1); ret = duk_hobject_delprop(thr, tv_obj, tv_key, 0 /*throw_flag*/); duk_push_boolean(thr, ret); return 1; } DUK_INTERNAL duk_ret_t duk_bi_reflect_object_get(duk_hthread *thr) { duk_tval *tv_obj; duk_tval *tv_key; duk_idx_t nargs; DUK_ASSERT(thr != NULL); nargs = duk_get_top_require_min(thr, 2 /*min_top*/); (void) duk_require_hobject(thr, 0); (void) duk_to_string(thr, 1); if (nargs >= 3 && !duk_strict_equals(thr, 0, 2)) { /* XXX: [[Get]] receiver currently unsupported */ DUK_ERROR_UNSUPPORTED(thr); DUK_WO_NORETURN(return 0;); } /* [ target key receiver? ...? ] */ tv_obj = DUK_GET_TVAL_POSIDX(thr, 0); tv_key = DUK_GET_TVAL_POSIDX(thr, 1); (void) duk_hobject_getprop(thr, tv_obj, tv_key); /* This could also be a duk_get_prop(). */ return 1; } DUK_INTERNAL duk_ret_t duk_bi_reflect_object_has(duk_hthread *thr) { duk_tval *tv_obj; duk_tval *tv_key; duk_bool_t ret; DUK_ASSERT(thr != NULL); DUK_ASSERT_TOP(thr, 2); (void) duk_require_hobject(thr, 0); (void) duk_to_string(thr, 1); /* [ target key ] */ tv_obj = DUK_GET_TVAL_POSIDX(thr, 0); tv_key = DUK_GET_TVAL_POSIDX(thr, 1); ret = duk_hobject_hasprop(thr, tv_obj, tv_key); duk_push_boolean(thr, ret); return 1; } DUK_INTERNAL duk_ret_t duk_bi_reflect_object_set(duk_hthread *thr) { duk_tval *tv_obj; duk_tval *tv_key; duk_tval *tv_val; duk_idx_t nargs; duk_bool_t ret; DUK_ASSERT(thr != NULL); nargs = duk_get_top_require_min(thr, 3 /*min_top*/); (void) duk_require_hobject(thr, 0); (void) duk_to_string(thr, 1); if (nargs >= 4 && !duk_strict_equals(thr, 0, 3)) { /* XXX: [[Set]] receiver currently unsupported */ DUK_ERROR_UNSUPPORTED(thr); DUK_WO_NORETURN(return 0;); } /* [ target key value receiver? ...? ] */ tv_obj = DUK_GET_TVAL_POSIDX(thr, 0); tv_key = DUK_GET_TVAL_POSIDX(thr, 1); tv_val = DUK_GET_TVAL_POSIDX(thr, 2); ret = duk_hobject_putprop(thr, tv_obj, tv_key, tv_val, 0 /*throw_flag*/); duk_push_boolean(thr, ret); return 1; } #endif /* DUK_USE_REFLECT_BUILTIN */ #line 1 "duk_bi_regexp.c" /* * RegExp built-ins */ /* #include duk_internal.h -> already included */ #if defined(DUK_USE_REGEXP_SUPPORT) DUK_LOCAL void duk__get_this_regexp(duk_hthread *thr) { duk_hobject *h; duk_push_this(thr); h = duk_require_hobject_with_class(thr, -1, DUK_HOBJECT_CLASS_REGEXP); DUK_ASSERT(h != NULL); DUK_UNREF(h); duk_insert(thr, 0); /* prepend regexp to valstack 0 index */ } /* XXX: much to improve (code size) */ DUK_INTERNAL duk_ret_t duk_bi_regexp_constructor(duk_hthread *thr) { duk_hobject *h_pattern; DUK_ASSERT_TOP(thr, 2); h_pattern = duk_get_hobject(thr, 0); if (!duk_is_constructor_call(thr) && h_pattern != NULL && DUK_HOBJECT_GET_CLASS_NUMBER(h_pattern) == DUK_HOBJECT_CLASS_REGEXP && duk_is_undefined(thr, 1)) { /* Called as a function, pattern has [[Class]] "RegExp" and * flags is undefined -> return object as is. */ /* XXX: ES2015 has a NewTarget SameValue() check which is not * yet implemented. */ duk_dup_0(thr); return 1; } /* Else functionality is identical for function call and constructor * call. */ if (h_pattern != NULL && DUK_HOBJECT_GET_CLASS_NUMBER(h_pattern) == DUK_HOBJECT_CLASS_REGEXP) { duk_get_prop_stridx_short(thr, 0, DUK_STRIDX_SOURCE); if (duk_is_undefined(thr, 1)) { /* In ES5 one would need to read the flags individually; * in ES2015 just read .flags. */ duk_get_prop_stridx(thr, 0, DUK_STRIDX_FLAGS); } else { /* In ES2015 allowed; overrides argument RegExp flags. */ duk_dup_1(thr); } } else { if (duk_is_undefined(thr, 0)) { duk_push_hstring_empty(thr); } else { duk_dup_0(thr); duk_to_string(thr, -1); /* Rejects Symbols. */ } if (duk_is_undefined(thr, 1)) { duk_push_hstring_empty(thr); } else { duk_dup_1(thr); duk_to_string(thr, -1); /* Rejects Symbols. */ } /* [ ... pattern flags ] */ } DUK_DDD(DUK_DDDPRINT("RegExp constructor/function call, pattern=%!T, flags=%!T", (duk_tval *) duk_get_tval(thr, -2), (duk_tval *) duk_get_tval(thr, -1))); /* [ ... pattern flags ] (both uncoerced) */ duk_to_string(thr, -2); duk_to_string(thr, -1); duk_regexp_compile(thr); /* [ ... bytecode escaped_source ] */ duk_regexp_create_instance(thr); /* [ ... RegExp ] */ return 1; } DUK_INTERNAL duk_ret_t duk_bi_regexp_prototype_exec(duk_hthread *thr) { duk__get_this_regexp(thr); /* [ regexp input ] */ duk_regexp_match(thr); /* [ result ] */ return 1; } DUK_INTERNAL duk_ret_t duk_bi_regexp_prototype_test(duk_hthread *thr) { duk__get_this_regexp(thr); /* [ regexp input ] */ /* result object is created and discarded; wasteful but saves code space */ duk_regexp_match(thr); /* [ result ] */ duk_push_boolean(thr, (duk_is_null(thr, -1) ? 0 : 1)); return 1; } DUK_INTERNAL duk_ret_t duk_bi_regexp_prototype_tostring(duk_hthread *thr) { /* This must be generic in ES2015 and later. */ DUK_ASSERT_TOP(thr, 0); duk_push_this(thr); duk_push_literal(thr, "/"); duk_get_prop_stridx(thr, 0, DUK_STRIDX_SOURCE); duk_dup_m2(thr); /* another "/" */ duk_get_prop_stridx(thr, 0, DUK_STRIDX_FLAGS); duk_concat(thr, 4); return 1; } DUK_INTERNAL duk_ret_t duk_bi_regexp_prototype_flags(duk_hthread *thr) { /* .flags is ES2015 but present even when ES2015 bindings are * disabled because the constructor relies on it. */ duk_uint8_t buf[8]; /* enough for all flags + NUL */ duk_uint8_t *p = buf; /* .flags is generic and works on any object. */ duk_push_this(thr); (void) duk_require_hobject(thr, -1); if (duk_get_prop_stridx_boolean(thr, 0, DUK_STRIDX_GLOBAL, NULL)) { *p++ = DUK_ASC_LC_G; } if (duk_get_prop_stridx_boolean(thr, 0, DUK_STRIDX_IGNORE_CASE, NULL)) { *p++ = DUK_ASC_LC_I; } if (duk_get_prop_stridx_boolean(thr, 0, DUK_STRIDX_MULTILINE, NULL)) { *p++ = DUK_ASC_LC_M; } /* .unicode: to be added */ /* .sticky: to be added */ *p++ = DUK_ASC_NUL; DUK_ASSERT((duk_size_t) (p - buf) <= sizeof(buf)); duk_push_string(thr, (const char *) buf); return 1; } /* Shared helper for providing .source, .global, .multiline, etc getters. */ DUK_INTERNAL duk_ret_t duk_bi_regexp_prototype_shared_getter(duk_hthread *thr) { duk_hstring *h_bc; duk_small_uint_t re_flags; duk_hobject *h; duk_int_t magic; DUK_ASSERT_TOP(thr, 0); duk_push_this(thr); h = duk_require_hobject(thr, -1); magic = duk_get_current_magic(thr); if (DUK_HOBJECT_GET_CLASS_NUMBER(h) == DUK_HOBJECT_CLASS_REGEXP) { duk_xget_owndataprop_stridx_short(thr, 0, DUK_STRIDX_INT_SOURCE); duk_xget_owndataprop_stridx_short(thr, 0, DUK_STRIDX_INT_BYTECODE); h_bc = duk_require_hstring(thr, -1); re_flags = (duk_small_uint_t) DUK_HSTRING_GET_DATA(h_bc)[0]; /* Safe even if h_bc length is 0 (= NUL) */ duk_pop(thr); } else if (h == thr->builtins[DUK_BIDX_REGEXP_PROTOTYPE]) { /* In ES2015 and ES2016 a TypeError would be thrown here. * However, this had real world issues so ES2017 draft * allows RegExp.prototype specifically, returning '(?:)' * for .source and undefined for all flags. */ if (magic != 16 /* .source */) { return 0; } duk_push_literal(thr, "(?:)"); /* .source handled by switch-case */ re_flags = 0; } else { DUK_DCERROR_TYPE_INVALID_ARGS(thr); } /* [ regexp source ] */ switch (magic) { case 0: { /* global */ duk_push_boolean(thr, (re_flags & DUK_RE_FLAG_GLOBAL)); break; } case 1: { /* ignoreCase */ duk_push_boolean(thr, (re_flags & DUK_RE_FLAG_IGNORE_CASE)); break; } case 2: { /* multiline */ duk_push_boolean(thr, (re_flags & DUK_RE_FLAG_MULTILINE)); break; } #if 0 /* Don't provide until implemented to avoid interfering with feature * detection in user code. */ case 3: /* sticky */ case 4: { /* unicode */ duk_push_false(thr); break; } #endif default: { /* source */ /* leave 'source' on top */ break; } } return 1; } #endif /* DUK_USE_REGEXP_SUPPORT */ #line 1 "duk_bi_string.c" /* * String built-ins * * Most String built-ins must only accept strings (or String objects). * Symbols, represented internally as strings, must be generally rejected. * The duk_push_this_coercible_to_string() helper does this automatically. */ /* XXX: There are several limitations in the current implementation for * strings with >= 0x80000000UL characters. In some cases one would need * to be able to represent the range [-0xffffffff,0xffffffff] and so on. * Generally character and byte length are assumed to fit into signed 32 * bits (< 0x80000000UL). Places with issues are not marked explicitly * below in all cases, look for signed type usage (duk_int_t etc) for * offsets/lengths. */ /* #include duk_internal.h -> already included */ #if defined(DUK_USE_STRING_BUILTIN) /* * Helpers */ DUK_LOCAL duk_hstring *duk__str_tostring_notregexp(duk_hthread *thr, duk_idx_t idx) { duk_hstring *h; if (duk_get_class_number(thr, idx) == DUK_HOBJECT_CLASS_REGEXP) { DUK_ERROR_TYPE_INVALID_ARGS(thr); DUK_WO_NORETURN(return NULL;); } h = duk_to_hstring(thr, idx); DUK_ASSERT(h != NULL); return h; } DUK_LOCAL duk_int_t duk__str_search_shared(duk_hthread *thr, duk_hstring *h_this, duk_hstring *h_search, duk_int_t start_cpos, duk_bool_t backwards) { duk_int_t cpos; duk_int_t bpos; const duk_uint8_t *p_start, *p_end, *p; const duk_uint8_t *q_start; duk_int_t q_blen; duk_uint8_t firstbyte; duk_uint8_t t; cpos = start_cpos; /* Empty searchstring always matches; cpos must be clamped here. * (If q_blen were < 0 due to clamped coercion, it would also be * caught here.) */ q_start = DUK_HSTRING_GET_DATA(h_search); q_blen = (duk_int_t) DUK_HSTRING_GET_BYTELEN(h_search); if (q_blen <= 0) { return cpos; } DUK_ASSERT(q_blen > 0); bpos = (duk_int_t) duk_heap_strcache_offset_char2byte(thr, h_this, (duk_uint32_t) cpos); p_start = DUK_HSTRING_GET_DATA(h_this); p_end = p_start + DUK_HSTRING_GET_BYTELEN(h_this); p = p_start + bpos; /* This loop is optimized for size. For speed, there should be * two separate loops, and we should ensure that memcmp() can be * used without an extra "will searchstring fit" check. Doing * the preconditioning for 'p' and 'p_end' is easy but cpos * must be updated if 'p' is wound back (backward scanning). */ firstbyte = q_start[0]; /* leading byte of match string */ while (p <= p_end && p >= p_start) { t = *p; /* For ECMAScript strings, this check can only match for * initial UTF-8 bytes (not continuation bytes). For other * strings all bets are off. */ if ((t == firstbyte) && ((duk_size_t) (p_end - p) >= (duk_size_t) q_blen)) { DUK_ASSERT(q_blen > 0); if (duk_memcmp((const void *) p, (const void *) q_start, (size_t) q_blen) == 0) { return cpos; } } /* track cpos while scanning */ if (backwards) { /* when going backwards, we decrement cpos 'early'; * 'p' may point to a continuation byte of the char * at offset 'cpos', but that's OK because we'll * backtrack all the way to the initial byte. */ if ((t & 0xc0) != 0x80) { cpos--; } p--; } else { if ((t & 0xc0) != 0x80) { cpos++; } p++; } } /* Not found. Empty string case is handled specially above. */ return -1; } /* * Constructor */ DUK_INTERNAL duk_ret_t duk_bi_string_constructor(duk_hthread *thr) { duk_hstring *h; duk_uint_t flags; /* String constructor needs to distinguish between an argument not given at all * vs. given as 'undefined'. We're a vararg function to handle this properly. */ /* XXX: copy current activation flags to thr, including current magic, * is_constructor_call etc. This takes a few bytes in duk_hthread but * makes call sites smaller (there are >30 is_constructor_call and get * current magic call sites. */ if (duk_get_top(thr) == 0) { duk_push_hstring_empty(thr); } else { h = duk_to_hstring_acceptsymbol(thr, 0); if (DUK_UNLIKELY(DUK_HSTRING_HAS_SYMBOL(h) && !duk_is_constructor_call(thr))) { duk_push_symbol_descriptive_string(thr, h); duk_replace(thr, 0); } } duk_to_string(thr, 0); /* catches symbol argument for constructor call */ DUK_ASSERT(duk_is_string(thr, 0)); duk_set_top(thr, 1); /* Top may be 1 or larger. */ if (duk_is_constructor_call(thr)) { /* String object internal value is immutable */ flags = DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_FLAG_FASTREFS | DUK_HOBJECT_FLAG_EXOTIC_STRINGOBJ | DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_STRING); duk_push_object_helper(thr, flags, DUK_BIDX_STRING_PROTOTYPE); duk_dup_0(thr); duk_xdef_prop_stridx_short(thr, -2, DUK_STRIDX_INT_VALUE, DUK_PROPDESC_FLAGS_NONE); } /* Note: unbalanced stack on purpose */ return 1; } DUK_LOCAL duk_ret_t duk__construct_from_codepoints(duk_hthread *thr, duk_bool_t nonbmp) { duk_bufwriter_ctx bw_alloc; duk_bufwriter_ctx *bw; duk_idx_t i, n; duk_ucodepoint_t cp; /* XXX: It would be nice to build the string directly but ToUint16() * coercion is needed so a generic helper would not be very * helpful (perhaps coerce the value stack first here and then * build a string from a duk_tval number sequence in one go?). */ n = duk_get_top(thr); bw = &bw_alloc; DUK_BW_INIT_PUSHBUF(thr, bw, (duk_size_t) n); /* initial estimate for ASCII only codepoints */ for (i = 0; i < n; i++) { /* XXX: could improve bufwriter handling to write multiple codepoints * with one ensure call but the relative benefit would be quite small. */ if (nonbmp) { /* ES2015 requires that (1) SameValue(cp, ToInteger(cp)) and * (2) cp >= 0 and cp <= 0x10ffff. This check does not * implement the steps exactly but the outcome should be * the same. */ duk_int32_t i32 = 0; if (!duk_is_whole_get_int32(duk_to_number(thr, i), &i32) || i32 < 0 || i32 > 0x10ffffL) { DUK_DCERROR_RANGE_INVALID_ARGS(thr); } DUK_ASSERT(i32 >= 0 && i32 <= 0x10ffffL); cp = (duk_ucodepoint_t) i32; DUK_BW_WRITE_ENSURE_CESU8(thr, bw, cp); } else { #if defined(DUK_USE_NONSTD_STRING_FROMCHARCODE_32BIT) /* ToUint16() coercion is mandatory in the E5.1 specification, but * this non-compliant behavior makes more sense because we support * non-BMP codepoints. Don't use CESU-8 because that'd create * surrogate pairs. */ cp = (duk_ucodepoint_t) duk_to_uint32(thr, i); DUK_BW_WRITE_ENSURE_XUTF8(thr, bw, cp); #else cp = (duk_ucodepoint_t) duk_to_uint16(thr, i); DUK_ASSERT(cp >= 0 && cp <= 0x10ffffL); DUK_BW_WRITE_ENSURE_CESU8(thr, bw, cp); #endif } } DUK_BW_COMPACT(thr, bw); (void) duk_buffer_to_string(thr, -1); /* Safe, extended UTF-8 or CESU-8 encoded. */ return 1; } DUK_INTERNAL duk_ret_t duk_bi_string_constructor_from_char_code(duk_hthread *thr) { return duk__construct_from_codepoints(thr, 0 /*nonbmp*/); } #if defined(DUK_USE_ES6) DUK_INTERNAL duk_ret_t duk_bi_string_constructor_from_code_point(duk_hthread *thr) { return duk__construct_from_codepoints(thr, 1 /*nonbmp*/); } #endif /* * toString(), valueOf() */ DUK_INTERNAL duk_ret_t duk_bi_string_prototype_to_string(duk_hthread *thr) { duk_tval *tv; duk_push_this(thr); tv = duk_require_tval(thr, -1); DUK_ASSERT(tv != NULL); if (DUK_TVAL_IS_STRING(tv)) { /* return as is */ } else if (DUK_TVAL_IS_OBJECT(tv)) { duk_hobject *h = DUK_TVAL_GET_OBJECT(tv); DUK_ASSERT(h != NULL); /* Must be a "string object", i.e. class "String" */ if (DUK_HOBJECT_GET_CLASS_NUMBER(h) != DUK_HOBJECT_CLASS_STRING) { goto type_error; } duk_xget_owndataprop_stridx_short(thr, -1, DUK_STRIDX_INT_VALUE); DUK_ASSERT(duk_is_string(thr, -1)); } else { goto type_error; } (void) duk_require_hstring_notsymbol(thr, -1); /* Reject symbols (and wrapped symbols). */ return 1; type_error: DUK_DCERROR_TYPE_INVALID_ARGS(thr); } /* * Character and charcode access */ DUK_INTERNAL duk_ret_t duk_bi_string_prototype_char_at(duk_hthread *thr) { duk_hstring *h; duk_int_t pos; /* XXX: faster implementation */ h = duk_push_this_coercible_to_string(thr); DUK_ASSERT(h != NULL); pos = duk_to_int(thr, 0); if (sizeof(duk_size_t) >= sizeof(duk_uint_t)) { /* Cast to duk_size_t works in this case: * - If pos < 0, (duk_size_t) pos will always be * >= max_charlen, and result will be the empty string * (see duk_substring()). * - If pos >= 0, pos + 1 cannot wrap. */ DUK_ASSERT((duk_size_t) DUK_INT_MIN >= DUK_HSTRING_MAX_BYTELEN); DUK_ASSERT((duk_size_t) DUK_INT_MAX + 1U > (duk_size_t) DUK_INT_MAX); duk_substring(thr, -1, (duk_size_t) pos, (duk_size_t) pos + 1U); } else { /* If size_t is smaller than int, explicit bounds checks * are needed because an int may wrap multiple times. */ if (DUK_UNLIKELY(pos < 0 || (duk_uint_t) pos >= (duk_uint_t) DUK_HSTRING_GET_CHARLEN(h))) { duk_push_hstring_empty(thr); } else { duk_substring(thr, -1, (duk_size_t) pos, (duk_size_t) pos + 1U); } } return 1; } /* Magic: 0=charCodeAt, 1=codePointAt */ DUK_INTERNAL duk_ret_t duk_bi_string_prototype_char_code_at(duk_hthread *thr) { duk_int_t pos; duk_hstring *h; duk_bool_t clamped; duk_uint32_t cp; duk_int_t magic; /* XXX: faster implementation */ DUK_DDD(DUK_DDDPRINT("arg=%!T", (duk_tval *) duk_get_tval(thr, 0))); h = duk_push_this_coercible_to_string(thr); DUK_ASSERT(h != NULL); pos = duk_to_int_clamped_raw(thr, 0 /*index*/, 0 /*min(incl)*/, (duk_int_t) DUK_HSTRING_GET_CHARLEN(h) - 1 /*max(incl)*/, &clamped /*out_clamped*/); #if defined(DUK_USE_ES6) magic = duk_get_current_magic(thr); #else DUK_ASSERT(duk_get_current_magic(thr) == 0); magic = 0; #endif if (clamped) { /* For out-of-bounds indices .charCodeAt() returns NaN and * .codePointAt() returns undefined. */ if (magic != 0) { return 0; } duk_push_nan(thr); } else { DUK_ASSERT(pos >= 0); cp = (duk_uint32_t) duk_hstring_char_code_at_raw(thr, h, (duk_uint_t) pos, (duk_bool_t) magic /*surrogate_aware*/); duk_push_u32(thr, cp); } return 1; } /* * substring(), substr(), slice() */ /* XXX: any chance of merging these three similar but still slightly * different algorithms so that footprint would be reduced? */ DUK_INTERNAL duk_ret_t duk_bi_string_prototype_substring(duk_hthread *thr) { duk_hstring *h; duk_int_t start_pos, end_pos; duk_int_t len; h = duk_push_this_coercible_to_string(thr); DUK_ASSERT(h != NULL); len = (duk_int_t) DUK_HSTRING_GET_CHARLEN(h); /* [ start end str ] */ start_pos = duk_to_int_clamped(thr, 0, 0, len); if (duk_is_undefined(thr, 1)) { end_pos = len; } else { end_pos = duk_to_int_clamped(thr, 1, 0, len); } DUK_ASSERT(start_pos >= 0 && start_pos <= len); DUK_ASSERT(end_pos >= 0 && end_pos <= len); if (start_pos > end_pos) { duk_int_t tmp = start_pos; start_pos = end_pos; end_pos = tmp; } DUK_ASSERT(end_pos >= start_pos); duk_substring(thr, -1, (duk_size_t) start_pos, (duk_size_t) end_pos); return 1; } #if defined(DUK_USE_SECTION_B) DUK_INTERNAL duk_ret_t duk_bi_string_prototype_substr(duk_hthread *thr) { duk_hstring *h; duk_int_t start_pos, end_pos; duk_int_t len; /* Unlike non-obsolete String calls, substr() algorithm in E5.1 * specification will happily coerce undefined and null to strings * ("undefined" and "null"). */ duk_push_this(thr); h = duk_to_hstring_m1(thr); /* Reject Symbols. */ DUK_ASSERT(h != NULL); len = (duk_int_t) DUK_HSTRING_GET_CHARLEN(h); /* [ start length str ] */ /* The implementation for computing of start_pos and end_pos differs * from the standard algorithm, but is intended to result in the exactly * same behavior. This is not always obvious. */ /* combines steps 2 and 5; -len ensures max() not needed for step 5 */ start_pos = duk_to_int_clamped(thr, 0, -len, len); if (start_pos < 0) { start_pos = len + start_pos; } DUK_ASSERT(start_pos >= 0 && start_pos <= len); /* combines steps 3, 6; step 7 is not needed */ if (duk_is_undefined(thr, 1)) { end_pos = len; } else { DUK_ASSERT(start_pos <= len); end_pos = start_pos + duk_to_int_clamped(thr, 1, 0, len - start_pos); } DUK_ASSERT(start_pos >= 0 && start_pos <= len); DUK_ASSERT(end_pos >= 0 && end_pos <= len); DUK_ASSERT(end_pos >= start_pos); duk_substring(thr, -1, (duk_size_t) start_pos, (duk_size_t) end_pos); return 1; } #endif /* DUK_USE_SECTION_B */ DUK_INTERNAL duk_ret_t duk_bi_string_prototype_slice(duk_hthread *thr) { duk_hstring *h; duk_int_t start_pos, end_pos; duk_int_t len; h = duk_push_this_coercible_to_string(thr); DUK_ASSERT(h != NULL); len = (duk_int_t) DUK_HSTRING_GET_CHARLEN(h); /* [ start end str ] */ start_pos = duk_to_int_clamped(thr, 0, -len, len); if (start_pos < 0) { start_pos = len + start_pos; } if (duk_is_undefined(thr, 1)) { end_pos = len; } else { end_pos = duk_to_int_clamped(thr, 1, -len, len); if (end_pos < 0) { end_pos = len + end_pos; } } DUK_ASSERT(start_pos >= 0 && start_pos <= len); DUK_ASSERT(end_pos >= 0 && end_pos <= len); if (end_pos < start_pos) { end_pos = start_pos; } DUK_ASSERT(end_pos >= start_pos); duk_substring(thr, -1, (duk_size_t) start_pos, (duk_size_t) end_pos); return 1; } /* * Case conversion */ DUK_INTERNAL duk_ret_t duk_bi_string_prototype_caseconv_shared(duk_hthread *thr) { duk_small_int_t uppercase = duk_get_current_magic(thr); (void) duk_push_this_coercible_to_string(thr); duk_unicode_case_convert_string(thr, (duk_bool_t) uppercase); return 1; } /* * indexOf() and lastIndexOf() */ DUK_INTERNAL duk_ret_t duk_bi_string_prototype_indexof_shared(duk_hthread *thr) { duk_hstring *h_this; duk_hstring *h_search; duk_int_t clen_this; duk_int_t cpos; duk_small_uint_t is_lastindexof = (duk_small_uint_t) duk_get_current_magic(thr); /* 0=indexOf, 1=lastIndexOf */ h_this = duk_push_this_coercible_to_string(thr); DUK_ASSERT(h_this != NULL); clen_this = (duk_int_t) DUK_HSTRING_GET_CHARLEN(h_this); h_search = duk_to_hstring(thr, 0); DUK_ASSERT(h_search != NULL); duk_to_number(thr, 1); if (duk_is_nan(thr, 1) && is_lastindexof) { /* indexOf: NaN should cause pos to be zero. * lastIndexOf: NaN should cause pos to be +Infinity * (and later be clamped to len). */ cpos = clen_this; } else { cpos = duk_to_int_clamped(thr, 1, 0, clen_this); } cpos = duk__str_search_shared(thr, h_this, h_search, cpos, is_lastindexof /*backwards*/); duk_push_int(thr, cpos); return 1; } /* * replace() */ /* XXX: the current implementation works but is quite clunky; it compiles * to almost 1,4kB of x86 code so it needs to be simplified (better approach, * shared helpers, etc). Some ideas for refactoring: * * - a primitive to convert a string into a regexp matcher (reduces matching * code at the cost of making matching much slower) * - use replace() as a basic helper for match() and split(), which are both * much simpler * - API call to get_prop and to_boolean */ DUK_INTERNAL duk_ret_t duk_bi_string_prototype_replace(duk_hthread *thr) { duk_hstring *h_input; duk_hstring *h_match; duk_hstring *h_search; duk_hobject *h_re; duk_bufwriter_ctx bw_alloc; duk_bufwriter_ctx *bw; #if defined(DUK_USE_REGEXP_SUPPORT) duk_bool_t is_regexp; duk_bool_t is_global; #endif duk_bool_t is_repl_func; duk_uint32_t match_start_coff, match_start_boff; #if defined(DUK_USE_REGEXP_SUPPORT) duk_int_t match_caps; #endif duk_uint32_t prev_match_end_boff; const duk_uint8_t *r_start, *r_end, *r; /* repl string scan */ duk_size_t tmp_sz; DUK_ASSERT_TOP(thr, 2); h_input = duk_push_this_coercible_to_string(thr); DUK_ASSERT(h_input != NULL); bw = &bw_alloc; DUK_BW_INIT_PUSHBUF(thr, bw, DUK_HSTRING_GET_BYTELEN(h_input)); /* input size is good output starting point */ DUK_ASSERT_TOP(thr, 4); /* stack[0] = search value * stack[1] = replace value * stack[2] = input string * stack[3] = result buffer */ h_re = duk_get_hobject_with_class(thr, 0, DUK_HOBJECT_CLASS_REGEXP); if (h_re) { #if defined(DUK_USE_REGEXP_SUPPORT) is_regexp = 1; is_global = duk_get_prop_stridx_boolean(thr, 0, DUK_STRIDX_GLOBAL, NULL); if (is_global) { /* start match from beginning */ duk_push_int(thr, 0); duk_put_prop_stridx_short(thr, 0, DUK_STRIDX_LAST_INDEX); } #else /* DUK_USE_REGEXP_SUPPORT */ DUK_DCERROR_UNSUPPORTED(thr); #endif /* DUK_USE_REGEXP_SUPPORT */ } else { duk_to_string(thr, 0); /* rejects symbols */ #if defined(DUK_USE_REGEXP_SUPPORT) is_regexp = 0; is_global = 0; #endif } if (duk_is_function(thr, 1)) { is_repl_func = 1; r_start = NULL; r_end = NULL; } else { duk_hstring *h_repl; is_repl_func = 0; h_repl = duk_to_hstring(thr, 1); /* reject symbols */ DUK_ASSERT(h_repl != NULL); r_start = DUK_HSTRING_GET_DATA(h_repl); r_end = r_start + DUK_HSTRING_GET_BYTELEN(h_repl); } prev_match_end_boff = 0; for (;;) { /* * If matching with a regexp: * - non-global RegExp: lastIndex not touched on a match, zeroed * on a non-match * - global RegExp: on match, lastIndex will be updated by regexp * executor to point to next char after the matching part (so that * characters in the matching part are not matched again) * * If matching with a string: * - always non-global match, find first occurrence * * We need: * - The character offset of start-of-match for the replacer function * - The byte offsets for start-of-match and end-of-match to implement * the replacement values $&, $`, and $', and to copy non-matching * input string portions (including header and trailer) verbatim. * * NOTE: the E5.1 specification is a bit vague how the RegExp should * behave in the replacement process; e.g. is matching done first for * all matches (in the global RegExp case) before any replacer calls * are made? See: test-bi-string-proto-replace.js for discussion. */ DUK_ASSERT_TOP(thr, 4); #if defined(DUK_USE_REGEXP_SUPPORT) if (is_regexp) { duk_dup_0(thr); duk_dup_2(thr); duk_regexp_match(thr); /* [ ... regexp input ] -> [ res_obj ] */ if (!duk_is_object(thr, -1)) { duk_pop(thr); break; } duk_get_prop_stridx_short(thr, -1, DUK_STRIDX_INDEX); DUK_ASSERT(duk_is_number(thr, -1)); match_start_coff = duk_get_uint(thr, -1); duk_pop(thr); duk_get_prop_index(thr, -1, 0); DUK_ASSERT(duk_is_string(thr, -1)); h_match = duk_known_hstring(thr, -1); duk_pop(thr); /* h_match is borrowed, remains reachable through match_obj */ if (DUK_HSTRING_GET_BYTELEN(h_match) == 0) { /* This should be equivalent to match() algorithm step 8.f.iii.2: * detect an empty match and allow it, but don't allow it twice. */ duk_uint32_t last_index; duk_get_prop_stridx_short(thr, 0, DUK_STRIDX_LAST_INDEX); last_index = (duk_uint32_t) duk_get_uint(thr, -1); DUK_DDD(DUK_DDDPRINT("empty match, bump lastIndex: %ld -> %ld", (long) last_index, (long) (last_index + 1))); duk_pop(thr); duk_push_uint(thr, (duk_uint_t) (last_index + 1)); duk_put_prop_stridx_short(thr, 0, DUK_STRIDX_LAST_INDEX); } DUK_ASSERT(duk_get_length(thr, -1) <= DUK_INT_MAX); /* string limits */ match_caps = (duk_int_t) duk_get_length(thr, -1); } else { #else /* DUK_USE_REGEXP_SUPPORT */ { /* unconditionally */ #endif /* DUK_USE_REGEXP_SUPPORT */ const duk_uint8_t *p_start, *p_end, *p; /* input string scan */ const duk_uint8_t *q_start; /* match string */ duk_size_t p_blen; duk_size_t q_blen; #if defined(DUK_USE_REGEXP_SUPPORT) DUK_ASSERT(!is_global); /* single match always */ #endif p_start = DUK_HSTRING_GET_DATA(h_input); p_end = p_start + DUK_HSTRING_GET_BYTELEN(h_input); p_blen = (duk_size_t) DUK_HSTRING_GET_BYTELEN(h_input); p = p_start; h_search = duk_known_hstring(thr, 0); q_start = DUK_HSTRING_GET_DATA(h_search); q_blen = (duk_size_t) DUK_HSTRING_GET_BYTELEN(h_search); if (q_blen > p_blen) { break; /* no match */ } p_end -= q_blen; /* ensure full memcmp() fits in while */ DUK_ASSERT(p_end >= p); match_start_coff = 0; while (p <= p_end) { DUK_ASSERT(p + q_blen <= DUK_HSTRING_GET_DATA(h_input) + DUK_HSTRING_GET_BYTELEN(h_input)); if (duk_memcmp((const void *) p, (const void *) q_start, (size_t) q_blen) == 0) { duk_dup_0(thr); h_match = duk_known_hstring(thr, -1); #if defined(DUK_USE_REGEXP_SUPPORT) match_caps = 0; #endif goto found; } /* track utf-8 non-continuation bytes */ if ((p[0] & 0xc0) != 0x80) { match_start_coff++; } p++; } /* not found */ break; } found: /* stack[0] = search value * stack[1] = replace value * stack[2] = input string * stack[3] = result buffer * stack[4] = regexp match OR match string */ match_start_boff = (duk_uint32_t) duk_heap_strcache_offset_char2byte(thr, h_input, match_start_coff); tmp_sz = (duk_size_t) (match_start_boff - prev_match_end_boff); DUK_BW_WRITE_ENSURE_BYTES(thr, bw, DUK_HSTRING_GET_DATA(h_input) + prev_match_end_boff, tmp_sz); prev_match_end_boff = match_start_boff + DUK_HSTRING_GET_BYTELEN(h_match); if (is_repl_func) { duk_idx_t idx_args; duk_hstring *h_repl; /* regexp res_obj is at index 4 */ duk_dup_1(thr); idx_args = duk_get_top(thr); #if defined(DUK_USE_REGEXP_SUPPORT) if (is_regexp) { duk_int_t idx; duk_require_stack(thr, match_caps + 2); for (idx = 0; idx < match_caps; idx++) { /* match followed by capture(s) */ duk_get_prop_index(thr, 4, (duk_uarridx_t) idx); } } else { #else /* DUK_USE_REGEXP_SUPPORT */ { /* unconditionally */ #endif /* DUK_USE_REGEXP_SUPPORT */ /* match == search string, by definition */ duk_dup_0(thr); } duk_push_uint(thr, (duk_uint_t) match_start_coff); duk_dup_2(thr); /* [ ... replacer match [captures] match_char_offset input ] */ duk_call(thr, duk_get_top(thr) - idx_args); h_repl = duk_to_hstring_m1(thr); /* -> [ ... repl_value ] */ DUK_ASSERT(h_repl != NULL); DUK_BW_WRITE_ENSURE_HSTRING(thr, bw, h_repl); duk_pop(thr); /* repl_value */ } else { r = r_start; while (r < r_end) { duk_int_t ch1; duk_int_t ch2; #if defined(DUK_USE_REGEXP_SUPPORT) duk_int_t ch3; #endif duk_size_t left; ch1 = *r++; if (ch1 != DUK_ASC_DOLLAR) { goto repl_write; } DUK_ASSERT(r <= r_end); left = (duk_size_t) (r_end - r); if (left <= 0) { goto repl_write; } ch2 = r[0]; switch (ch2) { case DUK_ASC_DOLLAR: { ch1 = (1 << 8) + DUK_ASC_DOLLAR; goto repl_write; } case DUK_ASC_AMP: { DUK_BW_WRITE_ENSURE_HSTRING(thr, bw, h_match); r++; continue; } case DUK_ASC_GRAVE: { tmp_sz = (duk_size_t) match_start_boff; DUK_BW_WRITE_ENSURE_BYTES(thr, bw, DUK_HSTRING_GET_DATA(h_input), tmp_sz); r++; continue; } case DUK_ASC_SINGLEQUOTE: { duk_uint32_t match_end_boff; /* Use match charlen instead of bytelen, just in case the input and * match codepoint encodings would have different lengths. */ /* XXX: charlen computed here, and also in char2byte helper. */ match_end_boff = (duk_uint32_t) duk_heap_strcache_offset_char2byte( thr, h_input, match_start_coff + (duk_uint_fast32_t) DUK_HSTRING_GET_CHARLEN(h_match)); tmp_sz = (duk_size_t) (DUK_HSTRING_GET_BYTELEN(h_input) - match_end_boff); DUK_BW_WRITE_ENSURE_BYTES(thr, bw, DUK_HSTRING_GET_DATA(h_input) + match_end_boff, tmp_sz); r++; continue; } default: { #if defined(DUK_USE_REGEXP_SUPPORT) duk_int_t capnum, captmp, capadv; /* XXX: optional check, match_caps is zero if no regexp, * so dollar will be interpreted literally anyway. */ if (!is_regexp) { goto repl_write; } if (!(ch2 >= DUK_ASC_0 && ch2 <= DUK_ASC_9)) { goto repl_write; } capnum = ch2 - DUK_ASC_0; capadv = 1; if (left >= 2) { ch3 = r[1]; if (ch3 >= DUK_ASC_0 && ch3 <= DUK_ASC_9) { captmp = capnum * 10 + (ch3 - DUK_ASC_0); if (captmp < match_caps) { capnum = captmp; capadv = 2; } } } if (capnum > 0 && capnum < match_caps) { DUK_ASSERT(is_regexp != 0); /* match_caps == 0 without regexps */ /* regexp res_obj is at offset 4 */ duk_get_prop_index(thr, 4, (duk_uarridx_t) capnum); if (duk_is_string(thr, -1)) { duk_hstring *h_tmp_str; h_tmp_str = duk_known_hstring(thr, -1); DUK_BW_WRITE_ENSURE_HSTRING(thr, bw, h_tmp_str); } else { /* undefined -> skip (replaced with empty) */ } duk_pop(thr); r += capadv; continue; } else { goto repl_write; } #else /* DUK_USE_REGEXP_SUPPORT */ goto repl_write; /* unconditionally */ #endif /* DUK_USE_REGEXP_SUPPORT */ } /* default case */ } /* switch (ch2) */ repl_write: /* ch1 = (r_increment << 8) + byte */ DUK_BW_WRITE_ENSURE_U8(thr, bw, (duk_uint8_t) (ch1 & 0xff)); r += ch1 >> 8; } /* while repl */ } /* if (is_repl_func) */ duk_pop(thr); /* pop regexp res_obj or match string */ #if defined(DUK_USE_REGEXP_SUPPORT) if (!is_global) { #else { /* unconditionally; is_global==0 */ #endif break; } } /* trailer */ tmp_sz = (duk_size_t) (DUK_HSTRING_GET_BYTELEN(h_input) - prev_match_end_boff); DUK_BW_WRITE_ENSURE_BYTES(thr, bw, DUK_HSTRING_GET_DATA(h_input) + prev_match_end_boff, tmp_sz); DUK_ASSERT_TOP(thr, 4); DUK_BW_COMPACT(thr, bw); (void) duk_buffer_to_string(thr, -1); /* Safe if inputs are safe. */ return 1; } /* * split() */ /* XXX: very messy now, but works; clean up, remove unused variables (nomimally * used so compiler doesn't complain). */ DUK_INTERNAL duk_ret_t duk_bi_string_prototype_split(duk_hthread *thr) { duk_hstring *h_input; duk_hstring *h_sep; duk_uint32_t limit; duk_uint32_t arr_idx; #if defined(DUK_USE_REGEXP_SUPPORT) duk_bool_t is_regexp; #endif duk_bool_t matched; /* set to 1 if any match exists (needed for empty input special case) */ duk_uint32_t prev_match_end_coff, prev_match_end_boff; duk_uint32_t match_start_boff, match_start_coff; duk_uint32_t match_end_boff, match_end_coff; h_input = duk_push_this_coercible_to_string(thr); DUK_ASSERT(h_input != NULL); duk_push_array(thr); if (duk_is_undefined(thr, 1)) { limit = 0xffffffffUL; } else { limit = duk_to_uint32(thr, 1); } if (limit == 0) { return 1; } /* If the separator is a RegExp, make a "clone" of it. The specification * algorithm calls [[Match]] directly for specific indices; we emulate this * by tweaking lastIndex and using a "force global" variant of duk_regexp_match() * which will use global-style matching even when the RegExp itself is non-global. */ if (duk_is_undefined(thr, 0)) { /* The spec algorithm first does "R = ToString(separator)" before checking * whether separator is undefined. Since this is side effect free, we can * skip the ToString() here. */ duk_dup_2(thr); duk_put_prop_index(thr, 3, 0); return 1; } else if (duk_get_hobject_with_class(thr, 0, DUK_HOBJECT_CLASS_REGEXP) != NULL) { #if defined(DUK_USE_REGEXP_SUPPORT) duk_push_hobject_bidx(thr, DUK_BIDX_REGEXP_CONSTRUCTOR); duk_dup_0(thr); duk_new(thr, 1); /* [ ... RegExp val ] -> [ ... res ] */ duk_replace(thr, 0); /* lastIndex is initialized to zero by new RegExp() */ is_regexp = 1; #else DUK_DCERROR_UNSUPPORTED(thr); #endif } else { duk_to_string(thr, 0); #if defined(DUK_USE_REGEXP_SUPPORT) is_regexp = 0; #endif } /* stack[0] = separator (string or regexp) * stack[1] = limit * stack[2] = input string * stack[3] = result array */ prev_match_end_boff = 0; prev_match_end_coff = 0; arr_idx = 0; matched = 0; for (;;) { /* * The specification uses RegExp [[Match]] to attempt match at specific * offsets. We don't have such a primitive, so we use an actual RegExp * and tweak lastIndex. Since the RegExp may be non-global, we use a * special variant which forces global-like behavior for matching. */ DUK_ASSERT_TOP(thr, 4); #if defined(DUK_USE_REGEXP_SUPPORT) if (is_regexp) { duk_dup_0(thr); duk_dup_2(thr); duk_regexp_match_force_global(thr); /* [ ... regexp input ] -> [ res_obj ] */ if (!duk_is_object(thr, -1)) { duk_pop(thr); break; } matched = 1; duk_get_prop_stridx_short(thr, -1, DUK_STRIDX_INDEX); DUK_ASSERT(duk_is_number(thr, -1)); match_start_coff = duk_get_uint(thr, -1); match_start_boff = (duk_uint32_t) duk_heap_strcache_offset_char2byte(thr, h_input, match_start_coff); duk_pop(thr); if (match_start_coff == DUK_HSTRING_GET_CHARLEN(h_input)) { /* don't allow an empty match at the end of the string */ duk_pop(thr); break; } duk_get_prop_stridx_short(thr, 0, DUK_STRIDX_LAST_INDEX); DUK_ASSERT(duk_is_number(thr, -1)); match_end_coff = duk_get_uint(thr, -1); match_end_boff = (duk_uint32_t) duk_heap_strcache_offset_char2byte(thr, h_input, match_end_coff); duk_pop(thr); /* empty match -> bump and continue */ if (prev_match_end_boff == match_end_boff) { duk_push_uint(thr, (duk_uint_t) (match_end_coff + 1)); duk_put_prop_stridx_short(thr, 0, DUK_STRIDX_LAST_INDEX); duk_pop(thr); continue; } } else { #else /* DUK_USE_REGEXP_SUPPORT */ { /* unconditionally */ #endif /* DUK_USE_REGEXP_SUPPORT */ const duk_uint8_t *p_start, *p_end, *p; /* input string scan */ const duk_uint8_t *q_start; /* match string */ duk_size_t q_blen, q_clen; p_start = DUK_HSTRING_GET_DATA(h_input); p_end = p_start + DUK_HSTRING_GET_BYTELEN(h_input); p = p_start + prev_match_end_boff; h_sep = duk_known_hstring(thr, 0); /* symbol already rejected above */ q_start = DUK_HSTRING_GET_DATA(h_sep); q_blen = (duk_size_t) DUK_HSTRING_GET_BYTELEN(h_sep); q_clen = (duk_size_t) DUK_HSTRING_GET_CHARLEN(h_sep); p_end -= q_blen; /* ensure full memcmp() fits in while */ match_start_coff = prev_match_end_coff; if (q_blen == 0) { /* Handle empty separator case: it will always match, and always * triggers the check in step 13.c.iii initially. Note that we * must skip to either end of string or start of first codepoint, * skipping over any continuation bytes! * * Don't allow an empty string to match at the end of the input. */ matched = 1; /* empty separator can always match */ match_start_coff++; p++; while (p < p_end) { if ((p[0] & 0xc0) != 0x80) { goto found; } p++; } goto not_found; } DUK_ASSERT(q_blen > 0 && q_clen > 0); while (p <= p_end) { DUK_ASSERT(p + q_blen <= DUK_HSTRING_GET_DATA(h_input) + DUK_HSTRING_GET_BYTELEN(h_input)); DUK_ASSERT(q_blen > 0); /* no issues with empty memcmp() */ if (duk_memcmp((const void *) p, (const void *) q_start, (size_t) q_blen) == 0) { /* never an empty match, so step 13.c.iii can't be triggered */ goto found; } /* track utf-8 non-continuation bytes */ if ((p[0] & 0xc0) != 0x80) { match_start_coff++; } p++; } not_found: /* not found */ break; found: matched = 1; match_start_boff = (duk_uint32_t) (p - p_start); match_end_coff = (duk_uint32_t) (match_start_coff + q_clen); /* constrained by string length */ match_end_boff = (duk_uint32_t) (match_start_boff + q_blen); /* ditto */ /* empty match (may happen with empty separator) -> bump and continue */ if (prev_match_end_boff == match_end_boff) { prev_match_end_boff++; prev_match_end_coff++; continue; } } /* if (is_regexp) */ /* stack[0] = separator (string or regexp) * stack[1] = limit * stack[2] = input string * stack[3] = result array * stack[4] = regexp res_obj (if is_regexp) */ DUK_DDD(DUK_DDDPRINT("split; match_start b=%ld,c=%ld, match_end b=%ld,c=%ld, prev_end b=%ld,c=%ld", (long) match_start_boff, (long) match_start_coff, (long) match_end_boff, (long) match_end_coff, (long) prev_match_end_boff, (long) prev_match_end_coff)); duk_push_lstring(thr, (const char *) (DUK_HSTRING_GET_DATA(h_input) + prev_match_end_boff), (duk_size_t) (match_start_boff - prev_match_end_boff)); duk_put_prop_index(thr, 3, arr_idx); arr_idx++; if (arr_idx >= limit) { goto hit_limit; } #if defined(DUK_USE_REGEXP_SUPPORT) if (is_regexp) { duk_size_t i, len; len = duk_get_length(thr, 4); for (i = 1; i < len; i++) { DUK_ASSERT(i <= DUK_UARRIDX_MAX); /* cannot have >4G captures */ duk_get_prop_index(thr, 4, (duk_uarridx_t) i); duk_put_prop_index(thr, 3, arr_idx); arr_idx++; if (arr_idx >= limit) { goto hit_limit; } } duk_pop(thr); /* lastIndex already set up for next match */ } else { #else /* DUK_USE_REGEXP_SUPPORT */ { /* unconditionally */ #endif /* DUK_USE_REGEXP_SUPPORT */ /* no action */ } prev_match_end_boff = match_end_boff; prev_match_end_coff = match_end_coff; continue; } /* for */ /* Combined step 11 (empty string special case) and 14-15. */ DUK_DDD(DUK_DDDPRINT("split trailer; prev_end b=%ld,c=%ld", (long) prev_match_end_boff, (long) prev_match_end_coff)); if (DUK_HSTRING_GET_BYTELEN(h_input) > 0 || !matched) { /* Add trailer if: * a) non-empty input * b) empty input and no (zero size) match found (step 11) */ duk_push_lstring(thr, (const char *) DUK_HSTRING_GET_DATA(h_input) + prev_match_end_boff, (duk_size_t) (DUK_HSTRING_GET_BYTELEN(h_input) - prev_match_end_boff)); duk_put_prop_index(thr, 3, arr_idx); /* No arr_idx update or limit check */ } return 1; hit_limit: #if defined(DUK_USE_REGEXP_SUPPORT) if (is_regexp) { duk_pop(thr); } #endif return 1; } /* * Various */ #if defined(DUK_USE_REGEXP_SUPPORT) DUK_LOCAL void duk__to_regexp_helper(duk_hthread *thr, duk_idx_t idx, duk_bool_t force_new) { duk_hobject *h; /* Shared helper for match() steps 3-4, search() steps 3-4. */ DUK_ASSERT(idx >= 0); if (force_new) { goto do_new; } h = duk_get_hobject_with_class(thr, idx, DUK_HOBJECT_CLASS_REGEXP); if (!h) { goto do_new; } return; do_new: duk_push_hobject_bidx(thr, DUK_BIDX_REGEXP_CONSTRUCTOR); duk_dup(thr, idx); duk_new(thr, 1); /* [ ... RegExp val ] -> [ ... res ] */ duk_replace(thr, idx); } #endif /* DUK_USE_REGEXP_SUPPORT */ #if defined(DUK_USE_REGEXP_SUPPORT) DUK_INTERNAL duk_ret_t duk_bi_string_prototype_search(duk_hthread *thr) { /* Easiest way to implement the search required by the specification * is to do a RegExp test() with lastIndex forced to zero. To avoid * side effects on the argument, "clone" the RegExp if a RegExp was * given as input. * * The global flag of the RegExp should be ignored; setting lastIndex * to zero (which happens when "cloning" the RegExp) should have an * equivalent effect. */ DUK_ASSERT_TOP(thr, 1); (void) duk_push_this_coercible_to_string(thr); /* at index 1 */ duk__to_regexp_helper(thr, 0 /*index*/, 1 /*force_new*/); /* stack[0] = regexp * stack[1] = string */ /* Avoid using RegExp.prototype methods, as they're writable and * configurable and may have been changed. */ duk_dup_0(thr); duk_dup_1(thr); /* [ ... re_obj input ] */ duk_regexp_match(thr); /* -> [ ... res_obj ] */ if (!duk_is_object(thr, -1)) { duk_push_int(thr, -1); return 1; } duk_get_prop_stridx_short(thr, -1, DUK_STRIDX_INDEX); DUK_ASSERT(duk_is_number(thr, -1)); return 1; } #endif /* DUK_USE_REGEXP_SUPPORT */ #if defined(DUK_USE_REGEXP_SUPPORT) DUK_INTERNAL duk_ret_t duk_bi_string_prototype_match(duk_hthread *thr) { duk_bool_t global; duk_int_t prev_last_index; duk_int_t this_index; duk_int_t arr_idx; DUK_ASSERT_TOP(thr, 1); (void) duk_push_this_coercible_to_string(thr); duk__to_regexp_helper(thr, 0 /*index*/, 0 /*force_new*/); global = duk_get_prop_stridx_boolean(thr, 0, DUK_STRIDX_GLOBAL, NULL); DUK_ASSERT_TOP(thr, 2); /* stack[0] = regexp * stack[1] = string */ if (!global) { duk_regexp_match(thr); /* -> [ res_obj ] */ return 1; /* return 'res_obj' */ } /* Global case is more complex. */ /* [ regexp string ] */ duk_push_int(thr, 0); duk_put_prop_stridx_short(thr, 0, DUK_STRIDX_LAST_INDEX); duk_push_array(thr); /* [ regexp string res_arr ] */ prev_last_index = 0; arr_idx = 0; for (;;) { DUK_ASSERT_TOP(thr, 3); duk_dup_0(thr); duk_dup_1(thr); duk_regexp_match(thr); /* -> [ ... regexp string ] -> [ ... res_obj ] */ if (!duk_is_object(thr, -1)) { duk_pop(thr); break; } duk_get_prop_stridx_short(thr, 0, DUK_STRIDX_LAST_INDEX); DUK_ASSERT(duk_is_number(thr, -1)); this_index = duk_get_int(thr, -1); duk_pop(thr); if (this_index == prev_last_index) { this_index++; duk_push_int(thr, this_index); duk_put_prop_stridx_short(thr, 0, DUK_STRIDX_LAST_INDEX); } prev_last_index = this_index; duk_get_prop_index(thr, -1, 0); /* match string */ duk_put_prop_index(thr, 2, (duk_uarridx_t) arr_idx); arr_idx++; duk_pop(thr); /* res_obj */ } if (arr_idx == 0) { duk_push_null(thr); } return 1; /* return 'res_arr' or 'null' */ } #endif /* DUK_USE_REGEXP_SUPPORT */ DUK_INTERNAL duk_ret_t duk_bi_string_prototype_concat(duk_hthread *thr) { /* duk_concat() coerces arguments with ToString() in correct order */ (void) duk_push_this_coercible_to_string(thr); duk_insert(thr, 0); /* this is relatively expensive */ duk_concat(thr, duk_get_top(thr)); return 1; } DUK_INTERNAL duk_ret_t duk_bi_string_prototype_trim(duk_hthread *thr) { DUK_ASSERT_TOP(thr, 0); (void) duk_push_this_coercible_to_string(thr); duk_trim(thr, 0); DUK_ASSERT_TOP(thr, 1); return 1; } #if defined(DUK_USE_ES6) DUK_INTERNAL duk_ret_t duk_bi_string_prototype_repeat(duk_hthread *thr) { duk_hstring *h_input; duk_size_t input_blen; duk_size_t result_len; duk_int_t count_signed; duk_uint_t count; const duk_uint8_t *src; duk_uint8_t *buf; duk_uint8_t *p; duk_double_t d; #if !defined(DUK_USE_PREFER_SIZE) duk_size_t copy_size; duk_uint8_t *p_end; #endif DUK_ASSERT_TOP(thr, 1); h_input = duk_push_this_coercible_to_string(thr); DUK_ASSERT(h_input != NULL); input_blen = DUK_HSTRING_GET_BYTELEN(h_input); /* Count is ToNumber() coerced; +Infinity must be always rejected * (even if input string is zero length), as well as negative values * and -Infinity. -Infinity doesn't require an explicit check * because duk_get_int() clamps it to DUK_INT_MIN which gets rejected * as a negative value (regardless of input string length). */ d = duk_to_number(thr, 0); if (duk_double_is_posinf(d)) { goto fail_range; } count_signed = duk_get_int(thr, 0); if (count_signed < 0) { goto fail_range; } count = (duk_uint_t) count_signed; /* Overflow check for result length. */ result_len = count * input_blen; if (count != 0 && result_len / count != input_blen) { goto fail_range; } /* Temporary fixed buffer, later converted to string. */ buf = (duk_uint8_t *) duk_push_fixed_buffer_nozero(thr, result_len); DUK_ASSERT(buf != NULL); src = (const duk_uint8_t *) DUK_HSTRING_GET_DATA(h_input); DUK_ASSERT(src != NULL); #if defined(DUK_USE_PREFER_SIZE) p = buf; while (count-- > 0) { duk_memcpy((void *) p, (const void *) src, input_blen); /* copy size may be zero, but pointers are valid */ p += input_blen; } #else /* DUK_USE_PREFER_SIZE */ /* Take advantage of already copied pieces to speed up the process * especially for small repeated strings. */ p = buf; p_end = p + result_len; copy_size = input_blen; for (;;) { duk_size_t remain = (duk_size_t) (p_end - p); DUK_DDD(DUK_DDDPRINT("remain=%ld, copy_size=%ld, input_blen=%ld, result_len=%ld", (long) remain, (long) copy_size, (long) input_blen, (long) result_len)); if (remain <= copy_size) { /* If result_len is zero, this case is taken and does * a zero size copy (with valid pointers). */ duk_memcpy((void *) p, (const void *) src, remain); break; } else { duk_memcpy((void *) p, (const void *) src, copy_size); p += copy_size; } src = (const duk_uint8_t *) buf; /* Use buf as source for larger copies. */ copy_size = (duk_size_t) (p - buf); } #endif /* DUK_USE_PREFER_SIZE */ /* XXX: It would be useful to be able to create a duk_hstring with * a certain byte size whose data area wasn't initialized and which * wasn't in the string table yet. This would allow a string to be * constructed directly without a buffer temporary and when it was * finished, it could be injected into the string table. Currently * this isn't possible because duk_hstrings are only tracked by the * intern table (they are not in heap_allocated). */ duk_buffer_to_string(thr, -1); /* Safe if input is safe. */ return 1; fail_range: DUK_DCERROR_RANGE_INVALID_ARGS(thr); } #endif /* DUK_USE_ES6 */ DUK_INTERNAL duk_ret_t duk_bi_string_prototype_locale_compare(duk_hthread *thr) { duk_hstring *h1; duk_hstring *h2; duk_size_t h1_len, h2_len, prefix_len; duk_small_int_t ret = 0; duk_small_int_t rc; /* The current implementation of localeCompare() is simply a codepoint * by codepoint comparison, implemented with a simple string compare * because UTF-8 should preserve codepoint ordering (assuming valid * shortest UTF-8 encoding). * * The specification requires that the return value must be related * to the sort order: e.g. negative means that 'this' comes before * 'that' in sort order. We assume an ascending sort order. */ /* XXX: could share code with duk_js_ops.c, duk_js_compare_helper */ h1 = duk_push_this_coercible_to_string(thr); DUK_ASSERT(h1 != NULL); h2 = duk_to_hstring(thr, 0); DUK_ASSERT(h2 != NULL); h1_len = (duk_size_t) DUK_HSTRING_GET_BYTELEN(h1); h2_len = (duk_size_t) DUK_HSTRING_GET_BYTELEN(h2); prefix_len = (h1_len <= h2_len ? h1_len : h2_len); rc = (duk_small_int_t) duk_memcmp((const void *) DUK_HSTRING_GET_DATA(h1), (const void *) DUK_HSTRING_GET_DATA(h2), (size_t) prefix_len); if (rc < 0) { ret = -1; goto done; } else if (rc > 0) { ret = 1; goto done; } /* prefix matches, lengths matter now */ if (h1_len > h2_len) { ret = 1; goto done; } else if (h1_len == h2_len) { DUK_ASSERT(ret == 0); goto done; } ret = -1; goto done; done: duk_push_int(thr, (duk_int_t) ret); return 1; } #if defined(DUK_USE_ES6) DUK_INTERNAL duk_ret_t duk_bi_string_prototype_startswith_endswith(duk_hthread *thr) { duk_int_t magic; duk_hstring *h_target; duk_size_t blen_target; duk_hstring *h_search; duk_size_t blen_search; duk_int_t off; duk_bool_t result = 0; duk_size_t blen_left; /* Because string byte lengths are in [0,DUK_INT_MAX] it's safe to * subtract two string lengths without overflow. */ DUK_ASSERT(DUK_HSTRING_MAX_BYTELEN <= DUK_INT_MAX); h_target = duk_push_this_coercible_to_string(thr); DUK_ASSERT(h_target != NULL); h_search = duk__str_tostring_notregexp(thr, 0); DUK_ASSERT(h_search != NULL); magic = duk_get_current_magic(thr); /* Careful to avoid pointer overflows in the matching logic. */ blen_target = DUK_HSTRING_GET_BYTELEN(h_target); blen_search = DUK_HSTRING_GET_BYTELEN(h_search); #if 0 /* If search string is longer than the target string, we can * never match. Could check explicitly, but should be handled * correctly below. */ if (blen_search > blen_target) { goto finish; } #endif off = 0; if (duk_is_undefined(thr, 1)) { if (magic) { off = (duk_int_t) blen_target - (duk_int_t) blen_search; } else { DUK_ASSERT(off == 0); } } else { duk_int_t len; duk_int_t pos; DUK_ASSERT(DUK_HSTRING_MAX_BYTELEN <= DUK_INT_MAX); len = (duk_int_t) DUK_HSTRING_GET_CHARLEN(h_target); pos = duk_to_int_clamped(thr, 1, 0, len); DUK_ASSERT(pos >= 0 && pos <= len); off = (duk_int_t) duk_heap_strcache_offset_char2byte(thr, h_target, (duk_uint_fast32_t) pos); if (magic) { off -= (duk_int_t) blen_search; } } if (off < 0 || off > (duk_int_t) blen_target) { goto finish; } /* The main comparison can be done using a memcmp() rather than * doing codepoint comparisons: for CESU-8 strings there is a * canonical representation for every codepoint. But we do need * to deal with the char/byte offset translation to find the * comparison range. */ DUK_ASSERT(off >= 0); DUK_ASSERT((duk_size_t) off <= blen_target); blen_left = blen_target - (duk_size_t) off; if (blen_left >= blen_search) { const duk_uint8_t *p_cmp_start = (const duk_uint8_t *) DUK_HSTRING_GET_DATA(h_target) + off; const duk_uint8_t *p_search = (const duk_uint8_t *) DUK_HSTRING_GET_DATA(h_search); if (duk_memcmp_unsafe((const void *) p_cmp_start, (const void *) p_search, (size_t) blen_search) == 0) { result = 1; } } finish: duk_push_boolean(thr, result); return 1; } #endif /* DUK_USE_ES6 */ #if defined(DUK_USE_ES6) DUK_INTERNAL duk_ret_t duk_bi_string_prototype_includes(duk_hthread *thr) { duk_hstring *h; duk_hstring *h_search; duk_int_t len; duk_int_t pos; h = duk_push_this_coercible_to_string(thr); DUK_ASSERT(h != NULL); h_search = duk__str_tostring_notregexp(thr, 0); DUK_ASSERT(h_search != NULL); len = (duk_int_t) DUK_HSTRING_GET_CHARLEN(h); pos = duk_to_int_clamped(thr, 1, 0, len); DUK_ASSERT(pos >= 0 && pos <= len); pos = duk__str_search_shared(thr, h, h_search, pos, 0 /*backwards*/); duk_push_boolean(thr, pos >= 0); return 1; } #endif /* DUK_USE_ES6 */ #endif /* DUK_USE_STRING_BUILTIN */ #line 1 "duk_bi_symbol.c" /* * Symbol built-in */ /* #include duk_internal.h -> already included */ #if defined(DUK_USE_SYMBOL_BUILTIN) /* * Constructor */ DUK_INTERNAL duk_ret_t duk_bi_symbol_constructor_shared(duk_hthread *thr) { const duk_uint8_t *desc; duk_size_t len; duk_uint8_t *buf; duk_uint8_t *p; duk_int_t magic; magic = duk_get_current_magic(thr); if (duk_is_undefined(thr, 0) && (magic == 0)) { /* Symbol() accepts undefined and empty string, but they are * treated differently. */ desc = NULL; len = 0; } else { /* Symbol.for() coerces undefined to 'undefined' */ desc = (const duk_uint8_t *) duk_to_lstring(thr, 0, &len); } /* Maximum symbol data length: * +1 initial byte (0x80 or 0x81) * +len description * +1 0xff after description, before unique suffix * +17 autogenerated unique suffix: 'ffffffff-ffffffff' is longest * +1 0xff after unique suffix for symbols with undefined description */ buf = (duk_uint8_t *) duk_push_fixed_buffer(thr, 1 + len + 1 + 17 + 1); DUK_ASSERT(buf != NULL); p = buf + 1; DUK_ASSERT(desc != NULL || len == 0); /* may be NULL if len is 0 */ duk_memcpy_unsafe((void *) p, (const void *) desc, len); p += len; if (magic == 0) { /* Symbol(): create unique symbol. Use two 32-bit values * to avoid dependency on 64-bit types and 64-bit integer * formatting (at least for now). */ if (++thr->heap->sym_counter[0] == 0) { thr->heap->sym_counter[1]++; } p += DUK_SPRINTF((char *) p, "\xFF" "%lx-%lx", (unsigned long) thr->heap->sym_counter[1], (unsigned long) thr->heap->sym_counter[0]); if (desc == NULL) { /* Special case for 'undefined' description, trailing * 0xff distinguishes from empty string description, * but needs minimal special case handling elsewhere. */ *p++ = 0xff; } buf[0] = 0x81; } else { /* Symbol.for(): create a global symbol */ buf[0] = 0x80; } duk_push_lstring(thr, (const char *) buf, (duk_size_t) (p - buf)); DUK_DDD(DUK_DDDPRINT("created symbol: %!T", duk_get_tval(thr, -1))); return 1; } DUK_LOCAL duk_hstring *duk__auto_unbox_symbol(duk_hthread *thr, duk_tval *tv_arg) { duk_tval *tv; duk_hobject *h_obj; duk_hstring *h_str; DUK_ASSERT(tv_arg != NULL); /* XXX: add internal helper: duk_auto_unbox_tval(thr, tv, mask); */ /* XXX: add internal helper: duk_auto_unbox(thr, tv, idx); */ tv = tv_arg; if (DUK_TVAL_IS_OBJECT(tv)) { h_obj = DUK_TVAL_GET_OBJECT(tv); DUK_ASSERT(h_obj != NULL); if (DUK_HOBJECT_GET_CLASS_NUMBER(h_obj) == DUK_HOBJECT_CLASS_SYMBOL) { tv = duk_hobject_get_internal_value_tval_ptr(thr->heap, h_obj); if (tv == NULL) { return NULL; } } else { return NULL; } } if (!DUK_TVAL_IS_STRING(tv)) { return NULL; } h_str = DUK_TVAL_GET_STRING(tv); DUK_ASSERT(h_str != NULL); /* Here symbol is more expected than not. */ if (DUK_UNLIKELY(!DUK_HSTRING_HAS_SYMBOL(h_str))) { return NULL; } return h_str; } DUK_INTERNAL duk_ret_t duk_bi_symbol_tostring_shared(duk_hthread *thr) { duk_hstring *h_str; h_str = duk__auto_unbox_symbol(thr, DUK_HTHREAD_THIS_PTR(thr)); if (h_str == NULL) { return DUK_RET_TYPE_ERROR; } if (duk_get_current_magic(thr) == 0) { /* .toString() */ duk_push_symbol_descriptive_string(thr, h_str); } else { /* .valueOf() */ duk_push_hstring(thr, h_str); } return 1; } DUK_INTERNAL duk_ret_t duk_bi_symbol_key_for(duk_hthread *thr) { duk_hstring *h; const duk_uint8_t *p; /* Argument must be a symbol but not checked here. The initial byte * check will catch non-symbol strings. */ h = duk_require_hstring(thr, 0); DUK_ASSERT(h != NULL); p = (const duk_uint8_t *) DUK_HSTRING_GET_DATA(h); DUK_ASSERT(p != NULL); /* Even for zero length strings there's at least one NUL byte so * we can safely check the initial byte. */ if (p[0] == 0x80) { /* Global symbol, return its key (bytes just after the initial byte). */ duk_push_lstring(thr, (const char *) (p + 1), (duk_size_t) (DUK_HSTRING_GET_BYTELEN(h) - 1)); return 1; } else if (p[0] == 0x81 || p[0] == 0x82 || p[0] == 0xff) { /* Local symbol or hidden symbol, return undefined. */ return 0; } /* Covers normal strings and unknown initial bytes. */ return DUK_RET_TYPE_ERROR; } DUK_INTERNAL duk_ret_t duk_bi_symbol_toprimitive(duk_hthread *thr) { duk_hstring *h_str; h_str = duk__auto_unbox_symbol(thr, DUK_HTHREAD_THIS_PTR(thr)); if (h_str == NULL) { return DUK_RET_TYPE_ERROR; } duk_push_hstring(thr, h_str); return 1; } #endif /* DUK_USE_SYMBOL_BUILTIN */ #line 1 "duk_bi_thread.c" /* * Thread builtins */ /* #include duk_internal.h -> already included */ /* * Constructor */ #if defined(DUK_USE_COROUTINE_SUPPORT) DUK_INTERNAL duk_ret_t duk_bi_thread_constructor(duk_hthread *thr) { duk_hthread *new_thr; duk_hobject *func; /* Check that the argument is callable; this is not 100% because we * don't allow native functions to be a thread's initial function. * Resume will reject such functions in any case. */ /* XXX: need a duk_require_func_promote_lfunc() */ func = duk_require_hobject_promote_lfunc(thr, 0); DUK_ASSERT(func != NULL); duk_require_callable(thr, 0); duk_push_thread(thr); new_thr = (duk_hthread *) duk_known_hobject(thr, -1); new_thr->state = DUK_HTHREAD_STATE_INACTIVE; /* push initial function call to new thread stack; this is * picked up by resume(). */ duk_push_hobject(new_thr, func); return 1; /* return thread */ } #endif /* * Resume a thread. * * The thread must be in resumable state, either (a) new thread which hasn't * yet started, or (b) a thread which has previously yielded. This method * must be called from an ECMAScript function. * * Args: * - thread * - value * - isError (defaults to false) * * Note: yield and resume handling is currently asymmetric. */ #if defined(DUK_USE_COROUTINE_SUPPORT) DUK_INTERNAL duk_ret_t duk_bi_thread_resume(duk_hthread *ctx) { duk_hthread *thr = (duk_hthread *) ctx; duk_hthread *thr_resume; duk_hobject *caller_func; duk_small_uint_t is_error; DUK_DDD(DUK_DDDPRINT("Duktape.Thread.resume(): thread=%!T, value=%!T, is_error=%!T", (duk_tval *) duk_get_tval(thr, 0), (duk_tval *) duk_get_tval(thr, 1), (duk_tval *) duk_get_tval(thr, 2))); DUK_ASSERT(thr->state == DUK_HTHREAD_STATE_RUNNING); DUK_ASSERT(thr->heap->curr_thread == thr); thr_resume = duk_require_hthread(thr, 0); DUK_ASSERT(duk_get_top(thr) == 3); is_error = (duk_small_uint_t) duk_to_boolean_top_pop(thr); DUK_ASSERT(duk_get_top(thr) == 2); /* [ thread value ] */ /* * Thread state and calling context checks */ if (thr->callstack_top < 2) { DUK_DD(DUK_DDPRINT( "resume state invalid: callstack should contain at least 2 entries (caller and Duktape.Thread.resume)")); goto state_error; } DUK_ASSERT(thr->callstack_curr != NULL); DUK_ASSERT(thr->callstack_curr->parent != NULL); DUK_ASSERT(DUK_ACT_GET_FUNC(thr->callstack_curr) != NULL); /* us */ DUK_ASSERT(DUK_HOBJECT_IS_NATFUNC(DUK_ACT_GET_FUNC(thr->callstack_curr))); DUK_ASSERT(DUK_ACT_GET_FUNC(thr->callstack_curr->parent) != NULL); /* caller */ caller_func = DUK_ACT_GET_FUNC(thr->callstack_curr->parent); if (!DUK_HOBJECT_IS_COMPFUNC(caller_func)) { DUK_DD(DUK_DDPRINT("resume state invalid: caller must be ECMAScript code")); goto state_error; } /* Note: there is no requirement that: 'thr->callstack_preventcount == 1' * like for yield. */ if (thr_resume->state != DUK_HTHREAD_STATE_INACTIVE && thr_resume->state != DUK_HTHREAD_STATE_YIELDED) { DUK_DD(DUK_DDPRINT("resume state invalid: target thread must be INACTIVE or YIELDED")); goto state_error; } DUK_ASSERT(thr_resume->state == DUK_HTHREAD_STATE_INACTIVE || thr_resume->state == DUK_HTHREAD_STATE_YIELDED); /* Further state-dependent pre-checks */ if (thr_resume->state == DUK_HTHREAD_STATE_YIELDED) { /* no pre-checks now, assume a previous yield() has left things in * tip-top shape (longjmp handler will assert for these). */ } else { duk_hobject *h_fun; DUK_ASSERT(thr_resume->state == DUK_HTHREAD_STATE_INACTIVE); /* The initial function must be an ECMAScript function (but * can be bound). We must make sure of that before we longjmp * because an error in the RESUME handler call processing will * not be handled very cleanly. */ if ((thr_resume->callstack_top != 0) || (thr_resume->valstack_top - thr_resume->valstack != 1)) { goto state_error; } duk_push_tval(thr, DUK_GET_TVAL_NEGIDX(thr_resume, -1)); duk_resolve_nonbound_function(thr); h_fun = duk_require_hobject(thr, -1); /* reject lightfuncs on purpose */ if (!DUK_HOBJECT_IS_CALLABLE(h_fun) || !DUK_HOBJECT_IS_COMPFUNC(h_fun)) { goto state_error; } duk_pop(thr); } #if 0 /* This check would prevent a heap destruction time finalizer from * launching a coroutine, which would ensure that during finalization * 'thr' would always equal heap_thread. Normal runtime finalizers * run with ms_running == 0, i.e. outside mark-and-sweep. See GH-2030. */ if (thr->heap->ms_running) { DUK_D(DUK_DPRINT("refuse Duktape.Thread.resume() when ms_running != 0")); goto state_error; } #endif /* * The error object has been augmented with a traceback and other * info from its creation point -- usually another thread. The * error handler is called here right before throwing, but it also * runs in the resumer's thread. It might be nice to get a traceback * from the resumee but this is not the case now. */ #if defined(DUK_USE_AUGMENT_ERROR_THROW) if (is_error) { DUK_ASSERT_TOP(thr, 2); /* value (error) is at stack top */ duk_err_augment_error_throw(thr); /* in resumer's context */ } #endif #if defined(DUK_USE_DEBUG) if (is_error) { DUK_DDD(DUK_DDDPRINT("RESUME ERROR: thread=%!T, value=%!T", (duk_tval *) duk_get_tval(thr, 0), (duk_tval *) duk_get_tval(thr, 1))); } else if (thr_resume->state == DUK_HTHREAD_STATE_YIELDED) { DUK_DDD(DUK_DDDPRINT("RESUME NORMAL: thread=%!T, value=%!T", (duk_tval *) duk_get_tval(thr, 0), (duk_tval *) duk_get_tval(thr, 1))); } else { DUK_DDD(DUK_DDDPRINT("RESUME INITIAL: thread=%!T, value=%!T", (duk_tval *) duk_get_tval(thr, 0), (duk_tval *) duk_get_tval(thr, 1))); } #endif thr->heap->lj.type = DUK_LJ_TYPE_RESUME; /* lj value2: thread */ DUK_ASSERT(thr->valstack_bottom < thr->valstack_top); DUK_TVAL_SET_TVAL_UPDREF(thr, &thr->heap->lj.value2, &thr->valstack_bottom[0]); /* side effects */ /* lj value1: value */ DUK_ASSERT(thr->valstack_bottom + 1 < thr->valstack_top); DUK_TVAL_SET_TVAL_UPDREF(thr, &thr->heap->lj.value1, &thr->valstack_bottom[1]); /* side effects */ DUK_TVAL_CHKFAST_INPLACE_SLOW(&thr->heap->lj.value1); thr->heap->lj.iserror = is_error; DUK_ASSERT(thr->heap->lj.jmpbuf_ptr != NULL); /* call is from executor, so we know we have a jmpbuf */ duk_err_longjmp(thr); /* execution resumes in bytecode executor */ DUK_UNREACHABLE(); /* Never here, fall through to error (from compiler point of view). */ state_error: DUK_DCERROR_TYPE_INVALID_STATE(thr); } #endif /* * Yield the current thread. * * The thread must be in yieldable state: it must have a resumer, and there * must not be any yield-preventing calls (native calls and constructor calls, * currently) in the thread's call stack (otherwise a resume would not be * possible later). This method must be called from an ECMAScript function. * * Args: * - value * - isError (defaults to false) * * Note: yield and resume handling is currently asymmetric. */ #if defined(DUK_USE_COROUTINE_SUPPORT) DUK_INTERNAL duk_ret_t duk_bi_thread_yield(duk_hthread *thr) { duk_hobject *caller_func; duk_small_uint_t is_error; DUK_DDD(DUK_DDDPRINT("Duktape.Thread.yield(): value=%!T, is_error=%!T", (duk_tval *) duk_get_tval(thr, 0), (duk_tval *) duk_get_tval(thr, 1))); DUK_ASSERT(thr->state == DUK_HTHREAD_STATE_RUNNING); DUK_ASSERT(thr->heap->curr_thread == thr); DUK_ASSERT(duk_get_top(thr) == 2); is_error = (duk_small_uint_t) duk_to_boolean_top_pop(thr); DUK_ASSERT(duk_get_top(thr) == 1); /* [ value ] */ /* * Thread state and calling context checks */ if (!thr->resumer) { DUK_DD(DUK_DDPRINT("yield state invalid: current thread must have a resumer")); goto state_error; } DUK_ASSERT(thr->resumer->state == DUK_HTHREAD_STATE_RESUMED); if (thr->callstack_top < 2) { DUK_DD(DUK_DDPRINT( "yield state invalid: callstack should contain at least 2 entries (caller and Duktape.Thread.yield)")); goto state_error; } DUK_ASSERT(thr->callstack_curr != NULL); DUK_ASSERT(thr->callstack_curr->parent != NULL); DUK_ASSERT(DUK_ACT_GET_FUNC(thr->callstack_curr) != NULL); /* us */ DUK_ASSERT(DUK_HOBJECT_IS_NATFUNC(DUK_ACT_GET_FUNC(thr->callstack_curr))); DUK_ASSERT(DUK_ACT_GET_FUNC(thr->callstack_curr->parent) != NULL); /* caller */ caller_func = DUK_ACT_GET_FUNC(thr->callstack_curr->parent); if (!DUK_HOBJECT_IS_COMPFUNC(caller_func)) { DUK_DD(DUK_DDPRINT("yield state invalid: caller must be ECMAScript code")); goto state_error; } DUK_ASSERT(thr->callstack_preventcount >= 1); /* should never be zero, because we (Duktape.Thread.yield) are on the stack */ if (thr->callstack_preventcount != 1) { /* Note: the only yield-preventing call is Duktape.Thread.yield(), hence check for 1, not 0 */ DUK_DD(DUK_DDPRINT("yield state invalid: there must be no yield-preventing calls in current thread callstack " "(preventcount is %ld)", (long) thr->callstack_preventcount)); goto state_error; } /* * The error object has been augmented with a traceback and other * info from its creation point -- usually the current thread. * The error handler, however, is called right before throwing * and runs in the yielder's thread. */ #if defined(DUK_USE_AUGMENT_ERROR_THROW) if (is_error) { DUK_ASSERT_TOP(thr, 1); /* value (error) is at stack top */ duk_err_augment_error_throw(thr); /* in yielder's context */ } #endif #if defined(DUK_USE_DEBUG) if (is_error) { DUK_DDD(DUK_DDDPRINT("YIELD ERROR: value=%!T", (duk_tval *) duk_get_tval(thr, 0))); } else { DUK_DDD(DUK_DDDPRINT("YIELD NORMAL: value=%!T", (duk_tval *) duk_get_tval(thr, 0))); } #endif /* * Process yield * * After longjmp(), processing continues in bytecode executor longjmp * handler, which will e.g. update thr->resumer to NULL. */ thr->heap->lj.type = DUK_LJ_TYPE_YIELD; /* lj value1: value */ DUK_ASSERT(thr->valstack_bottom < thr->valstack_top); DUK_TVAL_SET_TVAL_UPDREF(thr, &thr->heap->lj.value1, &thr->valstack_bottom[0]); /* side effects */ DUK_TVAL_CHKFAST_INPLACE_SLOW(&thr->heap->lj.value1); thr->heap->lj.iserror = is_error; DUK_ASSERT(thr->heap->lj.jmpbuf_ptr != NULL); /* call is from executor, so we know we have a jmpbuf */ duk_err_longjmp(thr); /* execution resumes in bytecode executor */ DUK_UNREACHABLE(); /* Never here, fall through to error (from compiler point of view). */ state_error: DUK_DCERROR_TYPE_INVALID_STATE(thr); } #endif #if defined(DUK_USE_COROUTINE_SUPPORT) DUK_INTERNAL duk_ret_t duk_bi_thread_current(duk_hthread *thr) { duk_push_current_thread(thr); return 1; } #endif #line 1 "duk_bi_thrower.c" /* * Type error thrower, E5 Section 13.2.3. */ /* #include duk_internal.h -> already included */ DUK_INTERNAL duk_ret_t duk_bi_type_error_thrower(duk_hthread *thr) { DUK_DCERROR_TYPE_INVALID_ARGS(thr); } #line 1 "duk_debug_fixedbuffer.c" /* * Fixed buffer helper useful for debugging, requires no allocation * which is critical for debugging. */ /* #include duk_internal.h -> already included */ #if defined(DUK_USE_DEBUG) DUK_INTERNAL void duk_fb_put_bytes(duk_fixedbuffer *fb, const duk_uint8_t *buffer, duk_size_t length) { duk_size_t avail; duk_size_t copylen; avail = (fb->offset >= fb->length ? (duk_size_t) 0 : (duk_size_t) (fb->length - fb->offset)); if (length > avail) { copylen = avail; fb->truncated = 1; } else { copylen = length; } duk_memcpy_unsafe(fb->buffer + fb->offset, buffer, copylen); fb->offset += copylen; } DUK_INTERNAL void duk_fb_put_byte(duk_fixedbuffer *fb, duk_uint8_t x) { duk_fb_put_bytes(fb, (const duk_uint8_t *) &x, 1); } DUK_INTERNAL void duk_fb_put_cstring(duk_fixedbuffer *fb, const char *x) { duk_fb_put_bytes(fb, (const duk_uint8_t *) x, (duk_size_t) DUK_STRLEN(x)); } DUK_INTERNAL void duk_fb_sprintf(duk_fixedbuffer *fb, const char *fmt, ...) { duk_size_t avail; va_list ap; va_start(ap, fmt); avail = (fb->offset >= fb->length ? (duk_size_t) 0 : (duk_size_t) (fb->length - fb->offset)); if (avail > 0) { duk_int_t res = (duk_int_t) DUK_VSNPRINTF((char *) (fb->buffer + fb->offset), avail, fmt, ap); if (res < 0) { /* error */ } else if ((duk_size_t) res >= avail) { /* (maybe) truncated */ fb->offset += avail; if ((duk_size_t) res > avail) { /* actual chars dropped (not just NUL term) */ fb->truncated = 1; } } else { /* normal */ fb->offset += (duk_size_t) res; } } va_end(ap); } DUK_INTERNAL void duk_fb_put_funcptr(duk_fixedbuffer *fb, duk_uint8_t *fptr, duk_size_t fptr_size) { char buf[64 + 1]; duk_debug_format_funcptr(buf, sizeof(buf), fptr, fptr_size); buf[sizeof(buf) - 1] = (char) 0; duk_fb_put_cstring(fb, buf); } DUK_INTERNAL duk_bool_t duk_fb_is_full(duk_fixedbuffer *fb) { return (fb->offset >= fb->length); } #endif /* DUK_USE_DEBUG */ #line 1 "duk_debug_vsnprintf.c" /* * Custom formatter for debug printing, allowing Duktape specific data * structures (such as tagged values and heap objects) to be printed with * a nice format string. Because debug printing should not affect execution * state, formatting here must be independent of execution (see implications * below) and must not allocate memory. * * Custom format tags begin with a '%!' to safely distinguish them from * standard format tags. The following conversions are supported: * * %!T tagged value (duk_tval *) * %!O heap object (duk_heaphdr *) * %!I decoded bytecode instruction * %!X bytecode instruction opcode name (arg is long) * %!C catcher (duk_catcher *) * %!A activation (duk_activation *) * * Everything is serialized in a JSON-like manner. The default depth is one * level, internal prototype is not followed, and internal properties are not * serialized. The following modifiers change this behavior: * * @ print pointers * # print binary representations (where applicable) * d deep traversal of own properties (not prototype) * p follow prototype chain (useless without 'd') * i include internal properties (other than prototype) * x hexdump buffers * h heavy formatting * * For instance, the following serializes objects recursively, but does not * follow the prototype chain nor print internal properties: "%!dO". * * Notes: * * * Standard snprintf return value semantics seem to vary. This * implementation returns the number of bytes it actually wrote * (excluding the null terminator). If retval == buffer size, * output was truncated (except for corner cases). * * * Output format is intentionally different from ECMAScript * formatting requirements, as formatting here serves debugging * of internals. * * * Depth checking (and updating) is done in each type printer * separately, to allow them to call each other freely. * * * Some pathological structures might take ages to print (e.g. * self recursion with 100 properties pointing to the object * itself). To guard against these, each printer also checks * whether the output buffer is full; if so, early exit. * * * Reference loops are detected using a loop stack. */ /* #include duk_internal.h -> already included */ #if defined(DUK_USE_DEBUG) /* #include stdio.h -> already included */ /* #include stdarg.h -> already included */ #include <string.h> /* list of conversion specifiers that terminate a format tag; * this is unfortunately guesswork. */ #define DUK__ALLOWED_STANDARD_SPECIFIERS "diouxXeEfFgGaAcsCSpnm" /* maximum length of standard format tag that we support */ #define DUK__MAX_FORMAT_TAG_LENGTH 32 /* heapobj recursion depth when deep printing is selected */ #define DUK__DEEP_DEPTH_LIMIT 8 /* maximum recursion depth for loop detection stacks */ #define DUK__LOOP_STACK_DEPTH 256 /* must match bytecode defines now; build autogenerate? */ DUK_LOCAL const char * const duk__bc_optab[256] = { "LDREG", "STREG", "JUMP", "LDCONST", "LDINT", "LDINTX", "LDTHIS", "LDUNDEF", "LDNULL", "LDTRUE", "LDFALSE", "GETVAR", "BNOT", "LNOT", "UNM", "UNP", "EQ_RR", "EQ_CR", "EQ_RC", "EQ_CC", "NEQ_RR", "NEQ_CR", "NEQ_RC", "NEQ_CC", "SEQ_RR", "SEQ_CR", "SEQ_RC", "SEQ_CC", "SNEQ_RR", "SNEQ_CR", "SNEQ_RC", "SNEQ_CC", "GT_RR", "GT_CR", "GT_RC", "GT_CC", "GE_RR", "GE_CR", "GE_RC", "GE_CC", "LT_RR", "LT_CR", "LT_RC", "LT_CC", "LE_RR", "LE_CR", "LE_RC", "LE_CC", "IFTRUE_R", "IFTRUE_C", "IFFALSE_R", "IFFALSE_C", "ADD_RR", "ADD_CR", "ADD_RC", "ADD_CC", "SUB_RR", "SUB_CR", "SUB_RC", "SUB_CC", "MUL_RR", "MUL_CR", "MUL_RC", "MUL_CC", "DIV_RR", "DIV_CR", "DIV_RC", "DIV_CC", "MOD_RR", "MOD_CR", "MOD_RC", "MOD_CC", "EXP_RR", "EXP_CR", "EXP_RC", "EXP_CC", "BAND_RR", "BAND_CR", "BAND_RC", "BAND_CC", "BOR_RR", "BOR_CR", "BOR_RC", "BOR_CC", "BXOR_RR", "BXOR_CR", "BXOR_RC", "BXOR_CC", "BASL_RR", "BASL_CR", "BASL_RC", "BASL_CC", "BLSR_RR", "BLSR_CR", "BLSR_RC", "BLSR_CC", "BASR_RR", "BASR_CR", "BASR_RC", "BASR_CC", "INSTOF_RR", "INSTOF_CR", "INSTOF_RC", "INSTOF_CC", "IN_RR", "IN_CR", "IN_RC", "IN_CC", "GETPROP_RR", "GETPROP_CR", "GETPROP_RC", "GETPROP_CC", "PUTPROP_RR", "PUTPROP_CR", "PUTPROP_RC", "PUTPROP_CC", "DELPROP_RR", "DELPROP_CR", "DELPROP_RC", "DELPROP_CC", "PREINCR", "PREDECR", "POSTINCR", "POSTDECR", "PREINCV", "PREDECV", "POSTINCV", "POSTDECV", "PREINCP_RR", "PREINCP_CR", "PREINCP_RC", "PREINCP_CC", "PREDECP_RR", "PREDECP_CR", "PREDECP_RC", "PREDECP_CC", "POSTINCP_RR", "POSTINCP_CR", "POSTINCP_RC", "POSTINCP_CC", "POSTDECP_RR", "POSTDECP_CR", "POSTDECP_RC", "POSTDECP_CC", "DECLVAR_RR", "DECLVAR_CR", "DECLVAR_RC", "DECLVAR_CC", "REGEXP_RR", "REGEXP_RC", "REGEXP_CR", "REGEXP_CC", "CLOSURE", "TYPEOF", "TYPEOFID", "PUTVAR", "DELVAR", "RETREG", "RETUNDEF", "RETCONST", "RETCONSTN", "LABEL", "ENDLABEL", "BREAK", "CONTINUE", "TRYCATCH", "ENDTRY", "ENDCATCH", "ENDFIN", "THROW", "INVLHS", "CSREG", "CSVAR_RR", "CSVAR_CR", "CSVAR_RC", "CSVAR_CC", "CALL0", "CALL1", "CALL2", "CALL3", "CALL4", "CALL5", "CALL6", "CALL7", "CALL8", "CALL9", "CALL10", "CALL11", "CALL12", "CALL13", "CALL14", "CALL15", "NEWOBJ", "NEWARR", "MPUTOBJ", "MPUTOBJI", "INITSET", "INITGET", "MPUTARR", "MPUTARRI", "SETALEN", "INITENUM", "NEXTENUM", "NEWTARGET", "DEBUGGER", "NOP", "INVALID", "UNUSED207", "GETPROPC_RR", "GETPROPC_CR", "GETPROPC_RC", "GETPROPC_CC", "UNUSED212", "UNUSED213", "UNUSED214", "UNUSED215", "UNUSED216", "UNUSED217", "UNUSED218", "UNUSED219", "UNUSED220", "UNUSED221", "UNUSED222", "UNUSED223", "UNUSED224", "UNUSED225", "UNUSED226", "UNUSED227", "UNUSED228", "UNUSED229", "UNUSED230", "UNUSED231", "UNUSED232", "UNUSED233", "UNUSED234", "UNUSED235", "UNUSED236", "UNUSED237", "UNUSED238", "UNUSED239", "UNUSED240", "UNUSED241", "UNUSED242", "UNUSED243", "UNUSED244", "UNUSED245", "UNUSED246", "UNUSED247", "UNUSED248", "UNUSED249", "UNUSED250", "UNUSED251", "UNUSED252", "UNUSED253", "UNUSED254", "UNUSED255" }; typedef struct duk__dprint_state duk__dprint_state; struct duk__dprint_state { duk_fixedbuffer *fb; /* loop_stack_index could be perhaps be replaced by 'depth', but it's nice * to not couple these two mechanisms unnecessarily. */ duk_hobject *loop_stack[DUK__LOOP_STACK_DEPTH]; duk_int_t loop_stack_index; duk_int_t loop_stack_limit; duk_int_t depth; duk_int_t depth_limit; duk_bool_t pointer; duk_bool_t heavy; duk_bool_t binary; duk_bool_t follow_proto; duk_bool_t internal; duk_bool_t hexdump; }; /* helpers */ DUK_LOCAL_DECL void duk__print_hstring(duk__dprint_state *st, duk_hstring *k, duk_bool_t quotes); DUK_LOCAL_DECL void duk__print_hobject(duk__dprint_state *st, duk_hobject *h); DUK_LOCAL_DECL void duk__print_hbuffer(duk__dprint_state *st, duk_hbuffer *h); DUK_LOCAL_DECL void duk__print_tval(duk__dprint_state *st, duk_tval *tv); DUK_LOCAL_DECL void duk__print_instr(duk__dprint_state *st, duk_instr_t ins); DUK_LOCAL_DECL void duk__print_heaphdr(duk__dprint_state *st, duk_heaphdr *h); DUK_LOCAL_DECL void duk__print_shared_heaphdr(duk__dprint_state *st, duk_heaphdr *h); DUK_LOCAL_DECL void duk__print_shared_heaphdr_string(duk__dprint_state *st, duk_heaphdr_string *h); DUK_LOCAL void duk__print_shared_heaphdr(duk__dprint_state *st, duk_heaphdr *h) { duk_fixedbuffer *fb = st->fb; if (st->heavy) { duk_fb_sprintf(fb, "(%p)", (void *) h); } if (!h) { return; } if (st->binary) { duk_size_t i; duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_LBRACKET); for (i = 0; i < (duk_size_t) sizeof(*h); i++) { duk_fb_sprintf(fb, "%02lx", (unsigned long) ((duk_uint8_t *) h)[i]); } duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_RBRACKET); } #if defined(DUK_USE_REFERENCE_COUNTING) /* currently implicitly also DUK_USE_DOUBLE_LINKED_HEAP */ if (st->heavy) { duk_fb_sprintf(fb, "[h_next=%p,h_prev=%p,h_refcount=%lu,h_flags=%08lx,type=%ld," "reachable=%ld,temproot=%ld,finalizable=%ld,finalized=%ld]", (void *) DUK_HEAPHDR_GET_NEXT(NULL, h), (void *) DUK_HEAPHDR_GET_PREV(NULL, h), (unsigned long) DUK_HEAPHDR_GET_REFCOUNT(h), (unsigned long) DUK_HEAPHDR_GET_FLAGS(h), (long) DUK_HEAPHDR_GET_TYPE(h), (long) (DUK_HEAPHDR_HAS_REACHABLE(h) ? 1 : 0), (long) (DUK_HEAPHDR_HAS_TEMPROOT(h) ? 1 : 0), (long) (DUK_HEAPHDR_HAS_FINALIZABLE(h) ? 1 : 0), (long) (DUK_HEAPHDR_HAS_FINALIZED(h) ? 1 : 0)); } #else if (st->heavy) { duk_fb_sprintf(fb, "[h_next=%p,h_flags=%08lx,type=%ld,reachable=%ld,temproot=%ld,finalizable=%ld,finalized=%ld]", (void *) DUK_HEAPHDR_GET_NEXT(NULL, h), (unsigned long) DUK_HEAPHDR_GET_FLAGS(h), (long) DUK_HEAPHDR_GET_TYPE(h), (long) (DUK_HEAPHDR_HAS_REACHABLE(h) ? 1 : 0), (long) (DUK_HEAPHDR_HAS_TEMPROOT(h) ? 1 : 0), (long) (DUK_HEAPHDR_HAS_FINALIZABLE(h) ? 1 : 0), (long) (DUK_HEAPHDR_HAS_FINALIZED(h) ? 1 : 0)); } #endif } DUK_LOCAL void duk__print_shared_heaphdr_string(duk__dprint_state *st, duk_heaphdr_string *h) { duk_fixedbuffer *fb = st->fb; if (st->heavy) { duk_fb_sprintf(fb, "(%p)", (void *) h); } if (!h) { return; } if (st->binary) { duk_size_t i; duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_LBRACKET); for (i = 0; i < (duk_size_t) sizeof(*h); i++) { duk_fb_sprintf(fb, "%02lx", (unsigned long) ((duk_uint8_t *) h)[i]); } duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_RBRACKET); } #if defined(DUK_USE_REFERENCE_COUNTING) if (st->heavy) { duk_fb_sprintf(fb, "[h_refcount=%lu,h_flags=%08lx,type=%ld,reachable=%ld,temproot=%ld,finalizable=%ld,finalized=%ld]", (unsigned long) DUK_HEAPHDR_GET_REFCOUNT((duk_heaphdr *) h), (unsigned long) DUK_HEAPHDR_GET_FLAGS((duk_heaphdr *) h), (long) DUK_HEAPHDR_GET_TYPE((duk_heaphdr *) h), (long) (DUK_HEAPHDR_HAS_REACHABLE((duk_heaphdr *) h) ? 1 : 0), (long) (DUK_HEAPHDR_HAS_TEMPROOT((duk_heaphdr *) h) ? 1 : 0), (long) (DUK_HEAPHDR_HAS_FINALIZABLE((duk_heaphdr *) h) ? 1 : 0), (long) (DUK_HEAPHDR_HAS_FINALIZED((duk_heaphdr *) h) ? 1 : 0)); } #else if (st->heavy) { duk_fb_sprintf(fb, "[h_flags=%08lx,type=%ld,reachable=%ld,temproot=%ld,finalizable=%ld,finalized=%ld]", (unsigned long) DUK_HEAPHDR_GET_FLAGS((duk_heaphdr *) h), (long) DUK_HEAPHDR_GET_TYPE((duk_heaphdr *) h), (long) (DUK_HEAPHDR_HAS_REACHABLE((duk_heaphdr *) h) ? 1 : 0), (long) (DUK_HEAPHDR_HAS_TEMPROOT((duk_heaphdr *) h) ? 1 : 0), (long) (DUK_HEAPHDR_HAS_FINALIZABLE((duk_heaphdr *) h) ? 1 : 0), (long) (DUK_HEAPHDR_HAS_FINALIZED((duk_heaphdr *) h) ? 1 : 0)); } #endif } DUK_LOCAL void duk__print_hstring(duk__dprint_state *st, duk_hstring *h, duk_bool_t quotes) { duk_fixedbuffer *fb = st->fb; const duk_uint8_t *p; const duk_uint8_t *p_end; /* terminal type: no depth check */ if (duk_fb_is_full(fb)) { return; } duk__print_shared_heaphdr_string(st, &h->hdr); if (!h) { duk_fb_put_cstring(fb, "NULL"); return; } p = DUK_HSTRING_GET_DATA(h); p_end = p + DUK_HSTRING_GET_BYTELEN(h); if (p_end > p && p[0] == DUK_ASC_UNDERSCORE) { /* If property key begins with underscore, encode it with * forced quotes (e.g. "_Foo") to distinguish it from encoded * internal properties (e.g. \x82Bar -> _Bar). */ quotes = 1; } if (quotes) { duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_DOUBLEQUOTE); } while (p < p_end) { duk_uint8_t ch = *p++; /* two special escapes: '\' and '"', other printables as is */ if (ch == '\\') { duk_fb_sprintf(fb, "\\\\"); } else if (ch == '"') { duk_fb_sprintf(fb, "\\\""); } else if (ch >= 0x20 && ch <= 0x7e) { duk_fb_put_byte(fb, ch); } else if (ch == 0x82 && !quotes) { /* encode \x82Bar as _Bar if no quotes are * applied, this is for readable internal keys. */ duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_UNDERSCORE); } else { duk_fb_sprintf(fb, "\\x%02lx", (unsigned long) ch); } } if (quotes) { duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_DOUBLEQUOTE); } #if defined(DUK_USE_REFERENCE_COUNTING) /* XXX: limit to quoted strings only, to save keys from being cluttered? */ duk_fb_sprintf(fb, "/%lu", (unsigned long) DUK_HEAPHDR_GET_REFCOUNT(&h->hdr)); #endif } #define DUK__COMMA() \ do { \ if (first) { \ first = 0; \ } else { \ duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_COMMA); \ } \ } while (0) DUK_LOCAL void duk__print_hobject(duk__dprint_state *st, duk_hobject *h) { duk_fixedbuffer *fb = st->fb; duk_uint_fast32_t i; duk_tval *tv; duk_hstring *key; duk_bool_t first = 1; const char *brace1 = "{"; const char *brace2 = "}"; duk_bool_t pushed_loopstack = 0; if (duk_fb_is_full(fb)) { return; } duk__print_shared_heaphdr(st, &h->hdr); if (h && DUK_HOBJECT_HAS_ARRAY_PART(h)) { brace1 = "["; brace2 = "]"; } if (!h) { duk_fb_put_cstring(fb, "NULL"); goto finished; } if (st->depth >= st->depth_limit) { const char *subtype = "generic"; if (DUK_HOBJECT_IS_COMPFUNC(h)) { subtype = "compfunc"; } else if (DUK_HOBJECT_IS_NATFUNC(h)) { subtype = "natfunc"; } else if (DUK_HOBJECT_IS_THREAD(h)) { subtype = "thread"; } else if (DUK_HOBJECT_IS_BUFOBJ(h)) { subtype = "bufobj"; } else if (DUK_HOBJECT_IS_ARRAY(h)) { subtype = "array"; } duk_fb_sprintf(fb, "%sobject/%s %p%s", (const char *) brace1, subtype, (void *) h, (const char *) brace2); return; } for (i = 0; i < (duk_uint_fast32_t) st->loop_stack_index; i++) { if (st->loop_stack[i] == h) { duk_fb_sprintf(fb, "%sLOOP:%p%s", (const char *) brace1, (void *) h, (const char *) brace2); return; } } /* after this, return paths should 'goto finished' for decrement */ st->depth++; if (st->loop_stack_index >= st->loop_stack_limit) { duk_fb_sprintf(fb, "%sOUT-OF-LOOP-STACK%s", (const char *) brace1, (const char *) brace2); goto finished; } st->loop_stack[st->loop_stack_index++] = h; pushed_loopstack = 1; /* * Notation: double underscore used for internal properties which are not * stored in the property allocation (e.g. '__valstack'). */ duk_fb_put_cstring(fb, brace1); if (DUK_HOBJECT_GET_PROPS(NULL, h)) { duk_uint32_t a_limit; a_limit = DUK_HOBJECT_GET_ASIZE(h); if (st->internal) { /* dump all allocated entries, unused entries print as 'unused', * note that these may extend beyond current 'length' and look * a bit funny. */ } else { /* leave out trailing 'unused' elements */ while (a_limit > 0) { tv = DUK_HOBJECT_A_GET_VALUE_PTR(NULL, h, a_limit - 1); if (!DUK_TVAL_IS_UNUSED(tv)) { break; } a_limit--; } } for (i = 0; i < a_limit; i++) { tv = DUK_HOBJECT_A_GET_VALUE_PTR(NULL, h, i); DUK__COMMA(); duk__print_tval(st, tv); } for (i = 0; i < DUK_HOBJECT_GET_ENEXT(h); i++) { key = DUK_HOBJECT_E_GET_KEY(NULL, h, i); if (!key) { continue; } if (!st->internal && DUK_HSTRING_HAS_HIDDEN(key)) { continue; } DUK__COMMA(); duk__print_hstring(st, key, 0); duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_COLON); if (DUK_HOBJECT_E_SLOT_IS_ACCESSOR(NULL, h, i)) { duk_fb_sprintf(fb, "[get:%p,set:%p]", (void *) DUK_HOBJECT_E_GET_VALUE(NULL, h, i).a.get, (void *) DUK_HOBJECT_E_GET_VALUE(NULL, h, i).a.set); } else { tv = &DUK_HOBJECT_E_GET_VALUE(NULL, h, i).v; duk__print_tval(st, tv); } if (st->heavy) { duk_fb_sprintf(fb, "<%02lx>", (unsigned long) DUK_HOBJECT_E_GET_FLAGS(NULL, h, i)); } } } if (st->internal) { if (DUK_HOBJECT_IS_ARRAY(h)) { DUK__COMMA(); duk_fb_sprintf(fb, "__array:true"); } if (DUK_HOBJECT_HAS_EXTENSIBLE(h)) { DUK__COMMA(); duk_fb_sprintf(fb, "__extensible:true"); } if (DUK_HOBJECT_HAS_CONSTRUCTABLE(h)) { DUK__COMMA(); duk_fb_sprintf(fb, "__constructable:true"); } if (DUK_HOBJECT_HAS_BOUNDFUNC(h)) { DUK__COMMA(); duk_fb_sprintf(fb, "__boundfunc:true"); } if (DUK_HOBJECT_HAS_COMPFUNC(h)) { DUK__COMMA(); duk_fb_sprintf(fb, "__compfunc:true"); } if (DUK_HOBJECT_HAS_NATFUNC(h)) { DUK__COMMA(); duk_fb_sprintf(fb, "__natfunc:true"); } if (DUK_HOBJECT_HAS_BUFOBJ(h)) { DUK__COMMA(); duk_fb_sprintf(fb, "__bufobj:true"); } if (DUK_HOBJECT_IS_THREAD(h)) { DUK__COMMA(); duk_fb_sprintf(fb, "__thread:true"); } if (DUK_HOBJECT_HAS_ARRAY_PART(h)) { DUK__COMMA(); duk_fb_sprintf(fb, "__array_part:true"); } if (DUK_HOBJECT_HAS_STRICT(h)) { DUK__COMMA(); duk_fb_sprintf(fb, "__strict:true"); } if (DUK_HOBJECT_HAS_NOTAIL(h)) { DUK__COMMA(); duk_fb_sprintf(fb, "__notail:true"); } if (DUK_HOBJECT_HAS_NEWENV(h)) { DUK__COMMA(); duk_fb_sprintf(fb, "__newenv:true"); } if (DUK_HOBJECT_HAS_NAMEBINDING(h)) { DUK__COMMA(); duk_fb_sprintf(fb, "__namebinding:true"); } if (DUK_HOBJECT_HAS_CREATEARGS(h)) { DUK__COMMA(); duk_fb_sprintf(fb, "__createargs:true"); } if (DUK_HOBJECT_HAS_EXOTIC_ARRAY(h)) { DUK__COMMA(); duk_fb_sprintf(fb, "__exotic_array:true"); } if (DUK_HOBJECT_HAS_EXOTIC_STRINGOBJ(h)) { DUK__COMMA(); duk_fb_sprintf(fb, "__exotic_stringobj:true"); } if (DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(h)) { DUK__COMMA(); duk_fb_sprintf(fb, "__exotic_arguments:true"); } if (DUK_HOBJECT_IS_BUFOBJ(h)) { DUK__COMMA(); duk_fb_sprintf(fb, "__exotic_bufobj:true"); } if (DUK_HOBJECT_HAS_EXOTIC_PROXYOBJ(h)) { DUK__COMMA(); duk_fb_sprintf(fb, "__exotic_proxyobj:true"); } } if (st->internal && DUK_HOBJECT_IS_ARRAY(h)) { duk_harray *a = (duk_harray *) h; DUK__COMMA(); duk_fb_sprintf(fb, "__length:%ld", (long) a->length); DUK__COMMA(); duk_fb_sprintf(fb, "__length_nonwritable:%ld", (long) a->length_nonwritable); } else if (st->internal && DUK_HOBJECT_IS_COMPFUNC(h)) { duk_hcompfunc *f = (duk_hcompfunc *) h; DUK__COMMA(); duk_fb_put_cstring(fb, "__data:"); duk__print_hbuffer(st, (duk_hbuffer *) DUK_HCOMPFUNC_GET_DATA(NULL, f)); DUK__COMMA(); duk_fb_put_cstring(fb, "__lexenv:"); duk__print_hobject(st, DUK_HCOMPFUNC_GET_LEXENV(NULL, f)); DUK__COMMA(); duk_fb_put_cstring(fb, "__varenv:"); duk__print_hobject(st, DUK_HCOMPFUNC_GET_VARENV(NULL, f)); DUK__COMMA(); duk_fb_sprintf(fb, "__nregs:%ld", (long) f->nregs); DUK__COMMA(); duk_fb_sprintf(fb, "__nargs:%ld", (long) f->nargs); #if defined(DUK_USE_DEBUGGER_SUPPORT) DUK__COMMA(); duk_fb_sprintf(fb, "__start_line:%ld", (long) f->start_line); DUK__COMMA(); duk_fb_sprintf(fb, "__end_line:%ld", (long) f->end_line); #endif DUK__COMMA(); duk_fb_put_cstring(fb, "__data:"); duk__print_hbuffer(st, (duk_hbuffer *) DUK_HCOMPFUNC_GET_DATA(NULL, f)); } else if (st->internal && DUK_HOBJECT_IS_NATFUNC(h)) { duk_hnatfunc *f = (duk_hnatfunc *) h; DUK__COMMA(); duk_fb_sprintf(fb, "__func:"); duk_fb_put_funcptr(fb, (duk_uint8_t *) &f->func, sizeof(f->func)); DUK__COMMA(); duk_fb_sprintf(fb, "__nargs:%ld", (long) f->nargs); DUK__COMMA(); duk_fb_sprintf(fb, "__magic:%ld", (long) f->magic); } else if (st->internal && DUK_HOBJECT_IS_DECENV(h)) { duk_hdecenv *e = (duk_hdecenv *) h; DUK__COMMA(); duk_fb_sprintf(fb, "__thread:"); duk__print_hobject(st, (duk_hobject *) e->thread); DUK__COMMA(); duk_fb_sprintf(fb, "__varmap:"); duk__print_hobject(st, (duk_hobject *) e->varmap); DUK__COMMA(); duk_fb_sprintf(fb, "__regbase_byteoff:%ld", (long) e->regbase_byteoff); } else if (st->internal && DUK_HOBJECT_IS_OBJENV(h)) { duk_hobjenv *e = (duk_hobjenv *) h; DUK__COMMA(); duk_fb_sprintf(fb, "__target:"); duk__print_hobject(st, (duk_hobject *) e->target); DUK__COMMA(); duk_fb_sprintf(fb, "__has_this:%ld", (long) e->has_this); #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) } else if (st->internal && DUK_HOBJECT_IS_BUFOBJ(h)) { duk_hbufobj *b = (duk_hbufobj *) h; DUK__COMMA(); duk_fb_sprintf(fb, "__buf:"); duk__print_hbuffer(st, (duk_hbuffer *) b->buf); DUK__COMMA(); duk_fb_sprintf(fb, "__buf_prop:"); duk__print_hobject(st, (duk_hobject *) b->buf_prop); DUK__COMMA(); duk_fb_sprintf(fb, "__offset:%ld", (long) b->offset); DUK__COMMA(); duk_fb_sprintf(fb, "__length:%ld", (long) b->length); DUK__COMMA(); duk_fb_sprintf(fb, "__shift:%ld", (long) b->shift); DUK__COMMA(); duk_fb_sprintf(fb, "__elemtype:%ld", (long) b->elem_type); #endif } else if (st->internal && DUK_HOBJECT_IS_PROXY(h)) { duk_hproxy *p = (duk_hproxy *) h; DUK__COMMA(); duk_fb_sprintf(fb, "__target:"); duk__print_hobject(st, p->target); DUK__COMMA(); duk_fb_sprintf(fb, "__handler:"); duk__print_hobject(st, p->handler); } else if (st->internal && DUK_HOBJECT_IS_THREAD(h)) { duk_hthread *t = (duk_hthread *) h; DUK__COMMA(); duk_fb_sprintf(fb, "__ptr_curr_pc:%p", (void *) t->ptr_curr_pc); DUK__COMMA(); duk_fb_sprintf(fb, "__heap:%p", (void *) t->heap); DUK__COMMA(); duk_fb_sprintf(fb, "__strict:%ld", (long) t->strict); DUK__COMMA(); duk_fb_sprintf(fb, "__state:%ld", (long) t->state); DUK__COMMA(); duk_fb_sprintf(fb, "__unused1:%ld", (long) t->unused1); DUK__COMMA(); duk_fb_sprintf(fb, "__unused2:%ld", (long) t->unused2); DUK__COMMA(); duk_fb_sprintf(fb, "__valstack:%p", (void *) t->valstack); DUK__COMMA(); duk_fb_sprintf(fb, "__valstack_end:%p/%ld", (void *) t->valstack_end, (long) (t->valstack_end - t->valstack)); DUK__COMMA(); duk_fb_sprintf(fb, "__valstack_alloc_end:%p/%ld", (void *) t->valstack_alloc_end, (long) (t->valstack_alloc_end - t->valstack)); DUK__COMMA(); duk_fb_sprintf(fb, "__valstack_bottom:%p/%ld", (void *) t->valstack_bottom, (long) (t->valstack_bottom - t->valstack)); DUK__COMMA(); duk_fb_sprintf(fb, "__valstack_top:%p/%ld", (void *) t->valstack_top, (long) (t->valstack_top - t->valstack)); DUK__COMMA(); duk_fb_sprintf(fb, "__callstack_curr:%p", (void *) t->callstack_curr); DUK__COMMA(); duk_fb_sprintf(fb, "__callstack_top:%ld", (long) t->callstack_top); DUK__COMMA(); duk_fb_sprintf(fb, "__callstack_preventcount:%ld", (long) t->callstack_preventcount); DUK__COMMA(); duk_fb_sprintf(fb, "__resumer:"); duk__print_hobject(st, (duk_hobject *) t->resumer); DUK__COMMA(); duk_fb_sprintf(fb, "__compile_ctx:%p", (void *) t->compile_ctx); #if defined(DUK_USE_INTERRUPT_COUNTER) DUK__COMMA(); duk_fb_sprintf(fb, "__interrupt_counter:%ld", (long) t->interrupt_counter); DUK__COMMA(); duk_fb_sprintf(fb, "__interrupt_init:%ld", (long) t->interrupt_init); #endif /* XXX: print built-ins array? */ } #if defined(DUK_USE_REFERENCE_COUNTING) if (st->internal) { DUK__COMMA(); duk_fb_sprintf(fb, "__refcount:%lu", (unsigned long) DUK_HEAPHDR_GET_REFCOUNT((duk_heaphdr *) h)); } #endif if (st->internal) { DUK__COMMA(); duk_fb_sprintf(fb, "__class:%ld", (long) DUK_HOBJECT_GET_CLASS_NUMBER(h)); } DUK__COMMA(); duk_fb_sprintf(fb, "__heapptr:%p", (void *) h); /* own pointer */ /* prototype should be last, for readability */ if (DUK_HOBJECT_GET_PROTOTYPE(NULL, h)) { if (st->follow_proto) { DUK__COMMA(); duk_fb_put_cstring(fb, "__prototype:"); duk__print_hobject(st, DUK_HOBJECT_GET_PROTOTYPE(NULL, h)); } else { DUK__COMMA(); duk_fb_sprintf(fb, "__prototype:%p", (void *) DUK_HOBJECT_GET_PROTOTYPE(NULL, h)); } } duk_fb_put_cstring(fb, brace2); #if defined(DUK_USE_HOBJECT_HASH_PART) if (st->heavy && DUK_HOBJECT_GET_HSIZE(h) > 0) { duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_LANGLE); for (i = 0; i < DUK_HOBJECT_GET_HSIZE(h); i++) { duk_uint_t h_idx = DUK_HOBJECT_H_GET_INDEX(NULL, h, i); if (i > 0) { duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_COMMA); } if (h_idx == DUK_HOBJECT_HASHIDX_UNUSED) { duk_fb_sprintf(fb, "u"); } else if (h_idx == DUK_HOBJECT_HASHIDX_DELETED) { duk_fb_sprintf(fb, "d"); } else { duk_fb_sprintf(fb, "%ld", (long) h_idx); } } duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_RANGLE); } #endif finished: st->depth--; if (pushed_loopstack) { st->loop_stack_index--; st->loop_stack[st->loop_stack_index] = NULL; } } DUK_LOCAL void duk__print_hbuffer(duk__dprint_state *st, duk_hbuffer *h) { duk_fixedbuffer *fb = st->fb; duk_size_t i, n; duk_uint8_t *p; if (duk_fb_is_full(fb)) { return; } /* terminal type: no depth check */ if (!h) { duk_fb_put_cstring(fb, "NULL"); return; } if (DUK_HBUFFER_HAS_DYNAMIC(h)) { if (DUK_HBUFFER_HAS_EXTERNAL(h)) { duk_hbuffer_external *g = (duk_hbuffer_external *) h; duk_fb_sprintf(fb, "buffer:external:%p:%ld", (void *) DUK_HBUFFER_EXTERNAL_GET_DATA_PTR(NULL, g), (long) DUK_HBUFFER_EXTERNAL_GET_SIZE(g)); } else { duk_hbuffer_dynamic *g = (duk_hbuffer_dynamic *) h; duk_fb_sprintf(fb, "buffer:dynamic:%p:%ld", (void *) DUK_HBUFFER_DYNAMIC_GET_DATA_PTR(NULL, g), (long) DUK_HBUFFER_DYNAMIC_GET_SIZE(g)); } } else { duk_fb_sprintf(fb, "buffer:fixed:%ld", (long) DUK_HBUFFER_GET_SIZE(h)); } #if defined(DUK_USE_REFERENCE_COUNTING) duk_fb_sprintf(fb, "/%lu", (unsigned long) DUK_HEAPHDR_GET_REFCOUNT(&h->hdr)); #endif if (st->hexdump) { duk_fb_sprintf(fb, "=["); n = DUK_HBUFFER_GET_SIZE(h); p = (duk_uint8_t *) DUK_HBUFFER_GET_DATA_PTR(NULL, h); for (i = 0; i < n; i++) { duk_fb_sprintf(fb, "%02lx", (unsigned long) p[i]); } duk_fb_sprintf(fb, "]"); } } DUK_LOCAL void duk__print_heaphdr(duk__dprint_state *st, duk_heaphdr *h) { duk_fixedbuffer *fb = st->fb; if (duk_fb_is_full(fb)) { return; } if (!h) { duk_fb_put_cstring(fb, "NULL"); return; } switch (DUK_HEAPHDR_GET_TYPE(h)) { case DUK_HTYPE_STRING: duk__print_hstring(st, (duk_hstring *) h, 1); break; case DUK_HTYPE_OBJECT: duk__print_hobject(st, (duk_hobject *) h); break; case DUK_HTYPE_BUFFER: duk__print_hbuffer(st, (duk_hbuffer *) h); break; default: duk_fb_sprintf(fb, "[unknown htype %ld]", (long) DUK_HEAPHDR_GET_TYPE(h)); break; } } DUK_LOCAL void duk__print_tval(duk__dprint_state *st, duk_tval *tv) { duk_fixedbuffer *fb = st->fb; if (duk_fb_is_full(fb)) { return; } /* depth check is done when printing an actual type */ if (st->heavy) { duk_fb_sprintf(fb, "(%p)", (void *) tv); } if (!tv) { duk_fb_put_cstring(fb, "NULL"); return; } if (st->binary) { duk_size_t i; duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_LBRACKET); for (i = 0; i < (duk_size_t) sizeof(*tv); i++) { duk_fb_sprintf(fb, "%02lx", (unsigned long) ((duk_uint8_t *) tv)[i]); } duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_RBRACKET); } if (st->heavy) { duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_LANGLE); } switch (DUK_TVAL_GET_TAG(tv)) { case DUK_TAG_UNDEFINED: { duk_fb_put_cstring(fb, "undefined"); break; } case DUK_TAG_UNUSED: { duk_fb_put_cstring(fb, "unused"); break; } case DUK_TAG_NULL: { duk_fb_put_cstring(fb, "null"); break; } case DUK_TAG_BOOLEAN: { duk_fb_put_cstring(fb, DUK_TVAL_GET_BOOLEAN(tv) ? "true" : "false"); break; } case DUK_TAG_STRING: { /* Note: string is a terminal heap object, so no depth check here */ duk__print_hstring(st, DUK_TVAL_GET_STRING(tv), 1); break; } case DUK_TAG_OBJECT: { duk__print_hobject(st, DUK_TVAL_GET_OBJECT(tv)); break; } case DUK_TAG_BUFFER: { duk__print_hbuffer(st, DUK_TVAL_GET_BUFFER(tv)); break; } case DUK_TAG_POINTER: { duk_fb_sprintf(fb, "pointer:%p", (void *) DUK_TVAL_GET_POINTER(tv)); break; } case DUK_TAG_LIGHTFUNC: { duk_c_function func; duk_small_uint_t lf_flags; DUK_TVAL_GET_LIGHTFUNC(tv, func, lf_flags); duk_fb_sprintf(fb, "lightfunc:"); duk_fb_put_funcptr(fb, (duk_uint8_t *) &func, sizeof(func)); duk_fb_sprintf(fb, ":%04lx", (long) lf_flags); break; } #if defined(DUK_USE_FASTINT) case DUK_TAG_FASTINT: DUK_ASSERT(!DUK_TVAL_IS_UNUSED(tv)); DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv)); duk_fb_sprintf(fb, "%.18g_F", (double) DUK_TVAL_GET_NUMBER(tv)); break; #endif default: { /* IEEE double is approximately 16 decimal digits; print a couple extra */ DUK_ASSERT(!DUK_TVAL_IS_UNUSED(tv)); DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv)); duk_fb_sprintf(fb, "%.18g", (double) DUK_TVAL_GET_NUMBER(tv)); break; } } if (st->heavy) { duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_RANGLE); } } DUK_LOCAL void duk__print_instr(duk__dprint_state *st, duk_instr_t ins) { duk_fixedbuffer *fb = st->fb; duk_small_int_t op; const char *op_name; op = (duk_small_int_t) DUK_DEC_OP(ins); op_name = duk__bc_optab[op]; /* XXX: option to fix opcode length so it lines up nicely */ if (op == DUK_OP_JUMP) { duk_int_t diff1 = (duk_int_t) (DUK_DEC_ABC(ins) - DUK_BC_JUMP_BIAS); /* from next pc */ duk_int_t diff2 = diff1 + 1; /* from curr pc */ duk_fb_sprintf(fb, "%s %ld (to pc%c%ld)", (const char *) op_name, (long) diff1, (int) (diff2 >= 0 ? '+' : '-'), /* char format: use int */ (long) (diff2 >= 0 ? diff2 : -diff2)); } else { duk_fb_sprintf(fb, "%s %ld, %ld, %ld", (const char *) op_name, (long) DUK_DEC_A(ins), (long) DUK_DEC_B(ins), (long) DUK_DEC_C(ins)); } } DUK_LOCAL void duk__print_opcode(duk__dprint_state *st, duk_small_int_t opcode) { duk_fixedbuffer *fb = st->fb; if (opcode < DUK_BC_OP_MIN || opcode > DUK_BC_OP_MAX) { duk_fb_sprintf(fb, "?(%ld)", (long) opcode); } else { duk_fb_sprintf(fb, "%s", (const char *) duk__bc_optab[opcode]); } } DUK_LOCAL void duk__print_catcher(duk__dprint_state *st, duk_catcher *cat) { duk_fixedbuffer *fb = st->fb; if (duk_fb_is_full(fb)) { return; } if (!cat) { duk_fb_put_cstring(fb, "NULL"); return; } duk_fb_sprintf(fb, "[catcher ptr=%p parent=%p varname=%p pc_base=%p, idx_base=%ld, flags=0x%08lx]", (void *) cat, (void *) cat->parent, (void *) cat->h_varname, (void *) cat->pc_base, (long) cat->idx_base, (unsigned long) cat->flags); } DUK_LOCAL void duk__print_activation(duk__dprint_state *st, duk_activation *act) { duk_fixedbuffer *fb = st->fb; if (duk_fb_is_full(fb)) { return; } if (!act) { duk_fb_put_cstring(fb, "NULL"); return; } /* prev_caller: conditional, omitted on purpose, it's rarely used. */ /* prev_line: conditional, omitted on purpose (but would be nice). */ duk_fb_sprintf(fb, "[activation ptr=%p tv_func=<omit> func=%p parent=%p var_env=%p lex_env=%p cat=%p curr_pc=%p " "bottom_byteoff=%ld retval_byteoff=%ld reserve_byteoff=%ld flags=%ld]", (void *) act, (void *) act->func, (void *) act->parent, (void *) act->var_env, (void *) act->lex_env, (void *) act->cat, (void *) act->curr_pc, (long) act->bottom_byteoff, (long) act->retval_byteoff, (long) act->reserve_byteoff, (long) act->flags); } DUK_INTERNAL duk_int_t duk_debug_vsnprintf(char *str, duk_size_t size, const char *format, va_list ap) { duk_fixedbuffer fb; const char *p = format; const char *p_end = p + DUK_STRLEN(format); duk_int_t retval; duk_memzero(&fb, sizeof(fb)); fb.buffer = (duk_uint8_t *) str; fb.length = size; fb.offset = 0; fb.truncated = 0; while (p < p_end) { char ch = *p++; const char *p_begfmt = NULL; duk_bool_t got_exclamation = 0; duk_bool_t got_long = 0; /* %lf, %ld etc */ duk__dprint_state st; if (ch != DUK_ASC_PERCENT) { duk_fb_put_byte(&fb, (duk_uint8_t) ch); continue; } /* * Format tag parsing. Since we don't understand all the * possible format tags allowed, we just scan for a terminating * specifier and keep track of relevant modifiers that we do * understand. See man 3 printf. */ duk_memzero(&st, sizeof(st)); st.fb = &fb; st.depth = 0; st.depth_limit = 1; st.loop_stack_index = 0; st.loop_stack_limit = DUK__LOOP_STACK_DEPTH; p_begfmt = p - 1; while (p < p_end) { ch = *p++; if (ch == DUK_ASC_STAR) { /* unsupported: would consume multiple args */ goto format_error; } else if (ch == DUK_ASC_PERCENT) { duk_fb_put_byte(&fb, (duk_uint8_t) DUK_ASC_PERCENT); break; } else if (ch == DUK_ASC_EXCLAMATION) { got_exclamation = 1; } else if (!got_exclamation && ch == DUK_ASC_LC_L) { got_long = 1; } else if (got_exclamation && ch == DUK_ASC_LC_D) { st.depth_limit = DUK__DEEP_DEPTH_LIMIT; } else if (got_exclamation && ch == DUK_ASC_LC_P) { st.follow_proto = 1; } else if (got_exclamation && ch == DUK_ASC_LC_I) { st.internal = 1; } else if (got_exclamation && ch == DUK_ASC_LC_X) { st.hexdump = 1; } else if (got_exclamation && ch == DUK_ASC_LC_H) { st.heavy = 1; } else if (got_exclamation && ch == DUK_ASC_ATSIGN) { st.pointer = 1; } else if (got_exclamation && ch == DUK_ASC_HASH) { st.binary = 1; } else if (got_exclamation && ch == DUK_ASC_UC_T) { duk_tval *t = va_arg(ap, duk_tval *); if (st.pointer && !st.heavy) { duk_fb_sprintf(&fb, "(%p)", (void *) t); } duk__print_tval(&st, t); break; } else if (got_exclamation && ch == DUK_ASC_UC_O) { duk_heaphdr *t = va_arg(ap, duk_heaphdr *); if (st.pointer && !st.heavy) { duk_fb_sprintf(&fb, "(%p)", (void *) t); } duk__print_heaphdr(&st, t); break; } else if (got_exclamation && ch == DUK_ASC_UC_I) { duk_instr_t t = va_arg(ap, duk_instr_t); duk__print_instr(&st, t); break; } else if (got_exclamation && ch == DUK_ASC_UC_X) { long t = va_arg(ap, long); duk__print_opcode(&st, (duk_small_int_t) t); break; } else if (got_exclamation && ch == DUK_ASC_UC_C) { duk_catcher *t = va_arg(ap, duk_catcher *); duk__print_catcher(&st, t); break; } else if (got_exclamation && ch == DUK_ASC_UC_A) { duk_activation *t = va_arg(ap, duk_activation *); duk__print_activation(&st, t); break; } else if (!got_exclamation && strchr(DUK__ALLOWED_STANDARD_SPECIFIERS, (int) ch)) { char fmtbuf[DUK__MAX_FORMAT_TAG_LENGTH]; duk_size_t fmtlen; DUK_ASSERT(p >= p_begfmt); fmtlen = (duk_size_t) (p - p_begfmt); if (fmtlen >= sizeof(fmtbuf)) { /* format is too large, abort */ goto format_error; } duk_memzero(fmtbuf, sizeof(fmtbuf)); duk_memcpy(fmtbuf, p_begfmt, fmtlen); /* assume exactly 1 arg, which is why '*' is forbidden; arg size still * depends on type though. */ if (ch == DUK_ASC_LC_F || ch == DUK_ASC_LC_G || ch == DUK_ASC_LC_E) { /* %f and %lf both consume a 'long' */ double arg = va_arg(ap, double); duk_fb_sprintf(&fb, fmtbuf, arg); } else if (ch == DUK_ASC_LC_D && got_long) { /* %ld */ long arg = va_arg(ap, long); duk_fb_sprintf(&fb, fmtbuf, arg); } else if (ch == DUK_ASC_LC_D) { /* %d; only 16 bits are guaranteed */ int arg = va_arg(ap, int); duk_fb_sprintf(&fb, fmtbuf, arg); } else if (ch == DUK_ASC_LC_U && got_long) { /* %lu */ unsigned long arg = va_arg(ap, unsigned long); duk_fb_sprintf(&fb, fmtbuf, arg); } else if (ch == DUK_ASC_LC_U) { /* %u; only 16 bits are guaranteed */ unsigned int arg = va_arg(ap, unsigned int); duk_fb_sprintf(&fb, fmtbuf, arg); } else if (ch == DUK_ASC_LC_X && got_long) { /* %lx */ unsigned long arg = va_arg(ap, unsigned long); duk_fb_sprintf(&fb, fmtbuf, arg); } else if (ch == DUK_ASC_LC_X) { /* %x; only 16 bits are guaranteed */ unsigned int arg = va_arg(ap, unsigned int); duk_fb_sprintf(&fb, fmtbuf, arg); } else if (ch == DUK_ASC_LC_S) { /* %s */ const char *arg = va_arg(ap, const char *); if (arg == NULL) { /* '%s' and NULL is not portable, so special case * it for debug printing. */ duk_fb_sprintf(&fb, "NULL"); } else { duk_fb_sprintf(&fb, fmtbuf, arg); } } else if (ch == DUK_ASC_LC_P) { /* %p */ void *arg = va_arg(ap, void *); if (arg == NULL) { /* '%p' and NULL is portable, but special case it * anyway to get a standard NULL marker in logs. */ duk_fb_sprintf(&fb, "NULL"); } else { duk_fb_sprintf(&fb, fmtbuf, arg); } } else if (ch == DUK_ASC_LC_C) { /* '%c', passed concretely as int */ int arg = va_arg(ap, int); duk_fb_sprintf(&fb, fmtbuf, arg); } else { /* Should not happen. */ duk_fb_sprintf(&fb, "INVALID-FORMAT(%s)", (const char *) fmtbuf); } break; } else { /* ignore */ } } } goto done; format_error: duk_fb_put_cstring(&fb, "FMTERR"); /* fall through */ done: retval = (duk_int_t) fb.offset; duk_fb_put_byte(&fb, (duk_uint8_t) 0); /* return total chars written excluding terminator */ return retval; } #if 0 /*unused*/ DUK_INTERNAL duk_int_t duk_debug_snprintf(char *str, duk_size_t size, const char *format, ...) { duk_int_t retval; va_list ap; va_start(ap, format); retval = duk_debug_vsnprintf(str, size, format, ap); va_end(ap); return retval; } #endif /* Formatting function pointers is tricky: there is no standard pointer for * function pointers and the size of a function pointer may depend on the * specific pointer type. This helper formats a function pointer based on * its memory layout to get something useful on most platforms. */ DUK_INTERNAL void duk_debug_format_funcptr(char *buf, duk_size_t buf_size, duk_uint8_t *fptr, duk_size_t fptr_size) { duk_size_t i; duk_uint8_t *p = (duk_uint8_t *) buf; duk_uint8_t *p_end = (duk_uint8_t *) (buf + buf_size - 1); DUK_ASSERT(buf != NULL); duk_memzero(buf, buf_size); for (i = 0; i < fptr_size; i++) { duk_int_t left = (duk_int_t) (p_end - p); duk_uint8_t ch; if (left <= 0) { break; } /* Quite approximate but should be useful for little and big endian. */ #if defined(DUK_USE_INTEGER_BE) ch = fptr[i]; #else ch = fptr[fptr_size - 1 - i]; #endif p += DUK_SNPRINTF((char *) p, (duk_size_t) left, "%02lx", (unsigned long) ch); } } #endif /* DUK_USE_DEBUG */ /* automatic undefs */ #undef DUK__ALLOWED_STANDARD_SPECIFIERS #undef DUK__COMMA #undef DUK__DEEP_DEPTH_LIMIT #undef DUK__LOOP_STACK_DEPTH #undef DUK__MAX_FORMAT_TAG_LENGTH #line 1 "duk_debugger.c" /* * Duktape debugger */ /* #include duk_internal.h -> already included */ #if defined(DUK_USE_DEBUGGER_SUPPORT) /* * Assert helpers */ #if defined(DUK_USE_ASSERTIONS) #define DUK__DBG_TPORT_ENTER() \ do { \ DUK_ASSERT(heap->dbg_calling_transport == 0); \ heap->dbg_calling_transport = 1; \ } while (0) #define DUK__DBG_TPORT_EXIT() \ do { \ DUK_ASSERT(heap->dbg_calling_transport == 1); \ heap->dbg_calling_transport = 0; \ } while (0) #else #define DUK__DBG_TPORT_ENTER() \ do { \ } while (0) #define DUK__DBG_TPORT_EXIT() \ do { \ } while (0) #endif /* * Helper structs */ typedef union { void *p; duk_uint_t b[1]; /* Use b[] to access the size of the union, which is strictly not * correct. Can't use fixed size unless there's feature detection * for pointer byte size. */ } duk__ptr_union; /* * Detach handling */ #define DUK__SET_CONN_BROKEN(thr, reason) \ do { \ /* For now shared handler is fine. */ \ duk__debug_do_detach1((thr)->heap, (reason)); \ } while (0) DUK_LOCAL void duk__debug_do_detach1(duk_heap *heap, duk_int_t reason) { /* Can be called multiple times with no harm. Mark the transport * bad (dbg_read_cb == NULL) and clear state except for the detached * callback and the udata field. The detached callback is delayed * to the message loop so that it can be called between messages; * this avoids corner cases related to immediate debugger reattach * inside the detached callback. */ if (heap->dbg_detaching) { DUK_D(DUK_DPRINT("debugger already detaching, ignore detach1")); return; } DUK_D(DUK_DPRINT("debugger transport detaching, marking transport broken")); heap->dbg_detaching = 1; /* prevent multiple in-progress detaches */ if (heap->dbg_write_cb != NULL) { duk_hthread *thr; thr = heap->heap_thread; DUK_ASSERT(thr != NULL); duk_debug_write_notify(thr, DUK_DBG_CMD_DETACHING); duk_debug_write_int(thr, reason); duk_debug_write_eom(thr); } heap->dbg_read_cb = NULL; heap->dbg_write_cb = NULL; heap->dbg_peek_cb = NULL; heap->dbg_read_flush_cb = NULL; heap->dbg_write_flush_cb = NULL; heap->dbg_request_cb = NULL; /* heap->dbg_detached_cb: keep */ /* heap->dbg_udata: keep */ /* heap->dbg_processing: keep on purpose to avoid debugger re-entry in detaching state */ heap->dbg_state_dirty = 0; heap->dbg_force_restart = 0; heap->dbg_pause_flags = 0; heap->dbg_pause_act = NULL; heap->dbg_pause_startline = 0; heap->dbg_have_next_byte = 0; duk_debug_clear_paused(heap); /* XXX: some overlap with field inits above */ heap->dbg_state_dirty = 0; /* XXX: clear_paused sets dirty; rework? */ /* Ensure there are no stale active breakpoint pointers. * Breakpoint list is currently kept - we could empty it * here but we'd need to handle refcounts correctly, and * we'd need a 'thr' reference for that. * * XXX: clear breakpoint on either attach or detach? */ heap->dbg_breakpoints_active[0] = (duk_breakpoint *) NULL; } DUK_LOCAL void duk__debug_do_detach2(duk_heap *heap) { duk_debug_detached_function detached_cb; void *detached_udata; duk_hthread *thr; thr = heap->heap_thread; if (thr == NULL) { DUK_ASSERT(heap->dbg_detached_cb == NULL); return; } /* Safe to call multiple times. */ detached_cb = heap->dbg_detached_cb; detached_udata = heap->dbg_udata; heap->dbg_detached_cb = NULL; heap->dbg_udata = NULL; if (detached_cb) { /* Careful here: state must be wiped before the call * so that we can cleanly handle a re-attach from * inside the callback. */ DUK_D(DUK_DPRINT("detached during message loop, delayed call to detached_cb")); detached_cb(thr, detached_udata); } heap->dbg_detaching = 0; } DUK_INTERNAL void duk_debug_do_detach(duk_heap *heap) { duk__debug_do_detach1(heap, 0); duk__debug_do_detach2(heap); } /* Called on a read/write error: NULL all callbacks except the detached * callback so that we never accidentally call them after a read/write * error has been indicated. This is especially important for the transport * I/O callbacks to fulfill guaranteed callback semantics. */ DUK_LOCAL void duk__debug_null_most_callbacks(duk_hthread *thr) { duk_heap *heap; DUK_ASSERT(thr != NULL); heap = thr->heap; DUK_D(DUK_DPRINT("transport read/write error, NULL all callbacks expected detached")); heap->dbg_read_cb = NULL; heap->dbg_write_cb = NULL; /* this is especially critical to avoid another write call in detach1() */ heap->dbg_peek_cb = NULL; heap->dbg_read_flush_cb = NULL; heap->dbg_write_flush_cb = NULL; heap->dbg_request_cb = NULL; /* keep heap->dbg_detached_cb */ } /* * Pause handling */ DUK_LOCAL void duk__debug_set_pause_state(duk_hthread *thr, duk_heap *heap, duk_small_uint_t pause_flags) { duk_uint_fast32_t line; line = duk_debug_curr_line(thr); if (line == 0) { /* No line info for current function. */ duk_small_uint_t updated_flags; updated_flags = pause_flags & ~(DUK_PAUSE_FLAG_LINE_CHANGE); DUK_D(DUK_DPRINT("no line info for current activation, disable line-based pause flags: 0x%08lx -> 0x%08lx", (long) pause_flags, (long) updated_flags)); pause_flags = updated_flags; } heap->dbg_pause_flags = pause_flags; heap->dbg_pause_act = thr->callstack_curr; heap->dbg_pause_startline = (duk_uint32_t) line; heap->dbg_state_dirty = 1; DUK_D(DUK_DPRINT("set state for automatic pause triggers, flags=0x%08lx, act=%p, startline=%ld", (long) heap->dbg_pause_flags, (void *) heap->dbg_pause_act, (long) heap->dbg_pause_startline)); } /* * Debug connection peek and flush primitives */ DUK_INTERNAL duk_bool_t duk_debug_read_peek(duk_hthread *thr) { duk_heap *heap; duk_bool_t ret; DUK_ASSERT(thr != NULL); heap = thr->heap; DUK_ASSERT(heap != NULL); if (heap->dbg_read_cb == NULL) { DUK_D(DUK_DPRINT("attempt to peek in detached state, return zero (= no data)")); return 0; } if (heap->dbg_peek_cb == NULL) { DUK_DD(DUK_DDPRINT("no peek callback, return zero (= no data)")); return 0; } DUK__DBG_TPORT_ENTER(); ret = (duk_bool_t) (heap->dbg_peek_cb(heap->dbg_udata) > 0); DUK__DBG_TPORT_EXIT(); return ret; } DUK_INTERNAL void duk_debug_read_flush(duk_hthread *thr) { duk_heap *heap; DUK_ASSERT(thr != NULL); heap = thr->heap; DUK_ASSERT(heap != NULL); if (heap->dbg_read_cb == NULL) { DUK_D(DUK_DPRINT("attempt to read flush in detached state, ignore")); return; } if (heap->dbg_read_flush_cb == NULL) { DUK_DD(DUK_DDPRINT("no read flush callback, ignore")); return; } DUK__DBG_TPORT_ENTER(); heap->dbg_read_flush_cb(heap->dbg_udata); DUK__DBG_TPORT_EXIT(); } DUK_INTERNAL void duk_debug_write_flush(duk_hthread *thr) { duk_heap *heap; DUK_ASSERT(thr != NULL); heap = thr->heap; DUK_ASSERT(heap != NULL); if (heap->dbg_read_cb == NULL) { DUK_D(DUK_DPRINT("attempt to write flush in detached state, ignore")); return; } if (heap->dbg_write_flush_cb == NULL) { DUK_DD(DUK_DDPRINT("no write flush callback, ignore")); return; } DUK__DBG_TPORT_ENTER(); heap->dbg_write_flush_cb(heap->dbg_udata); DUK__DBG_TPORT_EXIT(); } /* * Debug connection skip primitives */ /* Skip fully. */ DUK_INTERNAL void duk_debug_skip_bytes(duk_hthread *thr, duk_size_t length) { duk_uint8_t dummy[64]; duk_size_t now; DUK_ASSERT(thr != NULL); while (length > 0) { now = (length > sizeof(dummy) ? sizeof(dummy) : length); duk_debug_read_bytes(thr, dummy, now); length -= now; } } DUK_INTERNAL void duk_debug_skip_byte(duk_hthread *thr) { DUK_ASSERT(thr != NULL); (void) duk_debug_read_byte(thr); } /* * Debug connection read primitives */ /* Peek ahead in the stream one byte. */ DUK_INTERNAL uint8_t duk_debug_peek_byte(duk_hthread *thr) { /* It is important not to call this if the last byte read was an EOM. * Reading ahead in this scenario would cause unnecessary blocking if * another message is not available. */ duk_uint8_t x; x = duk_debug_read_byte(thr); thr->heap->dbg_have_next_byte = 1; thr->heap->dbg_next_byte = x; return x; } /* Read fully. */ DUK_INTERNAL void duk_debug_read_bytes(duk_hthread *thr, duk_uint8_t *data, duk_size_t length) { duk_heap *heap; duk_uint8_t *p; duk_size_t left; duk_size_t got; DUK_ASSERT(thr != NULL); heap = thr->heap; DUK_ASSERT(heap != NULL); DUK_ASSERT(data != NULL); if (heap->dbg_read_cb == NULL) { DUK_D(DUK_DPRINT("attempt to read %ld bytes in detached state, return zero data", (long) length)); goto fail; } /* NOTE: length may be zero */ p = data; if (length >= 1 && heap->dbg_have_next_byte) { heap->dbg_have_next_byte = 0; *p++ = heap->dbg_next_byte; } for (;;) { left = (duk_size_t) ((data + length) - p); if (left == 0) { break; } DUK_ASSERT(heap->dbg_read_cb != NULL); DUK_ASSERT(left >= 1); #if defined(DUK_USE_DEBUGGER_TRANSPORT_TORTURE) left = 1; #endif DUK__DBG_TPORT_ENTER(); got = heap->dbg_read_cb(heap->dbg_udata, (char *) p, left); DUK__DBG_TPORT_EXIT(); if (got == 0 || got > left) { DUK_D(DUK_DPRINT("connection error during read, return zero data")); duk__debug_null_most_callbacks(thr); /* avoid calling write callback in detach1() */ DUK__SET_CONN_BROKEN(thr, 1); goto fail; } p += got; } return; fail: duk_memzero((void *) data, (size_t) length); } DUK_INTERNAL duk_uint8_t duk_debug_read_byte(duk_hthread *thr) { duk_uint8_t x; x = 0; /* just in case callback is broken and won't write 'x' */ duk_debug_read_bytes(thr, &x, 1); return x; } DUK_LOCAL duk_uint32_t duk__debug_read_uint32_raw(duk_hthread *thr) { duk_uint8_t buf[4]; DUK_ASSERT(thr != NULL); duk_debug_read_bytes(thr, buf, 4); return ((duk_uint32_t) buf[0] << 24) | ((duk_uint32_t) buf[1] << 16) | ((duk_uint32_t) buf[2] << 8) | (duk_uint32_t) buf[3]; } DUK_LOCAL duk_int32_t duk__debug_read_int32_raw(duk_hthread *thr) { return (duk_int32_t) duk__debug_read_uint32_raw(thr); } DUK_LOCAL duk_uint16_t duk__debug_read_uint16_raw(duk_hthread *thr) { duk_uint8_t buf[2]; DUK_ASSERT(thr != NULL); duk_debug_read_bytes(thr, buf, 2); return ((duk_uint16_t) buf[0] << 8) | (duk_uint16_t) buf[1]; } DUK_INTERNAL duk_int32_t duk_debug_read_int(duk_hthread *thr) { duk_small_uint_t x; duk_small_uint_t t; DUK_ASSERT(thr != NULL); x = duk_debug_read_byte(thr); if (x >= 0xc0) { t = duk_debug_read_byte(thr); return (duk_int32_t) (((x - 0xc0) << 8) + t); } else if (x >= 0x80) { return (duk_int32_t) (x - 0x80); } else if (x == DUK_DBG_IB_INT4) { return (duk_int32_t) duk__debug_read_uint32_raw(thr); } DUK_D(DUK_DPRINT("debug connection error: failed to decode int")); DUK__SET_CONN_BROKEN(thr, 1); return 0; } DUK_LOCAL duk_hstring *duk__debug_read_hstring_raw(duk_hthread *thr, duk_uint32_t len) { duk_uint8_t buf[31]; duk_uint8_t *p; if (len <= sizeof(buf)) { duk_debug_read_bytes(thr, buf, (duk_size_t) len); duk_push_lstring(thr, (const char *) buf, (duk_size_t) len); } else { p = (duk_uint8_t *) duk_push_fixed_buffer(thr, (duk_size_t) len); /* zero for paranoia */ DUK_ASSERT(p != NULL); duk_debug_read_bytes(thr, p, (duk_size_t) len); (void) duk_buffer_to_string(thr, -1); /* Safety relies on debug client, which is OK. */ } return duk_require_hstring(thr, -1); } DUK_INTERNAL duk_hstring *duk_debug_read_hstring(duk_hthread *thr) { duk_small_uint_t x; duk_uint32_t len; DUK_ASSERT(thr != NULL); x = duk_debug_read_byte(thr); if (x >= 0x60 && x <= 0x7f) { /* For short strings, use a fixed temp buffer. */ len = (duk_uint32_t) (x - 0x60); } else if (x == DUK_DBG_IB_STR2) { len = (duk_uint32_t) duk__debug_read_uint16_raw(thr); } else if (x == DUK_DBG_IB_STR4) { len = (duk_uint32_t) duk__debug_read_uint32_raw(thr); } else { goto fail; } return duk__debug_read_hstring_raw(thr, len); fail: DUK_D(DUK_DPRINT("debug connection error: failed to decode int")); DUK__SET_CONN_BROKEN(thr, 1); duk_push_hstring_empty(thr); /* always push some string */ return duk_require_hstring(thr, -1); } DUK_LOCAL duk_hbuffer *duk__debug_read_hbuffer_raw(duk_hthread *thr, duk_uint32_t len) { duk_uint8_t *p; p = (duk_uint8_t *) duk_push_fixed_buffer(thr, (duk_size_t) len); /* zero for paranoia */ DUK_ASSERT(p != NULL); duk_debug_read_bytes(thr, p, (duk_size_t) len); return duk_require_hbuffer(thr, -1); } DUK_LOCAL void *duk__debug_read_pointer_raw(duk_hthread *thr) { duk_small_uint_t x; duk__ptr_union pu; DUK_ASSERT(thr != NULL); x = duk_debug_read_byte(thr); if (x != sizeof(pu)) { goto fail; } duk_debug_read_bytes(thr, (duk_uint8_t *) &pu.p, sizeof(pu)); #if defined(DUK_USE_INTEGER_LE) duk_byteswap_bytes((duk_uint8_t *) pu.b, sizeof(pu)); #endif return (void *) pu.p; fail: DUK_D(DUK_DPRINT("debug connection error: failed to decode pointer")); DUK__SET_CONN_BROKEN(thr, 1); return (void *) NULL; } DUK_LOCAL duk_double_t duk__debug_read_double_raw(duk_hthread *thr) { duk_double_union du; DUK_ASSERT(sizeof(du.uc) == 8); duk_debug_read_bytes(thr, (duk_uint8_t *) du.uc, sizeof(du.uc)); DUK_DBLUNION_DOUBLE_NTOH(&du); return du.d; } #if 0 DUK_INTERNAL duk_heaphdr *duk_debug_read_heapptr(duk_hthread *thr) { duk_small_uint_t x; DUK_ASSERT(thr != NULL); x = duk_debug_read_byte(thr); if (x != DUK_DBG_IB_HEAPPTR) { goto fail; } return (duk_heaphdr *) duk__debug_read_pointer_raw(thr); fail: DUK_D(DUK_DPRINT("debug connection error: failed to decode heapptr")); DUK__SET_CONN_BROKEN(thr, 1); return NULL; } #endif DUK_INTERNAL duk_heaphdr *duk_debug_read_any_ptr(duk_hthread *thr) { duk_small_uint_t x; DUK_ASSERT(thr != NULL); x = duk_debug_read_byte(thr); switch (x) { case DUK_DBG_IB_OBJECT: case DUK_DBG_IB_POINTER: case DUK_DBG_IB_HEAPPTR: /* Accept any pointer-like value; for 'object' dvalue, read * and ignore the class number. */ if (x == DUK_DBG_IB_OBJECT) { duk_debug_skip_byte(thr); } break; default: goto fail; } return (duk_heaphdr *) duk__debug_read_pointer_raw(thr); fail: DUK_D(DUK_DPRINT("debug connection error: failed to decode any pointer (object, pointer, heapptr)")); DUK__SET_CONN_BROKEN(thr, 1); return NULL; } DUK_INTERNAL duk_tval *duk_debug_read_tval(duk_hthread *thr) { duk_uint8_t x; duk_uint_t t; duk_uint32_t len; DUK_ASSERT(thr != NULL); x = duk_debug_read_byte(thr); if (x >= 0xc0) { t = (duk_uint_t) (x - 0xc0); t = (t << 8) + duk_debug_read_byte(thr); duk_push_uint(thr, (duk_uint_t) t); goto return_ptr; } if (x >= 0x80) { duk_push_uint(thr, (duk_uint_t) (x - 0x80)); goto return_ptr; } if (x >= 0x60) { len = (duk_uint32_t) (x - 0x60); duk__debug_read_hstring_raw(thr, len); goto return_ptr; } switch (x) { case DUK_DBG_IB_INT4: { duk_int32_t i = duk__debug_read_int32_raw(thr); duk_push_i32(thr, i); break; } case DUK_DBG_IB_STR4: { len = duk__debug_read_uint32_raw(thr); duk__debug_read_hstring_raw(thr, len); break; } case DUK_DBG_IB_STR2: { len = duk__debug_read_uint16_raw(thr); duk__debug_read_hstring_raw(thr, len); break; } case DUK_DBG_IB_BUF4: { len = duk__debug_read_uint32_raw(thr); duk__debug_read_hbuffer_raw(thr, len); break; } case DUK_DBG_IB_BUF2: { len = duk__debug_read_uint16_raw(thr); duk__debug_read_hbuffer_raw(thr, len); break; } case DUK_DBG_IB_UNDEFINED: { duk_push_undefined(thr); break; } case DUK_DBG_IB_NULL: { duk_push_null(thr); break; } case DUK_DBG_IB_TRUE: { duk_push_true(thr); break; } case DUK_DBG_IB_FALSE: { duk_push_false(thr); break; } case DUK_DBG_IB_NUMBER: { duk_double_t d; d = duk__debug_read_double_raw(thr); duk_push_number(thr, d); break; } case DUK_DBG_IB_OBJECT: { duk_heaphdr *h; duk_debug_skip_byte(thr); h = (duk_heaphdr *) duk__debug_read_pointer_raw(thr); duk_push_heapptr(thr, (void *) h); break; } case DUK_DBG_IB_POINTER: { void *ptr; ptr = duk__debug_read_pointer_raw(thr); duk_push_pointer(thr, ptr); break; } case DUK_DBG_IB_LIGHTFUNC: { /* XXX: Not needed for now, so not implemented. Note that * function pointers may have different size/layout than * a void pointer. */ DUK_D(DUK_DPRINT("reading lightfunc values unimplemented")); goto fail; } case DUK_DBG_IB_HEAPPTR: { duk_heaphdr *h; h = (duk_heaphdr *) duk__debug_read_pointer_raw(thr); duk_push_heapptr(thr, (void *) h); break; } case DUK_DBG_IB_UNUSED: /* unused: not accepted in inbound messages */ default: goto fail; } return_ptr: return DUK_GET_TVAL_NEGIDX(thr, -1); fail: DUK_D(DUK_DPRINT("debug connection error: failed to decode tval")); DUK__SET_CONN_BROKEN(thr, 1); return NULL; } /* * Debug connection write primitives */ /* Write fully. */ DUK_INTERNAL void duk_debug_write_bytes(duk_hthread *thr, const duk_uint8_t *data, duk_size_t length) { duk_heap *heap; const duk_uint8_t *p; duk_size_t left; duk_size_t got; DUK_ASSERT(thr != NULL); DUK_ASSERT(length == 0 || data != NULL); heap = thr->heap; DUK_ASSERT(heap != NULL); if (heap->dbg_write_cb == NULL) { DUK_D(DUK_DPRINT("attempt to write %ld bytes in detached state, ignore", (long) length)); return; } if (length == 0) { /* Avoid doing an actual write callback with length == 0, * because that's reserved for a write flush. */ return; } DUK_ASSERT(data != NULL); p = data; for (;;) { left = (duk_size_t) ((data + length) - p); if (left == 0) { break; } DUK_ASSERT(heap->dbg_write_cb != NULL); DUK_ASSERT(left >= 1); #if defined(DUK_USE_DEBUGGER_TRANSPORT_TORTURE) left = 1; #endif DUK__DBG_TPORT_ENTER(); got = heap->dbg_write_cb(heap->dbg_udata, (const char *) p, left); DUK__DBG_TPORT_EXIT(); if (got == 0 || got > left) { duk__debug_null_most_callbacks(thr); /* avoid calling write callback in detach1() */ DUK_D(DUK_DPRINT("connection error during write")); DUK__SET_CONN_BROKEN(thr, 1); return; } p += got; } } DUK_INTERNAL void duk_debug_write_byte(duk_hthread *thr, duk_uint8_t x) { duk_debug_write_bytes(thr, (const duk_uint8_t *) &x, 1); } DUK_INTERNAL void duk_debug_write_unused(duk_hthread *thr) { duk_debug_write_byte(thr, DUK_DBG_IB_UNUSED); } DUK_INTERNAL void duk_debug_write_undefined(duk_hthread *thr) { duk_debug_write_byte(thr, DUK_DBG_IB_UNDEFINED); } #if defined(DUK_USE_DEBUGGER_INSPECT) DUK_INTERNAL void duk_debug_write_null(duk_hthread *thr) { duk_debug_write_byte(thr, DUK_DBG_IB_NULL); } #endif DUK_INTERNAL void duk_debug_write_boolean(duk_hthread *thr, duk_uint_t val) { duk_debug_write_byte(thr, val ? DUK_DBG_IB_TRUE : DUK_DBG_IB_FALSE); } /* Write signed 32-bit integer. */ DUK_INTERNAL void duk_debug_write_int(duk_hthread *thr, duk_int32_t x) { duk_uint8_t buf[5]; duk_size_t len; DUK_ASSERT(thr != NULL); if (x >= 0 && x <= 0x3fL) { buf[0] = (duk_uint8_t) (0x80 + x); len = 1; } else if (x >= 0 && x <= 0x3fffL) { buf[0] = (duk_uint8_t) (0xc0 + (x >> 8)); buf[1] = (duk_uint8_t) (x & 0xff); len = 2; } else { /* Signed integers always map to 4 bytes now. */ buf[0] = (duk_uint8_t) DUK_DBG_IB_INT4; buf[1] = (duk_uint8_t) ((x >> 24) & 0xff); buf[2] = (duk_uint8_t) ((x >> 16) & 0xff); buf[3] = (duk_uint8_t) ((x >> 8) & 0xff); buf[4] = (duk_uint8_t) (x & 0xff); len = 5; } duk_debug_write_bytes(thr, buf, len); } /* Write unsigned 32-bit integer. */ DUK_INTERNAL void duk_debug_write_uint(duk_hthread *thr, duk_uint32_t x) { /* The debugger protocol doesn't support a plain integer encoding for * the full 32-bit unsigned range (only 32-bit signed). For now, * unsigned 32-bit values simply written as signed ones. This is not * a concrete issue except for 32-bit heaphdr fields. Proper solutions * would be to (a) write such integers as IEEE doubles or (b) add an * unsigned 32-bit dvalue. */ if (x >= 0x80000000UL) { DUK_D(DUK_DPRINT("writing unsigned integer 0x%08lx as signed integer", (long) x)); } duk_debug_write_int(thr, (duk_int32_t) x); } DUK_INTERNAL void duk_debug_write_strbuf(duk_hthread *thr, const char *data, duk_size_t length, duk_uint8_t marker_base) { duk_uint8_t buf[5]; duk_size_t buflen; DUK_ASSERT(thr != NULL); DUK_ASSERT(length == 0 || data != NULL); if (length <= 0x1fUL && marker_base == DUK_DBG_IB_STR4) { /* For strings, special form for short lengths. */ buf[0] = (duk_uint8_t) (0x60 + length); buflen = 1; } else if (length <= 0xffffUL) { buf[0] = (duk_uint8_t) (marker_base + 1); buf[1] = (duk_uint8_t) (length >> 8); buf[2] = (duk_uint8_t) (length & 0xff); buflen = 3; } else { buf[0] = (duk_uint8_t) marker_base; buf[1] = (duk_uint8_t) (length >> 24); buf[2] = (duk_uint8_t) ((length >> 16) & 0xff); buf[3] = (duk_uint8_t) ((length >> 8) & 0xff); buf[4] = (duk_uint8_t) (length & 0xff); buflen = 5; } duk_debug_write_bytes(thr, (const duk_uint8_t *) buf, buflen); duk_debug_write_bytes(thr, (const duk_uint8_t *) data, length); } DUK_INTERNAL void duk_debug_write_string(duk_hthread *thr, const char *data, duk_size_t length) { duk_debug_write_strbuf(thr, data, length, DUK_DBG_IB_STR4); } DUK_INTERNAL void duk_debug_write_cstring(duk_hthread *thr, const char *data) { DUK_ASSERT(thr != NULL); duk_debug_write_string(thr, data, data ? DUK_STRLEN(data) : 0); } DUK_INTERNAL void duk_debug_write_hstring(duk_hthread *thr, duk_hstring *h) { DUK_ASSERT(thr != NULL); /* XXX: differentiate null pointer from empty string? */ duk_debug_write_string(thr, (h != NULL ? (const char *) DUK_HSTRING_GET_DATA(h) : NULL), (h != NULL ? (duk_size_t) DUK_HSTRING_GET_BYTELEN(h) : 0)); } DUK_LOCAL void duk__debug_write_hstring_safe_top(duk_hthread *thr) { duk_debug_write_hstring(thr, duk_safe_to_hstring(thr, -1)); } DUK_INTERNAL void duk_debug_write_buffer(duk_hthread *thr, const char *data, duk_size_t length) { duk_debug_write_strbuf(thr, data, length, DUK_DBG_IB_BUF4); } DUK_INTERNAL void duk_debug_write_hbuffer(duk_hthread *thr, duk_hbuffer *h) { DUK_ASSERT(thr != NULL); duk_debug_write_buffer(thr, (h != NULL ? (const char *) DUK_HBUFFER_GET_DATA_PTR(thr->heap, h) : NULL), (h != NULL ? (duk_size_t) DUK_HBUFFER_GET_SIZE(h) : 0)); } DUK_LOCAL void duk__debug_write_pointer_raw(duk_hthread *thr, void *ptr, duk_uint8_t ibyte) { duk_uint8_t buf[2]; duk__ptr_union pu; DUK_ASSERT(thr != NULL); DUK_ASSERT(sizeof(ptr) >= 1 && sizeof(ptr) <= 16); /* ptr may be NULL */ buf[0] = ibyte; buf[1] = sizeof(pu); duk_debug_write_bytes(thr, buf, 2); pu.p = (void *) ptr; #if defined(DUK_USE_INTEGER_LE) duk_byteswap_bytes((duk_uint8_t *) pu.b, sizeof(pu)); #endif duk_debug_write_bytes(thr, (const duk_uint8_t *) &pu.p, (duk_size_t) sizeof(pu)); } DUK_INTERNAL void duk_debug_write_pointer(duk_hthread *thr, void *ptr) { duk__debug_write_pointer_raw(thr, ptr, DUK_DBG_IB_POINTER); } #if defined(DUK_USE_DEBUGGER_DUMPHEAP) || defined(DUK_USE_DEBUGGER_INSPECT) DUK_INTERNAL void duk_debug_write_heapptr(duk_hthread *thr, duk_heaphdr *h) { duk__debug_write_pointer_raw(thr, (void *) h, DUK_DBG_IB_HEAPPTR); } #endif /* DUK_USE_DEBUGGER_DUMPHEAP || DUK_USE_DEBUGGER_INSPECT */ DUK_INTERNAL void duk_debug_write_hobject(duk_hthread *thr, duk_hobject *obj) { duk_uint8_t buf[3]; duk__ptr_union pu; DUK_ASSERT(thr != NULL); DUK_ASSERT(sizeof(obj) >= 1 && sizeof(obj) <= 16); DUK_ASSERT(obj != NULL); buf[0] = DUK_DBG_IB_OBJECT; buf[1] = (duk_uint8_t) DUK_HOBJECT_GET_CLASS_NUMBER(obj); buf[2] = sizeof(pu); duk_debug_write_bytes(thr, buf, 3); pu.p = (void *) obj; #if defined(DUK_USE_INTEGER_LE) duk_byteswap_bytes((duk_uint8_t *) pu.b, sizeof(pu)); #endif duk_debug_write_bytes(thr, (const duk_uint8_t *) &pu.p, (duk_size_t) sizeof(pu)); } DUK_INTERNAL void duk_debug_write_tval(duk_hthread *thr, duk_tval *tv) { duk_c_function lf_func; duk_small_uint_t lf_flags; duk_uint8_t buf[4]; duk_double_union du1; duk_double_union du2; duk_int32_t i32; DUK_ASSERT(thr != NULL); DUK_ASSERT(tv != NULL); switch (DUK_TVAL_GET_TAG(tv)) { case DUK_TAG_UNDEFINED: duk_debug_write_byte(thr, DUK_DBG_IB_UNDEFINED); break; case DUK_TAG_UNUSED: duk_debug_write_byte(thr, DUK_DBG_IB_UNUSED); break; case DUK_TAG_NULL: duk_debug_write_byte(thr, DUK_DBG_IB_NULL); break; case DUK_TAG_BOOLEAN: DUK_ASSERT(DUK_TVAL_GET_BOOLEAN(tv) == 0 || DUK_TVAL_GET_BOOLEAN(tv) == 1); duk_debug_write_boolean(thr, DUK_TVAL_GET_BOOLEAN(tv)); break; case DUK_TAG_POINTER: duk_debug_write_pointer(thr, (void *) DUK_TVAL_GET_POINTER(tv)); break; case DUK_TAG_LIGHTFUNC: DUK_TVAL_GET_LIGHTFUNC(tv, lf_func, lf_flags); buf[0] = DUK_DBG_IB_LIGHTFUNC; buf[1] = (duk_uint8_t) (lf_flags >> 8); buf[2] = (duk_uint8_t) (lf_flags & 0xff); buf[3] = sizeof(lf_func); duk_debug_write_bytes(thr, buf, 4); duk_debug_write_bytes(thr, (const duk_uint8_t *) &lf_func, sizeof(lf_func)); break; case DUK_TAG_STRING: duk_debug_write_hstring(thr, DUK_TVAL_GET_STRING(tv)); break; case DUK_TAG_OBJECT: duk_debug_write_hobject(thr, DUK_TVAL_GET_OBJECT(tv)); break; case DUK_TAG_BUFFER: duk_debug_write_hbuffer(thr, DUK_TVAL_GET_BUFFER(tv)); break; #if defined(DUK_USE_FASTINT) case DUK_TAG_FASTINT: #endif default: /* Numbers are normalized to big (network) endian. We can * (but are not required) to use integer dvalues when there's * no loss of precision. * * XXX: share check with other code; this check is slow but * reliable and doesn't require careful exponent/mantissa * mask tricks as in the fastint downgrade code. */ DUK_ASSERT(!DUK_TVAL_IS_UNUSED(tv)); DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv)); du1.d = DUK_TVAL_GET_NUMBER(tv); i32 = (duk_int32_t) du1.d; du2.d = (duk_double_t) i32; DUK_DD(DUK_DDPRINT("i32=%ld du1=%02x%02x%02x%02x%02x%02x%02x%02x " "du2=%02x%02x%02x%02x%02x%02x%02x%02x", (long) i32, (unsigned int) du1.uc[0], (unsigned int) du1.uc[1], (unsigned int) du1.uc[2], (unsigned int) du1.uc[3], (unsigned int) du1.uc[4], (unsigned int) du1.uc[5], (unsigned int) du1.uc[6], (unsigned int) du1.uc[7], (unsigned int) du2.uc[0], (unsigned int) du2.uc[1], (unsigned int) du2.uc[2], (unsigned int) du2.uc[3], (unsigned int) du2.uc[4], (unsigned int) du2.uc[5], (unsigned int) du2.uc[6], (unsigned int) du2.uc[7])); if (duk_memcmp((const void *) du1.uc, (const void *) du2.uc, sizeof(du1.uc)) == 0) { duk_debug_write_int(thr, i32); } else { DUK_DBLUNION_DOUBLE_HTON(&du1); duk_debug_write_byte(thr, DUK_DBG_IB_NUMBER); duk_debug_write_bytes(thr, (const duk_uint8_t *) du1.uc, sizeof(du1.uc)); } } } #if defined(DUK_USE_DEBUGGER_DUMPHEAP) /* Variant for writing duk_tvals so that any heap allocated values are * written out as tagged heap pointers. */ DUK_LOCAL void duk__debug_write_tval_heapptr(duk_hthread *thr, duk_tval *tv) { if (DUK_TVAL_IS_HEAP_ALLOCATED(tv)) { duk_heaphdr *h = DUK_TVAL_GET_HEAPHDR(tv); duk_debug_write_heapptr(thr, h); } else { duk_debug_write_tval(thr, tv); } } #endif /* DUK_USE_DEBUGGER_DUMPHEAP */ /* * Debug connection message write helpers */ #if 0 /* unused */ DUK_INTERNAL void duk_debug_write_request(duk_hthread *thr, duk_small_uint_t command) { duk_debug_write_byte(thr, DUK_DBG_IB_REQUEST); duk_debug_write_int(thr, command); } #endif DUK_INTERNAL void duk_debug_write_reply(duk_hthread *thr) { duk_debug_write_byte(thr, DUK_DBG_IB_REPLY); } DUK_INTERNAL void duk_debug_write_error_eom(duk_hthread *thr, duk_small_uint_t err_code, const char *msg) { /* Allow NULL 'msg' */ duk_debug_write_byte(thr, DUK_DBG_IB_ERROR); duk_debug_write_int(thr, (duk_int32_t) err_code); duk_debug_write_cstring(thr, msg); duk_debug_write_eom(thr); } DUK_INTERNAL void duk_debug_write_notify(duk_hthread *thr, duk_small_uint_t command) { duk_debug_write_byte(thr, DUK_DBG_IB_NOTIFY); duk_debug_write_int(thr, (duk_int32_t) command); } DUK_INTERNAL void duk_debug_write_eom(duk_hthread *thr) { duk_debug_write_byte(thr, DUK_DBG_IB_EOM); /* As an initial implementation, write flush after every EOM (and the * version identifier). A better implementation would flush only when * Duktape is finished processing messages so that a flush only happens * after all outbound messages are finished on that occasion. */ duk_debug_write_flush(thr); } /* * Status message and helpers */ DUK_INTERNAL duk_uint_fast32_t duk_debug_curr_line(duk_hthread *thr) { duk_activation *act; duk_uint_fast32_t line; duk_uint_fast32_t pc; act = thr->callstack_curr; if (act == NULL) { return 0; } /* We're conceptually between two opcodes; act->pc indicates the next * instruction to be executed. This is usually the correct pc/line to * indicate in Status. (For the 'debugger' statement this now reports * the pc/line after the debugger statement because the debugger opcode * has already been executed.) */ pc = duk_hthread_get_act_curr_pc(thr, act); /* XXX: this should be optimized to be a raw query and avoid valstack * operations if possible. */ duk_push_tval(thr, &act->tv_func); line = duk_hobject_pc2line_query(thr, -1, pc); duk_pop(thr); return line; } DUK_INTERNAL void duk_debug_send_status(duk_hthread *thr) { duk_activation *act; duk_debug_write_notify(thr, DUK_DBG_CMD_STATUS); duk_debug_write_int(thr, (DUK_HEAP_HAS_DEBUGGER_PAUSED(thr->heap) ? 1 : 0)); act = thr->callstack_curr; if (act == NULL) { duk_debug_write_undefined(thr); duk_debug_write_undefined(thr); duk_debug_write_int(thr, 0); duk_debug_write_int(thr, 0); } else { duk_push_tval(thr, &act->tv_func); duk_get_prop_literal(thr, -1, "fileName"); duk__debug_write_hstring_safe_top(thr); duk_get_prop_literal(thr, -2, "name"); duk__debug_write_hstring_safe_top(thr); duk_pop_3(thr); /* Report next pc/line to be executed. */ duk_debug_write_uint(thr, (duk_uint32_t) duk_debug_curr_line(thr)); duk_debug_write_uint(thr, (duk_uint32_t) duk_hthread_get_act_curr_pc(thr, act)); } duk_debug_write_eom(thr); } #if defined(DUK_USE_DEBUGGER_THROW_NOTIFY) DUK_INTERNAL void duk_debug_send_throw(duk_hthread *thr, duk_bool_t fatal) { /* * NFY <int: 5> <int: fatal> <str: msg> <str: filename> <int: linenumber> EOM */ duk_activation *act; duk_uint32_t pc; DUK_ASSERT(thr->valstack_top > thr->valstack); /* At least: ... [err] */ duk_debug_write_notify(thr, DUK_DBG_CMD_THROW); duk_debug_write_int(thr, (duk_int32_t) fatal); /* Report thrown value to client coerced to string */ duk_dup_top(thr); duk__debug_write_hstring_safe_top(thr); duk_pop(thr); if (duk_is_error(thr, -1)) { /* Error instance, use augmented error data directly */ duk_get_prop_stridx_short(thr, -1, DUK_STRIDX_FILE_NAME); duk__debug_write_hstring_safe_top(thr); duk_get_prop_stridx_short(thr, -2, DUK_STRIDX_LINE_NUMBER); duk_debug_write_uint(thr, duk_get_uint(thr, -1)); duk_pop_2(thr); } else { /* For anything other than an Error instance, we calculate the * error location directly from the current activation if one * exists. */ act = thr->callstack_curr; if (act != NULL) { duk_push_tval(thr, &act->tv_func); duk_get_prop_literal(thr, -1, "fileName"); duk__debug_write_hstring_safe_top(thr); pc = (duk_uint32_t) duk_hthread_get_act_prev_pc(thr, act); duk_debug_write_uint(thr, (duk_uint32_t) duk_hobject_pc2line_query(thr, -2, pc)); duk_pop_2(thr); } else { /* Can happen if duk_throw() is called on an empty * callstack. */ duk_debug_write_cstring(thr, ""); duk_debug_write_uint(thr, 0); } } duk_debug_write_eom(thr); } #endif /* DUK_USE_DEBUGGER_THROW_NOTIFY */ /* * Debug message processing */ /* Skip dvalue. */ DUK_LOCAL duk_bool_t duk__debug_skip_dvalue(duk_hthread *thr) { duk_uint8_t x; duk_uint32_t len; x = duk_debug_read_byte(thr); if (x >= 0xc0) { duk_debug_skip_byte(thr); return 0; } if (x >= 0x80) { return 0; } if (x >= 0x60) { duk_debug_skip_bytes(thr, (duk_size_t) (x - 0x60)); return 0; } switch (x) { case DUK_DBG_IB_EOM: return 1; /* Return 1: got EOM */ case DUK_DBG_IB_REQUEST: case DUK_DBG_IB_REPLY: case DUK_DBG_IB_ERROR: case DUK_DBG_IB_NOTIFY: break; case DUK_DBG_IB_INT4: (void) duk__debug_read_uint32_raw(thr); break; case DUK_DBG_IB_STR4: case DUK_DBG_IB_BUF4: len = duk__debug_read_uint32_raw(thr); duk_debug_skip_bytes(thr, len); break; case DUK_DBG_IB_STR2: case DUK_DBG_IB_BUF2: len = duk__debug_read_uint16_raw(thr); duk_debug_skip_bytes(thr, len); break; case DUK_DBG_IB_UNUSED: case DUK_DBG_IB_UNDEFINED: case DUK_DBG_IB_NULL: case DUK_DBG_IB_TRUE: case DUK_DBG_IB_FALSE: break; case DUK_DBG_IB_NUMBER: duk_debug_skip_bytes(thr, 8); break; case DUK_DBG_IB_OBJECT: duk_debug_skip_byte(thr); len = duk_debug_read_byte(thr); duk_debug_skip_bytes(thr, len); break; case DUK_DBG_IB_POINTER: case DUK_DBG_IB_HEAPPTR: len = duk_debug_read_byte(thr); duk_debug_skip_bytes(thr, len); break; case DUK_DBG_IB_LIGHTFUNC: duk_debug_skip_bytes(thr, 2); len = duk_debug_read_byte(thr); duk_debug_skip_bytes(thr, len); break; default: goto fail; } return 0; fail: DUK__SET_CONN_BROKEN(thr, 1); return 1; /* Pretend like we got EOM */ } /* Skip dvalues to EOM. */ DUK_LOCAL void duk__debug_skip_to_eom(duk_hthread *thr) { for (;;) { if (duk__debug_skip_dvalue(thr)) { break; } } } /* Read and validate a call stack index. If index is invalid, write out an * error message and return zero. */ DUK_LOCAL duk_int32_t duk__debug_read_validate_csindex(duk_hthread *thr) { duk_int32_t level; level = duk_debug_read_int(thr); if (level >= 0 || -level > (duk_int32_t) thr->callstack_top) { duk_debug_write_error_eom(thr, DUK_DBG_ERR_NOTFOUND, "invalid callstack index"); return 0; /* zero indicates failure */ } return level; } /* Read a call stack index and lookup the corresponding duk_activation. * If index is invalid, write out an error message and return NULL. */ DUK_LOCAL duk_activation *duk__debug_read_level_get_activation(duk_hthread *thr) { duk_activation *act; duk_int32_t level; level = duk_debug_read_int(thr); act = duk_hthread_get_activation_for_level(thr, level); if (act == NULL) { duk_debug_write_error_eom(thr, DUK_DBG_ERR_NOTFOUND, "invalid callstack index"); } return act; } /* * Simple commands */ DUK_LOCAL void duk__debug_handle_basic_info(duk_hthread *thr, duk_heap *heap) { DUK_UNREF(heap); DUK_D(DUK_DPRINT("debug command Version")); duk_debug_write_reply(thr); duk_debug_write_int(thr, DUK_VERSION); duk_debug_write_cstring(thr, DUK_GIT_DESCRIBE); duk_debug_write_cstring(thr, DUK_USE_TARGET_INFO); #if defined(DUK_USE_DOUBLE_LE) duk_debug_write_int(thr, 1); #elif defined(DUK_USE_DOUBLE_ME) duk_debug_write_int(thr, 2); #elif defined(DUK_USE_DOUBLE_BE) duk_debug_write_int(thr, 3); #else duk_debug_write_int(thr, 0); #endif duk_debug_write_int(thr, (duk_int_t) sizeof(void *)); duk_debug_write_eom(thr); } DUK_LOCAL void duk__debug_handle_trigger_status(duk_hthread *thr, duk_heap *heap) { DUK_UNREF(heap); DUK_D(DUK_DPRINT("debug command TriggerStatus")); duk_debug_write_reply(thr); duk_debug_write_eom(thr); heap->dbg_state_dirty = 1; } DUK_LOCAL void duk__debug_handle_pause(duk_hthread *thr, duk_heap *heap) { DUK_D(DUK_DPRINT("debug command Pause")); duk_debug_set_paused(heap); duk_debug_write_reply(thr); duk_debug_write_eom(thr); } DUK_LOCAL void duk__debug_handle_resume(duk_hthread *thr, duk_heap *heap) { duk_small_uint_t pause_flags; DUK_D(DUK_DPRINT("debug command Resume")); duk_debug_clear_paused(heap); pause_flags = 0; #if 0 /* manual testing */ pause_flags |= DUK_PAUSE_FLAG_ONE_OPCODE; pause_flags |= DUK_PAUSE_FLAG_CAUGHT_ERROR; pause_flags |= DUK_PAUSE_FLAG_UNCAUGHT_ERROR; #endif #if defined(DUK_USE_DEBUGGER_PAUSE_UNCAUGHT) pause_flags |= DUK_PAUSE_FLAG_UNCAUGHT_ERROR; #endif duk__debug_set_pause_state(thr, heap, pause_flags); duk_debug_write_reply(thr); duk_debug_write_eom(thr); } DUK_LOCAL void duk__debug_handle_step(duk_hthread *thr, duk_heap *heap, duk_int32_t cmd) { duk_small_uint_t pause_flags; DUK_D(DUK_DPRINT("debug command StepInto/StepOver/StepOut: %d", (int) cmd)); if (cmd == DUK_DBG_CMD_STEPINTO) { pause_flags = DUK_PAUSE_FLAG_LINE_CHANGE | DUK_PAUSE_FLAG_FUNC_ENTRY | DUK_PAUSE_FLAG_FUNC_EXIT; } else if (cmd == DUK_DBG_CMD_STEPOVER) { pause_flags = DUK_PAUSE_FLAG_LINE_CHANGE | DUK_PAUSE_FLAG_FUNC_EXIT; } else { DUK_ASSERT(cmd == DUK_DBG_CMD_STEPOUT); pause_flags = DUK_PAUSE_FLAG_FUNC_EXIT; } #if defined(DUK_USE_DEBUGGER_PAUSE_UNCAUGHT) pause_flags |= DUK_PAUSE_FLAG_UNCAUGHT_ERROR; #endif /* If current activation doesn't have line information, line-based * pause flags are automatically disabled. As a result, e.g. * StepInto will then pause on (native) function entry or exit. */ duk_debug_clear_paused(heap); duk__debug_set_pause_state(thr, heap, pause_flags); duk_debug_write_reply(thr); duk_debug_write_eom(thr); } DUK_LOCAL void duk__debug_handle_list_break(duk_hthread *thr, duk_heap *heap) { duk_small_int_t i; DUK_D(DUK_DPRINT("debug command ListBreak")); duk_debug_write_reply(thr); for (i = 0; i < (duk_small_int_t) heap->dbg_breakpoint_count; i++) { duk_debug_write_hstring(thr, heap->dbg_breakpoints[i].filename); duk_debug_write_uint(thr, (duk_uint32_t) heap->dbg_breakpoints[i].line); } duk_debug_write_eom(thr); } DUK_LOCAL void duk__debug_handle_add_break(duk_hthread *thr, duk_heap *heap) { duk_hstring *filename; duk_uint32_t linenumber; duk_small_int_t idx; DUK_UNREF(heap); filename = duk_debug_read_hstring(thr); linenumber = (duk_uint32_t) duk_debug_read_int(thr); DUK_D(DUK_DPRINT("debug command AddBreak: %!O:%ld", (duk_hobject *) filename, (long) linenumber)); idx = duk_debug_add_breakpoint(thr, filename, linenumber); if (idx >= 0) { duk_debug_write_reply(thr); duk_debug_write_int(thr, (duk_int32_t) idx); duk_debug_write_eom(thr); } else { duk_debug_write_error_eom(thr, DUK_DBG_ERR_TOOMANY, "no space for breakpoint"); } } DUK_LOCAL void duk__debug_handle_del_break(duk_hthread *thr, duk_heap *heap) { duk_small_uint_t idx; DUK_UNREF(heap); DUK_D(DUK_DPRINT("debug command DelBreak")); idx = (duk_small_uint_t) duk_debug_read_int(thr); if (duk_debug_remove_breakpoint(thr, idx)) { duk_debug_write_reply(thr); duk_debug_write_eom(thr); } else { duk_debug_write_error_eom(thr, DUK_DBG_ERR_NOTFOUND, "invalid breakpoint index"); } } DUK_LOCAL void duk__debug_handle_get_var(duk_hthread *thr, duk_heap *heap) { duk_activation *act; duk_hstring *str; duk_bool_t rc; DUK_UNREF(heap); DUK_D(DUK_DPRINT("debug command GetVar")); act = duk__debug_read_level_get_activation(thr); if (act == NULL) { return; } str = duk_debug_read_hstring(thr); /* push to stack */ DUK_ASSERT(str != NULL); rc = duk_js_getvar_activation(thr, act, str, 0); duk_debug_write_reply(thr); if (rc) { duk_debug_write_int(thr, 1); DUK_ASSERT(duk_get_tval(thr, -2) != NULL); duk_debug_write_tval(thr, duk_get_tval(thr, -2)); } else { duk_debug_write_int(thr, 0); duk_debug_write_unused(thr); } duk_debug_write_eom(thr); } DUK_LOCAL void duk__debug_handle_put_var(duk_hthread *thr, duk_heap *heap) { duk_activation *act; duk_hstring *str; duk_tval *tv; DUK_UNREF(heap); DUK_D(DUK_DPRINT("debug command PutVar")); act = duk__debug_read_level_get_activation(thr); if (act == NULL) { return; } str = duk_debug_read_hstring(thr); /* push to stack */ DUK_ASSERT(str != NULL); tv = duk_debug_read_tval(thr); if (tv == NULL) { /* detached */ return; } duk_js_putvar_activation(thr, act, str, tv, 0); /* XXX: Current putvar implementation doesn't have a success flag, * add one and send to debug client? */ duk_debug_write_reply(thr); duk_debug_write_eom(thr); } DUK_LOCAL void duk__debug_handle_get_call_stack(duk_hthread *thr, duk_heap *heap) { duk_hthread *curr_thr = thr; duk_activation *curr_act; duk_uint_fast32_t pc; duk_uint_fast32_t line; DUK_ASSERT(thr != NULL); DUK_UNREF(heap); duk_debug_write_reply(thr); while (curr_thr != NULL) { for (curr_act = curr_thr->callstack_curr; curr_act != NULL; curr_act = curr_act->parent) { /* PC/line semantics here are: * - For callstack top we're conceptually between two * opcodes and current PC indicates next line to * execute, so report that (matches Status). * - For other activations we're conceptually still * executing the instruction at PC-1, so report that * (matches error stacktrace behavior). * - See: https://github.com/svaarala/duktape/issues/281 */ /* XXX: optimize to use direct reads, i.e. avoid * value stack operations. */ duk_push_tval(thr, &curr_act->tv_func); duk_get_prop_stridx_short(thr, -1, DUK_STRIDX_FILE_NAME); duk__debug_write_hstring_safe_top(thr); duk_get_prop_stridx_short(thr, -2, DUK_STRIDX_NAME); duk__debug_write_hstring_safe_top(thr); pc = duk_hthread_get_act_curr_pc(thr, curr_act); if (curr_act != curr_thr->callstack_curr && pc > 0) { pc--; } line = duk_hobject_pc2line_query(thr, -3, pc); duk_debug_write_uint(thr, (duk_uint32_t) line); duk_debug_write_uint(thr, (duk_uint32_t) pc); duk_pop_3(thr); } curr_thr = curr_thr->resumer; } /* SCANBUILD: warning about 'thr' potentially being NULL here, * warning is incorrect because thr != NULL always here. */ duk_debug_write_eom(thr); } DUK_LOCAL void duk__debug_handle_get_locals(duk_hthread *thr, duk_heap *heap) { duk_activation *act; duk_hstring *varname; DUK_UNREF(heap); act = duk__debug_read_level_get_activation(thr); if (act == NULL) { return; } duk_debug_write_reply(thr); /* XXX: several nice-to-have improvements here: * - Use direct reads avoiding value stack operations * - Avoid triggering getters, indicate getter values to debug client * - If side effects are possible, add error catching */ if (DUK_TVAL_IS_OBJECT(&act->tv_func)) { duk_hobject *h_func = DUK_TVAL_GET_OBJECT(&act->tv_func); duk_hobject *h_varmap; h_varmap = duk_hobject_get_varmap(thr, h_func); if (h_varmap != NULL) { duk_push_hobject(thr, h_varmap); duk_enum(thr, -1, 0 /*enum_flags*/); while (duk_next(thr, -1 /*enum_index*/, 0 /*get_value*/)) { varname = duk_known_hstring(thr, -1); duk_js_getvar_activation(thr, act, varname, 0 /*throw_flag*/); /* [ ... func varmap enum key value this ] */ duk_debug_write_hstring(thr, duk_get_hstring(thr, -3)); duk_debug_write_tval(thr, duk_get_tval(thr, -2)); duk_pop_3(thr); /* -> [ ... func varmap enum ] */ } } else { DUK_D(DUK_DPRINT("varmap missing in GetLocals, ignore")); } } else { DUK_D(DUK_DPRINT("varmap is not an object in GetLocals, ignore")); } duk_debug_write_eom(thr); } DUK_LOCAL void duk__debug_handle_eval(duk_hthread *thr, duk_heap *heap) { duk_small_uint_t call_flags; duk_int_t call_ret; duk_small_int_t eval_err; duk_bool_t direct_eval; duk_int32_t level; duk_idx_t idx_func; DUK_UNREF(heap); DUK_D(DUK_DPRINT("debug command Eval")); /* The eval code is executed within the lexical environment of a specified * activation. For now, use global object eval() function, with the eval * considered a 'direct call to eval'. * * Callstack index for debug commands only affects scope -- the callstack * as seen by, e.g. Duktape.act() will be the same regardless. */ /* nargs == 2 so we can pass a callstack index to eval(). */ idx_func = duk_get_top(thr); duk_push_c_function(thr, duk_bi_global_object_eval, 2 /*nargs*/); duk_push_undefined(thr); /* 'this' binding shouldn't matter here */ /* Read callstack index, if non-null. */ if (duk_debug_peek_byte(thr) == DUK_DBG_IB_NULL) { direct_eval = 0; level = -1; /* Not needed, but silences warning. */ (void) duk_debug_read_byte(thr); } else { direct_eval = 1; level = duk__debug_read_validate_csindex(thr); if (level == 0) { return; } } DUK_ASSERT(!direct_eval || (level < 0 && -level <= (duk_int32_t) thr->callstack_top)); (void) duk_debug_read_hstring(thr); if (direct_eval) { duk_push_int(thr, level - 1); /* compensate for eval() call */ } /* [ ... eval "eval" eval_input level? ] */ call_flags = 0; if (direct_eval) { duk_activation *act; duk_hobject *fun; act = duk_hthread_get_activation_for_level(thr, level); if (act != NULL) { fun = DUK_ACT_GET_FUNC(act); if (fun != NULL && DUK_HOBJECT_IS_COMPFUNC(fun)) { /* Direct eval requires that there's a current * activation and it is an ECMAScript function. * When Eval is executed from e.g. cooperate API * call we'll need to do an indirect eval instead. */ call_flags |= DUK_CALL_FLAG_DIRECT_EVAL; } } } call_ret = duk_pcall_method_flags(thr, duk_get_top(thr) - (idx_func + 2), call_flags); if (call_ret == DUK_EXEC_SUCCESS) { eval_err = 0; /* Use result value as is. */ } else { /* For errors a string coerced result is most informative * right now, as the debug client doesn't have the capability * to traverse the error object. */ eval_err = 1; duk_safe_to_string(thr, -1); } /* [ ... result ] */ duk_debug_write_reply(thr); duk_debug_write_int(thr, (duk_int32_t) eval_err); DUK_ASSERT(duk_get_tval(thr, -1) != NULL); duk_debug_write_tval(thr, duk_get_tval(thr, -1)); duk_debug_write_eom(thr); } DUK_LOCAL void duk__debug_handle_detach(duk_hthread *thr, duk_heap *heap) { DUK_UNREF(heap); DUK_D(DUK_DPRINT("debug command Detach")); duk_debug_write_reply(thr); duk_debug_write_eom(thr); DUK_D(DUK_DPRINT("debug connection detached, mark broken")); DUK__SET_CONN_BROKEN(thr, 0); /* not an error */ } DUK_LOCAL void duk__debug_handle_apprequest(duk_hthread *thr, duk_heap *heap) { duk_idx_t old_top; DUK_D(DUK_DPRINT("debug command AppRequest")); old_top = duk_get_top(thr); /* save stack top */ if (heap->dbg_request_cb != NULL) { duk_idx_t nrets; duk_idx_t nvalues = 0; duk_idx_t top, idx; /* Read tvals from the message and push them onto the valstack, * then call the request callback to process the request. */ while (duk_debug_peek_byte(thr) != DUK_DBG_IB_EOM) { duk_tval *tv; if (!duk_check_stack(thr, 1)) { DUK_D(DUK_DPRINT("failed to allocate space for request dvalue(s)")); goto fail; } tv = duk_debug_read_tval(thr); /* push to stack */ if (tv == NULL) { /* detached */ return; } nvalues++; } DUK_ASSERT(duk_get_top(thr) == old_top + nvalues); /* Request callback should push values for reply to client onto valstack */ DUK_D(DUK_DPRINT("calling into AppRequest request_cb with nvalues=%ld, old_top=%ld, top=%ld", (long) nvalues, (long) old_top, (long) duk_get_top(thr))); nrets = heap->dbg_request_cb(thr, heap->dbg_udata, nvalues); DUK_D(DUK_DPRINT("returned from AppRequest request_cb; nvalues=%ld -> nrets=%ld, old_top=%ld, top=%ld", (long) nvalues, (long) nrets, (long) old_top, (long) duk_get_top(thr))); if (nrets >= 0) { DUK_ASSERT(duk_get_top(thr) >= old_top + nrets); if (duk_get_top(thr) < old_top + nrets) { DUK_D(DUK_DPRINT("AppRequest callback doesn't match value stack configuration, " "top=%ld < old_top=%ld + nrets=%ld; " "this might mean it's unsafe to continue!", (long) duk_get_top(thr), (long) old_top, (long) nrets)); goto fail; } /* Reply with tvals pushed by request callback */ duk_debug_write_byte(thr, DUK_DBG_IB_REPLY); top = duk_get_top(thr); for (idx = top - nrets; idx < top; idx++) { duk_debug_write_tval(thr, DUK_GET_TVAL_POSIDX(thr, idx)); } duk_debug_write_eom(thr); } else { DUK_ASSERT(duk_get_top(thr) >= old_top + 1); if (duk_get_top(thr) < old_top + 1) { DUK_D(DUK_DPRINT("request callback return value doesn't match value stack configuration")); goto fail; } duk_debug_write_error_eom(thr, DUK_DBG_ERR_APPLICATION, duk_get_string(thr, -1)); } duk_set_top(thr, old_top); /* restore stack top */ } else { DUK_D(DUK_DPRINT("no request callback, treat AppRequest as unsupported")); duk_debug_write_error_eom(thr, DUK_DBG_ERR_UNSUPPORTED, "AppRequest unsupported by target"); } return; fail: duk_set_top(thr, old_top); /* restore stack top */ DUK__SET_CONN_BROKEN(thr, 1); } /* * DumpHeap command */ #if defined(DUK_USE_DEBUGGER_DUMPHEAP) /* XXX: this has some overlap with object inspection; remove this and make * DumpHeap return lists of heapptrs instead? */ DUK_LOCAL void duk__debug_dump_heaphdr(duk_hthread *thr, duk_heap *heap, duk_heaphdr *hdr) { DUK_UNREF(heap); duk_debug_write_heapptr(thr, hdr); duk_debug_write_uint(thr, (duk_uint32_t) DUK_HEAPHDR_GET_TYPE(hdr)); duk_debug_write_uint(thr, (duk_uint32_t) DUK_HEAPHDR_GET_FLAGS_RAW(hdr)); #if defined(DUK_USE_REFERENCE_COUNTING) duk_debug_write_uint(thr, (duk_uint32_t) DUK_HEAPHDR_GET_REFCOUNT(hdr)); #else duk_debug_write_int(thr, (duk_int32_t) -1); #endif switch (DUK_HEAPHDR_GET_TYPE(hdr)) { case DUK_HTYPE_STRING: { duk_hstring *h = (duk_hstring *) hdr; duk_debug_write_uint(thr, (duk_uint32_t) DUK_HSTRING_GET_BYTELEN(h)); duk_debug_write_uint(thr, (duk_uint32_t) DUK_HSTRING_GET_CHARLEN(h)); duk_debug_write_uint(thr, (duk_uint32_t) DUK_HSTRING_GET_HASH(h)); duk_debug_write_hstring(thr, h); break; } case DUK_HTYPE_OBJECT: { duk_hobject *h = (duk_hobject *) hdr; duk_hstring *k; duk_uint_fast32_t i; duk_debug_write_uint(thr, (duk_uint32_t) DUK_HOBJECT_GET_CLASS_NUMBER(h)); duk_debug_write_heapptr(thr, (duk_heaphdr *) DUK_HOBJECT_GET_PROTOTYPE(heap, h)); duk_debug_write_uint(thr, (duk_uint32_t) DUK_HOBJECT_GET_ESIZE(h)); duk_debug_write_uint(thr, (duk_uint32_t) DUK_HOBJECT_GET_ENEXT(h)); duk_debug_write_uint(thr, (duk_uint32_t) DUK_HOBJECT_GET_ASIZE(h)); duk_debug_write_uint(thr, (duk_uint32_t) DUK_HOBJECT_GET_HSIZE(h)); for (i = 0; i < (duk_uint_fast32_t) DUK_HOBJECT_GET_ENEXT(h); i++) { duk_debug_write_uint(thr, (duk_uint32_t) DUK_HOBJECT_E_GET_FLAGS(heap, h, i)); k = DUK_HOBJECT_E_GET_KEY(heap, h, i); duk_debug_write_heapptr(thr, (duk_heaphdr *) k); if (k == NULL) { duk_debug_write_int(thr, 0); /* isAccessor */ duk_debug_write_unused(thr); continue; } if (DUK_HOBJECT_E_SLOT_IS_ACCESSOR(heap, h, i)) { duk_debug_write_int(thr, 1); /* isAccessor */ duk_debug_write_heapptr(thr, (duk_heaphdr *) DUK_HOBJECT_E_GET_VALUE_PTR(heap, h, i)->a.get); duk_debug_write_heapptr(thr, (duk_heaphdr *) DUK_HOBJECT_E_GET_VALUE_PTR(heap, h, i)->a.set); } else { duk_debug_write_int(thr, 0); /* isAccessor */ duk__debug_write_tval_heapptr(thr, &DUK_HOBJECT_E_GET_VALUE_PTR(heap, h, i)->v); } } for (i = 0; i < (duk_uint_fast32_t) DUK_HOBJECT_GET_ASIZE(h); i++) { /* Note: array dump will include elements beyond * 'length'. */ duk__debug_write_tval_heapptr(thr, DUK_HOBJECT_A_GET_VALUE_PTR(heap, h, i)); } break; } case DUK_HTYPE_BUFFER: { duk_hbuffer *h = (duk_hbuffer *) hdr; duk_debug_write_uint(thr, (duk_uint32_t) DUK_HBUFFER_GET_SIZE(h)); duk_debug_write_buffer(thr, (const char *) DUK_HBUFFER_GET_DATA_PTR(heap, h), (duk_size_t) DUK_HBUFFER_GET_SIZE(h)); break; } default: { DUK_D(DUK_DPRINT("invalid htype: %d", (int) DUK_HEAPHDR_GET_TYPE(hdr))); } } } DUK_LOCAL void duk__debug_dump_heap_allocated(duk_hthread *thr, duk_heap *heap) { duk_heaphdr *hdr; hdr = heap->heap_allocated; while (hdr != NULL) { duk__debug_dump_heaphdr(thr, heap, hdr); hdr = DUK_HEAPHDR_GET_NEXT(heap, hdr); } } DUK_LOCAL void duk__debug_dump_strtab(duk_hthread *thr, duk_heap *heap) { duk_uint32_t i; duk_hstring *h; for (i = 0; i < heap->st_size; i++) { #if defined(DUK_USE_STRTAB_PTRCOMP) h = DUK_USE_HEAPPTR_DEC16((heap)->heap_udata, heap->strtable16[i]); #else h = heap->strtable[i]; #endif while (h != NULL) { duk__debug_dump_heaphdr(thr, heap, (duk_heaphdr *) h); h = h->hdr.h_next; } } } DUK_LOCAL void duk__debug_handle_dump_heap(duk_hthread *thr, duk_heap *heap) { DUK_D(DUK_DPRINT("debug command DumpHeap")); duk_debug_write_reply(thr); duk__debug_dump_heap_allocated(thr, heap); duk__debug_dump_strtab(thr, heap); duk_debug_write_eom(thr); } #endif /* DUK_USE_DEBUGGER_DUMPHEAP */ DUK_LOCAL void duk__debug_handle_get_bytecode(duk_hthread *thr, duk_heap *heap) { duk_activation *act; duk_hcompfunc *fun = NULL; duk_size_t i, n; duk_tval *tv; duk_hobject **fn; duk_int32_t level = -1; duk_uint8_t ibyte; DUK_UNREF(heap); DUK_D(DUK_DPRINT("debug command GetBytecode")); ibyte = duk_debug_peek_byte(thr); if (ibyte != DUK_DBG_IB_EOM) { tv = duk_debug_read_tval(thr); if (tv == NULL) { /* detached */ return; } if (DUK_TVAL_IS_OBJECT(tv)) { /* tentative, checked later */ fun = (duk_hcompfunc *) DUK_TVAL_GET_OBJECT(tv); DUK_ASSERT(fun != NULL); } else if (DUK_TVAL_IS_NUMBER(tv)) { level = (duk_int32_t) DUK_TVAL_GET_NUMBER(tv); } else { DUK_D(DUK_DPRINT("invalid argument to GetBytecode: %!T", tv)); goto fail_args; } } if (fun == NULL) { act = duk_hthread_get_activation_for_level(thr, level); if (act == NULL) { goto fail_index; } fun = (duk_hcompfunc *) DUK_ACT_GET_FUNC(act); } if (fun == NULL || !DUK_HOBJECT_IS_COMPFUNC((duk_hobject *) fun)) { DUK_D(DUK_DPRINT("invalid argument to GetBytecode: %!O", fun)); goto fail_args; } DUK_ASSERT(fun != NULL && DUK_HOBJECT_IS_COMPFUNC((duk_hobject *) fun)); duk_debug_write_reply(thr); n = DUK_HCOMPFUNC_GET_CONSTS_COUNT(heap, fun); duk_debug_write_int(thr, (duk_int32_t) n); tv = DUK_HCOMPFUNC_GET_CONSTS_BASE(heap, fun); for (i = 0; i < n; i++) { duk_debug_write_tval(thr, tv); tv++; } n = DUK_HCOMPFUNC_GET_FUNCS_COUNT(heap, fun); duk_debug_write_int(thr, (duk_int32_t) n); fn = DUK_HCOMPFUNC_GET_FUNCS_BASE(heap, fun); for (i = 0; i < n; i++) { duk_debug_write_hobject(thr, *fn); fn++; } duk_debug_write_string(thr, (const char *) DUK_HCOMPFUNC_GET_CODE_BASE(heap, fun), (duk_size_t) DUK_HCOMPFUNC_GET_CODE_SIZE(heap, fun)); duk_debug_write_eom(thr); return; fail_args: duk_debug_write_error_eom(thr, DUK_DBG_ERR_UNKNOWN, "invalid argument"); return; fail_index: duk_debug_write_error_eom(thr, DUK_DBG_ERR_NOTFOUND, "invalid callstack index"); return; } /* * Object inspection commands: GetHeapObjInfo, GetObjPropDesc, * GetObjPropDescRange */ #if defined(DUK_USE_DEBUGGER_INSPECT) #if 0 /* pruned */ DUK_LOCAL const char * const duk__debug_getinfo_heaphdr_keys[] = { "reachable", "temproot", "finalizable", "finalized", "readonly" /* NULL not needed here */ }; DUK_LOCAL duk_uint_t duk__debug_getinfo_heaphdr_masks[] = { DUK_HEAPHDR_FLAG_REACHABLE, DUK_HEAPHDR_FLAG_TEMPROOT, DUK_HEAPHDR_FLAG_FINALIZABLE, DUK_HEAPHDR_FLAG_FINALIZED, DUK_HEAPHDR_FLAG_READONLY, 0 /* terminator */ }; #endif DUK_LOCAL const char * const duk__debug_getinfo_hstring_keys[] = { #if 0 "arridx", "symbol", "hidden", "reserved_word", "strict_reserved_word", "eval_or_arguments", #endif "extdata" /* NULL not needed here */ }; DUK_LOCAL duk_uint_t duk__debug_getinfo_hstring_masks[] = { #if 0 DUK_HSTRING_FLAG_ARRIDX, DUK_HSTRING_FLAG_SYMBOL, DUK_HSTRING_FLAG_HIDDEN, DUK_HSTRING_FLAG_RESERVED_WORD, DUK_HSTRING_FLAG_STRICT_RESERVED_WORD, DUK_HSTRING_FLAG_EVAL_OR_ARGUMENTS, #endif DUK_HSTRING_FLAG_EXTDATA, 0 /* terminator */ }; DUK_LOCAL const char * const duk__debug_getinfo_hobject_keys[] = { "extensible", "constructable", "callable", "boundfunc", "compfunc", "natfunc", "bufobj", "fastrefs", "array_part", "strict", "notail", "newenv", "namebinding", "createargs", "have_finalizer", "exotic_array", "exotic_stringobj", "exotic_arguments", "exotic_proxyobj", "special_call" /* NULL not needed here */ }; DUK_LOCAL duk_uint_t duk__debug_getinfo_hobject_masks[] = { DUK_HOBJECT_FLAG_EXTENSIBLE, DUK_HOBJECT_FLAG_CONSTRUCTABLE, DUK_HOBJECT_FLAG_CALLABLE, DUK_HOBJECT_FLAG_BOUNDFUNC, DUK_HOBJECT_FLAG_COMPFUNC, DUK_HOBJECT_FLAG_NATFUNC, DUK_HOBJECT_FLAG_BUFOBJ, DUK_HOBJECT_FLAG_FASTREFS, DUK_HOBJECT_FLAG_ARRAY_PART, DUK_HOBJECT_FLAG_STRICT, DUK_HOBJECT_FLAG_NOTAIL, DUK_HOBJECT_FLAG_NEWENV, DUK_HOBJECT_FLAG_NAMEBINDING, DUK_HOBJECT_FLAG_CREATEARGS, DUK_HOBJECT_FLAG_HAVE_FINALIZER, DUK_HOBJECT_FLAG_EXOTIC_ARRAY, DUK_HOBJECT_FLAG_EXOTIC_STRINGOBJ, DUK_HOBJECT_FLAG_EXOTIC_ARGUMENTS, DUK_HOBJECT_FLAG_EXOTIC_PROXYOBJ, DUK_HOBJECT_FLAG_SPECIAL_CALL, 0 /* terminator */ }; DUK_LOCAL const char * const duk__debug_getinfo_hbuffer_keys[] = { "dynamic", "external" /* NULL not needed here */ }; DUK_LOCAL duk_uint_t duk__debug_getinfo_hbuffer_masks[] = { DUK_HBUFFER_FLAG_DYNAMIC, DUK_HBUFFER_FLAG_EXTERNAL, 0 /* terminator */ }; DUK_LOCAL void duk__debug_getinfo_flags_key(duk_hthread *thr, const char *key) { duk_debug_write_uint(thr, 0); duk_debug_write_cstring(thr, key); } DUK_LOCAL void duk__debug_getinfo_prop_uint(duk_hthread *thr, const char *key, duk_uint_t val) { duk_debug_write_uint(thr, 0); duk_debug_write_cstring(thr, key); duk_debug_write_uint(thr, val); } DUK_LOCAL void duk__debug_getinfo_prop_int(duk_hthread *thr, const char *key, duk_int_t val) { duk_debug_write_uint(thr, 0); duk_debug_write_cstring(thr, key); duk_debug_write_int(thr, val); } DUK_LOCAL void duk__debug_getinfo_prop_bool(duk_hthread *thr, const char *key, duk_bool_t val) { duk_debug_write_uint(thr, 0); duk_debug_write_cstring(thr, key); duk_debug_write_boolean(thr, val); } DUK_LOCAL void duk__debug_getinfo_bitmask(duk_hthread *thr, const char * const *keys, duk_uint_t *masks, duk_uint_t flags) { const char *key; duk_uint_t mask; for (;;) { mask = *masks++; if (mask == 0) { break; } key = *keys++; DUK_ASSERT(key != NULL); DUK_DD(DUK_DDPRINT("inspect bitmask: key=%s, mask=0x%08lx, flags=0x%08lx", key, (unsigned long) mask, (unsigned long) flags)); duk__debug_getinfo_prop_bool(thr, key, flags & mask); } } /* Inspect a property using a virtual index into a conceptual property list * consisting of (1) all array part items from [0,a_size[ (even when above * .length) and (2) all entry part items from [0,e_next[. Unused slots are * indicated using dvalue 'unused'. */ DUK_LOCAL duk_bool_t duk__debug_getprop_index(duk_hthread *thr, duk_heap *heap, duk_hobject *h_obj, duk_uint_t idx) { duk_uint_t a_size; duk_tval *tv; duk_hstring *h_key; duk_hobject *h_getset; duk_uint_t flags; DUK_UNREF(heap); a_size = DUK_HOBJECT_GET_ASIZE(h_obj); if (idx < a_size) { duk_debug_write_uint(thr, DUK_PROPDESC_FLAGS_WEC); duk_debug_write_uint(thr, idx); tv = DUK_HOBJECT_A_GET_VALUE_PTR(heap, h_obj, idx); duk_debug_write_tval(thr, tv); return 1; } idx -= a_size; if (idx >= DUK_HOBJECT_GET_ENEXT(h_obj)) { return 0; } h_key = DUK_HOBJECT_E_GET_KEY(heap, h_obj, idx); if (h_key == NULL) { duk_debug_write_uint(thr, 0); duk_debug_write_null(thr); duk_debug_write_unused(thr); return 1; } flags = DUK_HOBJECT_E_GET_FLAGS(heap, h_obj, idx); if (DUK_HSTRING_HAS_SYMBOL(h_key)) { flags |= DUK_DBG_PROPFLAG_SYMBOL; } if (DUK_HSTRING_HAS_HIDDEN(h_key)) { flags |= DUK_DBG_PROPFLAG_HIDDEN; } duk_debug_write_uint(thr, flags); duk_debug_write_hstring(thr, h_key); if (flags & DUK_PROPDESC_FLAG_ACCESSOR) { h_getset = DUK_HOBJECT_E_GET_VALUE_GETTER(heap, h_obj, idx); if (h_getset) { duk_debug_write_hobject(thr, h_getset); } else { duk_debug_write_null(thr); } h_getset = DUK_HOBJECT_E_GET_VALUE_SETTER(heap, h_obj, idx); if (h_getset) { duk_debug_write_hobject(thr, h_getset); } else { duk_debug_write_null(thr); } } else { tv = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(heap, h_obj, idx); duk_debug_write_tval(thr, tv); } return 1; } DUK_LOCAL void duk__debug_handle_get_heap_obj_info(duk_hthread *thr, duk_heap *heap) { duk_heaphdr *h; DUK_D(DUK_DPRINT("debug command GetHeapObjInfo")); DUK_UNREF(heap); DUK_ASSERT(sizeof(duk__debug_getinfo_hstring_keys) / sizeof(const char *) == sizeof(duk__debug_getinfo_hstring_masks) / sizeof(duk_uint_t) - 1); DUK_ASSERT(sizeof(duk__debug_getinfo_hobject_keys) / sizeof(const char *) == sizeof(duk__debug_getinfo_hobject_masks) / sizeof(duk_uint_t) - 1); DUK_ASSERT(sizeof(duk__debug_getinfo_hbuffer_keys) / sizeof(const char *) == sizeof(duk__debug_getinfo_hbuffer_masks) / sizeof(duk_uint_t) - 1); h = duk_debug_read_any_ptr(thr); if (!h) { duk_debug_write_error_eom(thr, DUK_DBG_ERR_UNKNOWN, "invalid target"); return; } duk_debug_write_reply(thr); /* As with all inspection code, we rely on the debug client providing * a valid, non-stale pointer: there's no portable way to safely * validate the pointer here. */ duk__debug_getinfo_flags_key(thr, "heapptr"); duk_debug_write_heapptr(thr, h); /* XXX: comes out as signed now */ duk__debug_getinfo_prop_uint(thr, "heaphdr_flags", (duk_uint_t) DUK_HEAPHDR_GET_FLAGS(h)); duk__debug_getinfo_prop_uint(thr, "heaphdr_type", (duk_uint_t) DUK_HEAPHDR_GET_TYPE(h)); #if defined(DUK_USE_REFERENCE_COUNTING) duk__debug_getinfo_prop_uint(thr, "refcount", (duk_uint_t) DUK_HEAPHDR_GET_REFCOUNT(h)); #endif #if 0 /* pruned */ duk__debug_getinfo_bitmask(thr, duk__debug_getinfo_heaphdr_keys, duk__debug_getinfo_heaphdr_masks, DUK_HEAPHDR_GET_FLAGS_RAW(h)); #endif switch (DUK_HEAPHDR_GET_TYPE(h)) { case DUK_HTYPE_STRING: { duk_hstring *h_str; h_str = (duk_hstring *) h; duk__debug_getinfo_bitmask(thr, duk__debug_getinfo_hstring_keys, duk__debug_getinfo_hstring_masks, DUK_HEAPHDR_GET_FLAGS_RAW(h)); duk__debug_getinfo_prop_uint(thr, "bytelen", (duk_uint_t) DUK_HSTRING_GET_BYTELEN(h_str)); duk__debug_getinfo_prop_uint(thr, "charlen", (duk_uint_t) DUK_HSTRING_GET_CHARLEN(h_str)); duk__debug_getinfo_prop_uint(thr, "hash", (duk_uint_t) DUK_HSTRING_GET_HASH(h_str)); duk__debug_getinfo_flags_key(thr, "data"); duk_debug_write_hstring(thr, h_str); break; } case DUK_HTYPE_OBJECT: { duk_hobject *h_obj; duk_hobject *h_proto; h_obj = (duk_hobject *) h; h_proto = DUK_HOBJECT_GET_PROTOTYPE(heap, h_obj); /* duk_hobject specific fields. */ duk__debug_getinfo_bitmask(thr, duk__debug_getinfo_hobject_keys, duk__debug_getinfo_hobject_masks, DUK_HEAPHDR_GET_FLAGS_RAW(h)); duk__debug_getinfo_prop_uint(thr, "class_number", DUK_HOBJECT_GET_CLASS_NUMBER(h_obj)); duk__debug_getinfo_flags_key(thr, "class_name"); duk_debug_write_hstring(thr, DUK_HOBJECT_GET_CLASS_STRING(heap, h_obj)); duk__debug_getinfo_flags_key(thr, "prototype"); if (h_proto != NULL) { duk_debug_write_hobject(thr, h_proto); } else { duk_debug_write_null(thr); } duk__debug_getinfo_flags_key(thr, "props"); duk_debug_write_pointer(thr, (void *) DUK_HOBJECT_GET_PROPS(heap, h_obj)); duk__debug_getinfo_prop_uint(thr, "e_size", (duk_uint_t) DUK_HOBJECT_GET_ESIZE(h_obj)); duk__debug_getinfo_prop_uint(thr, "e_next", (duk_uint_t) DUK_HOBJECT_GET_ENEXT(h_obj)); duk__debug_getinfo_prop_uint(thr, "a_size", (duk_uint_t) DUK_HOBJECT_GET_ASIZE(h_obj)); duk__debug_getinfo_prop_uint(thr, "h_size", (duk_uint_t) DUK_HOBJECT_GET_HSIZE(h_obj)); if (DUK_HOBJECT_IS_ARRAY(h_obj)) { duk_harray *h_arr; h_arr = (duk_harray *) h_obj; duk__debug_getinfo_prop_uint(thr, "length", (duk_uint_t) h_arr->length); duk__debug_getinfo_prop_bool(thr, "length_nonwritable", h_arr->length_nonwritable); } if (DUK_HOBJECT_IS_NATFUNC(h_obj)) { duk_hnatfunc *h_fun; h_fun = (duk_hnatfunc *) h_obj; duk__debug_getinfo_prop_int(thr, "nargs", h_fun->nargs); duk__debug_getinfo_prop_int(thr, "magic", h_fun->magic); duk__debug_getinfo_prop_bool(thr, "varargs", h_fun->magic == DUK_HNATFUNC_NARGS_VARARGS); /* Native function pointer may be different from a void pointer, * and we serialize it from memory directly now (no byte swapping etc). */ duk__debug_getinfo_flags_key(thr, "funcptr"); duk_debug_write_buffer(thr, (const char *) &h_fun->func, sizeof(h_fun->func)); } if (DUK_HOBJECT_IS_COMPFUNC(h_obj)) { duk_hcompfunc *h_fun; duk_hbuffer *h_buf; duk_hobject *h_lexenv; duk_hobject *h_varenv; h_fun = (duk_hcompfunc *) h_obj; duk__debug_getinfo_prop_int(thr, "nregs", h_fun->nregs); duk__debug_getinfo_prop_int(thr, "nargs", h_fun->nargs); duk__debug_getinfo_flags_key(thr, "lex_env"); h_lexenv = DUK_HCOMPFUNC_GET_LEXENV(thr->heap, h_fun); if (h_lexenv != NULL) { duk_debug_write_hobject(thr, h_lexenv); } else { duk_debug_write_null(thr); } duk__debug_getinfo_flags_key(thr, "var_env"); h_varenv = DUK_HCOMPFUNC_GET_VARENV(thr->heap, h_fun); if (h_varenv != NULL) { duk_debug_write_hobject(thr, h_varenv); } else { duk_debug_write_null(thr); } duk__debug_getinfo_prop_uint(thr, "start_line", h_fun->start_line); duk__debug_getinfo_prop_uint(thr, "end_line", h_fun->end_line); h_buf = (duk_hbuffer *) DUK_HCOMPFUNC_GET_DATA(thr->heap, h_fun); if (h_buf != NULL) { duk__debug_getinfo_flags_key(thr, "data"); duk_debug_write_heapptr(thr, (duk_heaphdr *) h_buf); } } if (DUK_HOBJECT_IS_BOUNDFUNC(h_obj)) { duk_hboundfunc *h_bfun; h_bfun = (duk_hboundfunc *) (void *) h_obj; duk__debug_getinfo_flags_key(thr, "target"); duk_debug_write_tval(thr, &h_bfun->target); duk__debug_getinfo_flags_key(thr, "this_binding"); duk_debug_write_tval(thr, &h_bfun->this_binding); duk__debug_getinfo_flags_key(thr, "nargs"); duk_debug_write_int(thr, h_bfun->nargs); /* h_bfun->args not exposed now */ } if (DUK_HOBJECT_IS_THREAD(h_obj)) { /* XXX: Currently no inspection of threads, e.g. value stack, call * stack, catch stack, etc. */ duk_hthread *h_thr; h_thr = (duk_hthread *) h_obj; DUK_UNREF(h_thr); } if (DUK_HOBJECT_IS_DECENV(h_obj)) { duk_hdecenv *h_env; h_env = (duk_hdecenv *) h_obj; duk__debug_getinfo_flags_key(thr, "thread"); duk_debug_write_heapptr(thr, (duk_heaphdr *) (h_env->thread)); duk__debug_getinfo_flags_key(thr, "varmap"); duk_debug_write_heapptr(thr, (duk_heaphdr *) (h_env->varmap)); duk__debug_getinfo_prop_uint(thr, "regbase", (duk_uint_t) h_env->regbase_byteoff); } if (DUK_HOBJECT_IS_OBJENV(h_obj)) { duk_hobjenv *h_env; h_env = (duk_hobjenv *) h_obj; duk__debug_getinfo_flags_key(thr, "target"); duk_debug_write_heapptr(thr, (duk_heaphdr *) (h_env->target)); duk__debug_getinfo_prop_bool(thr, "has_this", h_env->has_this); } #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) if (DUK_HOBJECT_IS_BUFOBJ(h_obj)) { duk_hbufobj *h_bufobj; h_bufobj = (duk_hbufobj *) h_obj; duk__debug_getinfo_prop_uint(thr, "slice_offset", h_bufobj->offset); duk__debug_getinfo_prop_uint(thr, "slice_length", h_bufobj->length); duk__debug_getinfo_prop_uint(thr, "elem_shift", (duk_uint_t) h_bufobj->shift); duk__debug_getinfo_prop_uint(thr, "elem_type", (duk_uint_t) h_bufobj->elem_type); duk__debug_getinfo_prop_bool(thr, "is_typedarray", (duk_uint_t) h_bufobj->is_typedarray); if (h_bufobj->buf != NULL) { duk__debug_getinfo_flags_key(thr, "buffer"); duk_debug_write_heapptr(thr, (duk_heaphdr *) h_bufobj->buf); } } #endif /* DUK_USE_BUFFEROBJECT_SUPPORT */ break; } case DUK_HTYPE_BUFFER: { duk_hbuffer *h_buf; h_buf = (duk_hbuffer *) h; duk__debug_getinfo_bitmask(thr, duk__debug_getinfo_hbuffer_keys, duk__debug_getinfo_hbuffer_masks, DUK_HEAPHDR_GET_FLAGS_RAW(h)); duk__debug_getinfo_prop_uint(thr, "size", (duk_uint_t) DUK_HBUFFER_GET_SIZE(h_buf)); duk__debug_getinfo_flags_key(thr, "dataptr"); duk_debug_write_pointer(thr, (void *) DUK_HBUFFER_GET_DATA_PTR(thr->heap, h_buf)); duk__debug_getinfo_flags_key(thr, "data"); duk_debug_write_hbuffer(thr, h_buf); /* tolerates NULL h_buf */ break; } default: { /* Since we already started writing the reply, just emit nothing. */ DUK_D(DUK_DPRINT("inspect target pointer has invalid heaphdr type")); } } duk_debug_write_eom(thr); } DUK_LOCAL void duk__debug_handle_get_obj_prop_desc(duk_hthread *thr, duk_heap *heap) { duk_heaphdr *h; duk_hobject *h_obj; duk_hstring *h_key; duk_propdesc desc; DUK_D(DUK_DPRINT("debug command GetObjPropDesc")); DUK_UNREF(heap); h = duk_debug_read_any_ptr(thr); if (!h) { duk_debug_write_error_eom(thr, DUK_DBG_ERR_UNKNOWN, "invalid target"); return; } h_key = duk_debug_read_hstring(thr); if (h == NULL || DUK_HEAPHDR_GET_TYPE(h) != DUK_HTYPE_OBJECT || h_key == NULL) { goto fail_args; } h_obj = (duk_hobject *) h; if (duk_hobject_get_own_propdesc(thr, h_obj, h_key, &desc, 0 /*flags*/)) { duk_int_t virtual_idx; duk_bool_t rc; /* To use the shared helper need the virtual index. */ DUK_ASSERT(desc.e_idx >= 0 || desc.a_idx >= 0); virtual_idx = (desc.a_idx >= 0 ? desc.a_idx : (duk_int_t) DUK_HOBJECT_GET_ASIZE(h_obj) + desc.e_idx); duk_debug_write_reply(thr); rc = duk__debug_getprop_index(thr, heap, h_obj, (duk_uint_t) virtual_idx); DUK_ASSERT(rc == 1); DUK_UNREF(rc); duk_debug_write_eom(thr); } else { duk_debug_write_error_eom(thr, DUK_DBG_ERR_NOTFOUND, "not found"); } return; fail_args: duk_debug_write_error_eom(thr, DUK_DBG_ERR_UNKNOWN, "invalid args"); } DUK_LOCAL void duk__debug_handle_get_obj_prop_desc_range(duk_hthread *thr, duk_heap *heap) { duk_heaphdr *h; duk_hobject *h_obj; duk_uint_t idx, idx_start, idx_end; DUK_D(DUK_DPRINT("debug command GetObjPropDescRange")); DUK_UNREF(heap); h = duk_debug_read_any_ptr(thr); idx_start = (duk_uint_t) duk_debug_read_int(thr); idx_end = (duk_uint_t) duk_debug_read_int(thr); if (h == NULL || DUK_HEAPHDR_GET_TYPE(h) != DUK_HTYPE_OBJECT) { goto fail_args; } h_obj = (duk_hobject *) h; /* The index range space is conceptually the array part followed by the * entry part. Unlike normal enumeration all slots are exposed here as * is and return 'unused' if the slots are not in active use. In particular * the array part is included for the full a_size regardless of what the * array .length is. */ duk_debug_write_reply(thr); for (idx = idx_start; idx < idx_end; idx++) { if (!duk__debug_getprop_index(thr, heap, h_obj, idx)) { break; } } duk_debug_write_eom(thr); return; fail_args: duk_debug_write_error_eom(thr, DUK_DBG_ERR_UNKNOWN, "invalid args"); } #endif /* DUK_USE_DEBUGGER_INSPECT */ /* * Process incoming debug requests * * Individual request handlers can push temporaries on the value stack and * rely on duk__debug_process_message() to restore the value stack top * automatically. */ /* Process one debug message. Automatically restore value stack top to its * entry value, so that individual message handlers don't need exact value * stack handling which is convenient. */ DUK_LOCAL void duk__debug_process_message(duk_hthread *thr) { duk_heap *heap; duk_uint8_t x; duk_int32_t cmd; duk_idx_t entry_top; DUK_ASSERT(thr != NULL); heap = thr->heap; DUK_ASSERT(heap != NULL); entry_top = duk_get_top(thr); x = duk_debug_read_byte(thr); switch (x) { case DUK_DBG_IB_REQUEST: { cmd = duk_debug_read_int(thr); switch (cmd) { case DUK_DBG_CMD_BASICINFO: { duk__debug_handle_basic_info(thr, heap); break; } case DUK_DBG_CMD_TRIGGERSTATUS: { duk__debug_handle_trigger_status(thr, heap); break; } case DUK_DBG_CMD_PAUSE: { duk__debug_handle_pause(thr, heap); break; } case DUK_DBG_CMD_RESUME: { duk__debug_handle_resume(thr, heap); break; } case DUK_DBG_CMD_STEPINTO: case DUK_DBG_CMD_STEPOVER: case DUK_DBG_CMD_STEPOUT: { duk__debug_handle_step(thr, heap, cmd); break; } case DUK_DBG_CMD_LISTBREAK: { duk__debug_handle_list_break(thr, heap); break; } case DUK_DBG_CMD_ADDBREAK: { duk__debug_handle_add_break(thr, heap); break; } case DUK_DBG_CMD_DELBREAK: { duk__debug_handle_del_break(thr, heap); break; } case DUK_DBG_CMD_GETVAR: { duk__debug_handle_get_var(thr, heap); break; } case DUK_DBG_CMD_PUTVAR: { duk__debug_handle_put_var(thr, heap); break; } case DUK_DBG_CMD_GETCALLSTACK: { duk__debug_handle_get_call_stack(thr, heap); break; } case DUK_DBG_CMD_GETLOCALS: { duk__debug_handle_get_locals(thr, heap); break; } case DUK_DBG_CMD_EVAL: { duk__debug_handle_eval(thr, heap); break; } case DUK_DBG_CMD_DETACH: { /* The actual detached_cb call is postponed to message loop so * we don't need any special precautions here (just skip to EOM * on the already closed connection). */ duk__debug_handle_detach(thr, heap); break; } #if defined(DUK_USE_DEBUGGER_DUMPHEAP) case DUK_DBG_CMD_DUMPHEAP: { duk__debug_handle_dump_heap(thr, heap); break; } #endif /* DUK_USE_DEBUGGER_DUMPHEAP */ case DUK_DBG_CMD_GETBYTECODE: { duk__debug_handle_get_bytecode(thr, heap); break; } case DUK_DBG_CMD_APPREQUEST: { duk__debug_handle_apprequest(thr, heap); break; } #if defined(DUK_USE_DEBUGGER_INSPECT) case DUK_DBG_CMD_GETHEAPOBJINFO: { duk__debug_handle_get_heap_obj_info(thr, heap); break; } case DUK_DBG_CMD_GETOBJPROPDESC: { duk__debug_handle_get_obj_prop_desc(thr, heap); break; } case DUK_DBG_CMD_GETOBJPROPDESCRANGE: { duk__debug_handle_get_obj_prop_desc_range(thr, heap); break; } #endif /* DUK_USE_DEBUGGER_INSPECT */ default: { DUK_D(DUK_DPRINT("debug command unsupported: %d", (int) cmd)); duk_debug_write_error_eom(thr, DUK_DBG_ERR_UNSUPPORTED, "unsupported command"); } } /* switch cmd */ break; } case DUK_DBG_IB_REPLY: { DUK_D(DUK_DPRINT("debug reply, skipping")); break; } case DUK_DBG_IB_ERROR: { DUK_D(DUK_DPRINT("debug error, skipping")); break; } case DUK_DBG_IB_NOTIFY: { DUK_D(DUK_DPRINT("debug notify, skipping")); break; } default: { DUK_D(DUK_DPRINT("invalid initial byte, drop connection: %d", (int) x)); goto fail; } } /* switch initial byte */ DUK_ASSERT(duk_get_top(thr) >= entry_top); duk_set_top(thr, entry_top); duk__debug_skip_to_eom(thr); return; fail: DUK_ASSERT(duk_get_top(thr) >= entry_top); duk_set_top(thr, entry_top); DUK__SET_CONN_BROKEN(thr, 1); return; } DUK_LOCAL void duk__check_resend_status(duk_hthread *thr) { if (thr->heap->dbg_read_cb != NULL && thr->heap->dbg_state_dirty) { duk_debug_send_status(thr); thr->heap->dbg_state_dirty = 0; } } DUK_INTERNAL duk_bool_t duk_debug_process_messages(duk_hthread *thr, duk_bool_t no_block) { #if defined(DUK_USE_ASSERTIONS) duk_idx_t entry_top; #endif duk_bool_t retval = 0; DUK_ASSERT(thr != NULL); DUK_ASSERT(thr->heap != NULL); #if defined(DUK_USE_ASSERTIONS) entry_top = duk_get_top(thr); #endif DUK_D(DUK_DPRINT("process debug messages: read_cb=%s, no_block=%ld, detaching=%ld, processing=%ld", thr->heap->dbg_read_cb ? "not NULL" : "NULL", (long) no_block, (long) thr->heap->dbg_detaching, (long) thr->heap->dbg_processing)); DUK_DD(DUK_DDPRINT("top at entry: %ld", (long) duk_get_top(thr))); /* thr->heap->dbg_detaching may be != 0 if a debugger write outside * the message loop caused a transport error and detach1() to run. */ DUK_ASSERT(thr->heap->dbg_detaching == 0 || thr->heap->dbg_detaching == 1); DUK_ASSERT(thr->heap->dbg_processing == 0); thr->heap->dbg_processing = 1; /* Ensure dirty state causes a Status even if never process any * messages. This is expected by the bytecode executor when in * the running state. */ duk__check_resend_status(thr); for (;;) { /* Process messages until we're no longer paused or we peek * and see there's nothing to read right now. */ DUK_DD(DUK_DDPRINT("top at loop top: %ld", (long) duk_get_top(thr))); DUK_ASSERT(thr->heap->dbg_processing == 1); while (thr->heap->dbg_read_cb == NULL && thr->heap->dbg_detaching) { /* Detach is pending; can be triggered from outside the * debugger loop (e.g. Status notify write error) or by * previous message handling. Call detached callback * here, in a controlled state, to ensure a possible * reattach inside the detached_cb is handled correctly. * * Recheck for detach in a while loop: an immediate * reattach involves a call to duk_debugger_attach() * which writes a debugger handshake line immediately * inside the API call. If the transport write fails * for that handshake, we can immediately end up in a * "transport broken, detaching" case several times here. * Loop back until we're either cleanly attached or * fully detached. * * NOTE: Reset dbg_processing = 1 forcibly, in case we * re-attached; duk_debugger_attach() sets dbg_processing * to 0 at the moment. */ DUK_D(DUK_DPRINT("detach pending (dbg_read_cb == NULL, dbg_detaching != 0), call detach2")); duk__debug_do_detach2(thr->heap); thr->heap->dbg_processing = 1; /* may be set to 0 by duk_debugger_attach() inside callback */ DUK_D(DUK_DPRINT("after detach2 (and possible reattach): dbg_read_cb=%s, dbg_detaching=%ld", thr->heap->dbg_read_cb ? "not NULL" : "NULL", (long) thr->heap->dbg_detaching)); } DUK_ASSERT(thr->heap->dbg_detaching == 0); /* true even with reattach */ DUK_ASSERT(thr->heap->dbg_processing == 1); /* even after a detach and possible reattach */ if (thr->heap->dbg_read_cb == NULL) { DUK_D(DUK_DPRINT("debug connection broken (and not detaching), stop processing messages")); break; } if (!DUK_HEAP_HAS_DEBUGGER_PAUSED(thr->heap) || no_block) { if (!duk_debug_read_peek(thr)) { /* Note: peek cannot currently trigger a detach * so the dbg_detaching == 0 assert outside the * loop is correct. */ DUK_D(DUK_DPRINT("processing debug message, peek indicated no data, stop processing messages")); break; } DUK_D(DUK_DPRINT("processing debug message, peek indicated there is data, handle it")); } else { DUK_D(DUK_DPRINT("paused, process debug message, blocking if necessary")); } duk__check_resend_status(thr); duk__debug_process_message(thr); duk__check_resend_status(thr); retval = 1; /* processed one or more messages */ } DUK_ASSERT(thr->heap->dbg_detaching == 0); DUK_ASSERT(thr->heap->dbg_processing == 1); thr->heap->dbg_processing = 0; /* As an initial implementation, read flush after exiting the message * loop. If transport is broken, this is a no-op (with debug logs). */ duk_debug_read_flush(thr); /* this cannot initiate a detach */ DUK_ASSERT(thr->heap->dbg_detaching == 0); DUK_DD(DUK_DDPRINT("top at exit: %ld", (long) duk_get_top(thr))); #if defined(DUK_USE_ASSERTIONS) /* Easy to get wrong, so assert for it. */ DUK_ASSERT(entry_top == duk_get_top(thr)); #endif return retval; } /* * Halt execution helper */ /* Halt execution and enter a debugger message loop until execution is resumed * by the client. PC for the current activation may be temporarily decremented * so that the "current" instruction will be shown by the client. This helper * is callable from anywhere, also outside bytecode executor. */ DUK_INTERNAL void duk_debug_halt_execution(duk_hthread *thr, duk_bool_t use_prev_pc) { duk_activation *act; duk_hcompfunc *fun; duk_instr_t *old_pc = NULL; DUK_ASSERT(thr != NULL); DUK_ASSERT(thr->heap != NULL); DUK_ASSERT(duk_debug_is_attached(thr->heap)); DUK_ASSERT(thr->heap->dbg_processing == 0); DUK_ASSERT(!duk_debug_is_paused(thr->heap)); duk_debug_set_paused(thr->heap); act = thr->callstack_curr; /* NOTE: act may be NULL if an error is thrown outside of any activation, * which may happen in the case of, e.g. syntax errors. */ /* Decrement PC if that was requested, this requires a PC sync. */ if (act != NULL) { duk_hthread_sync_currpc(thr); old_pc = act->curr_pc; fun = (duk_hcompfunc *) DUK_ACT_GET_FUNC(act); /* Short circuit if is safe: if act->curr_pc != NULL, 'fun' is * guaranteed to be a non-NULL ECMAScript function. */ DUK_ASSERT(act->curr_pc == NULL || (fun != NULL && DUK_HOBJECT_IS_COMPFUNC((duk_hobject *) fun))); if (use_prev_pc && act->curr_pc != NULL && act->curr_pc > DUK_HCOMPFUNC_GET_CODE_BASE(thr->heap, fun)) { act->curr_pc--; } } /* Process debug messages until we are no longer paused. */ /* NOTE: This is a bit fragile. It's important to ensure that * duk_debug_process_messages() never throws an error or * act->curr_pc will never be reset. */ thr->heap->dbg_state_dirty = 1; while (DUK_HEAP_HAS_DEBUGGER_PAUSED(thr->heap)) { DUK_ASSERT(duk_debug_is_attached(thr->heap)); DUK_ASSERT(thr->heap->dbg_processing == 0); duk_debug_process_messages(thr, 0 /*no_block*/); } /* XXX: Decrementing and restoring act->curr_pc works now, but if the * debugger message loop gains the ability to adjust the current PC * (e.g. a forced jump) restoring the PC here will break. Another * approach would be to use a state flag for the "decrement 1 from * topmost activation's PC" and take it into account whenever dealing * with PC values. */ if (act != NULL) { act->curr_pc = old_pc; /* restore PC */ } } /* * Breakpoint management */ DUK_INTERNAL duk_small_int_t duk_debug_add_breakpoint(duk_hthread *thr, duk_hstring *filename, duk_uint32_t line) { duk_heap *heap; duk_breakpoint *b; /* Caller must trigger recomputation of active breakpoint list. To * ensure stale values are not used if that doesn't happen, clear the * active breakpoint list here. */ DUK_ASSERT(thr != NULL); DUK_ASSERT(filename != NULL); heap = thr->heap; DUK_ASSERT(heap != NULL); if (heap->dbg_breakpoint_count >= DUK_HEAP_MAX_BREAKPOINTS) { DUK_D(DUK_DPRINT("failed to add breakpoint for %O:%ld, all breakpoint slots used", (duk_heaphdr *) filename, (long) line)); return -1; } heap->dbg_breakpoints_active[0] = (duk_breakpoint *) NULL; b = heap->dbg_breakpoints + (heap->dbg_breakpoint_count++); b->filename = filename; b->line = line; DUK_HSTRING_INCREF(thr, filename); return (duk_small_int_t) (heap->dbg_breakpoint_count - 1); /* index */ } DUK_INTERNAL duk_bool_t duk_debug_remove_breakpoint(duk_hthread *thr, duk_small_uint_t breakpoint_index) { duk_heap *heap; duk_hstring *h; duk_breakpoint *b; duk_size_t move_size; /* Caller must trigger recomputation of active breakpoint list. To * ensure stale values are not used if that doesn't happen, clear the * active breakpoint list here. */ DUK_ASSERT(thr != NULL); heap = thr->heap; DUK_ASSERT(heap != NULL); DUK_ASSERT(duk_debug_is_attached(thr->heap)); DUK_ASSERT_DISABLE(breakpoint_index >= 0); /* unsigned */ if (breakpoint_index >= heap->dbg_breakpoint_count) { DUK_D(DUK_DPRINT("invalid breakpoint index: %ld", (long) breakpoint_index)); return 0; } b = heap->dbg_breakpoints + breakpoint_index; h = b->filename; DUK_ASSERT(h != NULL); move_size = sizeof(duk_breakpoint) * (heap->dbg_breakpoint_count - breakpoint_index - 1); duk_memmove((void *) b, (const void *) (b + 1), (size_t) move_size); heap->dbg_breakpoint_count--; heap->dbg_breakpoints_active[0] = (duk_breakpoint *) NULL; DUK_HSTRING_DECREF(thr, h); /* side effects */ DUK_UNREF(h); /* w/o refcounting */ /* Breakpoint entries above the used area are left as garbage. */ return 1; } /* * Misc state management */ DUK_INTERNAL duk_bool_t duk_debug_is_attached(duk_heap *heap) { return (heap->dbg_read_cb != NULL); } DUK_INTERNAL duk_bool_t duk_debug_is_paused(duk_heap *heap) { return (DUK_HEAP_HAS_DEBUGGER_PAUSED(heap) != 0); } DUK_INTERNAL void duk_debug_set_paused(duk_heap *heap) { if (duk_debug_is_paused(heap)) { DUK_D(DUK_DPRINT("trying to set paused state when already paused, ignoring")); } else { DUK_HEAP_SET_DEBUGGER_PAUSED(heap); heap->dbg_state_dirty = 1; duk_debug_clear_pause_state(heap); DUK_ASSERT(heap->ms_running == 0); /* debugger can't be triggered within mark-and-sweep */ heap->ms_running = 2; /* prevent mark-and-sweep, prevent refzero queueing */ heap->ms_prevent_count++; DUK_ASSERT(heap->ms_prevent_count != 0); /* Wrap. */ DUK_ASSERT(heap->heap_thread != NULL); } } DUK_INTERNAL void duk_debug_clear_paused(duk_heap *heap) { if (duk_debug_is_paused(heap)) { DUK_HEAP_CLEAR_DEBUGGER_PAUSED(heap); heap->dbg_state_dirty = 1; duk_debug_clear_pause_state(heap); DUK_ASSERT(heap->ms_running == 2); DUK_ASSERT(heap->ms_prevent_count > 0); heap->ms_prevent_count--; heap->ms_running = 0; DUK_ASSERT(heap->heap_thread != NULL); } else { DUK_D(DUK_DPRINT("trying to clear paused state when not paused, ignoring")); } } DUK_INTERNAL void duk_debug_clear_pause_state(duk_heap *heap) { heap->dbg_pause_flags = 0; heap->dbg_pause_act = NULL; heap->dbg_pause_startline = 0; } #else /* DUK_USE_DEBUGGER_SUPPORT */ /* No debugger support. */ #endif /* DUK_USE_DEBUGGER_SUPPORT */ /* automatic undefs */ #undef DUK__DBG_TPORT_ENTER #undef DUK__DBG_TPORT_EXIT #undef DUK__SET_CONN_BROKEN #line 1 "duk_error_augment.c" /* * Augmenting errors at their creation site and their throw site. * * When errors are created, traceback data is added by built-in code * and a user error handler (if defined) can process or replace the * error. Similarly, when errors are thrown, a user error handler * (if defined) can process or replace the error. * * Augmentation and other processing at error creation time is nice * because an error is only created once, but it may be thrown and * rethrown multiple times. User error handler registered for processing * an error at its throw site must be careful to handle rethrowing in * a useful manner. * * Error augmentation may throw an internal error (e.g. alloc error). * * ECMAScript allows throwing any values, so all values cannot be * augmented. Currently, the built-in augmentation at error creation * only augments error values which are Error instances (= have the * built-in Error.prototype in their prototype chain) and are also * extensible. User error handlers have no limitations in this respect. */ /* #include duk_internal.h -> already included */ /* * Helper for calling a user error handler. * * 'thr' must be the currently active thread; the error handler is called * in its context. The valstack of 'thr' must have the error value on * top, and will be replaced by another error value based on the return * value of the error handler. * * The helper calls duk_handle_call() recursively in protected mode. * Before that call happens, no longjmps should happen; as a consequence, * we must assume that the valstack contains enough temporary space for * arguments and such. * * While the error handler runs, any errors thrown will not trigger a * recursive error handler call (this is implemented using a heap level * flag which will "follow" through any coroutines resumed inside the * error handler). If the error handler is not callable or throws an * error, the resulting error replaces the original error (for Duktape * internal errors, duk_error_throw.c further substitutes this error with * a DoubleError which is not ideal). This would be easy to change and * even signal to the caller. * * The user error handler is stored in 'Duktape.errCreate' or * 'Duktape.errThrow' depending on whether we're augmenting the error at * creation or throw time. There are several alternatives to this approach, * see doc/error-objects.rst for discussion. * * Note: since further longjmp()s may occur while calling the error handler * (for many reasons, e.g. a labeled 'break' inside the handler), the * caller can make no assumptions on the thr->heap->lj state after the * call (this affects especially duk_error_throw.c). This is not an issue * as long as the caller writes to the lj state only after the error handler * finishes. */ #if defined(DUK_USE_ERRTHROW) || defined(DUK_USE_ERRCREATE) DUK_LOCAL void duk__err_augment_user(duk_hthread *thr, duk_small_uint_t stridx_cb) { duk_tval *tv_hnd; duk_int_t rc; DUK_ASSERT(thr != NULL); DUK_ASSERT(thr->heap != NULL); DUK_ASSERT_STRIDX_VALID(stridx_cb); if (thr->heap->augmenting_error) { DUK_D(DUK_DPRINT("recursive call to error augmentation, ignore")); return; } /* * Check whether or not we have an error handler. * * We must be careful of not triggering an error when looking up the * property. For instance, if the property is a getter, we don't want * to call it, only plain values are allowed. The value, if it exists, * is not checked. If the value is not a function, a TypeError happens * when it is called and that error replaces the original one. */ DUK_ASSERT_VALSTACK_SPACE(thr, 4); /* 3 entries actually needed below */ /* [ ... errval ] */ if (thr->builtins[DUK_BIDX_DUKTAPE] == NULL) { /* When creating built-ins, some of the built-ins may not be set * and we want to tolerate that when throwing errors. */ DUK_DD(DUK_DDPRINT("error occurred when DUK_BIDX_DUKTAPE is NULL, ignoring")); return; } tv_hnd = duk_hobject_find_entry_tval_ptr_stridx(thr->heap, thr->builtins[DUK_BIDX_DUKTAPE], stridx_cb); if (tv_hnd == NULL) { DUK_DD(DUK_DDPRINT("error handler does not exist or is not a plain value: %!T", (duk_tval *) tv_hnd)); return; } DUK_DDD(DUK_DDDPRINT("error handler dump (callability not checked): %!T", (duk_tval *) tv_hnd)); duk_push_tval(thr, tv_hnd); /* [ ... errval errhandler ] */ duk_insert(thr, -2); /* -> [ ... errhandler errval ] */ duk_push_undefined(thr); duk_insert(thr, -2); /* -> [ ... errhandler undefined(= this) errval ] */ /* [ ... errhandler undefined errval ] */ /* * heap->augmenting_error prevents recursive re-entry and also causes * call handling to use a larger (but not unbounded) call stack limit * for the duration of error augmentation. * * We ignore errors now: a success return and an error value both * replace the original error value. (This would be easy to change.) */ DUK_ASSERT(thr->heap->augmenting_error == 0); thr->heap->augmenting_error = 1; rc = duk_pcall_method(thr, 1); DUK_UNREF(rc); /* no need to check now: both success and error are OK */ DUK_ASSERT(thr->heap->augmenting_error == 1); thr->heap->augmenting_error = 0; /* [ ... errval ] */ } #endif /* DUK_USE_ERRTHROW || DUK_USE_ERRCREATE */ /* * Add ._Tracedata to an error on the stack top. */ #if defined(DUK_USE_TRACEBACKS) DUK_LOCAL void duk__add_traceback(duk_hthread *thr, duk_hthread *thr_callstack, const char *c_filename, duk_int_t c_line, duk_small_uint_t flags) { duk_activation *act; duk_int_t depth; duk_int_t arr_size; duk_tval *tv; duk_hstring *s; duk_uint32_t u32; duk_double_t d; DUK_ASSERT(thr != NULL); DUK_ASSERT(thr_callstack != NULL); /* [ ... error ] */ /* * The traceback format is pretty arcane in an attempt to keep it compact * and cheap to create. It may change arbitrarily from version to version. * It should be decoded/accessed through version specific accessors only. * * See doc/error-objects.rst. */ DUK_DDD(DUK_DDDPRINT("adding traceback to object: %!T", (duk_tval *) duk_get_tval(thr, -1))); /* Preallocate array to correct size, so that we can just write out * the _Tracedata values into the array part. */ act = thr->callstack_curr; depth = DUK_USE_TRACEBACK_DEPTH; DUK_ASSERT(thr_callstack->callstack_top <= DUK_INT_MAX); /* callstack limits */ if (depth > (duk_int_t) thr_callstack->callstack_top) { depth = (duk_int_t) thr_callstack->callstack_top; } if (depth > 0) { if (flags & DUK_AUGMENT_FLAG_SKIP_ONE) { DUK_ASSERT(act != NULL); act = act->parent; depth--; } } arr_size = depth * 2; if (thr->compile_ctx != NULL && thr->compile_ctx->h_filename != NULL) { arr_size += 2; } if (c_filename) { /* We need the C filename to be interned before getting the * array part pointer to avoid any GC interference while the * array part is populated. */ duk_push_string(thr, c_filename); arr_size += 2; } /* XXX: Uninitialized would be OK. Maybe add internal primitive to * push bare duk_harray with size? */ DUK_D(DUK_DPRINT("preallocated _Tracedata to %ld items", (long) arr_size)); tv = duk_push_harray_with_size_outptr(thr, (duk_uint32_t) arr_size); duk_clear_prototype(thr, -1); DUK_ASSERT(duk_is_bare_object(thr, -1)); DUK_ASSERT(arr_size == 0 || tv != NULL); /* Compiler SyntaxErrors (and other errors) come first, and are * blamed by default (not flagged "noblame"). */ if (thr->compile_ctx != NULL && thr->compile_ctx->h_filename != NULL) { s = thr->compile_ctx->h_filename; DUK_TVAL_SET_STRING(tv, s); DUK_HSTRING_INCREF(thr, s); tv++; u32 = (duk_uint32_t) thr->compile_ctx->curr_token.start_line; /* (flags<<32) + (line), flags = 0 */ DUK_TVAL_SET_U32(tv, u32); tv++; } /* Filename/line from C macros (__FILE__, __LINE__) are added as an * entry with a special format: (string, number). The number contains * the line and flags. */ /* [ ... error c_filename? arr ] */ if (c_filename) { DUK_ASSERT(DUK_TVAL_IS_STRING(thr->valstack_top - 2)); s = DUK_TVAL_GET_STRING(thr->valstack_top - 2); /* interned c_filename */ DUK_ASSERT(s != NULL); DUK_TVAL_SET_STRING(tv, s); DUK_HSTRING_INCREF(thr, s); tv++; d = ((flags & DUK_AUGMENT_FLAG_NOBLAME_FILELINE) ? ((duk_double_t) DUK_TB_FLAG_NOBLAME_FILELINE) * DUK_DOUBLE_2TO32 : 0.0) + (duk_double_t) c_line; DUK_TVAL_SET_DOUBLE(tv, d); tv++; } /* Traceback depth doesn't take into account the filename/line * special handling above (intentional). */ for (; depth-- > 0; act = act->parent) { duk_uint32_t pc; duk_tval *tv_src; /* [... arr] */ DUK_ASSERT(act != NULL); /* depth check above, assumes book-keeping is correct */ DUK_ASSERT_DISABLE(act->pc >= 0); /* unsigned */ /* Add function object. */ tv_src = &act->tv_func; /* object (function) or lightfunc */ DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv_src) || DUK_TVAL_IS_LIGHTFUNC(tv_src)); DUK_TVAL_SET_TVAL(tv, tv_src); DUK_TVAL_INCREF(thr, tv); tv++; /* Add a number containing: pc, activation flags. * * PC points to next instruction, find offending PC. Note that * PC == 0 for native code. */ pc = (duk_uint32_t) duk_hthread_get_act_prev_pc(thr_callstack, act); DUK_ASSERT_DISABLE(pc >= 0); /* unsigned */ DUK_ASSERT((duk_double_t) pc < DUK_DOUBLE_2TO32); /* assume PC is at most 32 bits and non-negative */ d = ((duk_double_t) act->flags) * DUK_DOUBLE_2TO32 + (duk_double_t) pc; DUK_TVAL_SET_DOUBLE(tv, d); tv++; } #if defined(DUK_USE_ASSERTIONS) { duk_harray *a; a = (duk_harray *) duk_known_hobject(thr, -1); DUK_ASSERT(a != NULL); DUK_ASSERT((duk_uint32_t) (tv - DUK_HOBJECT_A_GET_BASE(thr->heap, (duk_hobject *) a)) == a->length); DUK_ASSERT(a->length == (duk_uint32_t) arr_size); DUK_ASSERT(duk_is_bare_object(thr, -1)); } #endif /* [ ... error c_filename? arr ] */ if (c_filename) { duk_remove_m2(thr); } /* [ ... error arr ] */ duk_xdef_prop_stridx_short_wec(thr, -2, DUK_STRIDX_INT_TRACEDATA); /* -> [ ... error ] */ } #endif /* DUK_USE_TRACEBACKS */ /* * Add .fileName and .lineNumber to an error on the stack top. */ #if defined(DUK_USE_AUGMENT_ERROR_CREATE) && !defined(DUK_USE_TRACEBACKS) DUK_LOCAL void duk__add_fileline(duk_hthread *thr, duk_hthread *thr_callstack, const char *c_filename, duk_int_t c_line, duk_small_uint_t flags) { #if defined(DUK_USE_ASSERTIONS) duk_int_t entry_top; #endif #if defined(DUK_USE_ASSERTIONS) entry_top = duk_get_top(thr); #endif /* * If tracebacks are disabled, 'fileName' and 'lineNumber' are added * as plain own properties. Since Error.prototype has accessors of * the same name, we need to define own properties directly (cannot * just use e.g. duk_put_prop_stridx). Existing properties are not * overwritten in case they already exist. */ if (thr->compile_ctx != NULL && thr->compile_ctx->h_filename != NULL) { /* Compiler SyntaxError (or other error) gets the primary blame. * Currently no flag to prevent blaming. */ duk_push_uint(thr, (duk_uint_t) thr->compile_ctx->curr_token.start_line); duk_push_hstring(thr, thr->compile_ctx->h_filename); } else if (c_filename && (flags & DUK_AUGMENT_FLAG_NOBLAME_FILELINE) == 0) { /* C call site gets blamed next, unless flagged not to do so. * XXX: file/line is disabled in minimal builds, so disable this * too when appropriate. */ duk_push_int(thr, c_line); duk_push_string(thr, c_filename); } else { /* Finally, blame the innermost callstack entry which has a * .fileName property. */ duk_small_uint_t depth; duk_uint32_t ecma_line; duk_activation *act; DUK_ASSERT(thr_callstack->callstack_top <= DUK_INT_MAX); /* callstack limits */ depth = DUK_USE_TRACEBACK_DEPTH; if (depth > thr_callstack->callstack_top) { depth = thr_callstack->callstack_top; } for (act = thr_callstack->callstack_curr; depth-- > 0; act = act->parent) { duk_hobject *func; duk_uint32_t pc; DUK_ASSERT(act != NULL); func = DUK_ACT_GET_FUNC(act); if (func == NULL) { /* Lightfunc, not blamed now. */ continue; } /* PC points to next instruction, find offending PC, * PC == 0 for native code. */ pc = duk_hthread_get_act_prev_pc( thr, act); /* thr argument only used for thr->heap, so specific thread doesn't matter */ DUK_UNREF(pc); DUK_ASSERT_DISABLE(pc >= 0); /* unsigned */ DUK_ASSERT((duk_double_t) pc < DUK_DOUBLE_2TO32); /* assume PC is at most 32 bits and non-negative */ duk_push_hobject(thr, func); /* [ ... error func ] */ duk_get_prop_stridx_short(thr, -1, DUK_STRIDX_FILE_NAME); if (!duk_is_string_notsymbol(thr, -1)) { duk_pop_2(thr); continue; } /* [ ... error func fileName ] */ ecma_line = 0; #if defined(DUK_USE_PC2LINE) if (DUK_HOBJECT_IS_COMPFUNC(func)) { ecma_line = duk_hobject_pc2line_query(thr, -2, (duk_uint_fast32_t) pc); } else { /* Native function, no relevant lineNumber. */ } #endif /* DUK_USE_PC2LINE */ duk_push_u32(thr, ecma_line); /* [ ... error func fileName lineNumber ] */ duk_replace(thr, -3); /* [ ... error lineNumber fileName ] */ goto define_props; } /* No activation matches, use undefined for both .fileName and * .lineNumber (matches what we do with a _Tracedata based * no-match lookup. */ duk_push_undefined(thr); duk_push_undefined(thr); } define_props: /* [ ... error lineNumber fileName ] */ #if defined(DUK_USE_ASSERTIONS) DUK_ASSERT(duk_get_top(thr) == entry_top + 2); #endif duk_xdef_prop_stridx_short(thr, -3, DUK_STRIDX_FILE_NAME, DUK_PROPDESC_FLAGS_C | DUK_PROPDESC_FLAG_NO_OVERWRITE); duk_xdef_prop_stridx_short(thr, -2, DUK_STRIDX_LINE_NUMBER, DUK_PROPDESC_FLAGS_C | DUK_PROPDESC_FLAG_NO_OVERWRITE); } #endif /* DUK_USE_AUGMENT_ERROR_CREATE && !DUK_USE_TRACEBACKS */ /* * Add line number to a compiler error. */ #if defined(DUK_USE_AUGMENT_ERROR_CREATE) DUK_LOCAL void duk__add_compiler_error_line(duk_hthread *thr) { /* Append a "(line NNN)" to the "message" property of any error * thrown during compilation. Usually compilation errors are * SyntaxErrors but they can also be out-of-memory errors and * the like. */ /* [ ... error ] */ DUK_ASSERT(duk_is_object(thr, -1)); if (!(thr->compile_ctx != NULL && thr->compile_ctx->h_filename != NULL)) { return; } DUK_DDD(DUK_DDDPRINT("compile error, before adding line info: %!T", (duk_tval *) duk_get_tval(thr, -1))); if (duk_get_prop_stridx_short(thr, -1, DUK_STRIDX_MESSAGE)) { duk_bool_t at_end; /* Best guesstimate that error occurred at end of input, token * truncated by end of input, etc. */ #if 0 at_end = (thr->compile_ctx->curr_token.start_offset + 1 >= thr->compile_ctx->lex.input_length); at_end = (thr->compile_ctx->lex.window[0].codepoint < 0 || thr->compile_ctx->lex.window[1].codepoint < 0); #endif at_end = (thr->compile_ctx->lex.window[0].codepoint < 0); DUK_D(DUK_DPRINT("syntax error, determined at_end=%ld; curr_token.start_offset=%ld, " "lex.input_length=%ld, window[0].codepoint=%ld, window[1].codepoint=%ld", (long) at_end, (long) thr->compile_ctx->curr_token.start_offset, (long) thr->compile_ctx->lex.input_length, (long) thr->compile_ctx->lex.window[0].codepoint, (long) thr->compile_ctx->lex.window[1].codepoint)); duk_push_sprintf(thr, " (line %ld%s)", (long) thr->compile_ctx->curr_token.start_line, at_end ? ", end of input" : ""); duk_concat(thr, 2); duk_put_prop_stridx_short(thr, -2, DUK_STRIDX_MESSAGE); } else { duk_pop(thr); } DUK_DDD(DUK_DDDPRINT("compile error, after adding line info: %!T", (duk_tval *) duk_get_tval(thr, -1))); } #endif /* DUK_USE_AUGMENT_ERROR_CREATE */ /* * Augment an error being created using Duktape specific properties * like _Tracedata or .fileName/.lineNumber. */ #if defined(DUK_USE_AUGMENT_ERROR_CREATE) DUK_LOCAL void duk__err_augment_builtin_create(duk_hthread *thr, duk_hthread *thr_callstack, const char *c_filename, duk_int_t c_line, duk_hobject *obj, duk_small_uint_t flags) { #if defined(DUK_USE_ASSERTIONS) duk_int_t entry_top; #endif #if defined(DUK_USE_ASSERTIONS) entry_top = duk_get_top(thr); #endif DUK_ASSERT(obj != NULL); DUK_UNREF(obj); /* unreferenced w/o tracebacks */ duk__add_compiler_error_line(thr); #if defined(DUK_USE_TRACEBACKS) /* If tracebacks are enabled, the '_Tracedata' property is the only * thing we need: 'fileName' and 'lineNumber' are virtual properties * which use '_Tracedata'. (Check _Tracedata only as own property.) */ if (duk_hobject_find_entry_tval_ptr_stridx(thr->heap, obj, DUK_STRIDX_INT_TRACEDATA) != NULL) { DUK_DDD(DUK_DDDPRINT("error value already has a '_Tracedata' property, not modifying it")); } else { duk__add_traceback(thr, thr_callstack, c_filename, c_line, flags); } #else /* Without tracebacks the concrete .fileName and .lineNumber need * to be added directly. */ duk__add_fileline(thr, thr_callstack, c_filename, c_line, flags); #endif #if defined(DUK_USE_ASSERTIONS) DUK_ASSERT(duk_get_top(thr) == entry_top); #endif } #endif /* DUK_USE_AUGMENT_ERROR_CREATE */ /* * Augment an error at creation time with _Tracedata/fileName/lineNumber * and allow a user error handler (if defined) to process/replace the error. * The error to be augmented is at the stack top. * * thr: thread containing the error value * thr_callstack: thread which should be used for generating callstack etc. * c_filename: C __FILE__ related to the error * c_line: C __LINE__ related to the error * flags & DUK_AUGMENT_FLAG_NOBLAME_FILELINE: * if true, don't fileName/line as error source, otherwise use traceback * (needed because user code filename/line are reported but internal ones * are not) */ #if defined(DUK_USE_AUGMENT_ERROR_CREATE) DUK_INTERNAL void duk_err_augment_error_create(duk_hthread *thr, duk_hthread *thr_callstack, const char *c_filename, duk_int_t c_line, duk_small_uint_t flags) { duk_hobject *obj; DUK_ASSERT(thr != NULL); DUK_ASSERT(thr_callstack != NULL); /* [ ... error ] */ /* * Criteria for augmenting: * * - augmentation enabled in build (naturally) * - error value internal prototype chain contains the built-in * Error prototype object (i.e. 'val instanceof Error') * * Additional criteria for built-in augmenting: * * - error value is an extensible object */ obj = duk_get_hobject(thr, -1); if (!obj) { DUK_DDD(DUK_DDDPRINT("value is not an object, skip both built-in and user augment")); return; } if (!duk_hobject_prototype_chain_contains(thr, obj, thr->builtins[DUK_BIDX_ERROR_PROTOTYPE], 1 /*ignore_loop*/)) { /* If the value has a prototype loop, it's critical not to * throw here. Instead, assume the value is not to be * augmented. */ DUK_DDD(DUK_DDDPRINT("value is not an error instance, skip both built-in and user augment")); return; } if (DUK_HOBJECT_HAS_EXTENSIBLE(obj)) { DUK_DDD(DUK_DDDPRINT("error meets criteria, built-in augment")); duk__err_augment_builtin_create(thr, thr_callstack, c_filename, c_line, obj, flags); } else { DUK_DDD(DUK_DDDPRINT("error does not meet criteria, no built-in augment")); } /* [ ... error ] */ #if defined(DUK_USE_ERRCREATE) duk__err_augment_user(thr, DUK_STRIDX_ERR_CREATE); #endif } #endif /* DUK_USE_AUGMENT_ERROR_CREATE */ /* * Augment an error at throw time; allow a user error handler (if defined) * to process/replace the error. The error to be augmented is at the * stack top. */ #if defined(DUK_USE_AUGMENT_ERROR_THROW) DUK_INTERNAL void duk_err_augment_error_throw(duk_hthread *thr) { #if defined(DUK_USE_ERRTHROW) duk__err_augment_user(thr, DUK_STRIDX_ERR_THROW); #endif /* DUK_USE_ERRTHROW */ } #endif /* DUK_USE_AUGMENT_ERROR_THROW */ #line 1 "duk_error_longjmp.c" /* * Do a longjmp call, calling the fatal error handler if no * catchpoint exists. */ /* #include duk_internal.h -> already included */ #if defined(DUK_USE_PREFER_SIZE) DUK_NORETURN(DUK_LOCAL_DECL void duk__uncaught_minimal(duk_hthread *thr)); DUK_LOCAL void duk__uncaught_minimal(duk_hthread *thr) { (void) duk_fatal(thr, "uncaught error"); DUK_WO_NORETURN(return;); } #endif #if 0 DUK_NORETURN(DUK_LOCAL_DECL void duk__uncaught_readable(duk_hthread *thr)); DUK_LOCAL void duk__uncaught_readable(duk_hthread *thr) { const char *summary; char buf[DUK_USE_FATAL_MAXLEN]; summary = duk_push_string_tval_readable(thr, &thr->heap->lj.value1); DUK_SNPRINTF(buf, sizeof(buf), "uncaught: %s", summary); buf[sizeof(buf) - 1] = (char) 0; (void) duk_fatal(thr, (const char *) buf); DUK_WO_NORETURN(return;); } #endif #if !defined(DUK_USE_PREFER_SIZE) DUK_NORETURN(DUK_LOCAL_DECL void duk__uncaught_error_aware(duk_hthread *thr)); DUK_LOCAL void duk__uncaught_error_aware(duk_hthread *thr) { const char *summary; char buf[DUK_USE_FATAL_MAXLEN]; summary = duk_push_string_tval_readable_error(thr, &thr->heap->lj.value1); DUK_ASSERT(summary != NULL); DUK_SNPRINTF(buf, sizeof(buf), "uncaught: %s", summary); buf[sizeof(buf) - 1] = (char) 0; (void) duk_fatal(thr, (const char *) buf); DUK_WO_NORETURN(return;); } #endif DUK_INTERNAL void duk_err_longjmp(duk_hthread *thr) { DUK_ASSERT(thr != NULL); DUK_ASSERT(thr->heap != NULL); DUK_DD(DUK_DDPRINT("longjmp error: type=%d iserror=%d value1=%!T value2=%!T", (int) thr->heap->lj.type, (int) thr->heap->lj.iserror, &thr->heap->lj.value1, &thr->heap->lj.value2)); /* Prevent finalizer execution during error handling. All error * handling sites will process pending finalizers once error handling * is complete and we're ready for the side effects. Does not prevent * refzero freeing or mark-and-sweep during error handling. * * NOTE: when we come here some calling code may have used DECREF * NORZ macros without an explicit DUK_REFZERO_CHECK_xxx() call. * We don't want to do it here because it would just check for * pending finalizers and we prevent that explicitly. Instead, * the error catcher will run the finalizers once error handling * is complete. */ DUK_ASSERT_LJSTATE_SET(thr->heap); thr->heap->pf_prevent_count++; DUK_ASSERT(thr->heap->pf_prevent_count != 0); /* Wrap. */ #if defined(DUK_USE_ASSERTIONS) /* XXX: set this immediately when longjmp state is set */ DUK_ASSERT(thr->heap->error_not_allowed == 0); /* Detect error within critical section. */ thr->heap->error_not_allowed = 1; #endif DUK_DD(DUK_DDPRINT("about to longjmp, pf_prevent_count=%ld", (long) thr->heap->pf_prevent_count)); /* If we don't have a jmpbuf_ptr, there is little we can do except * cause a fatal error. The caller's expectation is that we never * return. */ if (!thr->heap->lj.jmpbuf_ptr) { DUK_D(DUK_DPRINT("uncaught error: type=%d iserror=%d value1=%!T value2=%!T", (int) thr->heap->lj.type, (int) thr->heap->lj.iserror, &thr->heap->lj.value1, &thr->heap->lj.value2)); #if defined(DUK_USE_PREFER_SIZE) duk__uncaught_minimal(thr); #else duk__uncaught_error_aware(thr); #endif DUK_UNREACHABLE(); } #if defined(DUK_USE_CPP_EXCEPTIONS) throw duk_internal_exception(); /* dummy */ #else DUK_LONGJMP(thr->heap->lj.jmpbuf_ptr->jb); #endif DUK_UNREACHABLE(); } #line 1 "duk_error_misc.c" /* * Error helpers */ /* #include duk_internal.h -> already included */ /* * Helper to walk the thread chain and see if there is an active error * catcher. Protected calls or finally blocks aren't considered catching. */ #if defined(DUK_USE_DEBUGGER_SUPPORT) DUK_LOCAL duk_bool_t duk__have_active_catcher(duk_hthread *thr) { /* As noted above, a protected API call won't be counted as a * catcher. This is usually convenient, e.g. in the case of a top- * level duk_pcall(), but may not always be desirable. Perhaps add * an argument to treat them as catchers? */ duk_activation *act; duk_catcher *cat; DUK_ASSERT(thr != NULL); for (; thr != NULL; thr = thr->resumer) { for (act = thr->callstack_curr; act != NULL; act = act->parent) { for (cat = act->cat; cat != NULL; cat = cat->parent) { if (DUK_CAT_HAS_CATCH_ENABLED(cat)) { return 1; /* all we need to know */ } } } } return 0; } #endif /* DUK_USE_DEBUGGER_SUPPORT */ /* * Get prototype object for an integer error code. */ DUK_INTERNAL duk_hobject *duk_error_prototype_from_code(duk_hthread *thr, duk_errcode_t code) { switch (code) { case DUK_ERR_EVAL_ERROR: return thr->builtins[DUK_BIDX_EVAL_ERROR_PROTOTYPE]; case DUK_ERR_RANGE_ERROR: return thr->builtins[DUK_BIDX_RANGE_ERROR_PROTOTYPE]; case DUK_ERR_REFERENCE_ERROR: return thr->builtins[DUK_BIDX_REFERENCE_ERROR_PROTOTYPE]; case DUK_ERR_SYNTAX_ERROR: return thr->builtins[DUK_BIDX_SYNTAX_ERROR_PROTOTYPE]; case DUK_ERR_TYPE_ERROR: return thr->builtins[DUK_BIDX_TYPE_ERROR_PROTOTYPE]; case DUK_ERR_URI_ERROR: return thr->builtins[DUK_BIDX_URI_ERROR_PROTOTYPE]; case DUK_ERR_ERROR: default: return thr->builtins[DUK_BIDX_ERROR_PROTOTYPE]; } } /* * Helper for debugger throw notify and pause-on-uncaught integration. */ #if defined(DUK_USE_DEBUGGER_SUPPORT) DUK_INTERNAL void duk_err_check_debugger_integration(duk_hthread *thr) { duk_bool_t uncaught; duk_tval *tv_obj; /* If something is thrown with the debugger attached and nobody will * catch it, execution is paused before the longjmp, turning over * control to the debug client. This allows local state to be examined * before the stack is unwound. Errors are not intercepted when debug * message loop is active (e.g. for Eval). */ DUK_ASSERT(thr != NULL); DUK_ASSERT(thr->heap != NULL); /* XXX: Allow customizing the pause and notify behavior at runtime * using debugger runtime flags. For now the behavior is fixed using * config options. */ if (!duk_debug_is_attached(thr->heap) || thr->heap->dbg_processing || thr->heap->lj.type != DUK_LJ_TYPE_THROW || thr->heap->creating_error) { DUK_D(DUK_DPRINT("skip debugger error integration; not attached, debugger processing, not THROW, or error thrown " "while creating error")); return; } /* Don't intercept a DoubleError, we may have caused the initial double * fault and attempting to intercept it will cause us to be called * recursively and exhaust the C stack. (This should no longer happen * for the initial throw because DoubleError path doesn't do a debugger * integration check, but it might happen for rethrows.) */ tv_obj = &thr->heap->lj.value1; if (DUK_TVAL_IS_OBJECT(tv_obj) && DUK_TVAL_GET_OBJECT(tv_obj) == thr->builtins[DUK_BIDX_DOUBLE_ERROR]) { DUK_D(DUK_DPRINT("built-in DoubleError instance (re)thrown, not intercepting")); return; } uncaught = !duk__have_active_catcher(thr); /* Debugger code expects the value at stack top. This also serves * as a backup: we need to store/restore the longjmp state because * when the debugger is paused Eval commands may be executed and * they can arbitrarily clobber the longjmp state. */ duk_push_tval(thr, tv_obj); /* Store and reset longjmp state. */ DUK_ASSERT_LJSTATE_SET(thr->heap); DUK_TVAL_DECREF_NORZ(thr, tv_obj); DUK_ASSERT(DUK_TVAL_IS_UNDEFINED(&thr->heap->lj.value2)); /* Always for THROW type. */ DUK_TVAL_SET_UNDEFINED(tv_obj); thr->heap->lj.type = DUK_LJ_TYPE_UNKNOWN; DUK_ASSERT_LJSTATE_UNSET(thr->heap); #if defined(DUK_USE_DEBUGGER_THROW_NOTIFY) /* Report it to the debug client */ DUK_D(DUK_DPRINT("throw with debugger attached, report to client")); duk_debug_send_throw(thr, uncaught); #endif if (uncaught) { if (thr->heap->dbg_pause_flags & DUK_PAUSE_FLAG_UNCAUGHT_ERROR) { DUK_D(DUK_DPRINT("PAUSE TRIGGERED by uncaught error")); duk_debug_halt_execution(thr, 1 /*use_prev_pc*/); } } else { if (thr->heap->dbg_pause_flags & DUK_PAUSE_FLAG_CAUGHT_ERROR) { DUK_D(DUK_DPRINT("PAUSE TRIGGERED by caught error")); duk_debug_halt_execution(thr, 1 /*use_prev_pc*/); } } /* Restore longjmp state. */ DUK_ASSERT_LJSTATE_UNSET(thr->heap); thr->heap->lj.type = DUK_LJ_TYPE_THROW; tv_obj = DUK_GET_TVAL_NEGIDX(thr, -1); DUK_ASSERT(DUK_TVAL_IS_UNDEFINED(&thr->heap->lj.value1)); DUK_ASSERT(DUK_TVAL_IS_UNDEFINED(&thr->heap->lj.value2)); DUK_TVAL_SET_TVAL(&thr->heap->lj.value1, tv_obj); DUK_TVAL_INCREF(thr, tv_obj); DUK_ASSERT_LJSTATE_SET(thr->heap); duk_pop(thr); } #endif /* DUK_USE_DEBUGGER_SUPPORT */ /* * Helpers for setting up heap longjmp state. */ DUK_INTERNAL void duk_err_setup_ljstate1(duk_hthread *thr, duk_small_uint_t lj_type, duk_tval *tv_val) { duk_heap *heap; DUK_ASSERT(thr != NULL); heap = thr->heap; DUK_ASSERT(heap != NULL); DUK_ASSERT(tv_val != NULL); DUK_ASSERT_LJSTATE_UNSET(heap); heap->lj.type = lj_type; DUK_TVAL_SET_TVAL(&heap->lj.value1, tv_val); DUK_TVAL_INCREF(thr, tv_val); DUK_ASSERT_LJSTATE_SET(heap); } #line 1 "duk_error_throw.c" /* * Create and throw an ECMAScript error object based on a code and a message. * * Used when we throw errors internally. ECMAScript generated error objects * are created by ECMAScript code, and the throwing is handled by the bytecode * executor. */ /* #include duk_internal.h -> already included */ /* * Create and throw an error (originating from Duktape internally) * * Push an error object on top of the stack, possibly throw augmenting * the error, and finally longjmp. * * If an error occurs while we're dealing with the current error, we might * enter an infinite recursion loop. This is prevented by detecting a * "double fault" through the heap->creating_error flag; the recursion * then stops at the second level. */ #if defined(DUK_USE_VERBOSE_ERRORS) DUK_INTERNAL void duk_err_create_and_throw(duk_hthread *thr, duk_errcode_t code, const char *msg, const char *filename, duk_int_t line) { #else DUK_INTERNAL void duk_err_create_and_throw(duk_hthread *thr, duk_errcode_t code) { #endif #if defined(DUK_USE_VERBOSE_ERRORS) DUK_DD(DUK_DDPRINT("duk_err_create_and_throw(): code=%ld, msg=%s, filename=%s, line=%ld", (long) code, (const char *) msg, (const char *) filename, (long) line)); #else DUK_DD(DUK_DDPRINT("duk_err_create_and_throw(): code=%ld", (long) code)); #endif DUK_ASSERT(thr != NULL); /* Even though nested call is possible because we throw an error when * trying to create an error, the potential errors must happen before * the longjmp state is configured. */ DUK_ASSERT_LJSTATE_UNSET(thr->heap); /* Sync so that augmentation sees up-to-date activations, NULL * thr->ptr_curr_pc so that it's not used if side effects occur * in augmentation or longjmp handling. */ duk_hthread_sync_and_null_currpc(thr); /* * Create and push an error object onto the top of stack. * The error is potentially augmented before throwing. * * If a "double error" occurs, use a fixed error instance * to avoid further trouble. */ if (thr->heap->creating_error) { duk_tval tv_val; duk_hobject *h_err; thr->heap->creating_error = 0; h_err = thr->builtins[DUK_BIDX_DOUBLE_ERROR]; if (h_err != NULL) { DUK_D(DUK_DPRINT("double fault detected -> use built-in fixed 'double error' instance")); DUK_TVAL_SET_OBJECT(&tv_val, h_err); } else { DUK_D(DUK_DPRINT("double fault detected; there is no built-in fixed 'double error' instance " "-> use the error code as a number")); DUK_TVAL_SET_I32(&tv_val, (duk_int32_t) code); } duk_err_setup_ljstate1(thr, DUK_LJ_TYPE_THROW, &tv_val); /* No augmentation to avoid any allocations or side effects. */ } else { /* Prevent infinite recursion. Extra call stack and C * recursion headroom (see GH-191) is added for augmentation. * That is now signalled by heap->augmenting error and taken * into account in call handling without an explicit limit bump. */ thr->heap->creating_error = 1; duk_require_stack(thr, 1); /* XXX: usually unnecessary '%s' formatting here, but cannot * use 'msg' as a format string directly. */ #if defined(DUK_USE_VERBOSE_ERRORS) duk_push_error_object_raw(thr, code | DUK_ERRCODE_FLAG_NOBLAME_FILELINE, filename, line, "%s", (const char *) msg); #else duk_push_error_object_raw(thr, code | DUK_ERRCODE_FLAG_NOBLAME_FILELINE, NULL, 0, NULL); #endif /* Note that an alloc error may happen during error augmentation. * This may happen both when the original error is an alloc error * and when it's something else. Because any error in augmentation * must be handled correctly anyway, there's no special check for * avoiding it for alloc errors (this differs from Duktape 1.x). */ #if defined(DUK_USE_AUGMENT_ERROR_THROW) DUK_DDD(DUK_DDDPRINT("THROW ERROR (INTERNAL): %!iT (before throw augment)", (duk_tval *) duk_get_tval(thr, -1))); duk_err_augment_error_throw(thr); #endif duk_err_setup_ljstate1(thr, DUK_LJ_TYPE_THROW, DUK_GET_TVAL_NEGIDX(thr, -1)); thr->heap->creating_error = 0; /* Error is now created and we assume no errors can occur any * more. Check for debugger Throw integration only when the * error is complete. If we enter debugger message loop, * creating_error must be 0 so that errors can be thrown in * the paused state, e.g. in Eval commands. */ #if defined(DUK_USE_DEBUGGER_SUPPORT) duk_err_check_debugger_integration(thr); #endif } /* * Finally, longjmp */ DUK_DDD(DUK_DDDPRINT("THROW ERROR (INTERNAL): %!iT, %!iT (after throw augment)", (duk_tval *) &thr->heap->lj.value1, (duk_tval *) &thr->heap->lj.value2)); duk_err_longjmp(thr); DUK_UNREACHABLE(); } /* * Helper for C function call negative return values. */ DUK_INTERNAL void duk_error_throw_from_negative_rc(duk_hthread *thr, duk_ret_t rc) { DUK_ASSERT(thr != NULL); DUK_ASSERT(rc < 0); /* * The __FILE__ and __LINE__ information is intentionally not used in the * creation of the error object, as it isn't useful in the tracedata. The * tracedata still contains the function which returned the negative return * code, and having the file/line of this function isn't very useful. * * The error messages for DUK_RET_xxx shorthand are intentionally very * minimal: they're only really useful for low memory targets. */ duk_error_raw(thr, -rc, NULL, 0, "error (rc %ld)", (long) rc); DUK_WO_NORETURN(return;); } #line 1 "duk_hbuffer_alloc.c" /* * duk_hbuffer allocation and freeing. */ /* #include duk_internal.h -> already included */ /* Allocate a new duk_hbuffer of a certain type and return a pointer to it * (NULL on error). Write buffer data pointer to 'out_bufdata' (only if * allocation successful). */ DUK_INTERNAL duk_hbuffer *duk_hbuffer_alloc(duk_heap *heap, duk_size_t size, duk_small_uint_t flags, void **out_bufdata) { duk_hbuffer *res = NULL; duk_size_t header_size; duk_size_t alloc_size; DUK_ASSERT(heap != NULL); DUK_ASSERT(out_bufdata != NULL); DUK_DDD(DUK_DDDPRINT("allocate hbuffer")); /* Size sanity check. Should not be necessary because caller is * required to check this, but we don't want to cause a segfault * if the size wraps either in duk_size_t computation or when * storing the size in a 16-bit field. */ if (size > DUK_HBUFFER_MAX_BYTELEN) { DUK_D(DUK_DPRINT("hbuffer alloc failed: size too large: %ld", (long) size)); return NULL; /* no need to write 'out_bufdata' */ } if (flags & DUK_BUF_FLAG_EXTERNAL) { header_size = sizeof(duk_hbuffer_external); alloc_size = sizeof(duk_hbuffer_external); } else if (flags & DUK_BUF_FLAG_DYNAMIC) { header_size = sizeof(duk_hbuffer_dynamic); alloc_size = sizeof(duk_hbuffer_dynamic); } else { header_size = sizeof(duk_hbuffer_fixed); alloc_size = sizeof(duk_hbuffer_fixed) + size; DUK_ASSERT(alloc_size >= sizeof(duk_hbuffer_fixed)); /* no wrapping */ } res = (duk_hbuffer *) DUK_ALLOC(heap, alloc_size); if (DUK_UNLIKELY(res == NULL)) { goto alloc_error; } /* zero everything unless requested not to do so */ #if defined(DUK_USE_ZERO_BUFFER_DATA) duk_memzero((void *) res, (flags & DUK_BUF_FLAG_NOZERO) ? header_size : alloc_size); #else duk_memzero((void *) res, header_size); #endif if (flags & DUK_BUF_FLAG_EXTERNAL) { duk_hbuffer_external *h; h = (duk_hbuffer_external *) res; DUK_UNREF(h); *out_bufdata = NULL; #if defined(DUK_USE_EXPLICIT_NULL_INIT) #if defined(DUK_USE_HEAPPTR16) /* the compressed pointer is zeroed which maps to NULL, so nothing to do. */ #else DUK_HBUFFER_EXTERNAL_SET_DATA_PTR(heap, h, NULL); #endif #endif DUK_ASSERT(DUK_HBUFFER_EXTERNAL_GET_DATA_PTR(heap, h) == NULL); } else if (flags & DUK_BUF_FLAG_DYNAMIC) { duk_hbuffer_dynamic *h = (duk_hbuffer_dynamic *) res; void *ptr; if (size > 0) { DUK_ASSERT(!(flags & DUK_BUF_FLAG_EXTERNAL)); /* alloc external with size zero */ DUK_DDD(DUK_DDDPRINT("dynamic buffer with nonzero size, alloc actual buffer")); #if defined(DUK_USE_ZERO_BUFFER_DATA) ptr = DUK_ALLOC_ZEROED(heap, size); #else ptr = DUK_ALLOC(heap, size); #endif if (DUK_UNLIKELY(ptr == NULL)) { /* Because size > 0, NULL check is correct */ goto alloc_error; } *out_bufdata = ptr; DUK_HBUFFER_DYNAMIC_SET_DATA_PTR(heap, h, ptr); } else { *out_bufdata = NULL; #if defined(DUK_USE_EXPLICIT_NULL_INIT) #if defined(DUK_USE_HEAPPTR16) /* the compressed pointer is zeroed which maps to NULL, so nothing to do. */ #else DUK_HBUFFER_DYNAMIC_SET_DATA_PTR(heap, h, NULL); #endif #endif DUK_ASSERT(DUK_HBUFFER_DYNAMIC_GET_DATA_PTR(heap, h) == NULL); } } else { *out_bufdata = (void *) ((duk_hbuffer_fixed *) (void *) res + 1); } DUK_HBUFFER_SET_SIZE(res, size); DUK_HEAPHDR_SET_TYPE(&res->hdr, DUK_HTYPE_BUFFER); if (flags & DUK_BUF_FLAG_DYNAMIC) { DUK_HBUFFER_SET_DYNAMIC(res); if (flags & DUK_BUF_FLAG_EXTERNAL) { DUK_HBUFFER_SET_EXTERNAL(res); } } else { DUK_ASSERT(!(flags & DUK_BUF_FLAG_EXTERNAL)); } DUK_HEAP_INSERT_INTO_HEAP_ALLOCATED(heap, &res->hdr); DUK_DDD(DUK_DDDPRINT("allocated hbuffer: %p", (void *) res)); return res; alloc_error: DUK_DD(DUK_DDPRINT("hbuffer allocation failed")); DUK_FREE(heap, res); return NULL; /* no need to write 'out_bufdata' */ } /* For indirect allocs. */ DUK_INTERNAL void *duk_hbuffer_get_dynalloc_ptr(duk_heap *heap, void *ud) { duk_hbuffer_dynamic *buf = (duk_hbuffer_dynamic *) ud; DUK_UNREF(heap); return (void *) DUK_HBUFFER_DYNAMIC_GET_DATA_PTR(heap, buf); } #line 1 "duk_hbuffer_assert.c" /* * duk_hbuffer assertion helpers */ /* #include duk_internal.h -> already included */ #if defined(DUK_USE_ASSERTIONS) DUK_INTERNAL void duk_hbuffer_assert_valid(duk_hbuffer *h) { DUK_ASSERT(h != NULL); } #endif /* DUK_USE_ASSERTIONS */ #line 1 "duk_hbuffer_ops.c" /* * duk_hbuffer operations such as resizing and inserting/appending data to * a dynamic buffer. */ /* #include duk_internal.h -> already included */ /* * Resizing */ DUK_INTERNAL void duk_hbuffer_resize(duk_hthread *thr, duk_hbuffer_dynamic *buf, duk_size_t new_size) { void *res; duk_size_t prev_size; DUK_ASSERT(thr != NULL); DUK_ASSERT(buf != NULL); DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(buf)); DUK_ASSERT(!DUK_HBUFFER_HAS_EXTERNAL(buf)); /* * Maximum size check */ if (new_size > DUK_HBUFFER_MAX_BYTELEN) { DUK_ERROR_RANGE(thr, "buffer too long"); DUK_WO_NORETURN(return;); } /* * Note: use indirect realloc variant just in case mark-and-sweep * (finalizers) might resize this same buffer during garbage * collection. */ res = DUK_REALLOC_INDIRECT(thr->heap, duk_hbuffer_get_dynalloc_ptr, (void *) buf, new_size); if (DUK_LIKELY(res != NULL || new_size == 0)) { /* 'res' may be NULL if new allocation size is 0. */ DUK_DDD(DUK_DDDPRINT("resized dynamic buffer %p:%ld -> %p:%ld", (void *) DUK_HBUFFER_DYNAMIC_GET_DATA_PTR(thr->heap, buf), (long) DUK_HBUFFER_DYNAMIC_GET_SIZE(buf), (void *) res, (long) new_size)); /* * The entire allocated buffer area, regardless of actual used * size, is kept zeroed in resizes for simplicity. If the buffer * is grown, zero the new part. */ prev_size = DUK_HBUFFER_DYNAMIC_GET_SIZE(buf); if (new_size > prev_size) { DUK_ASSERT(new_size - prev_size > 0); #if defined(DUK_USE_ZERO_BUFFER_DATA) duk_memzero((void *) ((char *) res + prev_size), (duk_size_t) (new_size - prev_size)); #endif } DUK_HBUFFER_DYNAMIC_SET_SIZE(buf, new_size); DUK_HBUFFER_DYNAMIC_SET_DATA_PTR(thr->heap, buf, res); } else { DUK_ERROR_ALLOC_FAILED(thr); DUK_WO_NORETURN(return;); } DUK_ASSERT(res != NULL || new_size == 0); } DUK_INTERNAL void duk_hbuffer_reset(duk_hthread *thr, duk_hbuffer_dynamic *buf) { DUK_ASSERT(thr != NULL); DUK_ASSERT(buf != NULL); DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(buf)); DUK_ASSERT(!DUK_HBUFFER_HAS_EXTERNAL(buf)); duk_hbuffer_resize(thr, buf, 0); } /* #include duk_internal.h -> already included */ #line 2 "duk_hbufobj_misc.c" #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) DUK_INTERNAL duk_uint_t duk_hbufobj_clamp_bytelength(duk_hbufobj *h_bufobj, duk_uint_t len) { duk_uint_t buf_size; duk_uint_t buf_avail; DUK_ASSERT(h_bufobj != NULL); DUK_ASSERT(h_bufobj->buf != NULL); buf_size = (duk_uint_t) DUK_HBUFFER_GET_SIZE(h_bufobj->buf); if (h_bufobj->offset > buf_size) { /* Slice starting point is beyond current length. */ return 0; } buf_avail = buf_size - h_bufobj->offset; return buf_avail >= len ? len : buf_avail; } #endif /* DUK_USE_BUFFEROBJECT_SUPPORT */ #line 1 "duk_heap_alloc.c" /* * duk_heap allocation and freeing. */ /* #include duk_internal.h -> already included */ #if defined(DUK_USE_ROM_STRINGS) /* Fixed seed value used with ROM strings. */ #define DUK__FIXED_HASH_SEED 0xabcd1234 #endif /* * Free a heap object. * * Free heap object and its internal (non-heap) pointers. Assumes that * caller has removed the object from heap allocated list or the string * intern table, and any weak references (which strings may have) have * been already dealt with. */ DUK_INTERNAL void duk_free_hobject(duk_heap *heap, duk_hobject *h) { DUK_ASSERT(heap != NULL); DUK_ASSERT(h != NULL); DUK_FREE(heap, DUK_HOBJECT_GET_PROPS(heap, h)); if (DUK_HOBJECT_IS_COMPFUNC(h)) { duk_hcompfunc *f = (duk_hcompfunc *) h; DUK_UNREF(f); /* Currently nothing to free; 'data' is a heap object */ } else if (DUK_HOBJECT_IS_NATFUNC(h)) { duk_hnatfunc *f = (duk_hnatfunc *) h; DUK_UNREF(f); /* Currently nothing to free */ } else if (DUK_HOBJECT_IS_THREAD(h)) { duk_hthread *t = (duk_hthread *) h; duk_activation *act; DUK_FREE(heap, t->valstack); /* Don't free h->resumer because it exists in the heap. * Callstack entries also contain function pointers which * are not freed for the same reason. They are decref * finalized and the targets are freed if necessary based * on their refcount (or reachability). */ for (act = t->callstack_curr; act != NULL;) { duk_activation *act_next; duk_catcher *cat; for (cat = act->cat; cat != NULL;) { duk_catcher *cat_next; cat_next = cat->parent; DUK_FREE(heap, (void *) cat); cat = cat_next; } act_next = act->parent; DUK_FREE(heap, (void *) act); act = act_next; } /* XXX: with 'caller' property the callstack would need * to be unwound to update the 'caller' properties of * functions in the callstack. */ } else if (DUK_HOBJECT_IS_BOUNDFUNC(h)) { duk_hboundfunc *f = (duk_hboundfunc *) (void *) h; DUK_FREE(heap, f->args); } DUK_FREE(heap, (void *) h); } DUK_INTERNAL void duk_free_hbuffer(duk_heap *heap, duk_hbuffer *h) { DUK_ASSERT(heap != NULL); DUK_ASSERT(h != NULL); if (DUK_HBUFFER_HAS_DYNAMIC(h) && !DUK_HBUFFER_HAS_EXTERNAL(h)) { duk_hbuffer_dynamic *g = (duk_hbuffer_dynamic *) h; DUK_DDD(DUK_DDDPRINT("free dynamic buffer %p", (void *) DUK_HBUFFER_DYNAMIC_GET_DATA_PTR(heap, g))); DUK_FREE(heap, DUK_HBUFFER_DYNAMIC_GET_DATA_PTR(heap, g)); } DUK_FREE(heap, (void *) h); } DUK_INTERNAL void duk_free_hstring(duk_heap *heap, duk_hstring *h) { DUK_ASSERT(heap != NULL); DUK_ASSERT(h != NULL); DUK_UNREF(heap); DUK_UNREF(h); #if defined(DUK_USE_HSTRING_EXTDATA) && defined(DUK_USE_EXTSTR_FREE) if (DUK_HSTRING_HAS_EXTDATA(h)) { DUK_DDD( DUK_DDDPRINT("free extstr: hstring %!O, extdata: %p", h, DUK_HSTRING_GET_EXTDATA((duk_hstring_external *) h))); DUK_USE_EXTSTR_FREE(heap->heap_udata, (const void *) DUK_HSTRING_GET_EXTDATA((duk_hstring_external *) h)); } #endif DUK_FREE(heap, (void *) h); } DUK_INTERNAL void duk_heap_free_heaphdr_raw(duk_heap *heap, duk_heaphdr *hdr) { DUK_ASSERT(heap); DUK_ASSERT(hdr); DUK_DDD(DUK_DDDPRINT("free heaphdr %p, htype %ld", (void *) hdr, (long) DUK_HEAPHDR_GET_TYPE(hdr))); switch (DUK_HEAPHDR_GET_TYPE(hdr)) { case DUK_HTYPE_STRING: duk_free_hstring(heap, (duk_hstring *) hdr); break; case DUK_HTYPE_OBJECT: duk_free_hobject(heap, (duk_hobject *) hdr); break; default: DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(hdr) == DUK_HTYPE_BUFFER); duk_free_hbuffer(heap, (duk_hbuffer *) hdr); } } /* * Free the heap. * * Frees heap-related non-heap-tracked allocations such as the * string intern table; then frees the heap allocated objects; * and finally frees the heap structure itself. Reference counts * and GC markers are ignored (and not updated) in this process, * and finalizers won't be called. * * The heap pointer and heap object pointers must not be used * after this call. */ #if defined(DUK_USE_CACHE_ACTIVATION) DUK_LOCAL duk_size_t duk__heap_free_activation_freelist(duk_heap *heap) { duk_activation *act; duk_activation *act_next; duk_size_t count_act = 0; for (act = heap->activation_free; act != NULL;) { act_next = act->parent; DUK_FREE(heap, (void *) act); act = act_next; #if defined(DUK_USE_DEBUG) count_act++; #endif } heap->activation_free = NULL; /* needed when called from mark-and-sweep */ return count_act; } #endif /* DUK_USE_CACHE_ACTIVATION */ #if defined(DUK_USE_CACHE_CATCHER) DUK_LOCAL duk_size_t duk__heap_free_catcher_freelist(duk_heap *heap) { duk_catcher *cat; duk_catcher *cat_next; duk_size_t count_cat = 0; for (cat = heap->catcher_free; cat != NULL;) { cat_next = cat->parent; DUK_FREE(heap, (void *) cat); cat = cat_next; #if defined(DUK_USE_DEBUG) count_cat++; #endif } heap->catcher_free = NULL; /* needed when called from mark-and-sweep */ return count_cat; } #endif /* DUK_USE_CACHE_CATCHER */ DUK_INTERNAL void duk_heap_free_freelists(duk_heap *heap) { duk_size_t count_act = 0; duk_size_t count_cat = 0; #if defined(DUK_USE_CACHE_ACTIVATION) count_act = duk__heap_free_activation_freelist(heap); #endif #if defined(DUK_USE_CACHE_CATCHER) count_cat = duk__heap_free_catcher_freelist(heap); #endif DUK_UNREF(heap); DUK_UNREF(count_act); DUK_UNREF(count_cat); DUK_D( DUK_DPRINT("freed %ld activation freelist entries, %ld catcher freelist entries", (long) count_act, (long) count_cat)); } DUK_LOCAL void duk__free_allocated(duk_heap *heap) { duk_heaphdr *curr; duk_heaphdr *next; curr = heap->heap_allocated; while (curr) { /* We don't log or warn about freeing zero refcount objects * because they may happen with finalizer processing. */ DUK_DDD(DUK_DDDPRINT("FINALFREE (allocated): %!iO", (duk_heaphdr *) curr)); next = DUK_HEAPHDR_GET_NEXT(heap, curr); duk_heap_free_heaphdr_raw(heap, curr); curr = next; } } #if defined(DUK_USE_FINALIZER_SUPPORT) DUK_LOCAL void duk__free_finalize_list(duk_heap *heap) { duk_heaphdr *curr; duk_heaphdr *next; curr = heap->finalize_list; while (curr) { DUK_DDD(DUK_DDDPRINT("FINALFREE (finalize_list): %!iO", (duk_heaphdr *) curr)); next = DUK_HEAPHDR_GET_NEXT(heap, curr); duk_heap_free_heaphdr_raw(heap, curr); curr = next; } } #endif /* DUK_USE_FINALIZER_SUPPORT */ DUK_LOCAL void duk__free_stringtable(duk_heap *heap) { /* strings are only tracked by stringtable */ duk_heap_strtable_free(heap); } #if defined(DUK_USE_FINALIZER_SUPPORT) DUK_LOCAL void duk__free_run_finalizers(duk_heap *heap) { duk_heaphdr *curr; duk_uint_t round_no; duk_size_t count_all; duk_size_t count_finalized; duk_size_t curr_limit; DUK_ASSERT(heap != NULL); #if defined(DUK_USE_REFERENCE_COUNTING) DUK_ASSERT(heap->refzero_list == NULL); /* refzero not running -> must be empty */ #endif DUK_ASSERT(heap->finalize_list == NULL); /* mark-and-sweep last pass */ if (heap->heap_thread == NULL) { /* May happen when heap allocation fails right off. There * cannot be any finalizable objects in this case. */ DUK_D(DUK_DPRINT("no heap_thread in heap destruct, assume no finalizable objects")); return; } /* Prevent finalize_list processing and mark-and-sweep entirely. * Setting ms_running != 0 also prevents refzero handling from moving * objects away from the heap_allocated list. The flag name is a bit * misleading here. * * Use a distinct value for ms_running here (== 2) so that assertions * can detect this situation separate from the normal runtime * mark-and-sweep case. This allows better assertions (GH-2030). */ DUK_ASSERT(heap->pf_prevent_count == 0); DUK_ASSERT(heap->ms_running == 0); DUK_ASSERT(heap->ms_prevent_count == 0); heap->pf_prevent_count = 1; heap->ms_running = 2; /* Use distinguishable value. */ heap->ms_prevent_count = 1; /* Bump, because mark-and-sweep assumes it's bumped when ms_running is set. */ curr_limit = 0; /* suppress warning, not used */ for (round_no = 0;; round_no++) { curr = heap->heap_allocated; count_all = 0; count_finalized = 0; while (curr) { count_all++; if (DUK_HEAPHDR_IS_OBJECT(curr)) { /* Only objects in heap_allocated may have finalizers. Check that * the object itself has a _Finalizer property (own or inherited) * so that we don't execute finalizers for e.g. Proxy objects. */ DUK_ASSERT(curr != NULL); if (DUK_HOBJECT_HAS_FINALIZER_FAST(heap, (duk_hobject *) curr)) { if (!DUK_HEAPHDR_HAS_FINALIZED((duk_heaphdr *) curr)) { DUK_ASSERT( DUK_HEAP_HAS_FINALIZER_NORESCUE(heap)); /* maps to finalizer 2nd argument */ duk_heap_run_finalizer(heap, (duk_hobject *) curr); count_finalized++; } } } curr = DUK_HEAPHDR_GET_NEXT(heap, curr); } /* Each round of finalizer execution may spawn new finalizable objects * which is normal behavior for some applications. Allow multiple * rounds of finalization, but use a shrinking limit based on the * first round to detect the case where a runaway finalizer creates * an unbounded amount of new finalizable objects. Finalizer rescue * is not supported: the semantics are unclear because most of the * objects being finalized here are already reachable. The finalizer * is given a boolean to indicate that rescue is not possible. * * See discussion in: https://github.com/svaarala/duktape/pull/473 */ if (round_no == 0) { /* Cannot wrap: each object is at least 8 bytes so count is * at most 1/8 of that. */ curr_limit = count_all * 2; } else { curr_limit = (curr_limit * 3) / 4; /* Decrease by 25% every round */ } DUK_D(DUK_DPRINT("finalizer round %ld complete, %ld objects, tried to execute %ld finalizers, current limit is %ld", (long) round_no, (long) count_all, (long) count_finalized, (long) curr_limit)); if (count_finalized == 0) { DUK_D(DUK_DPRINT("no more finalizable objects, forced finalization finished")); break; } if (count_finalized >= curr_limit) { DUK_D(DUK_DPRINT("finalizer count above limit, potentially runaway finalizer; skip remaining finalizers")); break; } } DUK_ASSERT(heap->ms_running == 2); DUK_ASSERT(heap->pf_prevent_count == 1); heap->ms_running = 0; heap->pf_prevent_count = 0; } #endif /* DUK_USE_FINALIZER_SUPPORT */ DUK_INTERNAL void duk_heap_free(duk_heap *heap) { DUK_D(DUK_DPRINT("free heap: %p", (void *) heap)); #if defined(DUK_USE_DEBUG) duk_heap_strtable_dump(heap); #endif #if defined(DUK_USE_DEBUGGER_SUPPORT) /* Detach a debugger if attached (can be called multiple times) * safely. */ /* XXX: Add a flag to reject an attempt to re-attach? Otherwise * the detached callback may immediately reattach. */ duk_debug_do_detach(heap); #endif /* Execute finalizers before freeing the heap, even for reachable * objects. This gives finalizers the chance to free any native * resources like file handles, allocations made outside Duktape, * etc. This is quite tricky to get right, so that all finalizer * guarantees are honored. * * Run mark-and-sweep a few times just in case (unreachable object * finalizers run already here). The last round must rescue objects * from the previous round without running any more finalizers. This * ensures rescued objects get their FINALIZED flag cleared so that * their finalizer is called once more in forced finalization to * satisfy finalizer guarantees. However, we don't want to run any * more finalizers because that'd required one more loop, and so on. * * XXX: this perhaps requires an execution time limit. */ DUK_D(DUK_DPRINT("execute finalizers before freeing heap")); DUK_ASSERT(heap->pf_skip_finalizers == 0); DUK_D(DUK_DPRINT("forced gc #1 in heap destruction")); duk_heap_mark_and_sweep(heap, 0); DUK_D(DUK_DPRINT("forced gc #2 in heap destruction")); duk_heap_mark_and_sweep(heap, 0); DUK_D(DUK_DPRINT("forced gc #3 in heap destruction (don't run finalizers)")); heap->pf_skip_finalizers = 1; duk_heap_mark_and_sweep(heap, 0); /* Skip finalizers; queue finalizable objects to heap_allocated. */ /* There are never objects in refzero_list at this point, or at any * point beyond a DECREF (even a DECREF_NORZ). Since Duktape 2.1 * refzero_list processing is side effect free, so it is always * processed to completion by a DECREF initially triggering a zero * refcount. */ #if defined(DUK_USE_REFERENCE_COUNTING) DUK_ASSERT(heap->refzero_list == NULL); /* Always processed to completion inline. */ #endif #if defined(DUK_USE_FINALIZER_SUPPORT) DUK_ASSERT(heap->finalize_list == NULL); /* Last mark-and-sweep with skip_finalizers. */ #endif #if defined(DUK_USE_FINALIZER_SUPPORT) DUK_D(DUK_DPRINT("run finalizers for remaining finalizable objects")); DUK_HEAP_SET_FINALIZER_NORESCUE(heap); /* Rescue no longer supported. */ duk__free_run_finalizers(heap); #endif /* DUK_USE_FINALIZER_SUPPORT */ /* Note: heap->heap_thread, heap->curr_thread, and heap->heap_object * are on the heap allocated list. */ DUK_D(DUK_DPRINT("freeing temporary freelists")); duk_heap_free_freelists(heap); DUK_D(DUK_DPRINT("freeing heap_allocated of heap: %p", (void *) heap)); duk__free_allocated(heap); #if defined(DUK_USE_REFERENCE_COUNTING) DUK_ASSERT(heap->refzero_list == NULL); /* Always processed to completion inline. */ #endif #if defined(DUK_USE_FINALIZER_SUPPORT) DUK_D(DUK_DPRINT("freeing finalize_list of heap: %p", (void *) heap)); duk__free_finalize_list(heap); #endif DUK_D(DUK_DPRINT("freeing string table of heap: %p", (void *) heap)); duk__free_stringtable(heap); DUK_D(DUK_DPRINT("freeing heap structure: %p", (void *) heap)); heap->free_func(heap->heap_udata, heap); } /* * Allocate a heap. * * String table is initialized with built-in strings from genbuiltins.py, * either by dynamically creating the strings or by referring to ROM strings. */ #if defined(DUK_USE_ROM_STRINGS) DUK_LOCAL duk_bool_t duk__init_heap_strings(duk_heap *heap) { #if defined(DUK_USE_ASSERTIONS) duk_small_uint_t i; #endif DUK_UNREF(heap); /* With ROM-based strings, heap->strs[] and thr->strs[] are omitted * so nothing to initialize for strs[]. */ #if defined(DUK_USE_ASSERTIONS) for (i = 0; i < sizeof(duk_rom_strings_lookup) / sizeof(const duk_hstring *); i++) { const duk_hstring *h; duk_uint32_t hash; h = duk_rom_strings_lookup[i]; while (h != NULL) { hash = duk_heap_hashstring(heap, (const duk_uint8_t *) DUK_HSTRING_GET_DATA(h), DUK_HSTRING_GET_BYTELEN(h)); DUK_DD(DUK_DDPRINT("duk_rom_strings_lookup[%d] -> hash 0x%08lx, computed 0x%08lx", (int) i, (unsigned long) DUK_HSTRING_GET_HASH(h), (unsigned long) hash)); DUK_ASSERT(hash == (duk_uint32_t) DUK_HSTRING_GET_HASH(h)); h = (const duk_hstring *) h->hdr.h_next; } } #endif return 1; } #else /* DUK_USE_ROM_STRINGS */ DUK_LOCAL duk_bool_t duk__init_heap_strings(duk_heap *heap) { duk_bitdecoder_ctx bd_ctx; duk_bitdecoder_ctx *bd = &bd_ctx; /* convenience */ duk_small_uint_t i; duk_memzero(&bd_ctx, sizeof(bd_ctx)); bd->data = (const duk_uint8_t *) duk_strings_data; bd->length = (duk_size_t) DUK_STRDATA_DATA_LENGTH; for (i = 0; i < DUK_HEAP_NUM_STRINGS; i++) { duk_uint8_t tmp[DUK_STRDATA_MAX_STRLEN]; duk_small_uint_t len; duk_hstring *h; len = duk_bd_decode_bitpacked_string(bd, tmp); /* No need to length check string: it will never exceed even * the 16-bit length maximum. */ DUK_ASSERT(len <= 0xffffUL); DUK_DDD(DUK_DDDPRINT("intern built-in string %ld", (long) i)); h = duk_heap_strtable_intern(heap, tmp, len); if (!h) { goto failed; } DUK_ASSERT(!DUK_HEAPHDR_HAS_READONLY((duk_heaphdr *) h)); /* Special flags checks. Since these strings are always * reachable and a string cannot appear twice in the string * table, there's no need to check/set these flags elsewhere. * The 'internal' flag is set by string intern code. */ if (i == DUK_STRIDX_EVAL || i == DUK_STRIDX_LC_ARGUMENTS) { DUK_HSTRING_SET_EVAL_OR_ARGUMENTS(h); } if (i >= DUK_STRIDX_START_RESERVED && i < DUK_STRIDX_END_RESERVED) { DUK_HSTRING_SET_RESERVED_WORD(h); if (i >= DUK_STRIDX_START_STRICT_RESERVED) { DUK_HSTRING_SET_STRICT_RESERVED_WORD(h); } } DUK_DDD(DUK_DDDPRINT("interned: %!O", (duk_heaphdr *) h)); /* XXX: The incref macro takes a thread pointer but doesn't * use it right now. */ DUK_HSTRING_INCREF(_never_referenced_, h); #if defined(DUK_USE_HEAPPTR16) heap->strs16[i] = DUK_USE_HEAPPTR_ENC16(heap->heap_udata, (void *) h); #else heap->strs[i] = h; #endif } return 1; failed: return 0; } #endif /* DUK_USE_ROM_STRINGS */ DUK_LOCAL duk_bool_t duk__init_heap_thread(duk_heap *heap) { duk_hthread *thr; DUK_D(DUK_DPRINT("heap init: alloc heap thread")); thr = duk_hthread_alloc_unchecked(heap, DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_THREAD)); if (thr == NULL) { DUK_D(DUK_DPRINT("failed to alloc heap_thread")); return 0; } thr->state = DUK_HTHREAD_STATE_INACTIVE; #if defined(DUK_USE_ROM_STRINGS) /* No strs[] pointer. */ #else /* DUK_USE_ROM_STRINGS */ #if defined(DUK_USE_HEAPPTR16) thr->strs16 = heap->strs16; #else thr->strs = heap->strs; #endif #endif /* DUK_USE_ROM_STRINGS */ heap->heap_thread = thr; DUK_HTHREAD_INCREF(thr, thr); /* Note: first argument not really used */ /* 'thr' is now reachable */ DUK_D(DUK_DPRINT("heap init: init heap thread stacks")); if (!duk_hthread_init_stacks(heap, thr)) { return 0; } /* XXX: this may now fail, and is not handled correctly */ duk_hthread_create_builtin_objects(thr); /* default prototype */ DUK_HOBJECT_SET_PROTOTYPE_INIT_INCREF(thr, (duk_hobject *) thr, thr->builtins[DUK_BIDX_THREAD_PROTOTYPE]); return 1; } #if defined(DUK_USE_DEBUG) #define DUK__DUMPSZ(t) \ do { \ DUK_D(DUK_DPRINT("" #t "=%ld", (long) sizeof(t))); \ } while (0) /* These is not 100% because format would need to be non-portable "long long". * Also print out as doubles to catch cases where the "long" type is not wide * enough; the limits will then not be printed accurately but the magnitude * will be correct. */ #define DUK__DUMPLM_SIGNED_RAW(t, a, b) \ do { \ DUK_D(DUK_DPRINT(t "=[%ld,%ld]=[%lf,%lf]", (long) (a), (long) (b), (double) (a), (double) (b))); \ } while (0) #define DUK__DUMPLM_UNSIGNED_RAW(t, a, b) \ do { \ DUK_D(DUK_DPRINT(t "=[%lu,%lu]=[%lf,%lf]", (unsigned long) (a), (unsigned long) (b), (double) (a), (double) (b))); \ } while (0) #define DUK__DUMPLM_SIGNED(t) \ do { \ DUK__DUMPLM_SIGNED_RAW("DUK_" #t "_{MIN,MAX}", DUK_##t##_MIN, DUK_##t##_MAX); \ } while (0) #define DUK__DUMPLM_UNSIGNED(t) \ do { \ DUK__DUMPLM_UNSIGNED_RAW("DUK_" #t "_{MIN,MAX}", DUK_##t##_MIN, DUK_##t##_MAX); \ } while (0) DUK_LOCAL void duk__dump_type_sizes(void) { DUK_D(DUK_DPRINT("sizeof()")); /* basic platform types */ DUK__DUMPSZ(char); DUK__DUMPSZ(short); DUK__DUMPSZ(int); DUK__DUMPSZ(long); DUK__DUMPSZ(double); DUK__DUMPSZ(void *); DUK__DUMPSZ(size_t); /* basic types from duk_features.h */ DUK__DUMPSZ(duk_uint8_t); DUK__DUMPSZ(duk_int8_t); DUK__DUMPSZ(duk_uint16_t); DUK__DUMPSZ(duk_int16_t); DUK__DUMPSZ(duk_uint32_t); DUK__DUMPSZ(duk_int32_t); DUK__DUMPSZ(duk_uint64_t); DUK__DUMPSZ(duk_int64_t); DUK__DUMPSZ(duk_uint_least8_t); DUK__DUMPSZ(duk_int_least8_t); DUK__DUMPSZ(duk_uint_least16_t); DUK__DUMPSZ(duk_int_least16_t); DUK__DUMPSZ(duk_uint_least32_t); DUK__DUMPSZ(duk_int_least32_t); #if defined(DUK_USE_64BIT_OPS) DUK__DUMPSZ(duk_uint_least64_t); DUK__DUMPSZ(duk_int_least64_t); #endif DUK__DUMPSZ(duk_uint_fast8_t); DUK__DUMPSZ(duk_int_fast8_t); DUK__DUMPSZ(duk_uint_fast16_t); DUK__DUMPSZ(duk_int_fast16_t); DUK__DUMPSZ(duk_uint_fast32_t); DUK__DUMPSZ(duk_int_fast32_t); #if defined(DUK_USE_64BIT_OPS) DUK__DUMPSZ(duk_uint_fast64_t); DUK__DUMPSZ(duk_int_fast64_t); #endif DUK__DUMPSZ(duk_uintptr_t); DUK__DUMPSZ(duk_intptr_t); DUK__DUMPSZ(duk_uintmax_t); DUK__DUMPSZ(duk_intmax_t); DUK__DUMPSZ(duk_double_t); /* important chosen base types */ DUK__DUMPSZ(duk_int_t); DUK__DUMPSZ(duk_uint_t); DUK__DUMPSZ(duk_int_fast_t); DUK__DUMPSZ(duk_uint_fast_t); DUK__DUMPSZ(duk_small_int_t); DUK__DUMPSZ(duk_small_uint_t); DUK__DUMPSZ(duk_small_int_fast_t); DUK__DUMPSZ(duk_small_uint_fast_t); /* some derived types */ DUK__DUMPSZ(duk_codepoint_t); DUK__DUMPSZ(duk_ucodepoint_t); DUK__DUMPSZ(duk_idx_t); DUK__DUMPSZ(duk_errcode_t); DUK__DUMPSZ(duk_uarridx_t); /* tval */ DUK__DUMPSZ(duk_double_union); DUK__DUMPSZ(duk_tval); /* structs from duk_forwdecl.h */ DUK__DUMPSZ(duk_jmpbuf); /* just one 'int' for C++ exceptions */ DUK__DUMPSZ(duk_heaphdr); DUK__DUMPSZ(duk_heaphdr_string); DUK__DUMPSZ(duk_hstring); DUK__DUMPSZ(duk_hstring_external); DUK__DUMPSZ(duk_hobject); DUK__DUMPSZ(duk_harray); DUK__DUMPSZ(duk_hcompfunc); DUK__DUMPSZ(duk_hnatfunc); DUK__DUMPSZ(duk_hdecenv); DUK__DUMPSZ(duk_hobjenv); DUK__DUMPSZ(duk_hthread); #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) DUK__DUMPSZ(duk_hbufobj); #endif DUK__DUMPSZ(duk_hproxy); DUK__DUMPSZ(duk_hbuffer); DUK__DUMPSZ(duk_hbuffer_fixed); DUK__DUMPSZ(duk_hbuffer_dynamic); DUK__DUMPSZ(duk_hbuffer_external); DUK__DUMPSZ(duk_propaccessor); DUK__DUMPSZ(duk_propvalue); DUK__DUMPSZ(duk_propdesc); DUK__DUMPSZ(duk_heap); DUK__DUMPSZ(duk_activation); DUK__DUMPSZ(duk_catcher); DUK__DUMPSZ(duk_strcache_entry); DUK__DUMPSZ(duk_litcache_entry); DUK__DUMPSZ(duk_ljstate); DUK__DUMPSZ(duk_fixedbuffer); DUK__DUMPSZ(duk_bitdecoder_ctx); DUK__DUMPSZ(duk_bitencoder_ctx); DUK__DUMPSZ(duk_token); DUK__DUMPSZ(duk_re_token); DUK__DUMPSZ(duk_lexer_point); DUK__DUMPSZ(duk_lexer_ctx); DUK__DUMPSZ(duk_compiler_instr); DUK__DUMPSZ(duk_compiler_func); DUK__DUMPSZ(duk_compiler_ctx); DUK__DUMPSZ(duk_re_matcher_ctx); DUK__DUMPSZ(duk_re_compiler_ctx); } DUK_LOCAL void duk__dump_type_limits(void) { DUK_D(DUK_DPRINT("limits")); /* basic types */ DUK__DUMPLM_SIGNED(INT8); DUK__DUMPLM_UNSIGNED(UINT8); DUK__DUMPLM_SIGNED(INT_FAST8); DUK__DUMPLM_UNSIGNED(UINT_FAST8); DUK__DUMPLM_SIGNED(INT_LEAST8); DUK__DUMPLM_UNSIGNED(UINT_LEAST8); DUK__DUMPLM_SIGNED(INT16); DUK__DUMPLM_UNSIGNED(UINT16); DUK__DUMPLM_SIGNED(INT_FAST16); DUK__DUMPLM_UNSIGNED(UINT_FAST16); DUK__DUMPLM_SIGNED(INT_LEAST16); DUK__DUMPLM_UNSIGNED(UINT_LEAST16); DUK__DUMPLM_SIGNED(INT32); DUK__DUMPLM_UNSIGNED(UINT32); DUK__DUMPLM_SIGNED(INT_FAST32); DUK__DUMPLM_UNSIGNED(UINT_FAST32); DUK__DUMPLM_SIGNED(INT_LEAST32); DUK__DUMPLM_UNSIGNED(UINT_LEAST32); #if defined(DUK_USE_64BIT_OPS) DUK__DUMPLM_SIGNED(INT64); DUK__DUMPLM_UNSIGNED(UINT64); DUK__DUMPLM_SIGNED(INT_FAST64); DUK__DUMPLM_UNSIGNED(UINT_FAST64); DUK__DUMPLM_SIGNED(INT_LEAST64); DUK__DUMPLM_UNSIGNED(UINT_LEAST64); #endif DUK__DUMPLM_SIGNED(INTPTR); DUK__DUMPLM_UNSIGNED(UINTPTR); DUK__DUMPLM_SIGNED(INTMAX); DUK__DUMPLM_UNSIGNED(UINTMAX); /* derived types */ DUK__DUMPLM_SIGNED(INT); DUK__DUMPLM_UNSIGNED(UINT); DUK__DUMPLM_SIGNED(INT_FAST); DUK__DUMPLM_UNSIGNED(UINT_FAST); DUK__DUMPLM_SIGNED(SMALL_INT); DUK__DUMPLM_UNSIGNED(SMALL_UINT); DUK__DUMPLM_SIGNED(SMALL_INT_FAST); DUK__DUMPLM_UNSIGNED(SMALL_UINT_FAST); } DUK_LOCAL void duk__dump_misc_options(void) { DUK_D(DUK_DPRINT("DUK_VERSION: %ld", (long) DUK_VERSION)); DUK_D(DUK_DPRINT("DUK_GIT_DESCRIBE: %s", DUK_GIT_DESCRIBE)); DUK_D(DUK_DPRINT("OS string: %s", DUK_USE_OS_STRING)); DUK_D(DUK_DPRINT("architecture string: %s", DUK_USE_ARCH_STRING)); DUK_D(DUK_DPRINT("compiler string: %s", DUK_USE_COMPILER_STRING)); DUK_D(DUK_DPRINT("debug level: %ld", (long) DUK_USE_DEBUG_LEVEL)); #if defined(DUK_USE_PACKED_TVAL) DUK_D(DUK_DPRINT("DUK_USE_PACKED_TVAL: yes")); #else DUK_D(DUK_DPRINT("DUK_USE_PACKED_TVAL: no")); #endif #if defined(DUK_USE_VARIADIC_MACROS) DUK_D(DUK_DPRINT("DUK_USE_VARIADIC_MACROS: yes")); #else DUK_D(DUK_DPRINT("DUK_USE_VARIADIC_MACROS: no")); #endif #if defined(DUK_USE_INTEGER_LE) DUK_D(DUK_DPRINT("integer endianness: little")); #elif defined(DUK_USE_INTEGER_ME) DUK_D(DUK_DPRINT("integer endianness: mixed")); #elif defined(DUK_USE_INTEGER_BE) DUK_D(DUK_DPRINT("integer endianness: big")); #else DUK_D(DUK_DPRINT("integer endianness: ???")); #endif #if defined(DUK_USE_DOUBLE_LE) DUK_D(DUK_DPRINT("IEEE double endianness: little")); #elif defined(DUK_USE_DOUBLE_ME) DUK_D(DUK_DPRINT("IEEE double endianness: mixed")); #elif defined(DUK_USE_DOUBLE_BE) DUK_D(DUK_DPRINT("IEEE double endianness: big")); #else DUK_D(DUK_DPRINT("IEEE double endianness: ???")); #endif } #endif /* DUK_USE_DEBUG */ DUK_INTERNAL duk_heap *duk_heap_alloc(duk_alloc_function alloc_func, duk_realloc_function realloc_func, duk_free_function free_func, void *heap_udata, duk_fatal_function fatal_func) { duk_heap *res = NULL; duk_uint32_t st_initsize; DUK_D(DUK_DPRINT("allocate heap")); /* * Random config sanity asserts */ DUK_ASSERT(DUK_USE_STRTAB_MINSIZE >= 64); DUK_ASSERT((DUK_HTYPE_STRING & 0x01U) == 0); DUK_ASSERT((DUK_HTYPE_BUFFER & 0x01U) == 0); DUK_ASSERT((DUK_HTYPE_OBJECT & 0x01U) == 1); /* DUK_HEAPHDR_IS_OBJECT() relies ont his. */ /* * Debug dump type sizes */ #if defined(DUK_USE_DEBUG) duk__dump_misc_options(); duk__dump_type_sizes(); duk__dump_type_limits(); #endif /* * If selftests enabled, run them as early as possible. */ #if defined(DUK_USE_SELF_TESTS) DUK_D(DUK_DPRINT("run self tests")); if (duk_selftest_run_tests(alloc_func, realloc_func, free_func, heap_udata) > 0) { fatal_func(heap_udata, "self test(s) failed"); } DUK_D(DUK_DPRINT("self tests passed")); #endif /* * Important assert-like checks that should be enabled even * when assertions are otherwise not enabled. */ #if defined(DUK_USE_EXEC_REGCONST_OPTIMIZE) /* Can't check sizeof() using preprocessor so explicit check. * This will be optimized away in practice; unfortunately a * warning is generated on some compilers as a result. */ #if defined(DUK_USE_PACKED_TVAL) if (sizeof(duk_tval) != 8) { #else if (sizeof(duk_tval) != 16) { #endif fatal_func(heap_udata, "sizeof(duk_tval) not 8 or 16, cannot use DUK_USE_EXEC_REGCONST_OPTIMIZE option"); } #endif /* DUK_USE_EXEC_REGCONST_OPTIMIZE */ /* * Computed values (e.g. INFINITY) */ #if defined(DUK_USE_COMPUTED_NAN) do { /* Workaround for some exotic platforms where NAN is missing * and the expression (0.0 / 0.0) does NOT result in a NaN. * Such platforms use the global 'duk_computed_nan' which must * be initialized at runtime. Use 'volatile' to ensure that * the compiler will actually do the computation and not try * to do constant folding which might result in the original * problem. */ volatile double dbl1 = 0.0; volatile double dbl2 = 0.0; duk_computed_nan = dbl1 / dbl2; } while (0); #endif #if defined(DUK_USE_COMPUTED_INFINITY) do { /* Similar workaround for INFINITY. */ volatile double dbl1 = 1.0; volatile double dbl2 = 0.0; duk_computed_infinity = dbl1 / dbl2; } while (0); #endif /* * Allocate heap struct * * Use a raw call, all macros expect the heap to be initialized */ #if defined(DUK_USE_INJECT_HEAP_ALLOC_ERROR) && (DUK_USE_INJECT_HEAP_ALLOC_ERROR == 1) goto failed; #endif DUK_D(DUK_DPRINT("alloc duk_heap object")); res = (duk_heap *) alloc_func(heap_udata, sizeof(duk_heap)); if (!res) { goto failed; } /* * Zero the struct, and start initializing roughly in order */ duk_memzero(res, sizeof(*res)); #if defined(DUK_USE_ASSERTIONS) res->heap_initializing = 1; #endif /* explicit NULL inits */ #if defined(DUK_USE_EXPLICIT_NULL_INIT) res->heap_udata = NULL; res->heap_allocated = NULL; #if defined(DUK_USE_REFERENCE_COUNTING) res->refzero_list = NULL; #endif #if defined(DUK_USE_FINALIZER_SUPPORT) res->finalize_list = NULL; #if defined(DUK_USE_ASSERTIONS) res->currently_finalizing = NULL; #endif #endif #if defined(DUK_USE_CACHE_ACTIVATION) res->activation_free = NULL; #endif #if defined(DUK_USE_CACHE_CATCHER) res->catcher_free = NULL; #endif res->heap_thread = NULL; res->curr_thread = NULL; res->heap_object = NULL; #if defined(DUK_USE_STRTAB_PTRCOMP) res->strtable16 = NULL; #else res->strtable = NULL; #endif #if defined(DUK_USE_ROM_STRINGS) /* no res->strs[] */ #else /* DUK_USE_ROM_STRINGS */ #if defined(DUK_USE_HEAPPTR16) /* res->strs16[] is zeroed and zero decodes to NULL, so no NULL inits. */ #else { duk_small_uint_t i; for (i = 0; i < DUK_HEAP_NUM_STRINGS; i++) { res->strs[i] = NULL; } } #endif #endif /* DUK_USE_ROM_STRINGS */ #if defined(DUK_USE_DEBUGGER_SUPPORT) res->dbg_read_cb = NULL; res->dbg_write_cb = NULL; res->dbg_peek_cb = NULL; res->dbg_read_flush_cb = NULL; res->dbg_write_flush_cb = NULL; res->dbg_request_cb = NULL; res->dbg_udata = NULL; res->dbg_pause_act = NULL; #endif #endif /* DUK_USE_EXPLICIT_NULL_INIT */ res->alloc_func = alloc_func; res->realloc_func = realloc_func; res->free_func = free_func; res->heap_udata = heap_udata; res->fatal_func = fatal_func; /* XXX: for now there's a pointer packing zero assumption, i.e. * NULL <=> compressed pointer 0. If this is removed, may need * to precompute e.g. null16 here. */ /* res->ms_trigger_counter == 0 -> now causes immediate GC; which is OK */ /* Prevent mark-and-sweep and finalizer execution until heap is completely * initialized. */ DUK_ASSERT(res->ms_prevent_count == 0); DUK_ASSERT(res->pf_prevent_count == 0); res->ms_prevent_count = 1; res->pf_prevent_count = 1; DUK_ASSERT(res->ms_running == 0); res->call_recursion_depth = 0; res->call_recursion_limit = DUK_USE_NATIVE_CALL_RECLIMIT; /* XXX: use the pointer as a seed for now: mix in time at least */ /* The casts through duk_uintptr_t is to avoid the following GCC warning: * * warning: cast from pointer to integer of different size [-Wpointer-to-int-cast] * * This still generates a /Wp64 warning on VS2010 when compiling for x86. */ #if defined(DUK_USE_ROM_STRINGS) /* XXX: make a common DUK_USE_ option, and allow custom fixed seed? */ DUK_D(DUK_DPRINT("using rom strings, force heap hash_seed to fixed value 0x%08lx", (long) DUK__FIXED_HASH_SEED)); res->hash_seed = (duk_uint32_t) DUK__FIXED_HASH_SEED; #else /* DUK_USE_ROM_STRINGS */ res->hash_seed = (duk_uint32_t) (duk_uintptr_t) res; #if !defined(DUK_USE_STRHASH_DENSE) res->hash_seed ^= 5381; /* Bernstein hash init value is normally 5381; XOR it in in case pointer low bits are 0 */ #endif #endif /* DUK_USE_ROM_STRINGS */ #if defined(DUK_USE_EXPLICIT_NULL_INIT) res->lj.jmpbuf_ptr = NULL; #endif DUK_ASSERT(res->lj.type == DUK_LJ_TYPE_UNKNOWN); /* zero */ DUK_ASSERT(res->lj.iserror == 0); DUK_TVAL_SET_UNDEFINED(&res->lj.value1); DUK_TVAL_SET_UNDEFINED(&res->lj.value2); DUK_ASSERT_LJSTATE_UNSET(res); /* * Init stringtable: fixed variant */ st_initsize = DUK_USE_STRTAB_MINSIZE; #if defined(DUK_USE_STRTAB_PTRCOMP) res->strtable16 = (duk_uint16_t *) alloc_func(heap_udata, sizeof(duk_uint16_t) * st_initsize); if (res->strtable16 == NULL) { goto failed; } #else res->strtable = (duk_hstring **) alloc_func(heap_udata, sizeof(duk_hstring *) * st_initsize); if (res->strtable == NULL) { goto failed; } #endif res->st_size = st_initsize; res->st_mask = st_initsize - 1; #if (DUK_USE_STRTAB_MINSIZE != DUK_USE_STRTAB_MAXSIZE) DUK_ASSERT(res->st_count == 0); #endif #if defined(DUK_USE_STRTAB_PTRCOMP) /* zero assumption */ duk_memzero(res->strtable16, sizeof(duk_uint16_t) * st_initsize); #else #if defined(DUK_USE_EXPLICIT_NULL_INIT) { duk_uint32_t i; for (i = 0; i < st_initsize; i++) { res->strtable[i] = NULL; } } #else duk_memzero(res->strtable, sizeof(duk_hstring *) * st_initsize); #endif /* DUK_USE_EXPLICIT_NULL_INIT */ #endif /* DUK_USE_STRTAB_PTRCOMP */ /* * Init stringcache */ #if defined(DUK_USE_EXPLICIT_NULL_INIT) { duk_uint_t i; for (i = 0; i < DUK_HEAP_STRCACHE_SIZE; i++) { res->strcache[i].h = NULL; } } #endif /* * Init litcache */ #if defined(DUK_USE_LITCACHE_SIZE) DUK_ASSERT(DUK_USE_LITCACHE_SIZE > 0); DUK_ASSERT(DUK_IS_POWER_OF_TWO((duk_uint_t) DUK_USE_LITCACHE_SIZE)); #if defined(DUK_USE_EXPLICIT_NULL_INIT) { duk_uint_t i; for (i = 0; i < DUK_USE_LITCACHE_SIZE; i++) { res->litcache[i].addr = NULL; res->litcache[i].h = NULL; } } #endif #endif /* DUK_USE_LITCACHE_SIZE */ /* XXX: error handling is incomplete. It would be cleanest if * there was a setjmp catchpoint, so that all init code could * freely throw errors. If that were the case, the return code * passing here could be removed. */ /* * Init built-in strings */ #if defined(DUK_USE_INJECT_HEAP_ALLOC_ERROR) && (DUK_USE_INJECT_HEAP_ALLOC_ERROR == 2) goto failed; #endif DUK_D(DUK_DPRINT("heap init: initialize heap strings")); if (!duk__init_heap_strings(res)) { goto failed; } /* * Init the heap thread */ #if defined(DUK_USE_INJECT_HEAP_ALLOC_ERROR) && (DUK_USE_INJECT_HEAP_ALLOC_ERROR == 3) goto failed; #endif DUK_D(DUK_DPRINT("heap init: initialize heap thread")); if (!duk__init_heap_thread(res)) { goto failed; } /* * Init the heap object */ #if defined(DUK_USE_INJECT_HEAP_ALLOC_ERROR) && (DUK_USE_INJECT_HEAP_ALLOC_ERROR == 4) goto failed; #endif DUK_D(DUK_DPRINT("heap init: initialize heap object")); DUK_ASSERT(res->heap_thread != NULL); res->heap_object = duk_hobject_alloc_unchecked(res, DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_FLAG_FASTREFS | DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJECT)); if (res->heap_object == NULL) { goto failed; } DUK_HOBJECT_INCREF(res->heap_thread, res->heap_object); /* * Odds and ends depending on the heap thread */ #if !defined(DUK_USE_GET_RANDOM_DOUBLE) #if defined(DUK_USE_PREFER_SIZE) || !defined(DUK_USE_64BIT_OPS) res->rnd_state = (duk_uint32_t) duk_time_get_ecmascript_time(res->heap_thread); duk_util_tinyrandom_prepare_seed(res->heap_thread); #else res->rnd_state[0] = (duk_uint64_t) duk_time_get_ecmascript_time(res->heap_thread); DUK_ASSERT(res->rnd_state[1] == 0); /* Not filled here, filled in by seed preparation. */ #if 0 /* Manual test values matching misc/xoroshiro128plus_test.c. */ res->rnd_state[0] = DUK_U64_CONSTANT(0xdeadbeef12345678); res->rnd_state[1] = DUK_U64_CONSTANT(0xcafed00d12345678); #endif duk_util_tinyrandom_prepare_seed(res->heap_thread); /* Mix in heap pointer: this ensures that if two Duktape heaps are * created on the same millisecond, they get a different PRNG * sequence (unless e.g. virtual memory addresses cause also the * heap object pointer to be the same). */ { duk_uint64_t tmp_u64; tmp_u64 = 0; duk_memcpy((void *) &tmp_u64, (const void *) &res, (size_t) (sizeof(void *) >= sizeof(duk_uint64_t) ? sizeof(duk_uint64_t) : sizeof(void *))); res->rnd_state[1] ^= tmp_u64; } do { duk_small_uint_t i; for (i = 0; i < 10; i++) { /* Throw away a few initial random numbers just in * case. Probably unnecessary due to SplitMix64 * preparation. */ (void) duk_util_tinyrandom_get_double(res->heap_thread); } } while (0); #endif #endif /* * Allow finalizer and mark-and-sweep processing. */ DUK_D(DUK_DPRINT("heap init: allow finalizer/mark-and-sweep processing")); DUK_ASSERT(res->ms_prevent_count == 1); DUK_ASSERT(res->pf_prevent_count == 1); res->ms_prevent_count = 0; res->pf_prevent_count = 0; DUK_ASSERT(res->ms_running == 0); #if defined(DUK_USE_ASSERTIONS) res->heap_initializing = 0; #endif /* * All done. */ DUK_D(DUK_DPRINT("allocated heap: %p", (void *) res)); return res; failed: DUK_D(DUK_DPRINT("heap allocation failed")); if (res != NULL) { /* Assumes that allocated pointers and alloc funcs are valid * if res exists. */ DUK_ASSERT(res->ms_prevent_count == 1); DUK_ASSERT(res->pf_prevent_count == 1); DUK_ASSERT(res->ms_running == 0); if (res->heap_thread != NULL) { res->ms_prevent_count = 0; res->pf_prevent_count = 0; } #if defined(DUK_USE_ASSERTIONS) res->heap_initializing = 0; #endif DUK_ASSERT(res->alloc_func != NULL); DUK_ASSERT(res->realloc_func != NULL); DUK_ASSERT(res->free_func != NULL); duk_heap_free(res); } return NULL; } /* automatic undefs */ #undef DUK__DUMPLM_SIGNED #undef DUK__DUMPLM_SIGNED_RAW #undef DUK__DUMPLM_UNSIGNED #undef DUK__DUMPLM_UNSIGNED_RAW #undef DUK__DUMPSZ #undef DUK__FIXED_HASH_SEED #line 1 "duk_heap_finalize.c" /* * Finalizer handling. */ /* #include duk_internal.h -> already included */ #if defined(DUK_USE_FINALIZER_SUPPORT) /* * Fake torture finalizer. */ #if defined(DUK_USE_FINALIZER_TORTURE) DUK_LOCAL duk_ret_t duk__fake_global_finalizer(duk_hthread *thr) { DUK_DD(DUK_DDPRINT("fake global torture finalizer executed")); /* Require a lot of stack to force a value stack grow/shrink. */ duk_require_stack(thr, 100000); /* Force a reallocation with pointer change for value stack * to maximize side effects. */ duk_hthread_valstack_torture_realloc(thr); /* Inner function call, error throw. */ duk_eval_string_noresult(thr, "(function dummy() {\n" " dummy.prototype = null; /* break reference loop */\n" " try {\n" " throw 'fake-finalizer-dummy-error';\n" " } catch (e) {\n" " void e;\n" " }\n" "})()"); /* The above creates garbage (e.g. a function instance). Because * the function/prototype reference loop is broken, it gets collected * immediately by DECREF. If Function.prototype has a _Finalizer * property (happens in some test cases), the garbage gets queued to * finalize_list. This still won't cause an infinite loop because * the torture finalizer is called once per finalize_list run and * the garbage gets handled in the same run. (If the garbage needs * mark-and-sweep collection, an infinite loop might ensue.) */ return 0; } DUK_LOCAL void duk__run_global_torture_finalizer(duk_hthread *thr) { DUK_ASSERT(thr != NULL); /* Avoid fake finalization when callstack limit is near. Otherwise * a callstack limit error will be created, then refzero'ed. The * +5 headroom is conservative. */ if (thr->heap->call_recursion_depth + 5 >= thr->heap->call_recursion_limit || thr->callstack_top + 5 >= DUK_USE_CALLSTACK_LIMIT) { DUK_D(DUK_DPRINT("skip global torture finalizer, too little headroom for call recursion or call stack size")); return; } /* Run fake finalizer. Avoid creating unnecessary garbage. */ duk_push_c_function(thr, duk__fake_global_finalizer, 0 /*nargs*/); (void) duk_pcall(thr, 0 /*nargs*/); duk_pop(thr); } #endif /* DUK_USE_FINALIZER_TORTURE */ /* * Process the finalize_list to completion. * * An object may be placed on finalize_list by either refcounting or * mark-and-sweep. The refcount of objects placed by refcounting will be * zero; the refcount of objects placed by mark-and-sweep is > 0. In both * cases the refcount is bumped by 1 artificially so that a REFZERO event * can never happen while an object is waiting for finalization. Without * this bump a REFZERO could now happen because user code may call * duk_push_heapptr() and then pop a value even when it's on finalize_list. * * List processing assumes refcounts are kept up-to-date at all times, so * that once the finalizer returns, a zero refcount is a reliable reason to * free the object immediately rather than place it back to the heap. This * is the case because we run outside of refzero_list processing so that * DECREF cascades are handled fully inline. * * For mark-and-sweep queued objects (had_zero_refcount false) the object * may be freed immediately if its refcount is zero after the finalizer call * (i.e. finalizer removed the reference loop for the object). If not, the * next mark-and-sweep will collect the object unless it has become reachable * (i.e. rescued) by that time and its refcount hasn't fallen to zero before * that. Mark-and-sweep detects these objects because their FINALIZED flag * is set. * * There's an inherent limitation for mark-and-sweep finalizer rescuing: an * object won't get refinalized if (1) it's rescued, but (2) becomes * unreachable before mark-and-sweep has had time to notice it. The next * mark-and-sweep round simply doesn't have any information of whether the * object has been unreachable the whole time or not (the only way to get * that information would be a mark-and-sweep pass for *every finalized * object*). This is awkward for the application because the mark-and-sweep * round is not generally visible or under full application control. * * For refcount queued objects (had_zero_refcount true) the object is either * immediately freed or rescued, and waiting for a mark-and-sweep round is not * necessary (or desirable); FINALIZED is cleared when a rescued object is * queued back to heap_allocated. The object is eligible for finalization * again (either via refcounting or mark-and-sweep) immediately after being * rescued. If a refcount finalized object is placed into an unreachable * reference loop by its finalizer, it will get collected by mark-and-sweep * and currently the finalizer will execute again. * * There's a special case where: * * - Mark-and-sweep queues an object to finalize_list for finalization. * - The finalizer is executed, FINALIZED is set, and object is queued * back to heap_allocated, waiting for a new mark-and-sweep round. * - The object's refcount drops to zero before mark-and-sweep has a * chance to run another round and make a rescue/free decision. * * This is now handled by refzero code: if an object has a finalizer but * FINALIZED is already set, the object is freed without finalizer processing. * The outcome is the same as if mark-and-sweep was executed at that point; * mark-and-sweep would also free the object without another finalizer run. * This could also be changed so that the refzero-triggered finalizer *IS* * executed: being refzero collected implies someone has operated on the * object so it hasn't been totally unreachable the whole time. This would * risk a finalizer loop however. */ DUK_INTERNAL void duk_heap_process_finalize_list(duk_heap *heap) { duk_heaphdr *curr; #if defined(DUK_USE_DEBUG) duk_size_t count = 0; #endif DUK_DDD(DUK_DDDPRINT("duk_heap_process_finalize_list: %p", (void *) heap)); if (heap->pf_prevent_count != 0) { DUK_DDD(DUK_DDDPRINT("skip finalize_list processing: pf_prevent_count != 0")); return; } /* Heap alloc prevents mark-and-sweep before heap_thread is ready. */ DUK_ASSERT(heap != NULL); DUK_ASSERT(heap->heap_thread != NULL); DUK_ASSERT(heap->heap_thread->valstack != NULL); #if defined(DUK_USE_REFERENCE_COUNTING) DUK_ASSERT(heap->refzero_list == NULL); #endif DUK_ASSERT(heap->pf_prevent_count == 0); heap->pf_prevent_count = 1; /* Mark-and-sweep no longer needs to be prevented when running * finalizers: mark-and-sweep skips any rescue decisions if there * are any objects in finalize_list when mark-and-sweep is entered. * This protects finalized objects from incorrect rescue decisions * caused by finalize_list being a reachability root and only * partially processed. Freeing decisions are not postponed. */ /* When finalizer torture is enabled, make a fake finalizer call with * maximum side effects regardless of whether finalize_list is empty. */ #if defined(DUK_USE_FINALIZER_TORTURE) duk__run_global_torture_finalizer(heap->heap_thread); #endif /* Process finalize_list until it becomes empty. There's currently no * protection against a finalizer always creating more garbage. */ while ((curr = heap->finalize_list) != NULL) { #if defined(DUK_USE_REFERENCE_COUNTING) duk_bool_t queue_back; #endif DUK_DD(DUK_DDPRINT("processing finalize_list entry: %p -> %!iO", (void *) curr, curr)); DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(curr) == DUK_HTYPE_OBJECT); /* Only objects have finalizers. */ DUK_ASSERT(!DUK_HEAPHDR_HAS_REACHABLE(curr)); DUK_ASSERT(!DUK_HEAPHDR_HAS_TEMPROOT(curr)); DUK_ASSERT(DUK_HEAPHDR_HAS_FINALIZABLE( curr)); /* All objects on finalize_list will have this flag (except object being finalized right now). */ DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZED(curr)); /* Queueing code ensures. */ DUK_ASSERT(!DUK_HEAPHDR_HAS_READONLY(curr)); /* ROM objects never get freed (or finalized). */ #if defined(DUK_USE_ASSERTIONS) DUK_ASSERT(heap->currently_finalizing == NULL); heap->currently_finalizing = curr; #endif /* Clear FINALIZABLE for object being finalized, so that * duk_push_heapptr() can properly ignore the object. */ DUK_HEAPHDR_CLEAR_FINALIZABLE(curr); if (DUK_LIKELY(!heap->pf_skip_finalizers)) { /* Run the finalizer, duk_heap_run_finalizer() sets * and checks for FINALIZED to prevent the finalizer * from executing multiple times per finalization cycle. * (This safeguard shouldn't be actually needed anymore). */ #if defined(DUK_USE_REFERENCE_COUNTING) duk_bool_t had_zero_refcount; #endif /* The object's refcount is >0 throughout so it won't be * refzero processed prematurely. */ #if defined(DUK_USE_REFERENCE_COUNTING) DUK_ASSERT(DUK_HEAPHDR_GET_REFCOUNT(curr) >= 1); had_zero_refcount = (DUK_HEAPHDR_GET_REFCOUNT(curr) == 1); /* Preincremented on finalize_list insert. */ #endif DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZED(curr)); duk_heap_run_finalizer(heap, (duk_hobject *) curr); /* must never longjmp */ DUK_ASSERT(DUK_HEAPHDR_HAS_FINALIZED(curr)); /* XXX: assert that object is still in finalize_list * when duk_push_heapptr() allows automatic rescue. */ #if defined(DUK_USE_REFERENCE_COUNTING) DUK_DD(DUK_DDPRINT("refcount after finalizer (includes bump): %ld", (long) DUK_HEAPHDR_GET_REFCOUNT(curr))); if (DUK_HEAPHDR_GET_REFCOUNT(curr) == 1) { /* Only artificial bump in refcount? */ #if defined(DUK_USE_DEBUG) if (had_zero_refcount) { DUK_DD(DUK_DDPRINT( "finalized object's refcount is zero -> free immediately (refcount queued)")); } else { DUK_DD(DUK_DDPRINT( "finalized object's refcount is zero -> free immediately (mark-and-sweep queued)")); } #endif queue_back = 0; } else #endif { #if defined(DUK_USE_REFERENCE_COUNTING) queue_back = 1; if (had_zero_refcount) { /* When finalization is triggered * by refzero and we queue the object * back, clear FINALIZED right away * so that the object can be refinalized * immediately if necessary. */ DUK_HEAPHDR_CLEAR_FINALIZED(curr); } #endif } } else { /* Used during heap destruction: don't actually run finalizers * because we're heading into forced finalization. Instead, * queue finalizable objects back to the heap_allocated list. */ DUK_D(DUK_DPRINT("skip finalizers flag set, queue object to heap_allocated without finalizing")); DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZED(curr)); #if defined(DUK_USE_REFERENCE_COUNTING) queue_back = 1; #endif } /* Dequeue object from finalize_list. Note that 'curr' may no * longer be finalize_list head because new objects may have * been queued to the list. As a result we can't optimize for * the single-linked heap case and must scan the list for * removal, typically the scan is very short however. */ DUK_HEAP_REMOVE_FROM_FINALIZE_LIST(heap, curr); /* Queue back to heap_allocated or free immediately. */ #if defined(DUK_USE_REFERENCE_COUNTING) if (queue_back) { /* FINALIZED is only cleared if object originally * queued for finalization by refcounting. For * mark-and-sweep FINALIZED is left set, so that * next mark-and-sweep round can make a rescue/free * decision. */ DUK_ASSERT(DUK_HEAPHDR_GET_REFCOUNT(curr) >= 1); DUK_HEAPHDR_PREDEC_REFCOUNT(curr); /* Remove artificial refcount bump. */ DUK_HEAPHDR_CLEAR_FINALIZABLE(curr); DUK_HEAP_INSERT_INTO_HEAP_ALLOCATED(heap, curr); } else { /* No need to remove the refcount bump here. */ DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(curr) == DUK_HTYPE_OBJECT); /* currently, always the case */ DUK_DD(DUK_DDPRINT("refcount finalize after finalizer call: %!O", curr)); duk_hobject_refcount_finalize_norz(heap, (duk_hobject *) curr); duk_free_hobject(heap, (duk_hobject *) curr); DUK_DD(DUK_DDPRINT("freed hobject after finalization: %p", (void *) curr)); } #else /* DUK_USE_REFERENCE_COUNTING */ DUK_HEAPHDR_CLEAR_FINALIZABLE(curr); DUK_HEAP_INSERT_INTO_HEAP_ALLOCATED(heap, curr); #endif /* DUK_USE_REFERENCE_COUNTING */ #if defined(DUK_USE_DEBUG) count++; #endif #if defined(DUK_USE_ASSERTIONS) DUK_ASSERT(heap->currently_finalizing != NULL); heap->currently_finalizing = NULL; #endif } /* finalize_list will always be processed completely. */ DUK_ASSERT(heap->finalize_list == NULL); #if 0 /* While NORZ macros are used above, this is unnecessary because the * only pending side effects are now finalizers, and finalize_list is * empty. */ DUK_REFZERO_CHECK_SLOW(heap->heap_thread); #endif /* Prevent count may be bumped while finalizers run, but should always * be reliably unbumped by the time we get here. */ DUK_ASSERT(heap->pf_prevent_count == 1); heap->pf_prevent_count = 0; #if defined(DUK_USE_DEBUG) DUK_DD(DUK_DDPRINT("duk_heap_process_finalize_list: %ld finalizers called", (long) count)); #endif } /* * Run an duk_hobject finalizer. Must never throw an uncaught error * (but may throw caught errors). * * There is no return value. Any return value or error thrown by * the finalizer is ignored (although errors are debug logged). * * Notes: * * - The finalizer thread 'top' assertions are there because it is * critical that strict stack policy is observed (i.e. no cruft * left on the finalizer stack). */ DUK_LOCAL duk_ret_t duk__finalize_helper(duk_hthread *thr, void *udata) { DUK_ASSERT(thr != NULL); DUK_UNREF(udata); DUK_DDD(DUK_DDDPRINT("protected finalization helper running")); /* [... obj] */ /* _Finalizer property is read without checking if the value is * callable or even exists. This is intentional, and handled * by throwing an error which is caught by the safe call wrapper. * * XXX: Finalizer lookup should traverse the prototype chain (to allow * inherited finalizers) but should not invoke accessors or proxy object * behavior. At the moment this lookup will invoke proxy behavior, so * caller must ensure that this function is not called if the target is * a Proxy. */ duk_get_prop_stridx_short(thr, -1, DUK_STRIDX_INT_FINALIZER); /* -> [... obj finalizer] */ duk_dup_m2(thr); duk_push_boolean(thr, DUK_HEAP_HAS_FINALIZER_NORESCUE(thr->heap)); DUK_DDD(DUK_DDDPRINT("calling finalizer")); duk_call(thr, 2); /* [ ... obj finalizer obj heapDestruct ] -> [ ... obj retval ] */ DUK_DDD(DUK_DDDPRINT("finalizer returned successfully")); return 0; /* Note: we rely on duk_safe_call() to fix up the stack for the caller, * so we don't need to pop stuff here. There is no return value; * caller determines rescued status based on object refcount. */ } DUK_INTERNAL void duk_heap_run_finalizer(duk_heap *heap, duk_hobject *obj) { duk_hthread *thr; duk_ret_t rc; #if defined(DUK_USE_ASSERTIONS) duk_idx_t entry_top; #endif DUK_DD(DUK_DDPRINT("running duk_hobject finalizer for object: %p", (void *) obj)); DUK_ASSERT(heap != NULL); DUK_ASSERT(heap->heap_thread != NULL); thr = heap->heap_thread; DUK_ASSERT(obj != NULL); DUK_ASSERT_VALSTACK_SPACE(heap->heap_thread, 1); #if defined(DUK_USE_ASSERTIONS) entry_top = duk_get_top(thr); #endif /* * Get and call the finalizer. All of this must be wrapped * in a protected call, because even getting the finalizer * may trigger an error (getter may throw one, for instance). */ /* ROM objects could inherit a finalizer, but they are never deemed * unreachable by mark-and-sweep, and their refcount never falls to 0. */ DUK_ASSERT(!DUK_HEAPHDR_HAS_READONLY((duk_heaphdr *) obj)); /* Duktape 2.1: finalize_list never contains objects with FINALIZED * set, so no need to check here. */ DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZED((duk_heaphdr *) obj)); #if 0 if (DUK_HEAPHDR_HAS_FINALIZED((duk_heaphdr *) obj)) { DUK_D(DUK_DPRINT("object already finalized, avoid running finalizer twice: %!O", obj)); return; } #endif DUK_HEAPHDR_SET_FINALIZED((duk_heaphdr *) obj); /* ensure never re-entered until rescue cycle complete */ #if defined(DUK_USE_ES6_PROXY) if (DUK_HOBJECT_IS_PROXY(obj)) { /* This may happen if duk_set_finalizer() or Duktape.fin() is * called for a Proxy object. In such cases the fast finalizer * flag will be set on the Proxy, not the target, and neither * will be finalized. */ DUK_D(DUK_DPRINT("object is a Proxy, skip finalizer call")); return; } #endif /* DUK_USE_ES6_PROXY */ duk_push_hobject(thr, obj); /* this also increases refcount by one */ rc = duk_safe_call(thr, duk__finalize_helper, NULL /*udata*/, 0 /*nargs*/, 1 /*nrets*/); /* -> [... obj retval/error] */ DUK_ASSERT_TOP(thr, entry_top + 2); /* duk_safe_call discipline */ if (rc != DUK_EXEC_SUCCESS) { /* Note: we ask for one return value from duk_safe_call to get this * error debugging here. */ DUK_D(DUK_DPRINT("wrapped finalizer call failed for object %p (ignored); error: %!T", (void *) obj, (duk_tval *) duk_get_tval(thr, -1))); } duk_pop_2(thr); /* -> [...] */ DUK_ASSERT_TOP(thr, entry_top); } #else /* DUK_USE_FINALIZER_SUPPORT */ /* nothing */ #endif /* DUK_USE_FINALIZER_SUPPORT */ #line 1 "duk_heap_hashstring.c" /* * String hash computation (interning). * * String hashing is performance critical because a string hash is computed * for all new strings which are candidates to be added to the string table. * However, strings actually added to the string table go through a codepoint * length calculation which dominates performance because it goes through * every byte of the input string (but only for strings added). * * The string hash algorithm should be fast, but on the other hand provide * good enough hashes to ensure both string table and object property table * hash tables work reasonably well (i.e., there aren't too many collisions * with real world inputs). Unless the hash is cryptographic, it's always * possible to craft inputs with maximal hash collisions. * * NOTE: The hash algorithms must match tools/dukutil.py:duk_heap_hashstring() * for ROM string support! */ /* #include duk_internal.h -> already included */ #if defined(DUK_USE_STRHASH_DENSE) /* Constants for duk_hashstring(). */ #define DUK__STRHASH_SHORTSTRING 4096L #define DUK__STRHASH_MEDIUMSTRING (256L * 1024L) #define DUK__STRHASH_BLOCKSIZE 256L DUK_INTERNAL duk_uint32_t duk_heap_hashstring(duk_heap *heap, const duk_uint8_t *str, duk_size_t len) { duk_uint32_t hash; /* Use Murmurhash2 directly for short strings, and use "block skipping" * for long strings: hash an initial part and then sample the rest of * the string with reasonably sized chunks. An initial offset for the * sampling is computed based on a hash of the initial part of the string; * this is done to (usually) avoid the case where all long strings have * certain offset ranges which are never sampled. * * Skip should depend on length and bound the total time to roughly * logarithmic. With current values: * * 1M string => 256 * 241 = 61696 bytes (0.06M) of hashing * 1G string => 256 * 16321 = 4178176 bytes (3.98M) of hashing * * XXX: It would be better to compute the skip offset more "smoothly" * instead of having a few boundary values. */ /* note: mixing len into seed improves hashing when skipping */ duk_uint32_t str_seed = heap->hash_seed ^ ((duk_uint32_t) len); if (len <= DUK__STRHASH_SHORTSTRING) { hash = duk_util_hashbytes(str, len, str_seed); } else { duk_size_t off; duk_size_t skip; if (len <= DUK__STRHASH_MEDIUMSTRING) { skip = (duk_size_t) (16 * DUK__STRHASH_BLOCKSIZE + DUK__STRHASH_BLOCKSIZE); } else { skip = (duk_size_t) (256 * DUK__STRHASH_BLOCKSIZE + DUK__STRHASH_BLOCKSIZE); } hash = duk_util_hashbytes(str, (duk_size_t) DUK__STRHASH_SHORTSTRING, str_seed); off = DUK__STRHASH_SHORTSTRING + (skip * (hash % 256)) / 256; /* XXX: inefficient loop */ while (off < len) { duk_size_t left = len - off; duk_size_t now = (duk_size_t) (left > DUK__STRHASH_BLOCKSIZE ? DUK__STRHASH_BLOCKSIZE : left); hash ^= duk_util_hashbytes(str + off, now, str_seed); off += skip; } } #if defined(DUK_USE_STRHASH16) /* Truncate to 16 bits here, so that a computed hash can be compared * against a hash stored in a 16-bit field. */ hash &= 0x0000ffffUL; #endif return hash; } #else /* DUK_USE_STRHASH_DENSE */ DUK_INTERNAL duk_uint32_t duk_heap_hashstring(duk_heap *heap, const duk_uint8_t *str, duk_size_t len) { duk_uint32_t hash; duk_size_t step; duk_size_t off; /* Slightly modified "Bernstein hash" from: * * http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx * * Modifications: string skipping and reverse direction similar to * Lua 5.1.5, and different hash initializer. * * The reverse direction ensures last byte it always included in the * hash which is a good default as changing parts of the string are * more often in the suffix than in the prefix. */ hash = heap->hash_seed ^ ((duk_uint32_t) len); /* Bernstein hash init value is normally 5381 */ step = (len >> DUK_USE_STRHASH_SKIP_SHIFT) + 1; for (off = len; off >= step; off -= step) { DUK_ASSERT(off >= 1); /* off >= step, and step >= 1 */ hash = (hash * 33) + str[off - 1]; } #if defined(DUK_USE_STRHASH16) /* Truncate to 16 bits here, so that a computed hash can be compared * against a hash stored in a 16-bit field. */ hash &= 0x0000ffffUL; #endif return hash; } #endif /* DUK_USE_STRHASH_DENSE */ /* automatic undefs */ #undef DUK__STRHASH_BLOCKSIZE #undef DUK__STRHASH_MEDIUMSTRING #undef DUK__STRHASH_SHORTSTRING #line 1 "duk_heap_markandsweep.c" /* * Mark-and-sweep garbage collection. */ /* #include duk_internal.h -> already included */ DUK_LOCAL_DECL void duk__mark_heaphdr(duk_heap *heap, duk_heaphdr *h); DUK_LOCAL_DECL void duk__mark_heaphdr_nonnull(duk_heap *heap, duk_heaphdr *h); DUK_LOCAL_DECL void duk__mark_tval(duk_heap *heap, duk_tval *tv); DUK_LOCAL_DECL void duk__mark_tvals(duk_heap *heap, duk_tval *tv, duk_idx_t count); /* * Marking functions for heap types: mark children recursively. */ DUK_LOCAL void duk__mark_hstring(duk_heap *heap, duk_hstring *h) { DUK_UNREF(heap); DUK_UNREF(h); DUK_DDD(DUK_DDDPRINT("duk__mark_hstring: %p", (void *) h)); DUK_ASSERT(h); DUK_HSTRING_ASSERT_VALID(h); /* nothing to process */ } DUK_LOCAL void duk__mark_hobject(duk_heap *heap, duk_hobject *h) { duk_uint_fast32_t i; DUK_DDD(DUK_DDDPRINT("duk__mark_hobject: %p", (void *) h)); DUK_ASSERT(h); DUK_HOBJECT_ASSERT_VALID(h); /* XXX: use advancing pointers instead of index macros -> faster and smaller? */ for (i = 0; i < (duk_uint_fast32_t) DUK_HOBJECT_GET_ENEXT(h); i++) { duk_hstring *key = DUK_HOBJECT_E_GET_KEY(heap, h, i); if (key == NULL) { continue; } duk__mark_heaphdr_nonnull(heap, (duk_heaphdr *) key); if (DUK_HOBJECT_E_SLOT_IS_ACCESSOR(heap, h, i)) { duk__mark_heaphdr(heap, (duk_heaphdr *) DUK_HOBJECT_E_GET_VALUE_PTR(heap, h, i)->a.get); duk__mark_heaphdr(heap, (duk_heaphdr *) DUK_HOBJECT_E_GET_VALUE_PTR(heap, h, i)->a.set); } else { duk__mark_tval(heap, &DUK_HOBJECT_E_GET_VALUE_PTR(heap, h, i)->v); } } for (i = 0; i < (duk_uint_fast32_t) DUK_HOBJECT_GET_ASIZE(h); i++) { duk__mark_tval(heap, DUK_HOBJECT_A_GET_VALUE_PTR(heap, h, i)); } /* Hash part is a 'weak reference' and does not contribute. */ duk__mark_heaphdr(heap, (duk_heaphdr *) DUK_HOBJECT_GET_PROTOTYPE(heap, h)); /* Fast path for objects which don't have a subclass struct, or have a * subclass struct but nothing that needs marking in the subclass struct. */ if (DUK_HOBJECT_HAS_FASTREFS(h)) { DUK_ASSERT(DUK_HOBJECT_ALLOWS_FASTREFS(h)); return; } DUK_ASSERT(DUK_HOBJECT_PROHIBITS_FASTREFS(h)); /* XXX: reorg, more common first */ if (DUK_HOBJECT_IS_COMPFUNC(h)) { duk_hcompfunc *f = (duk_hcompfunc *) h; duk_tval *tv, *tv_end; duk_hobject **fn, **fn_end; DUK_HCOMPFUNC_ASSERT_VALID(f); /* 'data' is reachable through every compiled function which * contains a reference. */ duk__mark_heaphdr(heap, (duk_heaphdr *) DUK_HCOMPFUNC_GET_DATA(heap, f)); duk__mark_heaphdr(heap, (duk_heaphdr *) DUK_HCOMPFUNC_GET_LEXENV(heap, f)); duk__mark_heaphdr(heap, (duk_heaphdr *) DUK_HCOMPFUNC_GET_VARENV(heap, f)); if (DUK_HCOMPFUNC_GET_DATA(heap, f) != NULL) { tv = DUK_HCOMPFUNC_GET_CONSTS_BASE(heap, f); tv_end = DUK_HCOMPFUNC_GET_CONSTS_END(heap, f); while (tv < tv_end) { duk__mark_tval(heap, tv); tv++; } fn = DUK_HCOMPFUNC_GET_FUNCS_BASE(heap, f); fn_end = DUK_HCOMPFUNC_GET_FUNCS_END(heap, f); while (fn < fn_end) { duk__mark_heaphdr_nonnull(heap, (duk_heaphdr *) *fn); fn++; } } else { /* May happen in some out-of-memory corner cases. */ DUK_D(DUK_DPRINT("duk_hcompfunc 'data' is NULL, skipping marking")); } } else if (DUK_HOBJECT_IS_DECENV(h)) { duk_hdecenv *e = (duk_hdecenv *) h; DUK_HDECENV_ASSERT_VALID(e); duk__mark_heaphdr(heap, (duk_heaphdr *) e->thread); duk__mark_heaphdr(heap, (duk_heaphdr *) e->varmap); } else if (DUK_HOBJECT_IS_OBJENV(h)) { duk_hobjenv *e = (duk_hobjenv *) h; DUK_HOBJENV_ASSERT_VALID(e); duk__mark_heaphdr_nonnull(heap, (duk_heaphdr *) e->target); #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) } else if (DUK_HOBJECT_IS_BUFOBJ(h)) { duk_hbufobj *b = (duk_hbufobj *) h; DUK_HBUFOBJ_ASSERT_VALID(b); duk__mark_heaphdr(heap, (duk_heaphdr *) b->buf); duk__mark_heaphdr(heap, (duk_heaphdr *) b->buf_prop); #endif /* DUK_USE_BUFFEROBJECT_SUPPORT */ } else if (DUK_HOBJECT_IS_BOUNDFUNC(h)) { duk_hboundfunc *f = (duk_hboundfunc *) (void *) h; DUK_HBOUNDFUNC_ASSERT_VALID(f); duk__mark_tval(heap, &f->target); duk__mark_tval(heap, &f->this_binding); duk__mark_tvals(heap, f->args, f->nargs); #if defined(DUK_USE_ES6_PROXY) } else if (DUK_HOBJECT_IS_PROXY(h)) { duk_hproxy *p = (duk_hproxy *) h; DUK_HPROXY_ASSERT_VALID(p); duk__mark_heaphdr_nonnull(heap, (duk_heaphdr *) p->target); duk__mark_heaphdr_nonnull(heap, (duk_heaphdr *) p->handler); #endif /* DUK_USE_ES6_PROXY */ } else if (DUK_HOBJECT_IS_THREAD(h)) { duk_hthread *t = (duk_hthread *) h; duk_activation *act; duk_tval *tv; DUK_HTHREAD_ASSERT_VALID(t); tv = t->valstack; while (tv < t->valstack_top) { duk__mark_tval(heap, tv); tv++; } for (act = t->callstack_curr; act != NULL; act = act->parent) { duk__mark_heaphdr(heap, (duk_heaphdr *) DUK_ACT_GET_FUNC(act)); duk__mark_heaphdr(heap, (duk_heaphdr *) act->var_env); duk__mark_heaphdr(heap, (duk_heaphdr *) act->lex_env); #if defined(DUK_USE_NONSTD_FUNC_CALLER_PROPERTY) duk__mark_heaphdr(heap, (duk_heaphdr *) act->prev_caller); #endif #if 0 /* nothing now */ for (cat = act->cat; cat != NULL; cat = cat->parent) { } #endif } duk__mark_heaphdr(heap, (duk_heaphdr *) t->resumer); for (i = 0; i < DUK_NUM_BUILTINS; i++) { duk__mark_heaphdr(heap, (duk_heaphdr *) t->builtins[i]); } } else { /* We may come here if the object should have a FASTREFS flag * but it's missing for some reason. Assert for never getting * here; however, other than performance, this is harmless. */ DUK_D(DUK_DPRINT("missing FASTREFS flag for: %!iO", h)); DUK_ASSERT(0); } } /* Mark any duk_heaphdr type. Recursion tracking happens only here. */ DUK_LOCAL void duk__mark_heaphdr(duk_heap *heap, duk_heaphdr *h) { DUK_DDD( DUK_DDDPRINT("duk__mark_heaphdr %p, type %ld", (void *) h, (h != NULL ? (long) DUK_HEAPHDR_GET_TYPE(h) : (long) -1))); /* XXX: add non-null variant? */ if (h == NULL) { return; } DUK_HEAPHDR_ASSERT_VALID(h); DUK_ASSERT(!DUK_HEAPHDR_HAS_READONLY(h) || DUK_HEAPHDR_HAS_REACHABLE(h)); #if defined(DUK_USE_ASSERTIONS) && defined(DUK_USE_REFERENCE_COUNTING) if (!DUK_HEAPHDR_HAS_READONLY(h)) { h->h_assert_refcount++; /* Comparison refcount: bump even if already reachable. */ } #endif if (DUK_HEAPHDR_HAS_REACHABLE(h)) { DUK_DDD(DUK_DDDPRINT("already marked reachable, skip")); return; } #if defined(DUK_USE_ROM_OBJECTS) /* READONLY objects always have REACHABLE set, so the check above * will prevent READONLY objects from being marked here. */ DUK_ASSERT(!DUK_HEAPHDR_HAS_READONLY(h)); #endif DUK_HEAPHDR_SET_REACHABLE(h); if (heap->ms_recursion_depth >= DUK_USE_MARK_AND_SWEEP_RECLIMIT) { DUK_D(DUK_DPRINT("mark-and-sweep recursion limit reached, marking as temproot: %p", (void *) h)); DUK_HEAP_SET_MARKANDSWEEP_RECLIMIT_REACHED(heap); DUK_HEAPHDR_SET_TEMPROOT(h); return; } heap->ms_recursion_depth++; DUK_ASSERT(heap->ms_recursion_depth != 0); /* Wrap. */ switch (DUK_HEAPHDR_GET_TYPE(h)) { case DUK_HTYPE_STRING: duk__mark_hstring(heap, (duk_hstring *) h); break; case DUK_HTYPE_OBJECT: duk__mark_hobject(heap, (duk_hobject *) h); break; case DUK_HTYPE_BUFFER: /* nothing to mark */ break; default: DUK_D(DUK_DPRINT("attempt to mark heaphdr %p with invalid htype %ld", (void *) h, (long) DUK_HEAPHDR_GET_TYPE(h))); DUK_UNREACHABLE(); } DUK_ASSERT(heap->ms_recursion_depth > 0); heap->ms_recursion_depth--; } DUK_LOCAL void duk__mark_tval(duk_heap *heap, duk_tval *tv) { DUK_DDD(DUK_DDDPRINT("duk__mark_tval %p", (void *) tv)); if (tv == NULL) { return; } DUK_TVAL_ASSERT_VALID(tv); if (DUK_TVAL_IS_HEAP_ALLOCATED(tv)) { duk_heaphdr *h; h = DUK_TVAL_GET_HEAPHDR(tv); DUK_ASSERT(h != NULL); duk__mark_heaphdr_nonnull(heap, h); } } DUK_LOCAL void duk__mark_tvals(duk_heap *heap, duk_tval *tv, duk_idx_t count) { DUK_ASSERT(count == 0 || tv != NULL); while (count-- > 0) { DUK_TVAL_ASSERT_VALID(tv); if (DUK_TVAL_IS_HEAP_ALLOCATED(tv)) { duk_heaphdr *h; h = DUK_TVAL_GET_HEAPHDR(tv); DUK_ASSERT(h != NULL); duk__mark_heaphdr_nonnull(heap, h); } tv++; } } /* Mark any duk_heaphdr type, caller guarantees a non-NULL pointer. */ DUK_LOCAL void duk__mark_heaphdr_nonnull(duk_heap *heap, duk_heaphdr *h) { /* For now, just call the generic handler. Change when call sites * are changed too. */ duk__mark_heaphdr(heap, h); } /* * Mark the heap. */ DUK_LOCAL void duk__mark_roots_heap(duk_heap *heap) { duk_small_uint_t i; DUK_DD(DUK_DDPRINT("duk__mark_roots_heap: %p", (void *) heap)); duk__mark_heaphdr(heap, (duk_heaphdr *) heap->heap_thread); duk__mark_heaphdr(heap, (duk_heaphdr *) heap->heap_object); for (i = 0; i < DUK_HEAP_NUM_STRINGS; i++) { duk_hstring *h = DUK_HEAP_GET_STRING(heap, i); duk__mark_heaphdr(heap, (duk_heaphdr *) h); } duk__mark_tval(heap, &heap->lj.value1); duk__mark_tval(heap, &heap->lj.value2); #if defined(DUK_USE_DEBUGGER_SUPPORT) for (i = 0; i < heap->dbg_breakpoint_count; i++) { duk__mark_heaphdr(heap, (duk_heaphdr *) heap->dbg_breakpoints[i].filename); } #endif } /* * Mark unreachable, finalizable objects. * * Such objects will be moved aside and their finalizers run later. They * have to be treated as reachability roots for their properties etc to * remain allocated. This marking is only done for unreachable values which * would be swept later. * * Objects are first marked FINALIZABLE and only then marked as reachability * roots; otherwise circular references might be handled inconsistently. */ #if defined(DUK_USE_FINALIZER_SUPPORT) DUK_LOCAL void duk__mark_finalizable(duk_heap *heap) { duk_heaphdr *hdr; duk_size_t count_finalizable = 0; DUK_DD(DUK_DDPRINT("duk__mark_finalizable: %p", (void *) heap)); DUK_ASSERT(heap->heap_thread != NULL); hdr = heap->heap_allocated; while (hdr != NULL) { /* A finalizer is looked up from the object and up its * prototype chain (which allows inherited finalizers). * The finalizer is checked for using a duk_hobject flag * which is kept in sync with the presence and callability * of a _Finalizer hidden symbol. */ if (!DUK_HEAPHDR_HAS_REACHABLE(hdr) && DUK_HEAPHDR_IS_OBJECT(hdr) && !DUK_HEAPHDR_HAS_FINALIZED(hdr) && DUK_HOBJECT_HAS_FINALIZER_FAST(heap, (duk_hobject *) hdr)) { /* heaphdr: * - is not reachable * - is an object * - is not a finalized object waiting for rescue/keep decision * - has a finalizer */ DUK_DD(DUK_DDPRINT("unreachable heap object will be " "finalized -> mark as finalizable " "and treat as a reachability root: %p", (void *) hdr)); DUK_ASSERT(!DUK_HEAPHDR_HAS_READONLY(hdr)); DUK_HEAPHDR_SET_FINALIZABLE(hdr); count_finalizable++; } hdr = DUK_HEAPHDR_GET_NEXT(heap, hdr); } if (count_finalizable == 0) { return; } DUK_DD(DUK_DDPRINT("marked %ld heap objects as finalizable, now mark them reachable", (long) count_finalizable)); hdr = heap->heap_allocated; while (hdr != NULL) { if (DUK_HEAPHDR_HAS_FINALIZABLE(hdr)) { duk__mark_heaphdr_nonnull(heap, hdr); } hdr = DUK_HEAPHDR_GET_NEXT(heap, hdr); } /* Caller will finish the marking process if we hit a recursion limit. */ } #endif /* DUK_USE_FINALIZER_SUPPORT */ /* * Mark objects on finalize_list. */ #if defined(DUK_USE_FINALIZER_SUPPORT) DUK_LOCAL void duk__mark_finalize_list(duk_heap *heap) { duk_heaphdr *hdr; #if defined(DUK_USE_DEBUG) duk_size_t count_finalize_list = 0; #endif DUK_DD(DUK_DDPRINT("duk__mark_finalize_list: %p", (void *) heap)); hdr = heap->finalize_list; while (hdr != NULL) { duk__mark_heaphdr_nonnull(heap, hdr); hdr = DUK_HEAPHDR_GET_NEXT(heap, hdr); #if defined(DUK_USE_DEBUG) count_finalize_list++; #endif } #if defined(DUK_USE_DEBUG) if (count_finalize_list > 0) { DUK_D(DUK_DPRINT("marked %ld objects on the finalize_list as reachable (previous finalizer run skipped)", (long) count_finalize_list)); } #endif } #endif /* DUK_USE_FINALIZER_SUPPORT */ /* * Fallback marking handler if recursion limit is reached. * * Iterates 'temproots' until recursion limit is no longer hit. Temproots * can be in heap_allocated or finalize_list; refzero_list is now always * empty for mark-and-sweep. A temproot may occur in finalize_list now if * there are objects on the finalize_list and user code creates a reference * from an object in heap_allocated to the object in finalize_list (which is * now allowed), and it happened to coincide with the recursion depth limit. * * This is a slow scan, but guarantees that we finish with a bounded C stack. * * Note that nodes may have been marked as temproots before this scan begun, * OR they may have been marked during the scan (as we process nodes * recursively also during the scan). This is intended behavior. */ #if defined(DUK_USE_DEBUG) DUK_LOCAL void duk__handle_temproot(duk_heap *heap, duk_heaphdr *hdr, duk_size_t *count) { #else DUK_LOCAL void duk__handle_temproot(duk_heap *heap, duk_heaphdr *hdr) { #endif DUK_ASSERT(hdr != NULL); if (!DUK_HEAPHDR_HAS_TEMPROOT(hdr)) { DUK_DDD(DUK_DDDPRINT("not a temp root: %p", (void *) hdr)); return; } DUK_DDD(DUK_DDDPRINT("found a temp root: %p", (void *) hdr)); DUK_HEAPHDR_CLEAR_TEMPROOT(hdr); DUK_HEAPHDR_CLEAR_REACHABLE(hdr); /* Done so that duk__mark_heaphdr() works correctly. */ #if defined(DUK_USE_ASSERTIONS) && defined(DUK_USE_REFERENCE_COUNTING) hdr->h_assert_refcount--; /* Same node visited twice. */ #endif duk__mark_heaphdr_nonnull(heap, hdr); #if defined(DUK_USE_DEBUG) (*count)++; #endif } DUK_LOCAL void duk__mark_temproots_by_heap_scan(duk_heap *heap) { duk_heaphdr *hdr; #if defined(DUK_USE_DEBUG) duk_size_t count; #endif DUK_DD(DUK_DDPRINT("duk__mark_temproots_by_heap_scan: %p", (void *) heap)); while (DUK_HEAP_HAS_MARKANDSWEEP_RECLIMIT_REACHED(heap)) { DUK_DD(DUK_DDPRINT("recursion limit reached, doing heap scan to continue from temproots")); #if defined(DUK_USE_DEBUG) count = 0; #endif DUK_HEAP_CLEAR_MARKANDSWEEP_RECLIMIT_REACHED(heap); hdr = heap->heap_allocated; while (hdr) { #if defined(DUK_USE_DEBUG) duk__handle_temproot(heap, hdr, &count); #else duk__handle_temproot(heap, hdr); #endif hdr = DUK_HEAPHDR_GET_NEXT(heap, hdr); } #if defined(DUK_USE_FINALIZER_SUPPORT) hdr = heap->finalize_list; while (hdr) { #if defined(DUK_USE_DEBUG) duk__handle_temproot(heap, hdr, &count); #else duk__handle_temproot(heap, hdr); #endif hdr = DUK_HEAPHDR_GET_NEXT(heap, hdr); } #endif #if defined(DUK_USE_DEBUG) DUK_DD(DUK_DDPRINT("temproot mark heap scan processed %ld temp roots", (long) count)); #endif } } /* * Finalize refcounts for heap elements just about to be freed. * This must be done for all objects before freeing to avoid any * stale pointer dereferences. * * Note that this must deduce the set of objects to be freed * identically to duk__sweep_heap(). */ #if defined(DUK_USE_REFERENCE_COUNTING) DUK_LOCAL void duk__finalize_refcounts(duk_heap *heap) { duk_heaphdr *hdr; DUK_ASSERT(heap->heap_thread != NULL); DUK_DD(DUK_DDPRINT("duk__finalize_refcounts: heap=%p", (void *) heap)); hdr = heap->heap_allocated; while (hdr) { if (!DUK_HEAPHDR_HAS_REACHABLE(hdr)) { /* * Unreachable object about to be swept. Finalize target refcounts * (objects which the unreachable object points to) without doing * refzero processing. Recursive decrefs are also prevented when * refzero processing is disabled. * * Value cannot be a finalizable object, as they have been made * temporarily reachable for this round. */ DUK_DDD(DUK_DDDPRINT("unreachable object, refcount finalize before sweeping: %p", (void *) hdr)); /* Finalize using heap->heap_thread; DECREF has a * suppress check for mark-and-sweep which is based * on heap->ms_running. */ duk_heaphdr_refcount_finalize_norz(heap, hdr); } hdr = DUK_HEAPHDR_GET_NEXT(heap, hdr); } } #endif /* DUK_USE_REFERENCE_COUNTING */ /* * Clear (reachable) flags of finalize_list. * * We could mostly do in the sweep phase when we move objects from the * heap into the finalize_list. However, if a finalizer run is skipped * during a mark-and-sweep, the objects on the finalize_list will be marked * reachable during the next mark-and-sweep. Since they're already on the * finalize_list, no-one will be clearing their REACHABLE flag so we do it * here. (This now overlaps with the sweep handling in a harmless way.) */ #if defined(DUK_USE_FINALIZER_SUPPORT) DUK_LOCAL void duk__clear_finalize_list_flags(duk_heap *heap) { duk_heaphdr *hdr; DUK_DD(DUK_DDPRINT("duk__clear_finalize_list_flags: %p", (void *) heap)); hdr = heap->finalize_list; while (hdr) { DUK_HEAPHDR_CLEAR_REACHABLE(hdr); #if defined(DUK_USE_ASSERTIONS) DUK_ASSERT(DUK_HEAPHDR_HAS_FINALIZABLE(hdr) || (heap->currently_finalizing == hdr)); #endif /* DUK_HEAPHDR_FLAG_FINALIZED may be set. */ DUK_ASSERT(!DUK_HEAPHDR_HAS_TEMPROOT(hdr)); hdr = DUK_HEAPHDR_GET_NEXT(heap, hdr); } } #endif /* DUK_USE_FINALIZER_SUPPORT */ /* * Sweep stringtable. */ DUK_LOCAL void duk__sweep_stringtable(duk_heap *heap, duk_size_t *out_count_keep) { duk_hstring *h; duk_hstring *prev; duk_uint32_t i; #if defined(DUK_USE_DEBUG) duk_size_t count_free = 0; #endif duk_size_t count_keep = 0; DUK_DD(DUK_DDPRINT("duk__sweep_stringtable: %p", (void *) heap)); #if defined(DUK_USE_STRTAB_PTRCOMP) if (heap->strtable16 == NULL) { #else if (heap->strtable == NULL) { #endif goto done; } for (i = 0; i < heap->st_size; i++) { #if defined(DUK_USE_STRTAB_PTRCOMP) h = DUK_USE_HEAPPTR_DEC16(heap->heap_udata, heap->strtable16[i]); #else h = heap->strtable[i]; #endif prev = NULL; while (h != NULL) { duk_hstring *next; next = h->hdr.h_next; if (DUK_HEAPHDR_HAS_REACHABLE((duk_heaphdr *) h)) { DUK_HEAPHDR_CLEAR_REACHABLE((duk_heaphdr *) h); count_keep++; prev = h; } else { #if defined(DUK_USE_DEBUG) count_free++; #endif /* For pinned strings the refcount has been * bumped. We could unbump it here before * freeing, but that's actually not necessary * except for assertions. */ #if 0 if (DUK_HSTRING_HAS_PINNED_LITERAL(h)) { DUK_ASSERT(DUK_HEAPHDR_GET_REFCOUNT((duk_heaphdr *) h) > 0U); DUK_HSTRING_DECREF_NORZ(heap->heap_thread, h); DUK_HSTRING_CLEAR_PINNED_LITERAL(h); } #endif #if defined(DUK_USE_REFERENCE_COUNTING) /* Non-zero refcounts should not happen for unreachable strings, * because we refcount finalize all unreachable objects which * should have decreased unreachable string refcounts to zero * (even for cycles). However, pinned strings have a +1 bump. */ DUK_ASSERT(DUK_HEAPHDR_GET_REFCOUNT((duk_heaphdr *) h) == DUK_HSTRING_HAS_PINNED_LITERAL(h) ? 1U : 0U); #endif /* Deal with weak references first. */ duk_heap_strcache_string_remove(heap, (duk_hstring *) h); /* Remove the string from the string table. */ duk_heap_strtable_unlink_prev(heap, (duk_hstring *) h, (duk_hstring *) prev); /* Free inner references (these exist e.g. when external * strings are enabled) and the struct itself. */ duk_free_hstring(heap, (duk_hstring *) h); /* Don't update 'prev'; it should be last string kept. */ } h = next; } } done: #if defined(DUK_USE_DEBUG) DUK_D(DUK_DPRINT("mark-and-sweep sweep stringtable: %ld freed, %ld kept", (long) count_free, (long) count_keep)); #endif *out_count_keep = count_keep; } /* * Sweep heap. */ DUK_LOCAL void duk__sweep_heap(duk_heap *heap, duk_small_uint_t flags, duk_size_t *out_count_keep) { duk_heaphdr *prev; /* last element that was left in the heap */ duk_heaphdr *curr; duk_heaphdr *next; #if defined(DUK_USE_DEBUG) duk_size_t count_free = 0; duk_size_t count_finalize = 0; duk_size_t count_rescue = 0; #endif duk_size_t count_keep = 0; DUK_DD(DUK_DDPRINT("duk__sweep_heap: %p", (void *) heap)); prev = NULL; curr = heap->heap_allocated; heap->heap_allocated = NULL; while (curr) { /* Strings and ROM objects are never placed on the heap allocated list. */ DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(curr) != DUK_HTYPE_STRING); DUK_ASSERT(!DUK_HEAPHDR_HAS_READONLY(curr)); next = DUK_HEAPHDR_GET_NEXT(heap, curr); if (DUK_HEAPHDR_HAS_REACHABLE(curr)) { /* * Reachable object: * - If FINALIZABLE -> actually unreachable (but marked * artificially reachable), queue to finalize_list. * - If !FINALIZABLE but FINALIZED -> rescued after * finalizer execution. * - Otherwise just a normal, reachable object. * * Objects which are kept are queued to heap_allocated * tail (we're essentially filtering heap_allocated in * practice). */ #if defined(DUK_USE_FINALIZER_SUPPORT) if (DUK_UNLIKELY(DUK_HEAPHDR_HAS_FINALIZABLE(curr))) { DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZED(curr)); DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(curr) == DUK_HTYPE_OBJECT); DUK_DD(DUK_DDPRINT("sweep; reachable, finalizable --> move to finalize_list: %p", (void *) curr)); #if defined(DUK_USE_REFERENCE_COUNTING) DUK_HEAPHDR_PREINC_REFCOUNT( curr); /* Bump refcount so that refzero never occurs when pending a finalizer call. */ #endif DUK_HEAP_INSERT_INTO_FINALIZE_LIST(heap, curr); #if defined(DUK_USE_DEBUG) count_finalize++; #endif } else #endif /* DUK_USE_FINALIZER_SUPPORT */ { if (DUK_UNLIKELY(DUK_HEAPHDR_HAS_FINALIZED(curr))) { DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZABLE(curr)); DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(curr) == DUK_HTYPE_OBJECT); if (flags & DUK_MS_FLAG_POSTPONE_RESCUE) { DUK_DD(DUK_DDPRINT("sweep; reachable, finalized, but postponing rescue decisions " "--> keep object (with FINALIZED set): %!iO", curr)); count_keep++; } else { DUK_DD(DUK_DDPRINT("sweep; reachable, finalized --> rescued after finalization: %p", (void *) curr)); #if defined(DUK_USE_FINALIZER_SUPPORT) DUK_HEAPHDR_CLEAR_FINALIZED(curr); #endif #if defined(DUK_USE_DEBUG) count_rescue++; #endif } } else { DUK_DD(DUK_DDPRINT("sweep; reachable --> keep: %!iO", curr)); count_keep++; } if (prev != NULL) { DUK_ASSERT(heap->heap_allocated != NULL); DUK_HEAPHDR_SET_NEXT(heap, prev, curr); } else { DUK_ASSERT(heap->heap_allocated == NULL); heap->heap_allocated = curr; } #if defined(DUK_USE_DOUBLE_LINKED_HEAP) DUK_HEAPHDR_SET_PREV(heap, curr, prev); #endif DUK_HEAPHDR_ASSERT_LINKS(heap, prev); DUK_HEAPHDR_ASSERT_LINKS(heap, curr); prev = curr; } /* * Shrink check for value stacks here. We're inside * ms_prevent_count protection which prevents recursive * mark-and-sweep and refzero finalizers, so there are * no side effects that would affect the heap lists. */ if (DUK_HEAPHDR_IS_OBJECT(curr) && DUK_HOBJECT_IS_THREAD((duk_hobject *) curr)) { duk_hthread *thr_curr = (duk_hthread *) curr; DUK_DD(DUK_DDPRINT("value stack shrink check for thread: %!O", curr)); duk_valstack_shrink_check_nothrow(thr_curr, flags & DUK_MS_FLAG_EMERGENCY /*snug*/); } DUK_HEAPHDR_CLEAR_REACHABLE(curr); /* Keep FINALIZED if set, used if rescue decisions are postponed. */ /* Keep FINALIZABLE for objects on finalize_list. */ DUK_ASSERT(!DUK_HEAPHDR_HAS_REACHABLE(curr)); } else { /* * Unreachable object: * - If FINALIZED, object was finalized but not * rescued. This doesn't affect freeing. * - Otherwise normal unreachable object. * * There's no guard preventing a FINALIZED object * from being freed while finalizers execute: the * artificial finalize_list reachability roots can't * cause an incorrect free decision (but can cause * an incorrect rescue decision). */ #if defined(DUK_USE_REFERENCE_COUNTING) /* Non-zero refcounts should not happen because we refcount * finalize all unreachable objects which should cancel out * refcounts (even for cycles). */ DUK_ASSERT(DUK_HEAPHDR_GET_REFCOUNT(curr) == 0); #endif DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZABLE(curr)); #if defined(DUK_USE_DEBUG) if (DUK_HEAPHDR_HAS_FINALIZED(curr)) { DUK_DD(DUK_DDPRINT("sweep; unreachable, finalized --> finalized object not rescued: %p", (void *) curr)); } else { DUK_DD(DUK_DDPRINT("sweep; not reachable --> free: %p", (void *) curr)); } #endif /* Note: object cannot be a finalizable unreachable object, as * they have been marked temporarily reachable for this round, * and are handled above. */ #if defined(DUK_USE_DEBUG) count_free++; #endif /* Weak refs should be handled here, but no weak refs for * any non-string objects exist right now. */ /* Free object and all auxiliary (non-heap) allocs. */ duk_heap_free_heaphdr_raw(heap, curr); } curr = next; } if (prev != NULL) { DUK_HEAPHDR_SET_NEXT(heap, prev, NULL); } DUK_HEAPHDR_ASSERT_LINKS(heap, prev); #if defined(DUK_USE_DEBUG) DUK_D(DUK_DPRINT("mark-and-sweep sweep objects (non-string): %ld freed, %ld kept, %ld rescued, %ld queued for finalization", (long) count_free, (long) count_keep, (long) count_rescue, (long) count_finalize)); #endif *out_count_keep = count_keep; } /* * Litcache helpers. */ #if defined(DUK_USE_LITCACHE_SIZE) DUK_LOCAL void duk__wipe_litcache(duk_heap *heap) { duk_uint_t i; duk_litcache_entry *e; e = heap->litcache; for (i = 0; i < DUK_USE_LITCACHE_SIZE; i++) { e->addr = NULL; /* e->h does not need to be invalidated: when e->addr is * NULL, e->h is considered garbage. */ e++; } } #endif /* DUK_USE_LITCACHE_SIZE */ /* * Object compaction. * * Compaction is assumed to never throw an error. */ DUK_LOCAL int duk__protected_compact_object(duk_hthread *thr, void *udata) { duk_hobject *obj; /* XXX: for threads, compact stacks? */ DUK_UNREF(udata); obj = duk_known_hobject(thr, -1); duk_hobject_compact_props(thr, obj); return 0; } #if defined(DUK_USE_DEBUG) DUK_LOCAL void duk__compact_object_list(duk_heap *heap, duk_hthread *thr, duk_heaphdr *start, duk_size_t *p_count_check, duk_size_t *p_count_compact, duk_size_t *p_count_bytes_saved) { #else DUK_LOCAL void duk__compact_object_list(duk_heap *heap, duk_hthread *thr, duk_heaphdr *start) { #endif duk_heaphdr *curr; #if defined(DUK_USE_DEBUG) duk_size_t old_size, new_size; #endif duk_hobject *obj; DUK_UNREF(heap); curr = start; while (curr) { DUK_DDD(DUK_DDDPRINT("mark-and-sweep compact: %p", (void *) curr)); if (DUK_HEAPHDR_GET_TYPE(curr) != DUK_HTYPE_OBJECT) { goto next; } obj = (duk_hobject *) curr; #if defined(DUK_USE_DEBUG) old_size = DUK_HOBJECT_P_COMPUTE_SIZE(DUK_HOBJECT_GET_ESIZE(obj), DUK_HOBJECT_GET_ASIZE(obj), DUK_HOBJECT_GET_HSIZE(obj)); #endif DUK_DD(DUK_DDPRINT("compact object: %p", (void *) obj)); duk_push_hobject(thr, obj); /* XXX: disable error handlers for duration of compaction? */ duk_safe_call(thr, duk__protected_compact_object, NULL, 1, 0); #if defined(DUK_USE_DEBUG) new_size = DUK_HOBJECT_P_COMPUTE_SIZE(DUK_HOBJECT_GET_ESIZE(obj), DUK_HOBJECT_GET_ASIZE(obj), DUK_HOBJECT_GET_HSIZE(obj)); #endif #if defined(DUK_USE_DEBUG) (*p_count_compact)++; (*p_count_bytes_saved) += (duk_size_t) (old_size - new_size); #endif next: curr = DUK_HEAPHDR_GET_NEXT(heap, curr); #if defined(DUK_USE_DEBUG) (*p_count_check)++; #endif } } DUK_LOCAL void duk__compact_objects(duk_heap *heap) { /* XXX: which lists should participate? to be finalized? */ #if defined(DUK_USE_DEBUG) duk_size_t count_check = 0; duk_size_t count_compact = 0; duk_size_t count_bytes_saved = 0; #endif DUK_DD(DUK_DDPRINT("duk__compact_objects: %p", (void *) heap)); DUK_ASSERT(heap->heap_thread != NULL); #if defined(DUK_USE_DEBUG) duk__compact_object_list(heap, heap->heap_thread, heap->heap_allocated, &count_check, &count_compact, &count_bytes_saved); #if defined(DUK_USE_FINALIZER_SUPPORT) duk__compact_object_list(heap, heap->heap_thread, heap->finalize_list, &count_check, &count_compact, &count_bytes_saved); #endif #else duk__compact_object_list(heap, heap->heap_thread, heap->heap_allocated); #if defined(DUK_USE_FINALIZER_SUPPORT) duk__compact_object_list(heap, heap->heap_thread, heap->finalize_list); #endif #endif #if defined(DUK_USE_REFERENCE_COUNTING) DUK_ASSERT(heap->refzero_list == NULL); /* Always handled to completion inline in DECREF. */ #endif #if defined(DUK_USE_DEBUG) DUK_D(DUK_DPRINT("mark-and-sweep compact objects: %ld checked, %ld compaction attempts, %ld bytes saved by compaction", (long) count_check, (long) count_compact, (long) count_bytes_saved)); #endif } /* * Assertion helpers. */ #if defined(DUK_USE_ASSERTIONS) typedef void (*duk__gc_heaphdr_assert)(duk_heap *heap, duk_heaphdr *h); typedef void (*duk__gc_hstring_assert)(duk_heap *heap, duk_hstring *h); DUK_LOCAL void duk__assert_walk_list(duk_heap *heap, duk_heaphdr *start, duk__gc_heaphdr_assert func) { duk_heaphdr *curr; for (curr = start; curr != NULL; curr = DUK_HEAPHDR_GET_NEXT(heap, curr)) { func(heap, curr); } } DUK_LOCAL void duk__assert_walk_strtable(duk_heap *heap, duk__gc_hstring_assert func) { duk_uint32_t i; for (i = 0; i < heap->st_size; i++) { duk_hstring *h; #if defined(DUK_USE_STRTAB_PTRCOMP) h = DUK_USE_HEAPPTR_DEC16(heap->heap_udata, heap->strtable16[i]); #else h = heap->strtable[i]; #endif while (h != NULL) { func(heap, h); h = h->hdr.h_next; } } } DUK_LOCAL void duk__assert_heaphdr_flags_cb(duk_heap *heap, duk_heaphdr *h) { DUK_UNREF(heap); DUK_ASSERT(!DUK_HEAPHDR_HAS_REACHABLE(h)); DUK_ASSERT(!DUK_HEAPHDR_HAS_TEMPROOT(h)); DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZABLE(h)); /* may have FINALIZED */ } DUK_LOCAL void duk__assert_heaphdr_flags(duk_heap *heap) { duk__assert_walk_list(heap, heap->heap_allocated, duk__assert_heaphdr_flags_cb); #if defined(DUK_USE_REFERENCE_COUNTING) DUK_ASSERT(heap->refzero_list == NULL); /* Always handled to completion inline in DECREF. */ #endif /* XXX: Assertions for finalize_list? */ } DUK_LOCAL void duk__assert_validity_cb1(duk_heap *heap, duk_heaphdr *h) { DUK_UNREF(heap); DUK_ASSERT(DUK_HEAPHDR_IS_OBJECT(h) || DUK_HEAPHDR_IS_BUFFER(h)); duk_heaphdr_assert_valid_subclassed(h); } DUK_LOCAL void duk__assert_validity_cb2(duk_heap *heap, duk_hstring *h) { DUK_UNREF(heap); DUK_ASSERT(DUK_HEAPHDR_IS_STRING((duk_heaphdr *) h)); duk_heaphdr_assert_valid_subclassed((duk_heaphdr *) h); } DUK_LOCAL void duk__assert_validity(duk_heap *heap) { duk__assert_walk_list(heap, heap->heap_allocated, duk__assert_validity_cb1); #if defined(DUK_USE_FINALIZER_SUPPORT) duk__assert_walk_list(heap, heap->finalize_list, duk__assert_validity_cb1); #endif #if defined(DUK_USE_REFERENCE_COUNTING) duk__assert_walk_list(heap, heap->refzero_list, duk__assert_validity_cb1); #endif duk__assert_walk_strtable(heap, duk__assert_validity_cb2); } #if defined(DUK_USE_REFERENCE_COUNTING) DUK_LOCAL void duk__assert_valid_refcounts_cb(duk_heap *heap, duk_heaphdr *h) { /* Cannot really assert much w.r.t. refcounts now. */ DUK_UNREF(heap); if (DUK_HEAPHDR_GET_REFCOUNT(h) == 0 && DUK_HEAPHDR_HAS_FINALIZED(h)) { /* An object may be in heap_allocated list with a zero * refcount if it has just been finalized and is waiting * to be collected by the next cycle. * (This doesn't currently happen however.) */ } else if (DUK_HEAPHDR_GET_REFCOUNT(h) == 0) { /* An object may be in heap_allocated list with a zero * refcount also if it is a temporary object created * during debugger paused state. It will get collected * by mark-and-sweep based on its reachability status * (presumably not reachable because refcount is 0). */ } DUK_ASSERT_DISABLE(DUK_HEAPHDR_GET_REFCOUNT(h) >= 0); /* Unsigned. */ } DUK_LOCAL void duk__assert_valid_refcounts(duk_heap *heap) { duk__assert_walk_list(heap, heap->heap_allocated, duk__assert_valid_refcounts_cb); } DUK_LOCAL void duk__clear_assert_refcounts_cb1(duk_heap *heap, duk_heaphdr *h) { DUK_UNREF(heap); h->h_assert_refcount = 0; } DUK_LOCAL void duk__clear_assert_refcounts_cb2(duk_heap *heap, duk_hstring *h) { DUK_UNREF(heap); ((duk_heaphdr *) h)->h_assert_refcount = 0; } DUK_LOCAL void duk__clear_assert_refcounts(duk_heap *heap) { duk__assert_walk_list(heap, heap->heap_allocated, duk__clear_assert_refcounts_cb1); #if defined(DUK_USE_FINALIZER_SUPPORT) duk__assert_walk_list(heap, heap->finalize_list, duk__clear_assert_refcounts_cb1); #endif #if defined(DUK_USE_REFERENCE_COUNTING) duk__assert_walk_list(heap, heap->refzero_list, duk__clear_assert_refcounts_cb1); #endif duk__assert_walk_strtable(heap, duk__clear_assert_refcounts_cb2); } DUK_LOCAL void duk__check_refcount_heaphdr(duk_heaphdr *hdr) { duk_bool_t count_ok; duk_size_t expect_refc; /* The refcount check only makes sense for reachable objects on * heap_allocated or string table, after the sweep phase. Prior to * sweep phase refcounts will include references that are not visible * via reachability roots. * * Because we're called after the sweep phase, all heap objects on * heap_allocated are reachable. REACHABLE flags have already been * cleared so we can't check them. */ /* ROM objects have intentionally incorrect refcount (1), but we won't * check them. */ DUK_ASSERT(!DUK_HEAPHDR_HAS_READONLY(hdr)); expect_refc = hdr->h_assert_refcount; if (DUK_HEAPHDR_IS_STRING(hdr) && DUK_HSTRING_HAS_PINNED_LITERAL((duk_hstring *) hdr)) { expect_refc++; } count_ok = ((duk_size_t) DUK_HEAPHDR_GET_REFCOUNT(hdr) == expect_refc); if (!count_ok) { DUK_D(DUK_DPRINT("refcount mismatch for: %p: header=%ld counted=%ld --> %!iO", (void *) hdr, (long) DUK_HEAPHDR_GET_REFCOUNT(hdr), (long) hdr->h_assert_refcount, hdr)); DUK_ASSERT(0); } } DUK_LOCAL void duk__check_assert_refcounts_cb1(duk_heap *heap, duk_heaphdr *h) { DUK_UNREF(heap); duk__check_refcount_heaphdr(h); } DUK_LOCAL void duk__check_assert_refcounts_cb2(duk_heap *heap, duk_hstring *h) { DUK_UNREF(heap); duk__check_refcount_heaphdr((duk_heaphdr *) h); } DUK_LOCAL void duk__check_assert_refcounts(duk_heap *heap) { duk__assert_walk_list(heap, heap->heap_allocated, duk__check_assert_refcounts_cb1); #if defined(DUK_USE_FINALIZER_SUPPORT) duk__assert_walk_list(heap, heap->finalize_list, duk__check_assert_refcounts_cb1); #endif /* XXX: Assert anything for refzero_list? */ duk__assert_walk_strtable(heap, duk__check_assert_refcounts_cb2); } #endif /* DUK_USE_REFERENCE_COUNTING */ #if defined(DUK_USE_LITCACHE_SIZE) DUK_LOCAL void duk__assert_litcache_nulls(duk_heap *heap) { duk_uint_t i; duk_litcache_entry *e; e = heap->litcache; for (i = 0; i < DUK_USE_LITCACHE_SIZE; i++) { /* Entry addresses were NULLed before mark-and-sweep, check * that they're still NULL afterwards to ensure no pointers * were recorded through any side effects. */ DUK_ASSERT(e->addr == NULL); } } #endif /* DUK_USE_LITCACHE_SIZE */ #endif /* DUK_USE_ASSERTIONS */ /* * Stats dump. */ #if defined(DUK_USE_DEBUG) DUK_LOCAL void duk__dump_stats(duk_heap *heap) { DUK_D(DUK_DPRINT("stats executor: opcodes=%ld, interrupt=%ld, throw=%ld", (long) heap->stats_exec_opcodes, (long) heap->stats_exec_interrupt, (long) heap->stats_exec_throw)); DUK_D(DUK_DPRINT("stats call: all=%ld, tailcall=%ld, ecmatoecma=%ld", (long) heap->stats_call_all, (long) heap->stats_call_tailcall, (long) heap->stats_call_ecmatoecma)); DUK_D(DUK_DPRINT("stats safecall: all=%ld, nothrow=%ld, throw=%ld", (long) heap->stats_safecall_all, (long) heap->stats_safecall_nothrow, (long) heap->stats_safecall_throw)); DUK_D(DUK_DPRINT("stats mark-and-sweep: try_count=%ld, skip_count=%ld, emergency_count=%ld", (long) heap->stats_ms_try_count, (long) heap->stats_ms_skip_count, (long) heap->stats_ms_emergency_count)); DUK_D(DUK_DPRINT("stats stringtable: intern_hit=%ld, intern_miss=%ld, " "resize_check=%ld, resize_grow=%ld, resize_shrink=%ld, " "litcache_hit=%ld, litcache_miss=%ld, litcache_pin=%ld", (long) heap->stats_strtab_intern_hit, (long) heap->stats_strtab_intern_miss, (long) heap->stats_strtab_resize_check, (long) heap->stats_strtab_resize_grow, (long) heap->stats_strtab_resize_shrink, (long) heap->stats_strtab_litcache_hit, (long) heap->stats_strtab_litcache_miss, (long) heap->stats_strtab_litcache_pin)); DUK_D(DUK_DPRINT("stats object: realloc_props=%ld, abandon_array=%ld", (long) heap->stats_object_realloc_props, (long) heap->stats_object_abandon_array)); DUK_D(DUK_DPRINT("stats getownpropdesc: count=%ld, hit=%ld, miss=%ld", (long) heap->stats_getownpropdesc_count, (long) heap->stats_getownpropdesc_hit, (long) heap->stats_getownpropdesc_miss)); DUK_D(DUK_DPRINT("stats getpropdesc: count=%ld, hit=%ld, miss=%ld", (long) heap->stats_getpropdesc_count, (long) heap->stats_getpropdesc_hit, (long) heap->stats_getpropdesc_miss)); DUK_D(DUK_DPRINT("stats getprop: all=%ld, arrayidx=%ld, bufobjidx=%ld, " "bufferidx=%ld, bufferlen=%ld, stringidx=%ld, stringlen=%ld, " "proxy=%ld, arguments=%ld", (long) heap->stats_getprop_all, (long) heap->stats_getprop_arrayidx, (long) heap->stats_getprop_bufobjidx, (long) heap->stats_getprop_bufferidx, (long) heap->stats_getprop_bufferlen, (long) heap->stats_getprop_stringidx, (long) heap->stats_getprop_stringlen, (long) heap->stats_getprop_proxy, (long) heap->stats_getprop_arguments)); DUK_D(DUK_DPRINT("stats putprop: all=%ld, arrayidx=%ld, bufobjidx=%ld, " "bufferidx=%ld, proxy=%ld", (long) heap->stats_putprop_all, (long) heap->stats_putprop_arrayidx, (long) heap->stats_putprop_bufobjidx, (long) heap->stats_putprop_bufferidx, (long) heap->stats_putprop_proxy)); DUK_D(DUK_DPRINT("stats getvar: all=%ld", (long) heap->stats_getvar_all)); DUK_D(DUK_DPRINT("stats putvar: all=%ld", (long) heap->stats_putvar_all)); DUK_D(DUK_DPRINT("stats envrec: delayedcreate=%ld, create=%ld, newenv=%ld, oldenv=%ld, pushclosure=%ld", (long) heap->stats_envrec_delayedcreate, (long) heap->stats_envrec_create, (long) heap->stats_envrec_newenv, (long) heap->stats_envrec_oldenv, (long) heap->stats_envrec_pushclosure)); } #endif /* DUK_USE_DEBUG */ /* * Main mark-and-sweep function. * * 'flags' represents the features requested by the caller. The current * heap->ms_base_flags is ORed automatically into the flags; the base flags * mask typically prevents certain mark-and-sweep operation to avoid trouble. */ DUK_INTERNAL void duk_heap_mark_and_sweep(duk_heap *heap, duk_small_uint_t flags) { duk_size_t count_keep_obj; duk_size_t count_keep_str; #if defined(DUK_USE_VOLUNTARY_GC) duk_size_t tmp; #endif duk_bool_t entry_creating_error; DUK_STATS_INC(heap, stats_ms_try_count); #if defined(DUK_USE_DEBUG) if (flags & DUK_MS_FLAG_EMERGENCY) { DUK_STATS_INC(heap, stats_ms_emergency_count); } #endif /* If debugger is paused, garbage collection is disabled by default. * This is achieved by bumping ms_prevent_count when becoming paused. */ DUK_ASSERT(!DUK_HEAP_HAS_DEBUGGER_PAUSED(heap) || heap->ms_prevent_count > 0); /* Prevention/recursion check as soon as possible because we may * be called a number of times when voluntary mark-and-sweep is * pending. */ if (heap->ms_prevent_count != 0) { DUK_DD(DUK_DDPRINT("reject recursive mark-and-sweep")); DUK_STATS_INC(heap, stats_ms_skip_count); return; } DUK_ASSERT(heap->ms_running == 0); /* ms_prevent_count is bumped when ms_running is set */ /* Heap_thread is used during mark-and-sweep for refcount finalization * (it's also used for finalizer execution once mark-and-sweep is * complete). Heap allocation code ensures heap_thread is set and * properly initialized before setting ms_prevent_count to 0. */ DUK_ASSERT(heap->heap_thread != NULL); DUK_ASSERT(heap->heap_thread->valstack != NULL); DUK_D(DUK_DPRINT("garbage collect (mark-and-sweep) starting, requested flags: 0x%08lx, effective flags: 0x%08lx", (unsigned long) flags, (unsigned long) (flags | heap->ms_base_flags))); flags |= heap->ms_base_flags; #if defined(DUK_USE_FINALIZER_SUPPORT) if (heap->finalize_list != NULL) { flags |= DUK_MS_FLAG_POSTPONE_RESCUE; } #endif /* * Assertions before */ #if defined(DUK_USE_ASSERTIONS) DUK_ASSERT(heap->ms_prevent_count == 0); DUK_ASSERT(heap->ms_running == 0); DUK_ASSERT(!DUK_HEAP_HAS_DEBUGGER_PAUSED(heap)); DUK_ASSERT(!DUK_HEAP_HAS_MARKANDSWEEP_RECLIMIT_REACHED(heap)); DUK_ASSERT(heap->ms_recursion_depth == 0); duk__assert_heaphdr_flags(heap); duk__assert_validity(heap); #if defined(DUK_USE_REFERENCE_COUNTING) /* Note: heap->refzero_free_running may be true; a refcount * finalizer may trigger a mark-and-sweep. */ duk__assert_valid_refcounts(heap); #endif /* DUK_USE_REFERENCE_COUNTING */ #endif /* DUK_USE_ASSERTIONS */ /* * Begin */ DUK_ASSERT(heap->ms_prevent_count == 0); DUK_ASSERT(heap->ms_running == 0); heap->ms_prevent_count = 1; heap->ms_running = 1; entry_creating_error = heap->creating_error; heap->creating_error = 0; /* * Free activation/catcher freelists on every mark-and-sweep for now. * This is an initial rough draft; ideally we'd keep count of the * freelist size and free only excess entries. */ DUK_D(DUK_DPRINT("freeing temporary freelists")); duk_heap_free_freelists(heap); /* * Mark roots, hoping that recursion limit is not normally hit. * If recursion limit is hit, run additional reachability rounds * starting from "temproots" until marking is complete. * * Marking happens in two phases: first we mark actual reachability * roots (and run "temproots" to complete the process). Then we * check which objects are unreachable and are finalizable; such * objects are marked as FINALIZABLE and marked as reachability * (and "temproots" is run again to complete the process). * * The heap finalize_list must also be marked as a reachability root. * There may be objects on the list from a previous round if the * previous run had finalizer skip flag. */ #if defined(DUK_USE_ASSERTIONS) && defined(DUK_USE_REFERENCE_COUNTING) duk__clear_assert_refcounts(heap); #endif #if defined(DUK_USE_LITCACHE_SIZE) duk__wipe_litcache(heap); #endif duk__mark_roots_heap(heap); /* Mark main reachability roots. */ #if defined(DUK_USE_REFERENCE_COUNTING) DUK_ASSERT(heap->refzero_list == NULL); /* Always handled to completion inline in DECREF. */ #endif duk__mark_temproots_by_heap_scan(heap); /* Temproots. */ #if defined(DUK_USE_FINALIZER_SUPPORT) duk__mark_finalizable(heap); /* Mark finalizable as reachability roots. */ duk__mark_finalize_list(heap); /* Mark finalizer work list as reachability roots. */ #endif duk__mark_temproots_by_heap_scan(heap); /* Temproots. */ /* * Sweep garbage and remove marking flags, and move objects with * finalizers to the finalizer work list. * * Objects to be swept need to get their refcounts finalized before * they are swept. In other words, their target object refcounts * need to be decreased. This has to be done before freeing any * objects to avoid decref'ing dangling pointers (which may happen * even without bugs, e.g. with reference loops) * * Because strings don't point to other heap objects, similar * finalization is not necessary for strings. */ /* XXX: more emergency behavior, e.g. find smaller hash sizes etc */ #if defined(DUK_USE_REFERENCE_COUNTING) duk__finalize_refcounts(heap); #endif duk__sweep_heap(heap, flags, &count_keep_obj); duk__sweep_stringtable(heap, &count_keep_str); #if defined(DUK_USE_ASSERTIONS) && defined(DUK_USE_REFERENCE_COUNTING) duk__check_assert_refcounts(heap); #endif #if defined(DUK_USE_REFERENCE_COUNTING) DUK_ASSERT(heap->refzero_list == NULL); /* Always handled to completion inline in DECREF. */ #endif #if defined(DUK_USE_FINALIZER_SUPPORT) duk__clear_finalize_list_flags(heap); #endif /* * Object compaction (emergency only). * * Object compaction is a separate step after sweeping, as there is * more free memory for it to work with. Also, currently compaction * may insert new objects into the heap allocated list and the string * table which we don't want to do during a sweep (the reachability * flags of such objects would be incorrect). The objects inserted * are currently: * * - a temporary duk_hbuffer for a new properties allocation * - if array part is abandoned, string keys are interned * * The object insertions go to the front of the list, so they do not * cause an infinite loop (they are not compacted). * * At present compaction is not allowed when mark-and-sweep runs * during error handling because it involves a duk_safe_call() * interfering with error state. */ if ((flags & DUK_MS_FLAG_EMERGENCY) && !(flags & DUK_MS_FLAG_NO_OBJECT_COMPACTION)) { if (heap->lj.type != DUK_LJ_TYPE_UNKNOWN) { DUK_D(DUK_DPRINT("lj.type (%ld) not DUK_LJ_TYPE_UNKNOWN, skip object compaction", (long) heap->lj.type)); } else { DUK_D(DUK_DPRINT("object compaction")); duk__compact_objects(heap); } } /* * String table resize check. * * This is mainly useful in emergency GC: if the string table load * factor is really low for some reason, we can shrink the string * table to a smaller size and free some memory in the process. * Only execute in emergency GC. String table has internal flags * to protect against recursive resizing if this mark-and-sweep pass * was triggered by a string table resize. */ if (flags & DUK_MS_FLAG_EMERGENCY) { DUK_D(DUK_DPRINT("stringtable resize check in emergency gc")); duk_heap_strtable_force_resize(heap); } /* * Finish */ DUK_ASSERT(heap->ms_prevent_count == 1); DUK_ASSERT(heap->ms_running == 1); heap->ms_prevent_count = 0; heap->ms_running = 0; heap->creating_error = entry_creating_error; /* for nested error handling, see GH-2278 */ /* * Assertions after */ #if defined(DUK_USE_ASSERTIONS) DUK_ASSERT(heap->ms_prevent_count == 0); DUK_ASSERT(!DUK_HEAP_HAS_MARKANDSWEEP_RECLIMIT_REACHED(heap)); DUK_ASSERT(heap->ms_recursion_depth == 0); duk__assert_heaphdr_flags(heap); duk__assert_validity(heap); #if defined(DUK_USE_REFERENCE_COUNTING) /* Note: heap->refzero_free_running may be true; a refcount * finalizer may trigger a mark-and-sweep. */ duk__assert_valid_refcounts(heap); #endif /* DUK_USE_REFERENCE_COUNTING */ #if defined(DUK_USE_LITCACHE_SIZE) duk__assert_litcache_nulls(heap); #endif /* DUK_USE_LITCACHE_SIZE */ #endif /* DUK_USE_ASSERTIONS */ /* * Reset trigger counter */ #if defined(DUK_USE_VOLUNTARY_GC) tmp = (count_keep_obj + count_keep_str) / 256; heap->ms_trigger_counter = (duk_int_t) ((tmp * DUK_HEAP_MARK_AND_SWEEP_TRIGGER_MULT) + DUK_HEAP_MARK_AND_SWEEP_TRIGGER_ADD); DUK_D(DUK_DPRINT("garbage collect (mark-and-sweep) finished: %ld objects kept, %ld strings kept, trigger reset to %ld", (long) count_keep_obj, (long) count_keep_str, (long) heap->ms_trigger_counter)); #else DUK_D(DUK_DPRINT("garbage collect (mark-and-sweep) finished: %ld objects kept, %ld strings kept, no voluntary trigger", (long) count_keep_obj, (long) count_keep_str)); #endif /* * Stats dump */ #if defined(DUK_USE_DEBUG) duk__dump_stats(heap); #endif /* * Finalize objects in the finalization work list. Finalized * objects are queued back to heap_allocated with FINALIZED set. * * Since finalizers may cause arbitrary side effects, they are * prevented e.g. during string table and object property allocation * resizing using heap->pf_prevent_count. In this case the objects * remain in the finalization work list after mark-and-sweep exits * and they may be finalized on the next pass or any DECREF checking * for finalize_list. * * As of Duktape 2.1 finalization happens outside mark-and-sweep * protection. Mark-and-sweep is allowed while the finalize_list * is being processed, but no rescue decisions are done while the * process is on-going. This avoids incorrect rescue decisions * if an object is considered reachable (and thus rescued) because * of a reference via finalize_list (which is considered a reachability * root). When finalize_list is being processed, reachable objects * with FINALIZED set will just keep their FINALIZED flag for later * mark-and-sweep processing. * * This could also be handled (a bit better) by having a more refined * notion of reachability for rescue/free decisions. * * XXX: avoid finalizer execution when doing emergency GC? */ #if defined(DUK_USE_FINALIZER_SUPPORT) /* Attempt to process finalize_list, pf_prevent_count check * is inside the target. */ duk_heap_process_finalize_list(heap); #endif /* DUK_USE_FINALIZER_SUPPORT */ } #line 1 "duk_heap_memory.c" /* * Memory allocation handling. */ /* #include duk_internal.h -> already included */ /* * Allocate memory with garbage collection. */ /* Slow path: voluntary GC triggered, first alloc attempt failed, or zero size. */ DUK_LOCAL DUK_NOINLINE_PERF DUK_COLD void *duk__heap_mem_alloc_slowpath(duk_heap *heap, duk_size_t size) { void *res; duk_small_int_t i; DUK_ASSERT(heap != NULL); DUK_ASSERT(heap->alloc_func != NULL); DUK_ASSERT_DISABLE(size >= 0); if (size == 0) { DUK_D(DUK_DPRINT("zero size alloc in slow path, return NULL")); return NULL; } DUK_D(DUK_DPRINT("first alloc attempt failed or voluntary GC limit reached, attempt to gc and retry")); #if 0 /* * If GC is already running there is no point in attempting a GC * because it will be skipped. This could be checked for explicitly, * but it isn't actually needed: the loop below will eventually * fail resulting in a NULL. */ if (heap->ms_prevent_count != 0) { DUK_D(DUK_DPRINT("duk_heap_mem_alloc() failed, gc in progress (gc skipped), alloc size %ld", (long) size)); return NULL; } #endif /* * Retry with several GC attempts. Initial attempts are made without * emergency mode; later attempts use emergency mode which minimizes * memory allocations forcibly. */ for (i = 0; i < DUK_HEAP_ALLOC_FAIL_MARKANDSWEEP_LIMIT; i++) { duk_small_uint_t flags; flags = 0; if (i >= DUK_HEAP_ALLOC_FAIL_MARKANDSWEEP_EMERGENCY_LIMIT - 1) { flags |= DUK_MS_FLAG_EMERGENCY; } duk_heap_mark_and_sweep(heap, flags); DUK_ASSERT(size > 0); res = heap->alloc_func(heap->heap_udata, size); if (res != NULL) { DUK_D(DUK_DPRINT("duk_heap_mem_alloc() succeeded after gc (pass %ld), alloc size %ld", (long) (i + 1), (long) size)); return res; } } DUK_D(DUK_DPRINT("duk_heap_mem_alloc() failed even after gc, alloc size %ld", (long) size)); return NULL; } DUK_INTERNAL DUK_INLINE_PERF DUK_HOT void *duk_heap_mem_alloc(duk_heap *heap, duk_size_t size) { void *res; DUK_ASSERT(heap != NULL); DUK_ASSERT(heap->alloc_func != NULL); DUK_ASSERT_DISABLE(size >= 0); #if defined(DUK_USE_VOLUNTARY_GC) /* Voluntary periodic GC (if enabled). */ if (DUK_UNLIKELY(--(heap)->ms_trigger_counter < 0)) { goto slowpath; } #endif #if defined(DUK_USE_GC_TORTURE) /* Simulate alloc failure on every alloc, except when mark-and-sweep * is running. */ if (heap->ms_prevent_count == 0) { DUK_DDD(DUK_DDDPRINT("gc torture enabled, pretend that first alloc attempt fails")); res = NULL; DUK_UNREF(res); goto slowpath; } #endif /* Zero-size allocation should happen very rarely (if at all), so * don't check zero size on NULL; handle it in the slow path * instead. This reduces size of inlined code. */ res = heap->alloc_func(heap->heap_udata, size); if (DUK_LIKELY(res != NULL)) { return res; } slowpath: if (size == 0) { DUK_D(DUK_DPRINT("first alloc attempt returned NULL for zero size alloc, use slow path to deal with it")); } else { DUK_D(DUK_DPRINT("first alloc attempt failed, attempt to gc and retry")); } return duk__heap_mem_alloc_slowpath(heap, size); } DUK_INTERNAL DUK_INLINE_PERF DUK_HOT void *duk_heap_mem_alloc_zeroed(duk_heap *heap, duk_size_t size) { void *res; DUK_ASSERT(heap != NULL); DUK_ASSERT(heap->alloc_func != NULL); DUK_ASSERT_DISABLE(size >= 0); res = DUK_ALLOC(heap, size); if (DUK_LIKELY(res != NULL)) { duk_memzero(res, size); } return res; } DUK_INTERNAL DUK_INLINE_PERF DUK_HOT void *duk_heap_mem_alloc_checked(duk_hthread *thr, duk_size_t size) { void *res; DUK_ASSERT(thr != NULL); DUK_ASSERT(thr->heap != NULL); DUK_ASSERT(thr->heap->alloc_func != NULL); res = duk_heap_mem_alloc(thr->heap, size); if (DUK_LIKELY(res != NULL)) { return res; } else if (size == 0) { DUK_ASSERT(res == NULL); return res; } DUK_ERROR_ALLOC_FAILED(thr); DUK_WO_NORETURN(return NULL;); } DUK_INTERNAL DUK_INLINE_PERF DUK_HOT void *duk_heap_mem_alloc_checked_zeroed(duk_hthread *thr, duk_size_t size) { void *res; DUK_ASSERT(thr != NULL); DUK_ASSERT(thr->heap != NULL); DUK_ASSERT(thr->heap->alloc_func != NULL); res = duk_heap_mem_alloc(thr->heap, size); if (DUK_LIKELY(res != NULL)) { duk_memzero(res, size); return res; } else if (size == 0) { DUK_ASSERT(res == NULL); return res; } DUK_ERROR_ALLOC_FAILED(thr); DUK_WO_NORETURN(return NULL;); } /* * Reallocate memory with garbage collection. */ /* Slow path: voluntary GC triggered, first realloc attempt failed, or zero size. */ DUK_LOCAL DUK_NOINLINE_PERF DUK_COLD void *duk__heap_mem_realloc_slowpath(duk_heap *heap, void *ptr, duk_size_t newsize) { void *res; duk_small_int_t i; DUK_ASSERT(heap != NULL); DUK_ASSERT(heap->realloc_func != NULL); /* ptr may be NULL */ DUK_ASSERT_DISABLE(newsize >= 0); /* Unlike for malloc(), zero size NULL result check happens at the call site. */ DUK_D(DUK_DPRINT("first realloc attempt failed, attempt to gc and retry")); #if 0 /* * Avoid a GC if GC is already running. See duk_heap_mem_alloc(). */ if (heap->ms_prevent_count != 0) { DUK_D(DUK_DPRINT("duk_heap_mem_realloc() failed, gc in progress (gc skipped), alloc size %ld", (long) newsize)); return NULL; } #endif /* * Retry with several GC attempts. Initial attempts are made without * emergency mode; later attempts use emergency mode which minimizes * memory allocations forcibly. */ for (i = 0; i < DUK_HEAP_ALLOC_FAIL_MARKANDSWEEP_LIMIT; i++) { duk_small_uint_t flags; flags = 0; if (i >= DUK_HEAP_ALLOC_FAIL_MARKANDSWEEP_EMERGENCY_LIMIT - 1) { flags |= DUK_MS_FLAG_EMERGENCY; } duk_heap_mark_and_sweep(heap, flags); res = heap->realloc_func(heap->heap_udata, ptr, newsize); if (res != NULL || newsize == 0) { DUK_D(DUK_DPRINT("duk_heap_mem_realloc() succeeded after gc (pass %ld), alloc size %ld", (long) (i + 1), (long) newsize)); return res; } } DUK_D(DUK_DPRINT("duk_heap_mem_realloc() failed even after gc, alloc size %ld", (long) newsize)); return NULL; } DUK_INTERNAL DUK_INLINE_PERF DUK_HOT void *duk_heap_mem_realloc(duk_heap *heap, void *ptr, duk_size_t newsize) { void *res; DUK_ASSERT(heap != NULL); DUK_ASSERT(heap->realloc_func != NULL); /* ptr may be NULL */ DUK_ASSERT_DISABLE(newsize >= 0); #if defined(DUK_USE_VOLUNTARY_GC) /* Voluntary periodic GC (if enabled). */ if (DUK_UNLIKELY(--(heap)->ms_trigger_counter < 0)) { goto gc_retry; } #endif #if defined(DUK_USE_GC_TORTURE) /* Simulate alloc failure on every realloc, except when mark-and-sweep * is running. */ if (heap->ms_prevent_count == 0) { DUK_DDD(DUK_DDDPRINT("gc torture enabled, pretend that first realloc attempt fails")); res = NULL; DUK_UNREF(res); goto gc_retry; } #endif res = heap->realloc_func(heap->heap_udata, ptr, newsize); if (DUK_LIKELY(res != NULL) || newsize == 0) { if (res != NULL && newsize == 0) { DUK_DD(DUK_DDPRINT("first realloc attempt returned NULL for zero size realloc, accept and return NULL")); } return res; } else { goto gc_retry; } /* Never here. */ gc_retry: return duk__heap_mem_realloc_slowpath(heap, ptr, newsize); } /* * Reallocate memory with garbage collection, using a callback to provide * the current allocated pointer. This variant is used when a mark-and-sweep * (e.g. finalizers) might change the original pointer. */ /* Slow path: voluntary GC triggered, first realloc attempt failed, or zero size. */ DUK_LOCAL DUK_NOINLINE_PERF DUK_COLD void *duk__heap_mem_realloc_indirect_slowpath(duk_heap *heap, duk_mem_getptr cb, void *ud, duk_size_t newsize) { void *res; duk_small_int_t i; DUK_ASSERT(heap != NULL); DUK_ASSERT(heap->realloc_func != NULL); DUK_ASSERT_DISABLE(newsize >= 0); /* Unlike for malloc(), zero size NULL result check happens at the call site. */ DUK_D(DUK_DPRINT("first indirect realloc attempt failed, attempt to gc and retry")); #if 0 /* * Avoid a GC if GC is already running. See duk_heap_mem_alloc(). */ if (heap->ms_prevent_count != 0) { DUK_D(DUK_DPRINT("duk_heap_mem_realloc_indirect() failed, gc in progress (gc skipped), alloc size %ld", (long) newsize)); return NULL; } #endif /* * Retry with several GC attempts. Initial attempts are made without * emergency mode; later attempts use emergency mode which minimizes * memory allocations forcibly. */ for (i = 0; i < DUK_HEAP_ALLOC_FAIL_MARKANDSWEEP_LIMIT; i++) { duk_small_uint_t flags; #if defined(DUK_USE_DEBUG) void *ptr_pre; void *ptr_post; #endif #if defined(DUK_USE_DEBUG) ptr_pre = cb(heap, ud); #endif flags = 0; if (i >= DUK_HEAP_ALLOC_FAIL_MARKANDSWEEP_EMERGENCY_LIMIT - 1) { flags |= DUK_MS_FLAG_EMERGENCY; } duk_heap_mark_and_sweep(heap, flags); #if defined(DUK_USE_DEBUG) ptr_post = cb(heap, ud); if (ptr_pre != ptr_post) { DUK_DD(DUK_DDPRINT("realloc base pointer changed by mark-and-sweep: %p -> %p", (void *) ptr_pre, (void *) ptr_post)); } #endif /* Note: key issue here is to re-lookup the base pointer on every attempt. * The pointer being reallocated may change after every mark-and-sweep. */ res = heap->realloc_func(heap->heap_udata, cb(heap, ud), newsize); if (res != NULL || newsize == 0) { DUK_D(DUK_DPRINT("duk_heap_mem_realloc_indirect() succeeded after gc (pass %ld), alloc size %ld", (long) (i + 1), (long) newsize)); return res; } } DUK_D(DUK_DPRINT("duk_heap_mem_realloc_indirect() failed even after gc, alloc size %ld", (long) newsize)); return NULL; } DUK_INTERNAL DUK_INLINE_PERF DUK_HOT void *duk_heap_mem_realloc_indirect(duk_heap *heap, duk_mem_getptr cb, void *ud, duk_size_t newsize) { void *res; DUK_ASSERT(heap != NULL); DUK_ASSERT(heap->realloc_func != NULL); DUK_ASSERT_DISABLE(newsize >= 0); #if defined(DUK_USE_VOLUNTARY_GC) /* Voluntary periodic GC (if enabled). */ if (DUK_UNLIKELY(--(heap)->ms_trigger_counter < 0)) { goto gc_retry; } #endif #if defined(DUK_USE_GC_TORTURE) /* Simulate alloc failure on every realloc, except when mark-and-sweep * is running. */ if (heap->ms_prevent_count == 0) { DUK_DDD(DUK_DDDPRINT("gc torture enabled, pretend that first indirect realloc attempt fails")); res = NULL; DUK_UNREF(res); goto gc_retry; } #endif res = heap->realloc_func(heap->heap_udata, cb(heap, ud), newsize); if (DUK_LIKELY(res != NULL) || newsize == 0) { if (res != NULL && newsize == 0) { DUK_DD(DUK_DDPRINT( "first indirect realloc attempt returned NULL for zero size realloc, accept and return NULL")); } return res; } else { goto gc_retry; } /* Never here. */ gc_retry: return duk__heap_mem_realloc_indirect_slowpath(heap, cb, ud, newsize); } /* * Free memory */ DUK_INTERNAL DUK_INLINE_PERF DUK_HOT void duk_heap_mem_free(duk_heap *heap, void *ptr) { DUK_ASSERT(heap != NULL); DUK_ASSERT(heap->free_func != NULL); /* ptr may be NULL */ /* Must behave like a no-op with NULL and any pointer returned from * malloc/realloc with zero size. */ heap->free_func(heap->heap_udata, ptr); /* Never perform a GC (even voluntary) in a memory free, otherwise * all call sites doing frees would need to deal with the side effects. * No need to update voluntary GC counter either. */ } #line 1 "duk_heap_misc.c" /* * Support functions for duk_heap. */ /* #include duk_internal.h -> already included */ DUK_INTERNAL void duk_heap_insert_into_heap_allocated(duk_heap *heap, duk_heaphdr *hdr) { duk_heaphdr *root; DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(hdr) != DUK_HTYPE_STRING); root = heap->heap_allocated; #if defined(DUK_USE_DOUBLE_LINKED_HEAP) if (root != NULL) { DUK_ASSERT(DUK_HEAPHDR_GET_PREV(heap, root) == NULL); DUK_HEAPHDR_SET_PREV(heap, root, hdr); } DUK_HEAPHDR_SET_PREV(heap, hdr, NULL); #endif DUK_HEAPHDR_SET_NEXT(heap, hdr, root); DUK_HEAPHDR_ASSERT_LINKS(heap, hdr); DUK_HEAPHDR_ASSERT_LINKS(heap, root); heap->heap_allocated = hdr; } #if defined(DUK_USE_REFERENCE_COUNTING) DUK_INTERNAL void duk_heap_remove_from_heap_allocated(duk_heap *heap, duk_heaphdr *hdr) { duk_heaphdr *prev; duk_heaphdr *next; /* Strings are in string table. */ DUK_ASSERT(hdr != NULL); DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(hdr) != DUK_HTYPE_STRING); /* Target 'hdr' must be in heap_allocated (not e.g. finalize_list). * If not, heap lists will become corrupted so assert early for it. */ #if defined(DUK_USE_ASSERTIONS) { duk_heaphdr *tmp; for (tmp = heap->heap_allocated; tmp != NULL; tmp = DUK_HEAPHDR_GET_NEXT(heap, tmp)) { if (tmp == hdr) { break; } } DUK_ASSERT(tmp == hdr); } #endif /* Read/write only once to minimize pointer compression calls. */ prev = DUK_HEAPHDR_GET_PREV(heap, hdr); next = DUK_HEAPHDR_GET_NEXT(heap, hdr); if (prev != NULL) { DUK_ASSERT(heap->heap_allocated != hdr); DUK_HEAPHDR_SET_NEXT(heap, prev, next); } else { DUK_ASSERT(heap->heap_allocated == hdr); heap->heap_allocated = next; } if (next != NULL) { DUK_HEAPHDR_SET_PREV(heap, next, prev); } else { ; } } #endif /* DUK_USE_REFERENCE_COUNTING */ #if defined(DUK_USE_FINALIZER_SUPPORT) DUK_INTERNAL void duk_heap_insert_into_finalize_list(duk_heap *heap, duk_heaphdr *hdr) { duk_heaphdr *root; root = heap->finalize_list; #if defined(DUK_USE_DOUBLE_LINKED_HEAP) DUK_HEAPHDR_SET_PREV(heap, hdr, NULL); if (root != NULL) { DUK_ASSERT(DUK_HEAPHDR_GET_PREV(heap, root) == NULL); DUK_HEAPHDR_SET_PREV(heap, root, hdr); } #endif DUK_HEAPHDR_SET_NEXT(heap, hdr, root); DUK_HEAPHDR_ASSERT_LINKS(heap, hdr); DUK_HEAPHDR_ASSERT_LINKS(heap, root); heap->finalize_list = hdr; } #endif /* DUK_USE_FINALIZER_SUPPORT */ #if defined(DUK_USE_FINALIZER_SUPPORT) DUK_INTERNAL void duk_heap_remove_from_finalize_list(duk_heap *heap, duk_heaphdr *hdr) { #if defined(DUK_USE_DOUBLE_LINKED_HEAP) duk_heaphdr *next; duk_heaphdr *prev; next = DUK_HEAPHDR_GET_NEXT(heap, hdr); prev = DUK_HEAPHDR_GET_PREV(heap, hdr); if (next != NULL) { DUK_ASSERT(DUK_HEAPHDR_GET_PREV(heap, next) == hdr); DUK_HEAPHDR_SET_PREV(heap, next, prev); } if (prev == NULL) { DUK_ASSERT(hdr == heap->finalize_list); heap->finalize_list = next; } else { DUK_ASSERT(hdr != heap->finalize_list); DUK_HEAPHDR_SET_NEXT(heap, prev, next); } #else duk_heaphdr *next; duk_heaphdr *curr; /* Random removal is expensive: we need to locate the previous element * because we don't have a 'prev' pointer. */ curr = heap->finalize_list; if (curr == hdr) { heap->finalize_list = DUK_HEAPHDR_GET_NEXT(heap, curr); } else { DUK_ASSERT(hdr != heap->finalize_list); for (;;) { DUK_ASSERT(curr != NULL); /* Caller responsibility. */ next = DUK_HEAPHDR_GET_NEXT(heap, curr); if (next == hdr) { next = DUK_HEAPHDR_GET_NEXT(heap, hdr); DUK_HEAPHDR_SET_NEXT(heap, curr, next); break; } } } #endif } #endif /* DUK_USE_FINALIZER_SUPPORT */ #if defined(DUK_USE_ASSERTIONS) DUK_INTERNAL duk_bool_t duk_heap_in_heap_allocated(duk_heap *heap, duk_heaphdr *ptr) { duk_heaphdr *curr; DUK_ASSERT(heap != NULL); for (curr = heap->heap_allocated; curr != NULL; curr = DUK_HEAPHDR_GET_NEXT(heap, curr)) { if (curr == ptr) { return 1; } } return 0; } #endif /* DUK_USE_ASSERTIONS */ #if defined(DUK_USE_INTERRUPT_COUNTER) DUK_INTERNAL void duk_heap_switch_thread(duk_heap *heap, duk_hthread *new_thr) { duk_hthread *curr_thr; DUK_ASSERT(heap != NULL); if (new_thr != NULL) { curr_thr = heap->curr_thread; if (curr_thr == NULL) { /* For initial entry use default value; zero forces an * interrupt before executing the first insturction. */ DUK_DD(DUK_DDPRINT("switch thread, initial entry, init default interrupt counter")); new_thr->interrupt_counter = 0; new_thr->interrupt_init = 0; } else { /* Copy interrupt counter/init value state to new thread (if any). * It's OK for new_thr to be the same as curr_thr. */ #if defined(DUK_USE_DEBUG) if (new_thr != curr_thr) { DUK_DD(DUK_DDPRINT("switch thread, not initial entry, copy interrupt counter")); } #endif new_thr->interrupt_counter = curr_thr->interrupt_counter; new_thr->interrupt_init = curr_thr->interrupt_init; } } else { DUK_DD(DUK_DDPRINT("switch thread, new thread is NULL, no interrupt counter changes")); } heap->curr_thread = new_thr; /* may be NULL */ } #endif /* DUK_USE_INTERRUPT_COUNTER */ #if defined(DUK_USE_ASSERTIONS) DUK_INTERNAL void duk_heap_assert_valid(duk_heap *heap) { DUK_ASSERT(heap != NULL); } #endif #line 1 "duk_heap_refcount.c" /* * Reference counting implementation. * * INCREF/DECREF, finalization and freeing of objects whose refcount reaches * zero (refzero). These operations are very performance sensitive, so * various small tricks are used in an attempt to maximize speed. */ /* #include duk_internal.h -> already included */ #if defined(DUK_USE_REFERENCE_COUNTING) #if !defined(DUK_USE_DOUBLE_LINKED_HEAP) #error internal error, reference counting requires a double linked heap #endif /* * Heap object refcount finalization. * * When an object is about to be freed, all other objects it refers to must * be decref'd. Refcount finalization does NOT free the object or its inner * allocations (mark-and-sweep shares these helpers), it just manipulates * the refcounts. * * Note that any of the DECREFs may cause a refcount to drop to zero. If so, * the object won't be refzero processed inline, but will just be queued to * refzero_list and processed by an earlier caller working on refzero_list, * eliminating C recursion from even long refzero cascades. If refzero * finalization is triggered by mark-and-sweep, refzero conditions are ignored * (objects are not even queued to refzero_list) because mark-and-sweep deals * with them; refcounts are still updated so that they remain in sync with * actual references. */ DUK_LOCAL void duk__decref_tvals_norz(duk_hthread *thr, duk_tval *tv, duk_idx_t count) { DUK_ASSERT(count == 0 || tv != NULL); while (count-- > 0) { DUK_TVAL_DECREF_NORZ(thr, tv); tv++; } } DUK_INTERNAL void duk_hobject_refcount_finalize_norz(duk_heap *heap, duk_hobject *h) { duk_hthread *thr; duk_uint_fast32_t i; duk_uint_fast32_t n; duk_propvalue *p_val; duk_tval *p_tv; duk_hstring **p_key; duk_uint8_t *p_flag; duk_hobject *h_proto; DUK_ASSERT(heap != NULL); DUK_ASSERT(heap->heap_thread != NULL); DUK_ASSERT(h); DUK_ASSERT(DUK_HEAPHDR_GET_TYPE((duk_heaphdr *) h) == DUK_HTYPE_OBJECT); thr = heap->heap_thread; DUK_ASSERT(thr != NULL); p_key = DUK_HOBJECT_E_GET_KEY_BASE(heap, h); p_val = DUK_HOBJECT_E_GET_VALUE_BASE(heap, h); p_flag = DUK_HOBJECT_E_GET_FLAGS_BASE(heap, h); n = DUK_HOBJECT_GET_ENEXT(h); while (n-- > 0) { duk_hstring *key; key = p_key[n]; if (DUK_UNLIKELY(key == NULL)) { continue; } DUK_HSTRING_DECREF_NORZ(thr, key); if (DUK_UNLIKELY(p_flag[n] & DUK_PROPDESC_FLAG_ACCESSOR)) { duk_hobject *h_getset; h_getset = p_val[n].a.get; DUK_ASSERT(h_getset == NULL || DUK_HEAPHDR_IS_OBJECT((duk_heaphdr *) h_getset)); DUK_HOBJECT_DECREF_NORZ_ALLOWNULL(thr, h_getset); h_getset = p_val[n].a.set; DUK_ASSERT(h_getset == NULL || DUK_HEAPHDR_IS_OBJECT((duk_heaphdr *) h_getset)); DUK_HOBJECT_DECREF_NORZ_ALLOWNULL(thr, h_getset); } else { duk_tval *tv_val; tv_val = &p_val[n].v; DUK_TVAL_DECREF_NORZ(thr, tv_val); } } p_tv = DUK_HOBJECT_A_GET_BASE(heap, h); n = DUK_HOBJECT_GET_ASIZE(h); while (n-- > 0) { duk_tval *tv_val; tv_val = p_tv + n; DUK_TVAL_DECREF_NORZ(thr, tv_val); } /* Hash part is a 'weak reference' and doesn't contribute to refcounts. */ h_proto = (duk_hobject *) DUK_HOBJECT_GET_PROTOTYPE(heap, h); DUK_ASSERT(h_proto == NULL || DUK_HEAPHDR_IS_OBJECT((duk_heaphdr *) h_proto)); DUK_HOBJECT_DECREF_NORZ_ALLOWNULL(thr, h_proto); /* XXX: Object subclass tests are quite awkward at present, ideally * we should be able to switch-case here with a dense index (subtype * number or something). For now, fast path plain objects and arrays * and bit test the rest individually. */ if (DUK_HOBJECT_HAS_FASTREFS(h)) { /* Plain object or array, nothing more to do. While a * duk_harray has additional fields, none of them need * DECREF updates. */ DUK_ASSERT(DUK_HOBJECT_ALLOWS_FASTREFS(h)); return; } DUK_ASSERT(DUK_HOBJECT_PROHIBITS_FASTREFS(h)); /* Slow path: special object, start bit checks from most likely. */ /* XXX: reorg, more common first */ if (DUK_HOBJECT_IS_COMPFUNC(h)) { duk_hcompfunc *f = (duk_hcompfunc *) h; duk_tval *tv, *tv_end; duk_hobject **funcs, **funcs_end; DUK_HCOMPFUNC_ASSERT_VALID(f); if (DUK_LIKELY(DUK_HCOMPFUNC_GET_DATA(heap, f) != NULL)) { tv = DUK_HCOMPFUNC_GET_CONSTS_BASE(heap, f); tv_end = DUK_HCOMPFUNC_GET_CONSTS_END(heap, f); while (tv < tv_end) { DUK_TVAL_DECREF_NORZ(thr, tv); tv++; } funcs = DUK_HCOMPFUNC_GET_FUNCS_BASE(heap, f); funcs_end = DUK_HCOMPFUNC_GET_FUNCS_END(heap, f); while (funcs < funcs_end) { duk_hobject *h_func; h_func = *funcs; DUK_ASSERT(h_func != NULL); DUK_ASSERT(DUK_HEAPHDR_IS_OBJECT((duk_heaphdr *) h_func)); DUK_HCOMPFUNC_DECREF_NORZ(thr, (duk_hcompfunc *) h_func); funcs++; } } else { /* May happen in some out-of-memory corner cases. */ DUK_D(DUK_DPRINT("duk_hcompfunc 'data' is NULL, skipping decref")); } DUK_HEAPHDR_DECREF_ALLOWNULL(thr, (duk_heaphdr *) DUK_HCOMPFUNC_GET_LEXENV(heap, f)); DUK_HEAPHDR_DECREF_ALLOWNULL(thr, (duk_heaphdr *) DUK_HCOMPFUNC_GET_VARENV(heap, f)); DUK_HEAPHDR_DECREF_ALLOWNULL(thr, (duk_hbuffer *) DUK_HCOMPFUNC_GET_DATA(heap, f)); } else if (DUK_HOBJECT_IS_DECENV(h)) { duk_hdecenv *e = (duk_hdecenv *) h; DUK_HDECENV_ASSERT_VALID(e); DUK_HTHREAD_DECREF_NORZ_ALLOWNULL(thr, e->thread); DUK_HOBJECT_DECREF_NORZ_ALLOWNULL(thr, e->varmap); } else if (DUK_HOBJECT_IS_OBJENV(h)) { duk_hobjenv *e = (duk_hobjenv *) h; DUK_HOBJENV_ASSERT_VALID(e); DUK_ASSERT(e->target != NULL); /* Required for object environments. */ DUK_HOBJECT_DECREF_NORZ(thr, e->target); #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) } else if (DUK_HOBJECT_IS_BUFOBJ(h)) { duk_hbufobj *b = (duk_hbufobj *) h; DUK_HBUFOBJ_ASSERT_VALID(b); DUK_HBUFFER_DECREF_NORZ_ALLOWNULL(thr, (duk_hbuffer *) b->buf); DUK_HOBJECT_DECREF_NORZ_ALLOWNULL(thr, (duk_hobject *) b->buf_prop); #endif /* DUK_USE_BUFFEROBJECT_SUPPORT */ } else if (DUK_HOBJECT_IS_BOUNDFUNC(h)) { duk_hboundfunc *f = (duk_hboundfunc *) (void *) h; DUK_HBOUNDFUNC_ASSERT_VALID(f); DUK_TVAL_DECREF_NORZ(thr, &f->target); DUK_TVAL_DECREF_NORZ(thr, &f->this_binding); duk__decref_tvals_norz(thr, f->args, f->nargs); #if defined(DUK_USE_ES6_PROXY) } else if (DUK_HOBJECT_IS_PROXY(h)) { duk_hproxy *p = (duk_hproxy *) h; DUK_HPROXY_ASSERT_VALID(p); DUK_HOBJECT_DECREF_NORZ(thr, p->target); DUK_HOBJECT_DECREF_NORZ(thr, p->handler); #endif /* DUK_USE_ES6_PROXY */ } else if (DUK_HOBJECT_IS_THREAD(h)) { duk_hthread *t = (duk_hthread *) h; duk_activation *act; duk_tval *tv; DUK_HTHREAD_ASSERT_VALID(t); tv = t->valstack; while (tv < t->valstack_top) { DUK_TVAL_DECREF_NORZ(thr, tv); tv++; } for (act = t->callstack_curr; act != NULL; act = act->parent) { DUK_HOBJECT_DECREF_NORZ_ALLOWNULL(thr, (duk_hobject *) DUK_ACT_GET_FUNC(act)); DUK_HOBJECT_DECREF_NORZ_ALLOWNULL(thr, (duk_hobject *) act->var_env); DUK_HOBJECT_DECREF_NORZ_ALLOWNULL(thr, (duk_hobject *) act->lex_env); #if defined(DUK_USE_NONSTD_FUNC_CALLER_PROPERTY) DUK_HOBJECT_DECREF_NORZ_ALLOWNULL(thr, (duk_hobject *) act->prev_caller); #endif #if 0 /* nothing now */ for (cat = act->cat; cat != NULL; cat = cat->parent) { } #endif } for (i = 0; i < DUK_NUM_BUILTINS; i++) { DUK_HOBJECT_DECREF_NORZ_ALLOWNULL(thr, (duk_hobject *) t->builtins[i]); } DUK_HTHREAD_DECREF_NORZ_ALLOWNULL(thr, (duk_hthread *) t->resumer); } else { /* We may come here if the object should have a FASTREFS flag * but it's missing for some reason. Assert for never getting * here; however, other than performance, this is harmless. */ DUK_D(DUK_DPRINT("missing FASTREFS flag for: %!iO", h)); DUK_ASSERT(0); } } DUK_INTERNAL void duk_heaphdr_refcount_finalize_norz(duk_heap *heap, duk_heaphdr *hdr) { DUK_ASSERT(heap != NULL); DUK_ASSERT(heap->heap_thread != NULL); DUK_ASSERT(hdr != NULL); if (DUK_HEAPHDR_IS_OBJECT(hdr)) { duk_hobject_refcount_finalize_norz(heap, (duk_hobject *) hdr); } /* DUK_HTYPE_BUFFER: nothing to finalize */ /* DUK_HTYPE_STRING: nothing to finalize */ } /* * Refzero processing for duk_hobject: queue a refzero'ed object to either * finalize_list or refzero_list and process the relevent list(s) if * necessary. * * Refzero_list is single linked, with only 'prev' pointers set and valid. * All 'next' pointers are intentionally left as garbage. This doesn't * matter because refzero_list is processed to completion before any other * code (like mark-and-sweep) might walk the list. * * In more detail: * * - On first insert refzero_list is NULL and the new object becomes the * first and only element on the list; duk__refcount_free_pending() is * called and it starts processing the list from the initial element, * i.e. the list tail. * * - As each object is refcount finalized, new objects may be queued to * refzero_list head. Their 'next' pointers are left as garbage, but * 'prev' points are set correctly, with the element at refzero_list * having a NULL 'prev' pointer. The fact that refzero_list is non-NULL * is used to reject (1) recursive duk__refcount_free_pending() and * (2) finalize_list processing calls. * * - When we're done with the current object, read its 'prev' pointer and * free the object. If 'prev' is NULL, we've reached head of list and are * done: set refzero_list to NULL and process pending finalizers. Otherwise * continue processing the list. * * A refzero cascade is free of side effects because it only involves * queueing more objects and freeing memory; finalizer execution is blocked * in the code path queueing objects to finalize_list. As a result the * initial refzero call (which triggers duk__refcount_free_pending()) must * check finalize_list so that finalizers are executed snappily. * * If finalize_list processing starts first, refzero may occur while we're * processing finalizers. That's fine: that particular refzero cascade is * handled to completion without side effects. Once the cascade is complete, * we'll run pending finalizers but notice that we're already doing that and * return. * * This could be expanded to allow incremental freeing: just bail out * early and resume at a future alloc/decref/refzero. However, if that * were done, the list structure would need to be kept consistent at all * times, mark-and-sweep would need to handle refzero_list, etc. */ DUK_LOCAL void duk__refcount_free_pending(duk_heap *heap) { duk_heaphdr *curr; #if defined(DUK_USE_DEBUG) duk_int_t count = 0; #endif DUK_ASSERT(heap != NULL); curr = heap->refzero_list; DUK_ASSERT(curr != NULL); DUK_ASSERT(DUK_HEAPHDR_GET_PREV(heap, curr) == NULL); /* We're called on initial insert only. */ /* curr->next is GARBAGE. */ do { duk_heaphdr *prev; DUK_DDD(DUK_DDDPRINT("refzero processing %p: %!O", (void *) curr, (duk_heaphdr *) curr)); #if defined(DUK_USE_DEBUG) count++; #endif DUK_ASSERT(curr != NULL); DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(curr) == DUK_HTYPE_OBJECT); /* currently, always the case */ /* FINALIZED may be set; don't care about flags here. */ /* Refcount finalize 'curr'. Refzero_list must be non-NULL * here to prevent recursive entry to duk__refcount_free_pending(). */ DUK_ASSERT(heap->refzero_list != NULL); duk_hobject_refcount_finalize_norz(heap, (duk_hobject *) curr); prev = DUK_HEAPHDR_GET_PREV(heap, curr); DUK_ASSERT((prev == NULL && heap->refzero_list == curr) || (prev != NULL && heap->refzero_list != curr)); /* prev->next is intentionally not updated and is garbage. */ duk_free_hobject(heap, (duk_hobject *) curr); /* Invalidates 'curr'. */ curr = prev; } while (curr != NULL); heap->refzero_list = NULL; DUK_DD(DUK_DDPRINT("refzero processed %ld objects", (long) count)); } DUK_LOCAL DUK_INLINE void duk__refcount_refzero_hobject(duk_heap *heap, duk_hobject *obj, duk_bool_t skip_free_pending) { duk_heaphdr *hdr; duk_heaphdr *root; DUK_ASSERT(heap != NULL); DUK_ASSERT(heap->heap_thread != NULL); DUK_ASSERT(obj != NULL); DUK_ASSERT(DUK_HEAPHDR_GET_TYPE((duk_heaphdr *) obj) == DUK_HTYPE_OBJECT); hdr = (duk_heaphdr *) obj; /* Refzero'd objects must be in heap_allocated. They can't be in * finalize_list because all objects on finalize_list have an * artificial +1 refcount bump. */ #if defined(DUK_USE_ASSERTIONS) DUK_ASSERT(duk_heap_in_heap_allocated(heap, (duk_heaphdr *) obj)); #endif DUK_HEAP_REMOVE_FROM_HEAP_ALLOCATED(heap, hdr); #if defined(DUK_USE_FINALIZER_SUPPORT) /* This finalizer check MUST BE side effect free. It should also be * as fast as possible because it's applied to every object freed. */ if (DUK_UNLIKELY(DUK_HOBJECT_HAS_FINALIZER_FAST(heap, (duk_hobject *) hdr) != 0U)) { /* Special case: FINALIZED may be set if mark-and-sweep queued * object for finalization, the finalizer was executed (and * FINALIZED set), mark-and-sweep hasn't yet processed the * object again, but its refcount drops to zero. Free without * running the finalizer again. */ if (DUK_HEAPHDR_HAS_FINALIZED(hdr)) { DUK_D(DUK_DPRINT("refzero'd object has finalizer and FINALIZED is set -> free")); } else { /* Set FINALIZABLE flag so that all objects on finalize_list * will have it set and are thus detectable based on the * flag alone. */ DUK_HEAPHDR_SET_FINALIZABLE(hdr); DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZED(hdr)); #if defined(DUK_USE_REFERENCE_COUNTING) /* Bump refcount on finalize_list insert so that a * refzero can never occur when an object is waiting * for its finalizer call. Refzero might otherwise * now happen because we allow duk_push_heapptr() for * objects pending finalization. */ DUK_HEAPHDR_PREINC_REFCOUNT(hdr); #endif DUK_HEAP_INSERT_INTO_FINALIZE_LIST(heap, hdr); /* Process finalizers unless skipping is explicitly * requested (NORZ) or refzero_list is being processed * (avoids side effects during a refzero cascade). * If refzero_list is processed, the initial refzero * call will run pending finalizers when refzero_list * is done. */ if (!skip_free_pending && heap->refzero_list == NULL) { duk_heap_process_finalize_list(heap); } return; } } #endif /* DUK_USE_FINALIZER_SUPPORT */ /* No need to finalize, free object via refzero_list. */ root = heap->refzero_list; DUK_HEAPHDR_SET_PREV(heap, hdr, NULL); /* 'next' is left as GARBAGE. */ heap->refzero_list = hdr; if (root == NULL) { /* Object is now queued. Refzero_list was NULL so * no-one is currently processing it; do it here. * With refzero processing just doing a cascade of * free calls, we can process it directly even when * NORZ macros are used: there are no side effects. */ duk__refcount_free_pending(heap); DUK_ASSERT(heap->refzero_list == NULL); /* Process finalizers only after the entire cascade * is finished. In most cases there's nothing to * finalize, so fast path check to avoid a call. */ #if defined(DUK_USE_FINALIZER_SUPPORT) if (!skip_free_pending && DUK_UNLIKELY(heap->finalize_list != NULL)) { duk_heap_process_finalize_list(heap); } #endif } else { DUK_ASSERT(DUK_HEAPHDR_GET_PREV(heap, root) == NULL); DUK_HEAPHDR_SET_PREV(heap, root, hdr); /* Object is now queued. Because refzero_list was * non-NULL, it's already being processed by someone * in the C call stack, so we're done. */ } } #if defined(DUK_USE_FINALIZER_SUPPORT) DUK_INTERNAL DUK_ALWAYS_INLINE void duk_refzero_check_fast(duk_hthread *thr) { DUK_ASSERT(thr != NULL); DUK_ASSERT(thr->heap != NULL); DUK_ASSERT(thr->heap->refzero_list == NULL); /* Processed to completion inline. */ if (DUK_UNLIKELY(thr->heap->finalize_list != NULL)) { duk_heap_process_finalize_list(thr->heap); } } DUK_INTERNAL void duk_refzero_check_slow(duk_hthread *thr) { DUK_ASSERT(thr != NULL); DUK_ASSERT(thr->heap != NULL); DUK_ASSERT(thr->heap->refzero_list == NULL); /* Processed to completion inline. */ if (DUK_UNLIKELY(thr->heap->finalize_list != NULL)) { duk_heap_process_finalize_list(thr->heap); } } #endif /* DUK_USE_FINALIZER_SUPPORT */ /* * Refzero processing for duk_hstring. */ DUK_LOCAL DUK_INLINE void duk__refcount_refzero_hstring(duk_heap *heap, duk_hstring *str) { DUK_ASSERT(heap != NULL); DUK_ASSERT(heap->heap_thread != NULL); DUK_ASSERT(str != NULL); DUK_ASSERT(DUK_HEAPHDR_GET_TYPE((duk_heaphdr *) str) == DUK_HTYPE_STRING); duk_heap_strcache_string_remove(heap, str); duk_heap_strtable_unlink(heap, str); duk_free_hstring(heap, str); } /* * Refzero processing for duk_hbuffer. */ DUK_LOCAL DUK_INLINE void duk__refcount_refzero_hbuffer(duk_heap *heap, duk_hbuffer *buf) { DUK_ASSERT(heap != NULL); DUK_ASSERT(heap->heap_thread != NULL); DUK_ASSERT(buf != NULL); DUK_ASSERT(DUK_HEAPHDR_GET_TYPE((duk_heaphdr *) buf) == DUK_HTYPE_BUFFER); DUK_HEAP_REMOVE_FROM_HEAP_ALLOCATED(heap, (duk_heaphdr *) buf); duk_free_hbuffer(heap, buf); } /* * Incref and decref functions. * * Decref may trigger immediate refzero handling, which may free and finalize * an arbitrary number of objects (a "DECREF cascade"). * * Refzero handling is skipped entirely if (1) mark-and-sweep is running or * (2) execution is paused in the debugger. The objects are left in the heap, * and will be freed by mark-and-sweep or eventual heap destruction. * * This is necessary during mark-and-sweep because refcounts are also updated * during the sweep phase (otherwise objects referenced by a swept object * would have incorrect refcounts) which then calls here. This could be * avoided by using separate decref macros in mark-and-sweep; however, * mark-and-sweep also calls finalizers which would use the ordinary decref * macros anyway. * * We can't process refzeros (= free objects) when the debugger is running * as the debugger might make an object unreachable but still continue * inspecting it (or even cause it to be pushed back). So we must rely on * mark-and-sweep to collect them. * * The DUK__RZ_SUPPRESS_CHECK() condition is also used in heap destruction * when running finalizers for remaining objects: the flag prevents objects * from being moved around in heap linked lists while that's being done. * * The suppress condition is important to performance. */ #define DUK__RZ_SUPPRESS_ASSERT1() \ do { \ DUK_ASSERT(thr != NULL); \ DUK_ASSERT(thr->heap != NULL); \ /* When mark-and-sweep runs, heap_thread must exist. */ \ DUK_ASSERT(thr->heap->ms_running == 0 || thr->heap->heap_thread != NULL); \ /* In normal operation finalizers are executed with ms_running == 0 \ * so we should never see ms_running == 1 and thr != heap_thread. \ * In heap destruction finalizers are executed with ms_running != 0 \ * to e.g. prevent refzero; a special value ms_running == 2 is used \ * in that case so it can be distinguished from the normal runtime \ * case, and allows a stronger assertion here (GH-2030). \ */ \ DUK_ASSERT(!(thr->heap->ms_running == 1 && thr != thr->heap->heap_thread)); \ /* We may be called when the heap is initializing and we process \ * refzeros normally, but mark-and-sweep and finalizers are prevented \ * if that's the case. \ */ \ DUK_ASSERT(thr->heap->heap_initializing == 0 || thr->heap->ms_prevent_count > 0); \ DUK_ASSERT(thr->heap->heap_initializing == 0 || thr->heap->pf_prevent_count > 0); \ } while (0) #if defined(DUK_USE_DEBUGGER_SUPPORT) #define DUK__RZ_SUPPRESS_ASSERT2() \ do { \ /* When debugger is paused, ms_running is set. */ \ DUK_ASSERT(!DUK_HEAP_HAS_DEBUGGER_PAUSED(thr->heap) || thr->heap->ms_running != 0); \ } while (0) #define DUK__RZ_SUPPRESS_COND() (heap->ms_running != 0) #else #define DUK__RZ_SUPPRESS_ASSERT2() \ do { \ } while (0) #define DUK__RZ_SUPPRESS_COND() (heap->ms_running != 0) #endif /* DUK_USE_DEBUGGER_SUPPORT */ #define DUK__RZ_SUPPRESS_CHECK() \ do { \ DUK__RZ_SUPPRESS_ASSERT1(); \ DUK__RZ_SUPPRESS_ASSERT2(); \ if (DUK_UNLIKELY(DUK__RZ_SUPPRESS_COND())) { \ DUK_DDD( \ DUK_DDDPRINT("refzero handling suppressed (not even queued) when mark-and-sweep running, object: %p", \ (void *) h)); \ return; \ } \ } while (0) #define DUK__RZ_STRING() \ do { \ duk__refcount_refzero_hstring(heap, (duk_hstring *) h); \ } while (0) #define DUK__RZ_BUFFER() \ do { \ duk__refcount_refzero_hbuffer(heap, (duk_hbuffer *) h); \ } while (0) #define DUK__RZ_OBJECT() \ do { \ duk__refcount_refzero_hobject(heap, (duk_hobject *) h, skip_free_pending); \ } while (0) /* XXX: test the effect of inlining here vs. NOINLINE in refzero helpers */ #if defined(DUK_USE_FAST_REFCOUNT_DEFAULT) #define DUK__RZ_INLINE DUK_ALWAYS_INLINE #else #define DUK__RZ_INLINE /*nop*/ #endif DUK_LOCAL DUK__RZ_INLINE void duk__hstring_refzero_helper(duk_hthread *thr, duk_hstring *h) { duk_heap *heap; DUK_ASSERT(thr != NULL); DUK_ASSERT(h != NULL); heap = thr->heap; DUK__RZ_SUPPRESS_CHECK(); DUK__RZ_STRING(); } DUK_LOCAL DUK__RZ_INLINE void duk__hbuffer_refzero_helper(duk_hthread *thr, duk_hbuffer *h) { duk_heap *heap; DUK_ASSERT(thr != NULL); DUK_ASSERT(h != NULL); heap = thr->heap; DUK__RZ_SUPPRESS_CHECK(); DUK__RZ_BUFFER(); } DUK_LOCAL DUK__RZ_INLINE void duk__hobject_refzero_helper(duk_hthread *thr, duk_hobject *h, duk_bool_t skip_free_pending) { duk_heap *heap; DUK_ASSERT(thr != NULL); DUK_ASSERT(h != NULL); heap = thr->heap; DUK__RZ_SUPPRESS_CHECK(); DUK__RZ_OBJECT(); } DUK_LOCAL DUK__RZ_INLINE void duk__heaphdr_refzero_helper(duk_hthread *thr, duk_heaphdr *h, duk_bool_t skip_free_pending) { duk_heap *heap; duk_small_uint_t htype; DUK_ASSERT(thr != NULL); DUK_ASSERT(h != NULL); heap = thr->heap; htype = (duk_small_uint_t) DUK_HEAPHDR_GET_TYPE(h); DUK_DDD(DUK_DDDPRINT("ms_running=%ld, heap_thread=%p", (long) thr->heap->ms_running, thr->heap->heap_thread)); DUK__RZ_SUPPRESS_CHECK(); switch (htype) { case DUK_HTYPE_STRING: /* Strings have no internal references but do have "weak" * references in the string cache. Also note that strings * are not on the heap_allocated list like other heap * elements. */ DUK__RZ_STRING(); break; case DUK_HTYPE_OBJECT: /* Objects have internal references. Must finalize through * the "refzero" work list. */ DUK__RZ_OBJECT(); break; default: /* Buffers have no internal references. However, a dynamic * buffer has a separate allocation for the buffer. This is * freed by duk_heap_free_heaphdr_raw(). */ DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(h) == DUK_HTYPE_BUFFER); DUK__RZ_BUFFER(); break; } } DUK_INTERNAL DUK_NOINLINE void duk_heaphdr_refzero(duk_hthread *thr, duk_heaphdr *h) { duk__heaphdr_refzero_helper(thr, h, 0 /*skip_free_pending*/); } DUK_INTERNAL DUK_NOINLINE void duk_heaphdr_refzero_norz(duk_hthread *thr, duk_heaphdr *h) { duk__heaphdr_refzero_helper(thr, h, 1 /*skip_free_pending*/); } DUK_INTERNAL DUK_NOINLINE void duk_hstring_refzero(duk_hthread *thr, duk_hstring *h) { duk__hstring_refzero_helper(thr, h); } DUK_INTERNAL DUK_NOINLINE void duk_hbuffer_refzero(duk_hthread *thr, duk_hbuffer *h) { duk__hbuffer_refzero_helper(thr, h); } DUK_INTERNAL DUK_NOINLINE void duk_hobject_refzero(duk_hthread *thr, duk_hobject *h) { duk__hobject_refzero_helper(thr, h, 0 /*skip_free_pending*/); } DUK_INTERNAL DUK_NOINLINE void duk_hobject_refzero_norz(duk_hthread *thr, duk_hobject *h) { duk__hobject_refzero_helper(thr, h, 1 /*skip_free_pending*/); } #if !defined(DUK_USE_FAST_REFCOUNT_DEFAULT) DUK_INTERNAL void duk_tval_incref(duk_tval *tv) { DUK_ASSERT(tv != NULL); if (DUK_TVAL_NEEDS_REFCOUNT_UPDATE(tv)) { duk_heaphdr *h = DUK_TVAL_GET_HEAPHDR(tv); DUK_ASSERT(h != NULL); DUK_ASSERT(DUK_HEAPHDR_HTYPE_VALID(h)); DUK_ASSERT_DISABLE(h->h_refcount >= 0); DUK_HEAPHDR_PREINC_REFCOUNT(h); DUK_ASSERT(DUK_HEAPHDR_GET_REFCOUNT(h) != 0); /* No wrapping. */ } } DUK_INTERNAL void duk_tval_decref(duk_hthread *thr, duk_tval *tv) { DUK_ASSERT(thr != NULL); DUK_ASSERT(tv != NULL); if (DUK_TVAL_NEEDS_REFCOUNT_UPDATE(tv)) { duk_heaphdr *h = DUK_TVAL_GET_HEAPHDR(tv); DUK_ASSERT(h != NULL); DUK_ASSERT(DUK_HEAPHDR_HTYPE_VALID(h)); DUK_ASSERT(DUK_HEAPHDR_GET_REFCOUNT(h) >= 1); #if 0 if (DUK_HEAPHDR_PREDEC_REFCOUNT(h) != 0) { return; } duk_heaphdr_refzero(thr, h); #else duk_heaphdr_decref(thr, h); #endif } } DUK_INTERNAL void duk_tval_decref_norz(duk_hthread *thr, duk_tval *tv) { DUK_ASSERT(thr != NULL); DUK_ASSERT(tv != NULL); if (DUK_TVAL_NEEDS_REFCOUNT_UPDATE(tv)) { duk_heaphdr *h = DUK_TVAL_GET_HEAPHDR(tv); DUK_ASSERT(h != NULL); DUK_ASSERT(DUK_HEAPHDR_HTYPE_VALID(h)); DUK_ASSERT(DUK_HEAPHDR_GET_REFCOUNT(h) >= 1); #if 0 if (DUK_HEAPHDR_PREDEC_REFCOUNT(h) != 0) { return; } duk_heaphdr_refzero_norz(thr, h); #else duk_heaphdr_decref_norz(thr, h); #endif } } #endif /* !DUK_USE_FAST_REFCOUNT_DEFAULT */ #define DUK__DECREF_ASSERTS() \ do { \ DUK_ASSERT(thr != NULL); \ DUK_ASSERT(thr->heap != NULL); \ DUK_ASSERT(h != NULL); \ DUK_ASSERT(DUK_HEAPHDR_HTYPE_VALID((duk_heaphdr *) h)); \ DUK_ASSERT(DUK_HEAPHDR_GET_REFCOUNT((duk_heaphdr *) h) >= 1); \ } while (0) #if defined(DUK_USE_ROM_OBJECTS) #define DUK__INCREF_SHARED() \ do { \ if (DUK_HEAPHDR_HAS_READONLY((duk_heaphdr *) h)) { \ return; \ } \ DUK_HEAPHDR_PREINC_REFCOUNT((duk_heaphdr *) h); \ DUK_ASSERT(DUK_HEAPHDR_GET_REFCOUNT((duk_heaphdr *) h) != 0); /* No wrapping. */ \ } while (0) #define DUK__DECREF_SHARED() \ do { \ if (DUK_HEAPHDR_HAS_READONLY((duk_heaphdr *) h)) { \ return; \ } \ if (DUK_HEAPHDR_PREDEC_REFCOUNT((duk_heaphdr *) h) != 0) { \ return; \ } \ } while (0) #else #define DUK__INCREF_SHARED() \ do { \ DUK_HEAPHDR_PREINC_REFCOUNT((duk_heaphdr *) h); \ DUK_ASSERT(DUK_HEAPHDR_GET_REFCOUNT((duk_heaphdr *) h) != 0); /* No wrapping. */ \ } while (0) #define DUK__DECREF_SHARED() \ do { \ if (DUK_HEAPHDR_PREDEC_REFCOUNT((duk_heaphdr *) h) != 0) { \ return; \ } \ } while (0) #endif #if !defined(DUK_USE_FAST_REFCOUNT_DEFAULT) /* This will in practice be inlined because it's just an INC instructions * and a bit test + INC when ROM objects are enabled. */ DUK_INTERNAL void duk_heaphdr_incref(duk_heaphdr *h) { DUK_ASSERT(h != NULL); DUK_ASSERT(DUK_HEAPHDR_HTYPE_VALID(h)); DUK_ASSERT_DISABLE(DUK_HEAPHDR_GET_REFCOUNT(h) >= 0); DUK__INCREF_SHARED(); } DUK_INTERNAL void duk_heaphdr_decref(duk_hthread *thr, duk_heaphdr *h) { DUK__DECREF_ASSERTS(); DUK__DECREF_SHARED(); duk_heaphdr_refzero(thr, h); /* Forced mark-and-sweep when GC torture enabled; this could happen * on any DECREF (but not DECREF_NORZ). */ DUK_GC_TORTURE(thr->heap); } DUK_INTERNAL void duk_heaphdr_decref_norz(duk_hthread *thr, duk_heaphdr *h) { DUK__DECREF_ASSERTS(); DUK__DECREF_SHARED(); duk_heaphdr_refzero_norz(thr, h); } #endif /* !DUK_USE_FAST_REFCOUNT_DEFAULT */ #if 0 /* Not needed. */ DUK_INTERNAL void duk_hstring_decref(duk_hthread *thr, duk_hstring *h) { DUK__DECREF_ASSERTS(); DUK__DECREF_SHARED(); duk_hstring_refzero(thr, h); } DUK_INTERNAL void duk_hstring_decref_norz(duk_hthread *thr, duk_hstring *h) { DUK__DECREF_ASSERTS(); DUK__DECREF_SHARED(); duk_hstring_refzero_norz(thr, h); } DUK_INTERNAL void duk_hbuffer_decref(duk_hthread *thr, duk_hbuffer *h) { DUK__DECREF_ASSERTS(); DUK__DECREF_SHARED(); duk_hbuffer_refzero(thr, h); } DUK_INTERNAL void duk_hbuffer_decref_norz(duk_hthread *thr, duk_hbuffer *h) { DUK__DECREF_ASSERTS(); DUK__DECREF_SHARED(); duk_hbuffer_refzero_norz(thr, h); } DUK_INTERNAL void duk_hobject_decref(duk_hthread *thr, duk_hobject *h) { DUK__DECREF_ASSERTS(); DUK__DECREF_SHARED(); duk_hobject_refzero(thr, h); } DUK_INTERNAL void duk_hobject_decref_norz(duk_hthread *thr, duk_hobject *h) { DUK__DECREF_ASSERTS(); DUK__DECREF_SHARED(); duk_hobject_refzero_norz(thr, h); } #endif #else /* DUK_USE_REFERENCE_COUNTING */ /* no refcounting */ #endif /* DUK_USE_REFERENCE_COUNTING */ /* automatic undefs */ #undef DUK__DECREF_ASSERTS #undef DUK__DECREF_SHARED #undef DUK__INCREF_SHARED #undef DUK__RZ_BUFFER #undef DUK__RZ_INLINE #undef DUK__RZ_OBJECT #undef DUK__RZ_STRING #undef DUK__RZ_SUPPRESS_ASSERT1 #undef DUK__RZ_SUPPRESS_ASSERT2 #undef DUK__RZ_SUPPRESS_CHECK #undef DUK__RZ_SUPPRESS_COND #line 1 "duk_heap_stringcache.c" /* * String cache. * * Provides a cache to optimize indexed string lookups. The cache keeps * track of (byte offset, char offset) states for a fixed number of strings. * Otherwise we'd need to scan from either end of the string, as we store * strings in (extended) UTF-8. */ /* #include duk_internal.h -> already included */ /* * Delete references to given hstring from the heap string cache. * * String cache references are 'weak': they are not counted towards * reference counts, nor serve as roots for mark-and-sweep. When an * object is about to be freed, such references need to be removed. */ DUK_INTERNAL void duk_heap_strcache_string_remove(duk_heap *heap, duk_hstring *h) { duk_uint_t i; for (i = 0; i < DUK_HEAP_STRCACHE_SIZE; i++) { duk_strcache_entry *c = heap->strcache + i; if (c->h == h) { DUK_DD( DUK_DDPRINT("deleting weak strcache reference to hstring %p from heap %p", (void *) h, (void *) heap)); c->h = NULL; /* XXX: the string shouldn't appear twice, but we now loop to the * end anyway; if fixed, add a looping assertion to ensure there * is no duplicate. */ } } } /* * String scanning helpers * * All bytes other than UTF-8 continuation bytes ([0x80,0xbf]) are * considered to contribute a character. This must match how string * character length is computed. */ DUK_LOCAL const duk_uint8_t *duk__scan_forwards(const duk_uint8_t *p, const duk_uint8_t *q, duk_uint_fast32_t n) { while (n > 0) { for (;;) { p++; if (p >= q) { return NULL; } if ((*p & 0xc0) != 0x80) { break; } } n--; } return p; } DUK_LOCAL const duk_uint8_t *duk__scan_backwards(const duk_uint8_t *p, const duk_uint8_t *q, duk_uint_fast32_t n) { while (n > 0) { for (;;) { p--; if (p < q) { return NULL; } if ((*p & 0xc0) != 0x80) { break; } } n--; } return p; } /* * Convert char offset to byte offset * * Avoid using the string cache if possible: for ASCII strings byte and * char offsets are equal and for short strings direct scanning may be * better than using the string cache (which may evict a more important * entry). * * Typing now assumes 32-bit string byte/char offsets (duk_uint_fast32_t). * Better typing might be to use duk_size_t. * * Caller should ensure 'char_offset' is within the string bounds [0,charlen] * (endpoint is inclusive). If this is not the case, no memory unsafe * behavior will happen but an error will be thrown. */ DUK_INTERNAL duk_uint_fast32_t duk_heap_strcache_offset_char2byte(duk_hthread *thr, duk_hstring *h, duk_uint_fast32_t char_offset) { duk_heap *heap; duk_strcache_entry *sce; duk_uint_fast32_t byte_offset; duk_uint_t i; duk_bool_t use_cache; duk_uint_fast32_t dist_start, dist_end, dist_sce; duk_uint_fast32_t char_length; const duk_uint8_t *p_start; const duk_uint8_t *p_end; const duk_uint8_t *p_found; /* * For ASCII strings, the answer is simple. */ if (DUK_LIKELY(DUK_HSTRING_IS_ASCII(h))) { return char_offset; } char_length = (duk_uint_fast32_t) DUK_HSTRING_GET_CHARLEN(h); DUK_ASSERT(char_offset <= char_length); if (DUK_LIKELY(DUK_HSTRING_IS_ASCII(h))) { /* Must recheck because the 'is ascii' flag may be set * lazily. Alternatively, we could just compare charlen * to bytelen. */ return char_offset; } /* * For non-ASCII strings, we need to scan forwards or backwards * from some starting point. The starting point may be the start * or end of the string, or some cached midpoint in the string * cache. * * For "short" strings we simply scan without checking or updating * the cache. For longer strings we check and update the cache as * necessary, inserting a new cache entry if none exists. */ DUK_DDD(DUK_DDDPRINT("non-ascii string %p, char_offset=%ld, clen=%ld, blen=%ld", (void *) h, (long) char_offset, (long) DUK_HSTRING_GET_CHARLEN(h), (long) DUK_HSTRING_GET_BYTELEN(h))); heap = thr->heap; sce = NULL; use_cache = (char_length > DUK_HEAP_STRINGCACHE_NOCACHE_LIMIT); if (use_cache) { #if defined(DUK_USE_DEBUG_LEVEL) && (DUK_USE_DEBUG_LEVEL >= 2) DUK_DDD(DUK_DDDPRINT("stringcache before char2byte (using cache):")); for (i = 0; i < DUK_HEAP_STRCACHE_SIZE; i++) { duk_strcache_entry *c = heap->strcache + i; DUK_DDD(DUK_DDDPRINT(" [%ld] -> h=%p, cidx=%ld, bidx=%ld", (long) i, (void *) c->h, (long) c->cidx, (long) c->bidx)); } #endif for (i = 0; i < DUK_HEAP_STRCACHE_SIZE; i++) { duk_strcache_entry *c = heap->strcache + i; if (c->h == h) { sce = c; break; } } } /* * Scan from shortest distance: * - start of string * - end of string * - cache entry (if exists) */ DUK_ASSERT(DUK_HSTRING_GET_CHARLEN(h) >= char_offset); dist_start = char_offset; dist_end = char_length - char_offset; dist_sce = 0; DUK_UNREF(dist_sce); /* initialize for debug prints, needed if sce==NULL */ p_start = (const duk_uint8_t *) DUK_HSTRING_GET_DATA(h); p_end = (const duk_uint8_t *) (p_start + DUK_HSTRING_GET_BYTELEN(h)); p_found = NULL; if (sce) { if (char_offset >= sce->cidx) { dist_sce = char_offset - sce->cidx; if ((dist_sce <= dist_start) && (dist_sce <= dist_end)) { DUK_DDD(DUK_DDDPRINT("non-ascii string, use_cache=%ld, sce=%p:%ld:%ld, " "dist_start=%ld, dist_end=%ld, dist_sce=%ld => " "scan forwards from sce", (long) use_cache, (void *) (sce ? sce->h : NULL), (sce ? (long) sce->cidx : (long) -1), (sce ? (long) sce->bidx : (long) -1), (long) dist_start, (long) dist_end, (long) dist_sce)); p_found = duk__scan_forwards(p_start + sce->bidx, p_end, dist_sce); goto scan_done; } } else { dist_sce = sce->cidx - char_offset; if ((dist_sce <= dist_start) && (dist_sce <= dist_end)) { DUK_DDD(DUK_DDDPRINT("non-ascii string, use_cache=%ld, sce=%p:%ld:%ld, " "dist_start=%ld, dist_end=%ld, dist_sce=%ld => " "scan backwards from sce", (long) use_cache, (void *) (sce ? sce->h : NULL), (sce ? (long) sce->cidx : (long) -1), (sce ? (long) sce->bidx : (long) -1), (long) dist_start, (long) dist_end, (long) dist_sce)); p_found = duk__scan_backwards(p_start + sce->bidx, p_start, dist_sce); goto scan_done; } } } /* no sce, or sce scan not best */ if (dist_start <= dist_end) { DUK_DDD(DUK_DDDPRINT("non-ascii string, use_cache=%ld, sce=%p:%ld:%ld, " "dist_start=%ld, dist_end=%ld, dist_sce=%ld => " "scan forwards from string start", (long) use_cache, (void *) (sce ? sce->h : NULL), (sce ? (long) sce->cidx : (long) -1), (sce ? (long) sce->bidx : (long) -1), (long) dist_start, (long) dist_end, (long) dist_sce)); p_found = duk__scan_forwards(p_start, p_end, dist_start); } else { DUK_DDD(DUK_DDDPRINT("non-ascii string, use_cache=%ld, sce=%p:%ld:%ld, " "dist_start=%ld, dist_end=%ld, dist_sce=%ld => " "scan backwards from string end", (long) use_cache, (void *) (sce ? sce->h : NULL), (sce ? (long) sce->cidx : (long) -1), (sce ? (long) sce->bidx : (long) -1), (long) dist_start, (long) dist_end, (long) dist_sce)); p_found = duk__scan_backwards(p_end, p_start, dist_end); } scan_done: if (DUK_UNLIKELY(p_found == NULL)) { /* Scan error: this shouldn't normally happen; it could happen if * string is not valid UTF-8 data, and clen/blen are not consistent * with the scanning algorithm. */ goto scan_error; } DUK_ASSERT(p_found >= p_start); DUK_ASSERT(p_found <= p_end); /* may be equal */ byte_offset = (duk_uint32_t) (p_found - p_start); DUK_DDD(DUK_DDDPRINT("-> string %p, cidx %ld -> bidx %ld", (void *) h, (long) char_offset, (long) byte_offset)); /* * Update cache entry (allocating if necessary), and move the * cache entry to the first place (in an "LRU" policy). */ if (use_cache) { /* update entry, allocating if necessary */ if (!sce) { sce = heap->strcache + DUK_HEAP_STRCACHE_SIZE - 1; /* take last entry */ sce->h = h; } DUK_ASSERT(sce != NULL); sce->bidx = (duk_uint32_t) (p_found - p_start); sce->cidx = (duk_uint32_t) char_offset; /* LRU: move our entry to first */ if (sce > &heap->strcache[0]) { /* * A C * B A * C <- sce ==> B * D D */ duk_strcache_entry tmp; tmp = *sce; duk_memmove((void *) (&heap->strcache[1]), (const void *) (&heap->strcache[0]), (size_t) (((char *) sce) - ((char *) &heap->strcache[0]))); heap->strcache[0] = tmp; /* 'sce' points to the wrong entry here, but is no longer used */ } #if defined(DUK_USE_DEBUG_LEVEL) && (DUK_USE_DEBUG_LEVEL >= 2) DUK_DDD(DUK_DDDPRINT("stringcache after char2byte (using cache):")); for (i = 0; i < DUK_HEAP_STRCACHE_SIZE; i++) { duk_strcache_entry *c = heap->strcache + i; DUK_DDD(DUK_DDDPRINT(" [%ld] -> h=%p, cidx=%ld, bidx=%ld", (long) i, (void *) c->h, (long) c->cidx, (long) c->bidx)); } #endif } return byte_offset; scan_error: DUK_ERROR_INTERNAL(thr); DUK_WO_NORETURN(return 0;); } #line 1 "duk_heap_stringtable.c" /* * Heap string table handling, string interning. */ /* #include duk_internal.h -> already included */ /* Resize checks not needed if minsize == maxsize, typical for low memory * targets. */ #define DUK__STRTAB_RESIZE_CHECK #if (DUK_USE_STRTAB_MINSIZE == DUK_USE_STRTAB_MAXSIZE) #undef DUK__STRTAB_RESIZE_CHECK #endif #if defined(DUK_USE_STRTAB_PTRCOMP) #define DUK__HEAPPTR_ENC16(heap, ptr) DUK_USE_HEAPPTR_ENC16((heap)->heap_udata, (ptr)) #define DUK__HEAPPTR_DEC16(heap, val) DUK_USE_HEAPPTR_DEC16((heap)->heap_udata, (val)) #define DUK__GET_STRTABLE(heap) ((heap)->strtable16) #else #define DUK__HEAPPTR_ENC16(heap, ptr) (ptr) #define DUK__HEAPPTR_DEC16(heap, val) (val) #define DUK__GET_STRTABLE(heap) ((heap)->strtable) #endif #define DUK__STRTAB_U32_MAX_STRLEN 10 /* 4'294'967'295 */ /* * Debug dump stringtable. */ #if defined(DUK_USE_DEBUG) DUK_INTERNAL void duk_heap_strtable_dump(duk_heap *heap) { #if defined(DUK_USE_STRTAB_PTRCOMP) duk_uint16_t *strtable; #else duk_hstring **strtable; #endif duk_uint32_t i; duk_hstring *h; duk_size_t count_total = 0; duk_size_t count_chain; duk_size_t count_chain_min = DUK_SIZE_MAX; duk_size_t count_chain_max = 0; duk_size_t count_len[8]; /* chain lengths from 0 to 7 */ if (heap == NULL) { DUK_D(DUK_DPRINT("string table, heap=NULL")); return; } strtable = DUK__GET_STRTABLE(heap); if (strtable == NULL) { DUK_D(DUK_DPRINT("string table, strtab=NULL")); return; } duk_memzero((void *) count_len, sizeof(count_len)); for (i = 0; i < heap->st_size; i++) { h = DUK__HEAPPTR_DEC16(heap, strtable[i]); count_chain = 0; while (h != NULL) { count_chain++; h = h->hdr.h_next; } if (count_chain < sizeof(count_len) / sizeof(duk_size_t)) { count_len[count_chain]++; } count_chain_max = (count_chain > count_chain_max ? count_chain : count_chain_max); count_chain_min = (count_chain < count_chain_min ? count_chain : count_chain_min); count_total += count_chain; } DUK_D(DUK_DPRINT("string table, strtab=%p, count=%lu, chain min=%lu max=%lu avg=%lf: " "counts: %lu %lu %lu %lu %lu %lu %lu %lu ...", (void *) heap->strtable, (unsigned long) count_total, (unsigned long) count_chain_min, (unsigned long) count_chain_max, (double) count_total / (double) heap->st_size, (unsigned long) count_len[0], (unsigned long) count_len[1], (unsigned long) count_len[2], (unsigned long) count_len[3], (unsigned long) count_len[4], (unsigned long) count_len[5], (unsigned long) count_len[6], (unsigned long) count_len[7])); } #endif /* DUK_USE_DEBUG */ /* * Assertion helper to ensure strtable is populated correctly. */ #if defined(DUK_USE_ASSERTIONS) DUK_LOCAL void duk__strtable_assert_checks(duk_heap *heap) { #if defined(DUK_USE_STRTAB_PTRCOMP) duk_uint16_t *strtable; #else duk_hstring **strtable; #endif duk_uint32_t i; duk_hstring *h; duk_size_t count = 0; DUK_ASSERT(heap != NULL); strtable = DUK__GET_STRTABLE(heap); if (strtable != NULL) { DUK_ASSERT(heap->st_size != 0); DUK_ASSERT(heap->st_mask == heap->st_size - 1); for (i = 0; i < heap->st_size; i++) { h = DUK__HEAPPTR_DEC16(heap, strtable[i]); while (h != NULL) { DUK_ASSERT((DUK_HSTRING_GET_HASH(h) & heap->st_mask) == i); count++; h = h->hdr.h_next; } } } else { DUK_ASSERT(heap->st_size == 0); DUK_ASSERT(heap->st_mask == 0); } #if defined(DUK__STRTAB_RESIZE_CHECK) DUK_ASSERT(count == (duk_size_t) heap->st_count); #endif } #endif /* DUK_USE_ASSERTIONS */ /* * Allocate and initialize a duk_hstring. * * Returns a NULL if allocation or initialization fails for some reason. * * The string won't be inserted into the string table and isn't tracked in * any way (link pointers will be NULL). The caller must place the string * into the string table without any risk of a longjmp, otherwise the string * is leaked. */ DUK_LOCAL duk_hstring *duk__strtable_alloc_hstring(duk_heap *heap, const duk_uint8_t *str, duk_uint32_t blen, duk_uint32_t strhash, const duk_uint8_t *extdata) { duk_hstring *res; const duk_uint8_t *data; #if !defined(DUK_USE_HSTRING_ARRIDX) duk_uarridx_t dummy; #endif DUK_ASSERT(heap != NULL); DUK_UNREF(extdata); #if defined(DUK_USE_STRLEN16) /* If blen <= 0xffffUL, clen is also guaranteed to be <= 0xffffUL. */ if (blen > 0xffffUL) { DUK_D(DUK_DPRINT("16-bit string blen/clen active and blen over 16 bits, reject intern")); goto alloc_error; } #endif /* XXX: Memzeroing the allocated structure is not really necessary * because we could just initialize all fields explicitly (almost * all fields are initialized explicitly anyway). */ #if defined(DUK_USE_HSTRING_EXTDATA) && defined(DUK_USE_EXTSTR_INTERN_CHECK) if (extdata) { res = (duk_hstring *) DUK_ALLOC(heap, sizeof(duk_hstring_external)); if (DUK_UNLIKELY(res == NULL)) { goto alloc_error; } duk_memzero(res, sizeof(duk_hstring_external)); #if defined(DUK_USE_EXPLICIT_NULL_INIT) DUK_HEAPHDR_STRING_INIT_NULLS(&res->hdr); #endif DUK_HEAPHDR_SET_TYPE_AND_FLAGS(&res->hdr, DUK_HTYPE_STRING, DUK_HSTRING_FLAG_EXTDATA); DUK_ASSERT(extdata[blen] == 0); /* Application responsibility. */ data = extdata; ((duk_hstring_external *) res)->extdata = extdata; } else #endif /* DUK_USE_HSTRING_EXTDATA && DUK_USE_EXTSTR_INTERN_CHECK */ { duk_uint8_t *data_tmp; /* NUL terminate for convenient C access */ DUK_ASSERT(sizeof(duk_hstring) + blen + 1 > blen); /* No wrap, limits ensure. */ res = (duk_hstring *) DUK_ALLOC(heap, sizeof(duk_hstring) + blen + 1); if (DUK_UNLIKELY(res == NULL)) { goto alloc_error; } duk_memzero(res, sizeof(duk_hstring)); #if defined(DUK_USE_EXPLICIT_NULL_INIT) DUK_HEAPHDR_STRING_INIT_NULLS(&res->hdr); #endif DUK_HEAPHDR_SET_TYPE_AND_FLAGS(&res->hdr, DUK_HTYPE_STRING, 0); data_tmp = (duk_uint8_t *) (res + 1); duk_memcpy(data_tmp, str, blen); data_tmp[blen] = (duk_uint8_t) 0; data = (const duk_uint8_t *) data_tmp; } DUK_HSTRING_SET_BYTELEN(res, blen); DUK_HSTRING_SET_HASH(res, strhash); DUK_ASSERT(!DUK_HSTRING_HAS_ARRIDX(res)); #if defined(DUK_USE_HSTRING_ARRIDX) res->arridx = duk_js_to_arrayindex_string(data, blen); if (res->arridx != DUK_HSTRING_NO_ARRAY_INDEX) { #else dummy = duk_js_to_arrayindex_string(data, blen); if (dummy != DUK_HSTRING_NO_ARRAY_INDEX) { #endif /* Array index strings cannot be symbol strings, * and they're always pure ASCII so blen == clen. */ DUK_HSTRING_SET_ARRIDX(res); DUK_HSTRING_SET_ASCII(res); DUK_ASSERT(duk_unicode_unvalidated_utf8_length(data, (duk_size_t) blen) == blen); } else { /* Because 'data' is NUL-terminated, we don't need a * blen > 0 check here. For NUL (0x00) the symbol * checks will be false. */ if (DUK_UNLIKELY(data[0] >= 0x80U)) { if (data[0] <= 0x81) { DUK_HSTRING_SET_SYMBOL(res); } else if (data[0] == 0x82U || data[0] == 0xffU) { DUK_HSTRING_SET_HIDDEN(res); DUK_HSTRING_SET_SYMBOL(res); } } /* Using an explicit 'ASCII' flag has larger footprint (one call site * only) but is quite useful for the case when there's no explicit * 'clen' in duk_hstring. * * The flag is set lazily for RAM strings. */ DUK_ASSERT(!DUK_HSTRING_HAS_ASCII(res)); #if defined(DUK_USE_HSTRING_LAZY_CLEN) /* Charlen initialized to 0, updated on-the-fly. */ #else duk_hstring_init_charlen(res); /* Also sets ASCII flag. */ #endif } DUK_DDD(DUK_DDDPRINT("interned string, hash=0x%08lx, blen=%ld, has_arridx=%ld, has_extdata=%ld", (unsigned long) DUK_HSTRING_GET_HASH(res), (long) DUK_HSTRING_GET_BYTELEN(res), (long) (DUK_HSTRING_HAS_ARRIDX(res) ? 1 : 0), (long) (DUK_HSTRING_HAS_EXTDATA(res) ? 1 : 0))); DUK_ASSERT(res != NULL); return res; alloc_error: return NULL; } /* * Grow strtable allocation in-place. */ #if defined(DUK__STRTAB_RESIZE_CHECK) DUK_LOCAL void duk__strtable_grow_inplace(duk_heap *heap) { duk_uint32_t new_st_size; duk_uint32_t old_st_size; duk_uint32_t i; duk_hstring *h; duk_hstring *next; duk_hstring *prev; #if defined(DUK_USE_STRTAB_PTRCOMP) duk_uint16_t *new_ptr; duk_uint16_t *new_ptr_high; #else duk_hstring **new_ptr; duk_hstring **new_ptr_high; #endif DUK_DD(DUK_DDPRINT("grow in-place: %lu -> %lu", (unsigned long) heap->st_size, (unsigned long) heap->st_size * 2)); DUK_ASSERT(heap != NULL); DUK_ASSERT(heap->st_resizing == 1); DUK_ASSERT(heap->st_size >= 2); DUK_ASSERT((heap->st_size & (heap->st_size - 1)) == 0); /* 2^N */ DUK_ASSERT(DUK__GET_STRTABLE(heap) != NULL); DUK_STATS_INC(heap, stats_strtab_resize_grow); new_st_size = heap->st_size << 1U; DUK_ASSERT(new_st_size > heap->st_size); /* No overflow. */ /* Reallocate the strtable first and then work in-place to rehash * strings. We don't need an indirect allocation here: even if GC * is triggered to satisfy the allocation, recursive strtable resize * is prevented by flags. This is also why we don't need to use * DUK_REALLOC_INDIRECT(). */ #if defined(DUK_USE_STRTAB_PTRCOMP) new_ptr = (duk_uint16_t *) DUK_REALLOC(heap, heap->strtable16, sizeof(duk_uint16_t) * new_st_size); #else new_ptr = (duk_hstring **) DUK_REALLOC(heap, heap->strtable, sizeof(duk_hstring *) * new_st_size); #endif if (DUK_UNLIKELY(new_ptr == NULL)) { /* If realloc fails we can continue normally: the string table * won't "fill up" although chains will gradually get longer. * When string insertions continue, we'll quite soon try again * with no special handling. */ DUK_D(DUK_DPRINT("string table grow failed, ignoring")); return; } #if defined(DUK_USE_STRTAB_PTRCOMP) heap->strtable16 = new_ptr; #else heap->strtable = new_ptr; #endif /* Rehash a single bucket into two separate ones. When we grow * by x2 the highest 'new' bit determines whether a string remains * in its old position (bit is 0) or goes to a new one (bit is 1). */ old_st_size = heap->st_size; new_ptr_high = new_ptr + old_st_size; for (i = 0; i < old_st_size; i++) { duk_hstring *new_root; duk_hstring *new_root_high; h = DUK__HEAPPTR_DEC16(heap, new_ptr[i]); new_root = h; new_root_high = NULL; prev = NULL; while (h != NULL) { duk_uint32_t mask; DUK_ASSERT((DUK_HSTRING_GET_HASH(h) & heap->st_mask) == i); next = h->hdr.h_next; /* Example: if previous size was 256, previous mask is 0xFF * and size is 0x100 which corresponds to the new bit that * comes into play. */ DUK_ASSERT(heap->st_mask == old_st_size - 1); mask = old_st_size; if (DUK_HSTRING_GET_HASH(h) & mask) { if (prev != NULL) { prev->hdr.h_next = h->hdr.h_next; } else { DUK_ASSERT(h == new_root); new_root = h->hdr.h_next; } h->hdr.h_next = new_root_high; new_root_high = h; } else { prev = h; } h = next; } new_ptr[i] = DUK__HEAPPTR_ENC16(heap, new_root); new_ptr_high[i] = DUK__HEAPPTR_ENC16(heap, new_root_high); } heap->st_size = new_st_size; heap->st_mask = new_st_size - 1; #if defined(DUK_USE_ASSERTIONS) duk__strtable_assert_checks(heap); #endif } #endif /* DUK__STRTAB_RESIZE_CHECK */ /* * Shrink strtable allocation in-place. */ #if defined(DUK__STRTAB_RESIZE_CHECK) DUK_LOCAL void duk__strtable_shrink_inplace(duk_heap *heap) { duk_uint32_t new_st_size; duk_uint32_t i; duk_hstring *h; duk_hstring *other; duk_hstring *root; #if defined(DUK_USE_STRTAB_PTRCOMP) duk_uint16_t *old_ptr; duk_uint16_t *old_ptr_high; duk_uint16_t *new_ptr; #else duk_hstring **old_ptr; duk_hstring **old_ptr_high; duk_hstring **new_ptr; #endif DUK_DD(DUK_DDPRINT("shrink in-place: %lu -> %lu", (unsigned long) heap->st_size, (unsigned long) heap->st_size / 2)); DUK_ASSERT(heap != NULL); DUK_ASSERT(heap->st_resizing == 1); DUK_ASSERT(heap->st_size >= 2); DUK_ASSERT((heap->st_size & (heap->st_size - 1)) == 0); /* 2^N */ DUK_ASSERT(DUK__GET_STRTABLE(heap) != NULL); DUK_STATS_INC(heap, stats_strtab_resize_shrink); new_st_size = heap->st_size >> 1U; /* Combine two buckets into a single one. When we shrink, one hash * bit (highest) disappears. */ old_ptr = DUK__GET_STRTABLE(heap); old_ptr_high = old_ptr + new_st_size; for (i = 0; i < new_st_size; i++) { h = DUK__HEAPPTR_DEC16(heap, old_ptr[i]); other = DUK__HEAPPTR_DEC16(heap, old_ptr_high[i]); if (h == NULL) { /* First chain is empty, so use second one as is. */ root = other; } else { /* Find end of first chain, and link in the second. */ root = h; while (h->hdr.h_next != NULL) { h = h->hdr.h_next; } h->hdr.h_next = other; } old_ptr[i] = DUK__HEAPPTR_ENC16(heap, root); } heap->st_size = new_st_size; heap->st_mask = new_st_size - 1; /* The strtable is now consistent and we can realloc safely. Even * if side effects cause string interning or removal the strtable * updates are safe. Recursive resize has been prevented by caller. * This is also why we don't need to use DUK_REALLOC_INDIRECT(). * * We assume a realloc() to a smaller size is guaranteed to succeed. * It would be relatively straightforward to handle the error by * essentially performing a "grow" step to recover. */ #if defined(DUK_USE_STRTAB_PTRCOMP) new_ptr = (duk_uint16_t *) DUK_REALLOC(heap, heap->strtable16, sizeof(duk_uint16_t) * new_st_size); DUK_ASSERT(new_ptr != NULL); heap->strtable16 = new_ptr; #else new_ptr = (duk_hstring **) DUK_REALLOC(heap, heap->strtable, sizeof(duk_hstring *) * new_st_size); DUK_ASSERT(new_ptr != NULL); heap->strtable = new_ptr; #endif #if defined(DUK_USE_ASSERTIONS) duk__strtable_assert_checks(heap); #endif } #endif /* DUK__STRTAB_RESIZE_CHECK */ /* * Grow/shrink check. */ #if defined(DUK__STRTAB_RESIZE_CHECK) DUK_LOCAL DUK_COLD DUK_NOINLINE void duk__strtable_resize_check(duk_heap *heap) { duk_uint32_t load_factor; /* fixed point */ DUK_ASSERT(heap != NULL); #if defined(DUK_USE_STRTAB_PTRCOMP) DUK_ASSERT(heap->strtable16 != NULL); #else DUK_ASSERT(heap->strtable != NULL); #endif DUK_STATS_INC(heap, stats_strtab_resize_check); /* Prevent recursive resizing. */ if (DUK_UNLIKELY(heap->st_resizing != 0U)) { DUK_D(DUK_DPRINT("prevent recursive strtable resize")); return; } heap->st_resizing = 1; DUK_ASSERT(heap->st_size >= 16U); DUK_ASSERT((heap->st_size >> 4U) >= 1); load_factor = heap->st_count / (heap->st_size >> 4U); DUK_DD(DUK_DDPRINT("resize check string table: size=%lu, count=%lu, load_factor=%lu (fixed point .4; float %lf)", (unsigned long) heap->st_size, (unsigned long) heap->st_count, (unsigned long) load_factor, (double) heap->st_count / (double) heap->st_size)); if (load_factor >= DUK_USE_STRTAB_GROW_LIMIT) { if (heap->st_size >= DUK_USE_STRTAB_MAXSIZE) { DUK_DD(DUK_DDPRINT("want to grow strtable (based on load factor) but already maximum size")); } else { DUK_D(DUK_DPRINT("grow string table: %lu -> %lu", (unsigned long) heap->st_size, (unsigned long) heap->st_size * 2)); #if defined(DUK_USE_DEBUG) duk_heap_strtable_dump(heap); #endif duk__strtable_grow_inplace(heap); } } else if (load_factor <= DUK_USE_STRTAB_SHRINK_LIMIT) { if (heap->st_size <= DUK_USE_STRTAB_MINSIZE) { DUK_DD(DUK_DDPRINT("want to shrink strtable (based on load factor) but already minimum size")); } else { DUK_D(DUK_DPRINT("shrink string table: %lu -> %lu", (unsigned long) heap->st_size, (unsigned long) heap->st_size / 2)); #if defined(DUK_USE_DEBUG) duk_heap_strtable_dump(heap); #endif duk__strtable_shrink_inplace(heap); } } else { DUK_DD(DUK_DDPRINT("no need for strtable resize")); } heap->st_resizing = 0; } #endif /* DUK__STRTAB_RESIZE_CHECK */ /* * Torture grow/shrink: unconditionally grow and shrink back. */ #if defined(DUK_USE_STRTAB_TORTURE) && defined(DUK__STRTAB_RESIZE_CHECK) DUK_LOCAL void duk__strtable_resize_torture(duk_heap *heap) { duk_uint32_t old_st_size; DUK_ASSERT(heap != NULL); old_st_size = heap->st_size; if (old_st_size >= DUK_USE_STRTAB_MAXSIZE) { return; } heap->st_resizing = 1; duk__strtable_grow_inplace(heap); if (heap->st_size > old_st_size) { duk__strtable_shrink_inplace(heap); } heap->st_resizing = 0; } #endif /* DUK_USE_STRTAB_TORTURE && DUK__STRTAB_RESIZE_CHECK */ /* * Raw intern; string already checked not to be present. */ DUK_LOCAL duk_hstring *duk__strtable_do_intern(duk_heap *heap, const duk_uint8_t *str, duk_uint32_t blen, duk_uint32_t strhash) { duk_hstring *res; const duk_uint8_t *extdata; #if defined(DUK_USE_STRTAB_PTRCOMP) duk_uint16_t *slot; #else duk_hstring **slot; #endif DUK_DDD(DUK_DDDPRINT("do_intern: heap=%p, str=%p, blen=%lu, strhash=%lx, st_size=%lu, st_count=%lu, load=%lf", (void *) heap, (const void *) str, (unsigned long) blen, (unsigned long) strhash, (unsigned long) heap->st_size, (unsigned long) heap->st_count, (double) heap->st_count / (double) heap->st_size)); DUK_ASSERT(heap != NULL); /* Prevent any side effects on the string table and the caller provided * str/blen arguments while interning is in progress. For example, if * the caller provided str/blen from a dynamic buffer, a finalizer * might resize or modify that dynamic buffer, invalidating the call * arguments. * * While finalizers must be prevented, mark-and-sweep itself is fine. * Recursive string table resize is prevented explicitly here. */ heap->pf_prevent_count++; DUK_ASSERT(heap->pf_prevent_count != 0); /* Wrap. */ #if defined(DUK_USE_STRTAB_TORTURE) && defined(DUK__STRTAB_RESIZE_CHECK) duk__strtable_resize_torture(heap); #endif /* String table grow/shrink check. Because of chaining (and no * accumulation issues as with hash probe chains and DELETED * markers) there's never a mandatory need to resize right now. * Check for the resize only periodically, based on st_count * bit pattern. Because string table removal doesn't do a shrink * check, we do that also here. * * Do the resize and possible grow/shrink before the new duk_hstring * has been allocated. Otherwise we may trigger a GC when the result * duk_hstring is not yet strongly referenced. */ #if defined(DUK__STRTAB_RESIZE_CHECK) if (DUK_UNLIKELY((heap->st_count & DUK_USE_STRTAB_RESIZE_CHECK_MASK) == 0)) { duk__strtable_resize_check(heap); } #endif /* External string check (low memory optimization). */ #if defined(DUK_USE_HSTRING_EXTDATA) && defined(DUK_USE_EXTSTR_INTERN_CHECK) extdata = (const duk_uint8_t *) DUK_USE_EXTSTR_INTERN_CHECK(heap->heap_udata, (void *) DUK_LOSE_CONST(str), (duk_size_t) blen); #else extdata = (const duk_uint8_t *) NULL; #endif /* Allocate and initialize string, not yet linked. This may cause a * GC which may cause other strings to be interned and inserted into * the string table before we insert our string. Finalizer execution * is disabled intentionally to avoid a finalizer from e.g. resizing * a buffer used as a data area for 'str'. */ res = duk__strtable_alloc_hstring(heap, str, blen, strhash, extdata); /* Allow side effects again: GC must be avoided until duk_hstring * result (if successful) has been INCREF'd. */ DUK_ASSERT(heap->pf_prevent_count > 0); heap->pf_prevent_count--; /* Alloc error handling. */ if (DUK_UNLIKELY(res == NULL)) { #if defined(DUK_USE_HSTRING_EXTDATA) && defined(DUK_USE_EXTSTR_INTERN_CHECK) if (extdata != NULL) { DUK_USE_EXTSTR_FREE(heap->heap_udata, (const void *) extdata); } #endif return NULL; } /* Insert into string table. */ #if defined(DUK_USE_STRTAB_PTRCOMP) slot = heap->strtable16 + (strhash & heap->st_mask); #else slot = heap->strtable + (strhash & heap->st_mask); #endif DUK_ASSERT(res->hdr.h_next == NULL); /* This is the case now, but unnecessary zeroing/NULLing. */ res->hdr.h_next = DUK__HEAPPTR_DEC16(heap, *slot); *slot = DUK__HEAPPTR_ENC16(heap, res); /* Update string count only for successful inserts. */ #if defined(DUK__STRTAB_RESIZE_CHECK) heap->st_count++; #endif /* The duk_hstring is in the string table but is not yet strongly * reachable. Calling code MUST NOT make any allocations or other * side effects before the duk_hstring has been INCREF'd and made * reachable. */ return res; } /* * Intern a string from str/blen, returning either an existing duk_hstring * or adding a new one into the string table. The input string does -not- * need to be NUL terminated. * * The input 'str' argument may point to a Duktape managed data area such as * the data area of a dynamic buffer. It's crucial to avoid any side effects * that might affect the data area (e.g. resize the dynamic buffer, or write * to the buffer) before the string is fully interned. */ #if defined(DUK_USE_ROM_STRINGS) DUK_LOCAL duk_hstring *duk__strtab_romstring_lookup(duk_heap *heap, const duk_uint8_t *str, duk_size_t blen, duk_uint32_t strhash) { duk_size_t lookup_hash; duk_hstring *curr; DUK_ASSERT(heap != NULL); DUK_UNREF(heap); lookup_hash = (blen << 4); if (blen > 0) { lookup_hash += str[0]; } lookup_hash &= 0xff; curr = (duk_hstring *) DUK_LOSE_CONST(duk_rom_strings_lookup[lookup_hash]); while (curr != NULL) { /* Unsafe memcmp() because for zero blen, str may be NULL. */ if (strhash == DUK_HSTRING_GET_HASH(curr) && blen == DUK_HSTRING_GET_BYTELEN(curr) && duk_memcmp_unsafe((const void *) str, (const void *) DUK_HSTRING_GET_DATA(curr), blen) == 0) { DUK_DDD(DUK_DDDPRINT("intern check: rom string: %!O, computed hash 0x%08lx, rom hash 0x%08lx", curr, (unsigned long) strhash, (unsigned long) DUK_HSTRING_GET_HASH(curr))); return curr; } curr = curr->hdr.h_next; } return NULL; } #endif /* DUK_USE_ROM_STRINGS */ DUK_INTERNAL duk_hstring *duk_heap_strtable_intern(duk_heap *heap, const duk_uint8_t *str, duk_uint32_t blen) { duk_uint32_t strhash; duk_hstring *h; DUK_DDD(DUK_DDDPRINT("intern check: heap=%p, str=%p, blen=%lu", (void *) heap, (const void *) str, (unsigned long) blen)); /* Preliminaries. */ /* XXX: maybe just require 'str != NULL' even for zero size? */ DUK_ASSERT(heap != NULL); DUK_ASSERT(blen == 0 || str != NULL); DUK_ASSERT(blen <= DUK_HSTRING_MAX_BYTELEN); /* Caller is responsible for ensuring this. */ strhash = duk_heap_hashstring(heap, str, (duk_size_t) blen); /* String table lookup. */ DUK_ASSERT(DUK__GET_STRTABLE(heap) != NULL); DUK_ASSERT(heap->st_size > 0); DUK_ASSERT(heap->st_size == heap->st_mask + 1); #if defined(DUK_USE_STRTAB_PTRCOMP) h = DUK__HEAPPTR_DEC16(heap, heap->strtable16[strhash & heap->st_mask]); #else h = heap->strtable[strhash & heap->st_mask]; #endif while (h != NULL) { if (DUK_HSTRING_GET_HASH(h) == strhash && DUK_HSTRING_GET_BYTELEN(h) == blen && duk_memcmp_unsafe((const void *) str, (const void *) DUK_HSTRING_GET_DATA(h), (size_t) blen) == 0) { /* Found existing entry. */ DUK_STATS_INC(heap, stats_strtab_intern_hit); return h; } h = h->hdr.h_next; } /* ROM table lookup. Because this lookup is slower, do it only after * RAM lookup. This works because no ROM string is ever interned into * the RAM string table. */ #if defined(DUK_USE_ROM_STRINGS) h = duk__strtab_romstring_lookup(heap, str, blen, strhash); if (h != NULL) { DUK_STATS_INC(heap, stats_strtab_intern_hit); return h; } #endif /* Not found in string table; insert. */ DUK_STATS_INC(heap, stats_strtab_intern_miss); h = duk__strtable_do_intern(heap, str, blen, strhash); return h; /* may be NULL */ } /* * Intern a string from u32. */ /* XXX: Could arrange some special handling because we know that the result * will have an arridx flag and an ASCII flag, won't need a clen check, etc. */ DUK_INTERNAL duk_hstring *duk_heap_strtable_intern_u32(duk_heap *heap, duk_uint32_t val) { duk_uint8_t buf[DUK__STRTAB_U32_MAX_STRLEN]; duk_uint8_t *p; DUK_ASSERT(heap != NULL); /* This is smaller and faster than a %lu sprintf. */ p = buf + sizeof(buf); do { p--; *p = duk_lc_digits[val % 10]; val = val / 10; } while (val != 0); /* For val == 0, emit exactly one '0'. */ DUK_ASSERT(p >= buf); return duk_heap_strtable_intern(heap, (const duk_uint8_t *) p, (duk_uint32_t) ((buf + sizeof(buf)) - p)); } /* * Checked convenience variants. * * XXX: Because the main use case is for the checked variants, make them the * main functionality and provide a safe variant separately (it is only needed * during heap init). The problem with that is that longjmp state and error * creation must already be possible to throw. */ DUK_INTERNAL duk_hstring *duk_heap_strtable_intern_checked(duk_hthread *thr, const duk_uint8_t *str, duk_uint32_t blen) { duk_hstring *res; DUK_ASSERT(thr != NULL); DUK_ASSERT(thr->heap != NULL); DUK_ASSERT(blen == 0 || str != NULL); res = duk_heap_strtable_intern(thr->heap, str, blen); if (DUK_UNLIKELY(res == NULL)) { DUK_ERROR_ALLOC_FAILED(thr); DUK_WO_NORETURN(return NULL;); } return res; } #if defined(DUK_USE_LITCACHE_SIZE) DUK_LOCAL duk_uint_t duk__strtable_litcache_key(const duk_uint8_t *str, duk_uint32_t blen) { duk_uintptr_t key; DUK_ASSERT(DUK_USE_LITCACHE_SIZE > 0); DUK_ASSERT(DUK_IS_POWER_OF_TWO((duk_uint_t) DUK_USE_LITCACHE_SIZE)); key = (duk_uintptr_t) blen ^ (duk_uintptr_t) str; key &= (duk_uintptr_t) (DUK_USE_LITCACHE_SIZE - 1); /* Assumes size is power of 2. */ /* Due to masking, cast is in 32-bit range. */ DUK_ASSERT(key <= DUK_UINT_MAX); return (duk_uint_t) key; } DUK_INTERNAL duk_hstring *duk_heap_strtable_intern_literal_checked(duk_hthread *thr, const duk_uint8_t *str, duk_uint32_t blen) { duk_uint_t key; duk_litcache_entry *ent; duk_hstring *h; /* Fast path check: literal exists in literal cache. */ key = duk__strtable_litcache_key(str, blen); ent = thr->heap->litcache + key; if (ent->addr == str) { DUK_DD(DUK_DDPRINT("intern check for cached, pinned literal: str=%p, blen=%ld -> duk_hstring %!O", (const void *) str, (long) blen, (duk_heaphdr *) ent->h)); DUK_ASSERT(ent->h != NULL); DUK_ASSERT(DUK_HSTRING_HAS_PINNED_LITERAL(ent->h)); DUK_STATS_INC(thr->heap, stats_strtab_litcache_hit); return ent->h; } /* Intern and update (overwrite) cache entry. */ h = duk_heap_strtable_intern_checked(thr, str, blen); ent->addr = str; ent->h = h; DUK_STATS_INC(thr->heap, stats_strtab_litcache_miss); /* Pin the duk_hstring until the next mark-and-sweep. This means * litcache entries don't need to be invalidated until the next * mark-and-sweep as their target duk_hstring is not freed before * the mark-and-sweep happens. The pin remains even if the literal * cache entry is overwritten, and is still useful to avoid string * table traffic. */ if (!DUK_HSTRING_HAS_PINNED_LITERAL(h)) { DUK_DD(DUK_DDPRINT("pin duk_hstring because it is a literal: %!O", (duk_heaphdr *) h)); DUK_ASSERT(!DUK_HEAPHDR_HAS_READONLY((duk_heaphdr *) h)); DUK_HSTRING_INCREF(thr, h); DUK_HSTRING_SET_PINNED_LITERAL(h); DUK_STATS_INC(thr->heap, stats_strtab_litcache_pin); } return h; } #endif /* DUK_USE_LITCACHE_SIZE */ DUK_INTERNAL duk_hstring *duk_heap_strtable_intern_u32_checked(duk_hthread *thr, duk_uint32_t val) { duk_hstring *res; DUK_ASSERT(thr != NULL); DUK_ASSERT(thr->heap != NULL); res = duk_heap_strtable_intern_u32(thr->heap, val); if (DUK_UNLIKELY(res == NULL)) { DUK_ERROR_ALLOC_FAILED(thr); DUK_WO_NORETURN(return NULL;); } return res; } /* * Remove (unlink) a string from the string table. * * Just unlinks the duk_hstring, leaving link pointers as garbage. * Caller must free the string itself. */ #if defined(DUK_USE_REFERENCE_COUNTING) /* Unlink without a 'prev' pointer. */ DUK_INTERNAL void duk_heap_strtable_unlink(duk_heap *heap, duk_hstring *h) { #if defined(DUK_USE_STRTAB_PTRCOMP) duk_uint16_t *slot; #else duk_hstring **slot; #endif duk_hstring *other; duk_hstring *prev; DUK_DDD(DUK_DDDPRINT("remove: heap=%p, h=%p, blen=%lu, strhash=%lx", (void *) heap, (void *) h, (unsigned long) (h != NULL ? DUK_HSTRING_GET_BYTELEN(h) : 0), (unsigned long) (h != NULL ? DUK_HSTRING_GET_HASH(h) : 0))); DUK_ASSERT(heap != NULL); DUK_ASSERT(h != NULL); #if defined(DUK__STRTAB_RESIZE_CHECK) DUK_ASSERT(heap->st_count > 0); heap->st_count--; #endif #if defined(DUK_USE_STRTAB_PTRCOMP) slot = heap->strtable16 + (DUK_HSTRING_GET_HASH(h) & heap->st_mask); #else slot = heap->strtable + (DUK_HSTRING_GET_HASH(h) & heap->st_mask); #endif other = DUK__HEAPPTR_DEC16(heap, *slot); DUK_ASSERT(other != NULL); /* At least argument string is in the chain. */ prev = NULL; while (other != h) { prev = other; other = other->hdr.h_next; DUK_ASSERT(other != NULL); /* We'll eventually find 'h'. */ } if (prev != NULL) { /* Middle of list. */ prev->hdr.h_next = h->hdr.h_next; } else { /* Head of list. */ *slot = DUK__HEAPPTR_ENC16(heap, h->hdr.h_next); } /* There's no resize check on a string free. The next string * intern will do one. */ } #endif /* DUK_USE_REFERENCE_COUNTING */ /* Unlink with a 'prev' pointer. */ DUK_INTERNAL void duk_heap_strtable_unlink_prev(duk_heap *heap, duk_hstring *h, duk_hstring *prev) { #if defined(DUK_USE_STRTAB_PTRCOMP) duk_uint16_t *slot; #else duk_hstring **slot; #endif DUK_DDD(DUK_DDDPRINT("remove: heap=%p, prev=%p, h=%p, blen=%lu, strhash=%lx", (void *) heap, (void *) prev, (void *) h, (unsigned long) (h != NULL ? DUK_HSTRING_GET_BYTELEN(h) : 0), (unsigned long) (h != NULL ? DUK_HSTRING_GET_HASH(h) : 0))); DUK_ASSERT(heap != NULL); DUK_ASSERT(h != NULL); DUK_ASSERT(prev == NULL || prev->hdr.h_next == h); #if defined(DUK__STRTAB_RESIZE_CHECK) DUK_ASSERT(heap->st_count > 0); heap->st_count--; #endif if (prev != NULL) { /* Middle of list. */ prev->hdr.h_next = h->hdr.h_next; } else { /* Head of list. */ #if defined(DUK_USE_STRTAB_PTRCOMP) slot = heap->strtable16 + (DUK_HSTRING_GET_HASH(h) & heap->st_mask); #else slot = heap->strtable + (DUK_HSTRING_GET_HASH(h) & heap->st_mask); #endif DUK_ASSERT(DUK__HEAPPTR_DEC16(heap, *slot) == h); *slot = DUK__HEAPPTR_ENC16(heap, h->hdr.h_next); } } /* * Force string table resize check in mark-and-sweep. */ DUK_INTERNAL void duk_heap_strtable_force_resize(duk_heap *heap) { /* Does only one grow/shrink step if needed. The heap->st_resizing * flag protects against recursive resizing. */ DUK_ASSERT(heap != NULL); DUK_UNREF(heap); #if defined(DUK__STRTAB_RESIZE_CHECK) #if defined(DUK_USE_STRTAB_PTRCOMP) if (heap->strtable16 != NULL) { #else if (heap->strtable != NULL) { #endif duk__strtable_resize_check(heap); } #endif } /* * Free strings in the string table and the string table itself. */ DUK_INTERNAL void duk_heap_strtable_free(duk_heap *heap) { #if defined(DUK_USE_STRTAB_PTRCOMP) duk_uint16_t *strtable; duk_uint16_t *st; #else duk_hstring **strtable; duk_hstring **st; #endif duk_hstring *h; DUK_ASSERT(heap != NULL); #if defined(DUK_USE_ASSERTIONS) duk__strtable_assert_checks(heap); #endif /* Strtable can be NULL if heap init fails. However, in that case * heap->st_size is 0, so strtable == strtable_end and we skip the * loop without a special check. */ strtable = DUK__GET_STRTABLE(heap); st = strtable + heap->st_size; DUK_ASSERT(strtable != NULL || heap->st_size == 0); while (strtable != st) { --st; h = DUK__HEAPPTR_DEC16(heap, *st); while (h) { duk_hstring *h_next; h_next = h->hdr.h_next; /* Strings may have inner refs (extdata) in some cases. */ duk_free_hstring(heap, h); h = h_next; } } DUK_FREE(heap, strtable); } /* automatic undefs */ #undef DUK__GET_STRTABLE #undef DUK__HEAPPTR_DEC16 #undef DUK__HEAPPTR_ENC16 #undef DUK__STRTAB_U32_MAX_STRLEN #line 1 "duk_heaphdr_assert.c" /* * duk_heaphdr assertion helpers */ /* #include duk_internal.h -> already included */ #if defined(DUK_USE_ASSERTIONS) #if defined(DUK_USE_DOUBLE_LINKED_HEAP) DUK_INTERNAL void duk_heaphdr_assert_links(duk_heap *heap, duk_heaphdr *h) { DUK_UNREF(heap); if (h != NULL) { duk_heaphdr *h_prev, *h_next; h_prev = DUK_HEAPHDR_GET_PREV(heap, h); h_next = DUK_HEAPHDR_GET_NEXT(heap, h); DUK_ASSERT(h_prev == NULL || (DUK_HEAPHDR_GET_NEXT(heap, h_prev) == h)); DUK_ASSERT(h_next == NULL || (DUK_HEAPHDR_GET_PREV(heap, h_next) == h)); } } #else DUK_INTERNAL void duk_heaphdr_assert_links(duk_heap *heap, duk_heaphdr *h) { DUK_UNREF(heap); DUK_UNREF(h); } #endif DUK_INTERNAL void duk_heaphdr_assert_valid(duk_heaphdr *h) { DUK_ASSERT(h != NULL); DUK_ASSERT(DUK_HEAPHDR_HTYPE_VALID(h)); } /* Assert validity of a heaphdr, including all subclasses. */ DUK_INTERNAL void duk_heaphdr_assert_valid_subclassed(duk_heaphdr *h) { switch (DUK_HEAPHDR_GET_TYPE(h)) { case DUK_HTYPE_OBJECT: { duk_hobject *h_obj = (duk_hobject *) h; DUK_HOBJECT_ASSERT_VALID(h_obj); if (DUK_HOBJECT_IS_COMPFUNC(h_obj)) { DUK_HCOMPFUNC_ASSERT_VALID((duk_hcompfunc *) h_obj); } else if (DUK_HOBJECT_IS_NATFUNC(h_obj)) { DUK_HNATFUNC_ASSERT_VALID((duk_hnatfunc *) h_obj); } else if (DUK_HOBJECT_IS_DECENV(h_obj)) { DUK_HDECENV_ASSERT_VALID((duk_hdecenv *) h_obj); } else if (DUK_HOBJECT_IS_OBJENV(h_obj)) { DUK_HOBJENV_ASSERT_VALID((duk_hobjenv *) h_obj); } else if (DUK_HOBJECT_IS_BUFOBJ(h_obj)) { #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) DUK_HBUFOBJ_ASSERT_VALID((duk_hbufobj *) h_obj); #endif } else if (DUK_HOBJECT_IS_BOUNDFUNC(h_obj)) { DUK_HBOUNDFUNC_ASSERT_VALID((duk_hboundfunc *) h_obj); } else if (DUK_HOBJECT_IS_PROXY(h_obj)) { DUK_HPROXY_ASSERT_VALID((duk_hproxy *) h_obj); } else if (DUK_HOBJECT_IS_THREAD(h_obj)) { DUK_HTHREAD_ASSERT_VALID((duk_hthread *) h_obj); } else { /* Just a plain object. */ ; } break; } case DUK_HTYPE_STRING: { duk_hstring *h_str = (duk_hstring *) h; DUK_HSTRING_ASSERT_VALID(h_str); break; } case DUK_HTYPE_BUFFER: { duk_hbuffer *h_buf = (duk_hbuffer *) h; DUK_HBUFFER_ASSERT_VALID(h_buf); break; } default: { DUK_ASSERT(0); } } } #endif /* DUK_USE_ASSERTIONS */ #line 1 "duk_hobject_alloc.c" /* * Hobject allocation. * * Provides primitive allocation functions for all object types (plain object, * compiled function, native function, thread). The object return is not yet * in "heap allocated" list and has a refcount of zero, so caller must careful. */ /* XXX: In most cases there's no need for plain allocation without pushing * to the value stack. Maybe rework contract? */ /* #include duk_internal.h -> already included */ /* * Helpers. */ DUK_LOCAL void duk__init_object_parts(duk_heap *heap, duk_uint_t hobject_flags, duk_hobject *obj) { DUK_ASSERT(obj != NULL); /* Zeroed by caller. */ obj->hdr.h_flags = hobject_flags | DUK_HTYPE_OBJECT; DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(&obj->hdr) == DUK_HTYPE_OBJECT); /* Assume zero shift. */ #if defined(DUK_USE_EXPLICIT_NULL_INIT) DUK_HOBJECT_SET_PROTOTYPE(heap, obj, NULL); DUK_HOBJECT_SET_PROPS(heap, obj, NULL); #endif #if defined(DUK_USE_HEAPPTR16) /* Zero encoded pointer is required to match NULL. */ DUK_HEAPHDR_SET_NEXT(heap, &obj->hdr, NULL); #if defined(DUK_USE_DOUBLE_LINKED_HEAP) DUK_HEAPHDR_SET_PREV(heap, &obj->hdr, NULL); #endif #endif DUK_HEAPHDR_ASSERT_LINKS(heap, &obj->hdr); DUK_HEAP_INSERT_INTO_HEAP_ALLOCATED(heap, &obj->hdr); /* obj->props is intentionally left as NULL, and duk_hobject_props.c must deal * with this properly. This is intentional: empty objects consume a minimum * amount of memory. Further, an initial allocation might fail and cause * 'obj' to "leak" (require a mark-and-sweep) since it is not reachable yet. */ } DUK_LOCAL void *duk__hobject_alloc_init(duk_hthread *thr, duk_uint_t hobject_flags, duk_size_t size) { void *res; res = (void *) DUK_ALLOC_CHECKED_ZEROED(thr, size); DUK_ASSERT(res != NULL); duk__init_object_parts(thr->heap, hobject_flags, (duk_hobject *) res); return res; } /* * Allocate an duk_hobject. * * The allocated object has no allocation for properties; the caller may * want to force a resize if a desired size is known. * * The allocated object has zero reference count and is not reachable. * The caller MUST make the object reachable and increase its reference * count before invoking any operation that might require memory allocation. */ DUK_INTERNAL duk_hobject *duk_hobject_alloc_unchecked(duk_heap *heap, duk_uint_t hobject_flags) { duk_hobject *res; DUK_ASSERT(heap != NULL); /* different memory layout, alloc size, and init */ DUK_ASSERT((hobject_flags & DUK_HOBJECT_FLAG_COMPFUNC) == 0); DUK_ASSERT((hobject_flags & DUK_HOBJECT_FLAG_NATFUNC) == 0); DUK_ASSERT((hobject_flags & DUK_HOBJECT_FLAG_BOUNDFUNC) == 0); res = (duk_hobject *) DUK_ALLOC_ZEROED(heap, sizeof(duk_hobject)); if (DUK_UNLIKELY(res == NULL)) { return NULL; } DUK_ASSERT(!DUK_HOBJECT_IS_THREAD(res)); duk__init_object_parts(heap, hobject_flags, res); DUK_ASSERT(!DUK_HOBJECT_IS_THREAD(res)); return res; } DUK_INTERNAL duk_hobject *duk_hobject_alloc(duk_hthread *thr, duk_uint_t hobject_flags) { duk_hobject *res; res = (duk_hobject *) duk__hobject_alloc_init(thr, hobject_flags, sizeof(duk_hobject)); return res; } DUK_INTERNAL duk_hcompfunc *duk_hcompfunc_alloc(duk_hthread *thr, duk_uint_t hobject_flags) { duk_hcompfunc *res; res = (duk_hcompfunc *) duk__hobject_alloc_init(thr, hobject_flags, sizeof(duk_hcompfunc)); #if defined(DUK_USE_EXPLICIT_NULL_INIT) #if defined(DUK_USE_HEAPPTR16) /* NULL pointer is required to encode to zero, so memset is enough. */ #else res->data = NULL; res->funcs = NULL; res->bytecode = NULL; #endif res->lex_env = NULL; res->var_env = NULL; #endif return res; } DUK_INTERNAL duk_hnatfunc *duk_hnatfunc_alloc(duk_hthread *thr, duk_uint_t hobject_flags) { duk_hnatfunc *res; res = (duk_hnatfunc *) duk__hobject_alloc_init(thr, hobject_flags, sizeof(duk_hnatfunc)); #if defined(DUK_USE_EXPLICIT_NULL_INIT) res->func = NULL; #endif return res; } DUK_INTERNAL duk_hboundfunc *duk_hboundfunc_alloc(duk_heap *heap, duk_uint_t hobject_flags) { duk_hboundfunc *res; res = (duk_hboundfunc *) DUK_ALLOC(heap, sizeof(duk_hboundfunc)); if (!res) { return NULL; } duk_memzero(res, sizeof(duk_hboundfunc)); duk__init_object_parts(heap, hobject_flags, &res->obj); DUK_TVAL_SET_UNDEFINED(&res->target); DUK_TVAL_SET_UNDEFINED(&res->this_binding); #if defined(DUK_USE_EXPLICIT_NULL_INIT) res->args = NULL; #endif return res; } #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) DUK_INTERNAL duk_hbufobj *duk_hbufobj_alloc(duk_hthread *thr, duk_uint_t hobject_flags) { duk_hbufobj *res; res = (duk_hbufobj *) duk__hobject_alloc_init(thr, hobject_flags, sizeof(duk_hbufobj)); #if defined(DUK_USE_EXPLICIT_NULL_INIT) res->buf = NULL; res->buf_prop = NULL; #endif DUK_HBUFOBJ_ASSERT_VALID(res); return res; } #endif /* DUK_USE_BUFFEROBJECT_SUPPORT */ /* Allocate a new thread. * * Leaves the built-ins array uninitialized. The caller must either * initialize a new global context or share existing built-ins from * another thread. */ DUK_INTERNAL duk_hthread *duk_hthread_alloc_unchecked(duk_heap *heap, duk_uint_t hobject_flags) { duk_hthread *res; res = (duk_hthread *) DUK_ALLOC(heap, sizeof(duk_hthread)); if (DUK_UNLIKELY(res == NULL)) { return NULL; } duk_memzero(res, sizeof(duk_hthread)); duk__init_object_parts(heap, hobject_flags, &res->obj); #if defined(DUK_USE_EXPLICIT_NULL_INIT) res->ptr_curr_pc = NULL; res->heap = NULL; res->valstack = NULL; res->valstack_end = NULL; res->valstack_alloc_end = NULL; res->valstack_bottom = NULL; res->valstack_top = NULL; res->callstack_curr = NULL; res->resumer = NULL; res->compile_ctx = NULL, #if defined(DUK_USE_HEAPPTR16) res->strs16 = NULL; #else res->strs = NULL; #endif { duk_small_uint_t i; for (i = 0; i < DUK_NUM_BUILTINS; i++) { res->builtins[i] = NULL; } } #endif /* When nothing is running, API calls are in non-strict mode. */ DUK_ASSERT(res->strict == 0); res->heap = heap; /* XXX: Any reason not to merge duk_hthread_alloc.c here? */ return res; } DUK_INTERNAL duk_hthread *duk_hthread_alloc(duk_hthread *thr, duk_uint_t hobject_flags) { duk_hthread *res; res = duk_hthread_alloc_unchecked(thr->heap, hobject_flags); if (res == NULL) { DUK_ERROR_ALLOC_FAILED(thr); DUK_WO_NORETURN(return NULL;); } return res; } DUK_INTERNAL duk_harray *duk_harray_alloc(duk_hthread *thr, duk_uint_t hobject_flags) { duk_harray *res; res = (duk_harray *) duk__hobject_alloc_init(thr, hobject_flags, sizeof(duk_harray)); DUK_ASSERT(res->length == 0); return res; } DUK_INTERNAL duk_hdecenv *duk_hdecenv_alloc(duk_hthread *thr, duk_uint_t hobject_flags) { duk_hdecenv *res; res = (duk_hdecenv *) duk__hobject_alloc_init(thr, hobject_flags, sizeof(duk_hdecenv)); #if defined(DUK_USE_EXPLICIT_NULL_INIT) res->thread = NULL; res->varmap = NULL; #endif DUK_ASSERT(res->thread == NULL); DUK_ASSERT(res->varmap == NULL); DUK_ASSERT(res->regbase_byteoff == 0); return res; } DUK_INTERNAL duk_hobjenv *duk_hobjenv_alloc(duk_hthread *thr, duk_uint_t hobject_flags) { duk_hobjenv *res; res = (duk_hobjenv *) duk__hobject_alloc_init(thr, hobject_flags, sizeof(duk_hobjenv)); #if defined(DUK_USE_EXPLICIT_NULL_INIT) res->target = NULL; #endif DUK_ASSERT(res->target == NULL); return res; } DUK_INTERNAL duk_hproxy *duk_hproxy_alloc(duk_hthread *thr, duk_uint_t hobject_flags) { duk_hproxy *res; res = (duk_hproxy *) duk__hobject_alloc_init(thr, hobject_flags, sizeof(duk_hproxy)); /* Leave ->target and ->handler uninitialized, as caller will always * explicitly initialize them before any side effects are possible. */ return res; } #line 1 "duk_hobject_assert.c" /* * duk_hobject and subclass assertion helpers */ /* #include duk_internal.h -> already included */ #if defined(DUK_USE_ASSERTIONS) DUK_INTERNAL void duk_hobject_assert_valid(duk_hobject *h) { DUK_ASSERT(h != NULL); DUK_ASSERT(!DUK_HOBJECT_IS_CALLABLE(h) || DUK_HOBJECT_GET_CLASS_NUMBER(h) == DUK_HOBJECT_CLASS_FUNCTION); DUK_ASSERT(!DUK_HOBJECT_IS_BUFOBJ(h) || (DUK_HOBJECT_GET_CLASS_NUMBER(h) == DUK_HOBJECT_CLASS_ARRAYBUFFER || DUK_HOBJECT_GET_CLASS_NUMBER(h) == DUK_HOBJECT_CLASS_DATAVIEW || DUK_HOBJECT_GET_CLASS_NUMBER(h) == DUK_HOBJECT_CLASS_INT8ARRAY || DUK_HOBJECT_GET_CLASS_NUMBER(h) == DUK_HOBJECT_CLASS_UINT8ARRAY || DUK_HOBJECT_GET_CLASS_NUMBER(h) == DUK_HOBJECT_CLASS_UINT8CLAMPEDARRAY || DUK_HOBJECT_GET_CLASS_NUMBER(h) == DUK_HOBJECT_CLASS_INT16ARRAY || DUK_HOBJECT_GET_CLASS_NUMBER(h) == DUK_HOBJECT_CLASS_UINT16ARRAY || DUK_HOBJECT_GET_CLASS_NUMBER(h) == DUK_HOBJECT_CLASS_INT32ARRAY || DUK_HOBJECT_GET_CLASS_NUMBER(h) == DUK_HOBJECT_CLASS_UINT32ARRAY || DUK_HOBJECT_GET_CLASS_NUMBER(h) == DUK_HOBJECT_CLASS_FLOAT32ARRAY || DUK_HOBJECT_GET_CLASS_NUMBER(h) == DUK_HOBJECT_CLASS_FLOAT64ARRAY)); /* Object is an Array <=> object has exotic array behavior */ DUK_ASSERT((DUK_HOBJECT_GET_CLASS_NUMBER(h) == DUK_HOBJECT_CLASS_ARRAY && DUK_HOBJECT_HAS_EXOTIC_ARRAY(h)) || (DUK_HOBJECT_GET_CLASS_NUMBER(h) != DUK_HOBJECT_CLASS_ARRAY && !DUK_HOBJECT_HAS_EXOTIC_ARRAY(h))); } DUK_INTERNAL void duk_harray_assert_valid(duk_harray *h) { DUK_ASSERT(h != NULL); DUK_ASSERT(DUK_HOBJECT_IS_ARRAY((duk_hobject *) h)); DUK_ASSERT(DUK_HOBJECT_HAS_EXOTIC_ARRAY((duk_hobject *) h)); } DUK_INTERNAL void duk_hboundfunc_assert_valid(duk_hboundfunc *h) { DUK_ASSERT(h != NULL); DUK_ASSERT(DUK_HOBJECT_IS_BOUNDFUNC((duk_hobject *) h)); DUK_ASSERT(DUK_TVAL_IS_LIGHTFUNC(&h->target) || (DUK_TVAL_IS_OBJECT(&h->target) && DUK_HOBJECT_IS_CALLABLE(DUK_TVAL_GET_OBJECT(&h->target)))); DUK_ASSERT(!DUK_TVAL_IS_UNUSED(&h->this_binding)); DUK_ASSERT(h->nargs == 0 || h->args != NULL); } #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) DUK_INTERNAL void duk_hbufobj_assert_valid(duk_hbufobj *h) { DUK_ASSERT(h != NULL); DUK_ASSERT(h->shift <= 3); DUK_ASSERT(h->elem_type <= DUK_HBUFOBJ_ELEM_MAX); DUK_ASSERT((h->shift == 0 && h->elem_type == DUK_HBUFOBJ_ELEM_UINT8) || (h->shift == 0 && h->elem_type == DUK_HBUFOBJ_ELEM_UINT8CLAMPED) || (h->shift == 0 && h->elem_type == DUK_HBUFOBJ_ELEM_INT8) || (h->shift == 1 && h->elem_type == DUK_HBUFOBJ_ELEM_UINT16) || (h->shift == 1 && h->elem_type == DUK_HBUFOBJ_ELEM_INT16) || (h->shift == 2 && h->elem_type == DUK_HBUFOBJ_ELEM_UINT32) || (h->shift == 2 && h->elem_type == DUK_HBUFOBJ_ELEM_INT32) || (h->shift == 2 && h->elem_type == DUK_HBUFOBJ_ELEM_FLOAT32) || (h->shift == 3 && h->elem_type == DUK_HBUFOBJ_ELEM_FLOAT64)); DUK_ASSERT(h->is_typedarray == 0 || h->is_typedarray == 1); DUK_ASSERT(DUK_HOBJECT_IS_BUFOBJ((duk_hobject *) h)); if (h->buf == NULL) { DUK_ASSERT(h->offset == 0); DUK_ASSERT(h->length == 0); } else { /* No assertions for offset or length; in particular, * it's OK for length to be longer than underlying * buffer. Just ensure they don't wrap when added. */ DUK_ASSERT(h->offset + h->length >= h->offset); } } #endif /* DUK_USE_BUFFEROBJECT_SUPPORT */ DUK_INTERNAL void duk_hcompfunc_assert_valid(duk_hcompfunc *h) { DUK_ASSERT(h != NULL); } DUK_INTERNAL void duk_hnatfunc_assert_valid(duk_hnatfunc *h) { DUK_ASSERT(h != NULL); } DUK_INTERNAL void duk_hdecenv_assert_valid(duk_hdecenv *h) { DUK_ASSERT(h != NULL); DUK_ASSERT(DUK_HOBJECT_IS_DECENV((duk_hobject *) h)); DUK_ASSERT(h->thread == NULL || h->varmap != NULL); } DUK_INTERNAL void duk_hobjenv_assert_valid(duk_hobjenv *h) { DUK_ASSERT(h != NULL); DUK_ASSERT(DUK_HOBJECT_IS_OBJENV((duk_hobject *) h)); DUK_ASSERT(h->target != NULL); DUK_ASSERT(h->has_this == 0 || h->has_this == 1); } DUK_INTERNAL void duk_hproxy_assert_valid(duk_hproxy *h) { DUK_ASSERT(h != NULL); DUK_ASSERT(h->target != NULL); DUK_ASSERT(h->handler != NULL); DUK_ASSERT(DUK_HOBJECT_HAS_EXOTIC_PROXYOBJ((duk_hobject *) h)); } DUK_INTERNAL void duk_hthread_assert_valid(duk_hthread *thr) { DUK_ASSERT(thr != NULL); DUK_ASSERT(DUK_HEAPHDR_GET_TYPE((duk_heaphdr *) thr) == DUK_HTYPE_OBJECT); DUK_ASSERT(DUK_HOBJECT_IS_THREAD((duk_hobject *) thr)); DUK_ASSERT(thr->unused1 == 0); DUK_ASSERT(thr->unused2 == 0); } DUK_INTERNAL void duk_ctx_assert_valid(duk_hthread *thr) { DUK_ASSERT(thr != NULL); DUK_HTHREAD_ASSERT_VALID(thr); DUK_ASSERT(thr->valstack != NULL); DUK_ASSERT(thr->valstack_bottom != NULL); DUK_ASSERT(thr->valstack_top != NULL); DUK_ASSERT(thr->valstack_end != NULL); DUK_ASSERT(thr->valstack_alloc_end != NULL); DUK_ASSERT(thr->valstack_alloc_end >= thr->valstack); DUK_ASSERT(thr->valstack_end >= thr->valstack); DUK_ASSERT(thr->valstack_top >= thr->valstack); DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom); DUK_ASSERT(thr->valstack_end >= thr->valstack_top); DUK_ASSERT(thr->valstack_alloc_end >= thr->valstack_end); } #endif /* DUK_USE_ASSERTIONS */ #line 1 "duk_hobject_enum.c" /* * Object enumeration support. * * Creates an internal enumeration state object to be used e.g. with for-in * enumeration. The state object contains a snapshot of target object keys * and internal control state for enumeration. Enumerator flags allow caller * to e.g. request internal/non-enumerable properties, and to enumerate only * "own" properties. * * Also creates the result value for e.g. Object.keys() based on the same * internal structure. * * This snapshot-based enumeration approach is used to simplify enumeration: * non-snapshot-based approaches are difficult to reconcile with mutating * the enumeration target, running multiple long-lived enumerators at the * same time, garbage collection details, etc. The downside is that the * enumerator object is memory inefficient especially for iterating arrays. */ /* #include duk_internal.h -> already included */ /* XXX: identify enumeration target with an object index (not top of stack) */ /* First enumerated key index in enumerator object, must match exactly the * number of control properties inserted to the enumerator. */ #define DUK__ENUM_START_INDEX 2 /* Current implementation suffices for ES2015 for now because there's no symbol * sorting, so commented out for now. */ /* * Helper to sort enumeration keys using a callback for pairwise duk_hstring * comparisons. The keys are in the enumeration object entry part, starting * from DUK__ENUM_START_INDEX, and the entry part is dense. Entry part values * are all "true", e.g. "1" -> true, "3" -> true, "foo" -> true, "2" -> true, * so it suffices to just switch keys without switching values. * * ES2015 [[OwnPropertyKeys]] enumeration order for ordinary objects: * (1) array indices in ascending order, * (2) non-array-index keys in insertion order, and * (3) symbols in insertion order. * http://www.ecma-international.org/ecma-262/6.0/#sec-ordinary-object-internal-methods-and-internal-slots-ownpropertykeys. * * This rule is applied to "own properties" at each inheritance level; * non-duplicate parent keys always follow child keys. For example, * an inherited array index will enumerate -after- a symbol in the * child. * * Insertion sort is used because (1) it's simple and compact, (2) works * in-place, (3) minimizes operations if data is already nearly sorted, * (4) doesn't reorder elements considered equal. * http://en.wikipedia.org/wiki/Insertion_sort */ /* Sort key, must hold array indices, "not array index" marker, and one more * higher value for symbols. */ #if !defined(DUK_USE_SYMBOL_BUILTIN) typedef duk_uint32_t duk__sort_key_t; #elif defined(DUK_USE_64BIT_OPS) typedef duk_uint64_t duk__sort_key_t; #else typedef duk_double_t duk__sort_key_t; #endif /* Get sort key for a duk_hstring. */ DUK_LOCAL duk__sort_key_t duk__hstring_sort_key(duk_hstring *x) { duk__sort_key_t val; /* For array indices [0,0xfffffffe] use the array index as is. * For strings, use 0xffffffff, the marker 'arridx' already in * duk_hstring. For symbols, any value above 0xffffffff works, * as long as it is the same for all symbols; currently just add * the masked flag field into the arridx temporary. */ DUK_ASSERT(x != NULL); DUK_ASSERT(!DUK_HSTRING_HAS_SYMBOL(x) || DUK_HSTRING_GET_ARRIDX_FAST(x) == DUK_HSTRING_NO_ARRAY_INDEX); val = (duk__sort_key_t) DUK_HSTRING_GET_ARRIDX_FAST(x); #if defined(DUK_USE_SYMBOL_BUILTIN) val = val + (duk__sort_key_t) (DUK_HEAPHDR_GET_FLAGS_RAW((duk_heaphdr *) x) & DUK_HSTRING_FLAG_SYMBOL); #endif return (duk__sort_key_t) val; } /* Insert element 'b' after element 'a'? */ DUK_LOCAL duk_bool_t duk__sort_compare_es6(duk_hstring *a, duk_hstring *b, duk__sort_key_t val_b) { duk__sort_key_t val_a; DUK_ASSERT(a != NULL); DUK_ASSERT(b != NULL); DUK_UNREF(b); /* Not actually needed now, val_b suffices. */ val_a = duk__hstring_sort_key(a); if (val_a > val_b) { return 0; } else { return 1; } } DUK_LOCAL void duk__sort_enum_keys_es6(duk_hthread *thr, duk_hobject *h_obj, duk_int_fast32_t idx_start, duk_int_fast32_t idx_end) { duk_hstring **keys; duk_int_fast32_t idx; DUK_ASSERT(h_obj != NULL); DUK_ASSERT(idx_start >= DUK__ENUM_START_INDEX); DUK_ASSERT(idx_end >= idx_start); DUK_UNREF(thr); if (idx_end <= idx_start + 1) { return; /* Zero or one element(s). */ } keys = DUK_HOBJECT_E_GET_KEY_BASE(thr->heap, h_obj); for (idx = idx_start + 1; idx < idx_end; idx++) { duk_hstring *h_curr; duk_int_fast32_t idx_insert; duk__sort_key_t val_curr; h_curr = keys[idx]; DUK_ASSERT(h_curr != NULL); /* Scan backwards for insertion place. This works very well * when the elements are nearly in order which is the common * (and optimized for) case. */ val_curr = duk__hstring_sort_key(h_curr); /* Remains same during scanning. */ for (idx_insert = idx - 1; idx_insert >= idx_start; idx_insert--) { duk_hstring *h_insert; h_insert = keys[idx_insert]; DUK_ASSERT(h_insert != NULL); if (duk__sort_compare_es6(h_insert, h_curr, val_curr)) { break; } } /* If we're out of indices, idx_insert == idx_start - 1 and idx_insert++ * brings us back to idx_start. */ idx_insert++; DUK_ASSERT(idx_insert >= 0 && idx_insert <= idx); /* .-- p_insert .-- p_curr * v v * | ... | insert | ... | curr */ /* This could also done when the keys are in order, i.e. * idx_insert == idx. The result would be an unnecessary * memmove() but we use an explicit check because the keys * are very often in order already. */ if (idx != idx_insert) { duk_memmove((void *) (keys + idx_insert + 1), (const void *) (keys + idx_insert), ((size_t) (idx - idx_insert) * sizeof(duk_hstring *))); keys[idx_insert] = h_curr; } } /* Entry part has been reordered now with no side effects. * If the object has a hash part, it will now be incorrect * and we need to rehash. Do that by forcing a resize to * the current size. */ duk_hobject_resize_entrypart(thr, h_obj, DUK_HOBJECT_GET_ESIZE(h_obj)); } /* * Create an internal enumerator object E, which has its keys ordered * to match desired enumeration ordering. Also initialize internal control * properties for enumeration. * * Note: if an array was used to hold enumeration keys instead, an array * scan would be needed to eliminate duplicates found in the prototype chain. */ DUK_LOCAL void duk__add_enum_key(duk_hthread *thr, duk_hstring *k) { /* 'k' may be unreachable on entry so must push without any * potential for GC. */ duk_push_hstring(thr, k); duk_push_true(thr); duk_put_prop(thr, -3); } DUK_LOCAL void duk__add_enum_key_stridx(duk_hthread *thr, duk_small_uint_t stridx) { duk__add_enum_key(thr, DUK_HTHREAD_GET_STRING(thr, stridx)); } DUK_INTERNAL void duk_hobject_enumerator_create(duk_hthread *thr, duk_small_uint_t enum_flags) { duk_hobject *enum_target; duk_hobject *curr; duk_hobject *res; #if defined(DUK_USE_ES6_PROXY) duk_hobject *h_proxy_target; duk_hobject *h_proxy_handler; duk_hobject *h_trap_result; #endif duk_uint_fast32_t i, len; /* used for array, stack, and entry indices */ duk_uint_fast32_t sort_start_index; DUK_ASSERT(thr != NULL); enum_target = duk_require_hobject(thr, -1); DUK_ASSERT(enum_target != NULL); duk_push_bare_object(thr); res = duk_known_hobject(thr, -1); /* [enum_target res] */ /* Target must be stored so that we can recheck whether or not * keys still exist when we enumerate. This is not done if the * enumeration result comes from a proxy trap as there is no * real object to check against. */ duk_push_hobject(thr, enum_target); duk_put_prop_stridx_short(thr, -2, DUK_STRIDX_INT_TARGET); /* Target is bare, plain put OK. */ /* Initialize index so that we skip internal control keys. */ duk_push_int(thr, DUK__ENUM_START_INDEX); duk_put_prop_stridx_short(thr, -2, DUK_STRIDX_INT_NEXT); /* Target is bare, plain put OK. */ /* * Proxy object handling */ #if defined(DUK_USE_ES6_PROXY) if (DUK_LIKELY((enum_flags & DUK_ENUM_NO_PROXY_BEHAVIOR) != 0)) { goto skip_proxy; } if (DUK_LIKELY(!duk_hobject_proxy_check(enum_target, &h_proxy_target, &h_proxy_handler))) { goto skip_proxy; } /* XXX: share code with Object.keys() Proxy handling */ /* In ES2015 for-in invoked the "enumerate" trap; in ES2016 "enumerate" * has been obsoleted and "ownKeys" is used instead. */ DUK_DDD(DUK_DDDPRINT("proxy enumeration")); duk_push_hobject(thr, h_proxy_handler); if (!duk_get_prop_stridx_short(thr, -1, DUK_STRIDX_OWN_KEYS)) { /* No need to replace the 'enum_target' value in stack, only the * enum_target reference. This also ensures that the original * enum target is reachable, which keeps the proxy and the proxy * target reachable. We do need to replace the internal _Target. */ DUK_DDD(DUK_DDDPRINT("no ownKeys trap, enumerate proxy target instead")); DUK_DDD(DUK_DDDPRINT("h_proxy_target=%!O", (duk_heaphdr *) h_proxy_target)); enum_target = h_proxy_target; duk_push_hobject(thr, enum_target); /* -> [ ... enum_target res handler undefined target ] */ duk_put_prop_stridx_short(thr, -4, DUK_STRIDX_INT_TARGET); /* Target is bare, plain put OK. */ duk_pop_2(thr); /* -> [ ... enum_target res ] */ goto skip_proxy; } /* [ ... enum_target res handler trap ] */ duk_insert(thr, -2); duk_push_hobject(thr, h_proxy_target); /* -> [ ... enum_target res trap handler target ] */ duk_call_method(thr, 1 /*nargs*/); /* -> [ ... enum_target res trap_result ] */ h_trap_result = duk_require_hobject(thr, -1); DUK_UNREF(h_trap_result); duk_proxy_ownkeys_postprocess(thr, h_proxy_target, enum_flags); /* -> [ ... enum_target res trap_result keys_array ] */ /* Copy cleaned up trap result keys into the enumerator object. */ /* XXX: result is a dense array; could make use of that. */ DUK_ASSERT(duk_is_array(thr, -1)); len = (duk_uint_fast32_t) duk_get_length(thr, -1); for (i = 0; i < len; i++) { (void) duk_get_prop_index(thr, -1, (duk_uarridx_t) i); DUK_ASSERT(duk_is_string(thr, -1)); /* postprocess cleaned up */ /* [ ... enum_target res trap_result keys_array val ] */ duk_push_true(thr); /* [ ... enum_target res trap_result keys_array val true ] */ duk_put_prop(thr, -5); } /* [ ... enum_target res trap_result keys_array ] */ duk_pop_2(thr); duk_remove_m2(thr); /* [ ... res ] */ /* The internal _Target property is kept pointing to the original * enumeration target (the proxy object), so that the enumerator * 'next' operation can read property values if so requested. The * fact that the _Target is a proxy disables key existence check * during enumeration. */ DUK_DDD(DUK_DDDPRINT("proxy enumeration, final res: %!O", (duk_heaphdr *) res)); goto compact_and_return; skip_proxy: #endif /* DUK_USE_ES6_PROXY */ curr = enum_target; sort_start_index = DUK__ENUM_START_INDEX; DUK_ASSERT(DUK_HOBJECT_GET_ENEXT(res) == DUK__ENUM_START_INDEX); while (curr) { duk_uint_fast32_t sort_end_index; #if !defined(DUK_USE_PREFER_SIZE) duk_bool_t need_sort = 0; #endif duk_bool_t cond; /* Enumeration proceeds by inheritance level. Virtual * properties need to be handled specially, followed by * array part, and finally entry part. * * If there are array index keys in the entry part or any * other risk of the ES2015 [[OwnPropertyKeys]] order being * violated, need_sort is set and an explicit ES2015 sort is * done for the inheritance level. */ /* XXX: inheriting from proxy */ /* * Virtual properties. * * String and buffer indices are virtual and always enumerable, * 'length' is virtual and non-enumerable. Array and arguments * object props have special behavior but are concrete. * * String and buffer objects don't have an array part so as long * as virtual array index keys are enumerated first, we don't * need to set need_sort. */ #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) cond = DUK_HOBJECT_HAS_EXOTIC_STRINGOBJ(curr) || DUK_HOBJECT_IS_BUFOBJ(curr); #else cond = DUK_HOBJECT_HAS_EXOTIC_STRINGOBJ(curr); #endif cond = cond && !(enum_flags & DUK_ENUM_EXCLUDE_STRINGS); if (cond) { duk_bool_t have_length = 1; /* String and buffer enumeration behavior is identical now, * so use shared handler. */ if (DUK_HOBJECT_HAS_EXOTIC_STRINGOBJ(curr)) { duk_hstring *h_val; h_val = duk_hobject_get_internal_value_string(thr->heap, curr); DUK_ASSERT(h_val != NULL); /* string objects must not created without internal value */ len = (duk_uint_fast32_t) DUK_HSTRING_GET_CHARLEN(h_val); } #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) else { duk_hbufobj *h_bufobj; DUK_ASSERT(DUK_HOBJECT_IS_BUFOBJ(curr)); h_bufobj = (duk_hbufobj *) curr; if (h_bufobj == NULL || !h_bufobj->is_typedarray) { /* Zero length seems like a good behavior for neutered buffers. * ArrayBuffer (non-view) and DataView don't have index properties * or .length property. */ len = 0; have_length = 0; } else { /* There's intentionally no check for * current underlying buffer length. */ len = (duk_uint_fast32_t) (h_bufobj->length >> h_bufobj->shift); } } #endif /* DUK_USE_BUFFEROBJECT_SUPPORT */ for (i = 0; i < len; i++) { duk_hstring *k; /* This is a bit fragile: the string is not * reachable until it is pushed by the helper. */ k = duk_heap_strtable_intern_u32_checked(thr, (duk_uint32_t) i); DUK_ASSERT(k); duk__add_enum_key(thr, k); /* [enum_target res] */ } /* 'length' and other virtual properties are not * enumerable, but are included if non-enumerable * properties are requested. */ if (have_length && (enum_flags & DUK_ENUM_INCLUDE_NONENUMERABLE)) { duk__add_enum_key_stridx(thr, DUK_STRIDX_LENGTH); } } /* * Array part */ cond = !(enum_flags & DUK_ENUM_EXCLUDE_STRINGS); if (cond) { for (i = 0; i < (duk_uint_fast32_t) DUK_HOBJECT_GET_ASIZE(curr); i++) { duk_hstring *k; duk_tval *tv; tv = DUK_HOBJECT_A_GET_VALUE_PTR(thr->heap, curr, i); if (DUK_TVAL_IS_UNUSED(tv)) { continue; } k = duk_heap_strtable_intern_u32_checked(thr, (duk_uint32_t) i); /* Fragile reachability. */ DUK_ASSERT(k); duk__add_enum_key(thr, k); /* [enum_target res] */ } if (DUK_HOBJECT_HAS_EXOTIC_ARRAY(curr)) { /* Array .length comes after numeric indices. */ if (enum_flags & DUK_ENUM_INCLUDE_NONENUMERABLE) { duk__add_enum_key_stridx(thr, DUK_STRIDX_LENGTH); } } } /* * Entries part */ for (i = 0; i < (duk_uint_fast32_t) DUK_HOBJECT_GET_ENEXT(curr); i++) { duk_hstring *k; k = DUK_HOBJECT_E_GET_KEY(thr->heap, curr, i); if (!k) { continue; } if (!(enum_flags & DUK_ENUM_INCLUDE_NONENUMERABLE) && !DUK_HOBJECT_E_SLOT_IS_ENUMERABLE(thr->heap, curr, i)) { continue; } if (DUK_UNLIKELY(DUK_HSTRING_HAS_SYMBOL(k))) { if (!(enum_flags & DUK_ENUM_INCLUDE_HIDDEN) && DUK_HSTRING_HAS_HIDDEN(k)) { continue; } if (!(enum_flags & DUK_ENUM_INCLUDE_SYMBOLS)) { continue; } #if !defined(DUK_USE_PREFER_SIZE) need_sort = 1; #endif } else { DUK_ASSERT(!DUK_HSTRING_HAS_HIDDEN(k)); /* would also have symbol flag */ if (enum_flags & DUK_ENUM_EXCLUDE_STRINGS) { continue; } } if (DUK_HSTRING_HAS_ARRIDX(k)) { /* This in currently only possible if the * object has no array part: the array part * is exhaustive when it is present. */ #if !defined(DUK_USE_PREFER_SIZE) need_sort = 1; #endif } else { if (enum_flags & DUK_ENUM_ARRAY_INDICES_ONLY) { continue; } } DUK_ASSERT(DUK_HOBJECT_E_SLOT_IS_ACCESSOR(thr->heap, curr, i) || !DUK_TVAL_IS_UNUSED(&DUK_HOBJECT_E_GET_VALUE_PTR(thr->heap, curr, i)->v)); duk__add_enum_key(thr, k); /* [enum_target res] */ } /* Sort enumerated keys according to ES2015 requirements for * the "inheritance level" just processed. This is far from * optimal, ES2015 semantics could be achieved more efficiently * by handling array index string keys (and symbol keys) * specially above in effect doing the sort inline. * * Skip the sort if array index sorting is requested because * we must consider all keys, also inherited, so an explicit * sort is done for the whole result after we're done with the * prototype chain. * * Also skip the sort if need_sort == 0, i.e. we know for * certain that the enumerated order is already correct. */ sort_end_index = DUK_HOBJECT_GET_ENEXT(res); if (!(enum_flags & DUK_ENUM_SORT_ARRAY_INDICES)) { #if defined(DUK_USE_PREFER_SIZE) duk__sort_enum_keys_es6(thr, res, (duk_int_fast32_t) sort_start_index, (duk_int_fast32_t) sort_end_index); #else if (need_sort) { DUK_DDD(DUK_DDDPRINT("need to sort")); duk__sort_enum_keys_es6(thr, res, (duk_int_fast32_t) sort_start_index, (duk_int_fast32_t) sort_end_index); } else { DUK_DDD(DUK_DDDPRINT("no need to sort")); } #endif } sort_start_index = sort_end_index; if (enum_flags & DUK_ENUM_OWN_PROPERTIES_ONLY) { break; } curr = DUK_HOBJECT_GET_PROTOTYPE(thr->heap, curr); } /* [enum_target res] */ duk_remove_m2(thr); /* [res] */ if (enum_flags & DUK_ENUM_SORT_ARRAY_INDICES) { /* Some E5/E5.1 algorithms require that array indices are iterated * in a strictly ascending order. This is the case for e.g. * Array.prototype.forEach() and JSON.stringify() PropertyList * handling. The caller can request an explicit sort in these * cases. */ /* Sort to ES2015 order which works for pure array incides but * also for mixed keys. */ duk__sort_enum_keys_es6(thr, res, (duk_int_fast32_t) DUK__ENUM_START_INDEX, (duk_int_fast32_t) DUK_HOBJECT_GET_ENEXT(res)); } #if defined(DUK_USE_ES6_PROXY) compact_and_return: #endif /* compact; no need to seal because object is internal */ duk_hobject_compact_props(thr, res); DUK_DDD(DUK_DDDPRINT("created enumerator object: %!iT", (duk_tval *) duk_get_tval(thr, -1))); } /* * Returns non-zero if a key and/or value was enumerated, and: * * [enum] -> [key] (get_value == 0) * [enum] -> [key value] (get_value == 1) * * Returns zero without pushing anything on the stack otherwise. */ DUK_INTERNAL duk_bool_t duk_hobject_enumerator_next(duk_hthread *thr, duk_bool_t get_value) { duk_hobject *e; duk_hobject *enum_target; duk_hstring *res = NULL; duk_uint_fast32_t idx; duk_bool_t check_existence; DUK_ASSERT(thr != NULL); /* [... enum] */ e = duk_require_hobject(thr, -1); /* XXX use get tval ptr, more efficient */ duk_get_prop_stridx_short(thr, -1, DUK_STRIDX_INT_NEXT); idx = (duk_uint_fast32_t) duk_require_uint(thr, -1); duk_pop(thr); DUK_DDD(DUK_DDDPRINT("enumeration: index is: %ld", (long) idx)); /* Enumeration keys are checked against the enumeration target (to see * that they still exist). In the proxy enumeration case _Target will * be the proxy, and checking key existence against the proxy is not * required (or sensible, as the keys may be fully virtual). */ duk_xget_owndataprop_stridx_short(thr, -1, DUK_STRIDX_INT_TARGET); enum_target = duk_require_hobject(thr, -1); DUK_ASSERT(enum_target != NULL); #if defined(DUK_USE_ES6_PROXY) check_existence = (!DUK_HOBJECT_IS_PROXY(enum_target)); #else check_existence = 1; #endif duk_pop(thr); /* still reachable */ DUK_DDD(DUK_DDDPRINT("getting next enum value, enum_target=%!iO, enumerator=%!iT", (duk_heaphdr *) enum_target, (duk_tval *) duk_get_tval(thr, -1))); /* no array part */ for (;;) { duk_hstring *k; if (idx >= DUK_HOBJECT_GET_ENEXT(e)) { DUK_DDD(DUK_DDDPRINT("enumeration: ran out of elements")); break; } /* we know these because enum objects are internally created */ k = DUK_HOBJECT_E_GET_KEY(thr->heap, e, idx); DUK_ASSERT(k != NULL); DUK_ASSERT(!DUK_HOBJECT_E_SLOT_IS_ACCESSOR(thr->heap, e, idx)); DUK_ASSERT(!DUK_TVAL_IS_UNUSED(&DUK_HOBJECT_E_GET_VALUE(thr->heap, e, idx).v)); idx++; /* recheck that the property still exists */ if (check_existence && !duk_hobject_hasprop_raw(thr, enum_target, k)) { DUK_DDD(DUK_DDDPRINT("property deleted during enumeration, skip")); continue; } DUK_DDD(DUK_DDDPRINT("enumeration: found element, key: %!O", (duk_heaphdr *) k)); res = k; break; } DUK_DDD(DUK_DDDPRINT("enumeration: updating next index to %ld", (long) idx)); duk_push_u32(thr, (duk_uint32_t) idx); duk_put_prop_stridx_short(thr, -2, DUK_STRIDX_INT_NEXT); /* [... enum] */ if (res) { duk_push_hstring(thr, res); if (get_value) { duk_push_hobject(thr, enum_target); duk_dup_m2(thr); /* -> [... enum key enum_target key] */ duk_get_prop(thr, -2); /* -> [... enum key enum_target val] */ duk_remove_m2(thr); /* -> [... enum key val] */ duk_remove(thr, -3); /* -> [... key val] */ } else { duk_remove_m2(thr); /* -> [... key] */ } return 1; } else { duk_pop(thr); /* -> [...] */ return 0; } } /* * Get enumerated keys in an ECMAScript array. Matches Object.keys() behavior * described in E5 Section 15.2.3.14. */ DUK_INTERNAL duk_ret_t duk_hobject_get_enumerated_keys(duk_hthread *thr, duk_small_uint_t enum_flags) { duk_hobject *e; duk_hstring **keys; duk_tval *tv; duk_uint_fast32_t count; DUK_ASSERT(thr != NULL); DUK_ASSERT(duk_get_hobject(thr, -1) != NULL); /* Create a temporary enumerator to get the (non-duplicated) key list; * the enumerator state is initialized without being needed, but that * has little impact. */ duk_hobject_enumerator_create(thr, enum_flags); e = duk_known_hobject(thr, -1); /* [enum_target enum res] */ /* Create dense result array to exact size. */ DUK_ASSERT(DUK_HOBJECT_GET_ENEXT(e) >= DUK__ENUM_START_INDEX); count = (duk_uint32_t) (DUK_HOBJECT_GET_ENEXT(e) - DUK__ENUM_START_INDEX); /* XXX: uninit would be OK */ tv = duk_push_harray_with_size_outptr(thr, (duk_uint32_t) count); DUK_ASSERT(count == 0 || tv != NULL); DUK_ASSERT(!duk_is_bare_object(thr, -1)); /* Fill result array, no side effects. */ keys = DUK_HOBJECT_E_GET_KEY_BASE(thr->heap, e); keys += DUK__ENUM_START_INDEX; while (count-- > 0) { duk_hstring *k; k = *keys++; DUK_ASSERT(k != NULL); /* enumerator must have no keys deleted */ DUK_TVAL_SET_STRING(tv, k); tv++; DUK_HSTRING_INCREF(thr, k); } /* [enum_target enum res] */ duk_remove_m2(thr); /* [enum_target res] */ return 1; /* return 1 to allow callers to tail call */ } /* automatic undefs */ #undef DUK__ENUM_START_INDEX #line 1 "duk_hobject_misc.c" /* * Misc support functions */ /* #include duk_internal.h -> already included */ DUK_INTERNAL duk_bool_t duk_hobject_prototype_chain_contains(duk_hthread *thr, duk_hobject *h, duk_hobject *p, duk_bool_t ignore_loop) { duk_uint_t sanity; DUK_ASSERT(thr != NULL); /* False if the object is NULL or the prototype 'p' is NULL. * In particular, false if both are NULL (don't compare equal). */ if (h == NULL || p == NULL) { return 0; } sanity = DUK_HOBJECT_PROTOTYPE_CHAIN_SANITY; do { if (h == p) { return 1; } if (sanity-- == 0) { if (ignore_loop) { break; } else { DUK_ERROR_RANGE(thr, DUK_STR_PROTOTYPE_CHAIN_LIMIT); DUK_WO_NORETURN(return 0;); } } h = DUK_HOBJECT_GET_PROTOTYPE(thr->heap, h); } while (h); return 0; } DUK_INTERNAL void duk_hobject_set_prototype_updref(duk_hthread *thr, duk_hobject *h, duk_hobject *p) { #if defined(DUK_USE_REFERENCE_COUNTING) duk_hobject *tmp; DUK_ASSERT(h); tmp = DUK_HOBJECT_GET_PROTOTYPE(thr->heap, h); DUK_HOBJECT_SET_PROTOTYPE(thr->heap, h, p); DUK_HOBJECT_INCREF_ALLOWNULL(thr, p); /* avoid problems if p == h->prototype */ DUK_HOBJECT_DECREF_ALLOWNULL(thr, tmp); #else DUK_ASSERT(h); DUK_UNREF(thr); DUK_HOBJECT_SET_PROTOTYPE(thr->heap, h, p); #endif } #line 1 "duk_hobject_pc2line.c" /* * Helpers for creating and querying pc2line debug data, which * converts a bytecode program counter to a source line number. * * The run-time pc2line data is bit-packed, and documented in: * * doc/function-objects.rst */ /* #include duk_internal.h -> already included */ #if defined(DUK_USE_PC2LINE) /* Generate pc2line data for an instruction sequence, leaving a buffer on stack top. */ DUK_INTERNAL void duk_hobject_pc2line_pack(duk_hthread *thr, duk_compiler_instr *instrs, duk_uint_fast32_t length) { duk_hbuffer_dynamic *h_buf; duk_bitencoder_ctx be_ctx_alloc; duk_bitencoder_ctx *be_ctx = &be_ctx_alloc; duk_uint32_t *hdr; duk_size_t new_size; duk_uint_fast32_t num_header_entries; duk_uint_fast32_t curr_offset; duk_int_fast32_t curr_line, next_line, diff_line; duk_uint_fast32_t curr_pc; duk_uint_fast32_t hdr_index; DUK_ASSERT(length <= DUK_COMPILER_MAX_BYTECODE_LENGTH); num_header_entries = (length + DUK_PC2LINE_SKIP - 1) / DUK_PC2LINE_SKIP; curr_offset = (duk_uint_fast32_t) (sizeof(duk_uint32_t) + num_header_entries * sizeof(duk_uint32_t) * 2); duk_push_dynamic_buffer(thr, (duk_size_t) curr_offset); h_buf = (duk_hbuffer_dynamic *) duk_known_hbuffer(thr, -1); DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(h_buf) && !DUK_HBUFFER_HAS_EXTERNAL(h_buf)); hdr = (duk_uint32_t *) DUK_HBUFFER_DYNAMIC_GET_DATA_PTR(thr->heap, h_buf); DUK_ASSERT(hdr != NULL); hdr[0] = (duk_uint32_t) length; /* valid pc range is [0, length[ */ curr_pc = 0U; while (curr_pc < length) { new_size = (duk_size_t) (curr_offset + DUK_PC2LINE_MAX_DIFF_LENGTH); duk_hbuffer_resize(thr, h_buf, new_size); hdr = (duk_uint32_t *) DUK_HBUFFER_DYNAMIC_GET_DATA_PTR(thr->heap, h_buf); DUK_ASSERT(hdr != NULL); DUK_ASSERT(curr_pc < length); hdr_index = 1 + (curr_pc / DUK_PC2LINE_SKIP) * 2; curr_line = (duk_int_fast32_t) instrs[curr_pc].line; hdr[hdr_index + 0] = (duk_uint32_t) curr_line; hdr[hdr_index + 1] = (duk_uint32_t) curr_offset; #if 0 DUK_DDD(DUK_DDDPRINT("hdr[%ld]: pc=%ld line=%ld offset=%ld", (long) (curr_pc / DUK_PC2LINE_SKIP), (long) curr_pc, (long) hdr[hdr_index + 0], (long) hdr[hdr_index + 1])); #endif duk_memzero(be_ctx, sizeof(*be_ctx)); be_ctx->data = ((duk_uint8_t *) hdr) + curr_offset; be_ctx->length = (duk_size_t) DUK_PC2LINE_MAX_DIFF_LENGTH; for (;;) { curr_pc++; if (((curr_pc % DUK_PC2LINE_SKIP) == 0) || /* end of diff run */ (curr_pc >= length)) { /* end of bytecode */ break; } DUK_ASSERT(curr_pc < length); next_line = (duk_int32_t) instrs[curr_pc].line; diff_line = next_line - curr_line; #if 0 DUK_DDD(DUK_DDDPRINT("curr_line=%ld, next_line=%ld -> diff_line=%ld", (long) curr_line, (long) next_line, (long) diff_line)); #endif if (diff_line == 0) { /* 0 */ duk_be_encode(be_ctx, 0, 1); } else if (diff_line >= 1 && diff_line <= 4) { /* 1 0 <2 bits> */ duk_be_encode(be_ctx, (duk_uint32_t) ((0x02 << 2) + (diff_line - 1)), 4); } else if (diff_line >= -0x80 && diff_line <= 0x7f) { /* 1 1 0 <8 bits> */ DUK_ASSERT(diff_line + 0x80 >= 0 && diff_line + 0x80 <= 0xff); duk_be_encode(be_ctx, (duk_uint32_t) ((0x06 << 8) + (diff_line + 0x80)), 11); } else { /* 1 1 1 <32 bits> * Encode in two parts to avoid bitencode 24-bit limitation */ duk_be_encode(be_ctx, (duk_uint32_t) ((0x07 << 16) + ((next_line >> 16) & 0xffff)), 19); duk_be_encode(be_ctx, (duk_uint32_t) (next_line & 0xffff), 16); } curr_line = next_line; } duk_be_finish(be_ctx); DUK_ASSERT(!be_ctx->truncated); /* be_ctx->offset == length of encoded bitstream */ curr_offset += (duk_uint_fast32_t) be_ctx->offset; } /* compact */ new_size = (duk_size_t) curr_offset; duk_hbuffer_resize(thr, h_buf, new_size); (void) duk_to_fixed_buffer(thr, -1, NULL); DUK_DDD(DUK_DDDPRINT("final pc2line data: pc_limit=%ld, length=%ld, %lf bits/opcode --> %!ixT", (long) length, (long) new_size, (double) new_size * 8.0 / (double) length, (duk_tval *) duk_get_tval(thr, -1))); } /* PC is unsigned. If caller does PC arithmetic and gets a negative result, * it will map to a large PC which is out of bounds and causes a zero to be * returned. */ DUK_LOCAL duk_uint_fast32_t duk__hobject_pc2line_query_raw(duk_hthread *thr, duk_hbuffer_fixed *buf, duk_uint_fast32_t pc) { duk_bitdecoder_ctx bd_ctx_alloc; duk_bitdecoder_ctx *bd_ctx = &bd_ctx_alloc; duk_uint32_t *hdr; duk_uint_fast32_t start_offset; duk_uint_fast32_t pc_limit; duk_uint_fast32_t hdr_index; duk_uint_fast32_t pc_base; duk_uint_fast32_t n; duk_uint_fast32_t curr_line; DUK_ASSERT(buf != NULL); DUK_ASSERT(!DUK_HBUFFER_HAS_DYNAMIC((duk_hbuffer *) buf) && !DUK_HBUFFER_HAS_EXTERNAL((duk_hbuffer *) buf)); DUK_UNREF(thr); /* * Use the index in the header to find the right starting point */ hdr_index = pc / DUK_PC2LINE_SKIP; pc_base = hdr_index * DUK_PC2LINE_SKIP; n = pc - pc_base; if (DUK_HBUFFER_FIXED_GET_SIZE(buf) <= sizeof(duk_uint32_t)) { DUK_DD(DUK_DDPRINT("pc2line lookup failed: buffer is smaller than minimal header")); goto pc2line_error; } hdr = (duk_uint32_t *) (void *) DUK_HBUFFER_FIXED_GET_DATA_PTR(thr->heap, buf); pc_limit = hdr[0]; if (pc >= pc_limit) { /* Note: pc is unsigned and cannot be negative */ DUK_DD(DUK_DDPRINT("pc2line lookup failed: pc out of bounds (pc=%ld, limit=%ld)", (long) pc, (long) pc_limit)); goto pc2line_error; } curr_line = hdr[1 + hdr_index * 2]; start_offset = hdr[1 + hdr_index * 2 + 1]; if ((duk_size_t) start_offset > DUK_HBUFFER_FIXED_GET_SIZE(buf)) { DUK_DD(DUK_DDPRINT("pc2line lookup failed: start_offset out of bounds (start_offset=%ld, buffer_size=%ld)", (long) start_offset, (long) DUK_HBUFFER_GET_SIZE((duk_hbuffer *) buf))); goto pc2line_error; } /* * Iterate the bitstream (line diffs) until PC is reached */ duk_memzero(bd_ctx, sizeof(*bd_ctx)); bd_ctx->data = ((duk_uint8_t *) hdr) + start_offset; bd_ctx->length = (duk_size_t) (DUK_HBUFFER_FIXED_GET_SIZE(buf) - start_offset); #if 0 DUK_DDD(DUK_DDDPRINT("pc2line lookup: pc=%ld -> hdr_index=%ld, pc_base=%ld, n=%ld, start_offset=%ld", (long) pc, (long) hdr_index, (long) pc_base, (long) n, (long) start_offset)); #endif while (n > 0) { #if 0 DUK_DDD(DUK_DDDPRINT("lookup: n=%ld, curr_line=%ld", (long) n, (long) curr_line)); #endif if (duk_bd_decode_flag(bd_ctx)) { if (duk_bd_decode_flag(bd_ctx)) { if (duk_bd_decode_flag(bd_ctx)) { /* 1 1 1 <32 bits> */ duk_uint_fast32_t t; t = duk_bd_decode(bd_ctx, 16); /* workaround: max nbits = 24 now */ t = (t << 16) + duk_bd_decode(bd_ctx, 16); curr_line = t; } else { /* 1 1 0 <8 bits> */ duk_uint_fast32_t t; t = duk_bd_decode(bd_ctx, 8); curr_line = curr_line + t - 0x80; } } else { /* 1 0 <2 bits> */ duk_uint_fast32_t t; t = duk_bd_decode(bd_ctx, 2); curr_line = curr_line + t + 1; } } else { /* 0: no change */ } n--; } DUK_DDD(DUK_DDDPRINT("pc2line lookup result: pc %ld -> line %ld", (long) pc, (long) curr_line)); return curr_line; pc2line_error: DUK_D(DUK_DPRINT("pc2line conversion failed for pc=%ld", (long) pc)); return 0; } DUK_INTERNAL duk_uint_fast32_t duk_hobject_pc2line_query(duk_hthread *thr, duk_idx_t idx_func, duk_uint_fast32_t pc) { duk_hbuffer_fixed *pc2line; duk_uint_fast32_t line; /* XXX: now that pc2line is used by the debugger quite heavily in * checked execution, this should be optimized to avoid value stack * and perhaps also implement some form of pc2line caching (see * future work in debugger.rst). */ duk_xget_owndataprop_stridx_short(thr, idx_func, DUK_STRIDX_INT_PC2LINE); pc2line = (duk_hbuffer_fixed *) (void *) duk_get_hbuffer(thr, -1); if (pc2line != NULL) { DUK_ASSERT(!DUK_HBUFFER_HAS_DYNAMIC((duk_hbuffer *) pc2line) && !DUK_HBUFFER_HAS_EXTERNAL((duk_hbuffer *) pc2line)); line = duk__hobject_pc2line_query_raw(thr, pc2line, (duk_uint_fast32_t) pc); } else { line = 0; } duk_pop(thr); return line; } #endif /* DUK_USE_PC2LINE */ #line 1 "duk_hobject_props.c" /* * duk_hobject property access functionality. * * This is very central functionality for size, performance, and compliance. * It is also rather intricate; see hobject-algorithms.rst for discussion on * the algorithms and memory-management.rst for discussion on refcounts and * side effect issues. * * Notes: * * - It might be tempting to assert "refcount nonzero" for objects * being operated on, but that's not always correct: objects with * a zero refcount may be operated on by the refcount implementation * (finalization) for instance. Hence, no refcount assertions are made. * * - Many operations (memory allocation, identifier operations, etc) * may cause arbitrary side effects (e.g. through GC and finalization). * These side effects may invalidate duk_tval pointers which point to * areas subject to reallocation (like value stack). Heap objects * themselves have stable pointers. Holding heap object pointers or * duk_tval copies is not problematic with respect to side effects; * care must be taken when holding and using argument duk_tval pointers. * * - If a finalizer is executed, it may operate on the the same object * we're currently dealing with. For instance, the finalizer might * delete a certain property which has already been looked up and * confirmed to exist. Ideally finalizers would be disabled if GC * happens during property access. At the moment property table realloc * disables finalizers, and all DECREFs may cause arbitrary changes so * handle DECREF carefully. * * - The order of operations for a DECREF matters. When DECREF is executed, * the entire object graph must be consistent; note that a refzero may * lead to a mark-and-sweep through a refcount finalizer. Use NORZ macros * and an explicit DUK_REFZERO_CHECK_xxx() if achieving correct order is hard. */ /* * XXX: array indices are mostly typed as duk_uint32_t here; duk_uarridx_t * might be more appropriate. */ /* #include duk_internal.h -> already included */ /* * Local defines */ #define DUK__NO_ARRAY_INDEX DUK_HSTRING_NO_ARRAY_INDEX /* Marker values for hash part. */ #define DUK__HASH_UNUSED DUK_HOBJECT_HASHIDX_UNUSED #define DUK__HASH_DELETED DUK_HOBJECT_HASHIDX_DELETED /* Valstack space that suffices for all local calls, excluding any recursion * into ECMAScript or Duktape/C calls (Proxy, getters, etc). */ #define DUK__VALSTACK_SPACE 10 /* Valstack space allocated especially for proxy lookup which does a * recursive property lookup. */ #define DUK__VALSTACK_PROXY_LOOKUP 20 /* * Local prototypes */ DUK_LOCAL_DECL duk_bool_t duk__check_arguments_map_for_get(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, duk_propdesc *temp_desc); DUK_LOCAL_DECL void duk__check_arguments_map_for_put(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, duk_propdesc *temp_desc, duk_bool_t throw_flag); DUK_LOCAL_DECL void duk__check_arguments_map_for_delete(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, duk_propdesc *temp_desc); DUK_LOCAL_DECL duk_bool_t duk__handle_put_array_length_smaller(duk_hthread *thr, duk_hobject *obj, duk_uint32_t old_len, duk_uint32_t new_len, duk_bool_t force_flag, duk_uint32_t *out_result_len); DUK_LOCAL_DECL duk_bool_t duk__handle_put_array_length(duk_hthread *thr, duk_hobject *obj); DUK_LOCAL_DECL duk_bool_t duk__get_propdesc(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, duk_propdesc *out_desc, duk_small_uint_t flags); DUK_LOCAL_DECL duk_bool_t duk__get_own_propdesc_raw(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, duk_uint32_t arr_idx, duk_propdesc *out_desc, duk_small_uint_t flags); DUK_LOCAL_DECL void duk__abandon_array_part(duk_hthread *thr, duk_hobject *obj); DUK_LOCAL_DECL void duk__grow_props_for_array_item(duk_hthread *thr, duk_hobject *obj, duk_uint32_t highest_arr_idx); /* * Misc helpers */ /* Convert a duk_tval number (caller checks) to a 32-bit index. Returns * DUK__NO_ARRAY_INDEX if the number is not whole or not a valid array * index. */ /* XXX: for fastints, could use a variant which assumes a double duk_tval * (and doesn't need to check for fastint again). */ DUK_LOCAL duk_uint32_t duk__tval_number_to_arr_idx(duk_tval *tv) { duk_double_t dbl; duk_uint32_t idx; DUK_ASSERT(tv != NULL); DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv)); /* -0 is accepted here as index 0 because ToString(-0) == "0" which is * in canonical form and thus an array index. */ dbl = DUK_TVAL_GET_NUMBER(tv); idx = (duk_uint32_t) dbl; if (duk_double_equals((duk_double_t) idx, dbl)) { /* Is whole and within 32 bit range. If the value happens to be 0xFFFFFFFF, * it's not a valid array index but will then match DUK__NO_ARRAY_INDEX. */ return idx; } return DUK__NO_ARRAY_INDEX; } #if defined(DUK_USE_FASTINT) /* Convert a duk_tval fastint (caller checks) to a 32-bit index. */ DUK_LOCAL duk_uint32_t duk__tval_fastint_to_arr_idx(duk_tval *tv) { duk_int64_t t; DUK_ASSERT(tv != NULL); DUK_ASSERT(DUK_TVAL_IS_FASTINT(tv)); t = DUK_TVAL_GET_FASTINT(tv); if (((duk_uint64_t) t & ~DUK_U64_CONSTANT(0xffffffff)) != 0) { /* Catches >0x100000000 and negative values. */ return DUK__NO_ARRAY_INDEX; } /* If the value happens to be 0xFFFFFFFF, it's not a valid array index * but will then match DUK__NO_ARRAY_INDEX. */ return (duk_uint32_t) t; } #endif /* DUK_USE_FASTINT */ /* Convert a duk_tval on the value stack (in a trusted index we don't validate) * to a string or symbol using ES2015 ToPropertyKey(): * http://www.ecma-international.org/ecma-262/6.0/#sec-topropertykey. * * Also check if it's a valid array index and return that (or DUK__NO_ARRAY_INDEX * if not). */ DUK_LOCAL duk_uint32_t duk__to_property_key(duk_hthread *thr, duk_idx_t idx, duk_hstring **out_h) { duk_uint32_t arr_idx; duk_hstring *h; duk_tval *tv_dst; DUK_ASSERT(thr != NULL); DUK_ASSERT(out_h != NULL); DUK_ASSERT(duk_is_valid_index(thr, idx)); DUK_ASSERT(idx < 0); /* XXX: The revised ES2015 ToPropertyKey() handling (ES5.1 was just * ToString()) involves a ToPrimitive(), a symbol check, and finally * a ToString(). Figure out the best way to have a good fast path * but still be compliant and share code. */ tv_dst = DUK_GET_TVAL_NEGIDX(thr, idx); /* intentionally unvalidated */ if (DUK_TVAL_IS_STRING(tv_dst)) { /* Most important path: strings and plain symbols are used as * is. For symbols the array index check below is unnecessary * (they're never valid array indices) but checking that the * string is a symbol would make the plain string path slower * unnecessarily. */ h = DUK_TVAL_GET_STRING(tv_dst); } else { h = duk_to_property_key_hstring(thr, idx); } DUK_ASSERT(h != NULL); *out_h = h; arr_idx = DUK_HSTRING_GET_ARRIDX_FAST(h); return arr_idx; } DUK_LOCAL duk_uint32_t duk__push_tval_to_property_key(duk_hthread *thr, duk_tval *tv_key, duk_hstring **out_h) { duk_push_tval(thr, tv_key); /* XXX: could use an unsafe push here */ return duk__to_property_key(thr, -1, out_h); } /* String is an own (virtual) property of a plain buffer. */ DUK_LOCAL duk_bool_t duk__key_is_plain_buf_ownprop(duk_hthread *thr, duk_hbuffer *buf, duk_hstring *key, duk_uint32_t arr_idx) { DUK_UNREF(thr); /* Virtual index properties. Checking explicitly for * 'arr_idx != DUK__NO_ARRAY_INDEX' is not necessary * because DUK__NO_ARRAY_INDEXi is always larger than * maximum allowed buffer size. */ DUK_ASSERT(DUK__NO_ARRAY_INDEX >= DUK_HBUFFER_GET_SIZE(buf)); if (arr_idx < DUK_HBUFFER_GET_SIZE(buf)) { return 1; } /* Other virtual properties. */ return (key == DUK_HTHREAD_STRING_LENGTH(thr)); } /* * Helpers for managing property storage size */ /* Get default hash part size for a certain entry part size. */ #if defined(DUK_USE_HOBJECT_HASH_PART) DUK_LOCAL duk_uint32_t duk__get_default_h_size(duk_uint32_t e_size) { DUK_ASSERT(e_size <= DUK_HOBJECT_MAX_PROPERTIES); if (e_size >= DUK_USE_HOBJECT_HASH_PROP_LIMIT) { duk_uint32_t res; duk_uint32_t tmp; /* Hash size should be 2^N where N is chosen so that 2^N is * larger than e_size. Extra shifting is used to ensure hash * is relatively sparse. */ tmp = e_size; res = 2; /* Result will be 2 ** (N + 1). */ while (tmp >= 0x40) { tmp >>= 6; res <<= 6; } while (tmp != 0) { tmp >>= 1; res <<= 1; } DUK_ASSERT((DUK_HOBJECT_MAX_PROPERTIES << 2U) > DUK_HOBJECT_MAX_PROPERTIES); /* Won't wrap, even shifted by 2. */ DUK_ASSERT(res > e_size); return res; } else { return 0; } } #endif /* USE_PROP_HASH_PART */ /* Get minimum entry part growth for a certain size. */ DUK_LOCAL duk_uint32_t duk__get_min_grow_e(duk_uint32_t e_size) { duk_uint32_t res; res = (e_size + DUK_USE_HOBJECT_ENTRY_MINGROW_ADD) / DUK_USE_HOBJECT_ENTRY_MINGROW_DIVISOR; DUK_ASSERT(res >= 1); /* important for callers */ return res; } /* Get minimum array part growth for a certain size. */ DUK_LOCAL duk_uint32_t duk__get_min_grow_a(duk_uint32_t a_size) { duk_uint32_t res; res = (a_size + DUK_USE_HOBJECT_ARRAY_MINGROW_ADD) / DUK_USE_HOBJECT_ARRAY_MINGROW_DIVISOR; DUK_ASSERT(res >= 1); /* important for callers */ return res; } /* Count actually used entry part entries (non-NULL keys). */ DUK_LOCAL duk_uint32_t duk__count_used_e_keys(duk_hthread *thr, duk_hobject *obj) { duk_uint_fast32_t i; duk_uint_fast32_t n = 0; duk_hstring **e; DUK_ASSERT(obj != NULL); DUK_UNREF(thr); e = DUK_HOBJECT_E_GET_KEY_BASE(thr->heap, obj); for (i = 0; i < DUK_HOBJECT_GET_ENEXT(obj); i++) { if (*e++) { n++; } } return (duk_uint32_t) n; } /* Count actually used array part entries and array minimum size. * N