From 73debcee4563c8065f63977136660210bbeab85b Mon Sep 17 00:00:00 2001 From: Gladys-2592 Date: Sun, 18 Oct 2026 18:23:51 -0400 Subject: [PATCH 01/41] chore(engine): vendor quickjs-ng v0.17.0 for code_execution's engine code_execution is to run scripts in QuickJS-ng compiled to WebAssembly, built from source kept in this repository rather than taken from a package, so that a defect in the engine is fixed by bumping or patching this copy and rebuilding, without waiting on someone else's release. engine/quickjs-ng/ holds, unedited, the four C files the module compiles (quickjs.c, dtoa.c, libregexp.c, libunicode.c: 72,158 lines) and the 14 headers they include, with upstream's LICENSE: 19 files, 2.8 MB. It's a copy, not a git submodule, since the tooling we test this repository with refuses submodules. engine/vendor.sh makes the copy: it clones the pinned tag (v0.17.0, the latest release, of 2026-09-18), refuses unless the tag is the pinned commit (6d46d07d04041b40f4f49eaa7fdebe44c314c699), and copies the 19 files. A bump is: change TAG and COMMIT, run vendor.sh, run build.sh, and commit the source and the module together. The copied files are byte-identical to quickjs-wasi 3.6.2's submodule at the same commit (diff -rq, 2026-10-18). Nothing builds or ships these files yet; the next commit builds the module from them. engine/ isn't in package.json's `files`, so the C source doesn't go into the npm package. Co-Authored-By: Claude Opus 5.5 --- engine/quickjs-ng/LICENSE | 24 + engine/quickjs-ng/builtin-array-fromasync.h | 120 + .../quickjs-ng/builtin-iterator-zip-keyed.h | 332 + engine/quickjs-ng/builtin-iterator-zip.h | 336 + engine/quickjs-ng/cutils.h | 2021 + engine/quickjs-ng/dtoa.c | 1619 + engine/quickjs-ng/dtoa.h | 87 + engine/quickjs-ng/libregexp-opcode.h | 73 + engine/quickjs-ng/libregexp.c | 3522 + engine/quickjs-ng/libregexp.h | 101 + engine/quickjs-ng/libunicode-table.h | 5160 ++ engine/quickjs-ng/libunicode.c | 2074 + engine/quickjs-ng/libunicode.h | 172 + engine/quickjs-ng/list.h | 107 + engine/quickjs-ng/quickjs-atom.h | 280 + engine/quickjs-ng/quickjs-c-atomics.h | 54 + engine/quickjs-ng/quickjs-opcode.h | 377 + engine/quickjs-ng/quickjs.c | 64943 ++++++++++++++++ engine/quickjs-ng/quickjs.h | 1472 + engine/vendor.sh | 29 + 20 files changed, 82903 insertions(+) create mode 100644 engine/quickjs-ng/LICENSE create mode 100644 engine/quickjs-ng/builtin-array-fromasync.h create mode 100644 engine/quickjs-ng/builtin-iterator-zip-keyed.h create mode 100644 engine/quickjs-ng/builtin-iterator-zip.h create mode 100644 engine/quickjs-ng/cutils.h create mode 100644 engine/quickjs-ng/dtoa.c create mode 100644 engine/quickjs-ng/dtoa.h create mode 100644 engine/quickjs-ng/libregexp-opcode.h create mode 100644 engine/quickjs-ng/libregexp.c create mode 100644 engine/quickjs-ng/libregexp.h create mode 100644 engine/quickjs-ng/libunicode-table.h create mode 100644 engine/quickjs-ng/libunicode.c create mode 100644 engine/quickjs-ng/libunicode.h create mode 100644 engine/quickjs-ng/list.h create mode 100644 engine/quickjs-ng/quickjs-atom.h create mode 100644 engine/quickjs-ng/quickjs-c-atomics.h create mode 100644 engine/quickjs-ng/quickjs-opcode.h create mode 100644 engine/quickjs-ng/quickjs.c create mode 100644 engine/quickjs-ng/quickjs.h create mode 100755 engine/vendor.sh diff --git a/engine/quickjs-ng/LICENSE b/engine/quickjs-ng/LICENSE new file mode 100644 index 000000000..fae657320 --- /dev/null +++ b/engine/quickjs-ng/LICENSE @@ -0,0 +1,24 @@ +The MIT License (MIT) + +Copyright (c) 2017-2026 Fabrice Bellard +Copyright (c) 2017-2024 Charlie Gordon +Copyright (c) 2023-2026 Ben Noordhuis +Copyright (c) 2023-2026 Saúl Ibarra Corretgé + +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. diff --git a/engine/quickjs-ng/builtin-array-fromasync.h b/engine/quickjs-ng/builtin-array-fromasync.h new file mode 100644 index 000000000..f2b568af5 --- /dev/null +++ b/engine/quickjs-ng/builtin-array-fromasync.h @@ -0,0 +1,120 @@ +/* File generated automatically by the QuickJS-ng compiler. */ + +#include + +const uint32_t qjsc_builtin_array_fromasync_size = 888; + +const uint8_t qjsc_builtin_array_fromasync[888] = { + 0x1c, 0xc5, 0x39, 0x53, 0x3c, 0x0e, 0x01, 0x28, + 0x53, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0xb7, 0x61, + 0x73, 0x79, 0x6e, 0x63, 0x49, 0x74, 0x65, 0x72, + 0x61, 0x74, 0x6f, 0x72, 0x01, 0x2a, 0x4f, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0xb7, 0x64, 0x65, 0x66, + 0x69, 0x6e, 0x65, 0x50, 0x72, 0x6f, 0x70, 0x65, + 0x72, 0x74, 0x79, 0x01, 0x1e, 0x53, 0x79, 0x6d, + 0x62, 0x6f, 0x6c, 0xb7, 0x69, 0x74, 0x65, 0x72, + 0x61, 0x74, 0x6f, 0x72, 0x01, 0x12, 0x61, 0x72, + 0x72, 0x61, 0x79, 0x4c, 0x69, 0x6b, 0x65, 0x01, + 0x0a, 0x6d, 0x61, 0x70, 0x46, 0x6e, 0x01, 0x0e, + 0x74, 0x68, 0x69, 0x73, 0x41, 0x72, 0x67, 0x01, + 0x0c, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x01, + 0x02, 0x69, 0x01, 0x1a, 0x69, 0x73, 0x43, 0x6f, + 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x6f, + 0x72, 0x01, 0x08, 0x73, 0x79, 0x6e, 0x63, 0x01, + 0x0c, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x01, + 0x08, 0x69, 0x74, 0x65, 0x72, 0x01, 0x1c, 0x6e, + 0x6f, 0x74, 0x20, 0x61, 0x20, 0x66, 0x75, 0x6e, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x01, 0x08, 0x63, + 0x61, 0x6c, 0x6c, 0x0c, 0x00, 0x02, 0x00, 0xa8, + 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, + 0x04, 0x01, 0xaa, 0x01, 0x00, 0x00, 0x00, 0x0c, + 0x43, 0x02, 0x01, 0x00, 0x05, 0x00, 0x05, 0x01, + 0x05, 0x00, 0x01, 0x03, 0x05, 0xbe, 0x02, 0x00, + 0x01, 0x40, 0x03, 0xb4, 0x03, 0x00, 0x01, 0x40, + 0x00, 0xe4, 0x03, 0x00, 0x01, 0x40, 0x01, 0xe6, + 0x03, 0x00, 0x01, 0x40, 0x04, 0xe8, 0x03, 0x00, + 0x01, 0x40, 0x02, 0x0c, 0x60, 0x02, 0x01, 0x88, + 0x02, 0x03, 0x0e, 0x01, 0x06, 0x00, 0x05, 0x00, + 0x93, 0x04, 0x11, 0xea, 0x03, 0x00, 0x01, 0x00, + 0xec, 0x03, 0x00, 0x01, 0x00, 0xee, 0x03, 0x00, + 0x01, 0x00, 0xea, 0x03, 0x01, 0xff, 0xff, 0xff, + 0xff, 0x0f, 0x20, 0xec, 0x03, 0x01, 0x01, 0x20, + 0xee, 0x03, 0x01, 0x02, 0x20, 0xf0, 0x03, 0x02, + 0x00, 0x20, 0xf2, 0x03, 0x02, 0x04, 0x20, 0xf4, + 0x03, 0x02, 0x05, 0x20, 0xf6, 0x03, 0x02, 0x06, + 0x20, 0xf8, 0x03, 0x02, 0x07, 0x20, 0x66, 0x06, + 0x08, 0x20, 0x88, 0x01, 0x07, 0x09, 0x20, 0xfa, + 0x03, 0x0a, 0x08, 0x30, 0x88, 0x01, 0x0d, 0x0b, + 0x20, 0xe2, 0x01, 0x0d, 0x0c, 0x20, 0x10, 0x00, + 0x01, 0x00, 0xb4, 0x03, 0x01, 0x01, 0xe4, 0x03, + 0x02, 0x01, 0xe8, 0x03, 0x04, 0x01, 0xbe, 0x02, + 0x00, 0x01, 0xe6, 0x03, 0x03, 0x01, 0x08, 0xc8, + 0x0d, 0x60, 0x02, 0x00, 0x60, 0x01, 0x00, 0x60, + 0x00, 0x00, 0xd7, 0xcf, 0xd8, 0x11, 0xf8, 0xf0, + 0x08, 0x0e, 0x38, 0x49, 0x00, 0x00, 0x00, 0xe0, + 0xd0, 0xd9, 0x11, 0xf8, 0xf0, 0x08, 0x0e, 0x38, + 0x49, 0x00, 0x00, 0x00, 0xe1, 0xd1, 0x60, 0x07, + 0x00, 0x60, 0x06, 0x00, 0x60, 0x05, 0x00, 0x60, + 0x04, 0x00, 0x60, 0x03, 0x00, 0xd8, 0x38, 0x49, + 0x00, 0x00, 0x00, 0xae, 0xf0, 0x16, 0xd8, 0x96, + 0x04, 0x1b, 0x00, 0x00, 0x00, 0xae, 0xf0, 0x0c, + 0xe3, 0x11, 0x04, 0xfe, 0x00, 0x00, 0x00, 0x21, + 0x01, 0x00, 0x30, 0x06, 0xd2, 0xba, 0xc8, 0x04, + 0xc7, 0x0d, 0xfb, 0xc8, 0x05, 0x09, 0xc8, 0x06, + 0xd7, 0xe4, 0x46, 0xc8, 0x07, 0x61, 0x07, 0x00, + 0x07, 0xab, 0xf0, 0x0f, 0x0a, 0x11, 0x62, 0x06, + 0x00, 0x0e, 0xd7, 0xe5, 0x46, 0x11, 0x62, 0x07, + 0x00, 0x0e, 0x61, 0x07, 0x00, 0x07, 0xab, 0x68, + 0xac, 0x00, 0x00, 0x00, 0x60, 0x08, 0x00, 0x06, + 0x11, 0xf8, 0xf1, 0x0c, 0x70, 0x41, 0x33, 0x00, + 0x00, 0x00, 0xc8, 0x08, 0x0e, 0xf2, 0x05, 0x0e, + 0xd7, 0xf2, 0xf2, 0x61, 0x08, 0x00, 0x8c, 0x11, + 0xf1, 0x03, 0x0e, 0xba, 0x11, 0x62, 0x08, 0x00, + 0x0e, 0x61, 0x05, 0x00, 0xf0, 0x0c, 0xc7, 0x0d, + 0x11, 0x61, 0x08, 0x00, 0x21, 0x01, 0x00, 0xf2, + 0x06, 0xe6, 0x61, 0x08, 0x00, 0xf5, 0x11, 0x62, + 0x03, 0x00, 0x0e, 0x61, 0x04, 0x00, 0x61, 0x08, + 0x00, 0xa5, 0x68, 0x37, 0x01, 0x00, 0x00, 0x60, + 0x09, 0x00, 0xd7, 0x61, 0x04, 0x00, 0x46, 0xc8, + 0x09, 0x61, 0x06, 0x00, 0xf0, 0x0a, 0x61, 0x09, + 0x00, 0x8a, 0x11, 0x62, 0x09, 0x00, 0x0e, 0xd8, + 0xf0, 0x17, 0xd8, 0x41, 0xff, 0x00, 0x00, 0x00, + 0xd9, 0x61, 0x09, 0x00, 0x61, 0x04, 0x00, 0x24, + 0x03, 0x00, 0x8a, 0x11, 0x62, 0x09, 0x00, 0x0e, + 0x5d, 0x04, 0x00, 0x61, 0x03, 0x00, 0x61, 0x04, + 0x00, 0x90, 0x62, 0x04, 0x00, 0x0b, 0x61, 0x09, + 0x00, 0x4b, 0x44, 0x00, 0x00, 0x00, 0x0a, 0x4b, + 0x41, 0x00, 0x00, 0x00, 0x0a, 0x4b, 0x42, 0x00, + 0x00, 0x00, 0x0a, 0x4b, 0x43, 0x00, 0x00, 0x00, + 0xf7, 0x0e, 0xf2, 0x98, 0x60, 0x0a, 0x00, 0x61, + 0x07, 0x00, 0x41, 0xff, 0x00, 0x00, 0x00, 0xd7, + 0x24, 0x01, 0x00, 0xc8, 0x0a, 0x61, 0x05, 0x00, + 0xf0, 0x09, 0xc7, 0x0d, 0x11, 0x21, 0x00, 0x00, + 0xf2, 0x03, 0xe6, 0xf4, 0x11, 0x62, 0x03, 0x00, + 0x0e, 0x6b, 0x93, 0x00, 0x00, 0x00, 0x60, 0x0c, + 0x00, 0x60, 0x0b, 0x00, 0x06, 0x11, 0xf8, 0xf1, + 0x13, 0x70, 0x41, 0x44, 0x00, 0x00, 0x00, 0xc8, + 0x0b, 0x41, 0x71, 0x00, 0x00, 0x00, 0xc8, 0x0c, + 0x0e, 0xf2, 0x10, 0x0e, 0x61, 0x0a, 0x00, 0x41, + 0x72, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x8a, + 0xf2, 0xe0, 0x61, 0x0c, 0x00, 0xf1, 0x55, 0x61, + 0x06, 0x00, 0xf0, 0x0a, 0x61, 0x0b, 0x00, 0x8a, + 0x11, 0x62, 0x0b, 0x00, 0x0e, 0xd8, 0xf0, 0x17, + 0xd8, 0x41, 0xff, 0x00, 0x00, 0x00, 0xd9, 0x61, + 0x0b, 0x00, 0x61, 0x04, 0x00, 0x24, 0x03, 0x00, + 0x8a, 0x11, 0x62, 0x0b, 0x00, 0x0e, 0x5d, 0x04, + 0x00, 0x61, 0x03, 0x00, 0x61, 0x04, 0x00, 0x90, + 0x62, 0x04, 0x00, 0x0b, 0x61, 0x0b, 0x00, 0x4b, + 0x44, 0x00, 0x00, 0x00, 0x0a, 0x4b, 0x41, 0x00, + 0x00, 0x00, 0x0a, 0x4b, 0x42, 0x00, 0x00, 0x00, + 0x0a, 0x4b, 0x43, 0x00, 0x00, 0x00, 0xf7, 0x0e, + 0xf3, 0x7d, 0xff, 0x0e, 0x06, 0x6c, 0x0d, 0x00, + 0x00, 0x00, 0x0e, 0xf2, 0x1e, 0x6c, 0x05, 0x00, + 0x00, 0x00, 0x30, 0x61, 0x0a, 0x00, 0x40, 0x06, + 0x00, 0x00, 0x00, 0xf0, 0x0d, 0x61, 0x0a, 0x00, + 0x41, 0x06, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, + 0x0e, 0x6d, 0x61, 0x03, 0x00, 0x61, 0x04, 0x00, + 0x42, 0x33, 0x00, 0x00, 0x00, 0x61, 0x03, 0x00, + 0x2f, 0xc5, 0x00, 0x28, 0xc5, 0x00, 0xd3, 0x28, +}; + diff --git a/engine/quickjs-ng/builtin-iterator-zip-keyed.h b/engine/quickjs-ng/builtin-iterator-zip-keyed.h new file mode 100644 index 000000000..471c6bf21 --- /dev/null +++ b/engine/quickjs-ng/builtin-iterator-zip-keyed.h @@ -0,0 +1,332 @@ +/* File generated automatically by the QuickJS-ng compiler. */ + +#include + +const uint32_t qjsc_builtin_iterator_zip_keyed_size = 2577; + +const uint8_t qjsc_builtin_iterator_zip_keyed[2577] = { + 0x1c, 0x1f, 0x9e, 0x38, 0x2b, 0x2b, 0x01, 0x1c, + 0x49, 0x74, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, + 0x48, 0x65, 0x6c, 0x70, 0x65, 0x72, 0x01, 0x08, + 0x63, 0x61, 0x6c, 0x6c, 0x01, 0x24, 0x68, 0x61, + 0x73, 0x4f, 0x77, 0x6e, 0x45, 0x6e, 0x75, 0x6d, + 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, + 0x01, 0x24, 0x67, 0x65, 0x74, 0x4f, 0x77, 0x6e, + 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, + 0x4b, 0x65, 0x79, 0x73, 0x01, 0x1e, 0x53, 0x79, + 0x6d, 0x62, 0x6f, 0x6c, 0xb7, 0x69, 0x74, 0x65, + 0x72, 0x61, 0x74, 0x6f, 0x72, 0x01, 0x0a, 0x63, + 0x68, 0x65, 0x63, 0x6b, 0x01, 0x0a, 0x63, 0x6c, + 0x6f, 0x73, 0x65, 0x01, 0x10, 0x63, 0x6c, 0x6f, + 0x73, 0x65, 0x61, 0x6c, 0x6c, 0x01, 0x02, 0x76, + 0x01, 0x02, 0x73, 0x01, 0x08, 0x69, 0x74, 0x65, + 0x72, 0x01, 0x0c, 0x6d, 0x65, 0x74, 0x68, 0x6f, + 0x64, 0x01, 0x02, 0x65, 0x01, 0x0a, 0x69, 0x74, + 0x65, 0x72, 0x73, 0x01, 0x0a, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x01, 0x04, 0x65, 0x78, 0x01, 0x02, + 0x69, 0x01, 0x12, 0x69, 0x74, 0x65, 0x72, 0x61, + 0x62, 0x6c, 0x65, 0x73, 0x01, 0x0e, 0x6f, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x01, 0x08, 0x6d, + 0x6f, 0x64, 0x65, 0x01, 0x0e, 0x70, 0x61, 0x64, + 0x64, 0x69, 0x6e, 0x67, 0x01, 0x0a, 0x6e, 0x65, + 0x78, 0x74, 0x73, 0x01, 0x08, 0x70, 0x61, 0x64, + 0x73, 0x01, 0x06, 0x6b, 0x65, 0x79, 0x01, 0x06, + 0x64, 0x65, 0x6c, 0x01, 0x02, 0x6a, 0x01, 0x0a, + 0x73, 0x74, 0x61, 0x74, 0x65, 0x01, 0x0a, 0x61, + 0x6c, 0x69, 0x76, 0x65, 0x01, 0x0a, 0x64, 0x6f, + 0x6e, 0x65, 0x73, 0x01, 0x0e, 0x72, 0x65, 0x73, + 0x75, 0x6c, 0x74, 0x73, 0x01, 0x0c, 0x72, 0x65, + 0x73, 0x75, 0x6c, 0x74, 0x01, 0x1c, 0x72, 0x75, + 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x20, 0x7a, 0x69, + 0x70, 0x70, 0x65, 0x72, 0x01, 0x06, 0x62, 0x75, + 0x67, 0x01, 0x0e, 0x6c, 0x6f, 0x6e, 0x67, 0x65, + 0x73, 0x74, 0x01, 0x0c, 0x73, 0x74, 0x72, 0x69, + 0x63, 0x74, 0x01, 0x22, 0x6d, 0x69, 0x73, 0x6d, + 0x61, 0x74, 0x63, 0x68, 0x65, 0x64, 0x20, 0x69, + 0x6e, 0x70, 0x75, 0x74, 0x73, 0x01, 0x10, 0x73, + 0x68, 0x6f, 0x72, 0x74, 0x65, 0x73, 0x74, 0x01, + 0x16, 0x62, 0x75, 0x67, 0x3a, 0x20, 0x73, 0x74, + 0x61, 0x74, 0x65, 0x3d, 0x01, 0x1a, 0x62, 0x61, + 0x64, 0x20, 0x69, 0x74, 0x65, 0x72, 0x61, 0x62, + 0x6c, 0x65, 0x73, 0x01, 0x16, 0x62, 0x61, 0x64, + 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x01, 0x10, 0x62, 0x61, 0x64, 0x20, 0x6d, 0x6f, + 0x64, 0x65, 0x01, 0x16, 0x62, 0x61, 0x64, 0x20, + 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x01, + 0x18, 0x62, 0x61, 0x64, 0x20, 0x69, 0x74, 0x65, + 0x72, 0x61, 0x74, 0x6f, 0x72, 0x0c, 0x00, 0x02, + 0x00, 0xa8, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, + 0x00, 0x01, 0x04, 0x01, 0xaa, 0x01, 0x00, 0x00, + 0x00, 0x0c, 0x43, 0x02, 0x00, 0x00, 0x07, 0x03, + 0x07, 0x01, 0x0a, 0x00, 0x04, 0x0c, 0x0a, 0xe4, + 0x03, 0x00, 0x01, 0x40, 0x09, 0xb8, 0x03, 0x00, + 0x01, 0x40, 0x03, 0xb4, 0x03, 0x00, 0x01, 0x40, + 0x00, 0xe6, 0x03, 0x00, 0x01, 0x40, 0x01, 0xe8, + 0x03, 0x00, 0x01, 0x40, 0x07, 0xea, 0x03, 0x00, + 0x01, 0x40, 0x06, 0xec, 0x03, 0x00, 0x01, 0x40, + 0x08, 0xee, 0x03, 0x00, 0x00, 0x40, 0x05, 0xf0, + 0x03, 0x00, 0x01, 0x40, 0x02, 0xf2, 0x03, 0x00, + 0x02, 0x40, 0x04, 0x0c, 0x43, 0x02, 0x00, 0xee, + 0x03, 0x02, 0x00, 0x02, 0x03, 0x00, 0x01, 0x00, + 0x17, 0x02, 0xf4, 0x03, 0x00, 0x01, 0x00, 0xf6, + 0x03, 0x00, 0x01, 0x00, 0xb4, 0x03, 0x02, 0x01, + 0xd7, 0x96, 0x04, 0x4d, 0x00, 0x00, 0x00, 0xad, + 0xf0, 0x07, 0xd7, 0x07, 0xae, 0xf0, 0x02, 0x29, + 0xe3, 0x11, 0xd8, 0x21, 0x01, 0x00, 0x30, 0x0c, + 0x43, 0x02, 0x00, 0xf0, 0x03, 0x01, 0x02, 0x01, + 0x04, 0x00, 0x01, 0x00, 0x2d, 0x03, 0xf8, 0x03, + 0x00, 0x01, 0x00, 0xfa, 0x03, 0x02, 0x00, 0x20, + 0xfc, 0x03, 0x05, 0x00, 0x03, 0xe6, 0x03, 0x03, + 0x01, 0x6b, 0x22, 0x00, 0x00, 0x00, 0x60, 0x00, + 0x00, 0xd7, 0xf1, 0x04, 0x06, 0x6e, 0x28, 0xd7, + 0x40, 0x06, 0x00, 0x00, 0x00, 0xcf, 0x61, 0x00, + 0x00, 0xf0, 0x08, 0xe3, 0xd7, 0x61, 0x00, 0x00, + 0xf6, 0x0e, 0x0e, 0x29, 0xd0, 0x6b, 0x07, 0x00, + 0x00, 0x00, 0xcc, 0x6e, 0x28, 0x30, 0x0c, 0x43, + 0x02, 0x00, 0xf2, 0x03, 0x02, 0x04, 0x02, 0x03, + 0x00, 0x01, 0x00, 0x54, 0x06, 0xfe, 0x03, 0x00, + 0x01, 0x00, 0x80, 0x04, 0x00, 0x01, 0x00, 0x82, + 0x04, 0x01, 0x00, 0x20, 0x84, 0x04, 0x02, 0x01, + 0x20, 0xf8, 0x03, 0x03, 0x02, 0x20, 0xfc, 0x03, + 0x03, 0x03, 0x20, 0xf0, 0x03, 0x01, 0x00, 0x60, + 0x00, 0x00, 0x38, 0x49, 0x00, 0x00, 0x00, 0xcf, + 0x60, 0x01, 0x00, 0xd8, 0xd0, 0x61, 0x01, 0x00, + 0x8f, 0x62, 0x01, 0x00, 0xba, 0xa7, 0xf0, 0x38, + 0x60, 0x03, 0x00, 0x60, 0x02, 0x00, 0xd7, 0x61, + 0x01, 0x00, 0x46, 0xd1, 0xd7, 0x61, 0x01, 0x00, + 0x1b, 0x11, 0xaf, 0xf1, 0x04, 0x1b, 0x71, 0x1b, + 0x1b, 0x38, 0x49, 0x00, 0x00, 0x00, 0x1b, 0x71, + 0x1b, 0x48, 0xe3, 0x61, 0x02, 0x00, 0xf5, 0xd2, + 0x61, 0x00, 0x00, 0xf1, 0xc9, 0x61, 0x03, 0x00, + 0x11, 0x62, 0x00, 0x00, 0x0e, 0xf2, 0xbf, 0x61, + 0x00, 0x00, 0x28, 0x0c, 0x41, 0x02, 0x00, 0xba, + 0x02, 0x02, 0x15, 0x01, 0x06, 0x08, 0x09, 0x02, + 0xc8, 0x05, 0x17, 0x86, 0x04, 0x00, 0x01, 0x00, + 0x88, 0x04, 0x00, 0x01, 0x00, 0x86, 0x04, 0x01, + 0xff, 0xff, 0xff, 0xff, 0x0f, 0x20, 0x88, 0x04, + 0x01, 0x01, 0x20, 0x8a, 0x04, 0x02, 0x00, 0x60, + 0x04, 0x8c, 0x04, 0x02, 0x03, 0x20, 0x62, 0x02, + 0x04, 0x60, 0x02, 0x80, 0x04, 0x02, 0x05, 0x60, + 0x01, 0xfe, 0x03, 0x02, 0x06, 0x60, 0x03, 0x8e, + 0x04, 0x02, 0x07, 0x60, 0x06, 0x90, 0x04, 0x02, + 0x08, 0x60, 0x05, 0x92, 0x04, 0x09, 0x15, 0x20, + 0x84, 0x04, 0x0b, 0x15, 0x20, 0x94, 0x04, 0x0c, + 0x0b, 0x20, 0x92, 0x04, 0x0c, 0x0c, 0x20, 0xf8, + 0x03, 0x0e, 0x0d, 0x20, 0xfa, 0x03, 0x10, 0x0e, + 0x20, 0x96, 0x04, 0x14, 0x0d, 0x20, 0x84, 0x04, + 0x19, 0x15, 0x20, 0x84, 0x04, 0x1b, 0x15, 0x20, + 0xfc, 0x03, 0x1c, 0x15, 0x03, 0x98, 0x04, 0x02, + 0x09, 0x60, 0x00, 0x9a, 0x04, 0x02, 0x14, 0x60, + 0x07, 0xb4, 0x03, 0x02, 0x01, 0xb8, 0x03, 0x01, + 0x01, 0xe6, 0x03, 0x03, 0x01, 0xf2, 0x03, 0x02, + 0x00, 0xee, 0x03, 0x00, 0x00, 0xea, 0x03, 0x05, + 0x01, 0xe8, 0x03, 0x04, 0x01, 0xec, 0x03, 0x06, + 0x01, 0xe4, 0x03, 0x00, 0x01, 0x0c, 0x42, 0x03, + 0x00, 0x00, 0x00, 0x09, 0x00, 0x05, 0x00, 0x0c, + 0x00, 0xf5, 0x04, 0x09, 0x9c, 0x04, 0x01, 0x00, + 0x20, 0xe6, 0x01, 0x01, 0x01, 0x20, 0x9e, 0x04, + 0x01, 0x02, 0x20, 0x84, 0x04, 0x03, 0x03, 0x20, + 0x92, 0x04, 0x04, 0x04, 0x20, 0xf8, 0x03, 0x04, + 0x05, 0x20, 0xa0, 0x04, 0x04, 0x06, 0x20, 0xfc, + 0x03, 0x09, 0x07, 0x03, 0x82, 0x04, 0x10, 0x07, + 0x20, 0x98, 0x04, 0x13, 0x10, 0xb4, 0x03, 0x00, + 0x02, 0xb8, 0x03, 0x01, 0x02, 0x80, 0x04, 0x05, + 0x10, 0x62, 0x04, 0x10, 0xfe, 0x03, 0x06, 0x10, + 0x8a, 0x04, 0x02, 0x10, 0x90, 0x04, 0x08, 0x10, + 0xe6, 0x03, 0x02, 0x02, 0x8e, 0x04, 0x07, 0x10, + 0x9a, 0x04, 0x14, 0x10, 0xf2, 0x03, 0x03, 0x02, + 0x60, 0x02, 0x00, 0x60, 0x01, 0x00, 0x60, 0x00, + 0x00, 0x64, 0x00, 0x00, 0x11, 0xba, 0xad, 0xf1, + 0x06, 0x11, 0xbb, 0xad, 0xf0, 0x09, 0xbc, 0x11, + 0x65, 0x00, 0x00, 0x0e, 0xf2, 0x33, 0x11, 0xbc, + 0xad, 0xf0, 0x0c, 0xe4, 0x11, 0x04, 0x11, 0x01, + 0x00, 0x00, 0x21, 0x01, 0x00, 0x30, 0x11, 0xbd, + 0xad, 0xf0, 0x13, 0x0b, 0x38, 0x49, 0x00, 0x00, + 0x00, 0x4b, 0x44, 0x00, 0x00, 0x00, 0x0a, 0x4b, + 0x71, 0x00, 0x00, 0x00, 0x28, 0xe5, 0x11, 0x04, + 0x12, 0x01, 0x00, 0x00, 0x21, 0x01, 0x00, 0x30, + 0x0e, 0xba, 0xcf, 0xba, 0xd0, 0x0c, 0x07, 0xd1, + 0x60, 0x03, 0x00, 0xba, 0xd2, 0x61, 0x03, 0x00, + 0x64, 0x03, 0x00, 0xa5, 0x68, 0xdb, 0x01, 0x00, + 0x00, 0x60, 0x06, 0x00, 0x60, 0x05, 0x00, 0x60, + 0x04, 0x00, 0x64, 0x04, 0x00, 0x61, 0x03, 0x00, + 0x46, 0xc8, 0x04, 0x64, 0x05, 0x00, 0x61, 0x03, + 0x00, 0x46, 0xc8, 0x05, 0x61, 0x05, 0x00, 0xf1, + 0x34, 0x64, 0x06, 0x00, 0x04, 0x13, 0x01, 0x00, + 0x00, 0xae, 0xf0, 0x0c, 0xe5, 0x11, 0x04, 0x12, + 0x01, 0x00, 0x00, 0x21, 0x01, 0x00, 0x30, 0x61, + 0x02, 0x00, 0x61, 0x04, 0x00, 0x1b, 0x11, 0xaf, + 0xf1, 0x04, 0x1b, 0x71, 0x1b, 0x1b, 0x64, 0x07, + 0x00, 0x61, 0x03, 0x00, 0x46, 0x1b, 0x71, 0x1b, + 0x48, 0xf3, 0x7b, 0x01, 0x06, 0xc8, 0x06, 0x6b, + 0x1a, 0x00, 0x00, 0x00, 0x5d, 0x08, 0x00, 0x61, + 0x05, 0x00, 0x64, 0x09, 0x00, 0x61, 0x03, 0x00, + 0x46, 0xf6, 0x11, 0x62, 0x06, 0x00, 0x0e, 0x0e, + 0xf2, 0x35, 0xc8, 0x07, 0x6b, 0x30, 0x00, 0x00, + 0x00, 0xba, 0x11, 0x65, 0x0a, 0x00, 0x0e, 0x64, + 0x05, 0x00, 0x61, 0x03, 0x00, 0x1b, 0x11, 0xaf, + 0xf1, 0x04, 0x1b, 0x71, 0x1b, 0x1b, 0x38, 0x49, + 0x00, 0x00, 0x00, 0x1b, 0x71, 0x1b, 0x48, 0x5d, + 0x0b, 0x00, 0x64, 0x05, 0x00, 0x64, 0x03, 0x00, + 0xf6, 0x0e, 0xc7, 0x07, 0x30, 0x30, 0x61, 0x06, + 0x00, 0x40, 0x71, 0x00, 0x00, 0x00, 0xf1, 0x4f, + 0x64, 0x06, 0x00, 0x04, 0x14, 0x01, 0x00, 0x00, + 0xad, 0xf0, 0x1e, 0x61, 0x00, 0x00, 0xba, 0xa7, + 0xf0, 0x17, 0x5d, 0x0b, 0x00, 0x64, 0x05, 0x00, + 0x64, 0x03, 0x00, 0xf6, 0x0e, 0xe4, 0x11, 0x04, + 0x15, 0x01, 0x00, 0x00, 0x21, 0x01, 0x00, 0x30, + 0x61, 0x02, 0x00, 0x61, 0x04, 0x00, 0x1b, 0x11, + 0xaf, 0xf1, 0x04, 0x1b, 0x71, 0x1b, 0x1b, 0x61, + 0x06, 0x00, 0x40, 0x44, 0x00, 0x00, 0x00, 0x1b, + 0x71, 0x1b, 0x48, 0x61, 0x01, 0x00, 0x90, 0x62, + 0x01, 0x00, 0x0e, 0xf3, 0xd1, 0x00, 0x64, 0x0a, + 0x00, 0x8f, 0x65, 0x0a, 0x00, 0x0e, 0x61, 0x00, + 0x00, 0x90, 0x62, 0x00, 0x00, 0x0e, 0x64, 0x05, + 0x00, 0x61, 0x03, 0x00, 0x1b, 0x11, 0xaf, 0xf1, + 0x04, 0x1b, 0x71, 0x1b, 0x1b, 0x38, 0x49, 0x00, + 0x00, 0x00, 0x1b, 0x71, 0x1b, 0x48, 0x64, 0x06, + 0x00, 0x60, 0x08, 0x00, 0x11, 0x04, 0x16, 0x01, + 0x00, 0x00, 0xad, 0xf0, 0x2e, 0x5d, 0x0b, 0x00, + 0x64, 0x05, 0x00, 0x64, 0x03, 0x00, 0xf6, 0xc8, + 0x08, 0x61, 0x08, 0x00, 0xf0, 0x05, 0x61, 0x08, + 0x00, 0x30, 0xbd, 0x11, 0x65, 0x00, 0x00, 0x0e, + 0x0b, 0x38, 0x49, 0x00, 0x00, 0x00, 0x4b, 0x44, + 0x00, 0x00, 0x00, 0x0a, 0x4b, 0x71, 0x00, 0x00, + 0x00, 0x28, 0x11, 0x04, 0x13, 0x01, 0x00, 0x00, + 0xad, 0xf0, 0x3c, 0x64, 0x0a, 0x00, 0xbb, 0xa5, + 0xf0, 0x19, 0xbd, 0x11, 0x65, 0x00, 0x00, 0x0e, + 0x0b, 0x38, 0x49, 0x00, 0x00, 0x00, 0x4b, 0x44, + 0x00, 0x00, 0x00, 0x0a, 0x4b, 0x71, 0x00, 0x00, + 0x00, 0x28, 0x61, 0x02, 0x00, 0x61, 0x04, 0x00, + 0x1b, 0x11, 0xaf, 0xf1, 0x04, 0x1b, 0x71, 0x1b, + 0x1b, 0x64, 0x07, 0x00, 0x61, 0x03, 0x00, 0x46, + 0x1b, 0x71, 0x1b, 0x48, 0xf2, 0x27, 0x11, 0x04, + 0x14, 0x01, 0x00, 0x00, 0xad, 0xf0, 0x1e, 0x61, + 0x01, 0x00, 0xba, 0xa7, 0xf0, 0x17, 0x5d, 0x0b, + 0x00, 0x64, 0x05, 0x00, 0x64, 0x03, 0x00, 0xf6, + 0x0e, 0xe4, 0x11, 0x04, 0x15, 0x01, 0x00, 0x00, + 0x21, 0x01, 0x00, 0x30, 0x0e, 0x61, 0x03, 0x00, + 0x90, 0x62, 0x03, 0x00, 0x0e, 0xf3, 0x1f, 0xfe, + 0x61, 0x01, 0x00, 0xba, 0xad, 0xf0, 0x19, 0xbd, + 0x11, 0x65, 0x00, 0x00, 0x0e, 0x0b, 0x38, 0x49, + 0x00, 0x00, 0x00, 0x4b, 0x44, 0x00, 0x00, 0x00, + 0x0a, 0x4b, 0x71, 0x00, 0x00, 0x00, 0x28, 0xbb, + 0x11, 0x65, 0x00, 0x00, 0x0e, 0x0b, 0x61, 0x02, + 0x00, 0x4b, 0x44, 0x00, 0x00, 0x00, 0x09, 0x4b, + 0x71, 0x00, 0x00, 0x00, 0x28, 0x0c, 0x42, 0x03, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x06, 0x00, 0x06, + 0x00, 0x88, 0x01, 0x01, 0x82, 0x04, 0x01, 0x00, + 0x20, 0x98, 0x04, 0x13, 0x10, 0xb4, 0x03, 0x00, + 0x02, 0xb8, 0x03, 0x01, 0x02, 0xf2, 0x03, 0x03, + 0x02, 0xfe, 0x03, 0x06, 0x10, 0x80, 0x04, 0x05, + 0x10, 0x60, 0x00, 0x00, 0x64, 0x00, 0x00, 0x11, + 0xba, 0xad, 0xf0, 0x09, 0xbd, 0x11, 0x65, 0x00, + 0x00, 0x0e, 0xf2, 0x4b, 0x11, 0xbb, 0xad, 0xf0, + 0x09, 0xbc, 0x11, 0x65, 0x00, 0x00, 0x0e, 0xf2, + 0x3e, 0x11, 0xbc, 0xad, 0xf0, 0x0c, 0xe4, 0x11, + 0x04, 0x11, 0x01, 0x00, 0x00, 0x21, 0x01, 0x00, + 0x30, 0x11, 0xbd, 0xad, 0xf0, 0x13, 0x0b, 0x38, + 0x49, 0x00, 0x00, 0x00, 0x4b, 0x44, 0x00, 0x00, + 0x00, 0x0a, 0x4b, 0x71, 0x00, 0x00, 0x00, 0x28, + 0xe5, 0x11, 0x04, 0x17, 0x01, 0x00, 0x00, 0x41, + 0x64, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x24, + 0x01, 0x00, 0x21, 0x01, 0x00, 0x30, 0x0e, 0xe6, + 0x64, 0x04, 0x00, 0x64, 0x05, 0x00, 0xf6, 0xcf, + 0x61, 0x00, 0x00, 0xf0, 0x05, 0x61, 0x00, 0x00, + 0x30, 0xbd, 0x11, 0x65, 0x00, 0x00, 0x0e, 0x0b, + 0x38, 0x49, 0x00, 0x00, 0x00, 0x4b, 0x44, 0x00, + 0x00, 0x00, 0x0a, 0x4b, 0x71, 0x00, 0x00, 0x00, + 0x28, 0x60, 0x01, 0x00, 0x60, 0x00, 0x00, 0xd7, + 0xcf, 0xd8, 0x11, 0xf8, 0xf0, 0x08, 0x0e, 0x38, + 0x49, 0x00, 0x00, 0x00, 0xe0, 0xd0, 0x60, 0x14, + 0x00, 0x60, 0x13, 0x00, 0x60, 0x08, 0x00, 0x60, + 0x07, 0x00, 0x60, 0x06, 0x00, 0x60, 0x05, 0x00, + 0x60, 0x04, 0x00, 0x60, 0x03, 0x00, 0x60, 0x02, + 0x00, 0x5d, 0x04, 0x00, 0xd7, 0x04, 0x18, 0x01, + 0x00, 0x00, 0xf6, 0x0e, 0xd8, 0x38, 0x49, 0x00, + 0x00, 0x00, 0xad, 0xf0, 0x06, 0x0c, 0x07, 0xdc, + 0xf2, 0x0c, 0x5d, 0x04, 0x00, 0xd8, 0x04, 0x19, + 0x01, 0x00, 0x00, 0xf6, 0x0e, 0xd8, 0x40, 0x05, + 0x01, 0x00, 0x00, 0xd1, 0x61, 0x02, 0x00, 0x38, + 0x49, 0x00, 0x00, 0x00, 0xad, 0xf0, 0x0b, 0x04, + 0x16, 0x01, 0x00, 0x00, 0x11, 0x62, 0x02, 0x00, + 0x0e, 0x61, 0x02, 0x00, 0x04, 0x14, 0x01, 0x00, + 0x00, 0xad, 0x11, 0xf1, 0x18, 0x0e, 0x61, 0x02, + 0x00, 0x04, 0x13, 0x01, 0x00, 0x00, 0xad, 0x11, + 0xf1, 0x0b, 0x0e, 0x61, 0x02, 0x00, 0x04, 0x16, + 0x01, 0x00, 0x00, 0xad, 0xf1, 0x0c, 0xe3, 0x11, + 0x04, 0x1a, 0x01, 0x00, 0x00, 0x21, 0x01, 0x00, + 0x30, 0x38, 0x49, 0x00, 0x00, 0x00, 0xd2, 0x61, + 0x02, 0x00, 0x04, 0x13, 0x01, 0x00, 0x00, 0xad, + 0xf0, 0x24, 0xd8, 0x40, 0x06, 0x01, 0x00, 0x00, + 0x11, 0x62, 0x03, 0x00, 0x0e, 0x61, 0x03, 0x00, + 0x38, 0x49, 0x00, 0x00, 0x00, 0xae, 0xf0, 0x0e, + 0x5d, 0x04, 0x00, 0x61, 0x03, 0x00, 0x04, 0x1b, + 0x01, 0x00, 0x00, 0xf6, 0x0e, 0x26, 0x00, 0x00, + 0xc8, 0x04, 0xba, 0xc8, 0x05, 0x26, 0x00, 0x00, + 0xc8, 0x06, 0x26, 0x00, 0x00, 0xc8, 0x07, 0x26, + 0x00, 0x00, 0xc8, 0x08, 0x60, 0x09, 0x00, 0x5d, + 0x05, 0x00, 0xd7, 0xf5, 0x7e, 0xf2, 0x1d, 0xc8, + 0x09, 0x61, 0x04, 0x00, 0x61, 0x05, 0x00, 0x90, + 0x62, 0x05, 0x00, 0x1b, 0x11, 0xaf, 0xf1, 0x04, + 0x1b, 0x71, 0x1b, 0x1b, 0x61, 0x09, 0x00, 0x1b, + 0x71, 0x1b, 0x48, 0x81, 0x00, 0xf0, 0xe1, 0x0e, + 0x83, 0x6b, 0x7d, 0x01, 0x00, 0x00, 0x60, 0x0a, + 0x00, 0xba, 0xc8, 0x0a, 0x61, 0x0a, 0x00, 0x61, + 0x05, 0x00, 0xa5, 0x68, 0xf0, 0x00, 0x00, 0x00, + 0x60, 0x0c, 0x00, 0x60, 0x0b, 0x00, 0x0a, 0xc8, + 0x0b, 0x61, 0x04, 0x00, 0x61, 0x0a, 0x00, 0x46, + 0xc8, 0x0c, 0x5d, 0x06, 0x00, 0xd7, 0x61, 0x0c, + 0x00, 0xf6, 0xf0, 0x78, 0x60, 0x0d, 0x00, 0xd7, + 0x61, 0x0c, 0x00, 0x46, 0xc8, 0x0d, 0x61, 0x0d, + 0x00, 0x38, 0x49, 0x00, 0x00, 0x00, 0xae, 0xf0, + 0x63, 0x60, 0x0e, 0x00, 0x5d, 0x04, 0x00, 0x61, + 0x0d, 0x00, 0x04, 0x1c, 0x01, 0x00, 0x00, 0xf6, + 0x0e, 0x61, 0x0d, 0x00, 0x5d, 0x07, 0x00, 0x46, + 0xc8, 0x0e, 0x61, 0x0e, 0x00, 0xf0, 0x0e, 0xe5, + 0x61, 0x0d, 0x00, 0x61, 0x0e, 0x00, 0xf6, 0x11, + 0x62, 0x0d, 0x00, 0x0e, 0x61, 0x06, 0x00, 0x61, + 0x0a, 0x00, 0x1b, 0x11, 0xaf, 0xf1, 0x04, 0x1b, + 0x71, 0x1b, 0x1b, 0x61, 0x0d, 0x00, 0x1b, 0x71, + 0x1b, 0x48, 0x61, 0x07, 0x00, 0x61, 0x0a, 0x00, + 0x1b, 0x11, 0xaf, 0xf1, 0x04, 0x1b, 0x71, 0x1b, + 0x1b, 0x61, 0x0d, 0x00, 0x40, 0x72, 0x00, 0x00, + 0x00, 0x1b, 0x71, 0x1b, 0x48, 0x09, 0x11, 0x62, + 0x0b, 0x00, 0x0e, 0x61, 0x0b, 0x00, 0xf0, 0x4a, + 0x60, 0x0f, 0x00, 0x61, 0x0a, 0x00, 0xbb, 0x9c, + 0xc8, 0x0f, 0x61, 0x0f, 0x00, 0x61, 0x05, 0x00, + 0xa5, 0xf0, 0x27, 0x61, 0x04, 0x00, 0x61, 0x0f, + 0x00, 0xbb, 0x9d, 0x1b, 0x11, 0xaf, 0xf1, 0x04, + 0x1b, 0x71, 0x1b, 0x1b, 0x61, 0x04, 0x00, 0x61, + 0x0f, 0x00, 0x46, 0x1b, 0x71, 0x1b, 0x48, 0x61, + 0x0f, 0x00, 0x90, 0x62, 0x0f, 0x00, 0x0e, 0xf2, + 0xd2, 0x61, 0x05, 0x00, 0x8f, 0x62, 0x05, 0x00, + 0x0e, 0x61, 0x0a, 0x00, 0x8f, 0x62, 0x0a, 0x00, + 0x0e, 0x61, 0x0a, 0x00, 0x90, 0x62, 0x0a, 0x00, + 0x0e, 0xf3, 0x0a, 0xff, 0x61, 0x02, 0x00, 0x04, + 0x13, 0x01, 0x00, 0x00, 0xad, 0xf0, 0x6e, 0x61, + 0x03, 0x00, 0xf0, 0x38, 0x60, 0x10, 0x00, 0xba, + 0xc8, 0x10, 0x61, 0x10, 0x00, 0x61, 0x05, 0x00, + 0xa5, 0xf0, 0x5a, 0x61, 0x08, 0x00, 0x61, 0x10, + 0x00, 0x1b, 0x11, 0xaf, 0xf1, 0x04, 0x1b, 0x71, + 0x1b, 0x1b, 0x61, 0x03, 0x00, 0x61, 0x04, 0x00, + 0x61, 0x10, 0x00, 0x46, 0x46, 0x1b, 0x71, 0x1b, + 0x48, 0x61, 0x10, 0x00, 0x90, 0x62, 0x10, 0x00, + 0x0e, 0xf2, 0xd0, 0x60, 0x11, 0x00, 0xba, 0xc8, + 0x11, 0x61, 0x11, 0x00, 0x61, 0x05, 0x00, 0xa5, + 0xf0, 0x23, 0x61, 0x08, 0x00, 0x61, 0x11, 0x00, + 0x1b, 0x11, 0xaf, 0xf1, 0x04, 0x1b, 0x71, 0x1b, + 0x1b, 0x38, 0x49, 0x00, 0x00, 0x00, 0x1b, 0x71, + 0x1b, 0x48, 0x61, 0x11, 0x00, 0x90, 0x62, 0x11, + 0x00, 0x0e, 0xf2, 0xd6, 0x0e, 0xf2, 0x15, 0xc8, + 0x12, 0x6b, 0x10, 0x00, 0x00, 0x00, 0xe6, 0x61, + 0x06, 0x00, 0x61, 0x05, 0x00, 0xf6, 0x0e, 0xc7, + 0x12, 0x30, 0x30, 0xba, 0xc8, 0x13, 0x61, 0x05, + 0x00, 0xc8, 0x14, 0x0b, 0x5d, 0x08, 0x00, 0x4e, + 0xc5, 0x00, 0x53, 0x72, 0x00, 0x00, 0x00, 0x04, + 0xc5, 0x01, 0x53, 0x06, 0x00, 0x00, 0x00, 0x04, + 0x28, 0xc5, 0x00, 0xcf, 0xc5, 0x01, 0xd0, 0xc5, + 0x02, 0xd1, 0xc5, 0x03, 0x28, 0xc5, 0x00, 0xd3, + 0x28, +}; + diff --git a/engine/quickjs-ng/builtin-iterator-zip.h b/engine/quickjs-ng/builtin-iterator-zip.h new file mode 100644 index 000000000..b69844655 --- /dev/null +++ b/engine/quickjs-ng/builtin-iterator-zip.h @@ -0,0 +1,336 @@ +/* File generated automatically by the QuickJS-ng compiler. */ + +#include + +const uint32_t qjsc_builtin_iterator_zip_size = 2615; + +const uint8_t qjsc_builtin_iterator_zip[2615] = { + 0x1c, 0x7c, 0x26, 0x10, 0x65, 0x2a, 0x01, 0x1c, + 0x49, 0x74, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, + 0x48, 0x65, 0x6c, 0x70, 0x65, 0x72, 0x01, 0x08, + 0x63, 0x61, 0x6c, 0x6c, 0x01, 0x1e, 0x53, 0x79, + 0x6d, 0x62, 0x6f, 0x6c, 0xb7, 0x69, 0x74, 0x65, + 0x72, 0x61, 0x74, 0x6f, 0x72, 0x01, 0x0a, 0x63, + 0x68, 0x65, 0x63, 0x6b, 0x01, 0x0a, 0x63, 0x6c, + 0x6f, 0x73, 0x65, 0x01, 0x10, 0x63, 0x6c, 0x6f, + 0x73, 0x65, 0x61, 0x6c, 0x6c, 0x01, 0x02, 0x76, + 0x01, 0x02, 0x73, 0x01, 0x08, 0x69, 0x74, 0x65, + 0x72, 0x01, 0x0c, 0x6d, 0x65, 0x74, 0x68, 0x6f, + 0x64, 0x01, 0x02, 0x65, 0x01, 0x0a, 0x69, 0x74, + 0x65, 0x72, 0x73, 0x01, 0x0a, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x01, 0x04, 0x65, 0x78, 0x01, 0x02, + 0x69, 0x01, 0x12, 0x69, 0x74, 0x65, 0x72, 0x61, + 0x62, 0x6c, 0x65, 0x73, 0x01, 0x0e, 0x6f, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x01, 0x08, 0x6d, + 0x6f, 0x64, 0x65, 0x01, 0x0e, 0x70, 0x61, 0x64, + 0x64, 0x69, 0x6e, 0x67, 0x01, 0x08, 0x70, 0x61, + 0x64, 0x73, 0x01, 0x0a, 0x6e, 0x65, 0x78, 0x74, + 0x73, 0x01, 0x16, 0x70, 0x61, 0x64, 0x64, 0x69, + 0x6e, 0x67, 0x69, 0x74, 0x65, 0x72, 0x01, 0x1a, + 0x69, 0x74, 0x65, 0x72, 0x61, 0x62, 0x6c, 0x65, + 0x73, 0x69, 0x74, 0x65, 0x72, 0x01, 0x08, 0x69, + 0x74, 0x65, 0x6d, 0x01, 0x02, 0x74, 0x01, 0x0a, + 0x73, 0x74, 0x61, 0x74, 0x65, 0x01, 0x0a, 0x61, + 0x6c, 0x69, 0x76, 0x65, 0x01, 0x0a, 0x64, 0x6f, + 0x6e, 0x65, 0x73, 0x01, 0x0e, 0x72, 0x65, 0x73, + 0x75, 0x6c, 0x74, 0x73, 0x01, 0x0c, 0x72, 0x65, + 0x73, 0x75, 0x6c, 0x74, 0x01, 0x1c, 0x72, 0x75, + 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x20, 0x7a, 0x69, + 0x70, 0x70, 0x65, 0x72, 0x01, 0x06, 0x62, 0x75, + 0x67, 0x01, 0x0e, 0x6c, 0x6f, 0x6e, 0x67, 0x65, + 0x73, 0x74, 0x01, 0x0c, 0x73, 0x74, 0x72, 0x69, + 0x63, 0x74, 0x01, 0x22, 0x6d, 0x69, 0x73, 0x6d, + 0x61, 0x74, 0x63, 0x68, 0x65, 0x64, 0x20, 0x69, + 0x6e, 0x70, 0x75, 0x74, 0x73, 0x01, 0x10, 0x73, + 0x68, 0x6f, 0x72, 0x74, 0x65, 0x73, 0x74, 0x01, + 0x16, 0x62, 0x75, 0x67, 0x3a, 0x20, 0x73, 0x74, + 0x61, 0x74, 0x65, 0x3d, 0x01, 0x1a, 0x62, 0x61, + 0x64, 0x20, 0x69, 0x74, 0x65, 0x72, 0x61, 0x62, + 0x6c, 0x65, 0x73, 0x01, 0x16, 0x62, 0x61, 0x64, + 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x01, 0x10, 0x62, 0x61, 0x64, 0x20, 0x6d, 0x6f, + 0x64, 0x65, 0x01, 0x16, 0x62, 0x61, 0x64, 0x20, + 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x01, + 0x18, 0x62, 0x61, 0x64, 0x20, 0x69, 0x74, 0x65, + 0x72, 0x61, 0x74, 0x6f, 0x72, 0x0c, 0x00, 0x02, + 0x00, 0xa8, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, + 0x00, 0x01, 0x04, 0x01, 0xaa, 0x01, 0x00, 0x00, + 0x00, 0x0c, 0x43, 0x02, 0x00, 0x00, 0x05, 0x03, + 0x05, 0x01, 0x08, 0x00, 0x04, 0x0c, 0x08, 0xe4, + 0x03, 0x00, 0x01, 0x40, 0x07, 0xb8, 0x03, 0x00, + 0x01, 0x40, 0x03, 0xb4, 0x03, 0x00, 0x01, 0x40, + 0x00, 0xe6, 0x03, 0x00, 0x01, 0x40, 0x01, 0xe8, + 0x03, 0x00, 0x01, 0x40, 0x06, 0xea, 0x03, 0x00, + 0x00, 0x40, 0x05, 0xec, 0x03, 0x00, 0x01, 0x40, + 0x02, 0xee, 0x03, 0x00, 0x02, 0x40, 0x04, 0x0c, + 0x43, 0x02, 0x00, 0xea, 0x03, 0x02, 0x00, 0x02, + 0x03, 0x00, 0x01, 0x00, 0x17, 0x02, 0xf0, 0x03, + 0x00, 0x01, 0x00, 0xf2, 0x03, 0x00, 0x01, 0x00, + 0xb4, 0x03, 0x02, 0x01, 0xd7, 0x96, 0x04, 0x4d, + 0x00, 0x00, 0x00, 0xad, 0xf0, 0x07, 0xd7, 0x07, + 0xae, 0xf0, 0x02, 0x29, 0xe3, 0x11, 0xd8, 0x21, + 0x01, 0x00, 0x30, 0x0c, 0x43, 0x02, 0x00, 0xec, + 0x03, 0x01, 0x02, 0x01, 0x04, 0x00, 0x01, 0x00, + 0x2d, 0x03, 0xf4, 0x03, 0x00, 0x01, 0x00, 0xf6, + 0x03, 0x02, 0x00, 0x20, 0xf8, 0x03, 0x05, 0x00, + 0x03, 0xe6, 0x03, 0x03, 0x01, 0x6b, 0x22, 0x00, + 0x00, 0x00, 0x60, 0x00, 0x00, 0xd7, 0xf1, 0x04, + 0x06, 0x6e, 0x28, 0xd7, 0x40, 0x06, 0x00, 0x00, + 0x00, 0xcf, 0x61, 0x00, 0x00, 0xf0, 0x08, 0xe3, + 0xd7, 0x61, 0x00, 0x00, 0xf6, 0x0e, 0x0e, 0x29, + 0xd0, 0x6b, 0x07, 0x00, 0x00, 0x00, 0xcc, 0x6e, + 0x28, 0x30, 0x0c, 0x43, 0x02, 0x00, 0xee, 0x03, + 0x02, 0x04, 0x02, 0x03, 0x00, 0x01, 0x00, 0x54, + 0x06, 0xfa, 0x03, 0x00, 0x01, 0x00, 0xfc, 0x03, + 0x00, 0x01, 0x00, 0xfe, 0x03, 0x01, 0x00, 0x20, + 0x80, 0x04, 0x02, 0x01, 0x20, 0xf4, 0x03, 0x03, + 0x02, 0x20, 0xf8, 0x03, 0x03, 0x03, 0x20, 0xec, + 0x03, 0x01, 0x00, 0x60, 0x00, 0x00, 0x38, 0x49, + 0x00, 0x00, 0x00, 0xcf, 0x60, 0x01, 0x00, 0xd8, + 0xd0, 0x61, 0x01, 0x00, 0x8f, 0x62, 0x01, 0x00, + 0xba, 0xa7, 0xf0, 0x38, 0x60, 0x03, 0x00, 0x60, + 0x02, 0x00, 0xd7, 0x61, 0x01, 0x00, 0x46, 0xd1, + 0xd7, 0x61, 0x01, 0x00, 0x1b, 0x11, 0xaf, 0xf1, + 0x04, 0x1b, 0x71, 0x1b, 0x1b, 0x38, 0x49, 0x00, + 0x00, 0x00, 0x1b, 0x71, 0x1b, 0x48, 0xe3, 0x61, + 0x02, 0x00, 0xf5, 0xd2, 0x61, 0x00, 0x00, 0xf1, + 0xc9, 0x61, 0x03, 0x00, 0x11, 0x62, 0x00, 0x00, + 0x0e, 0xf2, 0xbf, 0x61, 0x00, 0x00, 0x28, 0x0c, + 0x41, 0x02, 0x00, 0xb8, 0x02, 0x02, 0x1a, 0x01, + 0x05, 0x07, 0x08, 0x02, 0x88, 0x06, 0x1c, 0x82, + 0x04, 0x00, 0x01, 0x00, 0x84, 0x04, 0x00, 0x01, + 0x00, 0x82, 0x04, 0x01, 0xff, 0xff, 0xff, 0xff, + 0x0f, 0x20, 0x84, 0x04, 0x01, 0x01, 0x20, 0x86, + 0x04, 0x02, 0x00, 0x60, 0x03, 0x88, 0x04, 0x02, + 0x03, 0x20, 0x8a, 0x04, 0x02, 0x04, 0x60, 0x04, + 0xfa, 0x03, 0x02, 0x05, 0x60, 0x02, 0x8c, 0x04, + 0x02, 0x06, 0x60, 0x05, 0xfc, 0x03, 0x02, 0x07, + 0x60, 0x01, 0x8e, 0x04, 0x02, 0x08, 0x20, 0x90, + 0x04, 0x02, 0x09, 0x20, 0xe4, 0x01, 0x09, 0x1a, + 0x20, 0x92, 0x04, 0x0b, 0x0b, 0x20, 0xf8, 0x03, + 0x0d, 0x0f, 0x03, 0xf4, 0x03, 0x0b, 0x0c, 0x20, + 0xf6, 0x03, 0x0b, 0x0e, 0x20, 0xe4, 0x01, 0x13, + 0x0b, 0x20, 0x80, 0x04, 0x13, 0x10, 0x20, 0xe2, + 0x01, 0x13, 0x11, 0x20, 0x88, 0x01, 0x15, 0x16, + 0x20, 0x92, 0x04, 0x16, 0x13, 0x20, 0xf8, 0x03, + 0x17, 0x13, 0x03, 0x94, 0x04, 0x13, 0x12, 0x20, + 0xfe, 0x03, 0x1c, 0x16, 0x20, 0xf8, 0x03, 0x1f, + 0x1a, 0x03, 0x96, 0x04, 0x02, 0x0a, 0x60, 0x00, + 0x98, 0x04, 0x02, 0x19, 0x60, 0x06, 0xb4, 0x03, + 0x02, 0x01, 0xb8, 0x03, 0x01, 0x01, 0xe6, 0x03, + 0x03, 0x01, 0xee, 0x03, 0x02, 0x00, 0xea, 0x03, + 0x00, 0x00, 0xe8, 0x03, 0x04, 0x01, 0xec, 0x03, + 0x01, 0x00, 0xe4, 0x03, 0x00, 0x01, 0x0c, 0x42, + 0x03, 0x00, 0x00, 0x00, 0x08, 0x00, 0x05, 0x00, + 0x0b, 0x00, 0xea, 0x04, 0x08, 0x9a, 0x04, 0x01, + 0x00, 0x20, 0xe6, 0x01, 0x01, 0x01, 0x20, 0x9c, + 0x04, 0x01, 0x02, 0x20, 0x80, 0x04, 0x03, 0x03, + 0x20, 0xf4, 0x03, 0x04, 0x04, 0x20, 0x9e, 0x04, + 0x04, 0x05, 0x20, 0xf8, 0x03, 0x09, 0x06, 0x03, + 0xfe, 0x03, 0x10, 0x06, 0x20, 0x96, 0x04, 0x18, + 0x10, 0xb4, 0x03, 0x00, 0x02, 0xb8, 0x03, 0x01, + 0x02, 0xfc, 0x03, 0x07, 0x10, 0xfa, 0x03, 0x05, + 0x10, 0x86, 0x04, 0x02, 0x10, 0x8a, 0x04, 0x04, + 0x10, 0xe6, 0x03, 0x02, 0x02, 0x8c, 0x04, 0x06, + 0x10, 0x98, 0x04, 0x19, 0x10, 0xee, 0x03, 0x03, + 0x02, 0x60, 0x02, 0x00, 0x60, 0x01, 0x00, 0x60, + 0x00, 0x00, 0x64, 0x00, 0x00, 0x11, 0xba, 0xad, + 0xf1, 0x06, 0x11, 0xbb, 0xad, 0xf0, 0x09, 0xbc, + 0x11, 0x65, 0x00, 0x00, 0x0e, 0xf2, 0x33, 0x11, + 0xbc, 0xad, 0xf0, 0x0c, 0xe4, 0x11, 0x04, 0x10, + 0x01, 0x00, 0x00, 0x21, 0x01, 0x00, 0x30, 0x11, + 0xbd, 0xad, 0xf0, 0x13, 0x0b, 0x38, 0x49, 0x00, + 0x00, 0x00, 0x4b, 0x44, 0x00, 0x00, 0x00, 0x0a, + 0x4b, 0x71, 0x00, 0x00, 0x00, 0x28, 0xe5, 0x11, + 0x04, 0x11, 0x01, 0x00, 0x00, 0x21, 0x01, 0x00, + 0x30, 0x0e, 0xba, 0xcf, 0xba, 0xd0, 0x26, 0x00, + 0x00, 0xd1, 0x60, 0x03, 0x00, 0xba, 0xd2, 0x61, + 0x03, 0x00, 0x64, 0x03, 0x00, 0xa5, 0x68, 0xcf, + 0x01, 0x00, 0x00, 0x60, 0x05, 0x00, 0x60, 0x04, + 0x00, 0x64, 0x04, 0x00, 0x61, 0x03, 0x00, 0x46, + 0xc8, 0x04, 0x61, 0x04, 0x00, 0xf1, 0x34, 0x64, + 0x05, 0x00, 0x04, 0x12, 0x01, 0x00, 0x00, 0xae, + 0xf0, 0x0c, 0xe5, 0x11, 0x04, 0x11, 0x01, 0x00, + 0x00, 0x21, 0x01, 0x00, 0x30, 0x61, 0x02, 0x00, + 0x61, 0x03, 0x00, 0x1b, 0x11, 0xaf, 0xf1, 0x04, + 0x1b, 0x71, 0x1b, 0x1b, 0x64, 0x06, 0x00, 0x61, + 0x03, 0x00, 0x46, 0x1b, 0x71, 0x1b, 0x48, 0xf3, + 0x7b, 0x01, 0x06, 0xc8, 0x05, 0x6b, 0x1a, 0x00, + 0x00, 0x00, 0x5d, 0x07, 0x00, 0x61, 0x04, 0x00, + 0x64, 0x08, 0x00, 0x61, 0x03, 0x00, 0x46, 0xf6, + 0x11, 0x62, 0x05, 0x00, 0x0e, 0x0e, 0xf2, 0x35, + 0xc8, 0x06, 0x6b, 0x30, 0x00, 0x00, 0x00, 0xba, + 0x11, 0x65, 0x09, 0x00, 0x0e, 0x64, 0x04, 0x00, + 0x61, 0x03, 0x00, 0x1b, 0x11, 0xaf, 0xf1, 0x04, + 0x1b, 0x71, 0x1b, 0x1b, 0x38, 0x49, 0x00, 0x00, + 0x00, 0x1b, 0x71, 0x1b, 0x48, 0x5d, 0x0a, 0x00, + 0x64, 0x04, 0x00, 0x64, 0x03, 0x00, 0xf6, 0x0e, + 0xc7, 0x06, 0x30, 0x30, 0x61, 0x05, 0x00, 0x40, + 0x71, 0x00, 0x00, 0x00, 0xf1, 0x4f, 0x64, 0x05, + 0x00, 0x04, 0x13, 0x01, 0x00, 0x00, 0xad, 0xf0, + 0x1e, 0x61, 0x00, 0x00, 0xba, 0xa7, 0xf0, 0x17, + 0x5d, 0x0a, 0x00, 0x64, 0x04, 0x00, 0x64, 0x03, + 0x00, 0xf6, 0x0e, 0xe4, 0x11, 0x04, 0x14, 0x01, + 0x00, 0x00, 0x21, 0x01, 0x00, 0x30, 0x61, 0x02, + 0x00, 0x61, 0x03, 0x00, 0x1b, 0x11, 0xaf, 0xf1, + 0x04, 0x1b, 0x71, 0x1b, 0x1b, 0x61, 0x05, 0x00, + 0x40, 0x44, 0x00, 0x00, 0x00, 0x1b, 0x71, 0x1b, + 0x48, 0x61, 0x01, 0x00, 0x90, 0x62, 0x01, 0x00, + 0x0e, 0xf3, 0xd1, 0x00, 0x64, 0x09, 0x00, 0x8f, + 0x65, 0x09, 0x00, 0x0e, 0x61, 0x00, 0x00, 0x90, + 0x62, 0x00, 0x00, 0x0e, 0x64, 0x04, 0x00, 0x61, + 0x03, 0x00, 0x1b, 0x11, 0xaf, 0xf1, 0x04, 0x1b, + 0x71, 0x1b, 0x1b, 0x38, 0x49, 0x00, 0x00, 0x00, + 0x1b, 0x71, 0x1b, 0x48, 0x64, 0x05, 0x00, 0x60, + 0x07, 0x00, 0x11, 0x04, 0x15, 0x01, 0x00, 0x00, + 0xad, 0xf0, 0x2e, 0x5d, 0x0a, 0x00, 0x64, 0x04, + 0x00, 0x64, 0x03, 0x00, 0xf6, 0xc8, 0x07, 0x61, + 0x07, 0x00, 0xf0, 0x05, 0x61, 0x07, 0x00, 0x30, + 0xbd, 0x11, 0x65, 0x00, 0x00, 0x0e, 0x0b, 0x38, + 0x49, 0x00, 0x00, 0x00, 0x4b, 0x44, 0x00, 0x00, + 0x00, 0x0a, 0x4b, 0x71, 0x00, 0x00, 0x00, 0x28, + 0x11, 0x04, 0x12, 0x01, 0x00, 0x00, 0xad, 0xf0, + 0x3c, 0x64, 0x09, 0x00, 0xbb, 0xa5, 0xf0, 0x19, + 0xbd, 0x11, 0x65, 0x00, 0x00, 0x0e, 0x0b, 0x38, + 0x49, 0x00, 0x00, 0x00, 0x4b, 0x44, 0x00, 0x00, + 0x00, 0x0a, 0x4b, 0x71, 0x00, 0x00, 0x00, 0x28, + 0x61, 0x02, 0x00, 0x61, 0x03, 0x00, 0x1b, 0x11, + 0xaf, 0xf1, 0x04, 0x1b, 0x71, 0x1b, 0x1b, 0x64, + 0x06, 0x00, 0x61, 0x03, 0x00, 0x46, 0x1b, 0x71, + 0x1b, 0x48, 0xf2, 0x27, 0x11, 0x04, 0x13, 0x01, + 0x00, 0x00, 0xad, 0xf0, 0x1e, 0x61, 0x01, 0x00, + 0xba, 0xa7, 0xf0, 0x17, 0x5d, 0x0a, 0x00, 0x64, + 0x04, 0x00, 0x64, 0x03, 0x00, 0xf6, 0x0e, 0xe4, + 0x11, 0x04, 0x14, 0x01, 0x00, 0x00, 0x21, 0x01, + 0x00, 0x30, 0x0e, 0x61, 0x03, 0x00, 0x90, 0x62, + 0x03, 0x00, 0x0e, 0xf3, 0x2b, 0xfe, 0x61, 0x01, + 0x00, 0xba, 0xad, 0xf0, 0x19, 0xbd, 0x11, 0x65, + 0x00, 0x00, 0x0e, 0x0b, 0x38, 0x49, 0x00, 0x00, + 0x00, 0x4b, 0x44, 0x00, 0x00, 0x00, 0x0a, 0x4b, + 0x71, 0x00, 0x00, 0x00, 0x28, 0xbb, 0x11, 0x65, + 0x00, 0x00, 0x0e, 0x0b, 0x61, 0x02, 0x00, 0x4b, + 0x44, 0x00, 0x00, 0x00, 0x09, 0x4b, 0x71, 0x00, + 0x00, 0x00, 0x28, 0x0c, 0x42, 0x03, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x06, 0x00, 0x06, 0x00, 0x88, + 0x01, 0x01, 0xfe, 0x03, 0x01, 0x00, 0x20, 0x96, + 0x04, 0x18, 0x10, 0xb4, 0x03, 0x00, 0x02, 0xb8, + 0x03, 0x01, 0x02, 0xee, 0x03, 0x03, 0x02, 0xfa, + 0x03, 0x05, 0x10, 0xfc, 0x03, 0x07, 0x10, 0x60, + 0x00, 0x00, 0x64, 0x00, 0x00, 0x11, 0xba, 0xad, + 0xf0, 0x09, 0xbd, 0x11, 0x65, 0x00, 0x00, 0x0e, + 0xf2, 0x4b, 0x11, 0xbb, 0xad, 0xf0, 0x09, 0xbc, + 0x11, 0x65, 0x00, 0x00, 0x0e, 0xf2, 0x3e, 0x11, + 0xbc, 0xad, 0xf0, 0x0c, 0xe4, 0x11, 0x04, 0x10, + 0x01, 0x00, 0x00, 0x21, 0x01, 0x00, 0x30, 0x11, + 0xbd, 0xad, 0xf0, 0x13, 0x0b, 0x38, 0x49, 0x00, + 0x00, 0x00, 0x4b, 0x44, 0x00, 0x00, 0x00, 0x0a, + 0x4b, 0x71, 0x00, 0x00, 0x00, 0x28, 0xe5, 0x11, + 0x04, 0x16, 0x01, 0x00, 0x00, 0x41, 0x64, 0x00, + 0x00, 0x00, 0x64, 0x00, 0x00, 0x24, 0x01, 0x00, + 0x21, 0x01, 0x00, 0x30, 0x0e, 0xe6, 0x64, 0x04, + 0x00, 0x64, 0x05, 0x00, 0xf6, 0xcf, 0x61, 0x00, + 0x00, 0xf0, 0x05, 0x61, 0x00, 0x00, 0x30, 0xbd, + 0x11, 0x65, 0x00, 0x00, 0x0e, 0x0b, 0x38, 0x49, + 0x00, 0x00, 0x00, 0x4b, 0x44, 0x00, 0x00, 0x00, + 0x0a, 0x4b, 0x71, 0x00, 0x00, 0x00, 0x28, 0x60, + 0x01, 0x00, 0x60, 0x00, 0x00, 0xd7, 0xcf, 0xd8, + 0x11, 0xf8, 0xf0, 0x08, 0x0e, 0x38, 0x49, 0x00, + 0x00, 0x00, 0xe0, 0xd0, 0x60, 0x19, 0x00, 0x60, + 0x18, 0x00, 0x60, 0x09, 0x00, 0x60, 0x08, 0x00, + 0x60, 0x07, 0x00, 0x60, 0x06, 0x00, 0x60, 0x05, + 0x00, 0x60, 0x04, 0x00, 0x60, 0x03, 0x00, 0x60, + 0x02, 0x00, 0x5d, 0x04, 0x00, 0xd7, 0x04, 0x17, + 0x01, 0x00, 0x00, 0xf6, 0x0e, 0xd8, 0x38, 0x49, + 0x00, 0x00, 0x00, 0xad, 0xf0, 0x06, 0x0c, 0x07, + 0xdc, 0xf2, 0x0c, 0x5d, 0x04, 0x00, 0xd8, 0x04, + 0x18, 0x01, 0x00, 0x00, 0xf6, 0x0e, 0xd8, 0x40, + 0x03, 0x01, 0x00, 0x00, 0xd1, 0x61, 0x02, 0x00, + 0x38, 0x49, 0x00, 0x00, 0x00, 0xad, 0xf0, 0x0b, + 0x04, 0x15, 0x01, 0x00, 0x00, 0x11, 0x62, 0x02, + 0x00, 0x0e, 0x61, 0x02, 0x00, 0x04, 0x13, 0x01, + 0x00, 0x00, 0xad, 0x11, 0xf1, 0x18, 0x0e, 0x61, + 0x02, 0x00, 0x04, 0x12, 0x01, 0x00, 0x00, 0xad, + 0x11, 0xf1, 0x0b, 0x0e, 0x61, 0x02, 0x00, 0x04, + 0x15, 0x01, 0x00, 0x00, 0xad, 0xf1, 0x0c, 0xe3, + 0x11, 0x04, 0x19, 0x01, 0x00, 0x00, 0x21, 0x01, + 0x00, 0x30, 0x38, 0x49, 0x00, 0x00, 0x00, 0xd2, + 0x61, 0x02, 0x00, 0x04, 0x12, 0x01, 0x00, 0x00, + 0xad, 0xf0, 0x24, 0xd8, 0x40, 0x04, 0x01, 0x00, + 0x00, 0x11, 0x62, 0x03, 0x00, 0x0e, 0x61, 0x03, + 0x00, 0x38, 0x49, 0x00, 0x00, 0x00, 0xae, 0xf0, + 0x0e, 0x5d, 0x04, 0x00, 0x61, 0x03, 0x00, 0x04, + 0x1a, 0x01, 0x00, 0x00, 0xf6, 0x0e, 0x26, 0x00, + 0x00, 0xc8, 0x04, 0x26, 0x00, 0x00, 0xc8, 0x05, + 0x26, 0x00, 0x00, 0xc8, 0x06, 0xba, 0xc8, 0x07, + 0x38, 0x49, 0x00, 0x00, 0x00, 0xc8, 0x08, 0xd7, + 0x5d, 0x05, 0x00, 0x47, 0x24, 0x00, 0x00, 0xc8, + 0x09, 0x6b, 0xcb, 0x01, 0x00, 0x00, 0x60, 0x0a, + 0x00, 0x61, 0x09, 0x00, 0x40, 0x72, 0x00, 0x00, + 0x00, 0xc8, 0x0a, 0x60, 0x0e, 0x00, 0x60, 0x0d, + 0x00, 0x60, 0x0b, 0x00, 0x06, 0xc8, 0x0b, 0x6b, + 0x14, 0x00, 0x00, 0x00, 0xe5, 0x61, 0x09, 0x00, + 0x61, 0x0a, 0x00, 0xf6, 0x11, 0x62, 0x0b, 0x00, + 0x0e, 0x0e, 0xf2, 0x16, 0xc8, 0x0c, 0x6b, 0x11, + 0x00, 0x00, 0x00, 0x38, 0x49, 0x00, 0x00, 0x00, + 0x11, 0x62, 0x09, 0x00, 0x0e, 0xc7, 0x0c, 0x30, + 0x30, 0x61, 0x0b, 0x00, 0x40, 0x71, 0x00, 0x00, + 0x00, 0xf1, 0x6f, 0x61, 0x0b, 0x00, 0x40, 0x44, + 0x00, 0x00, 0x00, 0xc8, 0x0d, 0x5d, 0x04, 0x00, + 0x61, 0x0d, 0x00, 0x04, 0x1b, 0x01, 0x00, 0x00, + 0xf6, 0x0e, 0x61, 0x0d, 0x00, 0x5d, 0x05, 0x00, + 0x46, 0xc8, 0x0e, 0x61, 0x0e, 0x00, 0xf0, 0x0e, + 0xe5, 0x61, 0x0d, 0x00, 0x61, 0x0e, 0x00, 0xf6, + 0x11, 0x62, 0x0d, 0x00, 0x0e, 0x61, 0x05, 0x00, + 0x61, 0x07, 0x00, 0x1b, 0x11, 0xaf, 0xf1, 0x04, + 0x1b, 0x71, 0x1b, 0x1b, 0x61, 0x0d, 0x00, 0x1b, + 0x71, 0x1b, 0x48, 0x61, 0x06, 0x00, 0x61, 0x07, + 0x00, 0x1b, 0x11, 0xaf, 0xf1, 0x04, 0x1b, 0x71, + 0x1b, 0x1b, 0x61, 0x0d, 0x00, 0x40, 0x72, 0x00, + 0x00, 0x00, 0x1b, 0x71, 0x1b, 0x48, 0x61, 0x07, + 0x00, 0x90, 0x62, 0x07, 0x00, 0x0e, 0xf3, 0x54, + 0xff, 0x38, 0x49, 0x00, 0x00, 0x00, 0x11, 0x62, + 0x09, 0x00, 0x0e, 0x61, 0x03, 0x00, 0x68, 0xfb, + 0x00, 0x00, 0x00, 0x60, 0x15, 0x00, 0x60, 0x11, + 0x00, 0x60, 0x10, 0x00, 0x60, 0x0f, 0x00, 0x61, + 0x03, 0x00, 0x5d, 0x05, 0x00, 0x47, 0x24, 0x00, + 0x00, 0x11, 0x62, 0x08, 0x00, 0x0e, 0x61, 0x08, + 0x00, 0x40, 0x72, 0x00, 0x00, 0x00, 0xc8, 0x0f, + 0xba, 0xc8, 0x10, 0x09, 0xc8, 0x11, 0x61, 0x10, + 0x00, 0x61, 0x07, 0x00, 0xa5, 0xf0, 0x70, 0x60, + 0x12, 0x00, 0x06, 0xc8, 0x12, 0x6b, 0x2e, 0x00, + 0x00, 0x00, 0x60, 0x13, 0x00, 0xe5, 0x61, 0x08, + 0x00, 0x61, 0x0f, 0x00, 0xf6, 0xc8, 0x13, 0x61, + 0x13, 0x00, 0x40, 0x71, 0x00, 0x00, 0x00, 0x11, + 0x62, 0x11, 0x00, 0x0e, 0x61, 0x13, 0x00, 0x40, + 0x44, 0x00, 0x00, 0x00, 0x11, 0x62, 0x12, 0x00, + 0x0e, 0x0e, 0xf2, 0x16, 0xc8, 0x14, 0x6b, 0x11, + 0x00, 0x00, 0x00, 0x38, 0x49, 0x00, 0x00, 0x00, + 0x11, 0x62, 0x08, 0x00, 0x0e, 0xc7, 0x14, 0x30, + 0x30, 0x61, 0x11, 0x00, 0xf1, 0x21, 0x61, 0x04, + 0x00, 0x61, 0x10, 0x00, 0x1b, 0x11, 0xaf, 0xf1, + 0x04, 0x1b, 0x71, 0x1b, 0x1b, 0x61, 0x12, 0x00, + 0x1b, 0x71, 0x1b, 0x48, 0x61, 0x10, 0x00, 0x90, + 0x62, 0x10, 0x00, 0x0e, 0xf2, 0x89, 0x61, 0x08, + 0x00, 0xc8, 0x15, 0x38, 0x49, 0x00, 0x00, 0x00, + 0x11, 0x62, 0x08, 0x00, 0x0e, 0x61, 0x11, 0x00, + 0xf1, 0x16, 0x60, 0x16, 0x00, 0x5d, 0x06, 0x00, + 0x61, 0x15, 0x00, 0xf5, 0xc8, 0x16, 0x61, 0x16, + 0x00, 0xf0, 0x05, 0x61, 0x16, 0x00, 0x30, 0x61, + 0x10, 0x00, 0x61, 0x07, 0x00, 0xa5, 0xf0, 0x23, + 0x61, 0x04, 0x00, 0x61, 0x10, 0x00, 0x1b, 0x11, + 0xaf, 0xf1, 0x04, 0x1b, 0x71, 0x1b, 0x1b, 0x38, + 0x49, 0x00, 0x00, 0x00, 0x1b, 0x71, 0x1b, 0x48, + 0x61, 0x10, 0x00, 0x90, 0x62, 0x10, 0x00, 0x0e, + 0xf2, 0xd6, 0x0e, 0xf2, 0x25, 0xc8, 0x17, 0x6b, + 0x20, 0x00, 0x00, 0x00, 0xe6, 0x61, 0x05, 0x00, + 0x61, 0x07, 0x00, 0xf6, 0x0e, 0x5d, 0x06, 0x00, + 0x61, 0x09, 0x00, 0xf5, 0x0e, 0x5d, 0x06, 0x00, + 0x61, 0x08, 0x00, 0xf5, 0x0e, 0xc7, 0x17, 0x30, + 0x30, 0xba, 0xc8, 0x18, 0x61, 0x07, 0x00, 0xc8, + 0x19, 0x0b, 0x5d, 0x07, 0x00, 0x4e, 0xc5, 0x00, + 0x53, 0x72, 0x00, 0x00, 0x00, 0x04, 0xc5, 0x01, + 0x53, 0x06, 0x00, 0x00, 0x00, 0x04, 0x28, 0xc5, + 0x00, 0xcf, 0xc5, 0x01, 0xd0, 0xc5, 0x02, 0xd1, + 0xc5, 0x03, 0x28, 0xc5, 0x00, 0xd3, 0x28, +}; + diff --git a/engine/quickjs-ng/cutils.h b/engine/quickjs-ng/cutils.h new file mode 100644 index 000000000..d441bba5e --- /dev/null +++ b/engine/quickjs-ng/cutils.h @@ -0,0 +1,2021 @@ +/* + * C utilities + * + * Copyright (c) 2017 Fabrice Bellard + * Copyright (c) 2018 Charlie Gordon + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef CUTILS_H +#define CUTILS_H + +#include +#include +#include +#include +#if !defined(_MSC_VER) +#include +#endif +#if defined(__APPLE__) +#include +#endif +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(_MSC_VER) +#include +#define alloca _alloca +#define ssize_t ptrdiff_t +#endif +#if defined(__APPLE__) +#include +#elif defined(__linux__) || defined(__ANDROID__) || defined(__CYGWIN__) || defined(__GLIBC__) || defined(__wasi__) +#include +#elif defined(__FreeBSD__) +#include +#elif defined(_WIN32) +#include +#include +#include // _beginthread +#endif +#if !defined(_WIN32) && !defined(EMSCRIPTEN) && !defined(__wasi__) && !defined(__DJGPP) +#include +#include +#endif +#if !defined(_WIN32) +#include +#include +#endif + +#if defined(__sun) +#undef __maybe_unused +#endif + +#if defined(_MSC_VER) && !defined(__clang__) +# define likely(x) (x) +# define unlikely(x) (x) +# define no_inline __declspec(noinline) +# define __maybe_unused +# define __attribute__(x) +# define __attribute(x) +#else +# define likely(x) __builtin_expect(!!(x), 1) +# define unlikely(x) __builtin_expect(!!(x), 0) +# define no_inline __attribute__((noinline)) +# define __maybe_unused __attribute__((unused)) +#endif + +#ifndef offsetof +#define offsetof(type, field) ((size_t) &((type *)0)->field) +#endif +#ifndef countof +#define countof(x) (sizeof(x) / sizeof((x)[0])) +#ifndef endof +#define endof(x) ((x) + countof(x)) +#endif +#endif +#ifndef container_of +/* return the pointer of type 'type *' containing 'ptr' as field 'member' */ +#define container_of(ptr, type, member) ((type *)((uint8_t *)(ptr) - offsetof(type, member))) +#endif + +#if defined(_MSC_VER) || defined(__cplusplus) +#define minimum_length(n) n +#else +#define minimum_length(n) static n +#endif + +/* Borrowed from Folly */ +#ifndef JS_PRINTF_FORMAT +/* Clang on Windows doesn't seem to support _Printf_format_string_ */ +#if defined(_MSC_VER) && !defined(__clang__) +#include +#define JS_PRINTF_FORMAT _Printf_format_string_ +#define JS_PRINTF_FORMAT_ATTR(format_param, dots_param) +#else +#define JS_PRINTF_FORMAT +#if !defined(__clang__) && defined(__GNUC__) +#define JS_PRINTF_FORMAT_ATTR(format_param, dots_param) \ + __attribute__((format(gnu_printf, format_param, dots_param))) +#else +#define JS_PRINTF_FORMAT_ATTR(format_param, dots_param) \ + __attribute__((format(printf, format_param, dots_param))) +#endif +#endif +#endif + +#if defined(PATH_MAX) +# define JS__PATH_MAX PATH_MAX +#elif defined(_WIN32) +# define JS__PATH_MAX 32767 +#else +# define JS__PATH_MAX 8192 +#endif + +static inline void js__pstrcpy(char *buf, int buf_size, const char *str); +static inline char *js__pstrcat(char *buf, int buf_size, const char *s); +static inline int js__strstart(const char *str, const char *val, const char **ptr); +static inline int js__has_suffix(const char *str, const char *suffix); + +static inline uint8_t is_be(void) { + union { + uint16_t a; + uint8_t b; + } u = { 0x100 }; + return u.b; +} + +static inline int max_int(int a, int b) +{ + if (a > b) + return a; + else + return b; +} + +static inline int min_int(int a, int b) +{ + if (a < b) + return a; + else + return b; +} + +static inline uint32_t max_uint32(uint32_t a, uint32_t b) +{ + if (a > b) + return a; + else + return b; +} + +static inline uint32_t min_uint32(uint32_t a, uint32_t b) +{ + if (a < b) + return a; + else + return b; +} + +static inline int64_t max_int64(int64_t a, int64_t b) +{ + if (a > b) + return a; + else + return b; +} + +static inline int64_t min_int64(int64_t a, int64_t b) +{ + if (a < b) + return a; + else + return b; +} + +static inline uint32_t hash32(uint32_t a) +{ + // use the negative of the golden ratio, it spreads out the bits nicely + // and is what the linux kernel does + // + // the golden ratio phi is defined as (1+sqrt(5))/2 or 1 + (sqrt(5)-1)/2 + // (approx. 1.618033988), and negated is round(2**32/phi**2) = 0x61c88647 + return a * 0x61c88647; +} + +static inline uint64_t hash64(uint64_t a) +{ + return a * 0x61c8864680b583ebULL; +} + +static inline uintptr_t hash_uintptr(uintptr_t a) +{ + if (sizeof(uintptr_t) == sizeof(uint32_t)) { + return hash32(a); + } else { + return hash64(a); + } +} + +/* WARNING: undefined if a = 0 */ +static inline int clz32(unsigned int a) +{ +#if defined(_MSC_VER) && !defined(__clang__) + unsigned long index; + _BitScanReverse(&index, a); + return 31 - index; +#else + return __builtin_clz(a); +#endif +} + +/* WARNING: undefined if a = 0 */ +static inline int clz64(uint64_t a) +{ +#if defined(_MSC_VER) && !defined(__clang__) +#if INTPTR_MAX == INT64_MAX + unsigned long index; + _BitScanReverse64(&index, a); + return 63 - index; +#else + if (a >> 32) + return clz32((unsigned)(a >> 32)); + else + return clz32((unsigned)a) + 32; +#endif +#else + return __builtin_clzll(a); +#endif +} + +static inline uintptr_t clz_uintptr(uintptr_t a) +{ + if (sizeof(uintptr_t) == sizeof(uint32_t)) { + return clz32(a); + } else { + return clz64(a); + } +} + +/* WARNING: undefined if a = 0 */ +static inline int ctz32(unsigned int a) +{ +#if defined(_MSC_VER) && !defined(__clang__) + unsigned long index; + _BitScanForward(&index, a); + return index; +#else + return __builtin_ctz(a); +#endif +} + +/* WARNING: undefined if a = 0 */ +static inline int ctz64(uint64_t a) +{ +#if defined(_MSC_VER) && !defined(__clang__) + unsigned long index; + _BitScanForward64(&index, a); + return index; +#else + return __builtin_ctzll(a); +#endif +} + +static inline uint64_t get_u64(const uint8_t *tab) +{ + uint64_t v; + memcpy(&v, tab, sizeof(v)); + return v; +} + +static inline int64_t get_i64(const uint8_t *tab) +{ + int64_t v; + memcpy(&v, tab, sizeof(v)); + return v; +} + +static inline void put_u64(uint8_t *tab, uint64_t val) +{ + memcpy(tab, &val, sizeof(val)); +} + +static inline uint32_t get_u32(const uint8_t *tab) +{ + uint32_t v; + memcpy(&v, tab, sizeof(v)); + return v; +} + +static inline int32_t get_i32(const uint8_t *tab) +{ + int32_t v; + memcpy(&v, tab, sizeof(v)); + return v; +} + +static inline void put_u32(uint8_t *tab, uint32_t val) +{ + memcpy(tab, &val, sizeof(val)); +} + +static inline uint32_t get_u16(const uint8_t *tab) +{ + uint16_t v; + memcpy(&v, tab, sizeof(v)); + return v; +} + +static inline int32_t get_i16(const uint8_t *tab) +{ + int16_t v; + memcpy(&v, tab, sizeof(v)); + return v; +} + +static inline void put_u16(uint8_t *tab, uint16_t val) +{ + memcpy(tab, &val, sizeof(val)); +} + +static inline uint32_t get_u8(const uint8_t *tab) +{ + return *tab; +} + +static inline int32_t get_i8(const uint8_t *tab) +{ + return (int8_t)*tab; +} + +static inline void put_u8(uint8_t *tab, uint8_t val) +{ + *tab = val; +} + +#ifndef bswap16 +static inline uint16_t bswap16(uint16_t x) +{ + return (x >> 8) | (x << 8); +} +#endif + +#ifndef bswap32 +static inline uint32_t bswap32(uint32_t v) +{ + return ((v & 0xff000000) >> 24) | ((v & 0x00ff0000) >> 8) | + ((v & 0x0000ff00) << 8) | ((v & 0x000000ff) << 24); +} +#endif + +#ifndef bswap64 +static inline uint64_t bswap64(uint64_t v) +{ + return ((v & ((uint64_t)0xff << (7 * 8))) >> (7 * 8)) | + ((v & ((uint64_t)0xff << (6 * 8))) >> (5 * 8)) | + ((v & ((uint64_t)0xff << (5 * 8))) >> (3 * 8)) | + ((v & ((uint64_t)0xff << (4 * 8))) >> (1 * 8)) | + ((v & ((uint64_t)0xff << (3 * 8))) << (1 * 8)) | + ((v & ((uint64_t)0xff << (2 * 8))) << (3 * 8)) | + ((v & ((uint64_t)0xff << (1 * 8))) << (5 * 8)) | + ((v & ((uint64_t)0xff << (0 * 8))) << (7 * 8)); +} +#endif + +static inline double fromfp16(uint16_t v) { + double d, s; + int e; + if ((v & 0x7C00) == 0x7C00) { + d = (v & 0x3FF) ? NAN : INFINITY; + } else { + d = (v & 0x3FF) / 1024.; + e = (v & 0x7C00) >> 10; + if (e == 0) { + e = -14; + } else { + d += 1; + e -= 15; + } + d = scalbn(d, e); + } + s = (v & 0x8000) ? -1.0 : 1.0; + return d * s; +} + +static inline uint16_t tofp16(double d) { + uint16_t f, s; + double t; + int e; + s = 0; + if (copysign(1, d) < 0) { // preserve sign when |d| is negative zero + d = -d; + s = 0x8000; + } + if (isinf(d)) + return s | 0x7C00; + if (isnan(d)) + return s | 0x7C01; + if (d == 0) + return s | 0; + d = 2 * frexp(d, &e); + e--; + if (e > 15) + return s | 0x7C00; // out of range, return +/-infinity + if (e < -25) { + d = 0; + e = 0; + } else if (e < -14) { + d = scalbn(d, e + 14); + e = 0; + } else { + d -= 1; + e += 15; + } + d *= 1024.; + f = (uint16_t)d; + t = d - f; + if (t < 0.5) + goto done; + if (t == 0.5) + if ((f & 1) == 0) + goto done; + // adjust for rounding + if (++f == 1024) { + f = 0; + if (++e == 31) + return s | 0x7C00; // out of range, return +/-infinity + } +done: + return s | (e << 10) | f; +} + +static inline int isfp16nan(uint16_t v) { + return (v & 0x7FFF) > 0x7C00; +} + +static inline int isfp16zero(uint16_t v) { + return (v & 0x7FFF) == 0; +} + +/* XXX: should take an extra argument to pass slack information to the caller */ +typedef void *DynBufReallocFunc(void *opaque, void *ptr, size_t size); + +typedef struct DynBuf { + uint8_t *buf; + size_t size; + size_t allocated_size; + bool error; /* true if a memory allocation error occurred */ + DynBufReallocFunc *realloc_func; + void *opaque; /* for realloc_func */ +} DynBuf; + +static inline void dbuf_init(DynBuf *s); +static inline void dbuf_init2(DynBuf *s, void *opaque, DynBufReallocFunc *realloc_func); +static inline int dbuf_claim(DynBuf *s, size_t len); +static inline int dbuf_put(DynBuf *s, const void *data, size_t len); +static inline int dbuf_put_self(DynBuf *s, size_t offset, size_t len); +static inline int __dbuf_putc(DynBuf *s, uint8_t c); +static inline int __dbuf_put_u16(DynBuf *s, uint16_t val); +static inline int __dbuf_put_u32(DynBuf *s, uint32_t val); +static inline int __dbuf_put_u64(DynBuf *s, uint64_t val); +static inline int dbuf_putstr(DynBuf *s, const char *str); +static inline int dbuf_putc(DynBuf *s, uint8_t val) +{ + if (unlikely((s->allocated_size - s->size) < 1)) + return __dbuf_putc(s, val); + s->buf[s->size++] = val; + return 0; +} +static inline int dbuf_put_u16(DynBuf *s, uint16_t val) +{ + if (unlikely((s->allocated_size - s->size) < 2)) + return __dbuf_put_u16(s, val); + put_u16(s->buf + s->size, val); + s->size += 2; + return 0; +} +static inline int dbuf_put_u32(DynBuf *s, uint32_t val) +{ + if (unlikely((s->allocated_size - s->size) < 4)) + return __dbuf_put_u32(s, val); + put_u32(s->buf + s->size, val); + s->size += 4; + return 0; +} +static inline int dbuf_put_u64(DynBuf *s, uint64_t val) +{ + if (unlikely((s->allocated_size - s->size) < 8)) + return __dbuf_put_u64(s, val); + put_u64(s->buf + s->size, val); + s->size += 8; + return 0; +} +static inline int JS_PRINTF_FORMAT_ATTR(2, 3) dbuf_printf(DynBuf *s, JS_PRINTF_FORMAT const char *fmt, ...); +static inline void dbuf_free(DynBuf *s); +static inline bool dbuf_error(DynBuf *s) { + return s->error; +} +static inline void dbuf_set_error(DynBuf *s) +{ + s->error = true; +} + +/*---- UTF-8 and UTF-16 handling ----*/ + +#define UTF8_CHAR_LEN_MAX 4 + +enum { + UTF8_PLAIN_ASCII = 0, // 7-bit ASCII plain text + UTF8_NON_ASCII = 1, // has non ASCII code points (8-bit or more) + UTF8_HAS_16BIT = 2, // has 16-bit code points + UTF8_HAS_NON_BMP1 = 4, // has non-BMP1 code points, needs UTF-16 surrogate pairs + UTF8_HAS_ERRORS = 8, // has encoding errors +}; +static inline int utf8_scan(const char *buf, size_t len, size_t *plen); +static inline size_t utf8_encode_len(uint32_t c); +static inline size_t utf8_encode(uint8_t buf[minimum_length(UTF8_CHAR_LEN_MAX)], uint32_t c); +static inline uint32_t utf8_decode_len(const uint8_t *p, size_t max_len, const uint8_t **pp); +static inline uint32_t utf8_decode(const uint8_t *p, const uint8_t **pp); +static inline size_t utf8_decode_buf8(uint8_t *dest, size_t dest_len, const char *src, size_t src_len); +static inline size_t utf8_decode_buf16(uint16_t *dest, size_t dest_len, const char *src, size_t src_len); +static inline size_t utf8_encode_buf8(char *dest, size_t dest_len, const uint8_t *src, size_t src_len); +static inline size_t utf8_encode_buf16(char *dest, size_t dest_len, const uint16_t *src, size_t src_len); + +static inline bool is_surrogate(uint32_t c) +{ + return (c >> 11) == (0xD800 >> 11); // 0xD800-0xDFFF +} + +static inline bool is_hi_surrogate(uint32_t c) +{ + return (c >> 10) == (0xD800 >> 10); // 0xD800-0xDBFF +} + +static inline bool is_lo_surrogate(uint32_t c) +{ + return (c >> 10) == (0xDC00 >> 10); // 0xDC00-0xDFFF +} + +static inline uint32_t get_hi_surrogate(uint32_t c) +{ + return (c >> 10) - (0x10000 >> 10) + 0xD800; +} + +static inline uint32_t get_lo_surrogate(uint32_t c) +{ + return (c & 0x3FF) | 0xDC00; +} + +static inline uint32_t from_surrogate(uint32_t hi, uint32_t lo) +{ + return 65536 + 1024 * (hi & 1023) + (lo & 1023); +} + +static inline int from_hex(int c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + else if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + else if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + else + return -1; +} + +static inline uint8_t is_upper_ascii(uint8_t c) { + return c >= 'A' && c <= 'Z'; +} + +static inline uint8_t to_upper_ascii(uint8_t c) { + return c >= 'a' && c <= 'z' ? c - 'a' + 'A' : c; +} + +static inline void rqsort(void *base, size_t nmemb, size_t size, + int (*cmp)(const void *, const void *, void *), + void *arg); + +static inline uint64_t float64_as_uint64(double d) +{ + union { + double d; + uint64_t u64; + } u; + u.d = d; + return u.u64; +} + +static inline double uint64_as_float64(uint64_t u64) +{ + union { + double d; + uint64_t u64; + } u; + u.u64 = u64; + return u.d; +} + +static inline int64_t js__gettimeofday_us(void); +static inline uint64_t js__hrtime_ns(void); + +static inline size_t js__malloc_usable_size(const void *ptr) +{ +#if defined(__APPLE__) + return malloc_size(ptr); +#elif defined(_WIN32) + return _msize((void *)ptr); +#elif defined(__linux__) || defined(__ANDROID__) || defined(__CYGWIN__) || defined(__FreeBSD__) || defined(__GLIBC__) || defined(__wasi__) + return malloc_usable_size((void *)ptr); +#else + return 0; +#endif +} + +static inline int js_exepath(char* buffer, size_t* size); + +/* Cross-platform threading APIs. */ + +#if defined(EMSCRIPTEN) || defined(__wasi__) || defined(__DJGPP) + +#define JS_HAVE_THREADS 0 + +#else + +#define JS_HAVE_THREADS 1 + +#if defined(_WIN32) +#define JS_ONCE_INIT INIT_ONCE_STATIC_INIT +typedef INIT_ONCE js_once_t; +typedef CRITICAL_SECTION js_mutex_t; +typedef CONDITION_VARIABLE js_cond_t; +typedef HANDLE js_thread_t; +#else +#define JS_ONCE_INIT PTHREAD_ONCE_INIT +typedef pthread_once_t js_once_t; +typedef pthread_mutex_t js_mutex_t; +typedef pthread_cond_t js_cond_t; +typedef pthread_t js_thread_t; +#endif + +static inline void js_once(js_once_t *guard, void (*callback)(void)); + +static inline void js_mutex_init(js_mutex_t *mutex); +static inline void js_mutex_destroy(js_mutex_t *mutex); +static inline void js_mutex_lock(js_mutex_t *mutex); +static inline void js_mutex_unlock(js_mutex_t *mutex); + +static inline void js_cond_init(js_cond_t *cond); +static inline void js_cond_destroy(js_cond_t *cond); +static inline void js_cond_signal(js_cond_t *cond); +static inline void js_cond_broadcast(js_cond_t *cond); +static inline void js_cond_wait(js_cond_t *cond, js_mutex_t *mutex); +static inline int js_cond_timedwait(js_cond_t *cond, js_mutex_t *mutex, uint64_t timeout); + +enum { + JS_THREAD_CREATE_DETACHED = 1, +}; + +// creates threads with 2 MB stacks (glibc default) +static inline int js_thread_create(js_thread_t *thrd, void (*start)(void *), void *arg, + int flags); +static inline int js_thread_join(js_thread_t thrd); + +#endif /* !defined(EMSCRIPTEN) && !defined(__wasi__) */ + +// JS requires strict rounding behavior. Turn on 64-bits double precision +// and disable x87 80-bits extended precision for intermediate floating-point +// results. 0x300 is extended precision, 0x200 is double precision. +// Note that `*&cw` in the asm constraints looks redundant but isn't. +#if defined(__i386__) && !defined(_MSC_VER) +#define JS_X87_FPCW_SAVE_AND_ADJUST(cw) \ + (void)0; \ + unsigned short cw; \ + __asm__ __volatile__("fnstcw %0" : "=m"(*&cw)); \ + do { \ + unsigned short t = 0x200 | (cw & ~0x300); \ + __asm__ __volatile__("fldcw %0" : /*empty*/ : "m"(*&t)); \ + } while (0) +#define JS_X87_FPCW_RESTORE(cw) \ + __asm__ __volatile__("fldcw %0" : /*empty*/ : "m"(*&cw)) +#else +#define JS_X87_FPCW_SAVE_AND_ADJUST(cw) +#define JS_X87_FPCW_RESTORE(cw) +#endif + +#undef NANOSEC +#define NANOSEC ((uint64_t) 1e9) + +static inline void js__pstrcpy(char *buf, int buf_size, const char *str) +{ + int c; + char *q = buf; + + if (buf_size <= 0) + return; + + for(;;) { + c = *str++; + if (c == 0 || q >= buf + buf_size - 1) + break; + *q++ = c; + } + *q = '\0'; +} + +/* strcat and truncate. */ +static inline char *js__pstrcat(char *buf, int buf_size, const char *s) +{ + int len; + len = strlen(buf); + if (len < buf_size) + js__pstrcpy(buf + len, buf_size - len, s); + return buf; +} + +static inline int js__strstart(const char *str, const char *val, const char **ptr) +{ + const char *p, *q; + p = str; + q = val; + while (*q != '\0') { + if (*p != *q) + return 0; + p++; + q++; + } + if (ptr) + *ptr = p; + return 1; +} + +static inline int js__has_suffix(const char *str, const char *suffix) +{ + size_t len = strlen(str); + size_t slen = strlen(suffix); + return (len >= slen && !memcmp(str + len - slen, suffix, slen)); +} + +/* Dynamic buffer package */ + +static void *dbuf_default_realloc(void *opaque, void *ptr, size_t size) +{ + if (unlikely(size == 0)) { + free(ptr); + return NULL; + } + return realloc(ptr, size); +} + +static inline void dbuf_init2(DynBuf *s, void *opaque, DynBufReallocFunc *realloc_func) +{ + memset(s, 0, sizeof(*s)); + if (!realloc_func) + realloc_func = dbuf_default_realloc; + s->opaque = opaque; + s->realloc_func = realloc_func; +} + +static inline void dbuf_init(DynBuf *s) +{ + dbuf_init2(s, NULL, NULL); +} + +/* Try to allocate 'len' more bytes. return < 0 if error */ +static inline int dbuf_claim(DynBuf *s, size_t len) +{ + size_t new_size, size, new_allocated_size; + uint8_t *new_buf; + new_size = s->size + len; + if (new_size < len) + return -1; /* overflow */ + if (new_size > s->allocated_size) { + if (s->error) + return -1; + size = s->allocated_size + (s->allocated_size / 2); + if (size < new_size || size < s->allocated_size) /* overflow test */ + new_allocated_size = new_size; + else + new_allocated_size = size; + new_buf = s->realloc_func(s->opaque, s->buf, new_allocated_size); + if (!new_buf) { + s->error = true; + return -1; + } + s->buf = new_buf; + s->allocated_size = new_allocated_size; + } + return 0; +} + +static inline int dbuf_put(DynBuf *s, const void *data, size_t len) +{ + if (unlikely((s->size + len) > s->allocated_size)) { + if (dbuf_claim(s, len)) + return -1; + } + if (len > 0) { + memcpy(s->buf + s->size, data, len); + s->size += len; + } + return 0; +} + +static inline int dbuf_put_self(DynBuf *s, size_t offset, size_t len) +{ + if (unlikely((s->size + len) > s->allocated_size)) { + if (dbuf_claim(s, len)) + return -1; + } + if (len > 0) { + memcpy(s->buf + s->size, s->buf + offset, len); + s->size += len; + } + return 0; +} + +static inline int __dbuf_putc(DynBuf *s, uint8_t c) +{ + return dbuf_put(s, &c, 1); +} + +static inline int __dbuf_put_u16(DynBuf *s, uint16_t val) +{ + return dbuf_put(s, (uint8_t *)&val, 2); +} + +static inline int __dbuf_put_u32(DynBuf *s, uint32_t val) +{ + return dbuf_put(s, (uint8_t *)&val, 4); +} + +static inline int __dbuf_put_u64(DynBuf *s, uint64_t val) +{ + return dbuf_put(s, (uint8_t *)&val, 8); +} + +static inline int dbuf_putstr(DynBuf *s, const char *str) +{ + return dbuf_put(s, (const uint8_t *)str, strlen(str)); +} + +static inline int JS_PRINTF_FORMAT_ATTR(2, 3) dbuf_printf(DynBuf *s, JS_PRINTF_FORMAT const char *fmt, ...) +{ + va_list ap; + char buf[128]; + int len; + + va_start(ap, fmt); + len = vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + if (len < (int)sizeof(buf)) { + /* fast case */ + return dbuf_put(s, (uint8_t *)buf, len); + } else { + if (dbuf_claim(s, len + 1)) + return -1; + va_start(ap, fmt); + vsnprintf((char *)(s->buf + s->size), s->allocated_size - s->size, + fmt, ap); + va_end(ap); + s->size += len; + } + return 0; +} + +static inline void dbuf_free(DynBuf *s) +{ + /* we test s->buf as a fail safe to avoid crashing if dbuf_free() + is called twice */ + if (s->buf) { + s->realloc_func(s->opaque, s->buf, 0); + } + memset(s, 0, sizeof(*s)); +} + +/*--- UTF-8 utility functions --*/ + +/* Note: only encode valid codepoints (0x0000..0x10FFFF). + At most UTF8_CHAR_LEN_MAX bytes are output. */ + +/* Compute the number of bytes of the UTF-8 encoding for a codepoint + `c` is a code-point. + Returns the number of bytes. If a codepoint is beyond 0x10FFFF the + return value is 3 as the codepoint would be encoded as 0xFFFD. + */ +static inline size_t utf8_encode_len(uint32_t c) +{ + if (c < 0x80) + return 1; + if (c < 0x800) + return 2; + if (c < 0x10000) + return 3; + if (c < 0x110000) + return 4; + return 3; +} + +/* Encode a codepoint in UTF-8 + `buf` points to an array of at least `UTF8_CHAR_LEN_MAX` bytes + `c` is a code-point. + Returns the number of bytes. If a codepoint is beyond 0x10FFFF the + return value is 3 and the codepoint is encoded as 0xFFFD. + No null byte is stored after the encoded bytes. + Return value is in range 1..4 + */ +static inline size_t utf8_encode(uint8_t buf[minimum_length(UTF8_CHAR_LEN_MAX)], uint32_t c) +{ + if (c < 0x80) { + buf[0] = c; + return 1; + } + if (c < 0x800) { + buf[0] = (c >> 6) | 0xC0; + buf[1] = (c & 0x3F) | 0x80; + return 2; + } + if (c < 0x10000) { + buf[0] = (c >> 12) | 0xE0; + buf[1] = ((c >> 6) & 0x3F) | 0x80; + buf[2] = (c & 0x3F) | 0x80; + return 3; + } + if (c < 0x110000) { + buf[0] = (c >> 18) | 0xF0; + buf[1] = ((c >> 12) & 0x3F) | 0x80; + buf[2] = ((c >> 6) & 0x3F) | 0x80; + buf[3] = (c & 0x3F) | 0x80; + return 4; + } + buf[0] = (0xFFFD >> 12) | 0xE0; + buf[1] = ((0xFFFD >> 6) & 0x3F) | 0x80; + buf[2] = (0xFFFD & 0x3F) | 0x80; + return 3; +} + +/* Decode a single code point from a UTF-8 encoded array of bytes + `p` is a valid pointer to an array of bytes + `pp` is a valid pointer to a `const uint8_t *` to store a pointer + to the byte following the current sequence. + Return the code point at `p`, in the range `0..0x10FFFF` + Return 0xFFFD on error. Only a single byte is consumed in this case + The maximum length for a UTF-8 byte sequence is 4 bytes. + This implements the algorithm specified in whatwg.org, except it accepts + UTF-8 encoded surrogates as JavaScript allows them in strings. + The source string is assumed to have at least UTF8_CHAR_LEN_MAX bytes + or be null terminated. + If `p[0]` is '\0', the return value is `0` and the byte is consumed. + cf: https://encoding.spec.whatwg.org/#utf-8-encoder + */ +static inline uint32_t utf8_decode(const uint8_t *p, const uint8_t **pp) +{ + uint32_t c; + uint8_t lower, upper; + + c = *p++; + if (c < 0x80) { + *pp = p; + return c; + } + switch(c) { + case 0xC2: case 0xC3: + case 0xC4: case 0xC5: case 0xC6: case 0xC7: + case 0xC8: case 0xC9: case 0xCA: case 0xCB: + case 0xCC: case 0xCD: case 0xCE: case 0xCF: + case 0xD0: case 0xD1: case 0xD2: case 0xD3: + case 0xD4: case 0xD5: case 0xD6: case 0xD7: + case 0xD8: case 0xD9: case 0xDA: case 0xDB: + case 0xDC: case 0xDD: case 0xDE: case 0xDF: + if (*p >= 0x80 && *p <= 0xBF) { + *pp = p + 1; + return ((c - 0xC0) << 6) + (*p - 0x80); + } + // otherwise encoding error + break; + case 0xE0: + lower = 0xA0; /* reject invalid encoding */ + goto need2; + case 0xE1: case 0xE2: case 0xE3: + case 0xE4: case 0xE5: case 0xE6: case 0xE7: + case 0xE8: case 0xE9: case 0xEA: case 0xEB: + case 0xEC: case 0xED: case 0xEE: case 0xEF: + lower = 0x80; + need2: + if (*p >= lower && *p <= 0xBF && p[1] >= 0x80 && p[1] <= 0xBF) { + *pp = p + 2; + return ((c - 0xE0) << 12) + ((*p - 0x80) << 6) + (p[1] - 0x80); + } + // otherwise encoding error + break; + case 0xF0: + lower = 0x90; /* reject invalid encoding */ + upper = 0xBF; + goto need3; + case 0xF4: + lower = 0x80; + upper = 0x8F; /* reject values above 0x10FFFF */ + goto need3; + case 0xF1: case 0xF2: case 0xF3: + lower = 0x80; + upper = 0xBF; + need3: + if (*p >= lower && *p <= upper && p[1] >= 0x80 && p[1] <= 0xBF + && p[2] >= 0x80 && p[2] <= 0xBF) { + *pp = p + 3; + return ((c - 0xF0) << 18) + ((*p - 0x80) << 12) + + ((p[1] - 0x80) << 6) + (p[2] - 0x80); + } + // otherwise encoding error + break; + default: + // invalid lead byte + break; + } + *pp = p; + return 0xFFFD; +} + +static inline uint32_t utf8_decode_len(const uint8_t *p, size_t max_len, const uint8_t **pp) { + switch (max_len) { + case 0: + *pp = p; + return 0xFFFD; + case 1: + if (*p < 0x80) + goto good; + break; + case 2: + if (*p < 0xE0) + goto good; + break; + case 3: + if (*p < 0xF0) + goto good; + break; + default: + good: + return utf8_decode(p, pp); + } + *pp = p + 1; + return 0xFFFD; +} + +/* Scan a UTF-8 encoded buffer for content type + `buf` is a valid pointer to a UTF-8 encoded string + `len` is the number of bytes to scan + `plen` points to a `size_t` variable to receive the number of units + Return value is a mask of bits. + - `UTF8_PLAIN_ASCII`: return value for 7-bit ASCII plain text + - `UTF8_NON_ASCII`: bit for non ASCII code points (8-bit or more) + - `UTF8_HAS_16BIT`: bit for 16-bit code points + - `UTF8_HAS_NON_BMP1`: bit for non-BMP1 code points, needs UTF-16 surrogate pairs + - `UTF8_HAS_ERRORS`: bit for encoding errors + */ +static inline int utf8_scan(const char *buf, size_t buf_len, size_t *plen) +{ + const uint8_t *p, *p_end, *p_next; + size_t i, len; + int kind; + uint8_t cbits; + + kind = UTF8_PLAIN_ASCII; + cbits = 0; + len = buf_len; + // TODO: handle more than 1 byte at a time + for (i = 0; i < buf_len; i++) + cbits |= buf[i]; + if (cbits >= 0x80) { + p = (const uint8_t *)buf; + p_end = p + buf_len; + kind = UTF8_NON_ASCII; + len = 0; + while (p < p_end) { + len++; + if (*p++ >= 0x80) { + /* parse UTF-8 sequence, check for encoding error */ + uint32_t c = utf8_decode_len(p - 1, p_end - (p - 1), &p_next); + if (p_next == p) + kind |= UTF8_HAS_ERRORS; + p = p_next; + if (c > 0xFF) { + kind |= UTF8_HAS_16BIT; + if (c > 0xFFFF) { + len++; + kind |= UTF8_HAS_NON_BMP1; + } + } + } + } + } + *plen = len; + return kind; +} + +/* Decode a string encoded in UTF-8 into an array of bytes + `src` points to the source string. It is assumed to be correctly encoded + and only contains code points below 0x800 + `src_len` is the length of the source string + `dest` points to the destination array, it can be null if `dest_len` is `0` + `dest_len` is the length of the destination array. A null + terminator is stored at the end of the array unless `dest_len` is `0`. + */ +static inline size_t utf8_decode_buf8(uint8_t *dest, size_t dest_len, const char *src, size_t src_len) +{ + const uint8_t *p, *p_end; + size_t i; + + p = (const uint8_t *)src; + p_end = p + src_len; + for (i = 0; p < p_end; i++) { + uint32_t c = *p++; + if (c >= 0xC0) + c = (c << 6) + *p++ - ((0xC0 << 6) + 0x80); + if (i < dest_len) + dest[i] = c; + } + if (i < dest_len) + dest[i] = '\0'; + else if (dest_len > 0) + dest[dest_len - 1] = '\0'; + return i; +} + +/* Decode a string encoded in UTF-8 into an array of 16-bit words + `src` points to the source string. It is assumed to be correctly encoded. + `src_len` is the length of the source string + `dest` points to the destination array, it can be null if `dest_len` is `0` + `dest_len` is the length of the destination array. No null terminator is + stored at the end of the array. + */ +static inline size_t utf8_decode_buf16(uint16_t *dest, size_t dest_len, const char *src, size_t src_len) +{ + const uint8_t *p, *p_end; + size_t i; + + p = (const uint8_t *)src; + p_end = p + src_len; + for (i = 0; p < p_end; i++) { + uint32_t c = *p++; + if (c >= 0x80) { + /* parse utf-8 sequence */ + c = utf8_decode_len(p - 1, p_end - (p - 1), &p); + /* encoding errors are converted as 0xFFFD and use a single byte */ + if (c > 0xFFFF) { + if (i < dest_len) + dest[i] = get_hi_surrogate(c); + i++; + c = get_lo_surrogate(c); + } + } + if (i < dest_len) + dest[i] = c; + } + return i; +} + +/* Encode a buffer of 8-bit bytes as a UTF-8 encoded string + `src` points to the source buffer. + `src_len` is the length of the source buffer + `dest` points to the destination array, it can be null if `dest_len` is `0` + `dest_len` is the length in bytes of the destination array. A null + terminator is stored at the end of the array unless `dest_len` is `0`. + */ +static inline size_t utf8_encode_buf8(char *dest, size_t dest_len, const uint8_t *src, size_t src_len) +{ + size_t i, j; + uint32_t c; + + for (i = j = 0; i < src_len; i++) { + c = src[i]; + if (c < 0x80) { + if (j + 1 >= dest_len) + goto overflow; + dest[j++] = c; + } else { + if (j + 2 >= dest_len) + goto overflow; + dest[j++] = (c >> 6) | 0xC0; + dest[j++] = (c & 0x3F) | 0x80; + } + } + if (j < dest_len) + dest[j] = '\0'; + return j; + +overflow: + if (j < dest_len) + dest[j] = '\0'; + while (i < src_len) + j += 1 + (src[i++] >= 0x80); + return j; +} + +/* Encode a buffer of 16-bit code points as a UTF-8 encoded string + `src` points to the source buffer. + `src_len` is the length of the source buffer + `dest` points to the destination array, it can be null if `dest_len` is `0` + `dest_len` is the length in bytes of the destination array. A null + terminator is stored at the end of the array unless `dest_len` is `0`. + */ +static inline size_t utf8_encode_buf16(char *dest, size_t dest_len, const uint16_t *src, size_t src_len) +{ + size_t i, j; + uint32_t c; + + for (i = j = 0; i < src_len;) { + c = src[i++]; + if (c < 0x80) { + if (j + 1 >= dest_len) + goto overflow; + dest[j++] = c; + } else { + if (is_hi_surrogate(c) && i < src_len && is_lo_surrogate(src[i])) + c = from_surrogate(c, src[i++]); + if (j + utf8_encode_len(c) >= dest_len) + goto overflow; + j += utf8_encode((uint8_t *)dest + j, c); + } + } + if (j < dest_len) + dest[j] = '\0'; + return j; + +overflow: + i -= 1 + (c > 0xFFFF); + if (j < dest_len) + dest[j] = '\0'; + while (i < src_len) { + c = src[i++]; + if (c < 0x80) { + j++; + } else { + if (is_hi_surrogate(c) && i < src_len && is_lo_surrogate(src[i])) + c = from_surrogate(c, src[i++]); + j += utf8_encode_len(c); + } + } + return j; +} + +/*---- sorting with opaque argument ----*/ + +typedef void (*exchange_f)(void *a, void *b, size_t size); +typedef int (*cmp_f)(const void *, const void *, void *opaque); + +static void exchange_bytes(void *a, void *b, size_t size) { + uint8_t *ap = (uint8_t *)a; + uint8_t *bp = (uint8_t *)b; + + while (size-- != 0) { + uint8_t t = *ap; + *ap++ = *bp; + *bp++ = t; + } +} + +static void exchange_one_byte(void *a, void *b, size_t size) { + uint8_t *ap = (uint8_t *)a; + uint8_t *bp = (uint8_t *)b; + uint8_t t = *ap; + *ap = *bp; + *bp = t; +} + +static void exchange_int16s(void *a, void *b, size_t size) { + uint16_t *ap = (uint16_t *)a; + uint16_t *bp = (uint16_t *)b; + + for (size /= sizeof(uint16_t); size-- != 0;) { + uint16_t t = *ap; + *ap++ = *bp; + *bp++ = t; + } +} + +static void exchange_one_int16(void *a, void *b, size_t size) { + uint16_t *ap = (uint16_t *)a; + uint16_t *bp = (uint16_t *)b; + uint16_t t = *ap; + *ap = *bp; + *bp = t; +} + +static void exchange_int32s(void *a, void *b, size_t size) { + uint32_t *ap = (uint32_t *)a; + uint32_t *bp = (uint32_t *)b; + + for (size /= sizeof(uint32_t); size-- != 0;) { + uint32_t t = *ap; + *ap++ = *bp; + *bp++ = t; + } +} + +static void exchange_one_int32(void *a, void *b, size_t size) { + uint32_t *ap = (uint32_t *)a; + uint32_t *bp = (uint32_t *)b; + uint32_t t = *ap; + *ap = *bp; + *bp = t; +} + +static void exchange_int64s(void *a, void *b, size_t size) { + uint64_t *ap = (uint64_t *)a; + uint64_t *bp = (uint64_t *)b; + + for (size /= sizeof(uint64_t); size-- != 0;) { + uint64_t t = *ap; + *ap++ = *bp; + *bp++ = t; + } +} + +static void exchange_one_int64(void *a, void *b, size_t size) { + uint64_t *ap = (uint64_t *)a; + uint64_t *bp = (uint64_t *)b; + uint64_t t = *ap; + *ap = *bp; + *bp = t; +} + +static void exchange_int128s(void *a, void *b, size_t size) { + uint64_t *ap = (uint64_t *)a; + uint64_t *bp = (uint64_t *)b; + + for (size /= sizeof(uint64_t) * 2; size-- != 0; ap += 2, bp += 2) { + uint64_t t = ap[0]; + uint64_t u = ap[1]; + ap[0] = bp[0]; + ap[1] = bp[1]; + bp[0] = t; + bp[1] = u; + } +} + +static void exchange_one_int128(void *a, void *b, size_t size) { + uint64_t *ap = (uint64_t *)a; + uint64_t *bp = (uint64_t *)b; + uint64_t t = ap[0]; + uint64_t u = ap[1]; + ap[0] = bp[0]; + ap[1] = bp[1]; + bp[0] = t; + bp[1] = u; +} + +static inline exchange_f exchange_func(const void *base, size_t size) { + switch (((uintptr_t)base | (uintptr_t)size) & 15) { + case 0: + if (size == sizeof(uint64_t) * 2) + return exchange_one_int128; + else + return exchange_int128s; + case 8: + if (size == sizeof(uint64_t)) + return exchange_one_int64; + else + return exchange_int64s; + case 4: + case 12: + if (size == sizeof(uint32_t)) + return exchange_one_int32; + else + return exchange_int32s; + case 2: + case 6: + case 10: + case 14: + if (size == sizeof(uint16_t)) + return exchange_one_int16; + else + return exchange_int16s; + default: + if (size == 1) + return exchange_one_byte; + else + return exchange_bytes; + } +} + +static void heapsortx(void *base, size_t nmemb, size_t size, cmp_f cmp, void *opaque) +{ + uint8_t *basep = (uint8_t *)base; + size_t i, n, c, r; + exchange_f swap = exchange_func(base, size); + + if (nmemb > 1) { + i = (nmemb / 2) * size; + n = nmemb * size; + + while (i > 0) { + i -= size; + for (r = i; (c = r * 2 + size) < n; r = c) { + if (c < n - size && cmp(basep + c, basep + c + size, opaque) <= 0) + c += size; + if (cmp(basep + r, basep + c, opaque) > 0) + break; + swap(basep + r, basep + c, size); + } + } + for (i = n - size; i > 0; i -= size) { + swap(basep, basep + i, size); + + for (r = 0; (c = r * 2 + size) < i; r = c) { + if (c < i - size && cmp(basep + c, basep + c + size, opaque) <= 0) + c += size; + if (cmp(basep + r, basep + c, opaque) > 0) + break; + swap(basep + r, basep + c, size); + } + } + } +} + +static inline void *med3(void *a, void *b, void *c, cmp_f cmp, void *opaque) +{ + return cmp(a, b, opaque) < 0 ? + (cmp(b, c, opaque) < 0 ? b : (cmp(a, c, opaque) < 0 ? c : a )) : + (cmp(b, c, opaque) > 0 ? b : (cmp(a, c, opaque) < 0 ? a : c )); +} + +/* pointer based version with local stack and insertion sort threshhold */ +static inline void rqsort(void *base, size_t nmemb, size_t size, cmp_f cmp, void *opaque) +{ + struct { uint8_t *base; size_t count; int depth; } stack[50], *sp = stack; + uint8_t *ptr, *pi, *pj, *plt, *pgt, *top, *m; + size_t m4, i, lt, gt, span, span2; + int c, depth; + exchange_f swap = exchange_func(base, size); + exchange_f swap_block = exchange_func(base, size | 128); + + if (nmemb < 2 || size <= 0) + return; + + sp->base = (uint8_t *)base; + sp->count = nmemb; + sp->depth = 0; + sp++; + + while (sp > stack) { + sp--; + ptr = sp->base; + nmemb = sp->count; + depth = sp->depth; + + while (nmemb > 6) { + if (++depth > 50) { + /* depth check to ensure worst case logarithmic time */ + heapsortx(ptr, nmemb, size, cmp, opaque); + nmemb = 0; + break; + } + /* select median of 3 from 1/4, 1/2, 3/4 positions */ + /* should use median of 5 or 9? */ + m4 = (nmemb >> 2) * size; + m = med3(ptr + m4, ptr + 2 * m4, ptr + 3 * m4, cmp, opaque); + swap(ptr, m, size); /* move the pivot to the start or the array */ + i = lt = 1; + pi = plt = ptr + size; + gt = nmemb; + pj = pgt = top = ptr + nmemb * size; + for (;;) { + while (pi < pj && (c = cmp(ptr, pi, opaque)) >= 0) { + if (c == 0) { + swap(plt, pi, size); + lt++; + plt += size; + } + i++; + pi += size; + } + while (pi < (pj -= size) && (c = cmp(ptr, pj, opaque)) <= 0) { + if (c == 0) { + gt--; + pgt -= size; + swap(pgt, pj, size); + } + } + if (pi >= pj) + break; + swap(pi, pj, size); + i++; + pi += size; + } + /* array has 4 parts: + * from 0 to lt excluded: elements identical to pivot + * from lt to pi excluded: elements smaller than pivot + * from pi to gt excluded: elements greater than pivot + * from gt to n excluded: elements identical to pivot + */ + /* move elements identical to pivot in the middle of the array: */ + /* swap values in ranges [0..lt[ and [i-lt..i[ + swapping the smallest span between lt and i-lt is sufficient + */ + span = plt - ptr; + span2 = pi - plt; + lt = i - lt; + if (span > span2) + span = span2; + swap_block(ptr, pi - span, span); + /* swap values in ranges [gt..top[ and [i..top-(top-gt)[ + swapping the smallest span between top-gt and gt-i is sufficient + */ + span = top - pgt; + span2 = pgt - pi; + pgt = top - span2; + gt = nmemb - (gt - i); + if (span > span2) + span = span2; + swap_block(pi, top - span, span); + + /* now array has 3 parts: + * from 0 to lt excluded: elements smaller than pivot + * from lt to gt excluded: elements identical to pivot + * from gt to n excluded: elements greater than pivot + */ + /* stack the larger segment and keep processing the smaller one + to minimize stack use for pathological distributions */ + if (lt > nmemb - gt) { + sp->base = ptr; + sp->count = lt; + sp->depth = depth; + sp++; + ptr = pgt; + nmemb -= gt; + } else { + sp->base = pgt; + sp->count = nmemb - gt; + sp->depth = depth; + sp++; + nmemb = lt; + } + } + /* Use insertion sort for small fragments */ + for (pi = ptr + size, top = ptr + nmemb * size; pi < top; pi += size) { + for (pj = pi; pj > ptr && cmp(pj - size, pj, opaque) > 0; pj -= size) + swap(pj, pj - size, size); + } + } +} + +/*---- Portable time functions ----*/ + +#ifdef _WIN32 + // From: https://stackoverflow.com/a/26085827 +static int gettimeofday_msvc(struct timeval *tp) +{ + static const uint64_t EPOCH = ((uint64_t)116444736000000000ULL); + + SYSTEMTIME system_time; + FILETIME file_time; + uint64_t time; + + GetSystemTime(&system_time); + SystemTimeToFileTime(&system_time, &file_time); + time = ((uint64_t)file_time.dwLowDateTime); + time += ((uint64_t)file_time.dwHighDateTime) << 32; + + tp->tv_sec = (long)((time - EPOCH) / 10000000L); + tp->tv_usec = (long)(system_time.wMilliseconds * 1000); + + return 0; +} + +static inline uint64_t js__hrtime_ns(void) { + LARGE_INTEGER counter, frequency; + double scaled_freq; + double result; + + if (!QueryPerformanceFrequency(&frequency)) + abort(); + assert(frequency.QuadPart != 0); + + if (!QueryPerformanceCounter(&counter)) + abort(); + assert(counter.QuadPart != 0); + + /* Because we have no guarantee about the order of magnitude of the + * performance counter interval, integer math could cause this computation + * to overflow. Therefore we resort to floating point math. + */ + scaled_freq = (double) frequency.QuadPart / NANOSEC; + result = (double) counter.QuadPart / scaled_freq; + return (uint64_t) result; +} +#else +static inline uint64_t js__hrtime_ns(void) { +#ifdef __DJGPP + struct timeval tv; + if (gettimeofday(&tv, NULL)) + abort(); + return tv.tv_sec * NANOSEC + tv.tv_usec * 1000; +#else + struct timespec t; + + if (clock_gettime(CLOCK_MONOTONIC, &t)) + abort(); + + return t.tv_sec * NANOSEC + t.tv_nsec; +#endif +} +#endif + +static inline int64_t js__gettimeofday_us(void) { + struct timeval tv; +#ifdef _WIN32 + gettimeofday_msvc(&tv); +#else + gettimeofday(&tv, NULL); +#endif + return ((int64_t)tv.tv_sec * 1000000) + tv.tv_usec; +} + +#if defined(_WIN32) +static inline int js_exepath(char *buffer, size_t *size_ptr) { + int utf8_len, utf16_buffer_len, utf16_len; + WCHAR* utf16_buffer; + + if (buffer == NULL || size_ptr == NULL || *size_ptr == 0) + return -1; + + if (*size_ptr > 32768) { + /* Windows paths can never be longer than this. */ + utf16_buffer_len = 32768; + } else { + utf16_buffer_len = (int)*size_ptr; + } + + utf16_buffer = malloc(sizeof(WCHAR) * utf16_buffer_len); + if (!utf16_buffer) + return -1; + + /* Get the path as UTF-16. */ + utf16_len = GetModuleFileNameW(NULL, utf16_buffer, utf16_buffer_len); + if (utf16_len <= 0) + goto error; + + /* Convert to UTF-8 */ + utf8_len = WideCharToMultiByte(CP_UTF8, + 0, + utf16_buffer, + -1, + buffer, + (int)*size_ptr, + NULL, + NULL); + if (utf8_len == 0) + goto error; + + free(utf16_buffer); + + /* utf8_len *does* include the terminating null at this point, but the + * returned size shouldn't. */ + *size_ptr = utf8_len - 1; + return 0; + +error: + free(utf16_buffer); + return -1; +} +#elif defined(__APPLE__) +static inline int js_exepath(char *buffer, size_t *size) { + /* realpath(exepath) may be > PATH_MAX so double it to be on the safe side. */ + char abspath[PATH_MAX * 2 + 1]; + char exepath[PATH_MAX + 1]; + uint32_t exepath_size; + size_t abspath_size; + + if (buffer == NULL || size == NULL || *size == 0) + return -1; + + exepath_size = sizeof(exepath); + if (_NSGetExecutablePath(exepath, &exepath_size)) + return -1; + + if (realpath(exepath, abspath) != abspath) + return -1; + + abspath_size = strlen(abspath); + if (abspath_size == 0) + return -1; + + *size -= 1; + if (*size > abspath_size) + *size = abspath_size; + + memcpy(buffer, abspath, *size); + buffer[*size] = '\0'; + + return 0; +} +#elif defined(__linux__) || defined(__GNU__) +static inline int js_exepath(char *buffer, size_t *size) { + ssize_t n; + + if (buffer == NULL || size == NULL || *size == 0) + return -1; + + n = *size - 1; + if (n > 0) + n = readlink("/proc/self/exe", buffer, n); + + if (n == -1) + return n; + + buffer[n] = '\0'; + *size = n; + + return 0; +} +#else +static inline int js_exepath(char* buffer, size_t* size_ptr) { + return -1; +} +#endif + +/*--- Cross-platform threading APIs. ----*/ + +#if JS_HAVE_THREADS +#if defined(_WIN32) +typedef void (*js__once_cb)(void); + +typedef struct { + js__once_cb callback; +} js__once_data_t; + +static int WINAPI js__once_inner(INIT_ONCE *once, void *param, void **context) { + js__once_data_t *data = param; + + data->callback(); + + return 1; +} + +static inline void js_once(js_once_t *guard, js__once_cb callback) { + js__once_data_t data = { .callback = callback }; + InitOnceExecuteOnce(guard, js__once_inner, (void*) &data, NULL); +} + +static inline void js_mutex_init(js_mutex_t *mutex) { + InitializeCriticalSection(mutex); +} + +static inline void js_mutex_destroy(js_mutex_t *mutex) { + DeleteCriticalSection(mutex); +} + +static inline void js_mutex_lock(js_mutex_t *mutex) { + EnterCriticalSection(mutex); +} + +static inline void js_mutex_unlock(js_mutex_t *mutex) { + LeaveCriticalSection(mutex); +} + +static inline void js_cond_init(js_cond_t *cond) { + InitializeConditionVariable(cond); +} + +static inline void js_cond_destroy(js_cond_t *cond) { + /* nothing to do */ + (void) cond; +} + +static inline void js_cond_signal(js_cond_t *cond) { + WakeConditionVariable(cond); +} + +static inline void js_cond_broadcast(js_cond_t *cond) { + WakeAllConditionVariable(cond); +} + +static inline void js_cond_wait(js_cond_t *cond, js_mutex_t *mutex) { + if (!SleepConditionVariableCS(cond, mutex, INFINITE)) + abort(); +} + +static inline int js_cond_timedwait(js_cond_t *cond, js_mutex_t *mutex, uint64_t timeout) { + if (SleepConditionVariableCS(cond, mutex, (DWORD)(timeout / 1e6))) + return 0; + if (GetLastError() != ERROR_TIMEOUT) + abort(); + return -1; +} + +static inline int js_thread_create(js_thread_t *thrd, void (*start)(void *), void *arg, + int flags) +{ + HANDLE h, cp; + + *thrd = INVALID_HANDLE_VALUE; + if (flags & ~JS_THREAD_CREATE_DETACHED) + return -1; + h = (HANDLE)_beginthread(start, /*stacksize*/2<<20, arg); + if (!h) + return -1; + if (flags & JS_THREAD_CREATE_DETACHED) + return 0; + // _endthread() automatically closes the handle but we want to wait on + // it so make a copy. Race-y for very short-lived threads. Can be solved + // by switching to _beginthreadex(CREATE_SUSPENDED) but means changing + // |start| from __cdecl to __stdcall. + cp = GetCurrentProcess(); + if (DuplicateHandle(cp, h, cp, thrd, 0, FALSE, DUPLICATE_SAME_ACCESS)) + return 0; + return -1; +} + +static inline int js_thread_join(js_thread_t thrd) +{ + if (WaitForSingleObject(thrd, INFINITE)) + return -1; + CloseHandle(thrd); + return 0; +} + +#else /* !defined(_WIN32) */ + +static inline void js_once(js_once_t *guard, void (*callback)(void)) { + if (pthread_once(guard, callback)) + abort(); +} + +static inline void js_mutex_init(js_mutex_t *mutex) { + if (pthread_mutex_init(mutex, NULL)) + abort(); +} + +static inline void js_mutex_destroy(js_mutex_t *mutex) { + if (pthread_mutex_destroy(mutex)) + abort(); +} + +static inline void js_mutex_lock(js_mutex_t *mutex) { + if (pthread_mutex_lock(mutex)) + abort(); +} + +static inline void js_mutex_unlock(js_mutex_t *mutex) { + if (pthread_mutex_unlock(mutex)) + abort(); +} + +static inline void js_cond_init(js_cond_t *cond) { +#if defined(__APPLE__) && defined(__MACH__) + if (pthread_cond_init(cond, NULL)) + abort(); +#else + pthread_condattr_t attr; + + if (pthread_condattr_init(&attr)) + abort(); + + if (pthread_condattr_setclock(&attr, CLOCK_MONOTONIC)) + abort(); + + if (pthread_cond_init(cond, &attr)) + abort(); + + if (pthread_condattr_destroy(&attr)) + abort(); +#endif +} + +static inline void js_cond_destroy(js_cond_t *cond) { +#if defined(__APPLE__) && defined(__MACH__) + /* It has been reported that destroying condition variables that have been + * signalled but not waited on can sometimes result in application crashes. + * See https://codereview.chromium.org/1323293005. + */ + pthread_mutex_t mutex; + struct timespec ts; + int err; + + if (pthread_mutex_init(&mutex, NULL)) + abort(); + + if (pthread_mutex_lock(&mutex)) + abort(); + + ts.tv_sec = 0; + ts.tv_nsec = 1; + + err = pthread_cond_timedwait_relative_np(cond, &mutex, &ts); + if (err != 0 && err != ETIMEDOUT) + abort(); + + if (pthread_mutex_unlock(&mutex)) + abort(); + + if (pthread_mutex_destroy(&mutex)) + abort(); +#endif /* defined(__APPLE__) && defined(__MACH__) */ + + if (pthread_cond_destroy(cond)) + abort(); +} + +static inline void js_cond_signal(js_cond_t *cond) { + if (pthread_cond_signal(cond)) + abort(); +} + +static inline void js_cond_broadcast(js_cond_t *cond) { + if (pthread_cond_broadcast(cond)) + abort(); +} + +static inline void js_cond_wait(js_cond_t *cond, js_mutex_t *mutex) { +#if defined(__APPLE__) && defined(__MACH__) + int r; + + errno = 0; + r = pthread_cond_wait(cond, mutex); + + /* Workaround for a bug in OS X at least up to 13.6 + * See https://github.com/libuv/libuv/issues/4165 + */ + if (r == EINVAL && errno == EBUSY) + return; + if (r) + abort(); +#else + if (pthread_cond_wait(cond, mutex)) + abort(); +#endif +} + +static inline int js_cond_timedwait(js_cond_t *cond, js_mutex_t *mutex, uint64_t timeout) { + int r; + struct timespec ts; + +#if !defined(__APPLE__) + timeout += js__hrtime_ns(); +#endif + + ts.tv_sec = timeout / NANOSEC; + ts.tv_nsec = timeout % NANOSEC; +#if defined(__APPLE__) && defined(__MACH__) + r = pthread_cond_timedwait_relative_np(cond, mutex, &ts); +#else + r = pthread_cond_timedwait(cond, mutex, &ts); +#endif + + if (r == 0) + return 0; + + if (r == ETIMEDOUT) + return -1; + + abort(); + + /* Pacify some compilers. */ + return -1; +} + +static inline int js_thread_create(js_thread_t *thrd, void (*start)(void *), void *arg, + int flags) +{ + union { + void (*x)(void *); + void *(*f)(void *); + } u = {start}; + pthread_attr_t attr; + int ret; + + if (flags & ~JS_THREAD_CREATE_DETACHED) + return -1; + if (pthread_attr_init(&attr)) + return -1; + ret = -1; + if (pthread_attr_setstacksize(&attr, 2<<20)) + goto fail; + if (flags & JS_THREAD_CREATE_DETACHED) + if (pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED)) + goto fail; + if (pthread_create(thrd, &attr, u.f, arg)) + goto fail; + ret = 0; +fail: + pthread_attr_destroy(&attr); + return ret; +} + +static inline int js_thread_join(js_thread_t thrd) +{ + if (pthread_join(thrd, NULL)) + return -1; + return 0; +} + +#endif /* !defined(_WIN32) */ +#endif /* JS_HAVE_THREADS */ + +#ifdef __cplusplus +} /* extern "C" { */ +#endif + +#endif /* CUTILS_H */ diff --git a/engine/quickjs-ng/dtoa.c b/engine/quickjs-ng/dtoa.c new file mode 100644 index 000000000..a89e824f9 --- /dev/null +++ b/engine/quickjs-ng/dtoa.c @@ -0,0 +1,1619 @@ +/* + * Tiny float64 printing and parsing library + * + * Copyright (c) 2024 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include +#include +#include +#include +#include +// #include +#include +// #include + +#include "cutils.h" +#include "dtoa.h" + +/* + TODO: + - simplify subnormal handling + - reduce max memory usage + - free format: could add shortcut if exact result + - use 64 bit limb_t when possible + - use another algorithm for free format dtoa in base 10 (ryu ?) +*/ + +#define USE_POW5_TABLE +/* use fast path to print small integers in free format */ +#define USE_FAST_INT + +#define LIMB_LOG2_BITS 5 + +#define LIMB_BITS (1 << LIMB_LOG2_BITS) + +typedef int32_t slimb_t; +typedef uint32_t limb_t; +typedef uint64_t dlimb_t; + +#define LIMB_DIGITS 9 + +#define JS_RADIX_MAX 36 + +#define DBIGNUM_LEN_MAX 52 /* ~ 2^(1072+53)*36^100 (dtoa) */ +#define MANT_LEN_MAX 18 /* < 36^100 */ + +typedef intptr_t mp_size_t; + +/* the represented number is sum(i, tab[i]*2^(LIMB_BITS * i)) */ +typedef struct { + int len; /* >= 1 */ + limb_t tab[]; +} mpb_t; + +static limb_t mp_add_ui(limb_t *tab, limb_t b, size_t n) +{ + size_t i; + limb_t k, a; + + k=b; + for(i=0;i> LIMB_BITS; + } + return l; +} + +/* WARNING: d must be >= 2^(LIMB_BITS-1) */ +static inline limb_t udiv1norm_init(limb_t d) +{ + limb_t a0, a1; + a1 = -d - 1; + a0 = -1; + return (((dlimb_t)a1 << LIMB_BITS) | a0) / d; +} + +/* return the quotient and the remainder in '*pr'of 'a1*2^LIMB_BITS+a0 + / d' with 0 <= a1 < d. */ +static inline limb_t udiv1norm(limb_t *pr, limb_t a1, limb_t a0, + limb_t d, limb_t d_inv) +{ + limb_t n1m, n_adj, q, r, ah; + dlimb_t a; + n1m = ((slimb_t)a0 >> (LIMB_BITS - 1)); + n_adj = a0 + (n1m & d); + a = (dlimb_t)d_inv * (a1 - n1m) + n_adj; + q = (a >> LIMB_BITS) + a1; + /* compute a - q * r and update q so that the remainder is between + 0 and d - 1 */ + a = ((dlimb_t)a1 << LIMB_BITS) | a0; + a = a - (dlimb_t)q * d - d; + ah = a >> LIMB_BITS; + q += 1 + ah; + r = (limb_t)a + (ah & d); + *pr = r; + return q; +} + +static limb_t mp_div1(limb_t *tabr, const limb_t *taba, limb_t n, + limb_t b, limb_t r) +{ + slimb_t i; + dlimb_t a1; + for(i = n - 1; i >= 0; i--) { + a1 = ((dlimb_t)r << LIMB_BITS) | taba[i]; + tabr[i] = a1 / b; + r = a1 % b; + } + return r; +} + +/* r = (a + high*B^n) >> shift. Return the remainder r (0 <= r < 2^shift). + 1 <= shift <= LIMB_BITS - 1 */ +static limb_t mp_shr(limb_t *tab_r, const limb_t *tab, mp_size_t n, + int shift, limb_t high) +{ + mp_size_t i; + limb_t l, a; + + assert(shift >= 1 && shift < LIMB_BITS); + l = high; + for(i = n - 1; i >= 0; i--) { + a = tab[i]; + tab_r[i] = (a >> shift) | (l << (LIMB_BITS - shift)); + l = a; + } + return l & (((limb_t)1 << shift) - 1); +} + +/* r = (a << shift) + low. 1 <= shift <= LIMB_BITS - 1, 0 <= low < + 2^shift. */ +static limb_t mp_shl(limb_t *tab_r, const limb_t *tab, mp_size_t n, + int shift, limb_t low) +{ + mp_size_t i; + limb_t l, a; + + assert(shift >= 1 && shift < LIMB_BITS); + l = low; + for(i = 0; i < n; i++) { + a = tab[i]; + tab_r[i] = (a << shift) | l; + l = (a >> (LIMB_BITS - shift)); + } + return l; +} + +static no_inline limb_t mp_div1norm(limb_t *tabr, const limb_t *taba, limb_t n, + limb_t b, limb_t r, limb_t b_inv, int shift) +{ + slimb_t i; + + if (shift != 0) { + r = (r << shift) | mp_shl(tabr, taba, n, shift, 0); + } + for(i = n - 1; i >= 0; i--) { + tabr[i] = udiv1norm(&r, r, taba[i], b, b_inv); + } + r >>= shift; + return r; +} + +static __maybe_unused void mpb_dump(const char *str, const mpb_t *a) +{ + int i; + + printf("%s= 0x", str); + for(i = a->len - 1; i >= 0; i--) { + printf("%08x", a->tab[i]); + if (i != 0) + printf("_"); + } + printf("\n"); +} + +static void mpb_renorm(mpb_t *r) +{ + while (r->len > 1 && r->tab[r->len - 1] == 0) + r->len--; +} + +#ifdef USE_POW5_TABLE +static const uint32_t pow5_table[17] = { + 0x00000005, 0x00000019, 0x0000007d, 0x00000271, + 0x00000c35, 0x00003d09, 0x0001312d, 0x0005f5e1, + 0x001dcd65, 0x009502f9, 0x02e90edd, 0x0e8d4a51, + 0x48c27395, 0x6bcc41e9, 0x1afd498d, 0x86f26fc1, + 0xa2bc2ec5, +}; + +static const uint8_t pow5h_table[4] = { + 0x00000001, 0x00000007, 0x00000023, 0x000000b1, +}; + +static const uint32_t pow5_inv_table[13] = { + 0x99999999, 0x47ae147a, 0x0624dd2f, 0xa36e2eb1, + 0x4f8b588e, 0x0c6f7a0b, 0xad7f29ab, 0x5798ee23, + 0x12e0be82, 0xb7cdfd9d, 0x5fd7fe17, 0x19799812, + 0xc25c2684, +}; +#endif + +/* return a^b */ +static uint64_t pow_ui(uint32_t a, uint32_t b) +{ + int i, n_bits; + uint64_t r; + if (b == 0) + return 1; + if (b == 1) + return a; +#ifdef USE_POW5_TABLE + if ((a == 5 || a == 10) && b <= 17) { + r = pow5_table[b - 1]; + if (b >= 14) { + r |= (uint64_t)pow5h_table[b - 14] << 32; + } + if (a == 10) + r <<= b; + return r; + } +#endif + r = a; + n_bits = 32 - clz32(b); + for(i = n_bits - 2; i >= 0; i--) { + r *= r; + if ((b >> i) & 1) + r *= a; + } + return r; +} + +static uint32_t pow_ui_inv(uint32_t *pr_inv, int *pshift, uint32_t a, uint32_t b) +{ + uint32_t r_inv, r; + int shift; +#ifdef USE_POW5_TABLE + if (a == 5 && b >= 1 && b <= 13) { + r = pow5_table[b - 1]; + shift = clz32(r); + r <<= shift; + r_inv = pow5_inv_table[b - 1]; + } else +#endif + { + r = pow_ui(a, b); + shift = clz32(r); + r <<= shift; + r_inv = udiv1norm_init(r); + } + *pshift = shift; + *pr_inv = r_inv; + return r; +} + +enum { + JS_RNDN, /* round to nearest, ties to even */ + JS_RNDNA, /* round to nearest, ties away from zero */ + JS_RNDZ, +}; + +static int mpb_get_bit(const mpb_t *r, int k) +{ + int l; + + l = (unsigned)k / LIMB_BITS; + k = k & (LIMB_BITS - 1); + if (l >= r->len) + return 0; + else + return (r->tab[l] >> k) & 1; +} + +/* compute round(r / 2^shift). 'shift' can be negative */ +static void mpb_shr_round(mpb_t *r, int shift, int rnd_mode) +{ + int l, i; + + if (shift == 0) + return; + if (shift < 0) { + shift = -shift; + l = (unsigned)shift / LIMB_BITS; + shift = shift & (LIMB_BITS - 1); + if (shift != 0) { + r->tab[r->len] = mp_shl(r->tab, r->tab, r->len, shift, 0); + r->len++; + mpb_renorm(r); + } + if (l > 0) { + for(i = r->len - 1; i >= 0; i--) + r->tab[i + l] = r->tab[i]; + for(i = 0; i < l; i++) + r->tab[i] = 0; + r->len += l; + } + } else { + limb_t bit1, bit2; + int k, add_one; + + switch(rnd_mode) { + default: + case JS_RNDZ: + add_one = 0; + break; + case JS_RNDN: + case JS_RNDNA: + bit1 = mpb_get_bit(r, shift - 1); + if (bit1) { + if (rnd_mode == JS_RNDNA) { + bit2 = 1; + } else { + /* bit2 = oring of all the bits after bit1 */ + bit2 = 0; + if (shift >= 2) { + k = shift - 1; + l = (unsigned)k / LIMB_BITS; + k = k & (LIMB_BITS - 1); + for(i = 0; i < min_int(l, r->len); i++) + bit2 |= r->tab[i]; + if (l < r->len) + bit2 |= r->tab[l] & (((limb_t)1 << k) - 1); + } + } + if (bit2) { + add_one = 1; + } else { + /* round to even */ + add_one = mpb_get_bit(r, shift); + } + } else { + add_one = 0; + } + break; + } + + l = (unsigned)shift / LIMB_BITS; + shift = shift & (LIMB_BITS - 1); + if (l >= r->len) { + r->len = 1; + r->tab[0] = add_one; + } else { + if (l > 0) { + r->len -= l; + for(i = 0; i < r->len; i++) + r->tab[i] = r->tab[i + l]; + } + if (shift != 0) { + mp_shr(r->tab, r->tab, r->len, shift, 0); + mpb_renorm(r); + } + if (add_one) { + limb_t a; + a = mp_add_ui(r->tab, 1, r->len); + if (a) + r->tab[r->len++] = a; + } + } + } +} + +/* return -1, 0 or 1 */ +static int mpb_cmp(const mpb_t *a, const mpb_t *b) +{ + mp_size_t i; + if (a->len < b->len) + return -1; + else if (a->len > b->len) + return 1; + for(i = a->len - 1; i >= 0; i--) { + if (a->tab[i] != b->tab[i]) { + if (a->tab[i] < b->tab[i]) + return -1; + else + return 1; + } + } + return 0; +} + +static void mpb_set_u64(mpb_t *r, uint64_t m) +{ +#if LIMB_BITS == 64 + r->tab[0] = m; + r->len = 1; +#else + r->tab[0] = m; + r->tab[1] = m >> LIMB_BITS; + if (r->tab[1] == 0) + r->len = 1; + else + r->len = 2; +#endif +} + +static uint64_t mpb_get_u64(mpb_t *r) +{ +#if LIMB_BITS == 64 + return r->tab[0]; +#else + if (r->len == 1) { + return r->tab[0]; + } else { + return r->tab[0] | ((uint64_t)r->tab[1] << LIMB_BITS); + } +#endif +} + +/* floor_log2() = position of the first non zero bit or -1 if zero. */ +static int mpb_floor_log2(mpb_t *a) +{ + limb_t v; + v = a->tab[a->len - 1]; + if (v == 0) + return -1; + else + return a->len * LIMB_BITS - 1 - clz32(v); +} + +#define MUL_LOG2_RADIX_BASE_LOG2 24 + +/* round((1 << MUL_LOG2_RADIX_BASE_LOG2)/log2(i + 2)) */ +static const uint32_t mul_log2_radix_table[JS_RADIX_MAX - 1] = { + 0x000000, 0xa1849d, 0x000000, 0x6e40d2, + 0x6308c9, 0x5b3065, 0x000000, 0x50c24e, + 0x4d104d, 0x4a0027, 0x4768ce, 0x452e54, + 0x433d00, 0x418677, 0x000000, 0x3ea16b, + 0x3d645a, 0x3c43c2, 0x3b3b9a, 0x3a4899, + 0x39680b, 0x3897b3, 0x37d5af, 0x372069, + 0x367686, 0x35d6df, 0x354072, 0x34b261, + 0x342bea, 0x33ac62, 0x000000, 0x32bfd9, + 0x3251dd, 0x31e8d6, 0x318465, +}; + +/* return floor(a / log2(radix)) for -2048 <= a <= 2047 */ +static int mul_log2_radix(int a, int radix) +{ + int radix_bits, mult; + + if ((radix & (radix - 1)) == 0) { + /* if the radix is a power of two better to do it exactly */ + radix_bits = 31 - clz32(radix); + if (a < 0) + a -= radix_bits - 1; + return a / radix_bits; + } else { + mult = mul_log2_radix_table[radix - 2]; + return ((int64_t)a * mult) >> MUL_LOG2_RADIX_BASE_LOG2; + } +} + +#if 0 +static void build_mul_log2_radix_table(void) +{ + int base, radix, mult, col, base_log2; + + base_log2 = 24; + base = 1 << base_log2; + col = 0; + for(radix = 2; radix <= 36; radix++) { + if ((radix & (radix - 1)) == 0) + mult = 0; + else + mult = lrint((double)base / log2(radix)); + printf("0x%06x, ", mult); + if (++col == 4) { + printf("\n"); + col = 0; + } + } + printf("\n"); +} + +static void mul_log2_radix_test(void) +{ + int radix, i, ref, r; + + for(radix = 2; radix <= 36; radix++) { + for(i = -2048; i <= 2047; i++) { + ref = (int)floor((double)i / log2(radix)); + r = mul_log2_radix(i, radix); + if (ref != r) { + printf("ERROR: radix=%d i=%d r=%d ref=%d\n", + radix, i, r, ref); + exit(1); + } + } + } + if (0) + build_mul_log2_radix_table(); +} +#endif + +static void u32toa_len(char *buf, uint32_t n, size_t len) +{ + int digit, i; + for(i = len - 1; i >= 0; i--) { + digit = n % 10; + n = n / 10; + buf[i] = digit + '0'; + } +} + +/* for power of 2 radixes. len >= 1 */ +static void u64toa_bin_len(char *buf, uint64_t n, unsigned int radix_bits, int len) +{ + int digit, i; + unsigned int mask; + + mask = (1 << radix_bits) - 1; + for(i = len - 1; i >= 0; i--) { + digit = n & mask; + n >>= radix_bits; + if (digit < 10) + digit += '0'; + else + digit += 'a' - 10; + buf[i] = digit; + } +} + +/* len >= 1. 2 <= radix <= 36 */ +static void limb_to_a(char *buf, limb_t n, unsigned int radix, int len) +{ + int digit, i; + + if (radix == 10) { + /* specific case with constant divisor */ +#if LIMB_BITS == 32 + u32toa_len(buf, n, len); +#else + /* XXX: optimize */ + for(i = len - 1; i >= 0; i--) { + digit = (limb_t)n % 10; + n = (limb_t)n / 10; + buf[i] = digit + '0'; + } +#endif + } else { + for(i = len - 1; i >= 0; i--) { + digit = (limb_t)n % radix; + n = (limb_t)n / radix; + if (digit < 10) + digit += '0'; + else + digit += 'a' - 10; + buf[i] = digit; + } + } +} + +size_t u32toa(char *buf, uint32_t n) +{ + char buf1[10], *q; + size_t len; + + q = buf1 + sizeof(buf1); + do { + *--q = n % 10 + '0'; + n /= 10; + } while (n != 0); + len = buf1 + sizeof(buf1) - q; + memcpy(buf, q, len); + return len; +} + +size_t i32toa(char *buf, int32_t n) +{ + if (n >= 0) { + return u32toa(buf, n); + } else { + buf[0] = '-'; + return u32toa(buf + 1, -(uint32_t)n) + 1; + } +} + +#ifdef USE_FAST_INT +size_t u64toa(char *buf, uint64_t n) +{ + if (n < 0x100000000) { + return u32toa(buf, n); + } else { + uint64_t n1; + char *q = buf; + uint32_t n2; + + n1 = n / 1000000000; + n %= 1000000000; + if (n1 >= 0x100000000) { + n2 = n1 / 1000000000; + n1 = n1 % 1000000000; + /* at most two digits */ + if (n2 >= 10) { + *q++ = n2 / 10 + '0'; + n2 %= 10; + } + *q++ = n2 + '0'; + u32toa_len(q, n1, 9); + q += 9; + } else { + q += u32toa(q, n1); + } + u32toa_len(q, n, 9); + q += 9; + return q - buf; + } +} + +size_t i64toa(char *buf, int64_t n) +{ + if (n >= 0) { + return u64toa(buf, n); + } else { + buf[0] = '-'; + return u64toa(buf + 1, -(uint64_t)n) + 1; + } +} + +/* XXX: only tested for 1 <= n < 2^53 */ +size_t u64toa_radix(char *buf, uint64_t n, unsigned int radix) +{ + int radix_bits, l; + if (likely(radix == 10)) + return u64toa(buf, n); + if ((radix & (radix - 1)) == 0) { + radix_bits = 31 - clz32(radix); + if (n == 0) + l = 1; + else + l = (64 - clz64(n) + radix_bits - 1) / radix_bits; + u64toa_bin_len(buf, n, radix_bits, l); + return l; + } else { + char buf1[41], *q; /* maximum length for radix = 3 */ + size_t len; + int digit; + q = buf1 + sizeof(buf1); + do { + digit = n % radix; + n /= radix; + if (digit < 10) + digit += '0'; + else + digit += 'a' - 10; + *--q = digit; + } while (n != 0); + len = buf1 + sizeof(buf1) - q; + memcpy(buf, q, len); + return len; + } +} + +size_t i64toa_radix(char *buf, int64_t n, unsigned int radix) +{ + if (n >= 0) { + return u64toa_radix(buf, n, radix); + } else { + buf[0] = '-'; + return u64toa_radix(buf + 1, -(uint64_t)n, radix) + 1; + } +} +#endif /* USE_FAST_INT */ + +static const uint8_t digits_per_limb_table[JS_RADIX_MAX - 1] = { +#if LIMB_BITS == 32 +32,20,16,13,12,11,10,10, 9, 9, 8, 8, 8, 8, 8, 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, +#else +64,40,32,27,24,22,21,20,19,18,17,17,16,16,16,15,15,15,14,14,14,14,13,13,13,13,13,13,13,12,12,12,12,12,12, +#endif +}; + +static const uint32_t radix_base_table[JS_RADIX_MAX - 1] = { + 0x00000000, 0xcfd41b91, 0x00000000, 0x48c27395, + 0x81bf1000, 0x75db9c97, 0x40000000, 0xcfd41b91, + 0x3b9aca00, 0x8c8b6d2b, 0x19a10000, 0x309f1021, + 0x57f6c100, 0x98c29b81, 0x00000000, 0x18754571, + 0x247dbc80, 0x3547667b, 0x4c4b4000, 0x6b5a6e1d, + 0x94ace180, 0xcaf18367, 0x0b640000, 0x0e8d4a51, + 0x1269ae40, 0x17179149, 0x1cb91000, 0x23744899, + 0x2b73a840, 0x34e63b41, 0x40000000, 0x4cfa3cc1, + 0x5c13d840, 0x6d91b519, 0x81bf1000, +}; + +/* XXX: remove the table ? */ +static uint8_t dtoa_max_digits_table[JS_RADIX_MAX - 1] = { + 54, 35, 28, 24, 22, 20, 19, 18, 17, 17, 16, 16, 15, 15, 15, 14, 14, 14, 14, 14, 13, 13, 13, 13, 13, 13, 13, 12, 12, 12, 12, 12, 12, 12, 12, +}; + +/* we limit the maximum number of significant digits for atod to about + 128 bits of precision for non power of two bases. The only + requirement for Javascript is at least 20 digits in base 10. For + power of two bases, we do an exact rounding in all the cases. */ +static uint8_t atod_max_digits_table[JS_RADIX_MAX - 1] = { + 64, 80, 32, 55, 49, 45, 21, 40, 38, 37, 35, 34, 33, 32, 16, 31, 30, 30, 29, 29, 28, 28, 27, 27, 27, 26, 26, 26, 26, 25, 12, 25, 25, 24, 24, +}; + +/* if abs(d) >= B^max_exponent, it is an overflow */ +static const int16_t max_exponent[JS_RADIX_MAX - 1] = { + 1024, 647, 512, 442, 397, 365, 342, 324, + 309, 297, 286, 277, 269, 263, 256, 251, + 246, 242, 237, 234, 230, 227, 224, 221, + 218, 216, 214, 211, 209, 207, 205, 203, + 202, 200, 199, +}; + +/* if abs(d) <= B^min_exponent, it is an underflow */ +static const int16_t min_exponent[JS_RADIX_MAX - 1] = { +-1075, -679, -538, -463, -416, -383, -359, -340, + -324, -311, -300, -291, -283, -276, -269, -263, + -258, -254, -249, -245, -242, -238, -235, -232, + -229, -227, -224, -222, -220, -217, -215, -214, + -212, -210, -208, +}; + +#if 0 +void build_tables(void) +{ + int r, j, radix, n, col, i; + + /* radix_base_table */ + for(radix = 2; radix <= 36; radix++) { + r = 1; + for(j = 0; j < digits_per_limb_table[radix - 2]; j++) { + r *= radix; + } + printf(" 0x%08x,", r); + if ((radix % 4) == 1) + printf("\n"); + } + printf("\n"); + + /* dtoa_max_digits_table */ + for(radix = 2; radix <= 36; radix++) { + /* Note: over estimated when the radix is a power of two */ + printf(" %d,", 1 + (int)ceil(53.0 / log2(radix))); + } + printf("\n"); + + /* atod_max_digits_table */ + for(radix = 2; radix <= 36; radix++) { + if ((radix & (radix - 1)) == 0) { + /* 64 bits is more than enough */ + n = (int)floor(64.0 / log2(radix)); + } else { + n = (int)floor(128.0 / log2(radix)); + } + printf(" %d,", n); + } + printf("\n"); + + printf("static const int16_t max_exponent[JS_RADIX_MAX - 1] = {\n"); + col = 0; + for(radix = 2; radix <= 36; radix++) { + printf("%5d, ", (int)ceil(1024 / log2(radix))); + if (++col == 8) { + col = 0; + printf("\n"); + } + } + printf("\n};\n\n"); + + printf("static const int16_t min_exponent[JS_RADIX_MAX - 1] = {\n"); + col = 0; + for(radix = 2; radix <= 36; radix++) { + printf("%5d, ", (int)floor(-1075 / log2(radix))); + if (++col == 8) { + col = 0; + printf("\n"); + } + } + printf("\n};\n\n"); + + printf("static const uint32_t pow5_table[16] = {\n"); + col = 0; + for(i = 2; i <= 17; i++) { + r = 1; + for(j = 0; j < i; j++) { + r *= 5; + } + printf("0x%08x, ", r); + if (++col == 4) { + col = 0; + printf("\n"); + } + } + printf("\n};\n\n"); + + /* high part */ + printf("static const uint8_t pow5h_table[4] = {\n"); + col = 0; + for(i = 14; i <= 17; i++) { + uint64_t r1; + r1 = 1; + for(j = 0; j < i; j++) { + r1 *= 5; + } + printf("0x%08x, ", (uint32_t)(r1 >> 32)); + if (++col == 4) { + col = 0; + printf("\n"); + } + } + printf("\n};\n\n"); +} +#endif + +/* n_digits >= 1. 0 <= dot_pos <= n_digits. If dot_pos == n_digits, + the dot is not displayed. 'a' is modified. */ +static int output_digits(char *buf, + mpb_t *a, int radix, int n_digits1, + int dot_pos) +{ + int n_digits, digits_per_limb, radix_bits, n, len; + + n_digits = n_digits1; + if ((radix & (radix - 1)) == 0) { + /* radix = 2^radix_bits */ + radix_bits = 31 - clz32(radix); + } else { + radix_bits = 0; + } + digits_per_limb = digits_per_limb_table[radix - 2]; + if (radix_bits != 0) { + for(;;) { + n = min_int(n_digits, digits_per_limb); + n_digits -= n; + u64toa_bin_len(buf + n_digits, a->tab[0], radix_bits, n); + if (n_digits == 0) + break; + mpb_shr_round(a, digits_per_limb * radix_bits, JS_RNDZ); + } + } else { + limb_t r; + while (n_digits != 0) { + n = min_int(n_digits, digits_per_limb); + n_digits -= n; + r = mp_div1(a->tab, a->tab, a->len, radix_base_table[radix - 2], 0); + mpb_renorm(a); + limb_to_a(buf + n_digits, r, radix, n); + } + } + + /* add the dot */ + len = n_digits1; + if (dot_pos != n_digits1) { + memmove(buf + dot_pos + 1, buf + dot_pos, n_digits1 - dot_pos); + buf[dot_pos] = '.'; + len++; + } + return len; +} + +/* return (a, e_offset) such that a = a * (radix1*2^radix_shift)^f * + 2^-e_offset. 'f' can be negative. */ +static int mul_pow(mpb_t *a, int radix1, int radix_shift, int f, bool is_int, int e) +{ + int e_offset, d, n, n0; + + e_offset = -f * radix_shift; + if (radix1 != 1) { + d = digits_per_limb_table[radix1 - 2]; + if (f >= 0) { + limb_t h, b; + + b = 0; + n0 = 0; + while (f != 0) { + n = min_int(f, d); + if (n != n0) { + b = pow_ui(radix1, n); + n0 = n; + } + h = mp_mul1(a->tab, a->tab, a->len, b, 0); + if (h != 0) { + a->tab[a->len++] = h; + } + f -= n; + } + } else { + int extra_bits, l, shift; + limb_t r, rem, b, b_inv; + + f = -f; + l = (f + d - 1) / d; /* high bound for the number of limbs (XXX: make it better) */ + e_offset += l * LIMB_BITS; + if (!is_int) { + /* at least 'e' bits are needed in the final result for rounding */ + extra_bits = max_int(e - mpb_floor_log2(a), 0); + } else { + /* at least two extra bits are needed in the final result + for rounding */ + extra_bits = max_int(2 + e - e_offset, 0); + } + e_offset += extra_bits; + mpb_shr_round(a, -(l * LIMB_BITS + extra_bits), JS_RNDZ); + + b = 0; + b_inv = 0; + shift = 0; + n0 = 0; + rem = 0; + while (f != 0) { + n = min_int(f, d); + if (n != n0) { + b = pow_ui_inv(&b_inv, &shift, radix1, n); + n0 = n; + } + r = mp_div1norm(a->tab, a->tab, a->len, b, 0, b_inv, shift); + rem |= r; + mpb_renorm(a); + f -= n; + } + /* if the remainder is non zero, use it for rounding */ + a->tab[0] |= (rem != 0); + } + } + return e_offset; +} + +/* tmp1 = round(m*2^e*radix^f). 'tmp0' is a temporary storage */ +static void mul_pow_round(mpb_t *tmp1, uint64_t m, int e, int radix1, int radix_shift, int f, + int rnd_mode) +{ + int e_offset; + + mpb_set_u64(tmp1, m); + e_offset = mul_pow(tmp1, radix1, radix_shift, f, true, e); + mpb_shr_round(tmp1, -e + e_offset, rnd_mode); +} + +/* return round(a*2^e_offset) rounded as a float64. 'a' is modified */ +static uint64_t round_to_d(int *pe, mpb_t *a, int e_offset, int rnd_mode) +{ + int e; + uint64_t m; + + if (a->tab[0] == 0 && a->len == 1) { + /* zero result */ + m = 0; + e = 0; /* don't care */ + } else { + int prec, prec1, e_min; + e = mpb_floor_log2(a) + 1 - e_offset; + prec1 = 53; + e_min = -1021; + if (e < e_min) { + /* subnormal result or zero */ + prec = prec1 - (e_min - e); + } else { + prec = prec1; + } + mpb_shr_round(a, e + e_offset - prec, rnd_mode); + m = mpb_get_u64(a); + m <<= (53 - prec); + /* mantissa overflow due to rounding */ + if (m >= (uint64_t)1 << 53) { + m >>= 1; + e++; + } + } + *pe = e; + return m; +} + +/* return (m, e) such that m*2^(e-53) = round(a * radix^f) with 2^52 + <= m < 2^53 or m = 0. + 'a' is modified. */ +static uint64_t mul_pow_round_to_d(int *pe, mpb_t *a, + int radix1, int radix_shift, int f, int rnd_mode) +{ + int e_offset; + + e_offset = mul_pow(a, radix1, radix_shift, f, false, 55); + return round_to_d(pe, a, e_offset, rnd_mode); +} + +#ifdef JS_DTOA_DUMP_STATS +static int out_len_count[17]; + +void js_dtoa_dump_stats(void) +{ + int i, sum; + sum = 0; + for(i = 0; i < 17; i++) + sum += out_len_count[i]; + for(i = 0; i < 17; i++) { + printf("%2d %8d %5.2f%%\n", + i + 1, out_len_count[i], (double)out_len_count[i] / sum * 100); + } +} +#endif + +/* return a maximum bound of the string length. The bound depends on + 'd' only if format = JS_DTOA_FORMAT_FRAC or if JS_DTOA_EXP_DISABLED + is enabled. */ +int js_dtoa_max_len(double d, int radix, int n_digits, int flags) +{ + int fmt = flags & JS_DTOA_FORMAT_MASK; + int n, e; + uint64_t a; + + if (fmt != JS_DTOA_FORMAT_FRAC) { + if (fmt == JS_DTOA_FORMAT_FREE) { + n = dtoa_max_digits_table[radix - 2]; + } else { + n = n_digits; + } + if ((flags & JS_DTOA_EXP_MASK) == JS_DTOA_EXP_DISABLED) { + /* no exponential */ + a = float64_as_uint64(d); + e = (a >> 52) & 0x7ff; + if (e == 0x7ff) { + /* NaN, Infinity */ + n = 0; + } else { + e -= 1023; + /* XXX: adjust */ + n += 10 + abs(mul_log2_radix(e - 1, radix)); + } + } else { + /* extra: sign, 1 dot and exponent "e-1000" */ + n += 1 + 1 + 6; + } + } else { + a = float64_as_uint64(d); + e = (a >> 52) & 0x7ff; + if (e == 0x7ff) { + /* NaN, Infinity */ + n = 0; + } else { + /* high bound for the integer part */ + e -= 1023; + /* x < 2^(e + 1) */ + if (e < 0) { + n = 1; + } else { + n = 2 + mul_log2_radix(e - 1, radix); + } + /* sign, extra digit, 1 dot */ + n += 1 + 1 + 1 + n_digits; + } + } + return max_int(n, 9); /* also include NaN and [-]Infinity */ +} + +#if defined(__SANITIZE_ADDRESS__) && 0 +static void *dtoa_malloc(uint64_t **pptr, size_t size) +{ + return malloc(size); +} +static void dtoa_free(void *ptr) +{ + free(ptr); +} +#else +static void *dtoa_malloc(uint64_t **pptr, size_t size) +{ + void *ret; + ret = *pptr; + *pptr += (size + 7) / 8; + return ret; +} + +static void dtoa_free(void *ptr) +{ +} +#endif + +/* return the length */ +int js_dtoa(char *buf, double d, int radix, int n_digits, int flags, + JSDTOATempMem *tmp_mem) +{ + uint64_t a, m, *mptr = tmp_mem->mem; + int e, sgn, l, E, P, i, E_max, radix1, radix_shift; + char *q; + mpb_t *tmp1, *mant_max; + int fmt = flags & JS_DTOA_FORMAT_MASK; + + tmp1 = dtoa_malloc(&mptr, sizeof(mpb_t) + sizeof(limb_t) * DBIGNUM_LEN_MAX); + mant_max = dtoa_malloc(&mptr, sizeof(mpb_t) + sizeof(limb_t) * MANT_LEN_MAX); + assert((mptr - tmp_mem->mem) <= sizeof(JSDTOATempMem) / sizeof(mptr[0])); + + radix_shift = ctz32(radix); + radix1 = radix >> radix_shift; + a = float64_as_uint64(d); + sgn = a >> 63; + e = (a >> 52) & 0x7ff; + m = a & (((uint64_t)1 << 52) - 1); + q = buf; + if (e == 0x7ff) { + if (m == 0) { + if (sgn) + *q++ = '-'; + memcpy(q, "Infinity", 8); + q += 8; + } else { + memcpy(q, "NaN", 3); + q += 3; + } + goto done; + } else if (e == 0) { + if (m == 0) { + tmp1->len = 1; + tmp1->tab[0] = 0; + E = 1; + if (fmt == JS_DTOA_FORMAT_FREE) + P = 1; + else if (fmt == JS_DTOA_FORMAT_FRAC) + P = n_digits + 1; + else + P = n_digits; + /* "-0" is displayed as "0" if JS_DTOA_MINUS_ZERO is not present */ + if (sgn && (flags & JS_DTOA_MINUS_ZERO)) + *q++ = '-'; + goto output; + } + /* denormal number: convert to a normal number */ + l = clz64(m) - 11; + e -= l - 1; + m <<= l; + } else { + m |= (uint64_t)1 << 52; + } + if (sgn) + *q++ = '-'; + /* remove the bias */ + e -= 1022; + /* d = 2^(e-53)*m */ + // printf("m=0x%016" PRIx64 " e=%d\n", m, e); +#ifdef USE_FAST_INT + if (fmt == JS_DTOA_FORMAT_FREE && + e >= 1 && e <= 53 && + (m & (((uint64_t)1 << (53 - e)) - 1)) == 0 && + (flags & JS_DTOA_EXP_MASK) != JS_DTOA_EXP_ENABLED) { + m >>= 53 - e; + /* 'm' is never zero */ + q += u64toa_radix(q, m, radix); + goto done; + } +#endif + + /* this choice of E implies F=round(x*B^(P-E) is such as: + B^(P-1) <= F < 2.B^P. */ + E = 1 + mul_log2_radix(e - 1, radix); + + if (fmt == JS_DTOA_FORMAT_FREE) { + int P_max, E0, e1, E_found, P_found; + uint64_t m1, mant_found, mant, mant_max1; + /* P_max is guarranteed to work by construction */ + P_max = dtoa_max_digits_table[radix - 2]; + E0 = E; + E_found = 0; + P_found = 0; + mant_found = 0; + /* find the minimum number of digits by successive tries */ + P = P_max; /* P_max is guarateed to work */ + for(;;) { + /* mant_max always fits on 64 bits */ + mant_max1 = pow_ui(radix, P); + /* compute the mantissa in base B */ + E = E0; + for(;;) { + /* XXX: add inexact flag */ + mul_pow_round(tmp1, m, e - 53, radix1, radix_shift, P - E, JS_RNDN); + mant = mpb_get_u64(tmp1); + if (mant < mant_max1) + break; + E++; /* at most one iteration is possible */ + } + /* remove useless trailing zero digits */ + while ((mant % radix) == 0) { + mant /= radix; + P--; + } + /* garanteed to work for P = P_max */ + if (P_found == 0) + goto prec_found; + /* convert back to base 2 */ + mpb_set_u64(tmp1, mant); + m1 = mul_pow_round_to_d(&e1, tmp1, radix1, radix_shift, E - P, JS_RNDN); + // printf("P=%2d: m=0x%016" PRIx64 " e=%d m1=0x%016" PRIx64 " e1=%d\n", P, m, e, m1, e1); + /* Note: (m, e) is never zero here, so the exponent for m1 + = 0 does not matter */ + if (m1 == m && e1 == e) { + prec_found: + P_found = P; + E_found = E; + mant_found = mant; + if (P == 1) + break; + P--; /* try lower exponent */ + } else { + break; + } + } + P = P_found; + E = E_found; + mpb_set_u64(tmp1, mant_found); +#ifdef JS_DTOA_DUMP_STATS + if (radix == 10) { + out_len_count[P - 1]++; + } +#endif + } else if (fmt == JS_DTOA_FORMAT_FRAC) { + int len; + + assert(n_digits >= 0 && n_digits <= JS_DTOA_MAX_DIGITS); + /* P = max_int(E, 1) + n_digits; */ + /* frac is rounded using RNDNA */ + mul_pow_round(tmp1, m, e - 53, radix1, radix_shift, n_digits, JS_RNDNA); + + /* we add one extra digit on the left and remove it if needed + to avoid testing if the result is < radix^P */ + len = output_digits(q, tmp1, radix, max_int(E + 1, 1) + n_digits, + max_int(E + 1, 1)); + if (q[0] == '0' && len >= 2 && q[1] != '.') { + len--; + memmove(q, q + 1, len); + } + q += len; + goto done; + } else { + int pow_shift; + assert(n_digits >= 1 && n_digits <= JS_DTOA_MAX_DIGITS); + P = n_digits; + /* mant_max = radix^P */ + mant_max->len = 1; + mant_max->tab[0] = 1; + pow_shift = mul_pow(mant_max, radix1, radix_shift, P, false, 0); + mpb_shr_round(mant_max, pow_shift, JS_RNDZ); + + for(;;) { + /* fixed and frac are rounded using RNDNA */ + mul_pow_round(tmp1, m, e - 53, radix1, radix_shift, P - E, JS_RNDNA); + if (mpb_cmp(tmp1, mant_max) < 0) + break; + E++; /* at most one iteration is possible */ + } + } + output: + if (fmt == JS_DTOA_FORMAT_FIXED) + E_max = n_digits; + else + E_max = dtoa_max_digits_table[radix - 2] + 4; + if ((flags & JS_DTOA_EXP_MASK) == JS_DTOA_EXP_ENABLED || + ((flags & JS_DTOA_EXP_MASK) == JS_DTOA_EXP_AUTO && (E <= -6 || E > E_max))) { + q += output_digits(q, tmp1, radix, P, 1); + E--; + if (radix == 10) { + *q++ = 'e'; + } else if (radix1 == 1 && radix_shift <= 4) { + E *= radix_shift; + *q++ = 'p'; + } else { + *q++ = '@'; + } + if (E < 0) { + *q++ = '-'; + E = -E; + } else { + *q++ = '+'; + } + q += u32toa(q, E); + } else if (E <= 0) { + *q++ = '0'; + *q++ = '.'; + for(i = 0; i < -E; i++) + *q++ = '0'; + q += output_digits(q, tmp1, radix, P, P); + } else { + q += output_digits(q, tmp1, radix, P, min_int(P, E)); + for(i = 0; i < E - P; i++) + *q++ = '0'; + } + done: + *q = '\0'; + dtoa_free(mant_max); + dtoa_free(tmp1); + return q - buf; +} + +static inline int to_digit(int c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + else if (c >= 'A' && c <= 'Z') + return c - 'A' + 10; + else if (c >= 'a' && c <= 'z') + return c - 'a' + 10; + else + return 36; +} + +/* r = r * radix_base + a. radix_base = 0 means radix_base = 2^32 */ +static void mpb_mul1_base(mpb_t *r, limb_t radix_base, limb_t a) +{ + int i; + if (r->tab[0] == 0 && r->len == 1) { + r->tab[0] = a; + } else { + if (radix_base == 0) { + for(i = r->len; i >= 0; i--) { + r->tab[i + 1] = r->tab[i]; + } + r->tab[0] = a; + } else { + r->tab[r->len] = mp_mul1(r->tab, r->tab, r->len, + radix_base, a); + } + r->len++; + mpb_renorm(r); + } +} + +/* XXX: add fast path for small integers */ +double js_atod(const char *str, const char **pnext, int radix, int flags, + JSATODTempMem *tmp_mem) +{ + uint64_t *mptr = tmp_mem->mem; + const char *p, *p_start; + limb_t cur_limb, radix_base, extra_digits; + int is_neg, digit_count, limb_digit_count, digits_per_limb, sep, radix1, radix_shift; + int radix_bits, expn, e, max_digits, expn_offset, dot_pos, sig_pos, pos; + mpb_t *tmp0; + double dval; + bool is_bin_exp, is_zero, expn_overflow; + uint64_t m, a; + + tmp0 = dtoa_malloc(&mptr, sizeof(mpb_t) + sizeof(limb_t) * DBIGNUM_LEN_MAX); + assert((mptr - tmp_mem->mem) <= sizeof(JSATODTempMem) / sizeof(mptr[0])); + /* optional separator between digits */ + sep = (flags & JS_ATOD_ACCEPT_UNDERSCORES) ? '_' : 256; + + p = str; + is_neg = 0; + if (p[0] == '+') { + p++; + p_start = p; + } else if (p[0] == '-') { + is_neg = 1; + p++; + p_start = p; + } else { + p_start = p; + } + + if (p[0] == '0') { + if ((p[1] == 'x' || p[1] == 'X') && + (radix == 0 || radix == 16)) { + p += 2; + radix = 16; + } else if ((p[1] == 'o' || p[1] == 'O') && + radix == 0 && (flags & JS_ATOD_ACCEPT_BIN_OCT)) { + p += 2; + radix = 8; + } else if ((p[1] == 'b' || p[1] == 'B') && + radix == 0 && (flags & JS_ATOD_ACCEPT_BIN_OCT)) { + p += 2; + radix = 2; + } else if ((p[1] >= '0' && p[1] <= '9') && + radix == 0 && (flags & JS_ATOD_ACCEPT_LEGACY_OCTAL)) { + int i; + sep = 256; + for (i = 1; (p[i] >= '0' && p[i] <= '7'); i++) + continue; + if (p[i] == '8' || p[i] == '9') + goto no_prefix; + p += 1; + radix = 8; + } else { + goto no_prefix; + } + /* there must be a digit after the prefix */ + if (to_digit((uint8_t)*p) >= radix) + goto fail; + no_prefix: ; + } else { + if (!(flags & JS_ATOD_INT_ONLY) && js__strstart(p, "Infinity", &p)) + goto overflow; + } + if (radix == 0) + radix = 10; + + cur_limb = 0; + expn_offset = 0; + digit_count = 0; + limb_digit_count = 0; + max_digits = atod_max_digits_table[radix - 2]; + digits_per_limb = digits_per_limb_table[radix - 2]; + radix_base = radix_base_table[radix - 2]; + radix_shift = ctz32(radix); + radix1 = radix >> radix_shift; + if (radix1 == 1) { + /* radix = 2^radix_bits */ + radix_bits = radix_shift; + } else { + radix_bits = 0; + } + tmp0->len = 1; + tmp0->tab[0] = 0; + extra_digits = 0; + pos = 0; + dot_pos = -1; + /* skip leading zeros */ + for(;;) { + if (*p == '.' && (p > p_start || to_digit(p[1]) < radix) && + !(flags & JS_ATOD_INT_ONLY)) { + if (*p == sep) + goto fail; + if (dot_pos >= 0) + break; + dot_pos = pos; + p++; + } + if (*p == sep && p > p_start && p[1] == '0') + p++; + if (*p != '0') + break; + p++; + pos++; + } + + sig_pos = pos; + for(;;) { + limb_t c; + if (*p == '.' && (p > p_start || to_digit(p[1]) < radix) && + !(flags & JS_ATOD_INT_ONLY)) { + if (*p == sep) + goto fail; + if (dot_pos >= 0) + break; + dot_pos = pos; + p++; + } + if (*p == sep && p > p_start && to_digit(p[1]) < radix) + p++; + c = to_digit(*p); + if (c >= radix) + break; + p++; + pos++; + if (digit_count < max_digits) { + /* XXX: could be faster when radix_bits != 0 */ + cur_limb = cur_limb * radix + c; + limb_digit_count++; + if (limb_digit_count == digits_per_limb) { + mpb_mul1_base(tmp0, radix_base, cur_limb); + cur_limb = 0; + limb_digit_count = 0; + } + digit_count++; + } else { + extra_digits |= c; + } + } + if (limb_digit_count != 0) { + mpb_mul1_base(tmp0, pow_ui(radix, limb_digit_count), cur_limb); + } + if (digit_count == 0) { + is_zero = true; + expn_offset = 0; + } else { + is_zero = false; + if (dot_pos < 0) + dot_pos = pos; + expn_offset = sig_pos + digit_count - dot_pos; + } + + /* Use the extra digits for rounding if the base is a power of + two. Otherwise they are just truncated. */ + if (radix_bits != 0 && extra_digits != 0) { + tmp0->tab[0] |= 1; + } + + /* parse the exponent, if any */ + expn = 0; + expn_overflow = false; + is_bin_exp = false; + if (!(flags & JS_ATOD_INT_ONLY) && + ((radix == 10 && (*p == 'e' || *p == 'E')) || + (radix != 10 && (*p == '@' || + (radix_bits >= 1 && radix_bits <= 4 && (*p == 'p' || *p == 'P'))))) && + p > p_start) { + bool exp_is_neg; + int c; + is_bin_exp = (*p == 'p' || *p == 'P'); + p++; + exp_is_neg = false; + if (*p == '+') { + p++; + } else if (*p == '-') { + exp_is_neg = true; + p++; + } + c = to_digit(*p); + if (c >= 10) + goto fail; /* XXX: could stop before the exponent part */ + expn = c; + p++; + for(;;) { + if (*p == sep && to_digit(p[1]) < 10) + p++; + c = to_digit(*p); + if (c >= 10) + break; + if (!expn_overflow) { + if (unlikely(expn > ((INT32_MAX - 2 - 9) / 10))) { + expn_overflow = true; + } else { + expn = expn * 10 + c; + } + } + p++; + } + if (exp_is_neg) + expn = -expn; + /* if zero result, the exponent can be arbitrarily large */ + if (!is_zero && expn_overflow) { + if (exp_is_neg) + a = 0; + else + a = (uint64_t)0x7ff << 52; /* infinity */ + goto done; + } + } + + if (p == p_start) + goto fail; + + if (is_zero) { + a = 0; + } else { + int expn1; + if (radix_bits != 0) { + if (!is_bin_exp) + expn *= radix_bits; + expn -= expn_offset * radix_bits; + expn1 = expn + digit_count * radix_bits; + if (expn1 >= 1024 + radix_bits) + goto overflow; + else if (expn1 <= -1075) + goto underflow; + m = round_to_d(&e, tmp0, -expn, JS_RNDN); + } else { + expn -= expn_offset; + expn1 = expn + digit_count; + if (expn1 >= max_exponent[radix - 2] + 1) + goto overflow; + else if (expn1 <= min_exponent[radix - 2]) + goto underflow; + m = mul_pow_round_to_d(&e, tmp0, radix1, radix_shift, expn, JS_RNDN); + } + if (m == 0) { + underflow: + a = 0; + } else if (e > 1024) { + overflow: + /* overflow */ + a = (uint64_t)0x7ff << 52; + } else if (e < -1073) { + /* underflow */ + /* XXX: check rounding */ + a = 0; + } else if (e < -1021) { + /* subnormal */ + a = m >> (-e - 1021); + } else { + a = ((uint64_t)(e + 1022) << 52) | (m & (((uint64_t)1 << 52) - 1)); + } + } + done: + a |= (uint64_t)is_neg << 63; + dval = uint64_as_float64(a); + done1: + if (pnext) + *pnext = p; + dtoa_free(tmp0); + return dval; + fail: + dval = NAN; + goto done1; +} diff --git a/engine/quickjs-ng/dtoa.h b/engine/quickjs-ng/dtoa.h new file mode 100644 index 000000000..85be04069 --- /dev/null +++ b/engine/quickjs-ng/dtoa.h @@ -0,0 +1,87 @@ +/* + * Tiny float64 printing and parsing library + * + * Copyright (c) 2024 Fabrice Bellard + * + * 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. + */ +#ifndef DTOA_H +#define DTOA_H + +//#define JS_DTOA_DUMP_STATS + +/* maximum number of digits for fixed and frac formats */ +#define JS_DTOA_MAX_DIGITS 101 + +/* radix != 10 is only supported with flags = JS_DTOA_FORMAT_FREE */ +/* use as many digits as necessary */ +#define JS_DTOA_FORMAT_FREE (0 << 0) +/* use n_digits significant digits (1 <= n_digits <= JS_DTOA_MAX_DIGITS) */ +#define JS_DTOA_FORMAT_FIXED (1 << 0) +/* force fractional format: [-]dd.dd with n_digits fractional digits. + 0 <= n_digits <= JS_DTOA_MAX_DIGITS */ +#define JS_DTOA_FORMAT_FRAC (2 << 0) +#define JS_DTOA_FORMAT_MASK (3 << 0) + +/* select exponential notation either in fixed or free format */ +#define JS_DTOA_EXP_AUTO (0 << 2) +#define JS_DTOA_EXP_ENABLED (1 << 2) +#define JS_DTOA_EXP_DISABLED (2 << 2) +#define JS_DTOA_EXP_MASK (3 << 2) + +#define JS_DTOA_MINUS_ZERO (1 << 4) /* show the minus sign for -0 */ + +/* only accepts integers (no dot, no exponent) */ +#define JS_ATOD_INT_ONLY (1 << 0) +/* accept Oo and Ob prefixes in addition to 0x prefix if radix = 0 */ +#define JS_ATOD_ACCEPT_BIN_OCT (1 << 1) +/* accept O prefix as octal if radix == 0 and properly formed (Annex B) */ +#define JS_ATOD_ACCEPT_LEGACY_OCTAL (1 << 2) +/* accept _ between digits as a digit separator */ +#define JS_ATOD_ACCEPT_UNDERSCORES (1 << 3) + +typedef struct { + uint64_t mem[37]; +} JSDTOATempMem; + +typedef struct { + uint64_t mem[27]; +} JSATODTempMem; + +/* return a maximum bound of the string length */ +int js_dtoa_max_len(double d, int radix, int n_digits, int flags); +/* return the string length */ +int js_dtoa(char *buf, double d, int radix, int n_digits, int flags, + JSDTOATempMem *tmp_mem); +double js_atod(const char *str, const char **pnext, int radix, int flags, + JSATODTempMem *tmp_mem); + +#ifdef JS_DTOA_DUMP_STATS +void js_dtoa_dump_stats(void); +#endif + +/* additional exported functions */ +size_t u32toa(char *buf, uint32_t n); +size_t i32toa(char *buf, int32_t n); +size_t u64toa(char *buf, uint64_t n); +size_t i64toa(char *buf, int64_t n); +size_t u64toa_radix(char *buf, uint64_t n, unsigned int radix); +size_t i64toa_radix(char *buf, int64_t n, unsigned int radix); + +#endif /* DTOA_H */ diff --git a/engine/quickjs-ng/libregexp-opcode.h b/engine/quickjs-ng/libregexp-opcode.h new file mode 100644 index 000000000..b3d7b6fdf --- /dev/null +++ b/engine/quickjs-ng/libregexp-opcode.h @@ -0,0 +1,73 @@ +/* + * Regular Expression Engine + * + * Copyright (c) 2017-2018 Fabrice Bellard + * + * 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. + */ + +#ifdef DEF + +DEF(invalid, 1) /* never used */ +DEF(char, 3) +DEF(char_i, 3) +DEF(char32, 5) +DEF(char32_i, 5) +DEF(dot, 1) +DEF(any, 1) /* same as dot but match any character including line terminator */ +DEF(space, 1) +DEF(not_space, 1) /* must come after */ +DEF(line_start, 1) +DEF(line_start_m, 1) +DEF(line_end, 1) +DEF(line_end_m, 1) +DEF(goto, 5) +DEF(split_goto_first, 5) +DEF(split_next_first, 5) +DEF(match, 1) +DEF(lookahead_match, 1) +DEF(negative_lookahead_match, 1) /* must come after */ +DEF(save_start, 2) /* save start position */ +DEF(save_end, 2) /* save end position, must come after saved_start */ +DEF(save_reset, 3) /* reset save positions */ +DEF(loop, 6) /* decrement the top the stack and goto if != 0 */ +DEF(loop_split_goto_first, 10) /* loop and then split */ +DEF(loop_split_next_first, 10) +DEF(loop_check_adv_split_goto_first, 10) /* loop and then check advance and split */ +DEF(loop_check_adv_split_next_first, 10) +DEF(set_i32, 6) /* store the immediate value to a register */ +DEF(word_boundary, 1) +DEF(word_boundary_i, 1) +DEF(not_word_boundary, 1) +DEF(not_word_boundary_i, 1) +DEF(back_reference, 2) /* variable length */ +DEF(back_reference_i, 2) /* must come after */ +DEF(backward_back_reference, 2) /* must come after */ +DEF(backward_back_reference_i, 2) /* must come after */ +DEF(range, 3) /* variable length */ +DEF(range_i, 3) /* variable length */ +DEF(range32, 3) /* variable length */ +DEF(range32_i, 3) /* variable length */ +DEF(lookahead, 5) +DEF(negative_lookahead, 5) /* must come after */ +DEF(set_char_pos, 2) /* store the character position to a register */ +DEF(check_advance, 2) /* check that the register is different from the character position */ +DEF(prev, 1) /* go to the previous char */ + +#endif /* DEF */ diff --git a/engine/quickjs-ng/libregexp.c b/engine/quickjs-ng/libregexp.c new file mode 100644 index 000000000..49c474c46 --- /dev/null +++ b/engine/quickjs-ng/libregexp.c @@ -0,0 +1,3522 @@ +/* + * Regular Expression Engine + * + * Copyright (c) 2017-2018 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include +#include +#include +#include + +#include "cutils.h" +#include "libregexp.h" +#include "libunicode.h" + +/* ASCII identifier tables, used by lre_js_is_ident_first/next in libregexp.h + and by quickjs.c. */ +uint32_t const lre_id_start_table_ascii[4] = { + /* $ A-Z _ a-z */ + 0x00000000, 0x00000010, 0x87FFFFFE, 0x07FFFFFE +}; + +uint32_t const lre_id_continue_table_ascii[4] = { + /* $ 0-9 A-Z _ a-z */ + 0x00000000, 0x03FF0010, 0x87FFFFFE, 0x07FFFFFE +}; + +/* + TODO: + - remove REOP_char_i and REOP_range_i by precomputing the case folding. + - add specific opcodes for simple unicode property tests so that the + generated bytecode is smaller. + - Add a lock step execution mode (=linear time execution guaranteed) + when the regular expression is "simple" i.e. no backreference nor + complicated lookahead. The opcodes are designed for this execution + model. +*/ + +#if defined(TEST) +#define DUMP_REOP +#endif +//#define DUMP_REOP +//#define DUMP_EXEC + +typedef enum { +#define DEF(id, size) REOP_ ## id, +#include "libregexp-opcode.h" +#undef DEF + REOP_COUNT, +} REOPCodeEnum; + +#define CAPTURE_COUNT_MAX 255 +#define REGISTER_COUNT_MAX 255 +#define GROUP_NAME_SCOPE_MAX 255 +/* must be large enough to have a negligible runtime cost and small + enough to call the interrupt callback often. */ +#define INTERRUPT_COUNTER_INIT 10000 + +/* unicode code points */ +#define CP_LS 0x2028 +#define CP_PS 0x2029 + +#define TMP_BUF_SIZE 128 + +typedef struct { + DynBuf byte_code; + const uint8_t *buf_ptr; + const uint8_t *buf_end; + const uint8_t *buf_start; + int re_flags; + bool is_unicode; + bool unicode_sets; /* if set, is_unicode is also set */ + bool ignore_case; + bool multi_line; + bool dotall; + uint8_t group_name_scope; + int capture_count; + int total_capture_count; /* -1 = not computed yet */ + int has_named_captures; /* -1 = don't know, 0 = no, 1 = yes */ + void *opaque; + DynBuf group_names; + union { + char error_msg[TMP_BUF_SIZE]; + char tmp_buf[TMP_BUF_SIZE]; + } u; +} REParseState; + +typedef struct { +#ifdef DUMP_REOP + const char *name; +#endif + uint8_t size; +} REOpCode; + +static const REOpCode reopcode_info[REOP_COUNT] = { +#ifdef DUMP_REOP +#define DEF(id, size) { #id, size }, +#else +#define DEF(id, size) { size }, +#endif +#include "libregexp-opcode.h" +#undef DEF +}; + +#define RE_HEADER_FLAGS 0 +#define RE_HEADER_CAPTURE_COUNT 2 +#define RE_HEADER_REGISTER_COUNT 3 +#define RE_HEADER_BYTECODE_LEN 4 + +#define RE_HEADER_LEN 8 + +static inline int lre_is_digit(int c) { + return c >= '0' && c <= '9'; +} + +/* insert 'len' bytes at position 'pos'. Return < 0 if error. */ +static int dbuf_insert(DynBuf *s, int pos, int len) +{ + if (dbuf_claim(s, len)) + return -1; + memmove(s->buf + pos + len, s->buf + pos, s->size - pos); + s->size += len; + return 0; +} + +typedef struct REString { + struct REString *next; + uint32_t hash; + uint32_t len; + uint32_t buf[]; +} REString; + +typedef struct { + /* the string list is the union of 'char_range' and of the strings + in hash_table[]. The strings in hash_table[] have a length != + 1. */ + CharRange cr; + uint32_t n_strings; + uint32_t hash_size; + int hash_bits; + REString **hash_table; +} REStringList; + +static uint32_t re_string_hash(int len, const uint32_t *buf) +{ + int i; + uint32_t h; + h = 1; + for(i = 0; i < len; i++) + h = h * 263 + buf[i]; + return hash32(h); +} + +static void re_string_list_init(REParseState *s1, REStringList *s) +{ + cr_init(&s->cr, s1->opaque, lre_realloc); + s->n_strings = 0; + s->hash_size = 0; + s->hash_bits = 0; + s->hash_table = NULL; +} + +static void re_string_list_free(REStringList *s) +{ + REString *p, *p_next; + int i; + for(i = 0; i < s->hash_size; i++) { + for(p = s->hash_table[i]; p != NULL; p = p_next) { + p_next = p->next; + lre_realloc(s->cr.mem_opaque, p, 0); + } + } + lre_realloc(s->cr.mem_opaque, s->hash_table, 0); + + cr_free(&s->cr); +} + +#ifdef DUMP_REOP +static void lre_print_char(int c, bool is_range) +{ + if (c == '\'' || c == '\\' || + (is_range && (c == '-' || c == ']'))) { + printf("\\%c", c); + } else if (c >= ' ' && c <= 126) { + printf("%c", c); + } else { + printf("\\u{%04x}", c); + } +} + +static __maybe_unused void re_string_list_dump(const char *str, const REStringList *s) +{ + REString *p; + const CharRange *cr; + int i, j, k; + + printf("%s:\n", str); + printf(" ranges: ["); + cr = &s->cr; + for(i = 0; i < cr->len; i += 2) { + lre_print_char(cr->points[i], true); + if (cr->points[i] != cr->points[i + 1] - 1) { + printf("-"); + lre_print_char(cr->points[i + 1] - 1, true); + } + } + printf("]\n"); + + j = 0; + for(i = 0; i < s->hash_size; i++) { + for(p = s->hash_table[i]; p != NULL; p = p->next) { + printf(" %d/%d: '", j, s->n_strings); + for(k = 0; k < p->len; k++) { + lre_print_char(p->buf[k], false); + } + printf("'\n"); + j++; + } + } +} +#endif /* DUMP_REOP */ + +/* 'buf' is NULL if 'len' is zero: the empty string is a valid member of a + string set, e.g. /[\q{}]/v */ +static int re_string_find2(REStringList *s, int len, const uint32_t *buf, + uint32_t h0, bool add_flag) +{ + uint32_t h = 0; /* avoid warning */ + REString *p; + if (s->n_strings != 0) { + h = h0 >> (32 - s->hash_bits); + for(p = s->hash_table[h]; p != NULL; p = p->next) { + if (p->hash == h0 && p->len == len && + (len == 0 || + !memcmp(p->buf, buf, len * sizeof(buf[0])))) { + return 1; + } + } + } + /* not found */ + if (!add_flag) + return 0; + /* increase the size of the hash table if needed */ + if (unlikely((s->n_strings + 1) > s->hash_size)) { + REString **new_hash_table, *p_next; + int new_hash_bits, i; + uint32_t new_hash_size; + new_hash_bits = max_int(s->hash_bits + 1, 4); + new_hash_size = 1 << new_hash_bits; + new_hash_table = lre_realloc(s->cr.mem_opaque, NULL, + sizeof(new_hash_table[0]) * new_hash_size); + if (!new_hash_table) + return -1; + memset(new_hash_table, 0, sizeof(new_hash_table[0]) * new_hash_size); + for(i = 0; i < s->hash_size; i++) { + for(p = s->hash_table[i]; p != NULL; p = p_next) { + p_next = p->next; + h = p->hash >> (32 - new_hash_bits); + p->next = new_hash_table[h]; + new_hash_table[h] = p; + } + } + lre_realloc(s->cr.mem_opaque, s->hash_table, 0); + s->hash_bits = new_hash_bits; + s->hash_size = new_hash_size; + s->hash_table = new_hash_table; + h = h0 >> (32 - s->hash_bits); + } + + p = lre_realloc(s->cr.mem_opaque, NULL, sizeof(REString) + len * sizeof(buf[0])); + if (!p) + return -1; + p->next = s->hash_table[h]; + s->hash_table[h] = p; + s->n_strings++; + p->hash = h0; + p->len = len; + if (len != 0) + memcpy(p->buf, buf, sizeof(buf[0]) * len); + return 1; +} + +static int re_string_find(REStringList *s, int len, const uint32_t *buf, + bool add_flag) +{ + uint32_t h0; + h0 = re_string_hash(len, buf); + return re_string_find2(s, len, buf, h0, add_flag); +} + +/* return -1 if memory error, 0 if OK */ +static int re_string_add(REStringList *s, int len, const uint32_t *buf) +{ + if (len == 1) { + return cr_union_interval(&s->cr, buf[0], buf[0]); + } + if (re_string_find(s, len, buf, true) < 0) + return -1; + return 0; +} + +/* a = a op b */ +static int re_string_list_op(REStringList *a, REStringList *b, int op) +{ + int i, ret; + REString *p, **pp; + + if (cr_op1(&a->cr, b->cr.points, b->cr.len, op)) + return -1; + + switch(op) { + case CR_OP_UNION: + if (b->n_strings != 0) { + for(i = 0; i < b->hash_size; i++) { + for(p = b->hash_table[i]; p != NULL; p = p->next) { + if (re_string_find2(a, p->len, p->buf, p->hash, true) < 0) + return -1; + } + } + } + break; + case CR_OP_INTER: + case CR_OP_SUB: + for(i = 0; i < a->hash_size; i++) { + pp = &a->hash_table[i]; + for(;;) { + p = *pp; + if (p == NULL) + break; + ret = re_string_find2(b, p->len, p->buf, p->hash, false); + if (op == CR_OP_SUB) + ret = !ret; + if (!ret) { + /* remove it */ + *pp = p->next; + a->n_strings--; + lre_realloc(a->cr.mem_opaque, p, 0); + } else { + /* keep it */ + pp = &p->next; + } + } + } + break; + default: + abort(); + } + return 0; +} + +static int re_string_list_canonicalize(REParseState *s1, + REStringList *s, bool is_unicode) +{ + if (cr_regexp_canonicalize(&s->cr, is_unicode)) + return -1; + if (s->n_strings != 0) { + REStringList a_s, *a = &a_s; + int i, j; + REString *p; + + /* XXX: simplify */ + re_string_list_init(s1, a); + + a->n_strings = s->n_strings; + a->hash_size = s->hash_size; + a->hash_bits = s->hash_bits; + a->hash_table = s->hash_table; + + s->n_strings = 0; + s->hash_size = 0; + s->hash_bits = 0; + s->hash_table = NULL; + + for(i = 0; i < a->hash_size; i++) { + for(p = a->hash_table[i]; p != NULL; p = p->next) { + for(j = 0; j < p->len; j++) { + p->buf[j] = lre_canonicalize(p->buf[j], is_unicode); + } + if (re_string_add(s, p->len, p->buf)) { + re_string_list_free(a); + return -1; + } + } + } + re_string_list_free(a); + } + return 0; +} + +static const uint16_t char_range_d[] = { + 1, + 0x0030, 0x0039 + 1, +}; + +/* code point ranges for Zs,Zl or Zp property */ +static const uint16_t char_range_s[] = { + 10, + 0x0009, 0x000D + 1, + 0x0020, 0x0020 + 1, + 0x00A0, 0x00A0 + 1, + 0x1680, 0x1680 + 1, + 0x2000, 0x200A + 1, + /* 2028;LINE SEPARATOR;Zl;0;WS;;;;;N;;;;; */ + /* 2029;PARAGRAPH SEPARATOR;Zp;0;B;;;;;N;;;;; */ + 0x2028, 0x2029 + 1, + 0x202F, 0x202F + 1, + 0x205F, 0x205F + 1, + 0x3000, 0x3000 + 1, + /* FEFF;ZERO WIDTH NO-BREAK SPACE;Cf;0;BN;;;;;N;BYTE ORDER MARK;;;; */ + 0xFEFF, 0xFEFF + 1, +}; + +static const uint16_t char_range_w[] = { + 4, + 0x0030, 0x0039 + 1, + 0x0041, 0x005A + 1, + 0x005F, 0x005F + 1, + 0x0061, 0x007A + 1, +}; + +#define CLASS_RANGE_BASE 0x40000000 + +typedef enum { + CHAR_RANGE_d, + CHAR_RANGE_D, + CHAR_RANGE_s, + CHAR_RANGE_S, + CHAR_RANGE_w, + CHAR_RANGE_W, +} CharRangeEnum; + +static const uint16_t * const char_range_table[] = { + char_range_d, + char_range_s, + char_range_w, +}; + +static int cr_init_char_range(REParseState *s, REStringList *cr, uint32_t c) +{ + bool invert; + const uint16_t *c_pt; + int len, i; + + invert = c & 1; + c_pt = char_range_table[c >> 1]; + len = *c_pt++; + re_string_list_init(s, cr); + for(i = 0; i < len * 2; i++) { + if (cr_add_point(&cr->cr, c_pt[i])) + goto fail; + } + if (invert) { + if (cr_invert(&cr->cr)) + goto fail; + } + return 0; + fail: + re_string_list_free(cr); + return -1; +} + +#ifdef DUMP_REOP +static __maybe_unused void lre_dump_bytecode(const uint8_t *buf, + int buf_len) +{ + int pos, len, opcode, bc_len, re_flags, i; + uint32_t val, val2; + + assert(buf_len >= RE_HEADER_LEN); + + re_flags = lre_get_flags(buf); + bc_len = get_u32(buf + RE_HEADER_BYTECODE_LEN); + assert(bc_len + RE_HEADER_LEN <= buf_len); + printf("flags: 0x%x capture_count=%d reg_count=%d\n", + re_flags, buf[RE_HEADER_CAPTURE_COUNT], buf[RE_HEADER_REGISTER_COUNT]); + if (re_flags & LRE_FLAG_NAMED_GROUPS) { + const char *p; + p = (char *)buf + RE_HEADER_LEN + bc_len; + printf("named groups: "); + for(i = 1; i < buf[RE_HEADER_CAPTURE_COUNT]; i++) { + if (i != 1) + printf(","); + printf("<%s>", p); + p += strlen(p) + LRE_GROUP_NAME_TRAILER_LEN; + } + printf("\n"); + assert(p == (char *)(buf + buf_len)); + } + printf("bytecode_len=%d\n", bc_len); + + buf += RE_HEADER_LEN; + pos = 0; + while (pos < bc_len) { + printf("%5u: ", pos); + opcode = buf[pos]; + len = reopcode_info[opcode].size; + if (opcode >= REOP_COUNT) { + printf(" invalid opcode=0x%02x\n", opcode); + break; + } + if ((pos + len) > bc_len) { + printf(" buffer overflow (opcode=0x%02x)\n", opcode); + break; + } + printf("%s", reopcode_info[opcode].name); + switch(opcode) { + case REOP_char: + case REOP_char_i: + val = get_u16(buf + pos + 1); + if (val >= ' ' && val <= 126) + printf(" '%c'", val); + else + printf(" 0x%04x", val); + break; + case REOP_char32: + case REOP_char32_i: + val = get_u32(buf + pos + 1); + if (val >= ' ' && val <= 126) + printf(" '%c'", val); + else + printf(" 0x%08x", val); + break; + case REOP_goto: + case REOP_split_goto_first: + case REOP_split_next_first: + case REOP_lookahead: + case REOP_negative_lookahead: + val = get_u32(buf + pos + 1); + val += (pos + 5); + printf(" %u", val); + break; + case REOP_loop: + val2 = buf[pos + 1]; + val = get_u32(buf + pos + 2); + val += (pos + 6); + printf(" r%u, %u", val2, val); + break; + case REOP_loop_split_goto_first: + case REOP_loop_split_next_first: + case REOP_loop_check_adv_split_goto_first: + case REOP_loop_check_adv_split_next_first: + { + uint32_t limit; + val2 = buf[pos + 1]; + limit = get_u32(buf + pos + 2); + val = get_u32(buf + pos + 6); + val += (pos + 10); + printf(" r%u, %u, %u", val2, limit, val); + } + break; + case REOP_save_start: + case REOP_save_end: + printf(" %u", buf[pos + 1]); + break; + case REOP_back_reference: + case REOP_back_reference_i: + case REOP_backward_back_reference: + case REOP_backward_back_reference_i: + { + int n, i; + n = buf[pos + 1]; + len += n; + for(i = 0; i < n; i++) { + if (i != 0) + printf(","); + printf(" %u", buf[pos + 2 + i]); + } + } + break; + case REOP_save_reset: + printf(" %u %u", buf[pos + 1], buf[pos + 2]); + break; + case REOP_set_i32: + val = buf[pos + 1]; + val2 = get_u32(buf + pos + 2); + printf(" r%u, %d", val, val2); + break; + case REOP_set_char_pos: + case REOP_check_advance: + val = buf[pos + 1]; + printf(" r%u", val); + break; + case REOP_range: + case REOP_range_i: + { + int n, i; + n = get_u16(buf + pos + 1); + len += n * 4; + for(i = 0; i < n * 2; i++) { + val = get_u16(buf + pos + 3 + i * 2); + printf(" 0x%04x", val); + } + } + break; + case REOP_range32: + case REOP_range32_i: + { + int n, i; + n = get_u16(buf + pos + 1); + len += n * 8; + for(i = 0; i < n * 2; i++) { + val = get_u32(buf + pos + 3 + i * 4); + printf(" 0x%08x", val); + } + } + break; + default: + break; + } + printf("\n"); + pos += len; + } +} +#endif + +static void re_emit_op(REParseState *s, int op) +{ + dbuf_putc(&s->byte_code, op); +} + +/* return the offset of the u32 value */ +static int re_emit_op_u32(REParseState *s, int op, uint32_t val) +{ + int pos; + dbuf_putc(&s->byte_code, op); + pos = s->byte_code.size; + dbuf_put_u32(&s->byte_code, val); + return pos; +} + +static int re_emit_goto(REParseState *s, int op, uint32_t val) +{ + int pos; + dbuf_putc(&s->byte_code, op); + pos = s->byte_code.size; + dbuf_put_u32(&s->byte_code, val - (pos + 4)); + return pos; +} + +static int re_emit_goto_u8(REParseState *s, int op, uint32_t arg, uint32_t val) +{ + int pos; + dbuf_putc(&s->byte_code, op); + dbuf_putc(&s->byte_code, arg); + pos = s->byte_code.size; + dbuf_put_u32(&s->byte_code, val - (pos + 4)); + return pos; +} + +static int re_emit_goto_u8_u32(REParseState *s, int op, uint32_t arg0, uint32_t arg1, uint32_t val) +{ + int pos; + dbuf_putc(&s->byte_code, op); + dbuf_putc(&s->byte_code, arg0); + dbuf_put_u32(&s->byte_code, arg1); + pos = s->byte_code.size; + dbuf_put_u32(&s->byte_code, val - (pos + 4)); + return pos; +} + +static void re_emit_op_u8(REParseState *s, int op, uint32_t val) +{ + dbuf_putc(&s->byte_code, op); + dbuf_putc(&s->byte_code, val); +} + +static void re_emit_op_u16(REParseState *s, int op, uint32_t val) +{ + dbuf_putc(&s->byte_code, op); + dbuf_put_u16(&s->byte_code, val); +} + +static int JS_PRINTF_FORMAT_ATTR(2, 3) re_parse_error(REParseState *s, JS_PRINTF_FORMAT const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vsnprintf(s->u.error_msg, sizeof(s->u.error_msg), fmt, ap); + va_end(ap); + return -1; +} + +static int re_parse_out_of_memory(REParseState *s) +{ + return re_parse_error(s, "out of memory"); +} + +/* If allow_overflow is false, return -1 in case of + overflow. Otherwise return INT32_MAX. */ +static int parse_digits(const uint8_t **pp, bool allow_overflow) +{ + const uint8_t *p; + uint64_t v; + int c; + + p = *pp; + v = 0; + for(;;) { + c = *p; + if (c < '0' || c > '9') + break; + v = v * 10 + c - '0'; + if (v >= INT32_MAX) { + if (allow_overflow) + v = INT32_MAX; + else + return -1; + } + p++; + } + *pp = p; + return v; +} + +static int re_parse_expect(REParseState *s, const uint8_t **pp, int c) +{ + const uint8_t *p; + p = *pp; + if (*p != c) + return re_parse_error(s, "expecting '%c'", c); + p++; + *pp = p; + return 0; +} + +/* Parse an escape sequence, *pp points after the '\': + allow_utf16 value: + 0 : no UTF-16 escapes allowed + 1 : UTF-16 escapes allowed + 2 : UTF-16 escapes allowed and escapes of surrogate pairs are + converted to a unicode character (unicode regexp case). + + Return the unicode char and update *pp if recognized, + return -1 if malformed escape, + return -2 otherwise. */ +int lre_parse_escape(const uint8_t **pp, int allow_utf16) +{ + const uint8_t *p; + uint32_t c; + + p = *pp; + c = *p++; + switch(c) { + case 'b': + c = '\b'; + break; + case 'f': + c = '\f'; + break; + case 'n': + c = '\n'; + break; + case 'r': + c = '\r'; + break; + case 't': + c = '\t'; + break; + case 'v': + c = '\v'; + break; + case 'x': + { + int h0, h1; + + h0 = from_hex(*p++); + if (h0 < 0) + return -1; + h1 = from_hex(*p++); + if (h1 < 0) + return -1; + c = (h0 << 4) | h1; + } + break; + case 'u': + { + int h, i; + uint32_t c1; + + if (*p == '{' && allow_utf16) { + p++; + c = 0; + for(;;) { + h = from_hex(*p++); + if (h < 0) + return -1; + c = (c << 4) | h; + if (c > 0x10FFFF) + return -1; + if (*p == '}') + break; + } + p++; + } else { + c = 0; + for(i = 0; i < 4; i++) { + h = from_hex(*p++); + if (h < 0) { + return -1; + } + c = (c << 4) | h; + } + if (is_hi_surrogate(c) && + allow_utf16 == 2 && p[0] == '\\' && p[1] == 'u') { + /* convert an escaped surrogate pair into a + unicode char */ + c1 = 0; + for(i = 0; i < 4; i++) { + h = from_hex(p[2 + i]); + if (h < 0) + break; + c1 = (c1 << 4) | h; + } + if (i == 4 && is_lo_surrogate(c1)) { + p += 6; + c = from_surrogate(c, c1); + } + } + } + } + break; + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + c -= '0'; + if (allow_utf16 == 2) { + /* only accept \0 not followed by digit */ + if (c != 0 || lre_is_digit(*p)) + return -1; + } else { + /* parse a legacy octal sequence */ + uint32_t v; + v = *p - '0'; + if (v > 7) + break; + c = (c << 3) | v; + p++; + if (c >= 32) + break; + v = *p - '0'; + if (v > 7) + break; + c = (c << 3) | v; + p++; + } + break; + default: + return -2; + } + *pp = p; + return c; +} + +/* XXX: we use the same chars for name and value */ +static bool is_unicode_char(int c) +{ + return ((c >= '0' && c <= '9') || + (c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z') || + (c == '_')); +} + +/* XXX: memory error test */ +static void seq_prop_cb(void *opaque, const uint32_t *seq, int seq_len) +{ + REStringList *sl = opaque; + re_string_add(sl, seq_len, seq); +} + +static int parse_unicode_property(REParseState *s, REStringList *cr, + const uint8_t **pp, bool is_inv, + bool allow_sequence_prop) +{ + const uint8_t *p; + char name[64], value[64]; + char *q; + bool script_ext; + int ret; + + p = *pp; + if (*p != '{') + return re_parse_error(s, "expecting '{' after \\p"); + p++; + q = name; + while (is_unicode_char(*p)) { + if ((q - name) >= sizeof(name) - 1) + goto unknown_property_name; + *q++ = *p++; + } + *q = '\0'; + q = value; + if (*p == '=') { + p++; + while (is_unicode_char(*p)) { + if ((q - value) >= sizeof(value) - 1) + return re_parse_error(s, "unknown unicode property value"); + *q++ = *p++; + } + } + *q = '\0'; + if (*p != '}') + return re_parse_error(s, "expecting '}'"); + p++; + // printf("name=%s value=%s\n", name, value); + + if (!strcmp(name, "Script") || !strcmp(name, "sc")) { + script_ext = false; + goto do_script; + } else if (!strcmp(name, "Script_Extensions") || !strcmp(name, "scx")) { + script_ext = true; + do_script: + re_string_list_init(s, cr); + ret = unicode_script(&cr->cr, value, script_ext); + if (ret) { + re_string_list_free(cr); + if (ret == -2) + return re_parse_error(s, "unknown unicode script"); + else + goto out_of_memory; + } + } else if (!strcmp(name, "General_Category") || !strcmp(name, "gc")) { + re_string_list_init(s, cr); + ret = unicode_general_category(&cr->cr, value); + if (ret) { + re_string_list_free(cr); + if (ret == -2) + return re_parse_error(s, "unknown unicode general category"); + else + goto out_of_memory; + } + } else if (value[0] == '\0') { + re_string_list_init(s, cr); + ret = unicode_general_category(&cr->cr, name); + if (ret == -1) { + re_string_list_free(cr); + goto out_of_memory; + } + if (ret < 0) { + ret = unicode_prop(&cr->cr, name); + if (ret == -1) { + re_string_list_free(cr); + goto out_of_memory; + } + } + if (ret < 0 && !is_inv && allow_sequence_prop) { + CharRange cr_tmp; + cr_init(&cr_tmp, s->opaque, lre_realloc); + ret = unicode_sequence_prop(name, seq_prop_cb, cr, &cr_tmp); + cr_free(&cr_tmp); + if (ret == -1) { + re_string_list_free(cr); + goto out_of_memory; + } + } + if (ret < 0) + goto unknown_property_name; + } else { + unknown_property_name: + return re_parse_error(s, "unknown unicode property name"); + } + + /* the ordering of case folding and inversion differs with + unicode_sets. 'unicode_sets' ordering is more consistent */ + /* XXX: the spec seems incorrect, we do it as the other engines + seem to do it. */ + if (s->ignore_case && s->unicode_sets) { + if (re_string_list_canonicalize(s, cr, s->is_unicode)) { + re_string_list_free(cr); + goto out_of_memory; + } + } + if (is_inv) { + if (cr_invert(&cr->cr)) { + re_string_list_free(cr); + goto out_of_memory; + } + } + if (s->ignore_case && !s->unicode_sets) { + if (re_string_list_canonicalize(s, cr, s->is_unicode)) { + re_string_list_free(cr); + goto out_of_memory; + } + } + *pp = p; + return 0; + out_of_memory: + return re_parse_out_of_memory(s); +} + +static int get_class_atom(REParseState *s, REStringList *cr, + const uint8_t **pp, bool inclass); + +static int parse_class_string_disjunction(REParseState *s, REStringList *cr, + const uint8_t **pp) +{ + const uint8_t *p; + DynBuf str; + int c; + + p = *pp; + if (*p != '{') + return re_parse_error(s, "expecting '{' after \\q"); + + dbuf_init2(&str, s->opaque, lre_realloc); + re_string_list_init(s, cr); + + p++; + for(;;) { + str.size = 0; + while (*p != '}' && *p != '|') { + c = get_class_atom(s, NULL, &p, true); + if (c < 0) + goto fail; + if (dbuf_put_u32(&str, c)) { + re_parse_out_of_memory(s); + goto fail; + } + } + if (re_string_add(cr, str.size / 4, (uint32_t *)str.buf)) { + re_parse_out_of_memory(s); + goto fail; + } + if (*p == '}') + break; + p++; + } + if (s->ignore_case) { + if (re_string_list_canonicalize(s, cr, true)) + goto fail; + } + p++; /* skip the '}' */ + dbuf_free(&str); + *pp = p; + return 0; + fail: + dbuf_free(&str); + re_string_list_free(cr); + return -1; +} + +/* return -1 if error otherwise the character or a class range + (CLASS_RANGE_BASE) if cr != NULL. In case of class range, 'cr' is + initialized. Otherwise, it is ignored. */ +static int get_class_atom(REParseState *s, REStringList *cr, + const uint8_t **pp, bool inclass) +{ + const uint8_t *p; + uint32_t c; + int ret; + + p = *pp; + + c = *p; + switch(c) { + case '\\': + p++; + if (p >= s->buf_end) + goto unexpected_end; + c = *p++; + switch(c) { + case 'd': + c = CHAR_RANGE_d; + goto class_range; + case 'D': + c = CHAR_RANGE_D; + goto class_range; + case 's': + c = CHAR_RANGE_s; + goto class_range; + case 'S': + c = CHAR_RANGE_S; + goto class_range; + case 'w': + c = CHAR_RANGE_w; + goto class_range; + case 'W': + c = CHAR_RANGE_W; + class_range: + if (!cr) + goto default_escape; + if (cr_init_char_range(s, cr, c)) + return -1; + c += CLASS_RANGE_BASE; + break; + case 'c': + c = *p; + if ((c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + (((c >= '0' && c <= '9') || c == '_') && + inclass && !s->is_unicode)) { /* Annex B.1.4 */ + c &= 0x1f; + p++; + } else if (s->is_unicode) { + goto invalid_escape; + } else { + /* otherwise return '\' and 'c' */ + p--; + c = '\\'; + } + break; + case '-': + if (!inclass && s->is_unicode) + goto invalid_escape; + break; + case '&': + case '!': + case '#': + case '%': + case ',': + case ':': + case ';': + case '<': + case '=': + case '>': + case '@': + case '`': + case '~': + if (s->is_unicode && (!inclass || !s->unicode_sets)) + /* Only illegal if in unicode mode and not in a class */ + goto invalid_escape; + break; + case '^': + case '$': + case '\\': + case '.': + case '*': + case '+': + case '?': + case '(': + case ')': + case '[': + case ']': + case '{': + case '}': + case '|': + case '/': + /* always valid to escape these characters */ + break; + case 'p': + case 'P': + if (s->is_unicode && cr) { + if (parse_unicode_property(s, cr, &p, (c == 'P'), s->unicode_sets)) + return -1; + c = CLASS_RANGE_BASE; + break; + } + goto default_escape; + case 'q': + if (s->unicode_sets && cr && inclass) { + if (parse_class_string_disjunction(s, cr, &p)) + return -1; + c = CLASS_RANGE_BASE; + break; + } + goto default_escape; + default: + default_escape: + p--; + ret = lre_parse_escape(&p, s->is_unicode * 2); + if (ret >= 0) { + c = ret; + } else { + if (s->is_unicode) { + invalid_escape: + return re_parse_error(s, "invalid escape sequence in regular expression"); + } else { + /* just ignore the '\' */ + goto normal_char; + } + } + break; + } + break; + case '\0': + if (p >= s->buf_end) { + unexpected_end: + return re_parse_error(s, "unexpected end"); + } + /* fall thru */ + goto normal_char; + + case '&': + case '!': + case '#': + case '$': + case '%': + case '*': + case '+': + case ',': + case '.': + case ':': + case ';': + case '<': + case '=': + case '>': + case '?': + case '@': + case '^': + case '`': + case '~': + if (s->unicode_sets && p[1] == c) { + /* forbidden double characters */ + return re_parse_error(s, "invalid class set operation in regular expression"); + } + goto normal_char; + + case '(': + case ')': + case '[': + case ']': + case '{': + case '}': + case '/': + case '-': + case '|': + if (s->unicode_sets) { + /* invalid characters in unicode sets */ + return re_parse_error(s, "invalid character in class in regular expression"); + } + goto normal_char; + + default: + normal_char: + /* normal char */ + if (c >= 128) { + c = utf8_decode_len(p, UTF8_CHAR_LEN_MAX, &p); + if ((unsigned)c > 0xffff && !s->is_unicode) { + /* XXX: should handle non BMP-1 code points */ + return re_parse_error(s, "malformed unicode char"); + } + } else { + p++; + } + break; + } + *pp = p; + return c; +} + +static int re_emit_range(REParseState *s, const CharRange *cr) +{ + int len, i; + uint32_t high; + + len = (unsigned)cr->len / 2; + if (len >= 65535) + return re_parse_error(s, "too many ranges"); + if (len == 0) { + re_emit_op_u32(s, REOP_char32, -1); + } else { + high = cr->points[cr->len - 1]; + if (high == UINT32_MAX) + high = cr->points[cr->len - 2]; + if (high <= 0xffff) { + /* can use 16 bit ranges with the conversion that 0xffff = + infinity */ + re_emit_op_u16(s, s->ignore_case ? REOP_range_i : REOP_range, len); + for(i = 0; i < cr->len; i += 2) { + dbuf_put_u16(&s->byte_code, cr->points[i]); + high = cr->points[i + 1] - 1; + if (high == UINT32_MAX - 1) + high = 0xffff; + dbuf_put_u16(&s->byte_code, high); + } + } else { + re_emit_op_u16(s, s->ignore_case ? REOP_range32_i : REOP_range32, len); + for(i = 0; i < cr->len; i += 2) { + dbuf_put_u32(&s->byte_code, cr->points[i]); + dbuf_put_u32(&s->byte_code, cr->points[i + 1] - 1); + } + } + } + return 0; +} + +static int re_string_cmp_len(const void *a, const void *b, void *arg) +{ + REString *p1 = *(REString **)a; + REString *p2 = *(REString **)b; + return (p1->len < p2->len) - (p1->len > p2->len); +} + +static void re_emit_char(REParseState *s, int c) +{ + if (c <= 0xffff) + re_emit_op_u16(s, s->ignore_case ? REOP_char_i : REOP_char, c); + else + re_emit_op_u32(s, s->ignore_case ? REOP_char32_i : REOP_char32, c); +} + +static int re_emit_string_list(REParseState *s, const REStringList *sl) +{ + REString **tab, *p; + int i, j, split_pos, last_match_pos, n; + bool has_empty_string, is_last; + + // re_string_list_dump("sl", sl); + if (sl->n_strings == 0) { + /* simple case: only characters */ + if (re_emit_range(s, &sl->cr)) + return -1; + } else { + /* at least one string list is present : match the longest ones first */ + /* XXX: add a new op_switch opcode to compile as a trie */ + tab = lre_realloc(s->opaque, NULL, sizeof(tab[0]) * sl->n_strings); + if (!tab) { + re_parse_out_of_memory(s); + return -1; + } + has_empty_string = false; + n = 0; + for(i = 0; i < sl->hash_size; i++) { + for(p = sl->hash_table[i]; p != NULL; p = p->next) { + if (p->len == 0) { + has_empty_string = true; + } else { + tab[n++] = p; + } + } + } + assert(n <= sl->n_strings); + + rqsort(tab, n, sizeof(tab[0]), re_string_cmp_len, NULL); + + last_match_pos = -1; + for(i = 0; i < n; i++) { + p = tab[i]; + is_last = !has_empty_string && sl->cr.len == 0 && i == (n - 1); + if (!is_last) + split_pos = re_emit_op_u32(s, REOP_split_next_first, 0); + else + split_pos = 0; + for(j = 0; j < p->len; j++) { + re_emit_char(s, p->buf[j]); + } + if (!is_last) { + last_match_pos = re_emit_op_u32(s, REOP_goto, last_match_pos); + /* the positions to patch are only valid if all the byte code + could be emitted */ + if (dbuf_error(&s->byte_code)) + goto out_of_memory; + put_u32(s->byte_code.buf + split_pos, s->byte_code.size - (split_pos + 4)); + } + } + + if (sl->cr.len != 0) { + /* char range */ + is_last = !has_empty_string; + if (!is_last) + split_pos = re_emit_op_u32(s, REOP_split_next_first, 0); + else + split_pos = 0; /* not used */ + if (re_emit_range(s, &sl->cr)) + goto fail; + if (!is_last) { + if (dbuf_error(&s->byte_code)) + goto out_of_memory; + put_u32(s->byte_code.buf + split_pos, s->byte_code.size - (split_pos + 4)); + } + } + + /* patch the 'goto match' */ + if (dbuf_error(&s->byte_code)) + goto out_of_memory; + while (last_match_pos != -1) { + int next_pos = get_u32(s->byte_code.buf + last_match_pos); + put_u32(s->byte_code.buf + last_match_pos, s->byte_code.size - (last_match_pos + 4)); + last_match_pos = next_pos; + } + + lre_realloc(s->opaque, tab, 0); + } + return 0; + out_of_memory: + re_parse_out_of_memory(s); + fail: + lre_realloc(s->opaque, tab, 0); + return -1; +} + +static int re_parse_nested_class(REParseState *s, REStringList *cr, const uint8_t **pp); + +static int re_parse_class_set_operand(REParseState *s, REStringList *cr, const uint8_t **pp) +{ + int c1; + const uint8_t *p = *pp; + + if (*p == '[') { + if (re_parse_nested_class(s, cr, pp)) + return -1; + } else { + c1 = get_class_atom(s, cr, pp, true); + if (c1 < 0) + return -1; + if (c1 < CLASS_RANGE_BASE) { + /* create a range with a single character */ + re_string_list_init(s, cr); + if (s->ignore_case) + c1 = lre_canonicalize(c1, s->is_unicode); + if (cr_union_interval(&cr->cr, c1, c1)) { + re_string_list_free(cr); + return -1; + } + } + } + return 0; +} + +static int re_parse_nested_class(REParseState *s, REStringList *cr, const uint8_t **pp) +{ + const uint8_t *p; + uint32_t c1, c2; + int ret; + REStringList cr1_s, *cr1 = &cr1_s; + bool invert, is_first; + + if (lre_check_stack_overflow(s->opaque, 0)) + return re_parse_error(s, "stack overflow"); + + re_string_list_init(s, cr); + p = *pp; + p++; /* skip '[' */ + + invert = false; + if (*p == '^') { + p++; + invert = true; + } + + /* handle unions */ + is_first = true; + for(;;) { + if (*p == ']') + break; + if (*p == '[' && s->unicode_sets) { + if (re_parse_nested_class(s, cr1, &p)) + goto fail; + goto class_union; + } else { + c1 = get_class_atom(s, cr1, &p, true); + if ((int)c1 < 0) + goto fail; + if (*p == '-' && p[1] != ']') { + const uint8_t *p0 = p + 1; + if (p[1] == '-' && s->unicode_sets && is_first) + goto class_atom; /* first character class followed by '--' */ + if (c1 >= CLASS_RANGE_BASE) { + if (s->is_unicode) { + re_string_list_free(cr1); + goto invalid_class_range; + } + /* Annex B: match '-' character */ + goto class_atom; + } + c2 = get_class_atom(s, cr1, &p0, true); + if ((int)c2 < 0) + goto fail; + if (c2 >= CLASS_RANGE_BASE) { + re_string_list_free(cr1); + if (s->is_unicode) { + goto invalid_class_range; + } + /* Annex B: match '-' character */ + goto class_atom; + } + p = p0; + if (c2 < c1) { + invalid_class_range: + re_parse_error(s, "invalid class range"); + goto fail; + } + if (s->ignore_case) { + CharRange cr2_s, *cr2 = &cr2_s; + cr_init(cr2, s->opaque, lre_realloc); + if (cr_add_interval(cr2, c1, c2 + 1) || + cr_regexp_canonicalize(cr2, s->is_unicode) || + cr_op1(&cr->cr, cr2->points, cr2->len, CR_OP_UNION)) { + cr_free(cr2); + goto memory_error; + } + cr_free(cr2); + } else { + if (cr_union_interval(&cr->cr, c1, c2)) + goto memory_error; + } + is_first = false; /* union operation */ + } else { + class_atom: + if (c1 >= CLASS_RANGE_BASE) { + class_union: + ret = re_string_list_op(cr, cr1, CR_OP_UNION); + re_string_list_free(cr1); + if (ret) + goto memory_error; + } else { + if (s->ignore_case) + c1 = lre_canonicalize(c1, s->is_unicode); + if (cr_union_interval(&cr->cr, c1, c1)) + goto memory_error; + } + } + } + if (s->unicode_sets && is_first) { + if (*p == '&' && p[1] == '&' && p[2] != '&') { + /* handle '&&' */ + for(;;) { + if (*p == ']') { + break; + } else if (*p == '&' && p[1] == '&' && p[2] != '&') { + p += 2; + } else { + goto invalid_operation; + } + if (re_parse_class_set_operand(s, cr1, &p)) + goto fail; + ret = re_string_list_op(cr, cr1, CR_OP_INTER); + re_string_list_free(cr1); + if (ret) + goto memory_error; + } + } else if (*p == '-' && p[1] == '-') { + /* handle '--' */ + for(;;) { + if (*p == ']') { + break; + } else if (*p == '-' && p[1] == '-') { + p += 2; + } else { + invalid_operation: + re_parse_error(s, "invalid operation in regular expression"); + goto fail; + } + if (re_parse_class_set_operand(s, cr1, &p)) + goto fail; + ret = re_string_list_op(cr, cr1, CR_OP_SUB); + re_string_list_free(cr1); + if (ret) + goto memory_error; + } + } + } + is_first = false; + } + + p++; /* skip ']' */ + *pp = p; + if (invert) { + /* XXX: add may_contain_string syntax check to be fully + compliant. The test here accepts more input than the + spec. */ + if (cr->n_strings != 0) { + re_parse_error(s, "negated character class with strings in regular expression debugger eval code"); + goto fail; + } + if (cr_invert(&cr->cr)) + goto memory_error; + } + return 0; + memory_error: + re_parse_out_of_memory(s); + fail: + re_string_list_free(cr); + return -1; +} + +static int re_parse_char_class(REParseState *s, const uint8_t **pp) +{ + REStringList cr_s, *cr = &cr_s; + + if (re_parse_nested_class(s, cr, pp)) + return -1; + if (re_emit_string_list(s, cr)) + goto fail; + re_string_list_free(cr); + return 0; + fail: + re_string_list_free(cr); + return -1; +} + +/* need_check_adv: false if the opcodes always advance the char pointer + need_capture_init: true if all the captures in the atom are not set +*/ +static bool re_need_check_adv_and_capture_init(bool *pneed_capture_init, + const uint8_t *bc_buf, int bc_buf_len) +{ + int pos, opcode, len; + uint32_t val; + bool need_check_adv, need_capture_init; + + need_check_adv = true; + need_capture_init = false; + pos = 0; + while (pos < bc_buf_len) { + opcode = bc_buf[pos]; + len = reopcode_info[opcode].size; + switch(opcode) { + case REOP_range: + case REOP_range_i: + val = get_u16(bc_buf + pos + 1); + len += val * 4; + need_check_adv = false; + break; + case REOP_range32: + case REOP_range32_i: + val = get_u16(bc_buf + pos + 1); + len += val * 8; + need_check_adv = false; + break; + case REOP_char: + case REOP_char_i: + case REOP_char32: + case REOP_char32_i: + case REOP_dot: + case REOP_any: + case REOP_space: + case REOP_not_space: + need_check_adv = false; + break; + case REOP_line_start: + case REOP_line_start_m: + case REOP_line_end: + case REOP_line_end_m: + case REOP_set_i32: + case REOP_set_char_pos: + case REOP_word_boundary: + case REOP_word_boundary_i: + case REOP_not_word_boundary: + case REOP_not_word_boundary_i: + case REOP_prev: + /* no effect */ + break; + case REOP_save_start: + case REOP_save_end: + case REOP_save_reset: + break; + case REOP_back_reference: + case REOP_back_reference_i: + case REOP_backward_back_reference: + case REOP_backward_back_reference_i: + val = bc_buf[pos + 1]; + len += val; + need_capture_init = true; + break; + default: + /* safe behavior: we cannot predict the outcome */ + need_capture_init = true; + goto done; + } + pos += len; + } + done: + *pneed_capture_init = need_capture_init; + return need_check_adv; +} + +/* '*pp' is the first char after '<' */ +static int re_parse_group_name(char *buf, int buf_size, const uint8_t **pp) +{ + const uint8_t *p, *p1; + uint32_t c, d; + char *q; + + p = *pp; + q = buf; + for(;;) { + c = *p; + if (c == '\\') { + p++; + if (*p != 'u') + return -1; + c = lre_parse_escape(&p, 2); // accept surrogate pairs + } else if (c == '>') { + break; + } else if (c >= 128) { + c = utf8_decode_len(p, UTF8_CHAR_LEN_MAX, &p); + if (is_hi_surrogate(c)) { + d = utf8_decode_len(p, UTF8_CHAR_LEN_MAX, &p1); + if (is_lo_surrogate(d)) { + c = from_surrogate(c, d); + p = p1; + } + } + } else { + p++; + } + if (c > 0x10FFFF) + return -1; + if (q == buf) { + if (!lre_js_is_ident_first(c)) + return -1; + } else { + if (!lre_js_is_ident_next(c)) + return -1; + } + if ((q - buf + UTF8_CHAR_LEN_MAX + 1) > buf_size) + return -1; + if (c < 128) { + *q++ = c; + } else { + q += utf8_encode((uint8_t*)q, c); + } + } + if (q == buf) + return -1; + *q = '\0'; + p++; + *pp = p; + return 0; +} + +/* if capture_name = NULL: return the number of captures + 1. + Otherwise, return the number of matching capture groups */ +static int re_parse_captures(REParseState *s, int *phas_named_captures, + const char *capture_name, bool emit_group_index) +{ + const uint8_t *p; + int capture_index, n; + char name[TMP_BUF_SIZE]; + + capture_index = 1; + n = 0; + *phas_named_captures = 0; + for (p = s->buf_start; p < s->buf_end; p++) { + switch (*p) { + case '(': + if (p[1] == '?') { + if (p[2] == '<' && p[3] != '=' && p[3] != '!') { + *phas_named_captures = 1; + /* potential named capture */ + if (capture_name) { + p += 3; + if (re_parse_group_name(name, sizeof(name), &p) == 0) { + if (!strcmp(name, capture_name)) { + if (emit_group_index) + dbuf_putc(&s->byte_code, capture_index); + n++; + } + } + } + capture_index++; + if (capture_index >= CAPTURE_COUNT_MAX) + goto done; + } + } else { + capture_index++; + if (capture_index >= CAPTURE_COUNT_MAX) + goto done; + } + break; + case '\\': + p++; + break; + case '[': + for (p += 1 + (*p == ']'); p < s->buf_end && *p != ']'; p++) { + if (*p == '\\') + p++; + } + break; + } + } + done: + if (capture_name) { + return n; + } else { + return capture_index; + } +} + +static int re_count_captures(REParseState *s) +{ + if (s->total_capture_count < 0) { + s->total_capture_count = re_parse_captures(s, &s->has_named_captures, + NULL, false); + } + return s->total_capture_count; +} + +static bool re_has_named_captures(REParseState *s) +{ + if (s->has_named_captures < 0) + re_count_captures(s); + return s->has_named_captures; +} + +static int find_group_name(REParseState *s, const char *name, bool emit_group_index) +{ + const char *p, *buf_end; + size_t len, name_len; + int capture_index, n; + + p = (char *)s->group_names.buf; + if (!p) + return 0; + buf_end = (char *)s->group_names.buf + s->group_names.size; + name_len = strlen(name); + capture_index = 1; + n = 0; + while (p < buf_end) { + len = strlen(p); + if (len == name_len && memcmp(name, p, name_len) == 0) { + if (emit_group_index) + dbuf_putc(&s->byte_code, capture_index); + n++; + } + p += len + LRE_GROUP_NAME_TRAILER_LEN; + capture_index++; + } + return n; +} + +static bool is_duplicate_group_name(REParseState *s, const char *name, int scope) +{ + const char *p, *buf_end; + size_t len, name_len; + int scope1; + + p = (char *)s->group_names.buf; + if (!p) + return 0; + buf_end = (char *)s->group_names.buf + s->group_names.size; + name_len = strlen(name); + while (p < buf_end) { + len = strlen(p); + if (len == name_len && memcmp(name, p, name_len) == 0) { + scope1 = (uint8_t)p[len + 1]; + if (scope == scope1) + return true; + } + p += len + LRE_GROUP_NAME_TRAILER_LEN; + } + return false; +} + +static int re_parse_disjunction(REParseState *s, bool is_backward_dir); + +static int re_parse_modifiers(REParseState *s, const uint8_t **pp) +{ + const uint8_t *p = *pp; + int mask = 0; + int val; + + for(;;) { + if (*p == 'i') { + val = LRE_FLAG_IGNORECASE; + } else if (*p == 'm') { + val = LRE_FLAG_MULTILINE; + } else if (*p == 's') { + val = LRE_FLAG_DOTALL; + } else { + break; + } + if (mask & val) + return re_parse_error(s, "duplicate modifier: '%c'", *p); + mask |= val; + p++; + } + *pp = p; + return mask; +} + +static bool update_modifier(bool val, int add_mask, int remove_mask, + int mask) +{ + if (add_mask & mask) + val = true; + if (remove_mask & mask) + val = false; + return val; +} + +static int re_parse_term(REParseState *s, bool is_backward_dir) +{ + const uint8_t *p; + int c, last_atom_start, quant_min, quant_max, last_capture_count; + bool greedy, is_neg, is_backward_lookahead; + REStringList cr_s, *cr = &cr_s; + + /* after a failed allocation the byte code is incomplete and 'buf' may + still be NULL: stop before computing positions inside it */ + if (dbuf_error(&s->byte_code)) + return re_parse_out_of_memory(s); + + last_atom_start = -1; + last_capture_count = 0; + p = s->buf_ptr; + c = *p; + switch(c) { + case '^': + p++; + re_emit_op(s, s->multi_line ? REOP_line_start_m : REOP_line_start); + break; + case '$': + p++; + re_emit_op(s, s->multi_line ? REOP_line_end_m : REOP_line_end); + break; + case '.': + p++; + last_atom_start = s->byte_code.size; + last_capture_count = s->capture_count; + if (is_backward_dir) + re_emit_op(s, REOP_prev); + re_emit_op(s, s->dotall ? REOP_any : REOP_dot); + if (is_backward_dir) + re_emit_op(s, REOP_prev); + break; + case '{': + if (s->is_unicode) { + return re_parse_error(s, "syntax error"); + } else if (!lre_is_digit(p[1])) { + /* Annex B: we accept '{' not followed by digits as a + normal atom */ + goto parse_class_atom; + } else { + const uint8_t *p1 = p + 1; + /* Annex B: error if it is like a repetition count */ + parse_digits(&p1, true); + if (*p1 == ',') { + p1++; + if (lre_is_digit(*p1)) { + parse_digits(&p1, true); + } + } + if (*p1 != '}') { + goto parse_class_atom; + } + } + /* fall thru */ + case '*': + case '+': + case '?': + return re_parse_error(s, "nothing to repeat"); + case '(': + if (p[1] == '?') { + if (p[2] == ':') { + p += 3; + last_atom_start = s->byte_code.size; + last_capture_count = s->capture_count; + s->buf_ptr = p; + if (re_parse_disjunction(s, is_backward_dir)) + return -1; + p = s->buf_ptr; + if (re_parse_expect(s, &p, ')')) + return -1; + } else if (p[2] == 'i' || p[2] == 'm' || p[2] == 's' || p[2] == '-') { + bool saved_ignore_case, saved_multi_line, saved_dotall; + int add_mask, remove_mask; + p += 2; + remove_mask = 0; + add_mask = re_parse_modifiers(s, &p); + if (add_mask < 0) + return -1; + if (*p == '-') { + p++; + remove_mask = re_parse_modifiers(s, &p); + if (remove_mask < 0) + return -1; + } + if ((add_mask == 0 && remove_mask == 0) || + (add_mask & remove_mask) != 0) { + return re_parse_error(s, "invalid modifiers"); + } + if (re_parse_expect(s, &p, ':')) + return -1; + saved_ignore_case = s->ignore_case; + saved_multi_line = s->multi_line; + saved_dotall = s->dotall; + s->ignore_case = update_modifier(s->ignore_case, add_mask, remove_mask, LRE_FLAG_IGNORECASE); + s->multi_line = update_modifier(s->multi_line, add_mask, remove_mask, LRE_FLAG_MULTILINE); + s->dotall = update_modifier(s->dotall, add_mask, remove_mask, LRE_FLAG_DOTALL); + + last_atom_start = s->byte_code.size; + last_capture_count = s->capture_count; + s->buf_ptr = p; + if (re_parse_disjunction(s, is_backward_dir)) + return -1; + p = s->buf_ptr; + if (re_parse_expect(s, &p, ')')) + return -1; + s->ignore_case = saved_ignore_case; + s->multi_line = saved_multi_line; + s->dotall = saved_dotall; + } else if ((p[2] == '=' || p[2] == '!')) { + is_neg = (p[2] == '!'); + is_backward_lookahead = false; + p += 3; + goto lookahead; + } else if (p[2] == '<' && + (p[3] == '=' || p[3] == '!')) { + int pos; + is_neg = (p[3] == '!'); + is_backward_lookahead = true; + p += 4; + /* lookahead */ + lookahead: + /* Annex B allows lookahead to be used as an atom for + the quantifiers */ + if (!s->is_unicode && !is_backward_lookahead) { + last_atom_start = s->byte_code.size; + last_capture_count = s->capture_count; + } + pos = re_emit_op_u32(s, REOP_lookahead + is_neg, 0); + s->buf_ptr = p; + if (re_parse_disjunction(s, is_backward_lookahead)) + return -1; + p = s->buf_ptr; + if (re_parse_expect(s, &p, ')')) + return -1; + re_emit_op(s, REOP_lookahead_match + is_neg); + /* jump after the 'match' after the lookahead is successful */ + if (dbuf_error(&s->byte_code)) + return re_parse_out_of_memory(s); + put_u32(s->byte_code.buf + pos, s->byte_code.size - (pos + 4)); + } else if (p[2] == '<') { + p += 3; + if (re_parse_group_name(s->u.tmp_buf, sizeof(s->u.tmp_buf), + &p)) { + return re_parse_error(s, "invalid group name"); + } + /* poor's man method to test duplicate group + names. */ + /* XXX: this method does not catch all the errors*/ + if (is_duplicate_group_name(s, s->u.tmp_buf, s->group_name_scope)) { + return re_parse_error(s, "duplicate group name"); + } + /* group name with a trailing zero */ + dbuf_put(&s->group_names, (uint8_t *)s->u.tmp_buf, + strlen(s->u.tmp_buf) + 1); + dbuf_putc(&s->group_names, s->group_name_scope); + s->has_named_captures = 1; + goto parse_capture; + } else { + return re_parse_error(s, "invalid group"); + } + } else { + int capture_index; + p++; + /* capture without group name */ + dbuf_putc(&s->group_names, 0); + dbuf_putc(&s->group_names, 0); + parse_capture: + if (s->capture_count >= CAPTURE_COUNT_MAX) + return re_parse_error(s, "too many captures"); + last_atom_start = s->byte_code.size; + last_capture_count = s->capture_count; + capture_index = s->capture_count++; + re_emit_op_u8(s, REOP_save_start + is_backward_dir, + capture_index); + + s->buf_ptr = p; + if (re_parse_disjunction(s, is_backward_dir)) + return -1; + p = s->buf_ptr; + + re_emit_op_u8(s, REOP_save_start + 1 - is_backward_dir, + capture_index); + + if (re_parse_expect(s, &p, ')')) + return -1; + } + break; + case '\\': + switch(p[1]) { + case 'b': + case 'B': + if (p[1] != 'b') { + re_emit_op(s, s->ignore_case && s->is_unicode ? REOP_not_word_boundary_i : REOP_not_word_boundary); + } else { + re_emit_op(s, s->ignore_case && s->is_unicode ? REOP_word_boundary_i : REOP_word_boundary); + } + p += 2; + break; + case 'k': + { + const uint8_t *p1; + int dummy_res, n; + bool is_forward; + + p1 = p; + if (p1[2] != '<') { + /* annex B: we tolerate invalid group names in non + unicode mode if there is no named capture + definition */ + if (s->is_unicode || re_has_named_captures(s)) + return re_parse_error(s, "expecting group name"); + else + goto parse_class_atom; + } + p1 += 3; + if (re_parse_group_name(s->u.tmp_buf, sizeof(s->u.tmp_buf), + &p1)) { + if (s->is_unicode || re_has_named_captures(s)) + return re_parse_error(s, "invalid group name"); + else + goto parse_class_atom; + } + is_forward = false; + n = find_group_name(s, s->u.tmp_buf, false); + if (n == 0) { + /* no capture name parsed before, try to look + after (inefficient, but hopefully not common */ + n = re_parse_captures(s, &dummy_res, s->u.tmp_buf, false); + if (n == 0) { + if (s->is_unicode || re_has_named_captures(s)) + return re_parse_error(s, "group name not defined"); + else + goto parse_class_atom; + } + is_forward = true; + } + last_atom_start = s->byte_code.size; + last_capture_count = s->capture_count; + + /* emit back references to all the captures indexes matching the group name */ + re_emit_op_u8(s, REOP_back_reference + 2 * is_backward_dir + s->ignore_case, n); + if (is_forward) { + re_parse_captures(s, &dummy_res, s->u.tmp_buf, true); + } else { + find_group_name(s, s->u.tmp_buf, true); + } + p = p1; + } + break; + case '0': + p += 2; + c = 0; + if (s->is_unicode) { + if (lre_is_digit(*p)) { + return re_parse_error(s, "invalid decimal escape in regular expression"); + } + } else { + /* Annex B.1.4: accept legacy octal */ + if (*p >= '0' && *p <= '7') { + c = *p++ - '0'; + if (*p >= '0' && *p <= '7') { + c = (c << 3) + *p++ - '0'; + } + } + } + goto normal_char; + case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': + case '9': + { + const uint8_t *q = ++p; + + c = parse_digits(&p, false); + if (c < 0 || (c >= s->capture_count && c >= re_count_captures(s))) { + if (!s->is_unicode) { + /* Annex B.1.4: accept legacy octal */ + p = q; + if (*p <= '7') { + c = 0; + if (*p <= '3') + c = *p++ - '0'; + if (*p >= '0' && *p <= '7') { + c = (c << 3) + *p++ - '0'; + if (*p >= '0' && *p <= '7') { + c = (c << 3) + *p++ - '0'; + } + } + } else { + c = *p++; + } + goto normal_char; + } + return re_parse_error(s, "back reference out of range in regular expression"); + } + last_atom_start = s->byte_code.size; + last_capture_count = s->capture_count; + + re_emit_op_u8(s, REOP_back_reference + 2 * is_backward_dir + s->ignore_case, 1); + dbuf_putc(&s->byte_code, c); + } + break; + default: + goto parse_class_atom; + } + break; + case '[': + last_atom_start = s->byte_code.size; + last_capture_count = s->capture_count; + if (is_backward_dir) + re_emit_op(s, REOP_prev); + if (re_parse_char_class(s, &p)) + return -1; + if (is_backward_dir) + re_emit_op(s, REOP_prev); + break; + case ']': + case '}': + if (s->is_unicode) + return re_parse_error(s, "syntax error"); + goto parse_class_atom; + default: + parse_class_atom: + c = get_class_atom(s, cr, &p, false); + if ((int)c < 0) + return -1; + normal_char: + last_atom_start = s->byte_code.size; + last_capture_count = s->capture_count; + if (is_backward_dir) + re_emit_op(s, REOP_prev); + if (c >= CLASS_RANGE_BASE) { + int ret = 0; + /* optimize the common 'space' tests */ + if (c == (CLASS_RANGE_BASE + CHAR_RANGE_s)) { + re_emit_op(s, REOP_space); + } else if (c == (CLASS_RANGE_BASE + CHAR_RANGE_S)) { + re_emit_op(s, REOP_not_space); + } else { + ret = re_emit_string_list(s, cr); + } + re_string_list_free(cr); + if (ret) + return -1; + } else { + if (s->ignore_case) + c = lre_canonicalize(c, s->is_unicode); + re_emit_char(s, c); + } + if (is_backward_dir) + re_emit_op(s, REOP_prev); + break; + } + + /* quantifier */ + if (last_atom_start >= 0) { + c = *p; + switch(c) { + case '*': + p++; + quant_min = 0; + quant_max = INT32_MAX; + goto quantifier; + case '+': + p++; + quant_min = 1; + quant_max = INT32_MAX; + goto quantifier; + case '?': + p++; + quant_min = 0; + quant_max = 1; + goto quantifier; + case '{': + { + const uint8_t *p1 = p; + /* As an extension (see ES6 annex B), we accept '{' not + followed by digits as a normal atom */ + if (!lre_is_digit(p[1])) { + if (s->is_unicode) + goto invalid_quant_count; + break; + } + p++; + quant_min = parse_digits(&p, true); + quant_max = quant_min; + if (*p == ',') { + p++; + if (lre_is_digit(*p)) { + quant_max = parse_digits(&p, true); + if (quant_max < quant_min) { + invalid_quant_count: + return re_parse_error(s, "invalid repetition count"); + } + } else { + quant_max = INT32_MAX; /* infinity */ + } + } + if (*p != '}' && !s->is_unicode) { + /* Annex B: normal atom if invalid '{' syntax */ + p = p1; + break; + } + if (re_parse_expect(s, &p, '}')) + return -1; + } + quantifier: + greedy = true; + if (*p == '?') { + p++; + greedy = false; + } + if (last_atom_start < 0) { + return re_parse_error(s, "nothing to repeat"); + } + { + bool need_capture_init, add_zero_advance_check; + int len, pos; + + /* the spec tells that if there is no advance when + running the atom after the first quant_min times, + then there is no match. We remove this test when we + are sure the atom always advances the position. */ + add_zero_advance_check = + re_need_check_adv_and_capture_init(&need_capture_init, + s->byte_code.buf + last_atom_start, + s->byte_code.size - last_atom_start); + + /* general case: need to reset the capture at each + iteration. We don't do it if there are no captures + in the atom or if we are sure all captures are + initialized in the atom. If quant_min = 0, we still + need to reset once the captures in case the atom + does not match. */ + if (need_capture_init && last_capture_count != s->capture_count) { + if (dbuf_insert(&s->byte_code, last_atom_start, 3)) + goto out_of_memory; + int pos = last_atom_start; + s->byte_code.buf[pos++] = REOP_save_reset; + s->byte_code.buf[pos++] = last_capture_count; + s->byte_code.buf[pos++] = s->capture_count - 1; + } + + len = s->byte_code.size - last_atom_start; + if (quant_min == 0) { + /* need to reset the capture in case the atom is + not executed */ + if (!need_capture_init && last_capture_count != s->capture_count) { + if (dbuf_insert(&s->byte_code, last_atom_start, 3)) + goto out_of_memory; + s->byte_code.buf[last_atom_start++] = REOP_save_reset; + s->byte_code.buf[last_atom_start++] = last_capture_count; + s->byte_code.buf[last_atom_start++] = s->capture_count - 1; + } + if (quant_max == 0) { + s->byte_code.size = last_atom_start; + } else if (quant_max == 1 || quant_max == INT32_MAX) { + bool has_goto = (quant_max == INT32_MAX); + if (dbuf_insert(&s->byte_code, last_atom_start, 5 + add_zero_advance_check * 2)) + goto out_of_memory; + s->byte_code.buf[last_atom_start] = REOP_split_goto_first + + greedy; + put_u32(s->byte_code.buf + last_atom_start + 1, + len + 5 * has_goto + add_zero_advance_check * 2 * 2); + if (add_zero_advance_check) { + s->byte_code.buf[last_atom_start + 1 + 4] = REOP_set_char_pos; + s->byte_code.buf[last_atom_start + 1 + 4 + 1] = 0; + re_emit_op_u8(s, REOP_check_advance, 0); + } + if (has_goto) + re_emit_goto(s, REOP_goto, last_atom_start); + } else { + if (dbuf_insert(&s->byte_code, last_atom_start, 11 + add_zero_advance_check * 2)) + goto out_of_memory; + pos = last_atom_start; + s->byte_code.buf[pos++] = REOP_split_goto_first + greedy; + put_u32(s->byte_code.buf + pos, 6 + add_zero_advance_check * 2 + len + 10); + pos += 4; + + s->byte_code.buf[pos++] = REOP_set_i32; + s->byte_code.buf[pos++] = 0; + put_u32(s->byte_code.buf + pos, quant_max); + pos += 4; + last_atom_start = pos; + if (add_zero_advance_check) { + s->byte_code.buf[pos++] = REOP_set_char_pos; + s->byte_code.buf[pos++] = 0; + } + re_emit_goto_u8_u32(s, (add_zero_advance_check ? REOP_loop_check_adv_split_next_first : REOP_loop_split_next_first) - greedy, 0, quant_max, last_atom_start); + } + } else if (quant_min == 1 && quant_max == INT32_MAX && + !add_zero_advance_check) { + re_emit_goto(s, REOP_split_next_first - greedy, + last_atom_start); + } else { + if (quant_min == quant_max) + add_zero_advance_check = false; + if (dbuf_insert(&s->byte_code, last_atom_start, 6 + add_zero_advance_check * 2)) + goto out_of_memory; + /* Note: we assume the string length is < INT32_MAX */ + pos = last_atom_start; + s->byte_code.buf[pos++] = REOP_set_i32; + s->byte_code.buf[pos++] = 0; + put_u32(s->byte_code.buf + pos, quant_max); + pos += 4; + last_atom_start = pos; + if (add_zero_advance_check) { + s->byte_code.buf[pos++] = REOP_set_char_pos; + s->byte_code.buf[pos++] = 0; + } + if (quant_min == quant_max) { + /* a simple loop is enough */ + re_emit_goto_u8(s, REOP_loop, 0, last_atom_start); + } else { + re_emit_goto_u8_u32(s, (add_zero_advance_check ? REOP_loop_check_adv_split_next_first : REOP_loop_split_next_first) - greedy, 0, quant_max - quant_min, last_atom_start); + } + } + last_atom_start = -1; + } + break; + default: + break; + } + } + s->buf_ptr = p; + return 0; + out_of_memory: + return re_parse_out_of_memory(s); +} + +static int re_parse_alternative(REParseState *s, bool is_backward_dir) +{ + const uint8_t *p; + int ret; + size_t start, term_start, end, term_size; + + start = s->byte_code.size; + for(;;) { + p = s->buf_ptr; + if (p >= s->buf_end) + break; + if (*p == '|' || *p == ')') + break; + term_start = s->byte_code.size; + ret = re_parse_term(s, is_backward_dir); + if (ret) + return ret; + if (is_backward_dir) { + /* reverse the order of the terms (XXX: inefficient, but + speed is not really critical here) */ + end = s->byte_code.size; + term_size = end - term_start; + if (dbuf_claim(&s->byte_code, term_size)) + return re_parse_out_of_memory(s); + memmove(s->byte_code.buf + start + term_size, + s->byte_code.buf + start, + end - start); + memcpy(s->byte_code.buf + start, s->byte_code.buf + end, + term_size); + } + } + return 0; +} + +static int re_parse_disjunction(REParseState *s, bool is_backward_dir) +{ + int start, len, pos; + + if (lre_check_stack_overflow(s->opaque, 0)) + return re_parse_error(s, "stack overflow"); + + start = s->byte_code.size; + if (re_parse_alternative(s, is_backward_dir)) + return -1; + while (*s->buf_ptr == '|') { + s->buf_ptr++; + + len = s->byte_code.size - start; + + /* insert a split before the first alternative */ + if (dbuf_insert(&s->byte_code, start, 5)) { + return re_parse_out_of_memory(s); + } + s->byte_code.buf[start] = REOP_split_next_first; + put_u32(s->byte_code.buf + start + 1, len + 5); + + pos = re_emit_op_u32(s, REOP_goto, 0); + + if (re_has_named_captures(s) && s->group_name_scope == GROUP_NAME_SCOPE_MAX) + return re_parse_error(s, "too many named groups"); + + s->group_name_scope++; + + if (re_parse_alternative(s, is_backward_dir)) + return -1; + + /* patch the goto ('pos' is only valid if the placeholder for the + offset could be emitted) */ + if (dbuf_error(&s->byte_code)) + return re_parse_out_of_memory(s); + len = s->byte_code.size - (pos + 4); + put_u32(s->byte_code.buf + pos, len); + } + return 0; +} + +/* Allocate the registers as a stack. The control flow is recursive so + the analysis can be linear. */ +static int compute_register_count(uint8_t *bc_buf, int bc_buf_len) +{ + int stack_size, stack_size_max, pos, opcode, len; + uint32_t val; + + stack_size = 0; + stack_size_max = 0; + bc_buf += RE_HEADER_LEN; + bc_buf_len -= RE_HEADER_LEN; + pos = 0; + while (pos < bc_buf_len) { + opcode = bc_buf[pos]; + len = reopcode_info[opcode].size; + assert(opcode < REOP_COUNT); + assert((pos + len) <= bc_buf_len); + switch(opcode) { + case REOP_set_i32: + case REOP_set_char_pos: + bc_buf[pos + 1] = stack_size; + stack_size++; + if (stack_size > stack_size_max) { + if (stack_size > REGISTER_COUNT_MAX) + return -1; + stack_size_max = stack_size; + } + break; + case REOP_check_advance: + case REOP_loop: + case REOP_loop_split_goto_first: + case REOP_loop_split_next_first: + assert(stack_size > 0); + stack_size--; + bc_buf[pos + 1] = stack_size; + break; + case REOP_loop_check_adv_split_goto_first: + case REOP_loop_check_adv_split_next_first: + assert(stack_size >= 2); + stack_size -= 2; + bc_buf[pos + 1] = stack_size; + break; + case REOP_range: + case REOP_range_i: + val = get_u16(bc_buf + pos + 1); + len += val * 4; + break; + case REOP_range32: + case REOP_range32_i: + val = get_u16(bc_buf + pos + 1); + len += val * 8; + break; + case REOP_back_reference: + case REOP_back_reference_i: + case REOP_backward_back_reference: + case REOP_backward_back_reference_i: + val = bc_buf[pos + 1]; + len += val; + break; + } + pos += len; + } + return stack_size_max; +} + +static void *lre_bytecode_realloc(void *opaque, void *ptr, size_t size) +{ + if (size > (INT32_MAX / 2)) { + /* the bytecode cannot be larger than 2G. Leave some slack to + avoid some overflows. */ + return NULL; + } else { + return lre_realloc(opaque, ptr, size); + } +} + +/* 'buf' must be a zero terminated UTF-8 string of length buf_len. + Return NULL if error and allocate an error message in *perror_msg, + otherwise the compiled bytecode and its length in plen. +*/ +uint8_t *lre_compile(int *plen, char *error_msg, int error_msg_size, + const char *buf, size_t buf_len, int re_flags, + void *opaque) +{ + REParseState s_s, *s = &s_s; + int register_count; + bool is_sticky; + + memset(s, 0, sizeof(*s)); + s->opaque = opaque; + s->buf_ptr = (const uint8_t *)buf; + s->buf_end = s->buf_ptr + buf_len; + s->buf_start = s->buf_ptr; + s->re_flags = re_flags; + s->is_unicode = ((re_flags & (LRE_FLAG_UNICODE | LRE_FLAG_UNICODE_SETS)) != 0); + is_sticky = ((re_flags & LRE_FLAG_STICKY) != 0); + s->ignore_case = ((re_flags & LRE_FLAG_IGNORECASE) != 0); + s->multi_line = ((re_flags & LRE_FLAG_MULTILINE) != 0); + s->dotall = ((re_flags & LRE_FLAG_DOTALL) != 0); + s->unicode_sets = ((re_flags & LRE_FLAG_UNICODE_SETS) != 0); + s->capture_count = 1; + s->total_capture_count = -1; + s->has_named_captures = -1; + + dbuf_init2(&s->byte_code, opaque, lre_bytecode_realloc); + dbuf_init2(&s->group_names, opaque, lre_realloc); + + dbuf_put_u16(&s->byte_code, re_flags); /* first element is the flags */ + dbuf_putc(&s->byte_code, 0); /* second element is the number of captures */ + dbuf_putc(&s->byte_code, 0); /* stack size */ + dbuf_put_u32(&s->byte_code, 0); /* bytecode length */ + + if (!is_sticky) { + /* iterate thru all positions (about the same as .*?( ... ) ) + . We do it without an explicit loop so that lock step + thread execution will be possible in an optimized + implementation */ + re_emit_op_u32(s, REOP_split_goto_first, 1 + 5); + re_emit_op(s, REOP_any); + re_emit_op_u32(s, REOP_goto, -(5 + 1 + 5)); + } + re_emit_op_u8(s, REOP_save_start, 0); + + if (re_parse_disjunction(s, false)) { + error: + dbuf_free(&s->byte_code); + dbuf_free(&s->group_names); + js__pstrcpy(error_msg, error_msg_size, s->u.error_msg); + *plen = 0; + return NULL; + } + + re_emit_op_u8(s, REOP_save_end, 0); + + re_emit_op(s, REOP_match); + + if (*s->buf_ptr != '\0') { + re_parse_error(s, "extraneous characters at the end"); + goto error; + } + + if (dbuf_error(&s->byte_code)) { + re_parse_out_of_memory(s); + goto error; + } + + register_count = compute_register_count(s->byte_code.buf, s->byte_code.size); + if (register_count < 0) { + re_parse_error(s, "too many imbricated quantifiers"); + goto error; + } + + s->byte_code.buf[RE_HEADER_CAPTURE_COUNT] = s->capture_count; + s->byte_code.buf[RE_HEADER_REGISTER_COUNT] = register_count; + put_u32(s->byte_code.buf + RE_HEADER_BYTECODE_LEN, + s->byte_code.size - RE_HEADER_LEN); + + /* add the named groups if needed */ + if (s->group_names.size > (s->capture_count - 1) * LRE_GROUP_NAME_TRAILER_LEN) { + if (dbuf_put(&s->byte_code, s->group_names.buf, s->group_names.size)) { + re_parse_out_of_memory(s); + goto error; + } + put_u16(s->byte_code.buf + RE_HEADER_FLAGS, + lre_get_flags(s->byte_code.buf) | LRE_FLAG_NAMED_GROUPS); + } + dbuf_free(&s->group_names); + +#ifdef DUMP_REOP + lre_dump_bytecode(s->byte_code.buf, s->byte_code.size); +#endif + + error_msg[0] = '\0'; + *plen = s->byte_code.size; + return s->byte_code.buf; +} + +static bool is_line_terminator(uint32_t c) +{ + return (c == '\n' || c == '\r' || c == CP_LS || c == CP_PS); +} + +#define GET_CHAR(c, cptr, cbuf_end, cbuf_type) \ + do { \ + if (cbuf_type == 0) { \ + c = *cptr++; \ + } else { \ + const uint16_t *_p = (const uint16_t *)cptr; \ + const uint16_t *_end = (const uint16_t *)cbuf_end; \ + c = *_p++; \ + if (is_hi_surrogate(c) && cbuf_type == 2) { \ + if (_p < _end && is_lo_surrogate(*_p)) { \ + c = from_surrogate(c, *_p++); \ + } \ + } \ + cptr = (const void *)_p; \ + } \ + } while (0) + +#define PEEK_CHAR(c, cptr, cbuf_end, cbuf_type) \ + do { \ + if (cbuf_type == 0) { \ + c = cptr[0]; \ + } else { \ + const uint16_t *_p = (const uint16_t *)cptr; \ + const uint16_t *_end = (const uint16_t *)cbuf_end; \ + c = *_p++; \ + if (is_hi_surrogate(c) && cbuf_type == 2) { \ + if (_p < _end && is_lo_surrogate(*_p)) { \ + c = from_surrogate(c, *_p); \ + } \ + } \ + } \ + } while (0) + +#define PEEK_PREV_CHAR(c, cptr, cbuf_start, cbuf_type) \ + do { \ + if (cbuf_type == 0) { \ + c = cptr[-1]; \ + } else { \ + const uint16_t *_p = (const uint16_t *)cptr - 1; \ + const uint16_t *_start = (const uint16_t *)cbuf_start; \ + c = *_p; \ + if (is_lo_surrogate(c) && cbuf_type == 2) { \ + if (_p > _start && is_hi_surrogate(_p[-1])) { \ + c = from_surrogate(*--_p, c); \ + } \ + } \ + } \ + } while (0) + +#define GET_PREV_CHAR(c, cptr, cbuf_start, cbuf_type) \ + do { \ + if (cbuf_type == 0) { \ + cptr--; \ + c = cptr[0]; \ + } else { \ + const uint16_t *_p = (const uint16_t *)cptr - 1; \ + const uint16_t *_start = (const uint16_t *)cbuf_start; \ + c = *_p; \ + if (is_lo_surrogate(c) && cbuf_type == 2) { \ + if (_p > _start && is_hi_surrogate(_p[-1])) { \ + c = from_surrogate(*--_p, c); \ + } \ + } \ + cptr = (const void *)_p; \ + } \ + } while (0) + +#define PREV_CHAR(cptr, cbuf_start, cbuf_type) \ + do { \ + if (cbuf_type == 0) { \ + cptr--; \ + } else { \ + const uint16_t *_p = (const uint16_t *)cptr - 1; \ + const uint16_t *_start = (const uint16_t *)cbuf_start; \ + if (is_lo_surrogate(*_p) && cbuf_type == 2) { \ + if (_p > _start && is_hi_surrogate(_p[-1])) { \ + --_p; \ + } \ + } \ + cptr = (const void *)_p; \ + } \ + } while (0) + +typedef enum { + RE_EXEC_STATE_SPLIT, + RE_EXEC_STATE_LOOKAHEAD, + RE_EXEC_STATE_NEGATIVE_LOOKAHEAD, +} REExecStateEnum; + +#if INTPTR_MAX >= INT64_MAX +#define BP_TYPE_BITS 3 +#else +#define BP_TYPE_BITS 2 +#endif + +typedef union { + uint8_t *ptr; + intptr_t val; /* for bp, the low BP_SHIFT bits store REExecStateEnum */ + struct { + uintptr_t val : sizeof(uintptr_t) * 8 - BP_TYPE_BITS; + uintptr_t type : BP_TYPE_BITS; + } bp; +} StackElem; + +typedef struct { + const uint8_t *cbuf; + const uint8_t *cbuf_end; + /* 0 = 8 bit chars, 1 = 16 bit chars, 2 = 16 bit chars, UTF-16 */ + int cbuf_type; + int capture_count; + bool is_unicode; + int interrupt_counter; + void *opaque; /* used for stack overflow check */ + + StackElem *stack_buf; + size_t stack_size; + StackElem static_stack_buf[32]; /* static stack to avoid allocation in most cases */ +} REExecContext; + +static int lre_poll_timeout(REExecContext *s) +{ + if (unlikely(--s->interrupt_counter <= 0)) { + s->interrupt_counter = INTERRUPT_COUNTER_INIT; + if (lre_check_timeout(s->opaque)) + return LRE_RET_TIMEOUT; + } + return 0; +} + +static no_inline int stack_realloc(REExecContext *s, size_t n) +{ + StackElem *new_stack; + size_t new_size; + new_size = s->stack_size * 3 / 2; + if (new_size < n) + new_size = n; + if (s->stack_buf == s->static_stack_buf) { + new_stack = lre_realloc(s->opaque, NULL, new_size * sizeof(StackElem)); + if (!new_stack) + return -1; + /* XXX: could use correct size */ + memcpy(new_stack, s->stack_buf, s->stack_size * sizeof(StackElem)); + } else { + new_stack = lre_realloc(s->opaque, s->stack_buf, new_size * sizeof(StackElem)); + if (!new_stack) + return -1; + } + s->stack_size = new_size; + s->stack_buf = new_stack; + return 0; +} + +/* return 1 if match, 0 if not match or < 0 if error. */ +static intptr_t lre_exec_backtrack(REExecContext *s, uint8_t **capture, + const uint8_t *pc, const uint8_t *cptr) +{ + int opcode; + int cbuf_type; + uint32_t val, c, idx; + const uint8_t *cbuf_end; + StackElem *sp, *bp, *stack_end; +#ifdef DUMP_EXEC + const uint8_t *pc_start = pc; /* TEST */ +#endif + cbuf_type = s->cbuf_type; + cbuf_end = s->cbuf_end; + + sp = s->stack_buf; + bp = s->stack_buf; + stack_end = s->stack_buf + s->stack_size; + +#define CHECK_STACK_SPACE(n) \ + if (unlikely((stack_end - sp) < (n))) { \ + size_t saved_sp = sp - s->stack_buf; \ + size_t saved_bp = bp - s->stack_buf; \ + if (stack_realloc(s, sp - s->stack_buf + (n))) \ + return LRE_RET_MEMORY_ERROR; \ + stack_end = s->stack_buf + s->stack_size; \ + sp = s->stack_buf + saved_sp; \ + bp = s->stack_buf + saved_bp; \ + } + + /* XXX: could test if the value was saved to reduce the stack size + but slower */ +#define SAVE_CAPTURE(idx, value) \ + { \ + CHECK_STACK_SPACE(2); \ + sp[0].val = idx; \ + sp[1].ptr = capture[idx]; \ + sp += 2; \ + capture[idx] = (value); \ + } + + /* avoid saving the previous value if already saved */ +#define SAVE_CAPTURE_CHECK(idx, value) \ + { \ + StackElem *sp1; \ + sp1 = sp; \ + for(;;) { \ + if (sp1 > bp) { \ + if (sp1[-2].val == idx) \ + break; \ + sp1 -= 2; \ + } else { \ + CHECK_STACK_SPACE(2); \ + sp[0].val = idx; \ + sp[1].ptr = capture[idx]; \ + sp += 2; \ + break; \ + } \ + } \ + capture[idx] = (value); \ + } + + +#ifdef DUMP_EXEC + printf("%5s %5s %5s %5s %s\n", "PC", "CP", "BP", "SP", "OPCODE"); +#endif + for(;;) { + opcode = *pc++; +#ifdef DUMP_EXEC + printf("%5ld %5ld %5ld %5ld %s\n", + pc - 1 - pc_start, + cbuf_type == 0 ? cptr - s->cbuf : (cptr - s->cbuf) / 2, + bp - s->stack_buf, + sp - s->stack_buf, + reopcode_info[opcode].name); +#endif + switch(opcode) { + case REOP_match: + return 1; + no_match: + for(;;) { + REExecStateEnum type; + if (bp == s->stack_buf) + return 0; + /* undo the modifications to capture[] */ + while (sp > bp) { + capture[sp[-2].val] = sp[-1].ptr; + sp -= 2; + } + + pc = sp[-3].ptr; + cptr = sp[-2].ptr; + type = sp[-1].bp.type; + bp = s->stack_buf + sp[-1].bp.val; + sp -= 3; + if (type != RE_EXEC_STATE_LOOKAHEAD) + break; + } + if (lre_poll_timeout(s)) + return LRE_RET_TIMEOUT; + break; + case REOP_lookahead_match: + /* pop all the saved states until reaching the start of + the lookahead and keep the updated captures and + variables and the corresponding undo info. */ + { + StackElem *sp1, *sp_top, *next_sp; + REExecStateEnum type; + + sp_top = sp; + for(;;) { + sp1 = sp; + sp = bp; + pc = sp[-3].ptr; + cptr = sp[-2].ptr; + type = sp[-1].bp.type; + bp = s->stack_buf + sp[-1].bp.val; + sp[-1].ptr = (void *)sp1; /* save the next value for the copy step */ + sp -= 3; + if (type == RE_EXEC_STATE_LOOKAHEAD) + break; + } + if (sp != s->stack_buf) { + /* keep the undo info if there is a saved state */ + sp1 = sp; + while (sp1 < sp_top) { + next_sp = (void *)sp1[2].ptr; + sp1 += 3; + while (sp1 < next_sp) + *sp++ = *sp1++; + } + } + } + break; + case REOP_negative_lookahead_match: + /* pop all the saved states until reaching start of the negative lookahead */ + for(;;) { + REExecStateEnum type; + type = bp[-1].bp.type; + /* undo the modifications to capture[] */ + while (sp > bp) { + capture[sp[-2].val] = sp[-1].ptr; + sp -= 2; + } + pc = sp[-3].ptr; + cptr = sp[-2].ptr; + type = sp[-1].bp.type; + bp = s->stack_buf + sp[-1].bp.val; + sp -= 3; + if (type == RE_EXEC_STATE_NEGATIVE_LOOKAHEAD) + break; + } + goto no_match; + case REOP_char32: + case REOP_char32_i: + val = get_u32(pc); + pc += 4; + goto test_char; + case REOP_char: + case REOP_char_i: + val = get_u16(pc); + pc += 2; + test_char: + if (cptr >= cbuf_end) + goto no_match; + GET_CHAR(c, cptr, cbuf_end, cbuf_type); + if (opcode == REOP_char_i || opcode == REOP_char32_i) { + c = lre_canonicalize(c, s->is_unicode); + } + if (val != c) + goto no_match; + break; + case REOP_split_goto_first: + case REOP_split_next_first: + { + const uint8_t *pc1; + + val = get_u32(pc); + pc += 4; + if (opcode == REOP_split_next_first) { + pc1 = pc + (int)val; + } else { + pc1 = pc; + pc = pc + (int)val; + } + CHECK_STACK_SPACE(3); + sp[0].ptr = (uint8_t *)pc1; + sp[1].ptr = (uint8_t *)cptr; + sp[2].bp.val = bp - s->stack_buf; + sp[2].bp.type = RE_EXEC_STATE_SPLIT; + sp += 3; + bp = sp; + } + break; + case REOP_lookahead: + case REOP_negative_lookahead: + val = get_u32(pc); + pc += 4; + CHECK_STACK_SPACE(3); + sp[0].ptr = (uint8_t *)(pc + (int)val); + sp[1].ptr = (uint8_t *)cptr; + sp[2].bp.val = bp - s->stack_buf; + sp[2].bp.type = RE_EXEC_STATE_LOOKAHEAD + opcode - REOP_lookahead; + sp += 3; + bp = sp; + break; + case REOP_goto: + val = get_u32(pc); + pc += 4 + (int)val; + if (lre_poll_timeout(s)) + return LRE_RET_TIMEOUT; + break; + case REOP_line_start: + case REOP_line_start_m: + if (cptr == s->cbuf) + break; + if (opcode == REOP_line_start) + goto no_match; + PEEK_PREV_CHAR(c, cptr, s->cbuf, cbuf_type); + if (!is_line_terminator(c)) + goto no_match; + break; + case REOP_line_end: + case REOP_line_end_m: + if (cptr == cbuf_end) + break; + if (opcode == REOP_line_end) + goto no_match; + PEEK_CHAR(c, cptr, cbuf_end, cbuf_type); + if (!is_line_terminator(c)) + goto no_match; + break; + case REOP_dot: + if (cptr == cbuf_end) + goto no_match; + GET_CHAR(c, cptr, cbuf_end, cbuf_type); + if (is_line_terminator(c)) + goto no_match; + break; + case REOP_any: + if (cptr == cbuf_end) + goto no_match; + GET_CHAR(c, cptr, cbuf_end, cbuf_type); + break; + case REOP_space: + if (cptr == cbuf_end) + goto no_match; + GET_CHAR(c, cptr, cbuf_end, cbuf_type); + if (!lre_is_space(c)) + goto no_match; + break; + case REOP_not_space: + if (cptr == cbuf_end) + goto no_match; + GET_CHAR(c, cptr, cbuf_end, cbuf_type); + if (lre_is_space(c)) + goto no_match; + break; + case REOP_save_start: + case REOP_save_end: + val = *pc++; + if (val >= (uint32_t)s->capture_count) + return LRE_RET_BYTECODE_ERROR; + idx = 2 * val + opcode - REOP_save_start; + SAVE_CAPTURE(idx, (uint8_t *)cptr); + break; + case REOP_save_reset: + { + uint32_t val2; + val = pc[0]; + val2 = pc[1]; + pc += 2; + if (val2 >= (uint32_t)s->capture_count) + return LRE_RET_BYTECODE_ERROR; + CHECK_STACK_SPACE(2 * (val2 - val + 1)); + while (val <= val2) { + idx = 2 * val; + SAVE_CAPTURE(idx, NULL); + idx = 2 * val + 1; + SAVE_CAPTURE(idx, NULL); + val++; + } + } + break; + case REOP_set_i32: + idx = 2 * s->capture_count + pc[0]; + val = get_u32(pc + 1); + pc += 5; + SAVE_CAPTURE_CHECK(idx, (void *)(uintptr_t)val); + break; + case REOP_loop: + { + uint32_t val2; + idx = 2 * s->capture_count + pc[0]; + val = get_u32(pc + 1); + pc += 5; + + val2 = (uintptr_t)capture[idx] - 1; + SAVE_CAPTURE_CHECK(idx, (void *)(uintptr_t)val2); + if (val2 != 0) { + pc += (int)val; + if (lre_poll_timeout(s)) + return LRE_RET_TIMEOUT; + } + } + break; + case REOP_loop_split_goto_first: + case REOP_loop_split_next_first: + case REOP_loop_check_adv_split_goto_first: + case REOP_loop_check_adv_split_next_first: + { + const uint8_t *pc1; + uint32_t val2, limit; + idx = 2 * s->capture_count + pc[0]; + limit = get_u32(pc + 1); + val = get_u32(pc + 5); + pc += 9; + + /* decrement the counter */ + val2 = (uintptr_t)capture[idx] - 1; + SAVE_CAPTURE_CHECK(idx, (void *)(uintptr_t)val2); + + if (val2 > limit) { + /* normal loop if counter > limit */ + pc += (int)val; + if (lre_poll_timeout(s)) + return LRE_RET_TIMEOUT; + } else { + /* check advance */ + if ((opcode == REOP_loop_check_adv_split_goto_first || + opcode == REOP_loop_check_adv_split_next_first) && + capture[idx + 1] == cptr && + val2 != limit) { + goto no_match; + } + + /* otherwise conditional split */ + if (val2 != 0) { + if (opcode == REOP_loop_split_next_first || + opcode == REOP_loop_check_adv_split_next_first) { + pc1 = pc + (int)val; + } else { + pc1 = pc; + pc = pc + (int)val; + } + CHECK_STACK_SPACE(3); + sp[0].ptr = (uint8_t *)pc1; + sp[1].ptr = (uint8_t *)cptr; + sp[2].bp.val = bp - s->stack_buf; + sp[2].bp.type = RE_EXEC_STATE_SPLIT; + sp += 3; + bp = sp; + } + } + } + break; + case REOP_set_char_pos: + idx = 2 * s->capture_count + pc[0]; + pc++; + SAVE_CAPTURE_CHECK(idx, (uint8_t *)cptr); + break; + case REOP_check_advance: + idx = 2 * s->capture_count + pc[0]; + pc++; + if (capture[idx] == cptr) + goto no_match; + break; + case REOP_word_boundary: + case REOP_word_boundary_i: + case REOP_not_word_boundary: + case REOP_not_word_boundary_i: + { + bool v1, v2; + int ignore_case = (opcode == REOP_word_boundary_i || opcode == REOP_not_word_boundary_i); + bool is_boundary = (opcode == REOP_word_boundary || opcode == REOP_word_boundary_i); + /* char before */ + if (cptr == s->cbuf) { + v1 = false; + } else { + PEEK_PREV_CHAR(c, cptr, s->cbuf, cbuf_type); + if (c < 256) { + v1 = (lre_is_word_byte(c) != 0); + } else { + v1 = ignore_case && (c == 0x017f || c == 0x212a); + } + } + /* current char */ + if (cptr >= cbuf_end) { + v2 = false; + } else { + PEEK_CHAR(c, cptr, cbuf_end, cbuf_type); + if (c < 256) { + v2 = (lre_is_word_byte(c) != 0); + } else { + v2 = ignore_case && (c == 0x017f || c == 0x212a); + } + } + if (v1 ^ v2 ^ is_boundary) + goto no_match; + } + break; + case REOP_back_reference: + case REOP_back_reference_i: + case REOP_backward_back_reference: + case REOP_backward_back_reference_i: + { + const uint8_t *cptr1, *cptr1_end, *cptr1_start; + const uint8_t *pc1; + uint32_t c1, c2; + int i, n; + + n = *pc++; + pc1 = pc; + pc += n; + + for(i = 0; i < n; i++) { + val = pc1[i]; + if (val >= s->capture_count) + goto no_match; + cptr1_start = capture[2 * val]; + cptr1_end = capture[2 * val + 1]; + /* test the first not empty capture */ + if (cptr1_start && cptr1_end) { + if (opcode == REOP_back_reference || + opcode == REOP_back_reference_i) { + cptr1 = cptr1_start; + while (cptr1 < cptr1_end) { + if (cptr >= cbuf_end) + goto no_match; + GET_CHAR(c1, cptr1, cptr1_end, cbuf_type); + GET_CHAR(c2, cptr, cbuf_end, cbuf_type); + if (opcode == REOP_back_reference_i) { + c1 = lre_canonicalize(c1, s->is_unicode); + c2 = lre_canonicalize(c2, s->is_unicode); + } + if (c1 != c2) + goto no_match; + } + } else { + cptr1 = cptr1_end; + while (cptr1 > cptr1_start) { + if (cptr == s->cbuf) + goto no_match; + GET_PREV_CHAR(c1, cptr1, cptr1_start, cbuf_type); + GET_PREV_CHAR(c2, cptr, s->cbuf, cbuf_type); + if (opcode == REOP_backward_back_reference_i) { + c1 = lre_canonicalize(c1, s->is_unicode); + c2 = lre_canonicalize(c2, s->is_unicode); + } + if (c1 != c2) + goto no_match; + } + } + break; + } + } + } + break; + case REOP_range: + case REOP_range_i: + { + int n; + uint32_t low, high, idx_min, idx_max, idx; + + n = get_u16(pc); /* n must be >= 1 */ + pc += 2; + if (cptr >= cbuf_end) + goto no_match; + GET_CHAR(c, cptr, cbuf_end, cbuf_type); + if (opcode == REOP_range_i) { + c = lre_canonicalize(c, s->is_unicode); + } + idx_min = 0; + low = get_u16(pc + 0 * 4); + if (c < low) + goto no_match; + idx_max = n - 1; + high = get_u16(pc + idx_max * 4 + 2); + /* 0xffff in for last value means +infinity */ + if (unlikely(c >= 0xffff) && high == 0xffff) + goto range_match; + if (c > high) + goto no_match; + while (idx_min <= idx_max) { + idx = (idx_min + idx_max) / 2; + low = get_u16(pc + idx * 4); + high = get_u16(pc + idx * 4 + 2); + if (c < low) + idx_max = idx - 1; + else if (c > high) + idx_min = idx + 1; + else + goto range_match; + } + goto no_match; + range_match: + pc += 4 * n; + } + break; + case REOP_range32: + case REOP_range32_i: + { + int n; + uint32_t low, high, idx_min, idx_max, idx; + + n = get_u16(pc); /* n must be >= 1 */ + pc += 2; + if (cptr >= cbuf_end) + goto no_match; + GET_CHAR(c, cptr, cbuf_end, cbuf_type); + if (opcode == REOP_range32_i) { + c = lre_canonicalize(c, s->is_unicode); + } + idx_min = 0; + low = get_u32(pc + 0 * 8); + if (c < low) + goto no_match; + idx_max = n - 1; + high = get_u32(pc + idx_max * 8 + 4); + if (c > high) + goto no_match; + while (idx_min <= idx_max) { + idx = (idx_min + idx_max) / 2; + low = get_u32(pc + idx * 8); + high = get_u32(pc + idx * 8 + 4); + if (c < low) + idx_max = idx - 1; + else if (c > high) + idx_min = idx + 1; + else + goto range32_match; + } + goto no_match; + range32_match: + pc += 8 * n; + } + break; + case REOP_prev: + /* go to the previous char */ + if (cptr == s->cbuf) + goto no_match; + PREV_CHAR(cptr, s->cbuf, cbuf_type); + break; + default: +#ifdef DUMP_EXEC + printf("unknown opcode pc=%ld\n", pc - 1 - pc_start); +#endif + abort(); + } + } +} + +/* Return 1 if match, 0 if not match or < 0 if error (see LRE_RET_x). cindex is the + starting position of the match and must be such as 0 <= cindex <= + clen. */ +int lre_exec(uint8_t **capture, + const uint8_t *bc_buf, const uint8_t *cbuf, int cindex, int clen, + int cbuf_type, void *opaque) +{ + REExecContext s_s, *s = &s_s; + int re_flags, i, ret; + const uint8_t *cptr; + + re_flags = lre_get_flags(bc_buf); + s->is_unicode = (re_flags & (LRE_FLAG_UNICODE | LRE_FLAG_UNICODE_SETS)) != 0; + s->capture_count = bc_buf[RE_HEADER_CAPTURE_COUNT]; + s->cbuf = cbuf; + s->cbuf_end = cbuf + (clen << cbuf_type); + s->cbuf_type = cbuf_type; + if (s->cbuf_type == 1 && s->is_unicode) + s->cbuf_type = 2; + s->interrupt_counter = INTERRUPT_COUNTER_INIT; + s->opaque = opaque; + + s->stack_buf = s->static_stack_buf; + s->stack_size = countof(s->static_stack_buf); + + for(i = 0; i < s->capture_count * 2; i++) + capture[i] = NULL; + + cptr = cbuf + (cindex << cbuf_type); + if (0 < cindex && cindex < clen && s->cbuf_type == 2) { + const uint16_t *p = (const uint16_t *)cptr; + if (is_lo_surrogate(*p) && is_hi_surrogate(p[-1])) { + cptr = (const uint8_t *)(p - 1); + } + } + + ret = lre_exec_backtrack(s, capture, bc_buf + RE_HEADER_LEN, cptr); + + if (s->stack_buf != s->static_stack_buf) + lre_realloc(s->opaque, s->stack_buf, 0); + return ret; +} + +int lre_get_alloc_count(const uint8_t *bc_buf) +{ + return bc_buf[RE_HEADER_CAPTURE_COUNT] * 2 + + bc_buf[RE_HEADER_REGISTER_COUNT]; +} + +/* Structurally validate serialized regexp bytecode (e.g. from JS_ReadObject) + before it is executed: a present header and a body length that fits within + the buffer. Returns 0 on success, -1 if the bytecode is malformed. */ +int lre_check_bytecode(const uint8_t *bc_buf, int bc_buf_len) +{ + uint32_t re_bytecode_len; + if (bc_buf_len < RE_HEADER_LEN) + return -1; + re_bytecode_len = get_u32(bc_buf + RE_HEADER_BYTECODE_LEN); + if (re_bytecode_len > (uint32_t)(bc_buf_len - RE_HEADER_LEN)) + return -1; + return 0; +} + +int lre_get_capture_count(const uint8_t *bc_buf) +{ + return bc_buf[RE_HEADER_CAPTURE_COUNT]; +} + +int lre_get_flags(const uint8_t *bc_buf) +{ + return get_u16(bc_buf + RE_HEADER_FLAGS); +} + +/* Return NULL if no group names. Otherwise, return a pointer to + 'capture_count - 1' zero terminated UTF-8 strings. */ +const char *lre_get_groupnames(const uint8_t *bc_buf) +{ + uint32_t re_bytecode_len; + if ((lre_get_flags(bc_buf) & LRE_FLAG_NAMED_GROUPS) == 0) + return NULL; + re_bytecode_len = get_u32(bc_buf + RE_HEADER_BYTECODE_LEN); + return (const char *)(bc_buf + RE_HEADER_LEN + re_bytecode_len); +} + +#ifdef TEST + +bool lre_check_stack_overflow(void *opaque, size_t alloca_size) +{ + return false; +} + +void *lre_realloc(void *opaque, void *ptr, size_t size) +{ + return realloc(ptr, size); +} + +int main(int argc, char **argv) +{ + int len, flags, ret, i; + uint8_t *bc; + char error_msg[64]; + uint8_t *capture; + const char *input; + int input_len, capture_count; + + if (argc < 4) { + printf("usage: %s regexp flags input\n", argv[0]); + return 1; + } + flags = atoi(argv[2]); + bc = lre_compile(&len, error_msg, sizeof(error_msg), argv[1], + strlen(argv[1]), flags, NULL); + if (!bc) { + fprintf(stderr, "error: %s\n", error_msg); + exit(1); + } + + input = argv[3]; + input_len = strlen(input); + + capture = malloc(sizeof(capture[0]) * lre_get_alloc_count(bc)); + ret = lre_exec(capture, bc, (uint8_t *)input, 0, input_len, 0, NULL); + printf("ret=%d\n", ret); + if (ret == 1) { + capture_count = lre_get_capture_count(bc); + for(i = 0; i < 2 * capture_count; i++) { + uint8_t *ptr; + ptr = capture[i]; + printf("%d: ", i); + if (!ptr) + printf(""); + else + printf("%u", (int)(ptr - (uint8_t *)input)); + printf("\n"); + } + } + free(capture); + return 0; +} +#endif diff --git a/engine/quickjs-ng/libregexp.h b/engine/quickjs-ng/libregexp.h new file mode 100644 index 000000000..44eded3bf --- /dev/null +++ b/engine/quickjs-ng/libregexp.h @@ -0,0 +1,101 @@ +/* + * Regular Expression Engine + * + * Copyright (c) 2017-2018 Fabrice Bellard + * + * 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. + */ +#ifndef LIBREGEXP_H +#define LIBREGEXP_H + +#include +#include + +#include "libunicode.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define LRE_FLAG_GLOBAL (1 << 0) +#define LRE_FLAG_IGNORECASE (1 << 1) +#define LRE_FLAG_MULTILINE (1 << 2) +#define LRE_FLAG_DOTALL (1 << 3) +#define LRE_FLAG_UNICODE (1 << 4) +#define LRE_FLAG_STICKY (1 << 5) +#define LRE_FLAG_INDICES (1 << 6) /* Unused by libregexp, just recorded. */ +#define LRE_FLAG_NAMED_GROUPS (1 << 7) /* named groups are present in the regexp */ +#define LRE_FLAG_UNICODE_SETS (1 << 8) + +#define LRE_RET_MEMORY_ERROR (-1) +#define LRE_RET_TIMEOUT (-2) +#define LRE_RET_BYTECODE_ERROR (-3) + +/* trailer length after the group name including the trailing '\0' */ +#define LRE_GROUP_NAME_TRAILER_LEN 2 + +uint8_t *lre_compile(int *plen, char *error_msg, int error_msg_size, + const char *buf, size_t buf_len, int re_flags, + void *opaque); +int lre_get_alloc_count(const uint8_t *bc_buf); +int lre_check_bytecode(const uint8_t *bc_buf, int bc_buf_len); +int lre_get_capture_count(const uint8_t *bc_buf); +int lre_get_flags(const uint8_t *bc_buf); +const char *lre_get_groupnames(const uint8_t *bc_buf); +int lre_exec(uint8_t **capture, + const uint8_t *bc_buf, const uint8_t *cbuf, int cindex, int clen, + int cbuf_type, void *opaque); + +int lre_parse_escape(const uint8_t **pp, int allow_utf16); +/* lre_is_space() is provided as an inline in libunicode.h */ + +/* must be provided by the user */ +bool lre_check_stack_overflow(void *opaque, size_t alloca_size); +/* must be provided by the user, return non zero if time out */ +int lre_check_timeout(void *opaque); +void *lre_realloc(void *opaque, void *ptr, size_t size); + +/* JS identifier test */ +extern uint32_t const lre_id_start_table_ascii[4]; +extern uint32_t const lre_id_continue_table_ascii[4]; + +static inline int lre_js_is_ident_first(int c) +{ + if ((uint32_t)c < 128) { + return (lre_id_start_table_ascii[c >> 5] >> (c & 31)) & 1; + } else { + return lre_is_id_start(c); + } +} + +static inline int lre_js_is_ident_next(int c) +{ + if ((uint32_t)c < 128) { + return (lre_id_continue_table_ascii[c >> 5] >> (c & 31)) & 1; + } else { + /* ZWNJ and ZWJ are accepted in identifiers */ + return lre_is_id_continue(c) || c == 0x200C || c == 0x200D; + } +} + +#ifdef __cplusplus +} /* extern "C" { */ +#endif + +#endif /* LIBREGEXP_H */ diff --git a/engine/quickjs-ng/libunicode-table.h b/engine/quickjs-ng/libunicode-table.h new file mode 100644 index 000000000..f1cf30418 --- /dev/null +++ b/engine/quickjs-ng/libunicode-table.h @@ -0,0 +1,5160 @@ +/* Compressed unicode tables */ +/* Automatically generated file - do not edit */ + +/* +UNICODE LICENSE V3 + +COPYRIGHT AND PERMISSION NOTICE + +Copyright © 1991-2026 Unicode, Inc. + +NOTICE TO USER: Carefully read the following legal agreement. BY +DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR +SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE +TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT +DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of data files and any associated documentation (the "Data Files") or +software and any associated documentation (the "Software") to deal in the +Data Files or Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, and/or sell +copies of the Data Files or Software, and to permit persons to whom the +Data Files or Software are furnished to do so, provided that either (a) +this copyright and permission notice appear with all copies of the Data +Files or Software, or (b) this copyright and permission notice appear in +associated Documentation. + +THE DATA FILES AND SOFTWARE ARE 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 OF +THIRD PARTY RIGHTS. + +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE +BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, +OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA +FILES OR SOFTWARE. + +Except as contained in this notice, the name of a copyright holder shall +not be used in advertising or otherwise to promote the sale, use or other +dealings in these Data Files or Software without prior written +authorization of the copyright holder. +*/ + +#include + +static const uint32_t case_conv_table1[378] = { + 0x00209a30, 0x00309a00, 0x005a8173, 0x00601730, + 0x006c0730, 0x006f81b3, 0x00701700, 0x007c0700, + 0x007f8100, 0x00803040, 0x009801c3, 0x00988190, + 0x00990640, 0x009c9040, 0x00a481b4, 0x00a52e40, + 0x00bc0130, 0x00bc8640, 0x00bf8170, 0x00c00100, + 0x00c08130, 0x00c10440, 0x00c30130, 0x00c38240, + 0x00c48230, 0x00c58240, 0x00c70130, 0x00c78130, + 0x00c80130, 0x00c88240, 0x00c98130, 0x00ca0130, + 0x00ca8100, 0x00cb0130, 0x00cb8130, 0x00cc0240, + 0x00cd0100, 0x00cd8101, 0x00ce0130, 0x00ce8130, + 0x00cf0100, 0x00cf8130, 0x00d00640, 0x00d30130, + 0x00d38240, 0x00d48130, 0x00d60240, 0x00d70130, + 0x00d78240, 0x00d88230, 0x00d98440, 0x00db8130, + 0x00dc0240, 0x00de0240, 0x00df8100, 0x00e20350, + 0x00e38350, 0x00e50350, 0x00e69040, 0x00ee8100, + 0x00ef1240, 0x00f801b4, 0x00f88350, 0x00fa0240, + 0x00fb0130, 0x00fb8130, 0x00fc2840, 0x01100130, + 0x01111240, 0x011d0131, 0x011d8240, 0x011e8130, + 0x011f0131, 0x011f8201, 0x01208240, 0x01218130, + 0x01220130, 0x01228130, 0x01230a40, 0x01280101, + 0x01288101, 0x01290101, 0x01298100, 0x012a0100, + 0x012b0200, 0x012c8100, 0x012d8100, 0x012e0101, + 0x01300100, 0x01308101, 0x01318100, 0x01320101, + 0x01328101, 0x01330101, 0x01340100, 0x01348100, + 0x01350101, 0x01358101, 0x01360101, 0x01378100, + 0x01388101, 0x01390100, 0x013a8100, 0x013e8101, + 0x01400100, 0x01410101, 0x01418100, 0x01438101, + 0x01440100, 0x01448100, 0x01450200, 0x01460100, + 0x01490100, 0x014e8101, 0x014f0101, 0x01a28173, + 0x01b80440, 0x01bb0240, 0x01bd8300, 0x01bf8130, + 0x01c30130, 0x01c40330, 0x01c60130, 0x01c70230, + 0x01c801d0, 0x01c89130, 0x01d18930, 0x01d60100, + 0x01d68300, 0x01d801d3, 0x01d89100, 0x01e10173, + 0x01e18900, 0x01e60100, 0x01e68200, 0x01e78130, + 0x01e80173, 0x01e88173, 0x01ea8173, 0x01eb0173, + 0x01eb8100, 0x01ec1840, 0x01f80173, 0x01f88173, + 0x01f90100, 0x01f98100, 0x01fa01a0, 0x01fa8173, + 0x01fb8240, 0x01fc8130, 0x01fd0240, 0x01fe8330, + 0x02001030, 0x02082030, 0x02182000, 0x02281000, + 0x02302240, 0x02453640, 0x02600130, 0x02608e40, + 0x02678100, 0x02686040, 0x0298a630, 0x02b0a600, + 0x02c381b5, 0x08502631, 0x08638131, 0x08668131, + 0x08682b00, 0x087e8300, 0x09d05011, 0x09f80610, + 0x09fc0620, 0x0e400174, 0x0e408174, 0x0e410174, + 0x0e418174, 0x0e420174, 0x0e428174, 0x0e430174, + 0x0e438180, 0x0e440180, 0x0e448240, 0x0e482b30, + 0x0e5e8330, 0x0ebc8101, 0x0ebe8101, 0x0ec70101, + 0x0f007e40, 0x0f3f1840, 0x0f4b01b5, 0x0f4b81b6, + 0x0f4c01b6, 0x0f4c81b6, 0x0f4d01b7, 0x0f4d8180, + 0x0f4f0130, 0x0f506040, 0x0f800800, 0x0f840830, + 0x0f880600, 0x0f8c0630, 0x0f900800, 0x0f940830, + 0x0f980800, 0x0f9c0830, 0x0fa00600, 0x0fa40630, + 0x0fa801b0, 0x0fa88100, 0x0fa901d3, 0x0fa98100, + 0x0faa01d3, 0x0faa8100, 0x0fab01d3, 0x0fab8100, + 0x0fac8130, 0x0fad8130, 0x0fae8130, 0x0faf8130, + 0x0fb00800, 0x0fb40830, 0x0fb80200, 0x0fb90400, + 0x0fbb0201, 0x0fbc0201, 0x0fbd0201, 0x0fbe0201, + 0x0fc008b7, 0x0fc40867, 0x0fc808b8, 0x0fcc0868, + 0x0fd008b8, 0x0fd40868, 0x0fd80200, 0x0fd901b9, + 0x0fd981b1, 0x0fda01b9, 0x0fdb01b1, 0x0fdb81d7, + 0x0fdc0230, 0x0fdd0230, 0x0fde0161, 0x0fdf0173, + 0x0fe101b9, 0x0fe181b2, 0x0fe201ba, 0x0fe301b2, + 0x0fe381d8, 0x0fe40430, 0x0fe60162, 0x0fe80201, + 0x0fe901d0, 0x0fe981d0, 0x0feb01b0, 0x0feb81d0, + 0x0fec0230, 0x0fed0230, 0x0ff00201, 0x0ff101d3, + 0x0ff181d3, 0x0ff201ba, 0x0ff28101, 0x0ff301b0, + 0x0ff381d3, 0x0ff40231, 0x0ff50230, 0x0ff60131, + 0x0ff901ba, 0x0ff981b2, 0x0ffa01bb, 0x0ffb01b2, + 0x0ffb81d9, 0x0ffc0230, 0x0ffd0230, 0x0ffe0162, + 0x109301a0, 0x109501a0, 0x109581a0, 0x10990131, + 0x10a70101, 0x10b01031, 0x10b81001, 0x10c18240, + 0x125b1a31, 0x12681a01, 0x16003031, 0x16183001, + 0x16300240, 0x16310130, 0x16318130, 0x16320130, + 0x16328100, 0x16330100, 0x16338640, 0x16368130, + 0x16370130, 0x16378130, 0x16380130, 0x16390240, + 0x163a8240, 0x163f0230, 0x16406440, 0x16758440, + 0x16790240, 0x16802600, 0x16938100, 0x16968100, + 0x53202e40, 0x53401c40, 0x53910e40, 0x53993e40, + 0x53bc8440, 0x53be8130, 0x53bf0a40, 0x53c58240, + 0x53c68130, 0x53c80440, 0x53ca0101, 0x53cb1440, + 0x53d50130, 0x53d58130, 0x53d60130, 0x53d68130, + 0x53d70130, 0x53d80130, 0x53d88130, 0x53d90130, + 0x53d98131, 0x53da1040, 0x53e20131, 0x53e28130, + 0x53e30130, 0x53e38440, 0x53e58130, 0x53e61040, + 0x53ee0130, 0x53fa8240, 0x55a98101, 0x55b85020, + 0x7d8001b2, 0x7d8081b2, 0x7d8101b2, 0x7d8181da, + 0x7d8201da, 0x7d8281b3, 0x7d8301b3, 0x7d8981bb, + 0x7d8a01bb, 0x7d8a81bb, 0x7d8b01bc, 0x7d8b81bb, + 0x7f909a31, 0x7fa09a01, 0x82002831, 0x82142801, + 0x82582431, 0x826c2401, 0x82b80b31, 0x82be0f31, + 0x82c60731, 0x82ca0231, 0x82cb8b01, 0x82d18f01, + 0x82d98701, 0x82dd8201, 0x86403331, 0x86603301, + 0x86a81631, 0x86b81601, 0x8c502031, 0x8c602001, + 0xb7202031, 0xb7302001, 0xb7501931, 0xb75d9901, + 0xf4802231, 0xf4912201, +}; + +static const uint8_t case_conv_table2[378] = { + 0x01, 0x00, 0x9c, 0x06, 0x07, 0x4d, 0x03, 0x04, + 0x10, 0x00, 0x8f, 0x0b, 0x00, 0x00, 0x11, 0x00, + 0x08, 0x00, 0x53, 0x4b, 0x52, 0x00, 0x53, 0x00, + 0x54, 0x00, 0x3b, 0x55, 0x56, 0x00, 0x58, 0x5a, + 0x40, 0x5f, 0x5e, 0x00, 0x47, 0x50, 0x63, 0x65, + 0x43, 0x66, 0x00, 0x68, 0x00, 0x6a, 0x00, 0x6c, + 0x00, 0x6e, 0x00, 0x70, 0x00, 0x00, 0x41, 0x00, + 0x00, 0x00, 0x00, 0x1a, 0x00, 0x93, 0x00, 0x00, + 0x20, 0x36, 0x00, 0x28, 0x00, 0x24, 0x00, 0x24, + 0x25, 0x2d, 0x00, 0x13, 0x6d, 0x6f, 0x00, 0x29, + 0x27, 0x2a, 0x14, 0x16, 0x18, 0x1b, 0x1c, 0x41, + 0x1e, 0x42, 0x1f, 0x4e, 0x3c, 0x40, 0x22, 0x21, + 0x44, 0x21, 0x43, 0x26, 0x28, 0x27, 0x29, 0x23, + 0x2b, 0x4b, 0x2d, 0x46, 0x2f, 0x4c, 0x31, 0x4d, + 0x33, 0x47, 0x45, 0x99, 0x00, 0x00, 0x97, 0x91, + 0x7f, 0x80, 0x85, 0x86, 0x12, 0x82, 0x84, 0x78, + 0x79, 0x12, 0x7d, 0xa3, 0x7e, 0x7a, 0x7b, 0x8c, + 0x92, 0x98, 0xa6, 0xa0, 0x87, 0x00, 0x9a, 0xa1, + 0x95, 0x77, 0x33, 0x95, 0x00, 0x90, 0x00, 0x76, + 0x9b, 0x9a, 0x99, 0x98, 0x00, 0x00, 0xa0, 0x00, + 0x9e, 0x00, 0xa3, 0xa2, 0x15, 0x31, 0x32, 0x33, + 0xb7, 0xb8, 0x53, 0xac, 0xab, 0x12, 0x14, 0x1e, + 0x21, 0x22, 0x22, 0x2a, 0x34, 0x35, 0x00, 0xa8, + 0xa9, 0x39, 0x22, 0x4c, 0x00, 0x00, 0x97, 0x01, + 0x5a, 0xda, 0x1d, 0x36, 0x05, 0x00, 0xc7, 0xc6, + 0xc9, 0xc8, 0xcb, 0xca, 0xcd, 0xcc, 0xcf, 0xce, + 0xc4, 0xd8, 0x45, 0xd9, 0x42, 0xda, 0x46, 0xdb, + 0xd1, 0xd3, 0xd5, 0xd7, 0xdd, 0xdc, 0xf1, 0xf9, + 0x01, 0x11, 0x0a, 0x12, 0x80, 0x9f, 0x00, 0x21, + 0x80, 0xa3, 0xf0, 0x00, 0xc0, 0x40, 0xc6, 0x60, + 0xea, 0xde, 0xe6, 0x99, 0xc0, 0x00, 0x00, 0x06, + 0x60, 0xdf, 0x29, 0x00, 0x15, 0x12, 0x06, 0x16, + 0xfb, 0xe0, 0x09, 0x15, 0x12, 0x84, 0x0b, 0xc6, + 0x16, 0x02, 0xe2, 0x06, 0xc0, 0x40, 0x00, 0x46, + 0x60, 0xe1, 0xe3, 0x6d, 0x37, 0x38, 0x39, 0x18, + 0x17, 0x1a, 0x19, 0x00, 0x1d, 0x1c, 0x1f, 0x1e, + 0x00, 0x61, 0xba, 0x67, 0x45, 0x48, 0x00, 0x50, + 0x64, 0x4f, 0x51, 0x00, 0x00, 0x49, 0x00, 0x00, + 0x00, 0xa5, 0xa6, 0xa7, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xb9, 0x00, 0x00, 0x5c, 0x00, 0x4a, 0x00, + 0x5d, 0x57, 0x59, 0x62, 0x60, 0x72, 0x6b, 0x71, + 0x52, 0x00, 0x3e, 0x69, 0xbb, 0x00, 0x5b, 0x00, + 0x25, 0x00, 0x48, 0xaa, 0x8a, 0x8b, 0x8c, 0xab, + 0xac, 0x58, 0x58, 0xaf, 0x94, 0xb0, 0x6f, 0xb2, + 0x61, 0x60, 0x63, 0x62, 0x65, 0x64, 0x6a, 0x6b, + 0x6c, 0x6d, 0x66, 0x67, 0x68, 0x69, 0x6f, 0x6e, + 0x71, 0x70, 0x73, 0x72, 0x75, 0x74, 0x77, 0x76, + 0x79, 0x78, +}; + +static const uint16_t case_conv_ext[58] = { + 0x0399, 0x0308, 0x0301, 0x03a5, 0x0313, 0x0300, 0x0342, 0x0391, + 0x0397, 0x03a9, 0x0046, 0x0049, 0x004c, 0x0053, 0x0069, 0x0307, + 0x02bc, 0x004e, 0x004a, 0x030c, 0x0535, 0x0552, 0x0048, 0x0331, + 0x0054, 0x0057, 0x030a, 0x0059, 0x0041, 0x02be, 0x1f08, 0x1f80, + 0x1f28, 0x1f90, 0x1f68, 0x1fa0, 0x1fba, 0x0386, 0x1fb3, 0x1fca, + 0x0389, 0x1fc3, 0x03a1, 0x1ffa, 0x038f, 0x1ff3, 0x0544, 0x0546, + 0x053b, 0x054e, 0x053d, 0x03b8, 0x0462, 0xa64a, 0x1e60, 0x03c9, + 0x006b, 0x00e5, +}; + +static const uint8_t unicode_prop_Cased1_table[190] = { + 0x40, 0xa9, 0x80, 0x8e, 0x80, 0xfc, 0x80, 0xd3, + 0x80, 0x9b, 0x81, 0x8d, 0x02, 0x80, 0xe1, 0x80, + 0x91, 0x85, 0x9a, 0x01, 0x00, 0x01, 0x11, 0x03, + 0x04, 0x08, 0x01, 0x08, 0x30, 0x08, 0x01, 0x15, + 0x20, 0x01, 0x31, 0x99, 0x31, 0x9d, 0x84, 0x40, + 0x94, 0x80, 0xd6, 0x82, 0xa6, 0x80, 0x41, 0x62, + 0x80, 0xa6, 0x80, 0x4b, 0x72, 0x80, 0x4c, 0x02, + 0xf8, 0x02, 0x80, 0x8f, 0x80, 0xb0, 0x40, 0xdb, + 0x08, 0x80, 0x41, 0xd0, 0x80, 0x8c, 0x80, 0x8f, + 0x8c, 0xe4, 0x03, 0x01, 0x89, 0x00, 0x14, 0x28, + 0x10, 0x11, 0x02, 0x01, 0x18, 0x0b, 0x24, 0x4b, + 0x26, 0x01, 0x01, 0x86, 0xe5, 0x80, 0x60, 0x79, + 0xb6, 0x81, 0x40, 0x91, 0x81, 0xbd, 0x88, 0x94, + 0x05, 0x80, 0x98, 0x80, 0xc0, 0x1a, 0x82, 0x43, + 0x34, 0xa2, 0x06, 0x80, 0x8d, 0x60, 0x5c, 0x15, + 0x01, 0x10, 0xa9, 0x80, 0x88, 0x60, 0xcc, 0x44, + 0xd4, 0x80, 0xc6, 0x01, 0x08, 0x09, 0x0b, 0x80, + 0x8b, 0x00, 0x06, 0x80, 0xc0, 0x03, 0x0f, 0x06, + 0x80, 0x9b, 0x03, 0x04, 0x00, 0x16, 0x80, 0x41, + 0x53, 0x81, 0x98, 0x80, 0x98, 0x80, 0x9e, 0x80, + 0x98, 0x80, 0x9e, 0x80, 0x98, 0x80, 0x9e, 0x80, + 0x98, 0x80, 0x9e, 0x80, 0x98, 0x07, 0x47, 0x33, + 0x89, 0x80, 0x93, 0x2d, 0x41, 0x04, 0xbd, 0x50, + 0xc1, 0x99, 0x85, 0x99, 0x85, 0x99, +}; + +static const uint8_t unicode_prop_Cased1_index[18] = { + 0xb9, 0x02, 0x80, 0xa0, 0x1e, 0x40, 0x9e, 0xa6, + 0x40, 0x55, 0xd4, 0x21, 0x15, 0xd7, 0x21, 0x8a, + 0xf1, 0x01, +}; + +static const uint8_t unicode_prop_Case_Ignorable_table[785] = { + 0xa6, 0x05, 0x80, 0x8a, 0x80, 0xa2, 0x00, 0x80, + 0xc6, 0x03, 0x00, 0x03, 0x01, 0x81, 0x41, 0xf6, + 0x40, 0xbf, 0x19, 0x18, 0x88, 0x08, 0x80, 0x40, + 0xfa, 0x86, 0x40, 0xce, 0x04, 0x80, 0xb0, 0xac, + 0x00, 0x01, 0x01, 0x00, 0xab, 0x80, 0x8a, 0x85, + 0x89, 0x8a, 0x00, 0xa2, 0x80, 0x89, 0x94, 0x8f, + 0x80, 0xe4, 0x38, 0x89, 0x03, 0xa0, 0x00, 0x80, + 0x9d, 0x9a, 0xda, 0x8a, 0xb9, 0x8a, 0x18, 0x08, + 0x97, 0x97, 0xaa, 0x82, 0xab, 0x06, 0x0c, 0x88, + 0xa8, 0xb9, 0xb6, 0x00, 0x03, 0x3b, 0x02, 0x86, + 0x89, 0x81, 0x8c, 0x80, 0x8e, 0x80, 0xb9, 0x03, + 0x1f, 0x80, 0x93, 0x81, 0x99, 0x01, 0x81, 0xb8, + 0x03, 0x0b, 0x09, 0x12, 0x80, 0x9d, 0x0a, 0x80, + 0x8a, 0x81, 0xb8, 0x03, 0x20, 0x0b, 0x80, 0x93, + 0x81, 0x95, 0x28, 0x80, 0xb9, 0x01, 0x00, 0x1f, + 0x06, 0x81, 0x8a, 0x81, 0x9d, 0x80, 0xbc, 0x80, + 0x8b, 0x80, 0xb1, 0x02, 0x80, 0xb6, 0x00, 0x14, + 0x10, 0x1e, 0x81, 0x8a, 0x81, 0x9c, 0x80, 0xb9, + 0x01, 0x05, 0x04, 0x81, 0x93, 0x81, 0x9b, 0x81, + 0xb8, 0x0b, 0x1f, 0x80, 0x93, 0x81, 0x9c, 0x80, + 0xc7, 0x06, 0x10, 0x80, 0xd9, 0x01, 0x86, 0x8a, + 0x88, 0xe1, 0x01, 0x88, 0x88, 0x00, 0x86, 0xc8, + 0x81, 0x9a, 0x00, 0x00, 0x80, 0xb6, 0x8d, 0x04, + 0x01, 0x84, 0x8a, 0x80, 0xa3, 0x88, 0x80, 0xe5, + 0x18, 0x28, 0x09, 0x81, 0x98, 0x0b, 0x82, 0x8f, + 0x83, 0x8c, 0x01, 0x0d, 0x80, 0x8e, 0x80, 0xdd, + 0x80, 0x42, 0x5f, 0x82, 0x43, 0xb1, 0x82, 0x9c, + 0x81, 0x9d, 0x81, 0x9d, 0x81, 0xbf, 0x08, 0x37, + 0x01, 0x8a, 0x10, 0x20, 0xac, 0x84, 0xb2, 0x80, + 0xc0, 0x81, 0xa1, 0x80, 0xf5, 0x13, 0x81, 0x88, + 0x05, 0x82, 0x40, 0xda, 0x09, 0x80, 0xb9, 0x00, + 0x30, 0x00, 0x01, 0x3d, 0x89, 0x08, 0xa6, 0x07, + 0xad, 0x81, 0x8b, 0x93, 0x83, 0xaf, 0x00, 0x20, + 0x04, 0x80, 0xa7, 0x88, 0x8b, 0x81, 0x9f, 0x19, + 0x08, 0x82, 0xb7, 0x00, 0x0a, 0x00, 0x82, 0xb9, + 0x39, 0x81, 0xbf, 0x85, 0xd1, 0x10, 0x8c, 0x06, + 0x18, 0x28, 0x11, 0xb1, 0xbe, 0x8c, 0x80, 0xa1, + 0xe4, 0x41, 0xbc, 0x00, 0x82, 0x8a, 0x82, 0x8c, + 0x82, 0x8c, 0x82, 0x8c, 0x81, 0x8b, 0x27, 0x81, + 0x89, 0x01, 0x01, 0x84, 0xb0, 0x20, 0x89, 0x00, + 0x8c, 0x80, 0x8f, 0x8c, 0xb2, 0xa0, 0x4b, 0x8a, + 0x81, 0xf0, 0x82, 0xfc, 0x80, 0x8e, 0x80, 0xdf, + 0x9f, 0xae, 0x80, 0x41, 0xd4, 0x80, 0xa3, 0x1a, + 0x24, 0x80, 0xdc, 0x85, 0xdc, 0x82, 0x60, 0x6f, + 0x15, 0x80, 0x44, 0xe1, 0x85, 0x41, 0x0d, 0x80, + 0xe1, 0x18, 0x89, 0x00, 0x9b, 0x83, 0xcf, 0x81, + 0x8d, 0xa1, 0xcd, 0x80, 0x96, 0x82, 0xe5, 0x1a, + 0x0f, 0x02, 0x03, 0x80, 0x98, 0x0c, 0x80, 0x40, + 0x96, 0x81, 0x99, 0x91, 0x8c, 0x80, 0xa5, 0x87, + 0x98, 0x8a, 0xad, 0x82, 0xaf, 0x01, 0x19, 0x81, + 0x90, 0x80, 0x94, 0x81, 0xc1, 0x29, 0x09, 0x81, + 0x8b, 0x07, 0x80, 0xa2, 0x80, 0x8a, 0x80, 0xb2, + 0x00, 0x11, 0x0c, 0x08, 0x80, 0x9a, 0x80, 0x8d, + 0x0c, 0x08, 0x80, 0xe3, 0x84, 0x88, 0x82, 0xf8, + 0x01, 0x03, 0x80, 0x60, 0x4f, 0x2f, 0x80, 0x40, + 0x92, 0x90, 0x42, 0x3c, 0x8f, 0x10, 0x8b, 0x8f, + 0xa1, 0x01, 0x80, 0x40, 0xa8, 0x06, 0x05, 0x80, + 0x8a, 0x80, 0xa2, 0x00, 0x80, 0xae, 0x80, 0xac, + 0x81, 0xc2, 0x80, 0x94, 0x82, 0x42, 0x00, 0x80, + 0x40, 0xe1, 0x80, 0x40, 0x94, 0x84, 0x44, 0x04, + 0x28, 0xa9, 0x80, 0x88, 0x42, 0x45, 0x10, 0x0c, + 0x83, 0xa7, 0x13, 0x80, 0x40, 0xa4, 0x81, 0x42, + 0x3c, 0x83, 0xa5, 0x80, 0x99, 0x20, 0x80, 0x41, + 0x3a, 0x81, 0x97, 0x80, 0xb3, 0x85, 0xc5, 0x8a, + 0xb0, 0x83, 0xfa, 0x80, 0xb5, 0x8e, 0xa8, 0x01, + 0x81, 0x89, 0x82, 0xb0, 0x19, 0x09, 0x03, 0x80, + 0x89, 0x80, 0xb1, 0x82, 0xa3, 0x20, 0x87, 0xbd, + 0x80, 0x8b, 0x81, 0xb3, 0x88, 0x89, 0x19, 0x80, + 0xde, 0x11, 0x00, 0x0d, 0x01, 0x80, 0x40, 0x9c, + 0x02, 0x87, 0x94, 0x81, 0xb8, 0x0a, 0x80, 0xa4, + 0x32, 0x84, 0xc5, 0x85, 0x8c, 0x00, 0x00, 0x80, + 0x8d, 0x81, 0xd4, 0x39, 0x10, 0x80, 0x96, 0x80, + 0xd3, 0x28, 0x03, 0x08, 0x81, 0x40, 0xed, 0x1d, + 0x08, 0x81, 0x9a, 0x81, 0xd4, 0x39, 0x00, 0x81, + 0xe9, 0x00, 0x01, 0x28, 0x80, 0xe4, 0x00, 0x01, + 0x18, 0x84, 0x41, 0x02, 0x88, 0x01, 0x40, 0xff, + 0x08, 0x03, 0x80, 0x40, 0x8f, 0x19, 0x0b, 0x80, + 0x9f, 0x89, 0xa7, 0x29, 0x1f, 0x80, 0x88, 0x29, + 0x82, 0xad, 0x8c, 0x01, 0x40, 0xc5, 0x00, 0x10, + 0x80, 0x40, 0xc8, 0x30, 0x28, 0x80, 0xd1, 0x95, + 0x0e, 0x01, 0x01, 0xf9, 0x2a, 0x00, 0x08, 0x30, + 0x80, 0xc7, 0x0a, 0x00, 0x80, 0xc0, 0x80, 0x41, + 0x18, 0x81, 0x8a, 0x81, 0xb3, 0x24, 0x00, 0x80, + 0x96, 0x80, 0x54, 0xd4, 0x90, 0x85, 0x8e, 0x60, + 0x2c, 0xc7, 0x8b, 0x12, 0x49, 0xbf, 0x84, 0xba, + 0x86, 0x88, 0x83, 0x41, 0xfb, 0x82, 0xa7, 0x81, + 0x41, 0xe1, 0x80, 0xbe, 0x90, 0xbf, 0x08, 0x81, + 0x8c, 0x81, 0x60, 0x3f, 0xfb, 0x18, 0x30, 0x81, + 0x4c, 0x9d, 0x08, 0x83, 0x52, 0x5b, 0xad, 0x81, + 0x96, 0x42, 0x1f, 0x82, 0x88, 0x8f, 0x0e, 0x9d, + 0x83, 0x40, 0x93, 0x82, 0x47, 0xba, 0xb6, 0x83, + 0xb1, 0x38, 0x8d, 0x80, 0x95, 0x20, 0x8e, 0x45, + 0x4f, 0x30, 0x90, 0x0e, 0x01, 0x04, 0x84, 0xbd, + 0xa0, 0x80, 0x40, 0x9f, 0x8d, 0x41, 0x6f, 0x80, + 0xbc, 0x83, 0x41, 0xfa, 0x84, 0x40, 0xfd, 0x81, + 0x40, 0xf2, 0x01, 0x06, 0x0c, 0x80, 0x88, 0x80, + 0x41, 0xcf, 0x86, 0xec, 0x87, 0x4a, 0xae, 0x84, + 0x6c, 0x0c, 0x00, 0x80, 0x9d, 0xdf, 0xff, 0x40, + 0xef, +}; + +static const uint8_t unicode_prop_Case_Ignorable_index[75] = { + 0xbe, 0x05, 0x00, 0xfe, 0x07, 0x00, 0x52, 0x0a, + 0xa0, 0xc1, 0x0b, 0x00, 0x82, 0x0d, 0x00, 0x3f, + 0x10, 0x80, 0xd4, 0x17, 0x40, 0xde, 0x1a, 0x20, + 0xe9, 0x1c, 0x00, 0x72, 0x20, 0x00, 0x16, 0xa0, + 0x40, 0xc6, 0xa8, 0x40, 0xc2, 0xaa, 0xa0, 0x30, + 0xfe, 0x00, 0xb1, 0x07, 0x41, 0x51, 0x0f, 0x01, + 0xd0, 0x11, 0x01, 0x5f, 0x14, 0x01, 0x44, 0x19, + 0x61, 0xa8, 0x1c, 0x01, 0x2a, 0x61, 0x61, 0xff, + 0xaf, 0x01, 0x19, 0xe0, 0x61, 0x00, 0xe7, 0x01, + 0xf0, 0x01, 0x0e, +}; + +static const uint8_t unicode_prop_ID_Start_table[1146] = { + 0xc0, 0x99, 0x85, 0x99, 0xae, 0x80, 0x89, 0x03, + 0x04, 0x96, 0x80, 0x9e, 0x80, 0x41, 0xc9, 0x83, + 0x8b, 0x8d, 0x26, 0x00, 0x80, 0x40, 0x80, 0x20, + 0x09, 0x18, 0x05, 0x00, 0x10, 0x00, 0x93, 0x80, + 0xd2, 0x80, 0x40, 0x8a, 0x87, 0x40, 0xa5, 0x80, + 0xa5, 0x08, 0x85, 0xa8, 0xc6, 0x9a, 0x1b, 0xac, + 0xaa, 0xa2, 0x08, 0xe2, 0x00, 0x8e, 0x0e, 0x81, + 0x89, 0x11, 0x80, 0x8f, 0x00, 0x9d, 0x9c, 0xd8, + 0x8a, 0x80, 0x97, 0xa0, 0x88, 0x0b, 0x04, 0x95, + 0x18, 0x88, 0x02, 0x80, 0x96, 0x98, 0x86, 0x8a, + 0x84, 0x97, 0x06, 0x8f, 0xa9, 0xb9, 0xb5, 0x10, + 0x91, 0x06, 0x89, 0x8e, 0x8f, 0x1f, 0x09, 0x81, + 0x95, 0x06, 0x00, 0x13, 0x10, 0x8f, 0x80, 0x8c, + 0x08, 0x82, 0x8d, 0x81, 0x89, 0x07, 0x2b, 0x09, + 0x95, 0x06, 0x01, 0x01, 0x01, 0x9e, 0x18, 0x80, + 0x92, 0x82, 0x8f, 0x88, 0x02, 0x80, 0x95, 0x06, + 0x01, 0x04, 0x10, 0x91, 0x80, 0x8e, 0x81, 0x96, + 0x80, 0x8a, 0x39, 0x09, 0x95, 0x06, 0x01, 0x04, + 0x10, 0x9d, 0x08, 0x82, 0x8e, 0x80, 0x90, 0x00, + 0x2a, 0x10, 0x1a, 0x08, 0x00, 0x0a, 0x0a, 0x12, + 0x8b, 0x95, 0x80, 0xb3, 0x38, 0x10, 0x96, 0x80, + 0x8f, 0x10, 0x99, 0x10, 0x09, 0x81, 0x9d, 0x03, + 0x38, 0x10, 0x96, 0x80, 0x89, 0x04, 0x10, 0x9d, + 0x10, 0x81, 0x8e, 0x81, 0x90, 0x88, 0x02, 0x80, + 0xa8, 0x08, 0x8f, 0x04, 0x17, 0x82, 0x97, 0x2c, + 0x91, 0x82, 0x97, 0x80, 0x88, 0x00, 0x0e, 0xb9, + 0xaf, 0x01, 0x8b, 0x86, 0xb9, 0x08, 0x00, 0x20, + 0x97, 0x00, 0x80, 0x89, 0x01, 0x88, 0x01, 0x20, + 0x80, 0x94, 0x83, 0x9f, 0x80, 0xbe, 0x38, 0xa3, + 0x9a, 0x84, 0xf2, 0xaa, 0x93, 0x80, 0x8f, 0x2b, + 0x1a, 0x02, 0x0e, 0x13, 0x8c, 0x8b, 0x80, 0x90, + 0xa5, 0x00, 0x20, 0x81, 0xaa, 0x80, 0x41, 0x4c, + 0x03, 0x0e, 0x00, 0x03, 0x81, 0xa8, 0x03, 0x81, + 0xa0, 0x03, 0x0e, 0x00, 0x03, 0x81, 0x8e, 0x80, + 0xb8, 0x03, 0x81, 0xc2, 0xa4, 0x8f, 0x8f, 0xd5, + 0x0d, 0x82, 0x42, 0x6b, 0x81, 0x90, 0x80, 0x99, + 0x84, 0xca, 0x82, 0x8a, 0x86, 0x91, 0x8c, 0x92, + 0x8d, 0x91, 0x8d, 0x8c, 0x02, 0x8e, 0xb3, 0xa2, + 0x03, 0x80, 0xc2, 0xd8, 0x86, 0xa8, 0x00, 0x84, + 0xc5, 0x89, 0x9e, 0xb0, 0x9d, 0x0c, 0x8a, 0xab, + 0x83, 0x99, 0xb5, 0x96, 0x88, 0xb4, 0xd1, 0x80, + 0xdc, 0xae, 0x90, 0x87, 0xb5, 0x9d, 0x8c, 0x81, + 0x89, 0xab, 0x99, 0xa3, 0xa8, 0x82, 0x89, 0xa3, + 0x81, 0x8a, 0x84, 0xaa, 0x0a, 0xa8, 0x18, 0x28, + 0x0a, 0x04, 0x40, 0xbf, 0xbf, 0x41, 0x15, 0x0d, + 0x81, 0xa5, 0x0d, 0x0f, 0x00, 0x00, 0x00, 0x80, + 0x9e, 0x81, 0xb4, 0x06, 0x00, 0x12, 0x06, 0x13, + 0x0d, 0x83, 0x8c, 0x22, 0x06, 0xf3, 0x80, 0x8c, + 0x80, 0x8f, 0x8c, 0xe4, 0x03, 0x01, 0x89, 0x00, + 0x0d, 0x28, 0x00, 0x00, 0x80, 0x8f, 0x0b, 0x24, + 0x18, 0x90, 0xa8, 0x4a, 0x76, 0x40, 0xe4, 0x2b, + 0x11, 0x8b, 0xa5, 0x00, 0x20, 0x81, 0xb7, 0x30, + 0x8f, 0x96, 0x88, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x86, 0x42, 0x25, 0x82, 0x98, 0x88, + 0x34, 0x0c, 0x83, 0xd5, 0x1c, 0x80, 0xd9, 0x03, + 0x84, 0xaa, 0x80, 0xdd, 0x90, 0x9f, 0xaf, 0x8f, + 0x41, 0xff, 0x59, 0xbf, 0xbf, 0x60, 0x56, 0x8c, + 0xc2, 0xad, 0x81, 0x41, 0x0c, 0x82, 0x8f, 0x89, + 0x81, 0x93, 0xae, 0x8f, 0x9e, 0x81, 0xcf, 0xa6, + 0x88, 0x81, 0xe6, 0x81, 0xd1, 0x93, 0x90, 0x02, + 0x03, 0x80, 0x96, 0x9c, 0xb3, 0x8d, 0xb1, 0xbd, + 0x2a, 0x00, 0x81, 0x8a, 0x9b, 0x89, 0x96, 0x98, + 0x9c, 0x86, 0xae, 0x9b, 0x80, 0x8f, 0x20, 0x89, + 0x89, 0x20, 0xa8, 0x96, 0x10, 0x87, 0x93, 0x96, + 0x10, 0x82, 0xb1, 0x00, 0x11, 0x0c, 0x08, 0x00, + 0x97, 0x11, 0x8a, 0x32, 0x8b, 0x29, 0x29, 0x85, + 0x88, 0x30, 0x30, 0xaa, 0x80, 0x8d, 0x85, 0xf2, + 0x9c, 0x60, 0x2b, 0xa3, 0x8b, 0x96, 0x83, 0xb0, + 0x60, 0x21, 0x03, 0x41, 0x6d, 0x81, 0xe9, 0xa5, + 0x86, 0x8b, 0x24, 0x00, 0x89, 0x80, 0x8c, 0x04, + 0x00, 0x01, 0x01, 0x80, 0xeb, 0xa0, 0x41, 0x6a, + 0x91, 0xbf, 0x81, 0xb5, 0xa7, 0x8b, 0xf3, 0x20, + 0x40, 0x86, 0xa3, 0x99, 0x85, 0x99, 0x8a, 0xd8, + 0x15, 0x0d, 0x0d, 0x0a, 0xa2, 0x8b, 0x80, 0x99, + 0x80, 0x92, 0x01, 0x80, 0x8e, 0x81, 0x8d, 0xa1, + 0xfa, 0xc4, 0xb4, 0x41, 0x0a, 0x9c, 0x82, 0xb0, + 0xae, 0x9f, 0x8c, 0x9d, 0x84, 0xa5, 0x89, 0x9d, + 0x81, 0xa3, 0x1f, 0x04, 0xa9, 0x40, 0x9d, 0x91, + 0xa3, 0x83, 0xa3, 0x83, 0xa7, 0x87, 0xb3, 0x8b, + 0x8a, 0x80, 0x8e, 0x06, 0x01, 0x80, 0x8a, 0x80, + 0x8e, 0x06, 0x01, 0x82, 0xb3, 0x8b, 0x41, 0x36, + 0x88, 0x95, 0x89, 0x87, 0x97, 0x28, 0xa9, 0x80, + 0x88, 0xc4, 0x29, 0x00, 0xab, 0x01, 0x10, 0x81, + 0x96, 0x89, 0x96, 0x88, 0x9e, 0xc0, 0x92, 0x01, + 0x89, 0x95, 0x89, 0x99, 0x85, 0x99, 0xa5, 0xb7, + 0x29, 0xbf, 0x80, 0x8e, 0x18, 0x10, 0x9c, 0xa9, + 0x9c, 0x82, 0x9c, 0xa2, 0x38, 0x9b, 0x9a, 0xb5, + 0x89, 0x95, 0x89, 0x92, 0x8c, 0x91, 0xed, 0xc8, + 0xb6, 0xb2, 0x8c, 0xb2, 0x8c, 0xa3, 0xa5, 0x9b, + 0x88, 0x96, 0x40, 0xf9, 0xa9, 0x29, 0x8f, 0x85, + 0xb7, 0x9c, 0x89, 0x07, 0x95, 0xa9, 0x91, 0xad, + 0x94, 0x9a, 0x96, 0x8b, 0xb4, 0xb8, 0x09, 0x80, + 0x8c, 0xac, 0x9f, 0x98, 0x99, 0xa3, 0x9c, 0x01, + 0x07, 0xa2, 0x10, 0x8b, 0xaf, 0x8d, 0x83, 0x94, + 0x00, 0x80, 0xa2, 0x91, 0x80, 0x98, 0x92, 0x81, + 0xbe, 0x30, 0x00, 0x18, 0x8e, 0x80, 0x89, 0x86, + 0xae, 0xa5, 0x39, 0x09, 0x95, 0x06, 0x01, 0x04, + 0x10, 0x91, 0x80, 0x8b, 0x84, 0x9d, 0x89, 0x00, + 0x08, 0x80, 0xa5, 0x00, 0x98, 0x00, 0x80, 0xab, + 0xb4, 0x91, 0x83, 0x93, 0x82, 0x9d, 0xaf, 0x93, + 0x08, 0x80, 0x40, 0xb7, 0xae, 0xa8, 0x83, 0xa3, + 0xaf, 0x93, 0x80, 0xba, 0xaa, 0x8c, 0x80, 0xc6, + 0x9a, 0xa4, 0x86, 0x40, 0xb8, 0xab, 0xf3, 0xbf, + 0x9e, 0x39, 0x01, 0x38, 0x08, 0x97, 0x8e, 0x00, + 0x80, 0xdd, 0x39, 0xa6, 0x8f, 0x00, 0x80, 0x9b, + 0x80, 0x89, 0xa7, 0x30, 0x94, 0x80, 0x8a, 0xad, + 0x92, 0x80, 0x91, 0xc8, 0x40, 0xc6, 0xa0, 0x9e, + 0x88, 0x80, 0xa4, 0x90, 0x80, 0xb0, 0x9d, 0xef, + 0x30, 0x08, 0xa5, 0x94, 0x80, 0x98, 0x28, 0x08, + 0x9f, 0x8d, 0x80, 0x96, 0xab, 0x41, 0x03, 0x92, + 0x8e, 0x00, 0x8c, 0x80, 0xa1, 0xfb, 0x80, 0xce, + 0x43, 0x99, 0xe5, 0xee, 0x90, 0x40, 0xc3, 0x4a, + 0x4b, 0xe0, 0x8e, 0x44, 0x2f, 0x90, 0x85, 0x98, + 0x4f, 0x9a, 0x84, 0x42, 0x46, 0x5a, 0xb8, 0x9d, + 0x46, 0xe1, 0x42, 0x38, 0x86, 0x9e, 0x90, 0xce, + 0x90, 0x9d, 0x91, 0xaf, 0x8f, 0x83, 0x9e, 0x94, + 0x84, 0x92, 0x41, 0xaf, 0xac, 0x40, 0xd2, 0xbf, + 0x9f, 0x98, 0x81, 0x98, 0xab, 0xca, 0x20, 0xc1, + 0x8c, 0xbf, 0x08, 0x80, 0x8d, 0x84, 0x88, 0x5c, + 0xd5, 0xa8, 0x9f, 0xe0, 0xf2, 0x60, 0x21, 0xfc, + 0x18, 0x30, 0x08, 0x41, 0x22, 0x8e, 0x80, 0x9c, + 0x11, 0x80, 0x8d, 0x1f, 0x41, 0x8b, 0x49, 0x03, + 0xea, 0x84, 0x8c, 0x82, 0x88, 0x86, 0x89, 0x57, + 0x65, 0xd4, 0x80, 0xc6, 0x01, 0x08, 0x09, 0x0b, + 0x80, 0x8b, 0x00, 0x06, 0x80, 0xc0, 0x03, 0x0f, + 0x06, 0x80, 0x9b, 0x03, 0x04, 0x00, 0x16, 0x80, + 0x41, 0x53, 0x81, 0x98, 0x80, 0x98, 0x80, 0x9e, + 0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, 0x80, 0x9e, + 0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, 0x07, 0x47, + 0x33, 0x9e, 0x2d, 0x41, 0x04, 0xbd, 0x40, 0x91, + 0xac, 0x89, 0x86, 0x8f, 0x80, 0x41, 0x40, 0x9d, + 0x91, 0xab, 0x41, 0xe3, 0x9b, 0x40, 0xe3, 0x9d, + 0x08, 0x40, 0xce, 0x9e, 0x02, 0x01, 0x06, 0x0c, + 0x88, 0x81, 0x40, 0xdf, 0x30, 0x18, 0x08, 0x8e, + 0x80, 0x40, 0xc4, 0xba, 0xc3, 0x30, 0x44, 0xb3, + 0x18, 0x9a, 0x01, 0x00, 0x08, 0x80, 0x89, 0x03, + 0x00, 0x00, 0x28, 0x18, 0x00, 0x00, 0x02, 0x01, + 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x0b, 0x06, 0x03, 0x03, 0x00, 0x80, 0x89, 0x80, + 0x90, 0x22, 0x04, 0x80, 0x90, 0x51, 0x43, 0x60, + 0xa6, 0xdf, 0x9f, 0x51, 0x1d, 0x81, 0x56, 0x8d, + 0x81, 0x5d, 0x30, 0x8e, 0x42, 0x6d, 0x49, 0xa1, + 0x42, 0x1d, 0x45, 0xe1, 0x53, 0x4a, 0x84, 0x60, + 0x21, 0x29, +}; + +static const uint8_t unicode_prop_ID_Start_index[108] = { + 0xf6, 0x03, 0x20, 0xa6, 0x07, 0x00, 0xa9, 0x09, + 0x20, 0xb1, 0x0a, 0x00, 0xba, 0x0b, 0x20, 0x3b, + 0x0d, 0x20, 0xc7, 0x0e, 0x20, 0x49, 0x12, 0x00, + 0x9b, 0x16, 0x00, 0xac, 0x19, 0x00, 0xc0, 0x1d, + 0x80, 0x80, 0x20, 0x20, 0x70, 0x2d, 0x00, 0x00, + 0x32, 0x00, 0x06, 0xa8, 0x00, 0x77, 0xaa, 0x00, + 0xfc, 0xd7, 0x00, 0xfd, 0xfe, 0x40, 0xd1, 0x02, + 0x01, 0xb2, 0x05, 0x21, 0xf6, 0x08, 0x01, 0x49, + 0x0c, 0x01, 0x76, 0x10, 0x01, 0xdf, 0x12, 0x21, + 0xc8, 0x14, 0x41, 0x42, 0x19, 0x21, 0x31, 0x1d, + 0x61, 0xf1, 0x2f, 0x41, 0x78, 0x6b, 0x01, 0x23, + 0xb1, 0xa1, 0xad, 0xd4, 0x01, 0x6f, 0xd7, 0x01, + 0xee, 0xe5, 0x01, 0x38, 0xee, 0x01, 0xe0, 0xa6, + 0x42, 0x7a, 0x34, 0x03, +}; + +static const uint8_t unicode_prop_ID_Continue1_table[708] = { + 0xaf, 0x89, 0xa4, 0x80, 0xd6, 0x80, 0x42, 0x47, + 0xef, 0x96, 0x80, 0x40, 0xfa, 0x84, 0x41, 0x08, + 0xac, 0x00, 0x01, 0x01, 0x00, 0xc7, 0x8a, 0xaf, + 0x9e, 0x28, 0xe4, 0x31, 0x29, 0x08, 0x19, 0x89, + 0x96, 0x80, 0x9d, 0x9a, 0xda, 0x8a, 0x8e, 0x89, + 0xa0, 0x88, 0x88, 0x80, 0x97, 0x18, 0x88, 0x02, + 0x04, 0xaa, 0x82, 0xba, 0x88, 0xa9, 0x97, 0x80, + 0xa0, 0xb5, 0x10, 0x91, 0x06, 0x89, 0x09, 0x89, + 0x90, 0x82, 0xb7, 0x00, 0x31, 0x09, 0x82, 0x88, + 0x80, 0x89, 0x09, 0x89, 0x8d, 0x01, 0x82, 0xb7, + 0x00, 0x23, 0x09, 0x12, 0x80, 0x93, 0x8b, 0x10, + 0x8a, 0x82, 0xb7, 0x00, 0x38, 0x10, 0x82, 0x93, + 0x09, 0x89, 0x89, 0x28, 0x82, 0xb7, 0x00, 0x31, + 0x09, 0x16, 0x82, 0x89, 0x09, 0x89, 0x91, 0x80, + 0xba, 0x22, 0x10, 0x83, 0x88, 0x80, 0x8d, 0x89, + 0x8f, 0x84, 0xb6, 0x00, 0x30, 0x10, 0x1e, 0x81, + 0x8a, 0x09, 0x89, 0x90, 0x82, 0xb7, 0x00, 0x30, + 0x10, 0x1e, 0x81, 0x8a, 0x09, 0x89, 0x10, 0x8b, + 0x83, 0xb6, 0x08, 0x30, 0x10, 0x83, 0x88, 0x80, + 0x89, 0x09, 0x89, 0x90, 0x82, 0xc5, 0x03, 0x28, + 0x00, 0x3d, 0x89, 0x09, 0xbc, 0x01, 0x86, 0x8b, + 0x38, 0x89, 0xd6, 0x01, 0x88, 0x8a, 0x30, 0x89, + 0xbd, 0x0d, 0x89, 0x8a, 0x00, 0x00, 0x03, 0x81, + 0xb0, 0x93, 0x01, 0x84, 0x8a, 0x80, 0xa3, 0x88, + 0x80, 0xe3, 0x93, 0x80, 0x89, 0x8b, 0x1b, 0x10, + 0x11, 0x32, 0x83, 0x8c, 0x8b, 0x80, 0x8e, 0x42, + 0xbe, 0x82, 0x88, 0x88, 0x43, 0x9f, 0x83, 0x9b, + 0x82, 0x9c, 0x81, 0x9d, 0x81, 0xbf, 0x9f, 0x88, + 0x01, 0x89, 0xa0, 0x10, 0x8a, 0x40, 0x8e, 0x80, + 0xf5, 0x8b, 0x83, 0x8b, 0x89, 0x89, 0xff, 0x8a, + 0xbb, 0x84, 0xb8, 0x89, 0x80, 0x9c, 0x81, 0x8a, + 0x85, 0x89, 0x95, 0x8d, 0x80, 0x9e, 0x81, 0x8b, + 0x93, 0x84, 0xae, 0x90, 0x8a, 0x89, 0x90, 0x88, + 0x8b, 0x82, 0x9d, 0x8c, 0x81, 0x89, 0xab, 0x8d, + 0xaf, 0x93, 0x87, 0x89, 0x85, 0x89, 0xf5, 0x10, + 0x94, 0x18, 0x28, 0x0a, 0x40, 0xc5, 0xbf, 0x42, + 0x0b, 0x81, 0xb0, 0x81, 0x92, 0x80, 0xfa, 0x8c, + 0x18, 0x82, 0x8b, 0x4b, 0xfd, 0x82, 0x40, 0x8c, + 0x80, 0xdf, 0x9f, 0x42, 0x29, 0x85, 0xe8, 0x81, + 0xdf, 0x80, 0x60, 0x75, 0x23, 0x89, 0xc4, 0x03, + 0x89, 0x9f, 0x81, 0xcf, 0x81, 0x41, 0x0f, 0x02, + 0x03, 0x80, 0x96, 0x23, 0x80, 0xd2, 0x81, 0xb1, + 0x91, 0x89, 0x89, 0x85, 0x91, 0x8c, 0x8a, 0x9b, + 0x87, 0x98, 0x8c, 0xab, 0x83, 0xae, 0x8d, 0x8e, + 0x89, 0x8a, 0x80, 0x89, 0x89, 0xae, 0x8d, 0x8b, + 0x07, 0x09, 0x89, 0xa0, 0x82, 0xb1, 0x00, 0x11, + 0x0c, 0x08, 0x80, 0xa8, 0x24, 0x81, 0x40, 0xeb, + 0x38, 0x09, 0x89, 0x60, 0x4f, 0x23, 0x80, 0x42, + 0xe0, 0x8f, 0x8f, 0x8f, 0x11, 0x97, 0x82, 0x40, + 0xbf, 0x89, 0xa4, 0x80, 0xa4, 0x80, 0x42, 0x96, + 0x80, 0x40, 0xe1, 0x80, 0x40, 0x94, 0x84, 0x41, + 0x24, 0x89, 0x45, 0x56, 0x10, 0x0c, 0x83, 0xa7, + 0x13, 0x80, 0x40, 0xa4, 0x81, 0x42, 0x3c, 0x1f, + 0x89, 0x85, 0x89, 0x9e, 0x84, 0x41, 0x3c, 0x81, + 0xcc, 0x85, 0xc5, 0x8a, 0xb0, 0x83, 0xf9, 0x82, + 0xb4, 0x8e, 0x9e, 0x8a, 0x09, 0x89, 0x83, 0xac, + 0x8a, 0x30, 0xac, 0x89, 0x2a, 0xa3, 0x8d, 0x80, + 0x89, 0x21, 0xab, 0x80, 0x8b, 0x82, 0xaf, 0x8d, + 0x3b, 0x80, 0x8b, 0xd1, 0x8b, 0x28, 0x08, 0x40, + 0x9c, 0x8b, 0x84, 0x89, 0x2b, 0xb6, 0x08, 0x31, + 0x09, 0x82, 0x88, 0x80, 0x89, 0x09, 0x32, 0x84, + 0xc2, 0x88, 0x00, 0x08, 0x03, 0x04, 0x00, 0x8d, + 0x81, 0xd1, 0x91, 0x88, 0x89, 0x18, 0xd0, 0x93, + 0x8b, 0x89, 0x40, 0xd4, 0x31, 0x88, 0x9a, 0x81, + 0xd1, 0x90, 0x8e, 0x89, 0xd0, 0x8c, 0x87, 0x89, + 0x85, 0x93, 0xb8, 0x8e, 0x83, 0x89, 0x40, 0xf1, + 0x8e, 0x40, 0xa4, 0x89, 0xc5, 0x28, 0x09, 0x18, + 0x00, 0x81, 0x8b, 0x89, 0xf6, 0x31, 0x32, 0x80, + 0x9b, 0x89, 0xa7, 0x30, 0x1f, 0x80, 0x88, 0x8a, + 0xad, 0x8f, 0x40, 0xc5, 0x87, 0x40, 0x87, 0x89, + 0xb4, 0x38, 0x87, 0x8f, 0x89, 0xb7, 0x95, 0x80, + 0x8d, 0xf9, 0x2a, 0x00, 0x08, 0x30, 0x07, 0x89, + 0xaf, 0x20, 0x08, 0x27, 0x89, 0xb5, 0x89, 0x41, + 0x08, 0x83, 0x88, 0x08, 0x80, 0xaf, 0x32, 0x84, + 0x8c, 0x8a, 0x54, 0xe4, 0x05, 0x8e, 0x60, 0x2c, + 0xc7, 0x9b, 0x49, 0x25, 0x89, 0xd5, 0x89, 0xa5, + 0x84, 0xba, 0x86, 0x98, 0x89, 0x42, 0x15, 0x89, + 0x41, 0xd4, 0x00, 0xb6, 0x33, 0xd0, 0x80, 0x8a, + 0x81, 0x60, 0x4c, 0xaa, 0x81, 0x50, 0x50, 0x89, + 0x42, 0x05, 0xad, 0x81, 0x96, 0x42, 0x1d, 0x22, + 0x2f, 0x39, 0x86, 0x9d, 0x83, 0x40, 0x93, 0x82, + 0x45, 0x88, 0xb1, 0x41, 0xff, 0xb6, 0x83, 0xb1, + 0x38, 0x8d, 0x80, 0x95, 0x20, 0x8e, 0x45, 0x4f, + 0x30, 0x90, 0x0e, 0x01, 0x04, 0xe3, 0x80, 0x40, + 0x9f, 0x86, 0x88, 0x89, 0x41, 0x63, 0x80, 0xbc, + 0x8d, 0x41, 0xf1, 0x8d, 0x40, 0xf3, 0x08, 0x89, + 0x40, 0xe7, 0x01, 0x06, 0x0c, 0x80, 0x41, 0xd9, + 0x86, 0xec, 0x34, 0x89, 0x52, 0x95, 0x89, 0x6c, + 0x05, 0x05, 0x40, 0xef, +}; + +static const uint8_t unicode_prop_ID_Continue1_index[66] = { + 0xfa, 0x06, 0x00, 0x70, 0x09, 0x00, 0xf0, 0x0a, + 0x40, 0x57, 0x0c, 0x00, 0xf0, 0x0d, 0x60, 0xc7, + 0x0f, 0x20, 0xea, 0x17, 0x40, 0xec, 0x1a, 0x00, + 0x0e, 0x20, 0x40, 0x7e, 0xa6, 0x20, 0xda, 0xa9, + 0x20, 0x10, 0xfe, 0x40, 0x40, 0x0a, 0x41, 0xbb, + 0x10, 0x21, 0x4e, 0x13, 0x41, 0xde, 0x15, 0x01, + 0xe5, 0x19, 0x01, 0x5a, 0x1d, 0x01, 0xf5, 0x6a, + 0x21, 0x8c, 0xd1, 0x61, 0x37, 0xe1, 0x41, 0xf0, + 0x01, 0x0e, +}; + +static const uint8_t unicode_prop_White_Space_table[22] = { + 0x88, 0x84, 0x91, 0x80, 0xe3, 0x80, 0x99, 0x80, + 0x55, 0xde, 0x80, 0x49, 0x7e, 0x8a, 0x9c, 0x0c, + 0x80, 0xae, 0x80, 0x4f, 0x9f, 0x80, +}; + +static const uint8_t unicode_prop_White_Space_index[3] = { + 0x01, 0x30, 0x00, +}; + +static const uint8_t unicode_cc_table[937] = { + 0xb2, 0xcf, 0xd4, 0x00, 0xe8, 0x03, 0xdc, 0x00, + 0xe8, 0x00, 0xd8, 0x04, 0xdc, 0x01, 0xca, 0x03, + 0xdc, 0x01, 0xca, 0x0a, 0xdc, 0x04, 0x01, 0x03, + 0xdc, 0xc7, 0x00, 0xf0, 0xc0, 0x02, 0xdc, 0xc2, + 0x01, 0xdc, 0x80, 0xc2, 0x03, 0xdc, 0xc0, 0x00, + 0xe8, 0x01, 0xdc, 0xc0, 0x41, 0xe9, 0x00, 0xea, + 0x41, 0xe9, 0x00, 0xea, 0x00, 0xe9, 0xcc, 0xb0, + 0xe2, 0xc4, 0xb0, 0xd8, 0x00, 0xdc, 0xc3, 0x00, + 0xdc, 0xc2, 0x00, 0xde, 0x00, 0xdc, 0xc5, 0x05, + 0xdc, 0xc1, 0x00, 0xdc, 0xc1, 0x00, 0xde, 0x00, + 0xe4, 0xc0, 0x49, 0x0a, 0x43, 0x13, 0x80, 0x00, + 0x17, 0x80, 0x41, 0x18, 0x80, 0xc0, 0x00, 0xdc, + 0x80, 0x00, 0x12, 0xb0, 0x17, 0xc7, 0x42, 0x1e, + 0xaf, 0x47, 0x1b, 0xc1, 0x01, 0xdc, 0xc4, 0x00, + 0xdc, 0xc1, 0x00, 0xdc, 0x8f, 0x00, 0x23, 0xb0, + 0x34, 0xc6, 0x81, 0xc3, 0x00, 0xdc, 0xc0, 0x81, + 0xc1, 0x80, 0x00, 0xdc, 0xc1, 0x00, 0xdc, 0xa2, + 0x00, 0x24, 0x9d, 0xc0, 0x00, 0xdc, 0xc1, 0x00, + 0xdc, 0xc1, 0x02, 0xdc, 0xc0, 0x01, 0xdc, 0xc0, + 0x00, 0xdc, 0xc2, 0x00, 0xdc, 0xc0, 0x00, 0xdc, + 0xc0, 0x00, 0xdc, 0xc0, 0x00, 0xdc, 0xc1, 0xb0, + 0x6f, 0xc6, 0x00, 0xdc, 0xc0, 0x88, 0x00, 0xdc, + 0x97, 0xc3, 0x80, 0xc8, 0x80, 0xc2, 0x80, 0xc4, + 0xaa, 0x02, 0xdc, 0xb0, 0x0a, 0xc1, 0x02, 0xdc, + 0xc3, 0xa9, 0xc4, 0x04, 0xdc, 0xcd, 0x80, 0x00, + 0xdc, 0xc1, 0x00, 0xdc, 0xc1, 0x00, 0xdc, 0xc2, + 0x02, 0xdc, 0x42, 0x1b, 0xc2, 0x00, 0xdc, 0xc1, + 0x01, 0xdc, 0xc4, 0xb0, 0x0b, 0x00, 0x07, 0x8f, + 0x00, 0x09, 0x82, 0xc0, 0x00, 0xdc, 0xc1, 0xb0, + 0x36, 0x00, 0x07, 0x8f, 0x00, 0x09, 0xaf, 0xc0, + 0xb0, 0x0c, 0x00, 0x07, 0x8f, 0x00, 0x09, 0xb0, + 0x3d, 0x00, 0x07, 0x8f, 0x00, 0x09, 0xb0, 0x3d, + 0x00, 0x07, 0x8f, 0x00, 0x09, 0xb0, 0x4e, 0x00, + 0x09, 0xb0, 0x3d, 0x00, 0x07, 0x8f, 0x00, 0x09, + 0x86, 0x00, 0x54, 0x00, 0x5b, 0xb0, 0x34, 0x00, + 0x07, 0x8f, 0x00, 0x09, 0xb0, 0x3c, 0x01, 0x09, + 0x8f, 0x00, 0x09, 0xb0, 0x4b, 0x00, 0x09, 0xb0, + 0x3c, 0x01, 0x67, 0x00, 0x09, 0x8c, 0x03, 0x6b, + 0xb0, 0x3b, 0x01, 0x76, 0x00, 0x09, 0x8c, 0x03, + 0x7a, 0xb0, 0x1b, 0x01, 0xdc, 0x9a, 0x00, 0xdc, + 0x80, 0x00, 0xdc, 0x80, 0x00, 0xd8, 0xb0, 0x06, + 0x41, 0x81, 0x80, 0x00, 0x84, 0x84, 0x03, 0x82, + 0x81, 0x00, 0x82, 0x80, 0xc1, 0x00, 0x09, 0x80, + 0xc1, 0xb0, 0x0d, 0x00, 0xdc, 0xb0, 0x3f, 0x00, + 0x07, 0x80, 0x01, 0x09, 0xb0, 0x21, 0x00, 0xdc, + 0xb2, 0x9e, 0xc2, 0xb3, 0x83, 0x01, 0x09, 0x9d, + 0x00, 0x09, 0xb0, 0x6c, 0x00, 0x09, 0x89, 0xc0, + 0xb0, 0x9a, 0x00, 0xe4, 0xb0, 0x5e, 0x00, 0xde, + 0xc0, 0x00, 0xdc, 0xb0, 0xaa, 0xc0, 0x00, 0xdc, + 0xb0, 0x16, 0x00, 0x09, 0x93, 0xc7, 0x81, 0x00, + 0xdc, 0xaf, 0xc4, 0x05, 0xdc, 0xc1, 0x00, 0xdc, + 0x80, 0x01, 0xdc, 0xc1, 0x01, 0xdc, 0xc4, 0x00, + 0xdc, 0xd1, 0x00, 0xdc, 0x81, 0xc5, 0x00, 0xdc, + 0xc3, 0x00, 0xea, 0xb0, 0x17, 0x00, 0x07, 0x8e, + 0x00, 0x09, 0xa5, 0xc0, 0x00, 0xdc, 0xc6, 0xb0, + 0x05, 0x01, 0x09, 0xb0, 0x09, 0x00, 0x07, 0x8a, + 0x01, 0x09, 0xb0, 0x12, 0x00, 0x07, 0xb0, 0x67, + 0xc2, 0x41, 0x00, 0x04, 0xdc, 0xc1, 0x03, 0xdc, + 0xc0, 0x41, 0x00, 0x05, 0x01, 0x83, 0x00, 0xdc, + 0x85, 0xc0, 0x82, 0xc1, 0xb0, 0x95, 0xc1, 0x00, + 0xdc, 0xc6, 0x00, 0xdc, 0xc1, 0x00, 0xea, 0x00, + 0xd6, 0x00, 0xdc, 0x00, 0xca, 0xe4, 0x00, 0xe8, + 0x01, 0xe4, 0x00, 0xdc, 0x00, 0xda, 0xc0, 0x00, + 0xe9, 0x00, 0xdc, 0xc0, 0x00, 0xdc, 0xb2, 0x9f, + 0xc1, 0x01, 0x01, 0xc3, 0x02, 0x01, 0xc1, 0x83, + 0xc0, 0x82, 0x01, 0x01, 0xc0, 0x00, 0xdc, 0xc0, + 0x01, 0x01, 0x03, 0xdc, 0xc0, 0xb8, 0x03, 0xcd, + 0xc2, 0xb0, 0x5c, 0x00, 0x09, 0xb0, 0x2f, 0xdf, + 0xb1, 0xf9, 0x00, 0xda, 0x00, 0xe4, 0x00, 0xe8, + 0x00, 0xde, 0x01, 0xe0, 0xb0, 0x38, 0x01, 0x08, + 0xb8, 0x6d, 0xa3, 0xc0, 0x83, 0xc9, 0x9f, 0xc1, + 0xb0, 0x1f, 0xc1, 0xb0, 0xe3, 0x00, 0x09, 0xa4, + 0x00, 0x09, 0xb0, 0x66, 0x00, 0x09, 0x9a, 0xd1, + 0xb0, 0x08, 0x02, 0xdc, 0xa4, 0x00, 0x09, 0xb0, + 0x2e, 0x00, 0x07, 0x8b, 0x00, 0x09, 0xb0, 0xbe, + 0xc0, 0x80, 0xc1, 0x00, 0xdc, 0x81, 0xc1, 0x84, + 0xc1, 0x80, 0xc0, 0xb0, 0x03, 0x00, 0x09, 0xb0, + 0xc5, 0x00, 0x09, 0xb8, 0x46, 0xff, 0x00, 0x1a, + 0xb2, 0xd0, 0xc6, 0x06, 0xdc, 0xc1, 0xb3, 0x9c, + 0x00, 0xdc, 0xb0, 0xb1, 0x00, 0xdc, 0xb0, 0x64, + 0xc4, 0xb6, 0x61, 0x00, 0xdc, 0x80, 0xc0, 0xa7, + 0xc0, 0x00, 0x01, 0x00, 0xdc, 0x83, 0x00, 0x09, + 0xb0, 0x74, 0xc0, 0x00, 0xdc, 0xb2, 0x0c, 0xc3, + 0xb0, 0x10, 0xc4, 0xb1, 0x0c, 0xc1, 0xb0, 0x1c, + 0x01, 0xdc, 0x80, 0x02, 0xdc, 0xb0, 0x15, 0x01, + 0xdc, 0xc2, 0x00, 0xdc, 0xc0, 0x03, 0xdc, 0xb0, + 0x00, 0xc0, 0x00, 0xdc, 0xc0, 0x00, 0xdc, 0xb0, + 0x8f, 0x00, 0x09, 0xa8, 0x00, 0x09, 0x8d, 0x00, + 0x09, 0xb0, 0x08, 0x00, 0x09, 0x00, 0x07, 0xb0, + 0x14, 0xc2, 0xaf, 0x01, 0x09, 0xb0, 0x0d, 0x00, + 0x07, 0xb0, 0x1b, 0x00, 0x09, 0x88, 0x00, 0x07, + 0xb0, 0x39, 0x00, 0x09, 0x00, 0x07, 0xb0, 0x81, + 0x00, 0x07, 0x00, 0x09, 0xb0, 0x1f, 0x01, 0x07, + 0x8f, 0x00, 0x09, 0x97, 0xc6, 0x82, 0xc4, 0xb0, + 0x28, 0x02, 0x09, 0xb0, 0x40, 0x00, 0x09, 0x82, + 0x00, 0x07, 0x96, 0xc0, 0xb0, 0x32, 0x00, 0x09, + 0x00, 0x07, 0xb0, 0xca, 0x00, 0x09, 0x00, 0x07, + 0xb0, 0x4d, 0x00, 0x09, 0xb0, 0x45, 0x00, 0x09, + 0x00, 0x07, 0xb0, 0x42, 0x00, 0x09, 0xb0, 0xdc, + 0x00, 0x09, 0x00, 0x07, 0xb0, 0xd1, 0x01, 0x09, + 0x83, 0x00, 0x07, 0xb0, 0x6b, 0x00, 0x09, 0xb0, + 0x22, 0x00, 0x09, 0x91, 0x00, 0x09, 0xb0, 0x20, + 0x00, 0x09, 0xb1, 0x74, 0x00, 0x09, 0xb0, 0xd1, + 0x00, 0x07, 0x80, 0x01, 0x09, 0xb0, 0x20, 0x00, + 0x09, 0xb1, 0x78, 0x01, 0x09, 0xb8, 0x39, 0xbb, + 0x00, 0x09, 0xb8, 0x01, 0x8f, 0x04, 0x01, 0xb0, + 0x0a, 0xc6, 0xb4, 0x88, 0x01, 0x06, 0xb8, 0x44, + 0x7b, 0x00, 0x01, 0xb8, 0x0c, 0x95, 0x01, 0xd8, + 0x02, 0x01, 0x82, 0x00, 0xe2, 0x04, 0xd8, 0x87, + 0x07, 0xdc, 0x81, 0xc4, 0x01, 0xdc, 0x9d, 0xc3, + 0xb0, 0x63, 0xc2, 0xb8, 0x05, 0x8a, 0xc6, 0x80, + 0xd0, 0x81, 0xc6, 0x80, 0xc1, 0x80, 0xc4, 0xb0, + 0x33, 0xc0, 0xb0, 0x6f, 0xc6, 0xb1, 0x46, 0xc0, + 0xb0, 0x0c, 0xc3, 0xb1, 0xcb, 0x01, 0xe8, 0x00, + 0xdc, 0xc0, 0xb0, 0xcd, 0xc0, 0x00, 0xdc, 0xb0, + 0xc2, 0xc0, 0x81, 0xc0, 0x86, 0xc1, 0x84, 0xc0, + 0xb1, 0xa9, 0x06, 0xdc, 0xb0, 0x3c, 0xc5, 0x00, + 0x07, +}; + +static const uint8_t unicode_cc_index[90] = { + 0x4d, 0x03, 0x00, 0x97, 0x05, 0x20, 0xc6, 0x05, + 0x00, 0xe7, 0x06, 0x00, 0x45, 0x07, 0x00, 0x9c, + 0x08, 0x00, 0x4d, 0x09, 0x00, 0x3c, 0x0b, 0x00, + 0x3d, 0x0d, 0x00, 0x36, 0x0f, 0x00, 0x38, 0x10, + 0x20, 0x3a, 0x19, 0x00, 0xcb, 0x1a, 0x20, 0xf2, + 0x1b, 0x00, 0xc3, 0x1d, 0x20, 0xd0, 0x20, 0x00, + 0x00, 0x2e, 0x00, 0x2c, 0xa8, 0x00, 0xbe, 0xaa, + 0x00, 0x76, 0x03, 0x01, 0xfa, 0x0e, 0x01, 0x80, + 0x10, 0x21, 0xe9, 0x12, 0x01, 0xc3, 0x14, 0x01, + 0x3f, 0x19, 0x01, 0x98, 0x1d, 0x21, 0x67, 0xd1, + 0x01, 0x8f, 0xe0, 0x21, 0xf6, 0xe6, 0x01, 0x4b, + 0xe9, 0x01, +}; + +static const uint32_t unicode_decomp_table1[709] = { + 0x00280081, 0x002a0097, 0x002a8081, 0x002bc097, + 0x002c8115, 0x002d0097, 0x002d4081, 0x002e0097, + 0x002e4115, 0x002f0199, 0x00302016, 0x00400842, + 0x00448a42, 0x004a0442, 0x004c0096, 0x004c8117, + 0x004d0242, 0x004e4342, 0x004fc12f, 0x0050c342, + 0x005240bf, 0x00530342, 0x00550942, 0x005a0842, + 0x005e0096, 0x005e4342, 0x005fc081, 0x00680142, + 0x006bc142, 0x00710185, 0x0071c317, 0x00734844, + 0x00778344, 0x00798342, 0x007b02be, 0x007c4197, + 0x007d0142, 0x007e0444, 0x00800e42, 0x00878142, + 0x00898744, 0x00ac0483, 0x00b60317, 0x00b80283, + 0x00d00214, 0x00d10096, 0x00dd0080, 0x00de8097, + 0x00df8080, 0x00e10097, 0x00e1413e, 0x00e1c080, + 0x00e204be, 0x00ea83ae, 0x00f282ae, 0x00f401ad, + 0x00f4c12e, 0x00f54103, 0x00fc0303, 0x00fe4081, + 0x0100023e, 0x0101c0be, 0x010301be, 0x010640be, + 0x010e40be, 0x0114023e, 0x0115c0be, 0x011701be, + 0x011d8144, 0x01304144, 0x01340244, 0x01358144, + 0x01368344, 0x01388344, 0x013a8644, 0x013e0144, + 0x0161c085, 0x018882ae, 0x019d422f, 0x01b00184, + 0x01b4c084, 0x024a4084, 0x024c4084, 0x024d0084, + 0x0256042e, 0x0272c12e, 0x02770120, 0x0277c084, + 0x028cc084, 0x028d8084, 0x029641ae, 0x02978084, + 0x02d20084, 0x02d2c12e, 0x02d70120, 0x02e50084, + 0x02f281ae, 0x03120084, 0x03300084, 0x0331c122, + 0x0332812e, 0x035281ae, 0x03768084, 0x037701ae, + 0x038cc085, 0x03acc085, 0x03b7012f, 0x03c30081, + 0x03d0c084, 0x03d34084, 0x03d48084, 0x03d5c084, + 0x03d70084, 0x03da4084, 0x03dcc084, 0x03dd412e, + 0x03ddc085, 0x03de0084, 0x03de4085, 0x03e04084, + 0x03e4c084, 0x03e74084, 0x03e88084, 0x03e9c084, + 0x03eb0084, 0x03ee4084, 0x04098084, 0x043f0081, + 0x06c18484, 0x06c48084, 0x06cec184, 0x06d00120, + 0x06d0c084, 0x074b0383, 0x074cc41f, 0x074f1783, + 0x075e0081, 0x0766d283, 0x07801d44, 0x078e8942, + 0x07931844, 0x079f0d42, 0x07a58216, 0x07a68085, + 0x07a6c0be, 0x07a80d44, 0x07aea044, 0x07c00122, + 0x07c08344, 0x07c20122, 0x07c28344, 0x07c40122, + 0x07c48244, 0x07c60122, 0x07c68244, 0x07c8113e, + 0x07d08244, 0x07d20122, 0x07d28244, 0x07d40122, + 0x07d48344, 0x07d64c3e, 0x07dc4080, 0x07dc80be, + 0x07dcc080, 0x07dd00be, 0x07dd4080, 0x07dd80be, + 0x07ddc080, 0x07de00be, 0x07de4080, 0x07de80be, + 0x07dec080, 0x07df00be, 0x07df4080, 0x07e00820, + 0x07e40820, 0x07e80820, 0x07ec05be, 0x07eec080, + 0x07ef00be, 0x07ef4097, 0x07ef8080, 0x07efc117, + 0x07f0443e, 0x07f24080, 0x07f280be, 0x07f2c080, + 0x07f303be, 0x07f4c080, 0x07f582ae, 0x07f6c080, + 0x07f7433e, 0x07f8c080, 0x07f903ae, 0x07fac080, + 0x07fb013e, 0x07fb8102, 0x07fc83be, 0x07fe4080, + 0x07fe80be, 0x07fec080, 0x07ff00be, 0x07ff4080, + 0x07ff8097, 0x0800011e, 0x08008495, 0x08044081, + 0x0805c097, 0x08090081, 0x08094097, 0x08098099, + 0x080bc081, 0x080cc085, 0x080d00b1, 0x080d8085, + 0x080dc0b1, 0x080f0197, 0x0811c197, 0x0815c0b3, + 0x0817c081, 0x081c0595, 0x081ec081, 0x081f0215, + 0x0820051f, 0x08228583, 0x08254415, 0x082a0097, + 0x08400119, 0x08408081, 0x0840c0bf, 0x08414119, + 0x0841c081, 0x084240bf, 0x0842852d, 0x08454081, + 0x08458097, 0x08464295, 0x08480097, 0x08484099, + 0x08488097, 0x08490081, 0x08498080, 0x084a0081, + 0x084a8102, 0x084b0495, 0x084d421f, 0x084e4081, + 0x084ec099, 0x084f0283, 0x08514295, 0x08540119, + 0x0854809b, 0x0854c619, 0x0857c097, 0x08580081, + 0x08584097, 0x08588099, 0x0858c097, 0x08590081, + 0x08594097, 0x08598099, 0x0859c09b, 0x085a0097, + 0x085a4081, 0x085a8097, 0x085ac099, 0x085b0295, + 0x085c4097, 0x085c8099, 0x085cc097, 0x085d0081, + 0x085d4097, 0x085d8099, 0x085dc09b, 0x085e0097, + 0x085e4081, 0x085e8097, 0x085ec099, 0x085f0215, + 0x08624099, 0x0866813e, 0x086b80be, 0x087341be, + 0x088100be, 0x088240be, 0x088300be, 0x088901be, + 0x088b0085, 0x088b40b1, 0x088bc085, 0x088c00b1, + 0x089040be, 0x089100be, 0x0891c1be, 0x089801be, + 0x089b42be, 0x089d0144, 0x089e0144, 0x08a00144, + 0x08a10144, 0x08a20144, 0x08ab023e, 0x08b80244, + 0x08ba8220, 0x08ca411e, 0x0918049f, 0x091a4523, + 0x091cc097, 0x091d04a5, 0x091f452b, 0x0921c09b, + 0x092204a1, 0x09244525, 0x0926c099, 0x09270d25, + 0x092d8d1f, 0x09340d1f, 0x093a8081, 0x0a8300b3, + 0x0a9d0099, 0x0a9d4097, 0x0a9d8099, 0x0ab700be, + 0x0b1f0115, 0x0b5bc081, 0x0ba7c081, 0x0bbcc081, + 0x0bc004ad, 0x0bc244ad, 0x0bc484ad, 0x0bc6f383, + 0x0be0852d, 0x0be31d03, 0x0bf1882d, 0x0c000081, + 0x0c0d8283, 0x0c130b84, 0x0c194284, 0x0c1c0122, + 0x0c1cc122, 0x0c1d8122, 0x0c1e4122, 0x0c1f0122, + 0x0c250084, 0x0c26c123, 0x0c278084, 0x0c27c085, + 0x0c2b0b84, 0x0c314284, 0x0c340122, 0x0c34c122, + 0x0c358122, 0x0c364122, 0x0c370122, 0x0c3d0084, + 0x0c3dc220, 0x0c3f8084, 0x0c3fc085, 0x0c4c4a2d, + 0x0c51451f, 0x0c53ca9f, 0x0c5915ad, 0x0c648703, + 0x0c800741, 0x0c838089, 0x0c83c129, 0x0c8441a9, + 0x0c850089, 0x0c854129, 0x0c85c2a9, 0x0c870089, + 0x0c87408f, 0x0c87808d, 0x0c881241, 0x0c910203, + 0x0c940099, 0x0c9444a3, 0x0c968323, 0x0c98072d, + 0x0c9b84af, 0x0c9dc2a1, 0x0c9f00b5, 0x0c9f40b3, + 0x0c9f8085, 0x0ca01883, 0x0cac4223, 0x0cad4523, + 0x0cafc097, 0x0cb004a1, 0x0cb241a5, 0x0cb30097, + 0x0cb34099, 0x0cb38097, 0x0cb3c099, 0x0cb417ad, + 0x0cbfc085, 0x0cc001b3, 0x0cc0c0b1, 0x0cc100b3, + 0x0cc14131, 0x0cc1c0b5, 0x0cc200b3, 0x0cc241b1, + 0x0cc30133, 0x0cc38131, 0x0cc40085, 0x0cc440b1, + 0x0cc48133, 0x0cc50085, 0x0cc540b5, 0x0cc580b7, + 0x0cc5c0b5, 0x0cc600b1, 0x0cc64135, 0x0cc6c0b3, + 0x0cc701b1, 0x0cc7c0b3, 0x0cc800b5, 0x0cc840b3, + 0x0cc881b1, 0x0cc9422f, 0x0cca4131, 0x0ccac0b5, + 0x0ccb00b1, 0x0ccb40b3, 0x0ccb80b5, 0x0ccbc0b1, + 0x0ccc012f, 0x0ccc80b5, 0x0cccc0b3, 0x0ccd00b5, + 0x0ccd40b1, 0x0ccd80b5, 0x0ccdc085, 0x0cce02b1, + 0x0ccf40b3, 0x0ccf80b1, 0x0ccfc085, 0x0cd001b1, + 0x0cd0c0b3, 0x0cd101b1, 0x0cd1c0b5, 0x0cd200b3, + 0x0cd24085, 0x0cd280b5, 0x0cd2c085, 0x0cd30133, + 0x0cd381b1, 0x0cd440b3, 0x0cd48085, 0x0cd4c0b1, + 0x0cd500b3, 0x0cd54085, 0x0cd580b5, 0x0cd5c0b1, + 0x0cd60521, 0x0cd88525, 0x0cdb02a5, 0x0cdc4099, + 0x0cdc8117, 0x0cdd0099, 0x0cdd4197, 0x0cde0127, + 0x0cde8285, 0x0cdfc089, 0x0ce0043f, 0x0ce20099, + 0x0ce2409b, 0x0ce283bf, 0x0ce44219, 0x0ce54205, + 0x0ce6433f, 0x0ce7c131, 0x0ce84085, 0x0ce881b1, + 0x0ce94085, 0x0ce98107, 0x0cea0089, 0x0cea4097, + 0x0cea8219, 0x0ceb809d, 0x0cebc08d, 0x0cec083f, + 0x0cf00105, 0x0cf0809b, 0x0cf0c197, 0x0cf1809b, + 0x0cf1c099, 0x0cf20517, 0x0cf48099, 0x0cf4c117, + 0x0cf54119, 0x0cf5c097, 0x0cf6009b, 0x0cf64099, + 0x0cf68217, 0x0cf78119, 0x0cf804a1, 0x0cfa4525, + 0x0cfcc525, 0x0cff4125, 0x0cffc099, 0x29a70103, + 0x29dc0081, 0x29fc4215, 0x29fe0103, 0x2ad70203, + 0x2ada4081, 0x3e401482, 0x3e4a7f82, 0x3e6a3f82, + 0x3e8aa102, 0x3e9b0110, 0x3e9c2f82, 0x3eb3c590, + 0x3ec00197, 0x3ec0c119, 0x3ec1413f, 0x3ec4c2af, + 0x3ec74184, 0x3ec804ad, 0x3eca4081, 0x3eca8304, + 0x3ecc03a0, 0x3ece02a0, 0x3ecf8084, 0x3ed00120, + 0x3ed0c120, 0x3ed184ae, 0x3ed3c085, 0x3ed4312d, + 0x3ef4cbad, 0x3efa892f, 0x3eff022d, 0x3f002f2f, + 0x3f1782a5, 0x3f18c0b1, 0x3f1907af, 0x3f1cffaf, + 0x3f3c81a5, 0x3f3d64af, 0x3f542031, 0x3f649b31, + 0x3f7c0131, 0x3f7c83b3, 0x3f7e40b1, 0x3f7e80bd, + 0x3f7ec0bb, 0x3f7f00b3, 0x3f840503, 0x3f8c01ad, + 0x3f8cc315, 0x3f8e462d, 0x3f91cc03, 0x3f97c695, + 0x3f9c01af, 0x3f9d0085, 0x3f9d852f, 0x3fa03aad, + 0x3fbd442f, 0x3fc06f1f, 0x3fd7c11f, 0x3fd85fad, + 0x3fe80081, 0x3fe84f1f, 0x3ff0831f, 0x3ff2831f, + 0x3ff4831f, 0x3ff6819f, 0x3ff80783, 0x41724092, + 0x41790092, 0x41e04d83, 0x41e70f91, 0x44268192, + 0x442ac092, 0x444b8112, 0x44d2c112, 0x44e0c192, + 0x44e38092, 0x44e44092, 0x44f14212, 0x452ec212, + 0x456e8112, 0x464e0092, 0x58484412, 0x5b5a0192, + 0x73358d1f, 0x733c051f, 0x74578392, 0x746ec312, + 0x75000d1f, 0x75068d1f, 0x750d0d1f, 0x7513839f, + 0x7515891f, 0x751a0d1f, 0x75208d1f, 0x75271015, + 0x752f439f, 0x7531459f, 0x75340d1f, 0x753a8d1f, + 0x75410395, 0x7543441f, 0x7545839f, 0x75478d1f, + 0x754e0795, 0x7552839f, 0x75548d1f, 0x755b0d1f, + 0x75618d1f, 0x75680d1f, 0x756e8d1f, 0x75750d1f, + 0x757b8d1f, 0x75820d1f, 0x75888d1f, 0x758f0d1f, + 0x75958d1f, 0x759c0d1f, 0x75a28d1f, 0x75a90103, + 0x75aa089f, 0x75ae4081, 0x75ae839f, 0x75b04081, + 0x75b08c9f, 0x75b6c081, 0x75b7032d, 0x75b8889f, + 0x75bcc081, 0x75bd039f, 0x75bec081, 0x75bf0c9f, + 0x75c54081, 0x75c5832d, 0x75c7089f, 0x75cb4081, + 0x75cb839f, 0x75cd4081, 0x75cd8c9f, 0x75d3c081, + 0x75d4032d, 0x75d5889f, 0x75d9c081, 0x75da039f, + 0x75dbc081, 0x75dc0c9f, 0x75e24081, 0x75e2832d, + 0x75e4089f, 0x75e84081, 0x75e8839f, 0x75ea4081, + 0x75ea8c9f, 0x75f0c081, 0x75f1042d, 0x75f3851f, + 0x75f6051f, 0x75f8851f, 0x75fb051f, 0x75fd851f, + 0x780c049f, 0x780e419f, 0x780f059f, 0x7811c203, + 0x7812d0ad, 0x781b0103, 0x7b80022d, 0x7b814dad, + 0x7b884203, 0x7b89c081, 0x7b8a452d, 0x7b8d0403, + 0x7b908081, 0x7b91dc03, 0x7ba0052d, 0x7ba2c8ad, + 0x7ba84483, 0x7baac8ad, 0x7c400097, 0x7c404521, + 0x7c440d25, 0x7c4a8087, 0x7c4ac115, 0x7c4b4117, + 0x7c4c0d1f, 0x7c528217, 0x7c538099, 0x7c53c097, + 0x7c5a8197, 0x7c640097, 0x7c80012f, 0x7c808081, + 0x7c841603, 0x7c9004c1, 0x7c940103, 0x7efc051f, + 0xbe0001ac, 0xbe00d110, 0xbe0947ac, 0xbe0d3910, + 0xbe29872c, 0xbe2d022c, 0xbe2e3790, 0xbe49ff90, + 0xbe69bc10, +}; + +static const uint16_t unicode_decomp_table2[709] = { + 0x0020, 0x0000, 0x0061, 0x0002, 0x0004, 0x0006, 0x03bc, 0x0008, + 0x000a, 0x000c, 0x0015, 0x0095, 0x00a5, 0x00b9, 0x00c1, 0x00c3, + 0x00c7, 0x00cb, 0x00d1, 0x00d7, 0x00dd, 0x00e0, 0x00e6, 0x00f8, + 0x0108, 0x010a, 0x0073, 0x0110, 0x0112, 0x0114, 0x0120, 0x012c, + 0x0144, 0x014d, 0x0153, 0x0162, 0x0168, 0x016a, 0x0176, 0x0192, + 0x0194, 0x01a9, 0x01bb, 0x01c7, 0x01d1, 0x01d5, 0x02b9, 0x01d7, + 0x003b, 0x01d9, 0x01db, 0x00b7, 0x01e1, 0x01fc, 0x020c, 0x0218, + 0x021d, 0x0223, 0x0227, 0x03a3, 0x0233, 0x023f, 0x0242, 0x024b, + 0x024e, 0x0251, 0x025d, 0x0260, 0x0269, 0x026c, 0x026f, 0x0275, + 0x0278, 0x0281, 0x028a, 0x029c, 0x029f, 0x02a3, 0x02af, 0x02b9, + 0x02c5, 0x02c9, 0x02cd, 0x02d1, 0x02d5, 0x02e7, 0x02ed, 0x02f1, + 0x02f5, 0x02f9, 0x02fd, 0x0305, 0x0309, 0x030d, 0x0313, 0x0317, + 0x031b, 0x0323, 0x0327, 0x032b, 0x032f, 0x0335, 0x033d, 0x0341, + 0x0349, 0x034d, 0x0351, 0x0f0b, 0x0357, 0x035b, 0x035f, 0x0363, + 0x0367, 0x036b, 0x036f, 0x0373, 0x0379, 0x037d, 0x0381, 0x0385, + 0x0389, 0x038d, 0x0391, 0x0395, 0x0399, 0x039d, 0x03a1, 0x10dc, + 0x03a5, 0x03c9, 0x03cd, 0x03d9, 0x03dd, 0x03e1, 0x03ef, 0x03f1, + 0x043d, 0x044f, 0x0499, 0x04f0, 0x0502, 0x054a, 0x0564, 0x056c, + 0x0570, 0x0573, 0x059a, 0x05fa, 0x05fe, 0x0607, 0x060b, 0x0614, + 0x0618, 0x061e, 0x0622, 0x0628, 0x068e, 0x0694, 0x0698, 0x069e, + 0x06a2, 0x06ab, 0x03ac, 0x06f3, 0x03ad, 0x06f6, 0x03ae, 0x06f9, + 0x03af, 0x06fc, 0x03cc, 0x06ff, 0x03cd, 0x0702, 0x03ce, 0x0705, + 0x0709, 0x070d, 0x0711, 0x0386, 0x0732, 0x0735, 0x03b9, 0x0737, + 0x073b, 0x0388, 0x0753, 0x0389, 0x0756, 0x0390, 0x076b, 0x038a, + 0x0777, 0x03b0, 0x0789, 0x038e, 0x0799, 0x079f, 0x07a3, 0x038c, + 0x07b8, 0x038f, 0x07bb, 0x00b4, 0x07be, 0x07c0, 0x07c2, 0x2010, + 0x07cb, 0x002e, 0x07cd, 0x07cf, 0x0020, 0x07d2, 0x07d6, 0x07db, + 0x07df, 0x07e4, 0x07ea, 0x07f0, 0x0020, 0x07f6, 0x2212, 0x0801, + 0x0805, 0x0807, 0x081d, 0x0825, 0x0827, 0x0043, 0x082d, 0x0830, + 0x0190, 0x0836, 0x0839, 0x004e, 0x0845, 0x0847, 0x084c, 0x084e, + 0x0851, 0x005a, 0x03a9, 0x005a, 0x0853, 0x0857, 0x0860, 0x0069, + 0x0862, 0x0865, 0x086f, 0x0874, 0x087a, 0x087e, 0x08a2, 0x0049, + 0x08a4, 0x08a6, 0x08a9, 0x0056, 0x08ab, 0x08ad, 0x08b0, 0x08b4, + 0x0058, 0x08b6, 0x08b8, 0x08bb, 0x08c0, 0x08c2, 0x08c5, 0x0076, + 0x08c7, 0x08c9, 0x08cc, 0x08d0, 0x0078, 0x08d2, 0x08d4, 0x08d7, + 0x08db, 0x08de, 0x08e4, 0x08e7, 0x08f0, 0x08f3, 0x08f6, 0x08f9, + 0x0902, 0x0906, 0x090b, 0x090f, 0x0914, 0x0917, 0x091a, 0x0923, + 0x092c, 0x093b, 0x093e, 0x0941, 0x0944, 0x0947, 0x094a, 0x0956, + 0x095c, 0x0960, 0x0962, 0x0964, 0x0968, 0x096a, 0x0970, 0x0978, + 0x097c, 0x0980, 0x0986, 0x0989, 0x098f, 0x0991, 0x0030, 0x0993, + 0x0999, 0x099c, 0x099e, 0x09a1, 0x09a4, 0x2d61, 0x6bcd, 0x9f9f, + 0x09a6, 0x09b1, 0x09bc, 0x09c7, 0x0a95, 0x0aa1, 0x0b15, 0x0020, + 0x0b27, 0x0b31, 0x0b8d, 0x0ba1, 0x0ba5, 0x0ba9, 0x0bad, 0x0bb1, + 0x0bb5, 0x0bb9, 0x0bbd, 0x0bc1, 0x0bc5, 0x0c21, 0x0c35, 0x0c39, + 0x0c3d, 0x0c41, 0x0c45, 0x0c49, 0x0c4d, 0x0c51, 0x0c55, 0x0c59, + 0x0c6f, 0x0c71, 0x0c73, 0x0ca0, 0x0cbc, 0x0cdc, 0x0ce4, 0x0cec, + 0x0cf4, 0x0cfc, 0x0d04, 0x0d0c, 0x0d14, 0x0d22, 0x0d2e, 0x0d7a, + 0x0d82, 0x0d85, 0x0d89, 0x0d8d, 0x0d9d, 0x0db1, 0x0db5, 0x0dbc, + 0x0dc2, 0x0dc6, 0x0e28, 0x0e2c, 0x0e30, 0x0e32, 0x0e36, 0x0e3c, + 0x0e3e, 0x0e41, 0x0e43, 0x0e46, 0x0e77, 0x0e7b, 0x0e89, 0x0e8e, + 0x0e94, 0x0e9c, 0x0ea3, 0x0ea9, 0x0eb4, 0x0ebe, 0x0ec6, 0x0eca, + 0x0ecf, 0x0ed9, 0x0edd, 0x0ee4, 0x0eec, 0x0ef3, 0x0ef8, 0x0f04, + 0x0f0a, 0x0f15, 0x0f1b, 0x0f22, 0x0f28, 0x0f33, 0x0f3d, 0x0f45, + 0x0f4c, 0x0f51, 0x0f57, 0x0f5e, 0x0f63, 0x0f69, 0x0f70, 0x0f76, + 0x0f7d, 0x0f82, 0x0f89, 0x0f8d, 0x0f9e, 0x0fa4, 0x0fa9, 0x0fad, + 0x0fb8, 0x0fbe, 0x0fc9, 0x0fd0, 0x0fd6, 0x0fda, 0x0fe1, 0x0fe5, + 0x0fef, 0x0ffa, 0x1000, 0x1004, 0x1009, 0x100f, 0x1013, 0x101a, + 0x101f, 0x1023, 0x1029, 0x102f, 0x1032, 0x1036, 0x1039, 0x103f, + 0x1045, 0x1059, 0x1061, 0x1079, 0x107c, 0x1080, 0x1095, 0x10a1, + 0x10b1, 0x10c3, 0x10cb, 0x10cf, 0x10da, 0x10de, 0x10ea, 0x10f2, + 0x10f4, 0x1100, 0x1105, 0x1111, 0x1141, 0x1149, 0x114d, 0x1153, + 0x1157, 0x115a, 0x116e, 0x1171, 0x1175, 0x117b, 0x117d, 0x1181, + 0x1184, 0x118c, 0x1192, 0x1196, 0x119c, 0x11a2, 0x11a8, 0x11ab, + 0xa76f, 0x11af, 0x11b3, 0x11b7, 0x028d, 0x11bf, 0x1211, 0x130f, + 0x140d, 0x1491, 0x1496, 0x1554, 0x156d, 0x1573, 0x1579, 0x157f, + 0x158b, 0x1597, 0x002b, 0x15a2, 0x15ba, 0x15be, 0x15c2, 0x15c6, + 0x15ca, 0x15ce, 0x15e2, 0x15e6, 0x164a, 0x1663, 0x1689, 0x168f, + 0x174d, 0x1753, 0x1758, 0x1778, 0x1878, 0x187e, 0x1912, 0x19d4, + 0x1a78, 0x1a80, 0x1a9e, 0x1aa3, 0x1ab7, 0x1ac1, 0x1ac7, 0x1adb, + 0x1ae0, 0x1ae6, 0x1af4, 0x1b24, 0x1b31, 0x1b39, 0x1b3d, 0x1b53, + 0x1bca, 0x1bdc, 0x1bde, 0x1be0, 0x3164, 0x1c21, 0x1c23, 0x1c25, + 0x1c27, 0x1c29, 0x1c2b, 0x1c49, 0x1c4e, 0x1c53, 0x1c89, 0x1ccf, + 0x1cdd, 0x1ce2, 0x1ceb, 0x1cf4, 0x1d02, 0x1d07, 0x1d0c, 0x1d1e, + 0x1d30, 0x1d39, 0x1d3e, 0x1d62, 0x1d70, 0x1d72, 0x1d74, 0x1d94, + 0x1daf, 0x1db1, 0x1db3, 0x1db5, 0x1db7, 0x1db9, 0x1dbb, 0x1dbd, + 0x1ddd, 0x1ddf, 0x1de1, 0x1de3, 0x1de5, 0x1dec, 0x1dee, 0x1df0, + 0x1df2, 0x1e01, 0x1e03, 0x1e05, 0x1e07, 0x1e09, 0x1e0b, 0x1e0d, + 0x1e0f, 0x1e11, 0x1e13, 0x1e15, 0x1e17, 0x1e19, 0x1e1b, 0x1e1d, + 0x1e21, 0x03f4, 0x1e23, 0x2207, 0x1e25, 0x2202, 0x1e27, 0x1e2f, + 0x03f4, 0x1e31, 0x2207, 0x1e33, 0x2202, 0x1e35, 0x1e3d, 0x03f4, + 0x1e3f, 0x2207, 0x1e41, 0x2202, 0x1e43, 0x1e4b, 0x03f4, 0x1e4d, + 0x2207, 0x1e4f, 0x2202, 0x1e51, 0x1e59, 0x03f4, 0x1e5b, 0x2207, + 0x1e5d, 0x2202, 0x1e5f, 0x1e69, 0x1e6b, 0x1e6d, 0x1e6f, 0x1e71, + 0x1e73, 0x1e75, 0x1e77, 0x1e79, 0x1e81, 0x1ea4, 0x1ea8, 0x1eae, + 0x1ecb, 0x062d, 0x1ed3, 0x1edf, 0x062c, 0x1eef, 0x1f5f, 0x1f6b, + 0x1f7e, 0x1f90, 0x1fa3, 0x1fa5, 0x1fa9, 0x1faf, 0x1fb5, 0x1fb7, + 0x1fbb, 0x1fbd, 0x1fc5, 0x1fc8, 0x1fca, 0x1fd0, 0x1fd2, 0x30b5, + 0x1fd8, 0x2030, 0x2046, 0x204a, 0x204c, 0x2051, 0x209e, 0x20af, + 0x21b0, 0x21c0, 0x21c6, 0x22c0, 0x23de, +}; + +static const uint8_t unicode_decomp_data[9452] = { + 0x20, 0x88, 0x20, 0x84, 0x32, 0x33, 0x20, 0x81, + 0x20, 0xa7, 0x31, 0x6f, 0x31, 0xd0, 0x34, 0x31, + 0xd0, 0x32, 0x33, 0xd0, 0x34, 0x41, 0x80, 0x41, + 0x81, 0x41, 0x82, 0x41, 0x83, 0x41, 0x88, 0x41, + 0x8a, 0x00, 0x00, 0x43, 0xa7, 0x45, 0x80, 0x45, + 0x81, 0x45, 0x82, 0x45, 0x88, 0x49, 0x80, 0x49, + 0x81, 0x49, 0x82, 0x49, 0x88, 0x00, 0x00, 0x4e, + 0x83, 0x4f, 0x80, 0x4f, 0x81, 0x4f, 0x82, 0x4f, + 0x83, 0x4f, 0x88, 0x00, 0x00, 0x00, 0x00, 0x55, + 0x80, 0x55, 0x81, 0x55, 0x82, 0x55, 0x88, 0x59, + 0x81, 0x00, 0x00, 0x00, 0x00, 0x61, 0x80, 0x61, + 0x81, 0x61, 0x82, 0x61, 0x83, 0x61, 0x88, 0x61, + 0x8a, 0x00, 0x00, 0x63, 0xa7, 0x65, 0x80, 0x65, + 0x81, 0x65, 0x82, 0x65, 0x88, 0x69, 0x80, 0x69, + 0x81, 0x69, 0x82, 0x69, 0x88, 0x00, 0x00, 0x6e, + 0x83, 0x6f, 0x80, 0x6f, 0x81, 0x6f, 0x82, 0x6f, + 0x83, 0x6f, 0x88, 0x00, 0x00, 0x00, 0x00, 0x75, + 0x80, 0x75, 0x81, 0x75, 0x82, 0x75, 0x88, 0x79, + 0x81, 0x00, 0x00, 0x79, 0x88, 0x41, 0x84, 0x41, + 0x86, 0x41, 0xa8, 0x43, 0x81, 0x43, 0x82, 0x43, + 0x87, 0x43, 0x8c, 0x44, 0x8c, 0x45, 0x84, 0x45, + 0x86, 0x45, 0x87, 0x45, 0xa8, 0x45, 0x8c, 0x47, + 0x82, 0x47, 0x86, 0x47, 0x87, 0x47, 0xa7, 0x48, + 0x82, 0x49, 0x83, 0x49, 0x84, 0x49, 0x86, 0x49, + 0xa8, 0x49, 0x87, 0x49, 0x4a, 0x69, 0x6a, 0x4a, + 0x82, 0x4b, 0xa7, 0x4c, 0x81, 0x4c, 0xa7, 0x4c, + 0x8c, 0x4c, 0x00, 0x00, 0x6b, 0x20, 0x6b, 0x4e, + 0x81, 0x4e, 0xa7, 0x4e, 0x8c, 0xbc, 0x02, 0x6e, + 0x4f, 0x84, 0x4f, 0x86, 0x4f, 0x8b, 0x52, 0x81, + 0x52, 0xa7, 0x52, 0x8c, 0x53, 0x81, 0x53, 0x82, + 0x53, 0xa7, 0x53, 0x8c, 0x54, 0xa7, 0x54, 0x8c, + 0x55, 0x83, 0x55, 0x84, 0x55, 0x86, 0x55, 0x8a, + 0x55, 0x8b, 0x55, 0xa8, 0x57, 0x82, 0x59, 0x82, + 0x59, 0x88, 0x5a, 0x81, 0x5a, 0x87, 0x5a, 0x8c, + 0x4f, 0x9b, 0x55, 0x9b, 0x44, 0x00, 0x7d, 0x01, + 0x44, 0x00, 0x7e, 0x01, 0x64, 0x00, 0x7e, 0x01, + 0x4c, 0x4a, 0x4c, 0x6a, 0x6c, 0x6a, 0x4e, 0x4a, + 0x4e, 0x6a, 0x6e, 0x6a, 0x41, 0x00, 0x8c, 0x49, + 0x00, 0x8c, 0x4f, 0x00, 0x8c, 0x55, 0x00, 0x8c, + 0xdc, 0x00, 0x84, 0xdc, 0x00, 0x81, 0xdc, 0x00, + 0x8c, 0xdc, 0x00, 0x80, 0xc4, 0x00, 0x84, 0x26, + 0x02, 0x84, 0xc6, 0x00, 0x84, 0x47, 0x8c, 0x4b, + 0x8c, 0x4f, 0xa8, 0xea, 0x01, 0x84, 0xeb, 0x01, + 0x84, 0xb7, 0x01, 0x8c, 0x92, 0x02, 0x8c, 0x6a, + 0x00, 0x8c, 0x44, 0x5a, 0x44, 0x7a, 0x64, 0x7a, + 0x47, 0x81, 0x4e, 0x00, 0x80, 0xc5, 0x00, 0x81, + 0xc6, 0x00, 0x81, 0xd8, 0x00, 0x81, 0x41, 0x8f, + 0x41, 0x91, 0x45, 0x8f, 0x45, 0x91, 0x49, 0x8f, + 0x49, 0x91, 0x4f, 0x8f, 0x4f, 0x91, 0x52, 0x8f, + 0x52, 0x91, 0x55, 0x8f, 0x55, 0x91, 0x53, 0xa6, + 0x54, 0xa6, 0x48, 0x8c, 0x41, 0x00, 0x87, 0x45, + 0x00, 0xa7, 0xd6, 0x00, 0x84, 0xd5, 0x00, 0x84, + 0x4f, 0x00, 0x87, 0x2e, 0x02, 0x84, 0x59, 0x00, + 0x84, 0x68, 0x00, 0x66, 0x02, 0x6a, 0x00, 0x72, + 0x00, 0x79, 0x02, 0x7b, 0x02, 0x81, 0x02, 0x77, + 0x00, 0x79, 0x00, 0x20, 0x86, 0x20, 0x87, 0x20, + 0x8a, 0x20, 0xa8, 0x20, 0x83, 0x20, 0x8b, 0x63, + 0x02, 0x6c, 0x00, 0x73, 0x00, 0x78, 0x00, 0x95, + 0x02, 0x80, 0x81, 0x00, 0x93, 0x88, 0x81, 0x20, + 0xc5, 0x20, 0x81, 0xa8, 0x00, 0x81, 0x91, 0x03, + 0x81, 0x95, 0x03, 0x81, 0x97, 0x03, 0x81, 0x99, + 0x03, 0x81, 0x00, 0x00, 0x00, 0x9f, 0x03, 0x81, + 0x00, 0x00, 0x00, 0xa5, 0x03, 0x81, 0xa9, 0x03, + 0x81, 0xca, 0x03, 0x81, 0x01, 0x03, 0x98, 0x07, + 0xa4, 0x07, 0xb0, 0x00, 0xb4, 0x00, 0xb6, 0x00, + 0xb8, 0x00, 0xca, 0x00, 0x01, 0x03, 0xb8, 0x07, + 0xc4, 0x07, 0xbe, 0x00, 0xc4, 0x00, 0xc8, 0x00, + 0xa5, 0x03, 0x0d, 0x13, 0x00, 0x01, 0x03, 0xd1, + 0x00, 0xd1, 0x07, 0xc6, 0x03, 0xc0, 0x03, 0xba, + 0x03, 0xc1, 0x03, 0xc2, 0x03, 0x00, 0x00, 0x98, + 0x03, 0xb5, 0x03, 0x15, 0x04, 0x80, 0x15, 0x04, + 0x88, 0x00, 0x00, 0x00, 0x13, 0x04, 0x81, 0x06, + 0x04, 0x88, 0x1a, 0x04, 0x81, 0x18, 0x04, 0x80, + 0x23, 0x04, 0x86, 0x18, 0x04, 0x86, 0x38, 0x04, + 0x86, 0x35, 0x04, 0x80, 0x35, 0x04, 0x88, 0x00, + 0x00, 0x00, 0x33, 0x04, 0x81, 0x56, 0x04, 0x88, + 0x3a, 0x04, 0x81, 0x38, 0x04, 0x80, 0x43, 0x04, + 0x86, 0x74, 0x04, 0x8f, 0x16, 0x04, 0x86, 0x10, + 0x04, 0x86, 0x10, 0x04, 0x88, 0x15, 0x04, 0x86, + 0xd8, 0x04, 0x88, 0x16, 0x04, 0x88, 0x17, 0x04, + 0x88, 0x18, 0x04, 0x84, 0x18, 0x04, 0x88, 0x1e, + 0x04, 0x88, 0xe8, 0x04, 0x88, 0x2d, 0x04, 0x88, + 0x23, 0x04, 0x84, 0x23, 0x04, 0x88, 0x23, 0x04, + 0x8b, 0x27, 0x04, 0x88, 0x2b, 0x04, 0x88, 0x65, + 0x05, 0x82, 0x05, 0x27, 0x06, 0x00, 0x2c, 0x00, + 0x2d, 0x21, 0x2d, 0x00, 0x2e, 0x23, 0x2d, 0x27, + 0x06, 0x00, 0x4d, 0x21, 0x4d, 0xa0, 0x4d, 0x23, + 0x4d, 0xd5, 0x06, 0x54, 0x06, 0x00, 0x00, 0x00, + 0x00, 0xc1, 0x06, 0x54, 0x06, 0xd2, 0x06, 0x54, + 0x06, 0x28, 0x09, 0x3c, 0x09, 0x30, 0x09, 0x3c, + 0x09, 0x33, 0x09, 0x3c, 0x09, 0x15, 0x09, 0x00, + 0x27, 0x01, 0x27, 0x02, 0x27, 0x07, 0x27, 0x0c, + 0x27, 0x0d, 0x27, 0x16, 0x27, 0x1a, 0x27, 0xbe, + 0x09, 0x09, 0x00, 0x09, 0x19, 0xa1, 0x09, 0xbc, + 0x09, 0xaf, 0x09, 0xbc, 0x09, 0x32, 0x0a, 0x3c, + 0x0a, 0x38, 0x0a, 0x3c, 0x0a, 0x16, 0x0a, 0x00, + 0x26, 0x01, 0x26, 0x06, 0x26, 0x2b, 0x0a, 0x3c, + 0x0a, 0x47, 0x0b, 0x56, 0x0b, 0x3e, 0x0b, 0x09, + 0x00, 0x09, 0x19, 0x21, 0x0b, 0x3c, 0x0b, 0x92, + 0x0b, 0xd7, 0x0b, 0xbe, 0x0b, 0x08, 0x00, 0x09, + 0x00, 0x08, 0x19, 0x46, 0x0c, 0x56, 0x0c, 0xbf, + 0x0c, 0xd5, 0x0c, 0xc6, 0x0c, 0xd5, 0x0c, 0xc2, + 0x0c, 0x04, 0x00, 0x08, 0x13, 0x3e, 0x0d, 0x08, + 0x00, 0x09, 0x00, 0x08, 0x19, 0xd9, 0x0d, 0xca, + 0x0d, 0xca, 0x0d, 0x0f, 0x05, 0x12, 0x00, 0x0f, + 0x15, 0x4d, 0x0e, 0x32, 0x0e, 0xcd, 0x0e, 0xb2, + 0x0e, 0x99, 0x0e, 0x12, 0x00, 0x12, 0x08, 0x42, + 0x0f, 0xb7, 0x0f, 0x4c, 0x0f, 0xb7, 0x0f, 0x51, + 0x0f, 0xb7, 0x0f, 0x56, 0x0f, 0xb7, 0x0f, 0x5b, + 0x0f, 0xb7, 0x0f, 0x40, 0x0f, 0xb5, 0x0f, 0x71, + 0x0f, 0x72, 0x0f, 0x71, 0x0f, 0x00, 0x03, 0x41, + 0x0f, 0xb2, 0x0f, 0x81, 0x0f, 0xb3, 0x0f, 0x80, + 0x0f, 0xb3, 0x0f, 0x81, 0x0f, 0x71, 0x0f, 0x80, + 0x0f, 0x92, 0x0f, 0xb7, 0x0f, 0x9c, 0x0f, 0xb7, + 0x0f, 0xa1, 0x0f, 0xb7, 0x0f, 0xa6, 0x0f, 0xb7, + 0x0f, 0xab, 0x0f, 0xb7, 0x0f, 0x90, 0x0f, 0xb5, + 0x0f, 0x25, 0x10, 0x2e, 0x10, 0x05, 0x1b, 0x35, + 0x1b, 0x00, 0x00, 0x00, 0x00, 0x07, 0x1b, 0x35, + 0x1b, 0x00, 0x00, 0x00, 0x00, 0x09, 0x1b, 0x35, + 0x1b, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x1b, 0x35, + 0x1b, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x1b, 0x35, + 0x1b, 0x11, 0x1b, 0x35, 0x1b, 0x3a, 0x1b, 0x35, + 0x1b, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x1b, 0x35, + 0x1b, 0x3e, 0x1b, 0x35, 0x1b, 0x42, 0x1b, 0x35, + 0x1b, 0x41, 0x00, 0xc6, 0x00, 0x42, 0x00, 0x00, + 0x00, 0x44, 0x00, 0x45, 0x00, 0x8e, 0x01, 0x47, + 0x00, 0x4f, 0x00, 0x22, 0x02, 0x50, 0x00, 0x52, + 0x00, 0x54, 0x00, 0x55, 0x00, 0x57, 0x00, 0x61, + 0x00, 0x50, 0x02, 0x51, 0x02, 0x02, 0x1d, 0x62, + 0x00, 0x64, 0x00, 0x65, 0x00, 0x59, 0x02, 0x5b, + 0x02, 0x5c, 0x02, 0x67, 0x00, 0x00, 0x00, 0x6b, + 0x00, 0x6d, 0x00, 0x4b, 0x01, 0x6f, 0x00, 0x54, + 0x02, 0x16, 0x1d, 0x17, 0x1d, 0x70, 0x00, 0x74, + 0x00, 0x75, 0x00, 0x1d, 0x1d, 0x6f, 0x02, 0x76, + 0x00, 0x25, 0x1d, 0xb2, 0x03, 0xb3, 0x03, 0xb4, + 0x03, 0xc6, 0x03, 0xc7, 0x03, 0x69, 0x00, 0x72, + 0x00, 0x75, 0x00, 0x76, 0x00, 0xb2, 0x03, 0xb3, + 0x03, 0xc1, 0x03, 0xc6, 0x03, 0xc7, 0x03, 0x52, + 0x02, 0x63, 0x00, 0x55, 0x02, 0xf0, 0x00, 0x5c, + 0x02, 0x66, 0x00, 0x5f, 0x02, 0x61, 0x02, 0x65, + 0x02, 0x68, 0x02, 0x69, 0x02, 0x6a, 0x02, 0x7b, + 0x1d, 0x9d, 0x02, 0x6d, 0x02, 0x85, 0x1d, 0x9f, + 0x02, 0x71, 0x02, 0x70, 0x02, 0x72, 0x02, 0x73, + 0x02, 0x74, 0x02, 0x75, 0x02, 0x78, 0x02, 0x82, + 0x02, 0x83, 0x02, 0xab, 0x01, 0x89, 0x02, 0x8a, + 0x02, 0x1c, 0x1d, 0x8b, 0x02, 0x8c, 0x02, 0x7a, + 0x00, 0x90, 0x02, 0x91, 0x02, 0x92, 0x02, 0xb8, + 0x03, 0x41, 0x00, 0xa5, 0x42, 0x00, 0x87, 0x42, + 0x00, 0xa3, 0x42, 0x00, 0xb1, 0xc7, 0x00, 0x81, + 0x44, 0x00, 0x87, 0x44, 0x00, 0xa3, 0x44, 0x00, + 0xb1, 0x44, 0x00, 0xa7, 0x44, 0x00, 0xad, 0x12, + 0x01, 0x80, 0x12, 0x01, 0x81, 0x45, 0x00, 0xad, + 0x45, 0x00, 0xb0, 0x28, 0x02, 0x86, 0x46, 0x00, + 0x87, 0x47, 0x00, 0x84, 0x48, 0x00, 0x87, 0x48, + 0x00, 0xa3, 0x48, 0x00, 0x88, 0x48, 0x00, 0xa7, + 0x48, 0x00, 0xae, 0x49, 0x00, 0xb0, 0xcf, 0x00, + 0x81, 0x4b, 0x00, 0x81, 0x4b, 0x00, 0xa3, 0x4b, + 0x00, 0xb1, 0x4c, 0x00, 0xa3, 0x36, 0x1e, 0x84, + 0x4c, 0xb1, 0x4c, 0xad, 0x4d, 0x81, 0x4d, 0x87, + 0x4d, 0xa3, 0x4e, 0x87, 0x4e, 0xa3, 0x4e, 0xb1, + 0x4e, 0xad, 0xd5, 0x00, 0x81, 0xd5, 0x00, 0x88, + 0x4c, 0x01, 0x80, 0x4c, 0x01, 0x81, 0x50, 0x00, + 0x81, 0x50, 0x00, 0x87, 0x52, 0x00, 0x87, 0x52, + 0x00, 0xa3, 0x5a, 0x1e, 0x84, 0x52, 0x00, 0xb1, + 0x53, 0x00, 0x87, 0x53, 0x00, 0xa3, 0x5a, 0x01, + 0x87, 0x60, 0x01, 0x87, 0x62, 0x1e, 0x87, 0x54, + 0x00, 0x87, 0x54, 0x00, 0xa3, 0x54, 0x00, 0xb1, + 0x54, 0x00, 0xad, 0x55, 0x00, 0xa4, 0x55, 0x00, + 0xb0, 0x55, 0x00, 0xad, 0x68, 0x01, 0x81, 0x6a, + 0x01, 0x88, 0x56, 0x83, 0x56, 0xa3, 0x57, 0x80, + 0x57, 0x81, 0x57, 0x88, 0x57, 0x87, 0x57, 0xa3, + 0x58, 0x87, 0x58, 0x88, 0x59, 0x87, 0x5a, 0x82, + 0x5a, 0xa3, 0x5a, 0xb1, 0x68, 0xb1, 0x74, 0x88, + 0x77, 0x8a, 0x79, 0x8a, 0x61, 0x00, 0xbe, 0x02, + 0x7f, 0x01, 0x87, 0x41, 0x00, 0xa3, 0x41, 0x00, + 0x89, 0xc2, 0x00, 0x81, 0xc2, 0x00, 0x80, 0xc2, + 0x00, 0x89, 0xc2, 0x00, 0x83, 0xa0, 0x1e, 0x82, + 0x02, 0x01, 0x81, 0x02, 0x01, 0x80, 0x02, 0x01, + 0x89, 0x02, 0x01, 0x83, 0xa0, 0x1e, 0x86, 0x45, + 0x00, 0xa3, 0x45, 0x00, 0x89, 0x45, 0x00, 0x83, + 0xca, 0x00, 0x81, 0xca, 0x00, 0x80, 0xca, 0x00, + 0x89, 0xca, 0x00, 0x83, 0xb8, 0x1e, 0x82, 0x49, + 0x00, 0x89, 0x49, 0x00, 0xa3, 0x4f, 0x00, 0xa3, + 0x4f, 0x00, 0x89, 0xd4, 0x00, 0x81, 0xd4, 0x00, + 0x80, 0xd4, 0x00, 0x89, 0xd4, 0x00, 0x83, 0xcc, + 0x1e, 0x82, 0xa0, 0x01, 0x81, 0xa0, 0x01, 0x80, + 0xa0, 0x01, 0x89, 0xa0, 0x01, 0x83, 0xa0, 0x01, + 0xa3, 0x55, 0x00, 0xa3, 0x55, 0x00, 0x89, 0xaf, + 0x01, 0x81, 0xaf, 0x01, 0x80, 0xaf, 0x01, 0x89, + 0xaf, 0x01, 0x83, 0xaf, 0x01, 0xa3, 0x59, 0x00, + 0x80, 0x59, 0x00, 0xa3, 0x59, 0x00, 0x89, 0x59, + 0x00, 0x83, 0xb1, 0x03, 0x13, 0x03, 0x00, 0x1f, + 0x80, 0x00, 0x1f, 0x81, 0x00, 0x1f, 0xc2, 0x91, + 0x03, 0x13, 0x03, 0x08, 0x1f, 0x80, 0x08, 0x1f, + 0x81, 0x08, 0x1f, 0xc2, 0xb5, 0x03, 0x13, 0x03, + 0x10, 0x1f, 0x80, 0x10, 0x1f, 0x81, 0x95, 0x03, + 0x13, 0x03, 0x18, 0x1f, 0x80, 0x18, 0x1f, 0x81, + 0xb7, 0x03, 0x93, 0xb7, 0x03, 0x94, 0x20, 0x1f, + 0x80, 0x21, 0x1f, 0x80, 0x20, 0x1f, 0x81, 0x21, + 0x1f, 0x81, 0x20, 0x1f, 0xc2, 0x21, 0x1f, 0xc2, + 0x97, 0x03, 0x93, 0x97, 0x03, 0x94, 0x28, 0x1f, + 0x80, 0x29, 0x1f, 0x80, 0x28, 0x1f, 0x81, 0x29, + 0x1f, 0x81, 0x28, 0x1f, 0xc2, 0x29, 0x1f, 0xc2, + 0xb9, 0x03, 0x93, 0xb9, 0x03, 0x94, 0x30, 0x1f, + 0x80, 0x31, 0x1f, 0x80, 0x30, 0x1f, 0x81, 0x31, + 0x1f, 0x81, 0x30, 0x1f, 0xc2, 0x31, 0x1f, 0xc2, + 0x99, 0x03, 0x93, 0x99, 0x03, 0x94, 0x38, 0x1f, + 0x80, 0x39, 0x1f, 0x80, 0x38, 0x1f, 0x81, 0x39, + 0x1f, 0x81, 0x38, 0x1f, 0xc2, 0x39, 0x1f, 0xc2, + 0xbf, 0x03, 0x93, 0xbf, 0x03, 0x94, 0x40, 0x1f, + 0x80, 0x40, 0x1f, 0x81, 0x9f, 0x03, 0x13, 0x03, + 0x48, 0x1f, 0x80, 0x48, 0x1f, 0x81, 0xc5, 0x03, + 0x13, 0x03, 0x50, 0x1f, 0x80, 0x50, 0x1f, 0x81, + 0x50, 0x1f, 0xc2, 0xa5, 0x03, 0x94, 0x00, 0x00, + 0x00, 0x59, 0x1f, 0x80, 0x00, 0x00, 0x00, 0x59, + 0x1f, 0x81, 0x00, 0x00, 0x00, 0x59, 0x1f, 0xc2, + 0xc9, 0x03, 0x93, 0xc9, 0x03, 0x94, 0x60, 0x1f, + 0x80, 0x61, 0x1f, 0x80, 0x60, 0x1f, 0x81, 0x61, + 0x1f, 0x81, 0x60, 0x1f, 0xc2, 0x61, 0x1f, 0xc2, + 0xa9, 0x03, 0x93, 0xa9, 0x03, 0x94, 0x68, 0x1f, + 0x80, 0x69, 0x1f, 0x80, 0x68, 0x1f, 0x81, 0x69, + 0x1f, 0x81, 0x68, 0x1f, 0xc2, 0x69, 0x1f, 0xc2, + 0xb1, 0x03, 0x80, 0xb5, 0x03, 0x80, 0xb7, 0x03, + 0x80, 0xb9, 0x03, 0x80, 0xbf, 0x03, 0x80, 0xc5, + 0x03, 0x80, 0xc9, 0x03, 0x80, 0x00, 0x1f, 0x45, + 0x03, 0x20, 0x1f, 0x45, 0x03, 0x60, 0x1f, 0x45, + 0x03, 0xb1, 0x03, 0x86, 0xb1, 0x03, 0x84, 0x70, + 0x1f, 0xc5, 0xb1, 0x03, 0xc5, 0xac, 0x03, 0xc5, + 0x00, 0x00, 0x00, 0xb1, 0x03, 0xc2, 0xb6, 0x1f, + 0xc5, 0x91, 0x03, 0x86, 0x91, 0x03, 0x84, 0x91, + 0x03, 0x80, 0x91, 0x03, 0xc5, 0x20, 0x93, 0x20, + 0x93, 0x20, 0xc2, 0xa8, 0x00, 0xc2, 0x74, 0x1f, + 0xc5, 0xb7, 0x03, 0xc5, 0xae, 0x03, 0xc5, 0x00, + 0x00, 0x00, 0xb7, 0x03, 0xc2, 0xc6, 0x1f, 0xc5, + 0x95, 0x03, 0x80, 0x97, 0x03, 0x80, 0x97, 0x03, + 0xc5, 0xbf, 0x1f, 0x80, 0xbf, 0x1f, 0x81, 0xbf, + 0x1f, 0xc2, 0xb9, 0x03, 0x86, 0xb9, 0x03, 0x84, + 0xca, 0x03, 0x80, 0x00, 0x03, 0xb9, 0x42, 0xca, + 0x42, 0x99, 0x06, 0x99, 0x04, 0x99, 0x00, 0xfe, + 0x1f, 0x80, 0xfe, 0x1f, 0x81, 0xfe, 0x1f, 0xc2, + 0xc5, 0x03, 0x86, 0xc5, 0x03, 0x84, 0xcb, 0x03, + 0x80, 0x00, 0x03, 0xc1, 0x13, 0xc1, 0x14, 0xc5, + 0x42, 0xcb, 0x42, 0xa5, 0x06, 0xa5, 0x04, 0xa5, + 0x00, 0xa1, 0x03, 0x94, 0xa8, 0x00, 0x80, 0x85, + 0x03, 0x60, 0x00, 0x7c, 0x1f, 0xc5, 0xc9, 0x03, + 0xc5, 0xce, 0x03, 0xc5, 0x00, 0x00, 0x00, 0xc9, + 0x03, 0xc2, 0xf6, 0x1f, 0xc5, 0x9f, 0x03, 0x80, + 0xa9, 0x03, 0x80, 0xa9, 0x03, 0xc5, 0x20, 0x94, + 0x02, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0xb3, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x32, 0x20, 0x32, 0x20, 0x32, 0x20, + 0x00, 0x00, 0x00, 0x35, 0x20, 0x35, 0x20, 0x35, + 0x20, 0x00, 0x00, 0x00, 0x21, 0x21, 0x00, 0x00, + 0x20, 0x85, 0x3f, 0x3f, 0x3f, 0x21, 0x21, 0x3f, + 0x32, 0x20, 0x00, 0x00, 0x00, 0x00, 0x30, 0x69, + 0x00, 0x00, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, + 0x2b, 0x3d, 0x28, 0x29, 0x6e, 0x30, 0x00, 0x2b, + 0x00, 0x12, 0x22, 0x3d, 0x00, 0x28, 0x00, 0x29, + 0x00, 0x00, 0x00, 0x61, 0x00, 0x65, 0x00, 0x6f, + 0x00, 0x78, 0x00, 0x59, 0x02, 0x68, 0x6b, 0x6c, + 0x6d, 0x6e, 0x70, 0x73, 0x74, 0x52, 0x73, 0x61, + 0x2f, 0x63, 0x61, 0x2f, 0x73, 0xb0, 0x00, 0x43, + 0x63, 0x2f, 0x6f, 0x63, 0x2f, 0x75, 0xb0, 0x00, + 0x46, 0x48, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x20, + 0xdf, 0x01, 0x01, 0x04, 0x24, 0x4e, 0x6f, 0x50, + 0x51, 0x52, 0x52, 0x52, 0x53, 0x4d, 0x54, 0x45, + 0x4c, 0x54, 0x4d, 0x4b, 0x00, 0xc5, 0x00, 0x42, + 0x43, 0x00, 0x65, 0x45, 0x46, 0x00, 0x4d, 0x6f, + 0xd0, 0x05, 0x46, 0x41, 0x58, 0xc0, 0x03, 0xb3, + 0x03, 0x93, 0x03, 0xa0, 0x03, 0x11, 0x22, 0x44, + 0x64, 0x65, 0x69, 0x6a, 0x31, 0xd0, 0x37, 0x31, + 0xd0, 0x39, 0x31, 0xd0, 0x31, 0x30, 0x31, 0xd0, + 0x33, 0x32, 0xd0, 0x33, 0x31, 0xd0, 0x35, 0x32, + 0xd0, 0x35, 0x33, 0xd0, 0x35, 0x34, 0xd0, 0x35, + 0x31, 0xd0, 0x36, 0x35, 0xd0, 0x36, 0x31, 0xd0, + 0x38, 0x33, 0xd0, 0x38, 0x35, 0xd0, 0x38, 0x37, + 0xd0, 0x38, 0x31, 0xd0, 0x49, 0x49, 0x49, 0x49, + 0x49, 0x49, 0x56, 0x56, 0x49, 0x56, 0x49, 0x49, + 0x56, 0x49, 0x49, 0x49, 0x49, 0x58, 0x58, 0x49, + 0x58, 0x49, 0x49, 0x4c, 0x43, 0x44, 0x4d, 0x69, + 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x76, 0x76, + 0x69, 0x76, 0x69, 0x69, 0x76, 0x69, 0x69, 0x69, + 0x69, 0x78, 0x78, 0x69, 0x78, 0x69, 0x69, 0x6c, + 0x63, 0x64, 0x6d, 0x30, 0xd0, 0x33, 0x90, 0x21, + 0xb8, 0x92, 0x21, 0xb8, 0x94, 0x21, 0xb8, 0xd0, + 0x21, 0xb8, 0xd4, 0x21, 0xb8, 0xd2, 0x21, 0xb8, + 0x03, 0x22, 0xb8, 0x08, 0x22, 0xb8, 0x0b, 0x22, + 0xb8, 0x23, 0x22, 0xb8, 0x00, 0x00, 0x00, 0x25, + 0x22, 0xb8, 0x2b, 0x22, 0x2b, 0x22, 0x2b, 0x22, + 0x00, 0x00, 0x00, 0x2e, 0x22, 0x2e, 0x22, 0x2e, + 0x22, 0x00, 0x00, 0x00, 0x3c, 0x22, 0xb8, 0x43, + 0x22, 0xb8, 0x45, 0x22, 0xb8, 0x00, 0x00, 0x00, + 0x48, 0x22, 0xb8, 0x3d, 0x00, 0xb8, 0x00, 0x00, + 0x00, 0x61, 0x22, 0xb8, 0x4d, 0x22, 0xb8, 0x3c, + 0x00, 0xb8, 0x3e, 0x00, 0xb8, 0x64, 0x22, 0xb8, + 0x65, 0x22, 0xb8, 0x72, 0x22, 0xb8, 0x76, 0x22, + 0xb8, 0x7a, 0x22, 0xb8, 0x82, 0x22, 0xb8, 0x86, + 0x22, 0xb8, 0xa2, 0x22, 0xb8, 0xa8, 0x22, 0xb8, + 0xa9, 0x22, 0xb8, 0xab, 0x22, 0xb8, 0x7c, 0x22, + 0xb8, 0x91, 0x22, 0xb8, 0xb2, 0x22, 0x38, 0x03, + 0x08, 0x30, 0x31, 0x00, 0x31, 0x00, 0x30, 0x00, + 0x32, 0x30, 0x28, 0x00, 0x31, 0x00, 0x29, 0x00, + 0x28, 0x00, 0x31, 0x00, 0x30, 0x00, 0x29, 0x00, + 0x28, 0x32, 0x30, 0x29, 0x31, 0x00, 0x2e, 0x00, + 0x31, 0x00, 0x30, 0x00, 0x2e, 0x00, 0x32, 0x30, + 0x2e, 0x28, 0x00, 0x61, 0x00, 0x29, 0x00, 0x41, + 0x00, 0x61, 0x00, 0x2b, 0x22, 0x00, 0x00, 0x00, + 0x00, 0x3a, 0x3a, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0xdd, 0x2a, 0xb8, 0x6a, 0x56, 0x00, 0x4e, + 0x00, 0x28, 0x36, 0x3f, 0x59, 0x85, 0x8c, 0xa0, + 0xba, 0x3f, 0x51, 0x00, 0x26, 0x2c, 0x43, 0x57, + 0x6c, 0xa1, 0xb6, 0xc1, 0x9b, 0x52, 0x00, 0x5e, + 0x7a, 0x7f, 0x9d, 0xa6, 0xc1, 0xce, 0xe7, 0xb6, + 0x53, 0xc8, 0x53, 0xe3, 0x53, 0xd7, 0x56, 0x1f, + 0x57, 0xeb, 0x58, 0x02, 0x59, 0x0a, 0x59, 0x15, + 0x59, 0x27, 0x59, 0x73, 0x59, 0x50, 0x5b, 0x80, + 0x5b, 0xf8, 0x5b, 0x0f, 0x5c, 0x22, 0x5c, 0x38, + 0x5c, 0x6e, 0x5c, 0x71, 0x5c, 0xdb, 0x5d, 0xe5, + 0x5d, 0xf1, 0x5d, 0xfe, 0x5d, 0x72, 0x5e, 0x7a, + 0x5e, 0x7f, 0x5e, 0xf4, 0x5e, 0xfe, 0x5e, 0x0b, + 0x5f, 0x13, 0x5f, 0x50, 0x5f, 0x61, 0x5f, 0x73, + 0x5f, 0xc3, 0x5f, 0x08, 0x62, 0x36, 0x62, 0x4b, + 0x62, 0x2f, 0x65, 0x34, 0x65, 0x87, 0x65, 0x97, + 0x65, 0xa4, 0x65, 0xb9, 0x65, 0xe0, 0x65, 0xe5, + 0x65, 0xf0, 0x66, 0x08, 0x67, 0x28, 0x67, 0x20, + 0x6b, 0x62, 0x6b, 0x79, 0x6b, 0xb3, 0x6b, 0xcb, + 0x6b, 0xd4, 0x6b, 0xdb, 0x6b, 0x0f, 0x6c, 0x14, + 0x6c, 0x34, 0x6c, 0x6b, 0x70, 0x2a, 0x72, 0x36, + 0x72, 0x3b, 0x72, 0x3f, 0x72, 0x47, 0x72, 0x59, + 0x72, 0x5b, 0x72, 0xac, 0x72, 0x84, 0x73, 0x89, + 0x73, 0xdc, 0x74, 0xe6, 0x74, 0x18, 0x75, 0x1f, + 0x75, 0x28, 0x75, 0x30, 0x75, 0x8b, 0x75, 0x92, + 0x75, 0x76, 0x76, 0x7d, 0x76, 0xae, 0x76, 0xbf, + 0x76, 0xee, 0x76, 0xdb, 0x77, 0xe2, 0x77, 0xf3, + 0x77, 0x3a, 0x79, 0xb8, 0x79, 0xbe, 0x79, 0x74, + 0x7a, 0xcb, 0x7a, 0xf9, 0x7a, 0x73, 0x7c, 0xf8, + 0x7c, 0x36, 0x7f, 0x51, 0x7f, 0x8a, 0x7f, 0xbd, + 0x7f, 0x01, 0x80, 0x0c, 0x80, 0x12, 0x80, 0x33, + 0x80, 0x7f, 0x80, 0x89, 0x80, 0xe3, 0x81, 0x00, + 0x07, 0x10, 0x19, 0x29, 0x38, 0x3c, 0x8b, 0x8f, + 0x95, 0x4d, 0x86, 0x6b, 0x86, 0x40, 0x88, 0x4c, + 0x88, 0x63, 0x88, 0x7e, 0x89, 0x8b, 0x89, 0xd2, + 0x89, 0x00, 0x8a, 0x37, 0x8c, 0x46, 0x8c, 0x55, + 0x8c, 0x78, 0x8c, 0x9d, 0x8c, 0x64, 0x8d, 0x70, + 0x8d, 0xb3, 0x8d, 0xab, 0x8e, 0xca, 0x8e, 0x9b, + 0x8f, 0xb0, 0x8f, 0xb5, 0x8f, 0x91, 0x90, 0x49, + 0x91, 0xc6, 0x91, 0xcc, 0x91, 0xd1, 0x91, 0x77, + 0x95, 0x80, 0x95, 0x1c, 0x96, 0xb6, 0x96, 0xb9, + 0x96, 0xe8, 0x96, 0x51, 0x97, 0x5e, 0x97, 0x62, + 0x97, 0x69, 0x97, 0xcb, 0x97, 0xed, 0x97, 0xf3, + 0x97, 0x01, 0x98, 0xa8, 0x98, 0xdb, 0x98, 0xdf, + 0x98, 0x96, 0x99, 0x99, 0x99, 0xac, 0x99, 0xa8, + 0x9a, 0xd8, 0x9a, 0xdf, 0x9a, 0x25, 0x9b, 0x2f, + 0x9b, 0x32, 0x9b, 0x3c, 0x9b, 0x5a, 0x9b, 0xe5, + 0x9c, 0x75, 0x9e, 0x7f, 0x9e, 0xa5, 0x9e, 0x00, + 0x16, 0x1e, 0x28, 0x2c, 0x54, 0x58, 0x69, 0x6e, + 0x7b, 0x96, 0xa5, 0xad, 0xe8, 0xf7, 0xfb, 0x12, + 0x30, 0x00, 0x00, 0x41, 0x53, 0x44, 0x53, 0x45, + 0x53, 0x4b, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x4d, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x4f, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x51, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x53, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x55, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x57, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x59, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x5b, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x5d, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x5f, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x61, 0x30, 0x99, 0x30, 0x64, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0x66, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0x68, 0x30, 0x99, + 0x30, 0x6f, 0x30, 0x99, 0x30, 0x72, 0x30, 0x99, + 0x30, 0x75, 0x30, 0x99, 0x30, 0x78, 0x30, 0x99, + 0x30, 0x7b, 0x30, 0x99, 0x30, 0x46, 0x30, 0x99, + 0x30, 0x20, 0x00, 0x99, 0x30, 0x9d, 0x30, 0x99, + 0x30, 0x88, 0x30, 0x8a, 0x30, 0xab, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xad, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xaf, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xb1, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xb3, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xb5, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xb7, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xb9, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xbb, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xbd, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xbf, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xc1, 0x30, 0x99, + 0x30, 0xc4, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0xc6, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0xc8, 0x30, 0x99, 0x30, 0xcf, 0x30, 0x99, + 0x30, 0xd2, 0x30, 0x99, 0x30, 0xd5, 0x30, 0x99, + 0x30, 0xd8, 0x30, 0x99, 0x30, 0xdb, 0x30, 0x99, + 0x30, 0xa6, 0x30, 0x99, 0x30, 0xef, 0x30, 0x99, + 0x30, 0xfd, 0x30, 0x99, 0x30, 0xb3, 0x30, 0xc8, + 0x30, 0x00, 0x11, 0x00, 0x01, 0xaa, 0x02, 0xac, + 0xad, 0x03, 0x04, 0x05, 0xb0, 0xb1, 0xb2, 0xb3, + 0xb4, 0xb5, 0x1a, 0x06, 0x07, 0x08, 0x21, 0x09, + 0x11, 0x61, 0x11, 0x14, 0x11, 0x4c, 0x00, 0x01, + 0xb3, 0xb4, 0xb8, 0xba, 0xbf, 0xc3, 0xc5, 0x08, + 0xc9, 0xcb, 0x09, 0x0a, 0x0c, 0x0e, 0x0f, 0x13, + 0x15, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1e, 0x22, + 0x2c, 0x33, 0x38, 0xdd, 0xde, 0x43, 0x44, 0x45, + 0x70, 0x71, 0x74, 0x7d, 0x7e, 0x80, 0x8a, 0x8d, + 0x00, 0x4e, 0x8c, 0x4e, 0x09, 0x4e, 0xdb, 0x56, + 0x0a, 0x4e, 0x2d, 0x4e, 0x0b, 0x4e, 0x32, 0x75, + 0x59, 0x4e, 0x19, 0x4e, 0x01, 0x4e, 0x29, 0x59, + 0x30, 0x57, 0xba, 0x4e, 0x28, 0x00, 0x29, 0x00, + 0x00, 0x11, 0x02, 0x11, 0x03, 0x11, 0x05, 0x11, + 0x06, 0x11, 0x07, 0x11, 0x09, 0x11, 0x0b, 0x11, + 0x0c, 0x11, 0x0e, 0x11, 0x0f, 0x11, 0x10, 0x11, + 0x11, 0x11, 0x12, 0x11, 0x28, 0x00, 0x00, 0x11, + 0x61, 0x11, 0x29, 0x00, 0x28, 0x00, 0x02, 0x11, + 0x61, 0x11, 0x29, 0x00, 0x28, 0x00, 0x05, 0x11, + 0x61, 0x11, 0x29, 0x00, 0x28, 0x00, 0x09, 0x11, + 0x61, 0x11, 0x29, 0x00, 0x28, 0x00, 0x0b, 0x11, + 0x61, 0x11, 0x29, 0x00, 0x28, 0x00, 0x0e, 0x11, + 0x61, 0x11, 0x29, 0x00, 0x28, 0x00, 0x0c, 0x11, + 0x6e, 0x11, 0x29, 0x00, 0x28, 0x00, 0x0b, 0x11, + 0x69, 0x11, 0x0c, 0x11, 0x65, 0x11, 0xab, 0x11, + 0x29, 0x00, 0x28, 0x00, 0x0b, 0x11, 0x69, 0x11, + 0x12, 0x11, 0x6e, 0x11, 0x29, 0x00, 0x28, 0x00, + 0x29, 0x00, 0x00, 0x4e, 0x8c, 0x4e, 0x09, 0x4e, + 0xdb, 0x56, 0x94, 0x4e, 0x6d, 0x51, 0x03, 0x4e, + 0x6b, 0x51, 0x5d, 0x4e, 0x41, 0x53, 0x08, 0x67, + 0x6b, 0x70, 0x34, 0x6c, 0x28, 0x67, 0xd1, 0x91, + 0x1f, 0x57, 0xe5, 0x65, 0x2a, 0x68, 0x09, 0x67, + 0x3e, 0x79, 0x0d, 0x54, 0x79, 0x72, 0xa1, 0x8c, + 0x5d, 0x79, 0xb4, 0x52, 0xe3, 0x4e, 0x7c, 0x54, + 0x66, 0x5b, 0xe3, 0x76, 0x01, 0x4f, 0xc7, 0x8c, + 0x54, 0x53, 0x6d, 0x79, 0x11, 0x4f, 0xea, 0x81, + 0xf3, 0x81, 0x4f, 0x55, 0x7c, 0x5e, 0x87, 0x65, + 0x8f, 0x7b, 0x50, 0x54, 0x45, 0x32, 0x00, 0x31, + 0x00, 0x33, 0x00, 0x30, 0x00, 0x00, 0x11, 0x00, + 0x02, 0x03, 0x05, 0x06, 0x07, 0x09, 0x0b, 0x0c, + 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x00, 0x11, 0x00, + 0x61, 0x02, 0x61, 0x03, 0x61, 0x05, 0x61, 0x06, + 0x61, 0x07, 0x61, 0x09, 0x61, 0x0b, 0x61, 0x0c, + 0x61, 0x0e, 0x11, 0x61, 0x11, 0x00, 0x11, 0x0e, + 0x61, 0xb7, 0x00, 0x69, 0x0b, 0x11, 0x01, 0x63, + 0x00, 0x69, 0x0b, 0x11, 0x6e, 0x11, 0x00, 0x4e, + 0x8c, 0x4e, 0x09, 0x4e, 0xdb, 0x56, 0x94, 0x4e, + 0x6d, 0x51, 0x03, 0x4e, 0x6b, 0x51, 0x5d, 0x4e, + 0x41, 0x53, 0x08, 0x67, 0x6b, 0x70, 0x34, 0x6c, + 0x28, 0x67, 0xd1, 0x91, 0x1f, 0x57, 0xe5, 0x65, + 0x2a, 0x68, 0x09, 0x67, 0x3e, 0x79, 0x0d, 0x54, + 0x79, 0x72, 0xa1, 0x8c, 0x5d, 0x79, 0xb4, 0x52, + 0xd8, 0x79, 0x37, 0x75, 0x73, 0x59, 0x69, 0x90, + 0x2a, 0x51, 0x70, 0x53, 0xe8, 0x6c, 0x05, 0x98, + 0x11, 0x4f, 0x99, 0x51, 0x63, 0x6b, 0x0a, 0x4e, + 0x2d, 0x4e, 0x0b, 0x4e, 0xe6, 0x5d, 0xf3, 0x53, + 0x3b, 0x53, 0x97, 0x5b, 0x66, 0x5b, 0xe3, 0x76, + 0x01, 0x4f, 0xc7, 0x8c, 0x54, 0x53, 0x1c, 0x59, + 0x33, 0x00, 0x36, 0x00, 0x34, 0x00, 0x30, 0x00, + 0x35, 0x30, 0x31, 0x00, 0x08, 0x67, 0x31, 0x00, + 0x30, 0x00, 0x08, 0x67, 0x48, 0x67, 0x65, 0x72, + 0x67, 0x65, 0x56, 0x4c, 0x54, 0x44, 0xa2, 0x30, + 0x00, 0x02, 0x04, 0x06, 0x08, 0x09, 0x0b, 0x0d, + 0x0f, 0x11, 0x13, 0x15, 0x17, 0x19, 0x1b, 0x1d, + 0x1f, 0x22, 0x24, 0x26, 0x28, 0x29, 0x2a, 0x2b, + 0x2c, 0x2d, 0x30, 0x33, 0x36, 0x39, 0x3c, 0x3d, + 0x3e, 0x3f, 0x40, 0x42, 0x44, 0x46, 0x47, 0x48, + 0x49, 0x4a, 0x4b, 0x4d, 0x4e, 0x4f, 0x50, 0xe4, + 0x4e, 0x8c, 0x54, 0xa1, 0x30, 0x01, 0x30, 0x5b, + 0x27, 0x01, 0x4a, 0x34, 0x00, 0x01, 0x52, 0x39, + 0x01, 0xa2, 0x30, 0x00, 0x5a, 0x49, 0xa4, 0x30, + 0x00, 0x27, 0x4f, 0x0c, 0xa4, 0x30, 0x00, 0x4f, + 0x1d, 0x02, 0x05, 0x4f, 0xa8, 0x30, 0x00, 0x11, + 0x07, 0x54, 0x21, 0xa8, 0x30, 0x00, 0x54, 0x03, + 0x54, 0xa4, 0x30, 0x06, 0x4f, 0x15, 0x06, 0x58, + 0x3c, 0x07, 0x00, 0x46, 0xab, 0x30, 0x00, 0x3e, + 0x18, 0x1d, 0x00, 0x42, 0x3f, 0x51, 0xac, 0x30, + 0x00, 0x41, 0x47, 0x00, 0x47, 0x32, 0xae, 0x30, + 0xac, 0x30, 0xae, 0x30, 0x00, 0x1d, 0x4e, 0xad, + 0x30, 0x00, 0x38, 0x3d, 0x4f, 0x01, 0x3e, 0x13, + 0x4f, 0xad, 0x30, 0xed, 0x30, 0xad, 0x30, 0x00, + 0x40, 0x03, 0x3c, 0x33, 0xad, 0x30, 0x00, 0x40, + 0x34, 0x4f, 0x1b, 0x3e, 0xad, 0x30, 0x00, 0x40, + 0x42, 0x16, 0x1b, 0xb0, 0x30, 0x00, 0x39, 0x30, + 0xa4, 0x30, 0x0c, 0x45, 0x3c, 0x24, 0x4f, 0x0b, + 0x47, 0x18, 0x00, 0x49, 0xaf, 0x30, 0x00, 0x3e, + 0x4d, 0x1e, 0xb1, 0x30, 0x00, 0x4b, 0x08, 0x02, + 0x3a, 0x19, 0x02, 0x4b, 0x2c, 0xa4, 0x30, 0x11, + 0x00, 0x0b, 0x47, 0xb5, 0x30, 0x00, 0x3e, 0x0c, + 0x47, 0x2b, 0xb0, 0x30, 0x07, 0x3a, 0x43, 0x00, + 0xb9, 0x30, 0x02, 0x3a, 0x08, 0x02, 0x3a, 0x0f, + 0x07, 0x43, 0x00, 0xb7, 0x30, 0x10, 0x00, 0x12, + 0x34, 0x11, 0x3c, 0x13, 0x17, 0xa4, 0x30, 0x2a, + 0x1f, 0x24, 0x2b, 0x00, 0x20, 0xbb, 0x30, 0x16, + 0x41, 0x00, 0x38, 0x0d, 0xc4, 0x30, 0x0d, 0x38, + 0x00, 0xd0, 0x30, 0x00, 0x2c, 0x1c, 0x1b, 0xa2, + 0x30, 0x32, 0x00, 0x17, 0x26, 0x49, 0xaf, 0x30, + 0x25, 0x00, 0x3c, 0xb3, 0x30, 0x21, 0x00, 0x20, + 0x38, 0xa1, 0x30, 0x34, 0x00, 0x48, 0x22, 0x28, + 0xa3, 0x30, 0x32, 0x00, 0x59, 0x25, 0xa7, 0x30, + 0x2f, 0x1c, 0x10, 0x00, 0x44, 0xd5, 0x30, 0x00, + 0x14, 0x1e, 0xaf, 0x30, 0x29, 0x00, 0x10, 0x4d, + 0x3c, 0xda, 0x30, 0xbd, 0x30, 0xb8, 0x30, 0x22, + 0x13, 0x1a, 0x20, 0x33, 0x0c, 0x22, 0x3b, 0x01, + 0x22, 0x44, 0x00, 0x21, 0x44, 0x07, 0xa4, 0x30, + 0x39, 0x00, 0x4f, 0x24, 0xc8, 0x30, 0x14, 0x23, + 0x00, 0xdb, 0x30, 0xf3, 0x30, 0xc9, 0x30, 0x14, + 0x2a, 0x00, 0x12, 0x33, 0x22, 0x12, 0x33, 0x2a, + 0xa4, 0x30, 0x3a, 0x00, 0x0b, 0x49, 0xa4, 0x30, + 0x3a, 0x00, 0x47, 0x3a, 0x1f, 0x2b, 0x3a, 0x47, + 0x0b, 0xb7, 0x30, 0x27, 0x3c, 0x00, 0x30, 0x3c, + 0xaf, 0x30, 0x30, 0x00, 0x3e, 0x44, 0xdf, 0x30, + 0xea, 0x30, 0xd0, 0x30, 0x0f, 0x1a, 0x00, 0x2c, + 0x1b, 0xe1, 0x30, 0xac, 0x30, 0xac, 0x30, 0x35, + 0x00, 0x1c, 0x47, 0x35, 0x50, 0x1c, 0x3f, 0xa2, + 0x30, 0x42, 0x5a, 0x27, 0x42, 0x5a, 0x49, 0x44, + 0x00, 0x51, 0xc3, 0x30, 0x27, 0x00, 0x05, 0x28, + 0xea, 0x30, 0xe9, 0x30, 0xd4, 0x30, 0x17, 0x00, + 0x28, 0xd6, 0x30, 0x15, 0x26, 0x00, 0x15, 0xec, + 0x30, 0xe0, 0x30, 0xb2, 0x30, 0x3a, 0x41, 0x16, + 0x00, 0x41, 0xc3, 0x30, 0x2c, 0x00, 0x05, 0x30, + 0x00, 0xb9, 0x70, 0x31, 0x00, 0x30, 0x00, 0xb9, + 0x70, 0x32, 0x00, 0x30, 0x00, 0xb9, 0x70, 0x68, + 0x50, 0x61, 0x64, 0x61, 0x41, 0x55, 0x62, 0x61, + 0x72, 0x6f, 0x56, 0x70, 0x63, 0x64, 0x6d, 0x64, + 0x00, 0x6d, 0x00, 0xb2, 0x00, 0x49, 0x00, 0x55, + 0x00, 0x73, 0x5e, 0x10, 0x62, 0x2d, 0x66, 0x8c, + 0x54, 0x27, 0x59, 0x63, 0x6b, 0x0e, 0x66, 0xbb, + 0x6c, 0x2a, 0x68, 0x0f, 0x5f, 0x1a, 0x4f, 0x3e, + 0x79, 0x70, 0x00, 0x41, 0x6e, 0x00, 0x41, 0xbc, + 0x03, 0x41, 0x6d, 0x00, 0x41, 0x6b, 0x00, 0x41, + 0x4b, 0x00, 0x42, 0x4d, 0x00, 0x42, 0x47, 0x00, + 0x42, 0x63, 0x61, 0x6c, 0x6b, 0x63, 0x61, 0x6c, + 0x70, 0x00, 0x46, 0x6e, 0x00, 0x46, 0xbc, 0x03, + 0x46, 0xbc, 0x03, 0x67, 0x6d, 0x00, 0x67, 0x6b, + 0x00, 0x67, 0x48, 0x00, 0x7a, 0x6b, 0x48, 0x7a, + 0x4d, 0x48, 0x7a, 0x47, 0x48, 0x7a, 0x54, 0x48, + 0x7a, 0xbc, 0x03, 0x13, 0x21, 0x6d, 0x00, 0x13, + 0x21, 0x64, 0x00, 0x13, 0x21, 0x6b, 0x00, 0x13, + 0x21, 0x66, 0x00, 0x6d, 0x6e, 0x00, 0x6d, 0xbc, + 0x03, 0x6d, 0x6d, 0x00, 0x6d, 0x63, 0x00, 0x6d, + 0x6b, 0x00, 0x6d, 0x63, 0x00, 0x0a, 0x0a, 0x4f, + 0x00, 0x0a, 0x4f, 0x6d, 0x00, 0xb2, 0x00, 0x63, + 0x00, 0x08, 0x0a, 0x4f, 0x0a, 0x0a, 0x50, 0x00, + 0x0a, 0x50, 0x6d, 0x00, 0xb3, 0x00, 0x6b, 0x00, + 0x6d, 0x00, 0xb3, 0x00, 0x6d, 0x00, 0x15, 0x22, + 0x73, 0x00, 0x6d, 0x00, 0x15, 0x22, 0x73, 0x00, + 0xb2, 0x00, 0x50, 0x61, 0x6b, 0x50, 0x61, 0x4d, + 0x50, 0x61, 0x47, 0x50, 0x61, 0x72, 0x61, 0x64, + 0x72, 0x61, 0x64, 0xd1, 0x73, 0x72, 0x00, 0x61, + 0x00, 0x64, 0x00, 0x15, 0x22, 0x73, 0x00, 0xb2, + 0x00, 0x70, 0x00, 0x73, 0x6e, 0x00, 0x73, 0xbc, + 0x03, 0x73, 0x6d, 0x00, 0x73, 0x70, 0x00, 0x56, + 0x6e, 0x00, 0x56, 0xbc, 0x03, 0x56, 0x6d, 0x00, + 0x56, 0x6b, 0x00, 0x56, 0x4d, 0x00, 0x56, 0x70, + 0x00, 0x57, 0x6e, 0x00, 0x57, 0xbc, 0x03, 0x57, + 0x6d, 0x00, 0x57, 0x6b, 0x00, 0x57, 0x4d, 0x00, + 0x57, 0x6b, 0x00, 0xa9, 0x03, 0x4d, 0x00, 0xa9, + 0x03, 0x61, 0x2e, 0x6d, 0x2e, 0x42, 0x71, 0x63, + 0x63, 0x63, 0x64, 0x43, 0xd1, 0x6b, 0x67, 0x43, + 0x6f, 0x2e, 0x64, 0x42, 0x47, 0x79, 0x68, 0x61, + 0x48, 0x50, 0x69, 0x6e, 0x4b, 0x4b, 0x4b, 0x4d, + 0x6b, 0x74, 0x6c, 0x6d, 0x6c, 0x6e, 0x6c, 0x6f, + 0x67, 0x6c, 0x78, 0x6d, 0x62, 0x6d, 0x69, 0x6c, + 0x6d, 0x6f, 0x6c, 0x50, 0x48, 0x70, 0x2e, 0x6d, + 0x2e, 0x50, 0x50, 0x4d, 0x50, 0x52, 0x73, 0x72, + 0x53, 0x76, 0x57, 0x62, 0x56, 0xd1, 0x6d, 0x41, + 0xd1, 0x6d, 0x31, 0x00, 0xe5, 0x65, 0x31, 0x00, + 0x30, 0x00, 0xe5, 0x65, 0x32, 0x00, 0x30, 0x00, + 0xe5, 0x65, 0x33, 0x00, 0x30, 0x00, 0xe5, 0x65, + 0x67, 0x61, 0x6c, 0x4a, 0x04, 0x4c, 0x04, 0x53, + 0x43, 0x46, 0x51, 0x26, 0x01, 0x53, 0x01, 0x27, + 0xa7, 0x37, 0xab, 0x6b, 0x02, 0x52, 0xab, 0x48, + 0x8c, 0xf4, 0x66, 0xca, 0x8e, 0xc8, 0x8c, 0xd1, + 0x6e, 0x32, 0x4e, 0xe5, 0x53, 0x9c, 0x9f, 0x9c, + 0x9f, 0x51, 0x59, 0xd1, 0x91, 0x87, 0x55, 0x48, + 0x59, 0xf6, 0x61, 0x69, 0x76, 0x85, 0x7f, 0x3f, + 0x86, 0xba, 0x87, 0xf8, 0x88, 0x8f, 0x90, 0x02, + 0x6a, 0x1b, 0x6d, 0xd9, 0x70, 0xde, 0x73, 0x3d, + 0x84, 0x6a, 0x91, 0xf1, 0x99, 0x82, 0x4e, 0x75, + 0x53, 0x04, 0x6b, 0x1b, 0x72, 0x2d, 0x86, 0x1e, + 0x9e, 0x50, 0x5d, 0xeb, 0x6f, 0xcd, 0x85, 0x64, + 0x89, 0xc9, 0x62, 0xd8, 0x81, 0x1f, 0x88, 0xca, + 0x5e, 0x17, 0x67, 0x6a, 0x6d, 0xfc, 0x72, 0xce, + 0x90, 0x86, 0x4f, 0xb7, 0x51, 0xde, 0x52, 0xc4, + 0x64, 0xd3, 0x6a, 0x10, 0x72, 0xe7, 0x76, 0x01, + 0x80, 0x06, 0x86, 0x5c, 0x86, 0xef, 0x8d, 0x32, + 0x97, 0x6f, 0x9b, 0xfa, 0x9d, 0x8c, 0x78, 0x7f, + 0x79, 0xa0, 0x7d, 0xc9, 0x83, 0x04, 0x93, 0x7f, + 0x9e, 0xd6, 0x8a, 0xdf, 0x58, 0x04, 0x5f, 0x60, + 0x7c, 0x7e, 0x80, 0x62, 0x72, 0xca, 0x78, 0xc2, + 0x8c, 0xf7, 0x96, 0xd8, 0x58, 0x62, 0x5c, 0x13, + 0x6a, 0xda, 0x6d, 0x0f, 0x6f, 0x2f, 0x7d, 0x37, + 0x7e, 0x4b, 0x96, 0xd2, 0x52, 0x8b, 0x80, 0xdc, + 0x51, 0xcc, 0x51, 0x1c, 0x7a, 0xbe, 0x7d, 0xf1, + 0x83, 0x75, 0x96, 0x80, 0x8b, 0xcf, 0x62, 0x02, + 0x6a, 0xfe, 0x8a, 0x39, 0x4e, 0xe7, 0x5b, 0x12, + 0x60, 0x87, 0x73, 0x70, 0x75, 0x17, 0x53, 0xfb, + 0x78, 0xbf, 0x4f, 0xa9, 0x5f, 0x0d, 0x4e, 0xcc, + 0x6c, 0x78, 0x65, 0x22, 0x7d, 0xc3, 0x53, 0x5e, + 0x58, 0x01, 0x77, 0x49, 0x84, 0xaa, 0x8a, 0xba, + 0x6b, 0xb0, 0x8f, 0x88, 0x6c, 0xfe, 0x62, 0xe5, + 0x82, 0xa0, 0x63, 0x65, 0x75, 0xae, 0x4e, 0x69, + 0x51, 0xc9, 0x51, 0x81, 0x68, 0xe7, 0x7c, 0x6f, + 0x82, 0xd2, 0x8a, 0xcf, 0x91, 0xf5, 0x52, 0x42, + 0x54, 0x73, 0x59, 0xec, 0x5e, 0xc5, 0x65, 0xfe, + 0x6f, 0x2a, 0x79, 0xad, 0x95, 0x6a, 0x9a, 0x97, + 0x9e, 0xce, 0x9e, 0x9b, 0x52, 0xc6, 0x66, 0x77, + 0x6b, 0x62, 0x8f, 0x74, 0x5e, 0x90, 0x61, 0x00, + 0x62, 0x9a, 0x64, 0x23, 0x6f, 0x49, 0x71, 0x89, + 0x74, 0xca, 0x79, 0xf4, 0x7d, 0x6f, 0x80, 0x26, + 0x8f, 0xee, 0x84, 0x23, 0x90, 0x4a, 0x93, 0x17, + 0x52, 0xa3, 0x52, 0xbd, 0x54, 0xc8, 0x70, 0xc2, + 0x88, 0xaa, 0x8a, 0xc9, 0x5e, 0xf5, 0x5f, 0x7b, + 0x63, 0xae, 0x6b, 0x3e, 0x7c, 0x75, 0x73, 0xe4, + 0x4e, 0xf9, 0x56, 0xe7, 0x5b, 0xba, 0x5d, 0x1c, + 0x60, 0xb2, 0x73, 0x69, 0x74, 0x9a, 0x7f, 0x46, + 0x80, 0x34, 0x92, 0xf6, 0x96, 0x48, 0x97, 0x18, + 0x98, 0x8b, 0x4f, 0xae, 0x79, 0xb4, 0x91, 0xb8, + 0x96, 0xe1, 0x60, 0x86, 0x4e, 0xda, 0x50, 0xee, + 0x5b, 0x3f, 0x5c, 0x99, 0x65, 0x02, 0x6a, 0xce, + 0x71, 0x42, 0x76, 0xfc, 0x84, 0x7c, 0x90, 0x8d, + 0x9f, 0x88, 0x66, 0x2e, 0x96, 0x89, 0x52, 0x7b, + 0x67, 0xf3, 0x67, 0x41, 0x6d, 0x9c, 0x6e, 0x09, + 0x74, 0x59, 0x75, 0x6b, 0x78, 0x10, 0x7d, 0x5e, + 0x98, 0x6d, 0x51, 0x2e, 0x62, 0x78, 0x96, 0x2b, + 0x50, 0x19, 0x5d, 0xea, 0x6d, 0x2a, 0x8f, 0x8b, + 0x5f, 0x44, 0x61, 0x17, 0x68, 0x87, 0x73, 0x86, + 0x96, 0x29, 0x52, 0x0f, 0x54, 0x65, 0x5c, 0x13, + 0x66, 0x4e, 0x67, 0xa8, 0x68, 0xe5, 0x6c, 0x06, + 0x74, 0xe2, 0x75, 0x79, 0x7f, 0xcf, 0x88, 0xe1, + 0x88, 0xcc, 0x91, 0xe2, 0x96, 0x3f, 0x53, 0xba, + 0x6e, 0x1d, 0x54, 0xd0, 0x71, 0x98, 0x74, 0xfa, + 0x85, 0xa3, 0x96, 0x57, 0x9c, 0x9f, 0x9e, 0x97, + 0x67, 0xcb, 0x6d, 0xe8, 0x81, 0xcb, 0x7a, 0x20, + 0x7b, 0x92, 0x7c, 0xc0, 0x72, 0x99, 0x70, 0x58, + 0x8b, 0xc0, 0x4e, 0x36, 0x83, 0x3a, 0x52, 0x07, + 0x52, 0xa6, 0x5e, 0xd3, 0x62, 0xd6, 0x7c, 0x85, + 0x5b, 0x1e, 0x6d, 0xb4, 0x66, 0x3b, 0x8f, 0x4c, + 0x88, 0x4d, 0x96, 0x8b, 0x89, 0xd3, 0x5e, 0x40, + 0x51, 0xc0, 0x55, 0x00, 0x00, 0x00, 0x00, 0x5a, + 0x58, 0x00, 0x00, 0x74, 0x66, 0x00, 0x00, 0x00, + 0x00, 0xde, 0x51, 0x2a, 0x73, 0xca, 0x76, 0x3c, + 0x79, 0x5e, 0x79, 0x65, 0x79, 0x8f, 0x79, 0x56, + 0x97, 0xbe, 0x7c, 0xbd, 0x7f, 0x00, 0x00, 0x12, + 0x86, 0x00, 0x00, 0xf8, 0x8a, 0x00, 0x00, 0x00, + 0x00, 0x38, 0x90, 0xfd, 0x90, 0xef, 0x98, 0xfc, + 0x98, 0x28, 0x99, 0xb4, 0x9d, 0xde, 0x90, 0xb7, + 0x96, 0xae, 0x4f, 0xe7, 0x50, 0x4d, 0x51, 0xc9, + 0x52, 0xe4, 0x52, 0x51, 0x53, 0x9d, 0x55, 0x06, + 0x56, 0x68, 0x56, 0x40, 0x58, 0xa8, 0x58, 0x64, + 0x5c, 0x6e, 0x5c, 0x94, 0x60, 0x68, 0x61, 0x8e, + 0x61, 0xf2, 0x61, 0x4f, 0x65, 0xe2, 0x65, 0x91, + 0x66, 0x85, 0x68, 0x77, 0x6d, 0x1a, 0x6e, 0x22, + 0x6f, 0x6e, 0x71, 0x2b, 0x72, 0x22, 0x74, 0x91, + 0x78, 0x3e, 0x79, 0x49, 0x79, 0x48, 0x79, 0x50, + 0x79, 0x56, 0x79, 0x5d, 0x79, 0x8d, 0x79, 0x8e, + 0x79, 0x40, 0x7a, 0x81, 0x7a, 0xc0, 0x7b, 0xf4, + 0x7d, 0x09, 0x7e, 0x41, 0x7e, 0x72, 0x7f, 0x05, + 0x80, 0xed, 0x81, 0x79, 0x82, 0x79, 0x82, 0x57, + 0x84, 0x10, 0x89, 0x96, 0x89, 0x01, 0x8b, 0x39, + 0x8b, 0xd3, 0x8c, 0x08, 0x8d, 0xb6, 0x8f, 0x38, + 0x90, 0xe3, 0x96, 0xff, 0x97, 0x3b, 0x98, 0x75, + 0x60, 0xee, 0x42, 0x18, 0x82, 0x02, 0x26, 0x4e, + 0xb5, 0x51, 0x68, 0x51, 0x80, 0x4f, 0x45, 0x51, + 0x80, 0x51, 0xc7, 0x52, 0xfa, 0x52, 0x9d, 0x55, + 0x55, 0x55, 0x99, 0x55, 0xe2, 0x55, 0x5a, 0x58, + 0xb3, 0x58, 0x44, 0x59, 0x54, 0x59, 0x62, 0x5a, + 0x28, 0x5b, 0xd2, 0x5e, 0xd9, 0x5e, 0x69, 0x5f, + 0xad, 0x5f, 0xd8, 0x60, 0x4e, 0x61, 0x08, 0x61, + 0x8e, 0x61, 0x60, 0x61, 0xf2, 0x61, 0x34, 0x62, + 0xc4, 0x63, 0x1c, 0x64, 0x52, 0x64, 0x56, 0x65, + 0x74, 0x66, 0x17, 0x67, 0x1b, 0x67, 0x56, 0x67, + 0x79, 0x6b, 0xba, 0x6b, 0x41, 0x6d, 0xdb, 0x6e, + 0xcb, 0x6e, 0x22, 0x6f, 0x1e, 0x70, 0x6e, 0x71, + 0xa7, 0x77, 0x35, 0x72, 0xaf, 0x72, 0x2a, 0x73, + 0x71, 0x74, 0x06, 0x75, 0x3b, 0x75, 0x1d, 0x76, + 0x1f, 0x76, 0xca, 0x76, 0xdb, 0x76, 0xf4, 0x76, + 0x4a, 0x77, 0x40, 0x77, 0xcc, 0x78, 0xb1, 0x7a, + 0xc0, 0x7b, 0x7b, 0x7c, 0x5b, 0x7d, 0xf4, 0x7d, + 0x3e, 0x7f, 0x05, 0x80, 0x52, 0x83, 0xef, 0x83, + 0x79, 0x87, 0x41, 0x89, 0x86, 0x89, 0x96, 0x89, + 0xbf, 0x8a, 0xf8, 0x8a, 0xcb, 0x8a, 0x01, 0x8b, + 0xfe, 0x8a, 0xed, 0x8a, 0x39, 0x8b, 0x8a, 0x8b, + 0x08, 0x8d, 0x38, 0x8f, 0x72, 0x90, 0x99, 0x91, + 0x76, 0x92, 0x7c, 0x96, 0xe3, 0x96, 0x56, 0x97, + 0xdb, 0x97, 0xff, 0x97, 0x0b, 0x98, 0x3b, 0x98, + 0x12, 0x9b, 0x9c, 0x9f, 0x4a, 0x28, 0x44, 0x28, + 0xd5, 0x33, 0x9d, 0x3b, 0x18, 0x40, 0x39, 0x40, + 0x49, 0x52, 0xd0, 0x5c, 0xd3, 0x7e, 0x43, 0x9f, + 0x8e, 0x9f, 0x2a, 0xa0, 0x02, 0x66, 0x66, 0x66, + 0x69, 0x66, 0x6c, 0x66, 0x66, 0x69, 0x66, 0x66, + 0x6c, 0x7f, 0x01, 0x74, 0x73, 0x00, 0x74, 0x65, + 0x05, 0x0f, 0x11, 0x0f, 0x00, 0x0f, 0x06, 0x19, + 0x11, 0x0f, 0x08, 0xd9, 0x05, 0xb4, 0x05, 0x00, + 0x00, 0x00, 0x00, 0xf2, 0x05, 0xb7, 0x05, 0xd0, + 0x05, 0x12, 0x00, 0x03, 0x04, 0x0b, 0x0c, 0x0d, + 0x18, 0x1a, 0xe9, 0x05, 0xc1, 0x05, 0xe9, 0x05, + 0xc2, 0x05, 0x49, 0xfb, 0xc1, 0x05, 0x49, 0xfb, + 0xc2, 0x05, 0xd0, 0x05, 0xb7, 0x05, 0xd0, 0x05, + 0xb8, 0x05, 0xd0, 0x05, 0xbc, 0x05, 0xd8, 0x05, + 0xbc, 0x05, 0xde, 0x05, 0xbc, 0x05, 0xe0, 0x05, + 0xbc, 0x05, 0xe3, 0x05, 0xbc, 0x05, 0xb9, 0x05, + 0x2d, 0x03, 0x2e, 0x03, 0x2f, 0x03, 0x30, 0x03, + 0x31, 0x03, 0x1c, 0x00, 0x18, 0x06, 0x22, 0x06, + 0x2b, 0x06, 0xd0, 0x05, 0xdc, 0x05, 0x71, 0x06, + 0x00, 0x00, 0x0a, 0x0a, 0x0a, 0x0a, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0f, 0x0f, 0x0f, 0x0f, 0x09, 0x09, + 0x09, 0x09, 0x0e, 0x0e, 0x0e, 0x0e, 0x08, 0x08, + 0x08, 0x08, 0x33, 0x33, 0x33, 0x33, 0x35, 0x35, + 0x35, 0x35, 0x13, 0x13, 0x13, 0x13, 0x12, 0x12, + 0x12, 0x12, 0x15, 0x15, 0x15, 0x15, 0x16, 0x16, + 0x16, 0x16, 0x1c, 0x1c, 0x1b, 0x1b, 0x1d, 0x1d, + 0x17, 0x17, 0x27, 0x27, 0x20, 0x20, 0x38, 0x38, + 0x38, 0x38, 0x3e, 0x3e, 0x3e, 0x3e, 0x42, 0x42, + 0x42, 0x42, 0x40, 0x40, 0x40, 0x40, 0x49, 0x49, + 0x4a, 0x4a, 0x4a, 0x4a, 0x4f, 0x4f, 0x50, 0x50, + 0x50, 0x50, 0x4d, 0x4d, 0x4d, 0x4d, 0x61, 0x61, + 0x62, 0x62, 0x49, 0x06, 0x64, 0x64, 0x64, 0x64, + 0x7e, 0x7e, 0x7d, 0x7d, 0x7f, 0x7f, 0x2e, 0x82, + 0x82, 0x7c, 0x7c, 0x80, 0x80, 0x87, 0x87, 0x87, + 0x87, 0x00, 0x00, 0x26, 0x06, 0x00, 0x01, 0x00, + 0x01, 0x00, 0xaf, 0x00, 0xaf, 0x00, 0x22, 0x00, + 0x22, 0x00, 0xa1, 0x00, 0xa1, 0x00, 0xa0, 0x00, + 0xa0, 0x00, 0xa2, 0x00, 0xa2, 0x00, 0xaa, 0x00, + 0xaa, 0x00, 0xaa, 0x00, 0x23, 0x00, 0x23, 0x00, + 0x23, 0xcc, 0x06, 0x00, 0x00, 0x00, 0x00, 0x26, + 0x06, 0x00, 0x06, 0x00, 0x07, 0x00, 0x1f, 0x00, + 0x23, 0x00, 0x24, 0x02, 0x06, 0x02, 0x07, 0x02, + 0x08, 0x02, 0x1f, 0x02, 0x23, 0x02, 0x24, 0x04, + 0x06, 0x04, 0x07, 0x04, 0x08, 0x04, 0x1f, 0x04, + 0x23, 0x04, 0x24, 0x05, 0x06, 0x05, 0x1f, 0x05, + 0x23, 0x05, 0x24, 0x06, 0x07, 0x06, 0x1f, 0x07, + 0x06, 0x07, 0x1f, 0x08, 0x06, 0x08, 0x07, 0x08, + 0x1f, 0x0d, 0x06, 0x0d, 0x07, 0x0d, 0x08, 0x0d, + 0x1f, 0x0f, 0x07, 0x0f, 0x1f, 0x10, 0x06, 0x10, + 0x07, 0x10, 0x08, 0x10, 0x1f, 0x11, 0x07, 0x11, + 0x1f, 0x12, 0x1f, 0x13, 0x06, 0x13, 0x1f, 0x14, + 0x06, 0x14, 0x1f, 0x1b, 0x06, 0x1b, 0x07, 0x1b, + 0x08, 0x1b, 0x1f, 0x1b, 0x23, 0x1b, 0x24, 0x1c, + 0x07, 0x1c, 0x1f, 0x1c, 0x23, 0x1c, 0x24, 0x1d, + 0x01, 0x1d, 0x06, 0x1d, 0x07, 0x1d, 0x08, 0x1d, + 0x1e, 0x1d, 0x1f, 0x1d, 0x23, 0x1d, 0x24, 0x1e, + 0x06, 0x1e, 0x07, 0x1e, 0x08, 0x1e, 0x1f, 0x1e, + 0x23, 0x1e, 0x24, 0x1f, 0x06, 0x1f, 0x07, 0x1f, + 0x08, 0x1f, 0x1f, 0x1f, 0x23, 0x1f, 0x24, 0x20, + 0x06, 0x20, 0x07, 0x20, 0x08, 0x20, 0x1f, 0x20, + 0x23, 0x20, 0x24, 0x21, 0x06, 0x21, 0x1f, 0x21, + 0x23, 0x21, 0x24, 0x24, 0x06, 0x24, 0x07, 0x24, + 0x08, 0x24, 0x1f, 0x24, 0x23, 0x24, 0x24, 0x0a, + 0x4a, 0x0b, 0x4a, 0x23, 0x4a, 0x20, 0x00, 0x4c, + 0x06, 0x51, 0x06, 0x51, 0x06, 0xff, 0x00, 0x1f, + 0x26, 0x06, 0x00, 0x0b, 0x00, 0x0c, 0x00, 0x1f, + 0x00, 0x20, 0x00, 0x23, 0x00, 0x24, 0x02, 0x0b, + 0x02, 0x0c, 0x02, 0x1f, 0x02, 0x20, 0x02, 0x23, + 0x02, 0x24, 0x04, 0x0b, 0x04, 0x0c, 0x04, 0x1f, + 0x26, 0x06, 0x04, 0x20, 0x04, 0x23, 0x04, 0x24, + 0x05, 0x0b, 0x05, 0x0c, 0x05, 0x1f, 0x05, 0x20, + 0x05, 0x23, 0x05, 0x24, 0x1b, 0x23, 0x1b, 0x24, + 0x1c, 0x23, 0x1c, 0x24, 0x1d, 0x01, 0x1d, 0x1e, + 0x1d, 0x1f, 0x1d, 0x23, 0x1d, 0x24, 0x1e, 0x1f, + 0x1e, 0x23, 0x1e, 0x24, 0x1f, 0x01, 0x1f, 0x1f, + 0x20, 0x0b, 0x20, 0x0c, 0x20, 0x1f, 0x20, 0x20, + 0x20, 0x23, 0x20, 0x24, 0x23, 0x4a, 0x24, 0x0b, + 0x24, 0x0c, 0x24, 0x1f, 0x24, 0x20, 0x24, 0x23, + 0x24, 0x24, 0x00, 0x06, 0x00, 0x07, 0x00, 0x08, + 0x00, 0x1f, 0x00, 0x21, 0x02, 0x06, 0x02, 0x07, + 0x02, 0x08, 0x02, 0x1f, 0x02, 0x21, 0x04, 0x06, + 0x04, 0x07, 0x04, 0x08, 0x04, 0x1f, 0x04, 0x21, + 0x05, 0x1f, 0x06, 0x07, 0x06, 0x1f, 0x07, 0x06, + 0x07, 0x1f, 0x08, 0x06, 0x08, 0x1f, 0x0d, 0x06, + 0x0d, 0x07, 0x0d, 0x08, 0x0d, 0x1f, 0x0f, 0x07, + 0x0f, 0x08, 0x0f, 0x1f, 0x10, 0x06, 0x10, 0x07, + 0x10, 0x08, 0x10, 0x1f, 0x11, 0x07, 0x12, 0x1f, + 0x13, 0x06, 0x13, 0x1f, 0x14, 0x06, 0x14, 0x1f, + 0x1b, 0x06, 0x1b, 0x07, 0x1b, 0x08, 0x1b, 0x1f, + 0x1c, 0x07, 0x1c, 0x1f, 0x1d, 0x06, 0x1d, 0x07, + 0x1d, 0x08, 0x1d, 0x1e, 0x1d, 0x1f, 0x1e, 0x06, + 0x1e, 0x07, 0x1e, 0x08, 0x1e, 0x1f, 0x1e, 0x21, + 0x1f, 0x06, 0x1f, 0x07, 0x1f, 0x08, 0x1f, 0x1f, + 0x20, 0x06, 0x20, 0x07, 0x20, 0x08, 0x20, 0x1f, + 0x20, 0x21, 0x21, 0x06, 0x21, 0x1f, 0x21, 0x4a, + 0x24, 0x06, 0x24, 0x07, 0x24, 0x08, 0x24, 0x1f, + 0x24, 0x21, 0x00, 0x1f, 0x00, 0x21, 0x02, 0x1f, + 0x02, 0x21, 0x04, 0x1f, 0x04, 0x21, 0x05, 0x1f, + 0x05, 0x21, 0x0d, 0x1f, 0x0d, 0x21, 0x0e, 0x1f, + 0x0e, 0x21, 0x1d, 0x1e, 0x1d, 0x1f, 0x1e, 0x1f, + 0x20, 0x1f, 0x20, 0x21, 0x24, 0x1f, 0x24, 0x21, + 0x40, 0x06, 0x4e, 0x06, 0x51, 0x06, 0x27, 0x06, + 0x10, 0x22, 0x10, 0x23, 0x12, 0x22, 0x12, 0x23, + 0x13, 0x22, 0x13, 0x23, 0x0c, 0x22, 0x0c, 0x23, + 0x0d, 0x22, 0x0d, 0x23, 0x06, 0x22, 0x06, 0x23, + 0x05, 0x22, 0x05, 0x23, 0x07, 0x22, 0x07, 0x23, + 0x0e, 0x22, 0x0e, 0x23, 0x0f, 0x22, 0x0f, 0x23, + 0x0d, 0x05, 0x0d, 0x06, 0x0d, 0x07, 0x0d, 0x1e, + 0x0d, 0x0a, 0x0c, 0x0a, 0x0e, 0x0a, 0x0f, 0x0a, + 0x10, 0x22, 0x10, 0x23, 0x12, 0x22, 0x12, 0x23, + 0x13, 0x22, 0x13, 0x23, 0x0c, 0x22, 0x0c, 0x23, + 0x0d, 0x22, 0x0d, 0x23, 0x06, 0x22, 0x06, 0x23, + 0x05, 0x22, 0x05, 0x23, 0x07, 0x22, 0x07, 0x23, + 0x0e, 0x22, 0x0e, 0x23, 0x0f, 0x22, 0x0f, 0x23, + 0x0d, 0x05, 0x0d, 0x06, 0x0d, 0x07, 0x0d, 0x1e, + 0x0d, 0x0a, 0x0c, 0x0a, 0x0e, 0x0a, 0x0f, 0x0a, + 0x0d, 0x05, 0x0d, 0x06, 0x0d, 0x07, 0x0d, 0x1e, + 0x0c, 0x20, 0x0d, 0x20, 0x10, 0x1e, 0x0c, 0x05, + 0x0c, 0x06, 0x0c, 0x07, 0x0d, 0x05, 0x0d, 0x06, + 0x0d, 0x07, 0x10, 0x1e, 0x11, 0x1e, 0x00, 0x24, + 0x00, 0x24, 0x2a, 0x06, 0x00, 0x02, 0x1b, 0x00, + 0x03, 0x02, 0x00, 0x03, 0x02, 0x00, 0x03, 0x1b, + 0x00, 0x04, 0x1b, 0x00, 0x1b, 0x02, 0x00, 0x1b, + 0x03, 0x00, 0x1b, 0x04, 0x02, 0x1b, 0x03, 0x02, + 0x1b, 0x03, 0x03, 0x1b, 0x20, 0x03, 0x1b, 0x1f, + 0x09, 0x03, 0x02, 0x09, 0x02, 0x03, 0x09, 0x02, + 0x1f, 0x09, 0x1b, 0x03, 0x09, 0x1b, 0x03, 0x09, + 0x1b, 0x02, 0x09, 0x1b, 0x1b, 0x09, 0x1b, 0x1b, + 0x0b, 0x03, 0x03, 0x0b, 0x03, 0x03, 0x0b, 0x1b, + 0x1b, 0x0a, 0x03, 0x1b, 0x0a, 0x03, 0x1b, 0x0a, + 0x02, 0x20, 0x0a, 0x1b, 0x04, 0x0a, 0x1b, 0x04, + 0x0a, 0x1b, 0x1b, 0x0a, 0x1b, 0x1b, 0x0c, 0x03, + 0x1f, 0x0c, 0x04, 0x1b, 0x0c, 0x04, 0x1b, 0x0d, + 0x1b, 0x03, 0x0d, 0x1b, 0x03, 0x0d, 0x1b, 0x1b, + 0x0d, 0x1b, 0x20, 0x0f, 0x02, 0x1b, 0x0f, 0x1b, + 0x1b, 0x0f, 0x1b, 0x1b, 0x0f, 0x1b, 0x1f, 0x10, + 0x1b, 0x1b, 0x10, 0x1b, 0x20, 0x10, 0x1b, 0x1f, + 0x17, 0x04, 0x1b, 0x17, 0x04, 0x1b, 0x18, 0x1b, + 0x03, 0x18, 0x1b, 0x1b, 0x1a, 0x03, 0x1b, 0x1a, + 0x03, 0x20, 0x1a, 0x03, 0x1f, 0x1a, 0x02, 0x02, + 0x1a, 0x02, 0x02, 0x1a, 0x04, 0x1b, 0x1a, 0x04, + 0x1b, 0x1a, 0x1b, 0x03, 0x1a, 0x1b, 0x03, 0x1b, + 0x03, 0x02, 0x1b, 0x03, 0x1b, 0x1b, 0x03, 0x20, + 0x1b, 0x02, 0x03, 0x1b, 0x02, 0x1b, 0x1b, 0x04, + 0x02, 0x1b, 0x04, 0x1b, 0x28, 0x06, 0x1d, 0x04, + 0x06, 0x1f, 0x1d, 0x04, 0x1f, 0x1d, 0x1d, 0x1e, + 0x05, 0x1d, 0x1e, 0x05, 0x21, 0x1e, 0x04, 0x1d, + 0x1e, 0x04, 0x1d, 0x1e, 0x04, 0x21, 0x1e, 0x1d, + 0x22, 0x1e, 0x1d, 0x21, 0x22, 0x1d, 0x1d, 0x22, + 0x1d, 0x1d, 0x00, 0x06, 0x22, 0x02, 0x04, 0x22, + 0x02, 0x04, 0x21, 0x02, 0x06, 0x22, 0x02, 0x06, + 0x21, 0x02, 0x1d, 0x22, 0x02, 0x1d, 0x21, 0x04, + 0x1d, 0x22, 0x04, 0x05, 0x21, 0x04, 0x1d, 0x21, + 0x0b, 0x06, 0x21, 0x0d, 0x05, 0x22, 0x0c, 0x05, + 0x22, 0x0e, 0x05, 0x22, 0x1c, 0x04, 0x22, 0x1c, + 0x1d, 0x22, 0x22, 0x05, 0x22, 0x22, 0x04, 0x22, + 0x22, 0x1d, 0x22, 0x1d, 0x1d, 0x22, 0x1a, 0x1d, + 0x22, 0x1e, 0x05, 0x22, 0x1a, 0x1d, 0x05, 0x1c, + 0x05, 0x1d, 0x11, 0x1d, 0x22, 0x1b, 0x1d, 0x22, + 0x1e, 0x04, 0x05, 0x1d, 0x06, 0x22, 0x1c, 0x04, + 0x1d, 0x1b, 0x1d, 0x1d, 0x1c, 0x04, 0x1d, 0x1e, + 0x04, 0x05, 0x04, 0x05, 0x22, 0x05, 0x04, 0x22, + 0x1d, 0x04, 0x22, 0x19, 0x1d, 0x22, 0x00, 0x05, + 0x22, 0x1b, 0x1d, 0x1d, 0x11, 0x04, 0x1d, 0x0d, + 0x1d, 0x1d, 0x0b, 0x06, 0x22, 0x1e, 0x04, 0x22, + 0x35, 0x06, 0x00, 0x0f, 0x9d, 0x0d, 0x0f, 0x9d, + 0x27, 0x06, 0x00, 0x1d, 0x1d, 0x20, 0x00, 0x1c, + 0x01, 0x0a, 0x1e, 0x06, 0x1e, 0x08, 0x0e, 0x1d, + 0x12, 0x1e, 0x0a, 0x0c, 0x21, 0x1d, 0x12, 0x1d, + 0x23, 0x20, 0x21, 0x0c, 0x1d, 0x1e, 0x35, 0x06, + 0x00, 0x0f, 0x14, 0x27, 0x06, 0x0e, 0x1d, 0x22, + 0xff, 0x00, 0x1d, 0x1d, 0x20, 0xff, 0x12, 0x1d, + 0x23, 0x20, 0xff, 0x21, 0x0c, 0x1d, 0x1e, 0x27, + 0x06, 0x05, 0x1d, 0xff, 0x05, 0x1d, 0x00, 0x1d, + 0x20, 0x27, 0x06, 0x0a, 0xa5, 0x00, 0x1d, 0x2c, + 0x00, 0x01, 0x30, 0x02, 0x30, 0x3a, 0x00, 0x3b, + 0x00, 0x21, 0x00, 0x3f, 0x00, 0x16, 0x30, 0x17, + 0x30, 0x26, 0x20, 0x13, 0x20, 0x12, 0x01, 0x00, + 0x5f, 0x5f, 0x28, 0x29, 0x7b, 0x7d, 0x08, 0x30, + 0x0c, 0x0d, 0x08, 0x09, 0x02, 0x03, 0x00, 0x01, + 0x04, 0x05, 0x06, 0x07, 0x5b, 0x00, 0x5d, 0x00, + 0x3e, 0x20, 0x3e, 0x20, 0x3e, 0x20, 0x3e, 0x20, + 0x5f, 0x00, 0x5f, 0x00, 0x5f, 0x00, 0x2c, 0x00, + 0x01, 0x30, 0x2e, 0x00, 0x00, 0x00, 0x3b, 0x00, + 0x3a, 0x00, 0x3f, 0x00, 0x21, 0x00, 0x14, 0x20, + 0x28, 0x00, 0x29, 0x00, 0x7b, 0x00, 0x7d, 0x00, + 0x14, 0x30, 0x15, 0x30, 0x23, 0x26, 0x2a, 0x2b, + 0x2d, 0x3c, 0x3e, 0x3d, 0x00, 0x5c, 0x24, 0x25, + 0x40, 0x40, 0x06, 0xff, 0x0b, 0x00, 0x0b, 0xff, + 0x0c, 0x20, 0x00, 0x4d, 0x06, 0x40, 0x06, 0xff, + 0x0e, 0x00, 0x0e, 0xff, 0x0f, 0x00, 0x0f, 0xff, + 0x10, 0x00, 0x10, 0xff, 0x11, 0x00, 0x11, 0xff, + 0x12, 0x00, 0x12, 0x21, 0x06, 0x00, 0x01, 0x01, + 0x02, 0x02, 0x03, 0x03, 0x04, 0x04, 0x05, 0x05, + 0x05, 0x05, 0x06, 0x06, 0x07, 0x07, 0x07, 0x07, + 0x08, 0x08, 0x09, 0x09, 0x09, 0x09, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0b, 0x0b, 0x0b, 0x0b, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0d, 0x0d, 0x0d, 0x0d, 0x0e, 0x0e, + 0x0f, 0x0f, 0x10, 0x10, 0x11, 0x11, 0x12, 0x12, + 0x12, 0x12, 0x13, 0x13, 0x13, 0x13, 0x14, 0x14, + 0x14, 0x14, 0x15, 0x15, 0x15, 0x15, 0x16, 0x16, + 0x16, 0x16, 0x17, 0x17, 0x17, 0x17, 0x18, 0x18, + 0x18, 0x18, 0x19, 0x19, 0x19, 0x19, 0x20, 0x20, + 0x20, 0x20, 0x21, 0x21, 0x21, 0x21, 0x22, 0x22, + 0x22, 0x22, 0x23, 0x23, 0x23, 0x23, 0x24, 0x24, + 0x24, 0x24, 0x25, 0x25, 0x25, 0x25, 0x26, 0x26, + 0x26, 0x26, 0x27, 0x27, 0x28, 0x28, 0x29, 0x29, + 0x29, 0x29, 0x22, 0x06, 0x22, 0x00, 0x22, 0x00, + 0x22, 0x01, 0x22, 0x01, 0x22, 0x03, 0x22, 0x03, + 0x22, 0x05, 0x22, 0x05, 0x21, 0x00, 0x85, 0x29, + 0x01, 0x30, 0x01, 0x0b, 0x0c, 0x00, 0xfa, 0xf1, + 0xa0, 0xa2, 0xa4, 0xa6, 0xa8, 0xe2, 0xe4, 0xe6, + 0xc2, 0xfb, 0xa1, 0xa3, 0xa5, 0xa7, 0xa9, 0xaa, + 0xac, 0xae, 0xb0, 0xb2, 0xb4, 0xb6, 0xb8, 0xba, + 0xbc, 0xbe, 0xc0, 0xc3, 0xc5, 0xc7, 0xc9, 0xca, + 0xcb, 0xcc, 0xcd, 0xce, 0xd1, 0xd4, 0xd7, 0xda, + 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe3, 0xe5, 0xe7, + 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xee, 0xf2, 0x98, + 0x99, 0x31, 0x31, 0x4f, 0x31, 0x55, 0x31, 0x5b, + 0x31, 0x61, 0x31, 0xa2, 0x00, 0xa3, 0x00, 0xac, + 0x00, 0xaf, 0x00, 0xa6, 0x00, 0xa5, 0x00, 0xa9, + 0x20, 0x00, 0x00, 0x02, 0x25, 0x90, 0x21, 0x91, + 0x21, 0x92, 0x21, 0x93, 0x21, 0xa0, 0x25, 0xcb, + 0x25, 0xd2, 0x05, 0x07, 0x03, 0x01, 0xda, 0x05, + 0x07, 0x03, 0x01, 0xd0, 0x02, 0xd1, 0x02, 0xe6, + 0x00, 0x99, 0x02, 0x53, 0x02, 0x00, 0x00, 0xa3, + 0x02, 0x66, 0xab, 0xa5, 0x02, 0xa4, 0x02, 0x56, + 0x02, 0x57, 0x02, 0x91, 0x1d, 0x58, 0x02, 0x5e, + 0x02, 0xa9, 0x02, 0x64, 0x02, 0x62, 0x02, 0x60, + 0x02, 0x9b, 0x02, 0x27, 0x01, 0x9c, 0x02, 0x67, + 0x02, 0x84, 0x02, 0xaa, 0x02, 0xab, 0x02, 0x6c, + 0x02, 0x04, 0xdf, 0x8e, 0xa7, 0x6e, 0x02, 0x05, + 0xdf, 0x8e, 0x02, 0x06, 0xdf, 0xf8, 0x00, 0x76, + 0x02, 0x77, 0x02, 0x71, 0x00, 0x7a, 0x02, 0x08, + 0xdf, 0x7d, 0x02, 0x7e, 0x02, 0x80, 0x02, 0xa8, + 0x02, 0xa6, 0x02, 0x67, 0xab, 0xa7, 0x02, 0x88, + 0x02, 0x71, 0x2c, 0x00, 0x00, 0x8f, 0x02, 0xa1, + 0x02, 0xa2, 0x02, 0x98, 0x02, 0xc0, 0x01, 0xc1, + 0x01, 0xc2, 0x01, 0x0a, 0xdf, 0x1e, 0xdf, 0x41, + 0x04, 0x40, 0x00, 0x00, 0x00, 0x00, 0x14, 0x99, + 0x10, 0xba, 0x10, 0x00, 0x00, 0x00, 0x00, 0x9b, + 0x10, 0xba, 0x10, 0x05, 0x05, 0xa5, 0x10, 0xba, + 0x10, 0x05, 0x31, 0x11, 0x27, 0x11, 0x32, 0x11, + 0x27, 0x11, 0x55, 0x47, 0x13, 0x3e, 0x13, 0x47, + 0x13, 0x57, 0x13, 0x55, 0x82, 0x13, 0xc9, 0x13, + 0x00, 0x00, 0x00, 0x00, 0x84, 0x13, 0xbb, 0x13, + 0x05, 0x05, 0x8b, 0x13, 0xc2, 0x13, 0x05, 0x90, + 0x13, 0xc9, 0x13, 0x05, 0xc2, 0x13, 0xc2, 0x13, + 0x00, 0x00, 0x00, 0x00, 0xc2, 0x13, 0xb8, 0x13, + 0xc2, 0x13, 0xc9, 0x13, 0x05, 0x55, 0xb9, 0x14, + 0xba, 0x14, 0xb9, 0x14, 0xb0, 0x14, 0x00, 0x00, + 0x00, 0x00, 0xb9, 0x14, 0xbd, 0x14, 0x55, 0x50, + 0xb8, 0x15, 0xaf, 0x15, 0xb9, 0x15, 0xaf, 0x15, + 0x55, 0x35, 0x19, 0x30, 0x19, 0x05, 0x1e, 0x61, + 0x1e, 0x61, 0x1e, 0x61, 0x29, 0x61, 0x1e, 0x61, + 0x1f, 0x61, 0x29, 0x61, 0x1f, 0x61, 0x1e, 0x61, + 0x20, 0x61, 0x21, 0x61, 0x1f, 0x61, 0x22, 0x61, + 0x1f, 0x61, 0x21, 0x61, 0x20, 0x61, 0x55, 0x55, + 0x55, 0x55, 0x67, 0x6d, 0x67, 0x6d, 0x63, 0x6d, + 0x67, 0x6d, 0x69, 0x6d, 0x67, 0x6d, 0x55, 0x05, + 0x41, 0x00, 0x30, 0x00, 0x57, 0xd1, 0x65, 0xd1, + 0x58, 0xd1, 0x65, 0xd1, 0x5f, 0xd1, 0x6e, 0xd1, + 0x5f, 0xd1, 0x6f, 0xd1, 0x5f, 0xd1, 0x70, 0xd1, + 0x5f, 0xd1, 0x71, 0xd1, 0x5f, 0xd1, 0x72, 0xd1, + 0x55, 0x55, 0x55, 0x05, 0xb9, 0xd1, 0x65, 0xd1, + 0xba, 0xd1, 0x65, 0xd1, 0xbb, 0xd1, 0x6e, 0xd1, + 0xbc, 0xd1, 0x6e, 0xd1, 0xbb, 0xd1, 0x6f, 0xd1, + 0xbc, 0xd1, 0x6f, 0xd1, 0x55, 0x55, 0x55, 0x41, + 0x00, 0x61, 0x00, 0x41, 0x00, 0x61, 0x00, 0x69, + 0x00, 0x41, 0x00, 0x61, 0x00, 0x41, 0x00, 0x43, + 0x44, 0x00, 0x00, 0x47, 0x00, 0x00, 0x4a, 0x4b, + 0x00, 0x00, 0x4e, 0x4f, 0x50, 0x51, 0x00, 0x53, + 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x61, + 0x62, 0x63, 0x64, 0x00, 0x66, 0x68, 0x00, 0x70, + 0x00, 0x41, 0x00, 0x61, 0x00, 0x41, 0x42, 0x00, + 0x44, 0x45, 0x46, 0x47, 0x4a, 0x00, 0x53, 0x00, + 0x61, 0x00, 0x41, 0x42, 0x00, 0x44, 0x45, 0x46, + 0x47, 0x00, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x00, + 0x4f, 0x53, 0x00, 0x61, 0x00, 0x41, 0x00, 0x61, + 0x00, 0x41, 0x00, 0x61, 0x00, 0x41, 0x00, 0x61, + 0x00, 0x41, 0x00, 0x61, 0x00, 0x41, 0x00, 0x61, + 0x00, 0x41, 0x00, 0x61, 0x00, 0x31, 0x01, 0x37, + 0x02, 0x91, 0x03, 0xa3, 0x03, 0xb1, 0x03, 0xd1, + 0x03, 0x24, 0x00, 0x1f, 0x04, 0x20, 0x05, 0x91, + 0x03, 0xa3, 0x03, 0xb1, 0x03, 0xd1, 0x03, 0x24, + 0x00, 0x1f, 0x04, 0x20, 0x05, 0x91, 0x03, 0xa3, + 0x03, 0xb1, 0x03, 0xd1, 0x03, 0x24, 0x00, 0x1f, + 0x04, 0x20, 0x05, 0x91, 0x03, 0xa3, 0x03, 0xb1, + 0x03, 0xd1, 0x03, 0x24, 0x00, 0x1f, 0x04, 0x20, + 0x05, 0x91, 0x03, 0xa3, 0x03, 0xb1, 0x03, 0xd1, + 0x03, 0x24, 0x00, 0x1f, 0x04, 0x20, 0x05, 0x0b, + 0x0c, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, + 0x00, 0x30, 0x00, 0x30, 0x04, 0x3a, 0x04, 0x3e, + 0x04, 0x4b, 0x04, 0x4d, 0x04, 0x4e, 0x04, 0x89, + 0xa6, 0x30, 0x04, 0xa9, 0x26, 0x28, 0xb9, 0x7f, + 0x9f, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x07, 0x08, 0x0a, 0x0b, 0x0e, 0x0f, 0x11, 0x13, + 0x14, 0x15, 0x16, 0x17, 0x18, 0x1a, 0x1b, 0x61, + 0x26, 0x25, 0x2f, 0x7b, 0x51, 0xa6, 0xb1, 0x04, + 0x27, 0x06, 0x00, 0x01, 0x05, 0x08, 0x2a, 0x06, + 0x1e, 0x08, 0x03, 0x0d, 0x20, 0x19, 0x1a, 0x1b, + 0x1c, 0x09, 0x0f, 0x17, 0x0b, 0x18, 0x07, 0x0a, + 0x00, 0x01, 0x04, 0x06, 0x0c, 0x0e, 0x10, 0x44, + 0x90, 0x77, 0x45, 0x28, 0x06, 0x2c, 0x06, 0x00, + 0x00, 0x47, 0x06, 0x33, 0x06, 0x17, 0x10, 0x11, + 0x12, 0x13, 0x00, 0x06, 0x0e, 0x02, 0x0f, 0x34, + 0x06, 0x2a, 0x06, 0x2b, 0x06, 0x2e, 0x06, 0x00, + 0x00, 0x36, 0x06, 0x00, 0x00, 0x3a, 0x06, 0x2d, + 0x06, 0x00, 0x00, 0x4a, 0x06, 0x00, 0x00, 0x44, + 0x06, 0x00, 0x00, 0x46, 0x06, 0x33, 0x06, 0x39, + 0x06, 0x00, 0x00, 0x35, 0x06, 0x42, 0x06, 0x00, + 0x00, 0x34, 0x06, 0x00, 0x00, 0x00, 0x00, 0x2e, + 0x06, 0x00, 0x00, 0x36, 0x06, 0x00, 0x00, 0x3a, + 0x06, 0x00, 0x00, 0xba, 0x06, 0x00, 0x00, 0x6f, + 0x06, 0x00, 0x00, 0x28, 0x06, 0x2c, 0x06, 0x00, + 0x00, 0x47, 0x06, 0x00, 0x00, 0x00, 0x00, 0x2d, + 0x06, 0x37, 0x06, 0x4a, 0x06, 0x43, 0x06, 0x00, + 0x00, 0x45, 0x06, 0x46, 0x06, 0x33, 0x06, 0x39, + 0x06, 0x41, 0x06, 0x35, 0x06, 0x42, 0x06, 0x00, + 0x00, 0x34, 0x06, 0x2a, 0x06, 0x2b, 0x06, 0x2e, + 0x06, 0x00, 0x00, 0x36, 0x06, 0x38, 0x06, 0x3a, + 0x06, 0x6e, 0x06, 0x00, 0x00, 0xa1, 0x06, 0x27, + 0x06, 0x00, 0x01, 0x05, 0x08, 0x20, 0x21, 0x0b, + 0x06, 0x10, 0x23, 0x2a, 0x06, 0x1a, 0x1b, 0x1c, + 0x09, 0x0f, 0x17, 0x0b, 0x18, 0x07, 0x0a, 0x00, + 0x01, 0x04, 0x06, 0x0c, 0x0e, 0x10, 0x28, 0x06, + 0x2c, 0x06, 0x2f, 0x06, 0x00, 0x00, 0x48, 0x06, + 0x32, 0x06, 0x2d, 0x06, 0x37, 0x06, 0x4a, 0x06, + 0x2a, 0x06, 0x1a, 0x1b, 0x1c, 0x09, 0x0f, 0x17, + 0x0b, 0x18, 0x07, 0x0a, 0x00, 0x01, 0x04, 0x06, + 0x0c, 0x0e, 0x10, 0x30, 0x2e, 0x30, 0x00, 0x2c, + 0x00, 0x28, 0x00, 0x41, 0x00, 0x29, 0x00, 0x14, + 0x30, 0x53, 0x00, 0x15, 0x30, 0x43, 0x52, 0x43, + 0x44, 0x57, 0x5a, 0x41, 0x00, 0x48, 0x56, 0x4d, + 0x56, 0x53, 0x44, 0x53, 0x53, 0x50, 0x50, 0x56, + 0x57, 0x43, 0x4d, 0x43, 0x4d, 0x44, 0x4d, 0x52, + 0x44, 0x4a, 0x4b, 0x30, 0x30, 0x00, 0x68, 0x68, + 0x4b, 0x62, 0x57, 0x5b, 0xcc, 0x53, 0xc7, 0x30, + 0x8c, 0x4e, 0x1a, 0x59, 0xe3, 0x89, 0x29, 0x59, + 0xa4, 0x4e, 0x20, 0x66, 0x21, 0x71, 0x99, 0x65, + 0x4d, 0x52, 0x8c, 0x5f, 0x8d, 0x51, 0xb0, 0x65, + 0x1d, 0x52, 0x42, 0x7d, 0x1f, 0x75, 0xa9, 0x8c, + 0xf0, 0x58, 0x39, 0x54, 0x14, 0x6f, 0x95, 0x62, + 0x55, 0x63, 0x00, 0x4e, 0x09, 0x4e, 0x4a, 0x90, + 0xe6, 0x5d, 0x2d, 0x4e, 0xf3, 0x53, 0x07, 0x63, + 0x70, 0x8d, 0x53, 0x62, 0x81, 0x79, 0x7a, 0x7a, + 0x08, 0x54, 0x80, 0x6e, 0x09, 0x67, 0x08, 0x67, + 0x33, 0x75, 0x72, 0x52, 0xb6, 0x55, 0x4d, 0x91, + 0x14, 0x30, 0x15, 0x30, 0x2c, 0x67, 0x09, 0x4e, + 0x8c, 0x4e, 0x89, 0x5b, 0xb9, 0x70, 0x53, 0x62, + 0xd7, 0x76, 0xdd, 0x52, 0x57, 0x65, 0x97, 0x5f, + 0xef, 0x53, 0x30, 0x00, 0x38, 0x4e, 0x05, 0x00, + 0x09, 0x22, 0x01, 0x60, 0x4f, 0xae, 0x4f, 0xbb, + 0x4f, 0x02, 0x50, 0x7a, 0x50, 0x99, 0x50, 0xe7, + 0x50, 0xcf, 0x50, 0x9e, 0x34, 0x3a, 0x06, 0x4d, + 0x51, 0x54, 0x51, 0x64, 0x51, 0x77, 0x51, 0x1c, + 0x05, 0xb9, 0x34, 0x67, 0x51, 0x8d, 0x51, 0x4b, + 0x05, 0x97, 0x51, 0xa4, 0x51, 0xcc, 0x4e, 0xac, + 0x51, 0xb5, 0x51, 0xdf, 0x91, 0xf5, 0x51, 0x03, + 0x52, 0xdf, 0x34, 0x3b, 0x52, 0x46, 0x52, 0x72, + 0x52, 0x77, 0x52, 0x15, 0x35, 0x02, 0x00, 0x20, + 0x80, 0x80, 0x00, 0x08, 0x00, 0x00, 0xc7, 0x52, + 0x00, 0x02, 0x1d, 0x33, 0x3e, 0x3f, 0x50, 0x82, + 0x8a, 0x93, 0xac, 0xb6, 0xb8, 0xb8, 0xb8, 0x2c, + 0x0a, 0x70, 0x70, 0xca, 0x53, 0xdf, 0x53, 0x63, + 0x0b, 0xeb, 0x53, 0xf1, 0x53, 0x06, 0x54, 0x9e, + 0x54, 0x38, 0x54, 0x48, 0x54, 0x68, 0x54, 0xa2, + 0x54, 0xf6, 0x54, 0x10, 0x55, 0x53, 0x55, 0x63, + 0x55, 0x84, 0x55, 0x84, 0x55, 0x99, 0x55, 0xab, + 0x55, 0xb3, 0x55, 0xc2, 0x55, 0x16, 0x57, 0x06, + 0x56, 0x17, 0x57, 0x51, 0x56, 0x74, 0x56, 0x07, + 0x52, 0xee, 0x58, 0xce, 0x57, 0xf4, 0x57, 0x0d, + 0x58, 0x8b, 0x57, 0x32, 0x58, 0x31, 0x58, 0xac, + 0x58, 0xe4, 0x14, 0xf2, 0x58, 0xf7, 0x58, 0x06, + 0x59, 0x1a, 0x59, 0x22, 0x59, 0x62, 0x59, 0xa8, + 0x16, 0xea, 0x16, 0xec, 0x59, 0x1b, 0x5a, 0x27, + 0x5a, 0xd8, 0x59, 0x66, 0x5a, 0xee, 0x36, 0xfc, + 0x36, 0x08, 0x5b, 0x3e, 0x5b, 0x3e, 0x5b, 0xc8, + 0x19, 0xc3, 0x5b, 0xd8, 0x5b, 0xe7, 0x5b, 0xf3, + 0x5b, 0x18, 0x1b, 0xff, 0x5b, 0x06, 0x5c, 0x53, + 0x5f, 0x22, 0x5c, 0x81, 0x37, 0x60, 0x5c, 0x6e, + 0x5c, 0xc0, 0x5c, 0x8d, 0x5c, 0xe4, 0x1d, 0x43, + 0x5d, 0xe6, 0x1d, 0x6e, 0x5d, 0x6b, 0x5d, 0x7c, + 0x5d, 0xe1, 0x5d, 0xe2, 0x5d, 0x2f, 0x38, 0xfd, + 0x5d, 0x28, 0x5e, 0x3d, 0x5e, 0x69, 0x5e, 0x62, + 0x38, 0x83, 0x21, 0x7c, 0x38, 0xb0, 0x5e, 0xb3, + 0x5e, 0xb6, 0x5e, 0xca, 0x5e, 0x92, 0xa3, 0xfe, + 0x5e, 0x31, 0x23, 0x31, 0x23, 0x01, 0x82, 0x22, + 0x5f, 0x22, 0x5f, 0xc7, 0x38, 0xb8, 0x32, 0xda, + 0x61, 0x62, 0x5f, 0x6b, 0x5f, 0xe3, 0x38, 0x9a, + 0x5f, 0xcd, 0x5f, 0xd7, 0x5f, 0xf9, 0x5f, 0x81, + 0x60, 0x3a, 0x39, 0x1c, 0x39, 0x94, 0x60, 0xd4, + 0x26, 0xc7, 0x60, 0x02, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0a, 0x00, + 0x00, 0x02, 0x08, 0x00, 0x80, 0x08, 0x00, 0x00, + 0x08, 0x80, 0x28, 0x80, 0x02, 0x00, 0x00, 0x02, + 0x48, 0x61, 0x00, 0x04, 0x06, 0x04, 0x32, 0x46, + 0x6a, 0x5c, 0x67, 0x96, 0xaa, 0xae, 0xc8, 0xd3, + 0x5d, 0x62, 0x00, 0x54, 0x77, 0xf3, 0x0c, 0x2b, + 0x3d, 0x63, 0xfc, 0x62, 0x68, 0x63, 0x83, 0x63, + 0xe4, 0x63, 0xf1, 0x2b, 0x22, 0x64, 0xc5, 0x63, + 0xa9, 0x63, 0x2e, 0x3a, 0x69, 0x64, 0x7e, 0x64, + 0x9d, 0x64, 0x77, 0x64, 0x6c, 0x3a, 0x4f, 0x65, + 0x6c, 0x65, 0x0a, 0x30, 0xe3, 0x65, 0xf8, 0x66, + 0x49, 0x66, 0x19, 0x3b, 0x91, 0x66, 0x08, 0x3b, + 0xe4, 0x3a, 0x92, 0x51, 0x95, 0x51, 0x00, 0x67, + 0x9c, 0x66, 0xad, 0x80, 0xd9, 0x43, 0x17, 0x67, + 0x1b, 0x67, 0x21, 0x67, 0x5e, 0x67, 0x53, 0x67, + 0xc3, 0x33, 0x49, 0x3b, 0xfa, 0x67, 0x85, 0x67, + 0x52, 0x68, 0x85, 0x68, 0x6d, 0x34, 0x8e, 0x68, + 0x1f, 0x68, 0x14, 0x69, 0x9d, 0x3b, 0x42, 0x69, + 0xa3, 0x69, 0xea, 0x69, 0xa8, 0x6a, 0xa3, 0x36, + 0xdb, 0x6a, 0x18, 0x3c, 0x21, 0x6b, 0xa7, 0x38, + 0x54, 0x6b, 0x4e, 0x3c, 0x72, 0x6b, 0x9f, 0x6b, + 0xba, 0x6b, 0xbb, 0x6b, 0x8d, 0x3a, 0x0b, 0x1d, + 0xfa, 0x3a, 0x4e, 0x6c, 0xbc, 0x3c, 0xbf, 0x6c, + 0xcd, 0x6c, 0x67, 0x6c, 0x16, 0x6d, 0x3e, 0x6d, + 0x77, 0x6d, 0x41, 0x6d, 0x69, 0x6d, 0x78, 0x6d, + 0x85, 0x6d, 0x1e, 0x3d, 0x34, 0x6d, 0x2f, 0x6e, + 0x6e, 0x6e, 0x33, 0x3d, 0xcb, 0x6e, 0xc7, 0x6e, + 0xd1, 0x3e, 0xf9, 0x6d, 0x6e, 0x6f, 0x5e, 0x3f, + 0x8e, 0x3f, 0xc6, 0x6f, 0x39, 0x70, 0x1e, 0x70, + 0x1b, 0x70, 0x96, 0x3d, 0x4a, 0x70, 0x7d, 0x70, + 0x77, 0x70, 0xad, 0x70, 0x25, 0x05, 0x45, 0x71, + 0x63, 0x42, 0x9c, 0x71, 0xab, 0x43, 0x28, 0x72, + 0x35, 0x72, 0x50, 0x72, 0x08, 0x46, 0x80, 0x72, + 0x95, 0x72, 0x35, 0x47, 0x02, 0x20, 0x00, 0x00, + 0x20, 0x00, 0x00, 0x00, 0x00, 0x08, 0x80, 0x00, + 0x00, 0x02, 0x02, 0x80, 0x8a, 0x00, 0x00, 0x20, + 0x00, 0x08, 0x0a, 0x00, 0x80, 0x88, 0x80, 0x20, + 0x14, 0x48, 0x7a, 0x73, 0x8b, 0x73, 0xac, 0x3e, + 0xa5, 0x73, 0xb8, 0x3e, 0xb8, 0x3e, 0x47, 0x74, + 0x5c, 0x74, 0x71, 0x74, 0x85, 0x74, 0xca, 0x74, + 0x1b, 0x3f, 0x24, 0x75, 0x36, 0x4c, 0x3e, 0x75, + 0x92, 0x4c, 0x70, 0x75, 0x9f, 0x21, 0x10, 0x76, + 0xa1, 0x4f, 0xb8, 0x4f, 0x44, 0x50, 0xfc, 0x3f, + 0x08, 0x40, 0xf4, 0x76, 0xf3, 0x50, 0xf2, 0x50, + 0x19, 0x51, 0x33, 0x51, 0x1e, 0x77, 0x1f, 0x77, + 0x1f, 0x77, 0x4a, 0x77, 0x39, 0x40, 0x8b, 0x77, + 0x46, 0x40, 0x96, 0x40, 0x1d, 0x54, 0x4e, 0x78, + 0x8c, 0x78, 0xcc, 0x78, 0xe3, 0x40, 0x26, 0x56, + 0x56, 0x79, 0x9a, 0x56, 0xc5, 0x56, 0x8f, 0x79, + 0xeb, 0x79, 0x2f, 0x41, 0x40, 0x7a, 0x4a, 0x7a, + 0x4f, 0x7a, 0x7c, 0x59, 0xa7, 0x5a, 0xa7, 0x5a, + 0xee, 0x7a, 0x02, 0x42, 0xab, 0x5b, 0xc6, 0x7b, + 0xc9, 0x7b, 0x27, 0x42, 0x80, 0x5c, 0xd2, 0x7c, + 0xa0, 0x42, 0xe8, 0x7c, 0xe3, 0x7c, 0x00, 0x7d, + 0x86, 0x5f, 0x63, 0x7d, 0x01, 0x43, 0xc7, 0x7d, + 0x02, 0x7e, 0x45, 0x7e, 0x34, 0x43, 0x28, 0x62, + 0x47, 0x62, 0x59, 0x43, 0xd9, 0x62, 0x7a, 0x7f, + 0x3e, 0x63, 0x95, 0x7f, 0xfa, 0x7f, 0x05, 0x80, + 0xda, 0x64, 0x23, 0x65, 0x60, 0x80, 0xa8, 0x65, + 0x70, 0x80, 0x5f, 0x33, 0xd5, 0x43, 0xb2, 0x80, + 0x03, 0x81, 0x0b, 0x44, 0x3e, 0x81, 0xb5, 0x5a, + 0xa7, 0x67, 0xb5, 0x67, 0x93, 0x33, 0x9c, 0x33, + 0x01, 0x82, 0x04, 0x82, 0x9e, 0x8f, 0x6b, 0x44, + 0x91, 0x82, 0x8b, 0x82, 0x9d, 0x82, 0xb3, 0x52, + 0xb1, 0x82, 0xb3, 0x82, 0xbd, 0x82, 0xe6, 0x82, + 0x3c, 0x6b, 0xe5, 0x82, 0x1d, 0x83, 0x63, 0x83, + 0xad, 0x83, 0x23, 0x83, 0xbd, 0x83, 0xe7, 0x83, + 0x57, 0x84, 0x53, 0x83, 0xca, 0x83, 0xcc, 0x83, + 0xdc, 0x83, 0x36, 0x6c, 0x6b, 0x6d, 0x02, 0x00, + 0x00, 0x20, 0x22, 0x2a, 0xa0, 0x0a, 0x00, 0x20, + 0x80, 0x28, 0x00, 0xa8, 0x20, 0x20, 0x00, 0x02, + 0x80, 0x22, 0x02, 0x8a, 0x08, 0x00, 0xaa, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x28, 0xd5, 0x6c, + 0x2b, 0x45, 0xf1, 0x84, 0xf3, 0x84, 0x16, 0x85, + 0xca, 0x73, 0x64, 0x85, 0x2c, 0x6f, 0x5d, 0x45, + 0x61, 0x45, 0xb1, 0x6f, 0xd2, 0x70, 0x6b, 0x45, + 0x50, 0x86, 0x5c, 0x86, 0x67, 0x86, 0x69, 0x86, + 0xa9, 0x86, 0x88, 0x86, 0x0e, 0x87, 0xe2, 0x86, + 0x79, 0x87, 0x28, 0x87, 0x6b, 0x87, 0x86, 0x87, + 0xd7, 0x45, 0xe1, 0x87, 0x01, 0x88, 0xf9, 0x45, + 0x60, 0x88, 0x63, 0x88, 0x67, 0x76, 0xd7, 0x88, + 0xde, 0x88, 0x35, 0x46, 0xfa, 0x88, 0xbb, 0x34, + 0xae, 0x78, 0x66, 0x79, 0xbe, 0x46, 0xc7, 0x46, + 0xa0, 0x8a, 0xed, 0x8a, 0x8a, 0x8b, 0x55, 0x8c, + 0xa8, 0x7c, 0xab, 0x8c, 0xc1, 0x8c, 0x1b, 0x8d, + 0x77, 0x8d, 0x2f, 0x7f, 0x04, 0x08, 0xcb, 0x8d, + 0xbc, 0x8d, 0xf0, 0x8d, 0xde, 0x08, 0xd4, 0x8e, + 0x38, 0x8f, 0xd2, 0x85, 0xed, 0x85, 0x94, 0x90, + 0xf1, 0x90, 0x11, 0x91, 0x2e, 0x87, 0x1b, 0x91, + 0x38, 0x92, 0xd7, 0x92, 0xd8, 0x92, 0x7c, 0x92, + 0xf9, 0x93, 0x15, 0x94, 0xfa, 0x8b, 0x8b, 0x95, + 0x95, 0x49, 0xb7, 0x95, 0x77, 0x8d, 0xe6, 0x49, + 0xc3, 0x96, 0xb2, 0x5d, 0x23, 0x97, 0x45, 0x91, + 0x1a, 0x92, 0x6e, 0x4a, 0x76, 0x4a, 0xe0, 0x97, + 0x0a, 0x94, 0xb2, 0x4a, 0x96, 0x94, 0x0b, 0x98, + 0x0b, 0x98, 0x29, 0x98, 0xb6, 0x95, 0xe2, 0x98, + 0x33, 0x4b, 0x29, 0x99, 0xa7, 0x99, 0xc2, 0x99, + 0xfe, 0x99, 0xce, 0x4b, 0x30, 0x9b, 0x12, 0x9b, + 0x40, 0x9c, 0xfd, 0x9c, 0xce, 0x4c, 0xed, 0x4c, + 0x67, 0x9d, 0xce, 0xa0, 0xf8, 0x4c, 0x05, 0xa1, + 0x0e, 0xa2, 0x91, 0xa2, 0xbb, 0x9e, 0x56, 0x4d, + 0xf9, 0x9e, 0xfe, 0x9e, 0x05, 0x9f, 0x0f, 0x9f, + 0x16, 0x9f, 0x3b, 0x9f, 0x00, 0xa6, 0x02, 0x88, + 0xa0, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x28, + 0x00, 0x08, 0xa0, 0x80, 0xa0, 0x80, 0x00, 0x80, + 0x80, 0x00, 0x0a, 0x88, 0x80, 0x00, 0x80, 0x00, + 0x20, 0x2a, 0x00, 0x80, +}; + +static const uint16_t unicode_comp_table[965] = { + 0x4a01, 0x49c0, 0x4a02, 0x0280, 0x0281, 0x0282, 0x0283, 0x02c0, + 0x02c2, 0x0a00, 0x0284, 0x2442, 0x0285, 0x07c0, 0x0980, 0x0982, + 0x2440, 0x2280, 0x02c4, 0x2282, 0x2284, 0x2286, 0x02c6, 0x02c8, + 0x02ca, 0x02cc, 0x0287, 0x228a, 0x02ce, 0x228c, 0x2290, 0x2292, + 0x228e, 0x0288, 0x0289, 0x028a, 0x2482, 0x0300, 0x0302, 0x0304, + 0x028b, 0x2480, 0x0308, 0x0984, 0x0986, 0x2458, 0x0a02, 0x0306, + 0x2298, 0x229a, 0x229e, 0x0900, 0x030a, 0x22a0, 0x030c, 0x030e, + 0x0840, 0x0310, 0x0312, 0x22a2, 0x22a6, 0x09c0, 0x22a4, 0x22a8, + 0x22aa, 0x028c, 0x028d, 0x028e, 0x0340, 0x0342, 0x0344, 0x0380, + 0x028f, 0x248e, 0x07c2, 0x0988, 0x098a, 0x2490, 0x0346, 0x22ac, + 0x0400, 0x22b0, 0x0842, 0x22b2, 0x0402, 0x22b4, 0x0440, 0x0444, + 0x22b6, 0x0442, 0x22c2, 0x22c0, 0x22c4, 0x22c6, 0x22c8, 0x0940, + 0x04c0, 0x0291, 0x22ca, 0x04c4, 0x22cc, 0x04c2, 0x22d0, 0x22ce, + 0x0292, 0x0293, 0x0294, 0x0295, 0x0540, 0x0542, 0x0a08, 0x0296, + 0x2494, 0x0544, 0x07c4, 0x098c, 0x098e, 0x06c0, 0x2492, 0x0844, + 0x2308, 0x230a, 0x0580, 0x230c, 0x0584, 0x0990, 0x0992, 0x230e, + 0x0582, 0x2312, 0x0586, 0x0588, 0x2314, 0x058c, 0x2316, 0x0998, + 0x058a, 0x231e, 0x0590, 0x2320, 0x099a, 0x058e, 0x2324, 0x2322, + 0x0299, 0x029a, 0x029b, 0x05c0, 0x05c2, 0x05c4, 0x029c, 0x24ac, + 0x05c6, 0x05c8, 0x07c6, 0x0994, 0x0996, 0x0700, 0x24aa, 0x2326, + 0x05ca, 0x232a, 0x2328, 0x2340, 0x2342, 0x2344, 0x2346, 0x05cc, + 0x234a, 0x2348, 0x234c, 0x234e, 0x2350, 0x24b8, 0x029d, 0x05ce, + 0x24be, 0x0a0c, 0x2352, 0x0600, 0x24bc, 0x24ba, 0x0640, 0x2354, + 0x0642, 0x0644, 0x2356, 0x2358, 0x02a0, 0x02a1, 0x02a2, 0x02a3, + 0x02c1, 0x02c3, 0x0a01, 0x02a4, 0x2443, 0x02a5, 0x07c1, 0x0981, + 0x0983, 0x2441, 0x2281, 0x02c5, 0x2283, 0x2285, 0x2287, 0x02c7, + 0x02c9, 0x02cb, 0x02cd, 0x02a7, 0x228b, 0x02cf, 0x228d, 0x2291, + 0x2293, 0x228f, 0x02a8, 0x02a9, 0x02aa, 0x2483, 0x0301, 0x0303, + 0x0305, 0x02ab, 0x2481, 0x0309, 0x0985, 0x0987, 0x2459, 0x0a03, + 0x0307, 0x2299, 0x229b, 0x229f, 0x0901, 0x030b, 0x22a1, 0x030d, + 0x030f, 0x0841, 0x0311, 0x0313, 0x22a3, 0x22a7, 0x09c1, 0x22a5, + 0x22a9, 0x22ab, 0x2380, 0x02ac, 0x02ad, 0x02ae, 0x0341, 0x0343, + 0x0345, 0x02af, 0x248f, 0x07c3, 0x0989, 0x098b, 0x2491, 0x0347, + 0x22ad, 0x0401, 0x0884, 0x22b1, 0x0843, 0x22b3, 0x0403, 0x22b5, + 0x0441, 0x0445, 0x22b7, 0x0443, 0x22c3, 0x22c1, 0x22c5, 0x22c7, + 0x22c9, 0x0941, 0x04c1, 0x02b1, 0x22cb, 0x04c5, 0x22cd, 0x04c3, + 0x22d1, 0x22cf, 0x02b2, 0x02b3, 0x02b4, 0x02b5, 0x0541, 0x0543, + 0x0a09, 0x02b6, 0x2495, 0x0545, 0x07c5, 0x098d, 0x098f, 0x06c1, + 0x2493, 0x0845, 0x2309, 0x230b, 0x0581, 0x230d, 0x0585, 0x0991, + 0x0993, 0x230f, 0x0583, 0x2313, 0x0587, 0x0589, 0x2315, 0x058d, + 0x2317, 0x0999, 0x058b, 0x231f, 0x2381, 0x0591, 0x2321, 0x099b, + 0x058f, 0x2325, 0x2323, 0x02b9, 0x02ba, 0x02bb, 0x05c1, 0x05c3, + 0x05c5, 0x02bc, 0x24ad, 0x05c7, 0x05c9, 0x07c7, 0x0995, 0x0997, + 0x0701, 0x24ab, 0x2327, 0x05cb, 0x232b, 0x2329, 0x2341, 0x2343, + 0x2345, 0x2347, 0x05cd, 0x234b, 0x2349, 0x2382, 0x234d, 0x234f, + 0x2351, 0x24b9, 0x02bd, 0x05cf, 0x24bf, 0x0a0d, 0x2353, 0x02bf, + 0x24bd, 0x2383, 0x24bb, 0x0641, 0x2355, 0x0643, 0x0645, 0x2357, + 0x2359, 0x3101, 0x0c80, 0x2e00, 0x2446, 0x2444, 0x244a, 0x2448, + 0x0800, 0x0942, 0x0944, 0x0804, 0x2288, 0x2486, 0x2484, 0x248a, + 0x2488, 0x22ae, 0x2498, 0x2496, 0x249c, 0x249a, 0x2300, 0x0a06, + 0x2302, 0x0a04, 0x0946, 0x07ce, 0x07ca, 0x07c8, 0x07cc, 0x2447, + 0x2445, 0x244b, 0x2449, 0x0801, 0x0943, 0x0945, 0x0805, 0x2289, + 0x2487, 0x2485, 0x248b, 0x2489, 0x22af, 0x2499, 0x2497, 0x249d, + 0x249b, 0x2301, 0x0a07, 0x2303, 0x0a05, 0x0947, 0x07cf, 0x07cb, + 0x07c9, 0x07cd, 0x2450, 0x244e, 0x2454, 0x2452, 0x2451, 0x244f, + 0x2455, 0x2453, 0x2294, 0x2296, 0x2295, 0x2297, 0x2304, 0x2306, + 0x2305, 0x2307, 0x2318, 0x2319, 0x231a, 0x231b, 0x232c, 0x232d, + 0x232e, 0x232f, 0x2400, 0x24a2, 0x24a0, 0x24a6, 0x24a4, 0x24a8, + 0x24a3, 0x24a1, 0x24a7, 0x24a5, 0x24a9, 0x24b0, 0x24ae, 0x24b4, + 0x24b2, 0x24b6, 0x24b1, 0x24af, 0x24b5, 0x24b3, 0x24b7, 0x0882, + 0x0880, 0x0881, 0x0802, 0x0803, 0x229c, 0x229d, 0x0a0a, 0x0a0b, + 0x0883, 0x0b40, 0x2c8a, 0x0c81, 0x2c89, 0x2c88, 0x2540, 0x2541, + 0x2d00, 0x2e07, 0x0d00, 0x2640, 0x2641, 0x2e80, 0x0d01, 0x26c8, + 0x26c9, 0x2f00, 0x2f84, 0x0d02, 0x2f83, 0x2f82, 0x0d40, 0x26d8, + 0x26d9, 0x3186, 0x0d04, 0x2740, 0x2741, 0x3100, 0x3086, 0x0d06, + 0x3085, 0x3084, 0x0d41, 0x2840, 0x3200, 0x0d07, 0x284f, 0x2850, + 0x3280, 0x2c84, 0x2e03, 0x2857, 0x0d42, 0x2c81, 0x2c80, 0x24c0, + 0x24c1, 0x2c86, 0x2c83, 0x28c0, 0x0d43, 0x25c0, 0x25c1, 0x2940, + 0x0d44, 0x26c0, 0x26c1, 0x2e05, 0x2e02, 0x29c0, 0x0d45, 0x2f05, + 0x2f04, 0x0d80, 0x26d0, 0x26d1, 0x2f80, 0x2a40, 0x0d82, 0x26e0, + 0x26e1, 0x3080, 0x3081, 0x2ac0, 0x0d83, 0x3004, 0x3003, 0x0d81, + 0x27c0, 0x27c1, 0x3082, 0x2b40, 0x0d84, 0x2847, 0x2848, 0x3184, + 0x3181, 0x2f06, 0x0d08, 0x2f81, 0x3005, 0x0d46, 0x3083, 0x3182, + 0x0e00, 0x0e01, 0x0f40, 0x1180, 0x1182, 0x0f03, 0x0f00, 0x11c0, + 0x0f01, 0x1140, 0x1202, 0x1204, 0x0f81, 0x1240, 0x0fc0, 0x1242, + 0x0f80, 0x1244, 0x1284, 0x0f82, 0x1286, 0x1288, 0x128a, 0x12c0, + 0x1282, 0x1181, 0x1183, 0x1043, 0x1040, 0x11c1, 0x1041, 0x1141, + 0x1203, 0x1205, 0x10c1, 0x1241, 0x1000, 0x1243, 0x10c0, 0x1245, + 0x1285, 0x10c2, 0x1287, 0x1289, 0x128b, 0x12c1, 0x1283, 0x1080, + 0x1100, 0x1101, 0x1200, 0x1201, 0x1280, 0x1281, 0x1340, 0x1341, + 0x1343, 0x1342, 0x1344, 0x13c2, 0x1400, 0x13c0, 0x1440, 0x1480, + 0x14c0, 0x1540, 0x1541, 0x1740, 0x1700, 0x1741, 0x17c0, 0x1800, + 0x1802, 0x1801, 0x1840, 0x1880, 0x1900, 0x18c0, 0x18c1, 0x1901, + 0x1940, 0x1942, 0x1941, 0x1980, 0x19c0, 0x19c2, 0x19c1, 0x1c80, + 0x1cc0, 0x1dc0, 0x1f80, 0x2000, 0x2002, 0x2004, 0x2006, 0x2008, + 0x2040, 0x2080, 0x2082, 0x20c0, 0x20c1, 0x2100, 0x22b8, 0x22b9, + 0x2310, 0x2311, 0x231c, 0x231d, 0x244c, 0x2456, 0x244d, 0x2457, + 0x248c, 0x248d, 0x249e, 0x249f, 0x2500, 0x2502, 0x2504, 0x2bc0, + 0x2501, 0x2503, 0x2505, 0x2bc1, 0x2bc2, 0x2bc3, 0x2bc4, 0x2bc5, + 0x2bc6, 0x2bc7, 0x2580, 0x2582, 0x2584, 0x2bc8, 0x2581, 0x2583, + 0x2585, 0x2bc9, 0x2bca, 0x2bcb, 0x2bcc, 0x2bcd, 0x2bce, 0x2bcf, + 0x2600, 0x2602, 0x2601, 0x2603, 0x2680, 0x2682, 0x2681, 0x2683, + 0x26c2, 0x26c4, 0x26c6, 0x2c00, 0x26c3, 0x26c5, 0x26c7, 0x2c01, + 0x2c02, 0x2c03, 0x2c04, 0x2c05, 0x2c06, 0x2c07, 0x26ca, 0x26cc, + 0x26ce, 0x2c08, 0x26cb, 0x26cd, 0x26cf, 0x2c09, 0x2c0a, 0x2c0b, + 0x2c0c, 0x2c0d, 0x2c0e, 0x2c0f, 0x26d2, 0x26d4, 0x26d6, 0x26d3, + 0x26d5, 0x26d7, 0x26da, 0x26dc, 0x26de, 0x26db, 0x26dd, 0x26df, + 0x2700, 0x2702, 0x2701, 0x2703, 0x2780, 0x2782, 0x2781, 0x2783, + 0x2800, 0x2802, 0x2804, 0x2801, 0x2803, 0x2805, 0x2842, 0x2844, + 0x2846, 0x2849, 0x284b, 0x284d, 0x2c40, 0x284a, 0x284c, 0x284e, + 0x2c41, 0x2c42, 0x2c43, 0x2c44, 0x2c45, 0x2c46, 0x2c47, 0x2851, + 0x2853, 0x2855, 0x2c48, 0x2852, 0x2854, 0x2856, 0x2c49, 0x2c4a, + 0x2c4b, 0x2c4c, 0x2c4d, 0x2c4e, 0x2c4f, 0x2c82, 0x2e01, 0x3180, + 0x2c87, 0x2f01, 0x2f02, 0x2f03, 0x2e06, 0x3185, 0x3000, 0x3001, + 0x3002, 0x4640, 0x4641, 0x4680, 0x46c0, 0x46c2, 0x46c1, 0x4700, + 0x4740, 0x4780, 0x47c0, 0x47c2, 0x4900, 0x4940, 0x4980, 0x4982, + 0x4a00, 0x49c2, 0x4a03, 0x4a04, 0x4a40, 0x4a41, 0x4a80, 0x4a81, + 0x4ac0, 0x4ac1, 0x4bc0, 0x4bc1, 0x4b00, 0x4b01, 0x4b40, 0x4b41, + 0x4bc2, 0x4bc3, 0x4b80, 0x4b81, 0x4b82, 0x4b83, 0x4c00, 0x4c01, + 0x4c02, 0x4c03, 0x5600, 0x5440, 0x5442, 0x5444, 0x5446, 0x5448, + 0x544a, 0x544c, 0x544e, 0x5450, 0x5452, 0x5454, 0x5456, 0x5480, + 0x5482, 0x5484, 0x54c0, 0x54c1, 0x5500, 0x5501, 0x5540, 0x5541, + 0x5580, 0x5581, 0x55c0, 0x55c1, 0x5680, 0x58c0, 0x5700, 0x5702, + 0x5704, 0x5706, 0x5708, 0x570a, 0x570c, 0x570e, 0x5710, 0x5712, + 0x5714, 0x5716, 0x5740, 0x5742, 0x5744, 0x5780, 0x5781, 0x57c0, + 0x57c1, 0x5800, 0x5801, 0x5840, 0x5841, 0x5880, 0x5881, 0x5900, + 0x5901, 0x5902, 0x5903, 0x5940, 0x8ec0, 0x8f00, 0x8fc0, 0x8fc2, + 0x9000, 0x9040, 0x9041, 0x9080, 0x9081, 0x90c0, 0x90c2, 0x9100, + 0x9140, 0x9182, 0x9180, 0x9183, 0x91c1, 0x91c0, 0x91c3, 0x9200, + 0x9201, 0x9240, 0x9280, 0x9282, 0x9284, 0x9281, 0x9285, 0x9287, + 0x9286, 0x9283, 0x92c1, 0x92c0, 0x92c2, +}; + +typedef enum { + UNICODE_GC_Cn, + UNICODE_GC_Lu, + UNICODE_GC_Ll, + UNICODE_GC_Lt, + UNICODE_GC_Lm, + UNICODE_GC_Lo, + UNICODE_GC_Mn, + UNICODE_GC_Mc, + UNICODE_GC_Me, + UNICODE_GC_Nd, + UNICODE_GC_Nl, + UNICODE_GC_No, + UNICODE_GC_Sm, + UNICODE_GC_Sc, + UNICODE_GC_Sk, + UNICODE_GC_So, + UNICODE_GC_Pc, + UNICODE_GC_Pd, + UNICODE_GC_Ps, + UNICODE_GC_Pe, + UNICODE_GC_Pi, + UNICODE_GC_Pf, + UNICODE_GC_Po, + UNICODE_GC_Zs, + UNICODE_GC_Zl, + UNICODE_GC_Zp, + UNICODE_GC_Cc, + UNICODE_GC_Cf, + UNICODE_GC_Cs, + UNICODE_GC_Co, + UNICODE_GC_LC, + UNICODE_GC_L, + UNICODE_GC_M, + UNICODE_GC_N, + UNICODE_GC_S, + UNICODE_GC_P, + UNICODE_GC_Z, + UNICODE_GC_C, + UNICODE_GC_COUNT, +} UnicodeGCEnum; + +static const char unicode_gc_name_table[] = + "Cn,Unassigned" "\0" + "Lu,Uppercase_Letter" "\0" + "Ll,Lowercase_Letter" "\0" + "Lt,Titlecase_Letter" "\0" + "Lm,Modifier_Letter" "\0" + "Lo,Other_Letter" "\0" + "Mn,Nonspacing_Mark" "\0" + "Mc,Spacing_Mark" "\0" + "Me,Enclosing_Mark" "\0" + "Nd,Decimal_Number,digit" "\0" + "Nl,Letter_Number" "\0" + "No,Other_Number" "\0" + "Sm,Math_Symbol" "\0" + "Sc,Currency_Symbol" "\0" + "Sk,Modifier_Symbol" "\0" + "So,Other_Symbol" "\0" + "Pc,Connector_Punctuation" "\0" + "Pd,Dash_Punctuation" "\0" + "Ps,Open_Punctuation" "\0" + "Pe,Close_Punctuation" "\0" + "Pi,Initial_Punctuation" "\0" + "Pf,Final_Punctuation" "\0" + "Po,Other_Punctuation" "\0" + "Zs,Space_Separator" "\0" + "Zl,Line_Separator" "\0" + "Zp,Paragraph_Separator" "\0" + "Cc,Control,cntrl" "\0" + "Cf,Format" "\0" + "Cs,Surrogate" "\0" + "Co,Private_Use" "\0" + "LC,Cased_Letter" "\0" + "L,Letter" "\0" + "M,Mark,Combining_Mark" "\0" + "N,Number" "\0" + "S,Symbol" "\0" + "P,Punctuation,punct" "\0" + "Z,Separator" "\0" + "C,Other" "\0" +; + +static const uint8_t unicode_gc_table[4122] = { + 0xfa, 0x18, 0x17, 0x56, 0x0d, 0x56, 0x12, 0x13, + 0x16, 0x0c, 0x16, 0x11, 0x36, 0xe9, 0x02, 0x36, + 0x4c, 0x36, 0xe1, 0x12, 0x12, 0x16, 0x13, 0x0e, + 0x10, 0x0e, 0xe2, 0x12, 0x12, 0x0c, 0x13, 0x0c, + 0xfa, 0x19, 0x17, 0x16, 0x6d, 0x0f, 0x16, 0x0e, + 0x0f, 0x05, 0x14, 0x0c, 0x1b, 0x0f, 0x0e, 0x0f, + 0x0c, 0x2b, 0x0e, 0x02, 0x36, 0x0e, 0x0b, 0x05, + 0x15, 0x4b, 0x16, 0xe1, 0x0f, 0x0c, 0xc1, 0xe2, + 0x10, 0x0c, 0xe2, 0x00, 0xff, 0x30, 0x02, 0xff, + 0x08, 0x02, 0xff, 0x27, 0xbf, 0x22, 0x21, 0x02, + 0x5f, 0x5f, 0x21, 0x22, 0x61, 0x02, 0x21, 0x02, + 0x41, 0x42, 0x21, 0x02, 0x21, 0x02, 0x9f, 0x7f, + 0x02, 0x5f, 0x5f, 0x21, 0x02, 0x5f, 0x3f, 0x02, + 0x05, 0x3f, 0x22, 0x65, 0x01, 0x03, 0x02, 0x01, + 0x03, 0x02, 0x01, 0x03, 0x02, 0xff, 0x08, 0x02, + 0xff, 0x0a, 0x02, 0x01, 0x03, 0x02, 0x5f, 0x21, + 0x02, 0xff, 0x32, 0xa2, 0x21, 0x02, 0x21, 0x22, + 0x5f, 0x41, 0x02, 0xff, 0x00, 0xe2, 0x3c, 0x25, + 0xe2, 0x12, 0xe4, 0x0a, 0x6e, 0xe4, 0x04, 0xee, + 0x06, 0x84, 0xce, 0x04, 0x0e, 0x04, 0xee, 0x09, + 0xe6, 0x68, 0x7f, 0x04, 0x0e, 0x3f, 0x20, 0x04, + 0x42, 0x16, 0x01, 0x60, 0x2e, 0x01, 0x16, 0x41, + 0x00, 0x01, 0x00, 0x21, 0x02, 0xe1, 0x09, 0x00, + 0xe1, 0x01, 0xe2, 0x1b, 0x3f, 0x02, 0x41, 0x42, + 0xff, 0x10, 0x62, 0x3f, 0x0c, 0x5f, 0x3f, 0x02, + 0xe1, 0x2b, 0xe2, 0x28, 0xff, 0x1a, 0x0f, 0x86, + 0x28, 0xff, 0x2f, 0xff, 0x06, 0x02, 0xff, 0x58, + 0x00, 0xe1, 0x1e, 0x20, 0x04, 0xb6, 0xe2, 0x21, + 0x16, 0x11, 0x20, 0x2f, 0x0d, 0x00, 0xe6, 0x25, + 0x11, 0x06, 0x16, 0x26, 0x16, 0x26, 0x16, 0x06, + 0xe0, 0x00, 0xe5, 0x13, 0x60, 0x65, 0x36, 0xe0, + 0x03, 0xbb, 0x4c, 0x36, 0x0d, 0x36, 0x2f, 0xe6, + 0x03, 0x16, 0x1b, 0x56, 0xe5, 0x18, 0x04, 0xe5, + 0x02, 0xe6, 0x0d, 0xe9, 0x02, 0x76, 0x25, 0x06, + 0xe5, 0x5b, 0x16, 0x05, 0xc6, 0x1b, 0x0f, 0xa6, + 0x24, 0x26, 0x0f, 0x66, 0x25, 0xe9, 0x02, 0x45, + 0x2f, 0x05, 0xf6, 0x06, 0x00, 0x1b, 0x05, 0x06, + 0xe5, 0x16, 0xe6, 0x13, 0x20, 0xe5, 0x51, 0xe6, + 0x03, 0x05, 0xe0, 0x06, 0xe9, 0x02, 0xe5, 0x19, + 0xe6, 0x01, 0x24, 0x0f, 0x56, 0x04, 0x20, 0x06, + 0x2d, 0xe5, 0x0e, 0x66, 0x04, 0xe6, 0x01, 0x04, + 0x46, 0x04, 0x86, 0x20, 0xf6, 0x07, 0x00, 0xe5, + 0x11, 0x46, 0x20, 0x16, 0x00, 0xe5, 0x03, 0x80, + 0xe5, 0x10, 0x0e, 0xc5, 0x3b, 0x80, 0xe6, 0x01, + 0xe5, 0x21, 0x04, 0xe6, 0x10, 0x1b, 0xe6, 0x18, + 0x07, 0xe5, 0x2e, 0x06, 0x07, 0x06, 0x05, 0x47, + 0xe6, 0x00, 0x67, 0x06, 0x27, 0x05, 0xc6, 0xe5, + 0x02, 0x26, 0x36, 0xe9, 0x02, 0x16, 0x04, 0xe5, + 0x07, 0x06, 0x27, 0x00, 0xe5, 0x00, 0x20, 0x25, + 0x20, 0xe5, 0x0e, 0x00, 0xc5, 0x00, 0x05, 0x40, + 0x65, 0x20, 0x06, 0x05, 0x47, 0x66, 0x20, 0x27, + 0x20, 0x27, 0x06, 0x05, 0xe0, 0x00, 0x07, 0x60, + 0x25, 0x00, 0x45, 0x26, 0x20, 0xe9, 0x02, 0x25, + 0x2d, 0xab, 0x0f, 0x0d, 0x05, 0x16, 0x06, 0x20, + 0x26, 0x07, 0x00, 0xa5, 0x60, 0x25, 0x20, 0xe5, + 0x0e, 0x00, 0xc5, 0x00, 0x25, 0x00, 0x25, 0x00, + 0x25, 0x20, 0x06, 0x00, 0x47, 0x26, 0x60, 0x26, + 0x20, 0x46, 0x40, 0x06, 0xc0, 0x65, 0x00, 0x05, + 0xc0, 0xe9, 0x02, 0x26, 0x45, 0x06, 0x16, 0xe0, + 0x02, 0x26, 0x07, 0x00, 0xe5, 0x01, 0x00, 0x45, + 0x00, 0xe5, 0x0e, 0x00, 0xc5, 0x00, 0x25, 0x00, + 0x85, 0x20, 0x06, 0x05, 0x47, 0x86, 0x00, 0x26, + 0x07, 0x00, 0x27, 0x06, 0x20, 0x05, 0xe0, 0x07, + 0x25, 0x26, 0x20, 0xe9, 0x02, 0x16, 0x0d, 0xc0, + 0x05, 0xa6, 0x00, 0x06, 0x27, 0x00, 0xe5, 0x00, + 0x20, 0x25, 0x20, 0xe5, 0x0e, 0x00, 0xc5, 0x00, + 0x25, 0x00, 0x85, 0x20, 0x06, 0x05, 0x07, 0x06, + 0x07, 0x66, 0x20, 0x27, 0x20, 0x27, 0x06, 0xc0, + 0x26, 0x07, 0x60, 0x25, 0x00, 0x45, 0x26, 0x20, + 0xe9, 0x02, 0x0f, 0x05, 0xab, 0xe0, 0x02, 0x06, + 0x05, 0x00, 0xa5, 0x40, 0x45, 0x00, 0x65, 0x40, + 0x25, 0x00, 0x05, 0x00, 0x25, 0x40, 0x25, 0x40, + 0x45, 0x40, 0xe5, 0x04, 0x60, 0x27, 0x06, 0x27, + 0x40, 0x47, 0x00, 0x47, 0x06, 0x20, 0x05, 0xa0, + 0x07, 0xe0, 0x06, 0xe9, 0x02, 0x4b, 0xaf, 0x0d, + 0x0f, 0x80, 0x06, 0x47, 0x06, 0xe5, 0x00, 0x00, + 0x45, 0x00, 0xe5, 0x0f, 0x00, 0xe5, 0x08, 0x20, + 0x06, 0x05, 0x46, 0x67, 0x00, 0x46, 0x00, 0x66, + 0xc0, 0x26, 0x00, 0x45, 0x00, 0x25, 0x20, 0x25, + 0x26, 0x20, 0xe9, 0x02, 0xc0, 0x16, 0xcb, 0x0f, + 0x05, 0x06, 0x27, 0x16, 0xe5, 0x00, 0x00, 0x45, + 0x00, 0xe5, 0x0f, 0x00, 0xe5, 0x02, 0x00, 0x85, + 0x20, 0x06, 0x05, 0x07, 0x06, 0x87, 0x00, 0x06, + 0x27, 0x00, 0x27, 0x26, 0xc0, 0x27, 0x80, 0x45, + 0x00, 0x25, 0x26, 0x20, 0xe9, 0x02, 0x00, 0x25, + 0x07, 0xe0, 0x04, 0x26, 0x27, 0xe5, 0x01, 0x00, + 0x45, 0x00, 0xe5, 0x21, 0x26, 0x05, 0x47, 0x66, + 0x00, 0x47, 0x00, 0x47, 0x06, 0x05, 0x0f, 0x60, + 0x45, 0x07, 0xcb, 0x45, 0x26, 0x20, 0xe9, 0x02, + 0xeb, 0x01, 0x0f, 0xa5, 0x00, 0x06, 0x27, 0x00, + 0xe5, 0x0a, 0x40, 0xe5, 0x10, 0x00, 0xe5, 0x01, + 0x00, 0x05, 0x20, 0xc5, 0x40, 0x06, 0x60, 0x47, + 0x46, 0x00, 0x06, 0x00, 0xe7, 0x00, 0xa0, 0xe9, + 0x02, 0x20, 0x27, 0x16, 0xe0, 0x04, 0xe5, 0x28, + 0x06, 0x25, 0xc6, 0x60, 0x0d, 0xa5, 0x04, 0xe6, + 0x00, 0x16, 0xe9, 0x02, 0x36, 0xe0, 0x1d, 0x25, + 0x00, 0x05, 0x00, 0x85, 0x00, 0xe5, 0x10, 0x00, + 0x05, 0x00, 0xe5, 0x02, 0x06, 0x25, 0xe6, 0x01, + 0x05, 0x20, 0x85, 0x00, 0x04, 0x00, 0xc6, 0x00, + 0xe9, 0x02, 0x20, 0x65, 0xe0, 0x18, 0x05, 0x4f, + 0xf6, 0x07, 0x0f, 0x16, 0x4f, 0x26, 0xaf, 0xe9, + 0x02, 0xeb, 0x02, 0x0f, 0x06, 0x0f, 0x06, 0x0f, + 0x06, 0x12, 0x13, 0x12, 0x13, 0x27, 0xe5, 0x00, + 0x00, 0xe5, 0x1c, 0x60, 0xe6, 0x06, 0x07, 0x86, + 0x16, 0x26, 0x85, 0xe6, 0x03, 0x00, 0xe6, 0x1c, + 0x00, 0xef, 0x00, 0x06, 0xaf, 0x00, 0x2f, 0x96, + 0x6f, 0x36, 0xe0, 0x1d, 0xe5, 0x23, 0x27, 0x66, + 0x07, 0xa6, 0x07, 0x26, 0x27, 0x26, 0x05, 0xe9, + 0x02, 0xb6, 0xa5, 0x27, 0x26, 0x65, 0x46, 0x05, + 0x47, 0x25, 0xc7, 0x45, 0x66, 0xe5, 0x05, 0x06, + 0x27, 0x26, 0xa7, 0x06, 0x05, 0x07, 0xe9, 0x02, + 0x47, 0x06, 0x2f, 0xe1, 0x1e, 0x00, 0x01, 0x80, + 0x01, 0x20, 0xe2, 0x23, 0x16, 0x04, 0x42, 0xe5, + 0x80, 0xc1, 0x00, 0x65, 0x20, 0xc5, 0x00, 0x05, + 0x00, 0x65, 0x20, 0xe5, 0x21, 0x00, 0x65, 0x20, + 0xe5, 0x19, 0x00, 0x65, 0x20, 0xc5, 0x00, 0x05, + 0x00, 0x65, 0x20, 0xe5, 0x07, 0x00, 0xe5, 0x31, + 0x00, 0x65, 0x20, 0xe5, 0x3b, 0x20, 0x46, 0xf6, + 0x01, 0xeb, 0x0c, 0x40, 0xe5, 0x08, 0xef, 0x02, + 0xa0, 0xe1, 0x4e, 0x20, 0xa2, 0x20, 0x11, 0xe5, + 0x81, 0xe4, 0x0f, 0x16, 0xe5, 0x09, 0x17, 0xe5, + 0x12, 0x12, 0x13, 0x40, 0xe5, 0x43, 0x56, 0x4a, + 0xe5, 0x00, 0xc0, 0xe5, 0x0a, 0x46, 0x07, 0xe0, + 0x01, 0xe5, 0x0b, 0x26, 0x07, 0x36, 0xe0, 0x01, + 0xe5, 0x0a, 0x26, 0xe0, 0x04, 0xe5, 0x05, 0x00, + 0x45, 0x00, 0x26, 0xe0, 0x04, 0xe5, 0x2c, 0x26, + 0x07, 0xc6, 0xe7, 0x00, 0x06, 0x27, 0xe6, 0x03, + 0x56, 0x04, 0x56, 0x0d, 0x05, 0x06, 0x20, 0xe9, + 0x02, 0xa0, 0xeb, 0x02, 0xa0, 0xb6, 0x11, 0x76, + 0x46, 0x1b, 0x06, 0xe9, 0x02, 0xa0, 0xe5, 0x1b, + 0x04, 0xe5, 0x2d, 0xc0, 0x85, 0x26, 0xe5, 0x1a, + 0x06, 0x05, 0x80, 0xe5, 0x3e, 0xe0, 0x02, 0xe5, + 0x17, 0x00, 0x46, 0x67, 0x26, 0x47, 0x60, 0x27, + 0x06, 0xa7, 0x46, 0x60, 0x0f, 0x40, 0x36, 0xe9, + 0x02, 0xe5, 0x16, 0x20, 0x85, 0xe0, 0x03, 0xe5, + 0x24, 0x60, 0xe5, 0x12, 0xa0, 0xe9, 0x02, 0x0b, + 0x40, 0xef, 0x1a, 0xe5, 0x0f, 0x26, 0x27, 0x06, + 0x20, 0x36, 0xe5, 0x2d, 0x07, 0x06, 0x07, 0xc6, + 0x00, 0x06, 0x07, 0x06, 0x27, 0xe6, 0x00, 0xa7, + 0xe6, 0x02, 0x20, 0x06, 0xe9, 0x02, 0xa0, 0xe9, + 0x02, 0xa0, 0xd6, 0x04, 0xb6, 0x20, 0xe6, 0x06, + 0x08, 0xe6, 0x17, 0x20, 0xe6, 0x04, 0xe0, 0x0c, + 0x66, 0x07, 0xe5, 0x27, 0x06, 0x07, 0x86, 0x07, + 0x06, 0x87, 0x06, 0x27, 0xe5, 0x00, 0x00, 0x36, + 0xe9, 0x02, 0xd6, 0xef, 0x02, 0xe6, 0x01, 0xef, + 0x01, 0x56, 0x26, 0x07, 0xe5, 0x16, 0x07, 0x66, + 0x27, 0x26, 0x07, 0x46, 0x25, 0xe9, 0x02, 0xe5, + 0x24, 0x06, 0x07, 0x26, 0x47, 0x06, 0x07, 0x46, + 0x27, 0xe0, 0x00, 0x76, 0xe5, 0x1c, 0xe7, 0x00, + 0xe6, 0x00, 0x27, 0x26, 0x40, 0x96, 0xe9, 0x02, + 0x40, 0x45, 0xe9, 0x02, 0xe5, 0x16, 0xa4, 0x36, + 0xe2, 0x01, 0x3f, 0x80, 0xe1, 0x23, 0x20, 0x41, + 0xf6, 0x00, 0xe0, 0x00, 0x46, 0x16, 0xe6, 0x05, + 0x07, 0xc6, 0x65, 0x06, 0xa5, 0x06, 0x25, 0x07, + 0x26, 0x05, 0x80, 0xe2, 0x24, 0xe4, 0x37, 0xe2, + 0x05, 0x04, 0xe2, 0x1a, 0xe4, 0x1d, 0xe6, 0x38, + 0xff, 0x80, 0x0e, 0xe2, 0x00, 0xff, 0x5a, 0xe2, + 0x00, 0xe1, 0x00, 0xa2, 0x20, 0xa1, 0x20, 0xe2, + 0x00, 0xe1, 0x00, 0xe2, 0x00, 0xe1, 0x00, 0xa2, + 0x20, 0xa1, 0x20, 0xe2, 0x00, 0x00, 0x01, 0x00, + 0x01, 0x00, 0x01, 0x00, 0x3f, 0xc2, 0xe1, 0x00, + 0xe2, 0x06, 0x20, 0xe2, 0x00, 0xe3, 0x00, 0xe2, + 0x00, 0xe3, 0x00, 0xe2, 0x00, 0xe3, 0x00, 0x82, + 0x00, 0x22, 0x61, 0x03, 0x0e, 0x02, 0x4e, 0x42, + 0x00, 0x22, 0x61, 0x03, 0x4e, 0x62, 0x20, 0x22, + 0x61, 0x00, 0x4e, 0xe2, 0x00, 0x81, 0x4e, 0x20, + 0x42, 0x00, 0x22, 0x61, 0x03, 0x2e, 0x00, 0xf7, + 0x03, 0x9b, 0xb1, 0x36, 0x14, 0x15, 0x12, 0x34, + 0x15, 0x12, 0x14, 0xf6, 0x00, 0x18, 0x19, 0x9b, + 0x17, 0xf6, 0x01, 0x14, 0x15, 0x76, 0x30, 0x56, + 0x0c, 0x12, 0x13, 0xf6, 0x03, 0x0c, 0x16, 0x10, + 0xf6, 0x02, 0x17, 0x9b, 0x00, 0xfb, 0x02, 0x0b, + 0x04, 0x20, 0xab, 0x4c, 0x12, 0x13, 0x04, 0xeb, + 0x02, 0x4c, 0x12, 0x13, 0x00, 0xe4, 0x05, 0x40, + 0xed, 0x1a, 0xe0, 0x06, 0xe6, 0x05, 0x68, 0x06, + 0x48, 0xe6, 0x04, 0xe0, 0x07, 0x2f, 0x01, 0x6f, + 0x01, 0x2f, 0x02, 0x41, 0x22, 0x41, 0x02, 0x0f, + 0x01, 0x2f, 0x0c, 0x81, 0xaf, 0x01, 0x0f, 0x01, + 0x0f, 0x01, 0x0f, 0x61, 0x0f, 0x02, 0x61, 0x02, + 0x65, 0x02, 0x2f, 0x22, 0x21, 0x8c, 0x3f, 0x42, + 0x0f, 0x0c, 0x2f, 0x02, 0x0f, 0xeb, 0x08, 0xea, + 0x1b, 0x3f, 0x6a, 0x0b, 0x2f, 0x60, 0x8c, 0x8f, + 0x2c, 0x6f, 0x0c, 0x2f, 0x0c, 0x2f, 0x0c, 0xcf, + 0x0c, 0xef, 0x17, 0x2c, 0x2f, 0x0c, 0x0f, 0x0c, + 0xef, 0x17, 0xec, 0x80, 0x84, 0xef, 0x00, 0x12, + 0x13, 0x12, 0x13, 0xef, 0x0c, 0x2c, 0xcf, 0x12, + 0x13, 0xef, 0x49, 0x0c, 0xef, 0x16, 0xec, 0x11, + 0xef, 0x20, 0xac, 0xef, 0x40, 0xe0, 0x0e, 0xef, + 0x03, 0xe0, 0x0d, 0xeb, 0x34, 0xef, 0x46, 0xeb, + 0x0e, 0xef, 0x80, 0x2f, 0x0c, 0xef, 0x01, 0x0c, + 0xef, 0x2e, 0xec, 0x00, 0xef, 0x67, 0x0c, 0xef, + 0x80, 0x70, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, + 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, + 0xeb, 0x16, 0xef, 0x24, 0x8c, 0x12, 0x13, 0xec, + 0x17, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, + 0x13, 0x12, 0x13, 0xec, 0x08, 0xef, 0x80, 0x78, + 0xec, 0x7b, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, + 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, + 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, + 0xec, 0x37, 0x12, 0x13, 0x12, 0x13, 0xec, 0x18, + 0x12, 0x13, 0xec, 0x80, 0x7a, 0xef, 0x28, 0xec, + 0x0d, 0x2f, 0xac, 0xef, 0x1f, 0x20, 0xef, 0x80, + 0x02, 0xe1, 0x28, 0xe2, 0x28, 0x5f, 0x21, 0x22, + 0xdf, 0x41, 0x02, 0x3f, 0x02, 0x3f, 0x82, 0x24, + 0x41, 0x02, 0xff, 0x5a, 0x02, 0xaf, 0x7f, 0x46, + 0x3f, 0x80, 0x76, 0x0b, 0x36, 0xe2, 0x1e, 0x00, + 0x02, 0x80, 0x02, 0x20, 0xe5, 0x30, 0xc0, 0x04, + 0x16, 0xe0, 0x06, 0x06, 0xe5, 0x0f, 0xe0, 0x01, + 0xc5, 0x00, 0xc5, 0x00, 0xc5, 0x00, 0xc5, 0x00, + 0xc5, 0x00, 0xc5, 0x00, 0xc5, 0x00, 0xc5, 0x00, + 0xe6, 0x18, 0x36, 0x14, 0x15, 0x14, 0x15, 0x56, + 0x14, 0x15, 0x16, 0x14, 0x15, 0xf6, 0x01, 0x11, + 0x36, 0x11, 0x16, 0x14, 0x15, 0x36, 0x14, 0x15, + 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, + 0x96, 0x04, 0xf6, 0x02, 0x31, 0x76, 0x11, 0x16, + 0x12, 0xf6, 0x05, 0x2f, 0x56, 0x12, 0x13, 0x12, + 0x13, 0x12, 0x13, 0x12, 0x13, 0x11, 0xe0, 0x1a, + 0xef, 0x12, 0x00, 0xef, 0x51, 0xe0, 0x04, 0xef, + 0x80, 0x4e, 0xe0, 0x12, 0xef, 0x08, 0x17, 0x56, + 0x0f, 0x04, 0x05, 0x0a, 0x12, 0x13, 0x12, 0x13, + 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x2f, 0x12, + 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x11, + 0x12, 0x33, 0x0f, 0xea, 0x01, 0x66, 0x27, 0x11, + 0x84, 0x2f, 0x4a, 0x04, 0x05, 0x16, 0x2f, 0x00, + 0xe5, 0x4e, 0x20, 0x26, 0x2e, 0x24, 0x05, 0x11, + 0xe5, 0x52, 0x16, 0x44, 0x05, 0x80, 0xe5, 0x23, + 0x00, 0xe5, 0x56, 0x00, 0x2f, 0x6b, 0xef, 0x02, + 0xe5, 0x18, 0xef, 0x1e, 0xe0, 0x01, 0x0f, 0xe5, + 0x08, 0xef, 0x17, 0x00, 0xeb, 0x02, 0xef, 0x16, + 0xeb, 0x00, 0x0f, 0xeb, 0x07, 0xef, 0x18, 0xeb, + 0x02, 0xef, 0x1f, 0xeb, 0x07, 0xef, 0x80, 0xb8, + 0xe5, 0x99, 0x38, 0xef, 0x38, 0xe5, 0xc0, 0x11, + 0x8d, 0x04, 0xe5, 0x83, 0xef, 0x40, 0xef, 0x2f, + 0xe0, 0x01, 0xe5, 0x20, 0xa4, 0x36, 0xe5, 0x80, + 0x84, 0x04, 0x56, 0xe5, 0x08, 0xe9, 0x02, 0x25, + 0xe0, 0x0c, 0xff, 0x26, 0x05, 0x06, 0x48, 0x16, + 0xe6, 0x02, 0x16, 0x04, 0xff, 0x14, 0x24, 0x26, + 0xe5, 0x3e, 0xea, 0x02, 0x26, 0xb6, 0xe0, 0x00, + 0xee, 0x0f, 0xe4, 0x01, 0x2e, 0xff, 0x06, 0x22, + 0xff, 0x36, 0x04, 0xe2, 0x00, 0x9f, 0xff, 0x02, + 0x04, 0x2e, 0x7f, 0x05, 0x7f, 0x22, 0xff, 0x0d, + 0x61, 0x02, 0x81, 0x02, 0xff, 0x07, 0x41, 0x02, + 0x5f, 0xff, 0x09, 0xe0, 0x0c, 0x64, 0x3f, 0x05, + 0x24, 0x02, 0xc5, 0x06, 0x45, 0x06, 0x65, 0x06, + 0xe5, 0x0f, 0x27, 0x26, 0x07, 0x6f, 0x06, 0x40, + 0xab, 0x2f, 0x0d, 0x0f, 0xa0, 0xe5, 0x2c, 0x76, + 0xe0, 0x00, 0x27, 0xe5, 0x2a, 0xe7, 0x08, 0x26, + 0xe0, 0x00, 0x36, 0xe9, 0x02, 0xa0, 0xe6, 0x0a, + 0xa5, 0x56, 0x05, 0x16, 0x25, 0x06, 0xe9, 0x02, + 0xe5, 0x14, 0xe6, 0x00, 0x36, 0xe5, 0x0f, 0xe6, + 0x03, 0x27, 0xe0, 0x03, 0x16, 0xe5, 0x15, 0x40, + 0x46, 0x07, 0xe5, 0x27, 0x06, 0x27, 0x66, 0x27, + 0x26, 0x47, 0xf6, 0x05, 0x00, 0x04, 0xe9, 0x02, + 0x60, 0x36, 0x85, 0x06, 0x04, 0xe5, 0x01, 0xe9, + 0x02, 0x85, 0x00, 0xe5, 0x21, 0xa6, 0x27, 0x26, + 0x27, 0x26, 0xe0, 0x01, 0x45, 0x06, 0xe5, 0x00, + 0x06, 0x07, 0x20, 0xe9, 0x02, 0x20, 0x76, 0xe5, + 0x08, 0x04, 0xa5, 0x4f, 0x05, 0x07, 0x06, 0x07, + 0xe5, 0x2a, 0x06, 0x05, 0x46, 0x25, 0x26, 0x85, + 0x26, 0x05, 0x06, 0x05, 0xe0, 0x10, 0x25, 0x04, + 0x36, 0xe5, 0x03, 0x07, 0x26, 0x27, 0x36, 0x05, + 0x24, 0x07, 0x06, 0xe0, 0x02, 0xa5, 0x20, 0xa5, + 0x20, 0xa5, 0xe0, 0x01, 0xc5, 0x00, 0xc5, 0x00, + 0xe2, 0x23, 0x0e, 0x64, 0xe2, 0x01, 0x04, 0x2e, + 0x60, 0xe2, 0x48, 0xe5, 0x1b, 0x27, 0x06, 0x27, + 0x06, 0x27, 0x16, 0x07, 0x06, 0x20, 0xe9, 0x02, + 0xa0, 0xe5, 0xab, 0x1c, 0xe0, 0x04, 0xe5, 0x0f, + 0x60, 0xe5, 0x29, 0x60, 0xfc, 0x87, 0x78, 0xfd, + 0x98, 0x78, 0xe5, 0x80, 0xe6, 0x20, 0xe5, 0x62, + 0xe0, 0x1e, 0xc2, 0xe0, 0x04, 0x82, 0x80, 0x05, + 0x06, 0xe5, 0x02, 0x0c, 0xe5, 0x05, 0x00, 0x85, + 0x00, 0x05, 0x00, 0x25, 0x00, 0x25, 0x00, 0xe5, + 0x64, 0xee, 0x09, 0xef, 0x08, 0xe5, 0x80, 0xe3, + 0x13, 0x12, 0xef, 0x08, 0xe5, 0x38, 0x2f, 0xe5, + 0x2e, 0xef, 0x00, 0xe0, 0x18, 0xe5, 0x04, 0x0d, + 0x4f, 0xe6, 0x08, 0xd6, 0x12, 0x13, 0x16, 0xa0, + 0xe6, 0x08, 0x16, 0x31, 0x30, 0x12, 0x13, 0x12, + 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, + 0x13, 0x12, 0x13, 0x12, 0x13, 0x36, 0x12, 0x13, + 0x76, 0x50, 0x56, 0x00, 0x76, 0x11, 0x12, 0x13, + 0x12, 0x13, 0x12, 0x13, 0x56, 0x0c, 0x11, 0x4c, + 0x00, 0x16, 0x0d, 0x36, 0x60, 0x85, 0x00, 0xe5, + 0x7f, 0x20, 0x1b, 0x00, 0x56, 0x0d, 0x56, 0x12, + 0x13, 0x16, 0x0c, 0x16, 0x11, 0x36, 0xe9, 0x02, + 0x36, 0x4c, 0x36, 0xe1, 0x12, 0x12, 0x16, 0x13, + 0x0e, 0x10, 0x0e, 0xe2, 0x12, 0x12, 0x0c, 0x13, + 0x0c, 0x12, 0x13, 0x16, 0x12, 0x13, 0x36, 0xe5, + 0x02, 0x04, 0xe5, 0x25, 0x24, 0xe5, 0x17, 0x40, + 0xa5, 0x20, 0xa5, 0x20, 0xa5, 0x20, 0x45, 0x40, + 0x2d, 0x0c, 0x0e, 0x0f, 0x2d, 0x00, 0x0f, 0x6c, + 0x2f, 0xe0, 0x02, 0x5b, 0x2f, 0x20, 0xe5, 0x04, + 0x00, 0xe5, 0x12, 0x00, 0xe5, 0x0b, 0x00, 0x25, + 0x00, 0xe5, 0x07, 0x20, 0xe5, 0x06, 0xe0, 0x1a, + 0xe5, 0x73, 0x80, 0x56, 0x60, 0xeb, 0x25, 0x40, + 0xef, 0x01, 0xea, 0x2d, 0x6b, 0xef, 0x09, 0x2b, + 0x4f, 0x00, 0xef, 0x05, 0x40, 0x0f, 0xe0, 0x27, + 0xef, 0x25, 0x06, 0xe0, 0x7a, 0xe5, 0x15, 0x40, + 0xe5, 0x29, 0xe0, 0x07, 0x06, 0xeb, 0x13, 0x60, + 0xe5, 0x18, 0x6b, 0xe0, 0x01, 0xe5, 0x0c, 0x0a, + 0xe5, 0x00, 0x0a, 0x80, 0xe5, 0x1e, 0x86, 0x80, + 0xe5, 0x16, 0x00, 0x16, 0xe5, 0x1c, 0x60, 0xe5, + 0x00, 0x16, 0x8a, 0xe0, 0x22, 0xe1, 0x20, 0xe2, + 0x20, 0xe5, 0x46, 0x20, 0xe9, 0x02, 0xa0, 0xe1, + 0x1c, 0x60, 0xe2, 0x1c, 0x60, 0xe5, 0x20, 0xe0, + 0x00, 0xe5, 0x2c, 0xe0, 0x03, 0x16, 0xe1, 0x03, + 0x00, 0xe1, 0x07, 0x00, 0xc1, 0x00, 0x21, 0x00, + 0xe2, 0x03, 0x00, 0xe2, 0x07, 0x00, 0xc2, 0x00, + 0x22, 0x40, 0xe5, 0x2c, 0xe0, 0x04, 0xe5, 0x80, + 0xaf, 0xe0, 0x01, 0xe5, 0x0e, 0xe0, 0x02, 0xe5, + 0x00, 0xe0, 0x10, 0xa4, 0x00, 0xe4, 0x22, 0x00, + 0xe4, 0x01, 0xe0, 0x3d, 0xa5, 0x20, 0x05, 0x00, + 0xe5, 0x24, 0x00, 0x25, 0x40, 0x05, 0x20, 0xe5, + 0x0f, 0x00, 0x16, 0xeb, 0x00, 0xe5, 0x0f, 0x2f, + 0xcb, 0xe5, 0x17, 0xe0, 0x00, 0xeb, 0x01, 0xe0, + 0x28, 0xe5, 0x0b, 0x00, 0x25, 0x80, 0x8b, 0xe5, + 0x0e, 0xab, 0x40, 0x16, 0xe5, 0x12, 0x80, 0x16, + 0xe5, 0x12, 0xe0, 0x1e, 0xe5, 0x30, 0x60, 0x2b, + 0x25, 0xeb, 0x08, 0x20, 0xeb, 0x26, 0x05, 0x46, + 0x00, 0x26, 0x80, 0x66, 0x65, 0x00, 0x45, 0x00, + 0xe5, 0x15, 0x20, 0x46, 0x60, 0x06, 0xeb, 0x01, + 0xc0, 0xf6, 0x01, 0xc0, 0xe5, 0x15, 0x2b, 0x16, + 0xe5, 0x15, 0x4b, 0xe0, 0x18, 0xe5, 0x00, 0x0f, + 0xe5, 0x14, 0x26, 0x60, 0x8b, 0xd6, 0xe0, 0x01, + 0xe5, 0x2e, 0x40, 0xd6, 0xe5, 0x0e, 0x20, 0xeb, + 0x00, 0xe5, 0x0b, 0x80, 0xeb, 0x00, 0xe5, 0x0a, + 0xc0, 0x76, 0xe0, 0x04, 0xcb, 0xe0, 0x48, 0xe5, + 0x41, 0xe0, 0x2f, 0xe1, 0x2b, 0xe0, 0x05, 0xe2, + 0x2b, 0xc0, 0xab, 0xe5, 0x1c, 0x66, 0xe0, 0x00, + 0xe9, 0x02, 0xa0, 0xe9, 0x02, 0x65, 0x04, 0x05, + 0xe1, 0x0e, 0x40, 0x86, 0x11, 0x04, 0xe2, 0x0e, + 0xe0, 0x00, 0x2c, 0xe0, 0x80, 0x48, 0xeb, 0x17, + 0x00, 0xe5, 0x22, 0x00, 0x26, 0x11, 0x20, 0x25, + 0xe0, 0x08, 0x45, 0x04, 0x25, 0xe0, 0x00, 0x16, + 0xef, 0x00, 0xe0, 0x19, 0xa6, 0xe5, 0x15, 0xeb, + 0x02, 0x05, 0xe0, 0x00, 0xe5, 0x0e, 0xe6, 0x03, + 0x6b, 0x96, 0xe0, 0x0e, 0xe5, 0x0a, 0x66, 0x76, + 0xe0, 0x1e, 0xe5, 0x0d, 0xcb, 0xe0, 0x0c, 0xe5, + 0x0f, 0xe0, 0x01, 0x07, 0x06, 0x07, 0xe5, 0x2d, + 0xe6, 0x07, 0xd6, 0x60, 0xeb, 0x0c, 0xe9, 0x02, + 0x06, 0x25, 0x26, 0x05, 0xe0, 0x01, 0x46, 0x07, + 0xe5, 0x25, 0x47, 0x66, 0x27, 0x26, 0x36, 0x1b, + 0x76, 0x06, 0xe0, 0x02, 0x1b, 0x20, 0xe5, 0x11, + 0xc0, 0xe9, 0x02, 0xa0, 0x46, 0xe5, 0x1c, 0x86, + 0x07, 0xe6, 0x00, 0x00, 0xe9, 0x02, 0x76, 0x05, + 0x27, 0x05, 0xe0, 0x00, 0xe5, 0x1b, 0x06, 0x36, + 0x05, 0xe0, 0x01, 0x26, 0x07, 0xe5, 0x28, 0x47, + 0xe6, 0x01, 0x27, 0x65, 0x76, 0x66, 0x16, 0x07, + 0x06, 0xe9, 0x02, 0x05, 0x16, 0x05, 0x56, 0x00, + 0xeb, 0x0c, 0xe0, 0x03, 0xe5, 0x0a, 0x00, 0xe5, + 0x11, 0x47, 0x46, 0x27, 0x06, 0x07, 0x26, 0xb6, + 0x06, 0x25, 0x06, 0xe0, 0x36, 0xc5, 0x00, 0x05, + 0x00, 0x65, 0x00, 0xe5, 0x07, 0x00, 0xe5, 0x02, + 0x16, 0xa0, 0xe5, 0x27, 0x06, 0x47, 0xe6, 0x00, + 0x80, 0xe9, 0x02, 0xa0, 0x26, 0x27, 0x00, 0xe5, + 0x00, 0x20, 0x25, 0x20, 0xe5, 0x0e, 0x00, 0xc5, + 0x00, 0x25, 0x00, 0x85, 0x00, 0x26, 0x05, 0x27, + 0x06, 0x67, 0x20, 0x27, 0x20, 0x47, 0x20, 0x05, + 0xa0, 0x07, 0x80, 0x85, 0x27, 0x20, 0xc6, 0x40, + 0x86, 0xe0, 0x03, 0xe5, 0x02, 0x00, 0x05, 0x20, + 0x05, 0x00, 0xe5, 0x1e, 0x00, 0x05, 0x47, 0xa6, + 0x00, 0x07, 0x20, 0x07, 0x00, 0x67, 0x00, 0x27, + 0x06, 0x07, 0x06, 0x05, 0x06, 0x05, 0x36, 0x00, + 0x36, 0xe0, 0x00, 0x26, 0xe0, 0x15, 0xe5, 0x2d, + 0x47, 0xe6, 0x00, 0x27, 0x46, 0x07, 0x06, 0x65, + 0x96, 0xe9, 0x02, 0x36, 0x00, 0x16, 0x06, 0x45, + 0xe0, 0x16, 0xe5, 0x28, 0x47, 0xa6, 0x07, 0x06, + 0x67, 0x26, 0x07, 0x26, 0x25, 0x16, 0x05, 0xe0, + 0x00, 0xe9, 0x02, 0xe0, 0x80, 0x1e, 0xe5, 0x27, + 0x47, 0x66, 0x20, 0x67, 0x26, 0x07, 0x26, 0xf6, + 0x0f, 0x65, 0x26, 0xe0, 0x1a, 0xe5, 0x28, 0x47, + 0xe6, 0x00, 0x27, 0x06, 0x07, 0x26, 0x56, 0x05, + 0xe0, 0x03, 0xe9, 0x02, 0xa0, 0xf6, 0x05, 0xe0, + 0x0b, 0xe5, 0x23, 0x06, 0x07, 0x06, 0x27, 0xa6, + 0x07, 0x06, 0x05, 0x16, 0xa0, 0xe9, 0x02, 0xa0, + 0xe9, 0x0c, 0xe0, 0x14, 0xe5, 0x13, 0x20, 0x06, + 0x07, 0x06, 0x27, 0x66, 0x07, 0x86, 0x60, 0xe9, + 0x02, 0x2b, 0x56, 0x0f, 0xc5, 0xe0, 0x80, 0x31, + 0xe5, 0x24, 0x47, 0xe6, 0x01, 0x07, 0x26, 0x16, + 0xe0, 0x5c, 0xe1, 0x18, 0xe2, 0x18, 0xe9, 0x02, + 0xeb, 0x01, 0xe0, 0x04, 0xe5, 0x00, 0x20, 0x05, + 0x20, 0xe5, 0x00, 0x00, 0x25, 0x00, 0xe5, 0x10, + 0xa7, 0x00, 0x27, 0x20, 0x26, 0x07, 0x06, 0x05, + 0x07, 0x05, 0x07, 0x06, 0x56, 0xe0, 0x01, 0xe9, + 0x02, 0xe0, 0x3e, 0xe5, 0x00, 0x20, 0xe5, 0x1f, + 0x47, 0x66, 0x20, 0x26, 0x67, 0x06, 0x05, 0x16, + 0x05, 0x07, 0xe0, 0x13, 0x05, 0xe6, 0x02, 0xe5, + 0x20, 0xa6, 0x07, 0x05, 0x66, 0xf6, 0x00, 0x06, + 0xe0, 0x00, 0x05, 0xa6, 0x27, 0x46, 0xe5, 0x26, + 0xe6, 0x05, 0x07, 0x26, 0x56, 0x05, 0x96, 0xe0, + 0x05, 0xe5, 0x41, 0xc0, 0xf6, 0x02, 0xe0, 0x4e, + 0x06, 0x07, 0x46, 0x07, 0x06, 0x07, 0xe0, 0x50, + 0xe5, 0x19, 0x16, 0xe0, 0x06, 0xe9, 0x02, 0xa0, + 0xe5, 0x01, 0x00, 0xe5, 0x1d, 0x07, 0xc6, 0x00, + 0xa6, 0x07, 0x06, 0x05, 0x96, 0xe0, 0x02, 0xe9, + 0x02, 0xeb, 0x0b, 0x40, 0x36, 0xe5, 0x16, 0x20, + 0xe6, 0x0e, 0x00, 0x07, 0xc6, 0x07, 0x26, 0x07, + 0x26, 0xe0, 0x41, 0xc5, 0x00, 0x25, 0x00, 0xe5, + 0x1e, 0xa6, 0x40, 0x06, 0x00, 0x26, 0x00, 0xc6, + 0x05, 0x06, 0xe0, 0x00, 0xe9, 0x02, 0xa0, 0xa5, + 0x00, 0x25, 0x00, 0xe5, 0x18, 0x87, 0x00, 0x26, + 0x00, 0x27, 0x06, 0x07, 0x06, 0x05, 0xc0, 0xe9, + 0x02, 0xa0, 0xe5, 0x21, 0x04, 0x25, 0x60, 0xe9, + 0x02, 0xe0, 0x80, 0x6e, 0xe5, 0x0b, 0x26, 0x27, + 0x36, 0xc0, 0x26, 0x05, 0x07, 0xe5, 0x05, 0x00, + 0xe5, 0x1a, 0x27, 0x86, 0x40, 0x27, 0x06, 0x07, + 0x06, 0xf6, 0x05, 0xe9, 0x02, 0x06, 0xe0, 0x4d, + 0x05, 0xe0, 0x07, 0xeb, 0x0d, 0xef, 0x00, 0x6d, + 0xef, 0x09, 0xe0, 0x05, 0x16, 0xe5, 0x83, 0x12, + 0xe0, 0x5e, 0xea, 0x67, 0x00, 0x96, 0xe0, 0x03, + 0xe5, 0x80, 0x3c, 0xe0, 0x89, 0xc4, 0xe5, 0x59, + 0x36, 0xe0, 0x05, 0xe5, 0x83, 0xa8, 0xfb, 0x08, + 0x06, 0xa5, 0xe6, 0x07, 0xe0, 0x02, 0xe5, 0x8f, + 0x13, 0x80, 0xe5, 0x81, 0xbf, 0xe0, 0x9a, 0x31, + 0xe5, 0x16, 0xe6, 0x04, 0x47, 0x46, 0xe9, 0x02, + 0xe0, 0x86, 0x3e, 0xe5, 0x81, 0xb1, 0xc0, 0xe5, + 0x17, 0x00, 0xe9, 0x02, 0x60, 0x36, 0xe5, 0x47, + 0x00, 0xe9, 0x02, 0xa0, 0xe5, 0x16, 0x20, 0x86, + 0x16, 0xe0, 0x02, 0xe5, 0x28, 0xc6, 0x96, 0x6f, + 0x64, 0x16, 0x0f, 0xe0, 0x02, 0xe9, 0x02, 0x00, + 0xcb, 0x00, 0xe5, 0x0d, 0x80, 0xe5, 0x0b, 0xe0, + 0x81, 0x28, 0x44, 0xe5, 0x20, 0x24, 0x56, 0xe9, + 0x02, 0xe0, 0x80, 0x3e, 0xe1, 0x18, 0xe2, 0x18, + 0xeb, 0x0f, 0x76, 0x80, 0xe1, 0x11, 0x20, 0xe2, + 0x11, 0xe0, 0x24, 0xe5, 0x43, 0x60, 0x06, 0x05, + 0xe7, 0x2f, 0xc0, 0x66, 0xe4, 0x05, 0xe0, 0x38, + 0x24, 0x16, 0x04, 0x06, 0xe0, 0x03, 0x27, 0x24, + 0x4a, 0xe0, 0x01, 0xe5, 0x9c, 0x4e, 0xe0, 0x21, + 0xe5, 0x18, 0xe0, 0x59, 0xe5, 0x6b, 0xe0, 0xa1, + 0x75, 0x64, 0x00, 0xc4, 0x00, 0x24, 0x00, 0xe5, + 0x80, 0x9b, 0xe0, 0x07, 0x05, 0xe0, 0x15, 0x45, + 0x20, 0x05, 0xe0, 0x06, 0x65, 0xe0, 0x00, 0xe5, + 0x81, 0x04, 0xe0, 0x88, 0x7c, 0xe5, 0x63, 0x80, + 0xe5, 0x05, 0x40, 0xe5, 0x01, 0xc0, 0xe5, 0x02, + 0x20, 0x0f, 0x26, 0x16, 0x7b, 0xe0, 0x8e, 0xd4, + 0xef, 0x80, 0x68, 0xe9, 0x02, 0x4f, 0x40, 0xef, + 0x81, 0x2c, 0xa0, 0xef, 0x0f, 0xe0, 0x07, 0xef, + 0x08, 0x0c, 0xe0, 0x07, 0xe6, 0x26, 0x20, 0xe6, + 0x0f, 0xe0, 0x01, 0xef, 0x6c, 0xe0, 0x34, 0xef, + 0x80, 0x6e, 0xe0, 0x02, 0xef, 0x1f, 0x20, 0xef, + 0x34, 0x27, 0x46, 0x4f, 0xa7, 0xfb, 0x00, 0xe6, + 0x00, 0x2f, 0xc6, 0xef, 0x16, 0x66, 0xef, 0x35, + 0xe0, 0x0d, 0xef, 0x3a, 0x46, 0x0f, 0xe0, 0x72, + 0xeb, 0x0c, 0xe0, 0x04, 0xeb, 0x0c, 0xe0, 0x04, + 0xef, 0x4f, 0xe0, 0x01, 0xeb, 0x11, 0xe0, 0x7f, + 0xe1, 0x12, 0xe2, 0x12, 0xe1, 0x12, 0xc2, 0x00, + 0xe2, 0x0a, 0xe1, 0x12, 0xe2, 0x12, 0x01, 0x00, + 0x21, 0x20, 0x01, 0x20, 0x21, 0x20, 0x61, 0x00, + 0xe1, 0x00, 0x62, 0x00, 0x02, 0x00, 0xc2, 0x00, + 0xe2, 0x03, 0xe1, 0x12, 0xe2, 0x12, 0x21, 0x00, + 0x61, 0x20, 0xe1, 0x00, 0x00, 0xc1, 0x00, 0xe2, + 0x12, 0x21, 0x00, 0x61, 0x00, 0x81, 0x00, 0x01, + 0x40, 0xc1, 0x00, 0xe2, 0x12, 0xe1, 0x12, 0xe2, + 0x12, 0xe1, 0x12, 0xe2, 0x12, 0xe1, 0x12, 0xe2, + 0x12, 0xe1, 0x12, 0xe2, 0x12, 0xe1, 0x12, 0xe2, + 0x12, 0xe1, 0x12, 0xe2, 0x14, 0x20, 0xe1, 0x11, + 0x0c, 0xe2, 0x11, 0x0c, 0xa2, 0xe1, 0x11, 0x0c, + 0xe2, 0x11, 0x0c, 0xa2, 0xe1, 0x11, 0x0c, 0xe2, + 0x11, 0x0c, 0xa2, 0xe1, 0x11, 0x0c, 0xe2, 0x11, + 0x0c, 0xa2, 0xe1, 0x11, 0x0c, 0xe2, 0x11, 0x0c, + 0xa2, 0x3f, 0x20, 0xe9, 0x2a, 0xef, 0x81, 0x78, + 0xe6, 0x2f, 0x6f, 0xe6, 0x2a, 0xef, 0x00, 0x06, + 0xef, 0x06, 0x06, 0x2f, 0x96, 0xe0, 0x07, 0x86, + 0x00, 0xe6, 0x07, 0xe0, 0x83, 0xc8, 0xe2, 0x02, + 0x05, 0xe2, 0x0c, 0xa0, 0xa2, 0xe0, 0x80, 0x4d, + 0xc6, 0x00, 0xe6, 0x09, 0x20, 0xc6, 0x00, 0x26, + 0x00, 0x86, 0x80, 0xe4, 0x36, 0xe0, 0x19, 0x06, + 0xe0, 0x68, 0xe5, 0x25, 0x40, 0xc6, 0xc4, 0x20, + 0xe9, 0x02, 0x60, 0x05, 0x0f, 0xe0, 0x80, 0xb8, + 0xe5, 0x16, 0x06, 0xe0, 0x09, 0xe5, 0x24, 0x66, + 0xe9, 0x02, 0x80, 0x0d, 0xe0, 0x81, 0x48, 0xe5, + 0x13, 0x04, 0x66, 0xe9, 0x02, 0xe0, 0x80, 0x4e, + 0xe5, 0x16, 0x26, 0x05, 0xe9, 0x02, 0x60, 0x16, + 0xe0, 0x80, 0x38, 0xe5, 0x17, 0x00, 0x45, 0x06, + 0x25, 0x06, 0xc5, 0x26, 0x85, 0x06, 0xe0, 0x00, + 0x05, 0x04, 0xe0, 0x80, 0x58, 0xc5, 0x00, 0x65, + 0x00, 0x25, 0x00, 0xe5, 0x07, 0x00, 0xe5, 0x80, + 0x3d, 0x20, 0xeb, 0x01, 0xc6, 0xe0, 0x21, 0xe1, + 0x1a, 0xe2, 0x1a, 0xc6, 0x04, 0x60, 0xe9, 0x02, + 0x60, 0x36, 0xe0, 0x82, 0x89, 0xeb, 0x33, 0x0f, + 0x4b, 0x0d, 0x6b, 0xe0, 0x44, 0xeb, 0x25, 0x0f, + 0xeb, 0x07, 0xe0, 0x80, 0x3a, 0x65, 0x00, 0xe5, + 0x13, 0x00, 0x25, 0x00, 0x05, 0x20, 0x05, 0x00, + 0xe5, 0x02, 0x00, 0x65, 0x00, 0x05, 0x00, 0x05, + 0xa0, 0x05, 0x60, 0x05, 0x00, 0x05, 0x00, 0x05, + 0x00, 0x45, 0x00, 0x25, 0x00, 0x05, 0x20, 0x05, + 0x00, 0x05, 0x00, 0x05, 0x00, 0x05, 0x00, 0x05, + 0x00, 0x25, 0x00, 0x05, 0x20, 0x65, 0x00, 0xc5, + 0x00, 0x65, 0x00, 0x65, 0x00, 0x05, 0x00, 0xe5, + 0x02, 0x00, 0xe5, 0x09, 0x80, 0x45, 0x00, 0x85, + 0x00, 0xe5, 0x09, 0xe0, 0x2c, 0x2c, 0xe0, 0x80, + 0x86, 0xef, 0x24, 0x60, 0xef, 0x5c, 0xe0, 0x04, + 0xef, 0x07, 0x20, 0xef, 0x07, 0x00, 0xef, 0x07, + 0x00, 0xef, 0x1d, 0xe0, 0x02, 0xeb, 0x05, 0xef, + 0x80, 0x19, 0xe0, 0x30, 0xef, 0x15, 0xe0, 0x05, + 0xef, 0x24, 0x60, 0xef, 0x01, 0xc0, 0x2f, 0xe0, + 0x06, 0xaf, 0xe0, 0x80, 0x12, 0xef, 0x80, 0x73, + 0x8e, 0xef, 0x82, 0x51, 0x40, 0xef, 0x09, 0x40, + 0xef, 0x05, 0x40, 0xef, 0x80, 0x52, 0xa0, 0xef, + 0x04, 0x60, 0x0f, 0xe0, 0x07, 0xef, 0x04, 0x60, + 0xef, 0x30, 0xe0, 0x00, 0xef, 0x02, 0xa0, 0xef, + 0x20, 0xe0, 0x00, 0xef, 0x16, 0x20, 0xef, 0x04, + 0x60, 0x2f, 0xe0, 0x06, 0xec, 0x01, 0xe0, 0x1f, + 0xef, 0x80, 0xd0, 0xe0, 0x00, 0xef, 0x06, 0x20, + 0xef, 0x05, 0x40, 0xef, 0x03, 0x40, 0xef, 0x31, + 0x00, 0x0f, 0x60, 0xef, 0x08, 0x20, 0xef, 0x04, + 0x60, 0xef, 0x02, 0xc0, 0xef, 0x80, 0x0b, 0x00, + 0xef, 0x54, 0xe9, 0x02, 0x0f, 0xe0, 0x83, 0x7d, + 0xe5, 0xc0, 0x66, 0x58, 0xe0, 0x18, 0xe5, 0x90, + 0x96, 0x20, 0xe5, 0x96, 0x06, 0x20, 0xe5, 0x9c, + 0xa9, 0xe0, 0x07, 0xe5, 0x81, 0xe6, 0xe0, 0x89, + 0x1a, 0xe5, 0x81, 0x96, 0xe0, 0x85, 0x5a, 0xe5, + 0x92, 0xc3, 0x80, 0xe5, 0xa0, 0xa2, 0xe0, 0xca, + 0x8a, 0xff, 0x1b, 0xe0, 0x16, 0xfb, 0x58, 0xe0, + 0x78, 0xe6, 0x80, 0x68, 0xe0, 0xc0, 0xbd, 0x88, + 0xfd, 0xc0, 0xbf, 0x76, 0x20, 0xfd, 0xc0, 0xbf, + 0x76, 0x20, +}; + +typedef enum { + UNICODE_SCRIPT_Unknown, + UNICODE_SCRIPT_Adlam, + UNICODE_SCRIPT_Ahom, + UNICODE_SCRIPT_Anatolian_Hieroglyphs, + UNICODE_SCRIPT_Arabic, + UNICODE_SCRIPT_Armenian, + UNICODE_SCRIPT_Avestan, + UNICODE_SCRIPT_Balinese, + UNICODE_SCRIPT_Bamum, + UNICODE_SCRIPT_Bassa_Vah, + UNICODE_SCRIPT_Batak, + UNICODE_SCRIPT_Bengali, + UNICODE_SCRIPT_Beria_Erfe, + UNICODE_SCRIPT_Bhaiksuki, + UNICODE_SCRIPT_Bopomofo, + UNICODE_SCRIPT_Brahmi, + UNICODE_SCRIPT_Braille, + UNICODE_SCRIPT_Buginese, + UNICODE_SCRIPT_Buhid, + UNICODE_SCRIPT_Canadian_Aboriginal, + UNICODE_SCRIPT_Carian, + UNICODE_SCRIPT_Caucasian_Albanian, + UNICODE_SCRIPT_Chakma, + UNICODE_SCRIPT_Cham, + UNICODE_SCRIPT_Cherokee, + UNICODE_SCRIPT_Chorasmian, + UNICODE_SCRIPT_Common, + UNICODE_SCRIPT_Coptic, + UNICODE_SCRIPT_Cuneiform, + UNICODE_SCRIPT_Cypriot, + UNICODE_SCRIPT_Cyrillic, + UNICODE_SCRIPT_Cypro_Minoan, + UNICODE_SCRIPT_Deseret, + UNICODE_SCRIPT_Devanagari, + UNICODE_SCRIPT_Dives_Akuru, + UNICODE_SCRIPT_Dogra, + UNICODE_SCRIPT_Duployan, + UNICODE_SCRIPT_Egyptian_Hieroglyphs, + UNICODE_SCRIPT_Elbasan, + UNICODE_SCRIPT_Elymaic, + UNICODE_SCRIPT_Ethiopic, + UNICODE_SCRIPT_Georgian, + UNICODE_SCRIPT_Glagolitic, + UNICODE_SCRIPT_Gothic, + UNICODE_SCRIPT_Garay, + UNICODE_SCRIPT_Grantha, + UNICODE_SCRIPT_Greek, + UNICODE_SCRIPT_Gujarati, + UNICODE_SCRIPT_Gunjala_Gondi, + UNICODE_SCRIPT_Gurmukhi, + UNICODE_SCRIPT_Gurung_Khema, + UNICODE_SCRIPT_Han, + UNICODE_SCRIPT_Hangul, + UNICODE_SCRIPT_Hanifi_Rohingya, + UNICODE_SCRIPT_Hanunoo, + UNICODE_SCRIPT_Hatran, + UNICODE_SCRIPT_Hebrew, + UNICODE_SCRIPT_Hiragana, + UNICODE_SCRIPT_Imperial_Aramaic, + UNICODE_SCRIPT_Inherited, + UNICODE_SCRIPT_Inscriptional_Pahlavi, + UNICODE_SCRIPT_Inscriptional_Parthian, + UNICODE_SCRIPT_Javanese, + UNICODE_SCRIPT_Kaithi, + UNICODE_SCRIPT_Kannada, + UNICODE_SCRIPT_Katakana, + UNICODE_SCRIPT_Katakana_Or_Hiragana, + UNICODE_SCRIPT_Kawi, + UNICODE_SCRIPT_Kayah_Li, + UNICODE_SCRIPT_Kharoshthi, + UNICODE_SCRIPT_Khmer, + UNICODE_SCRIPT_Khojki, + UNICODE_SCRIPT_Khitan_Small_Script, + UNICODE_SCRIPT_Khudawadi, + UNICODE_SCRIPT_Kirat_Rai, + UNICODE_SCRIPT_Lao, + UNICODE_SCRIPT_Latin, + UNICODE_SCRIPT_Lepcha, + UNICODE_SCRIPT_Limbu, + UNICODE_SCRIPT_Linear_A, + UNICODE_SCRIPT_Linear_B, + UNICODE_SCRIPT_Lisu, + UNICODE_SCRIPT_Lycian, + UNICODE_SCRIPT_Lydian, + UNICODE_SCRIPT_Makasar, + UNICODE_SCRIPT_Mahajani, + UNICODE_SCRIPT_Malayalam, + UNICODE_SCRIPT_Mandaic, + UNICODE_SCRIPT_Manichaean, + UNICODE_SCRIPT_Marchen, + UNICODE_SCRIPT_Masaram_Gondi, + UNICODE_SCRIPT_Medefaidrin, + UNICODE_SCRIPT_Meetei_Mayek, + UNICODE_SCRIPT_Mende_Kikakui, + UNICODE_SCRIPT_Meroitic_Cursive, + UNICODE_SCRIPT_Meroitic_Hieroglyphs, + UNICODE_SCRIPT_Miao, + UNICODE_SCRIPT_Modi, + UNICODE_SCRIPT_Mongolian, + UNICODE_SCRIPT_Mro, + UNICODE_SCRIPT_Multani, + UNICODE_SCRIPT_Myanmar, + UNICODE_SCRIPT_Nabataean, + UNICODE_SCRIPT_Nag_Mundari, + UNICODE_SCRIPT_Nandinagari, + UNICODE_SCRIPT_New_Tai_Lue, + UNICODE_SCRIPT_Newa, + UNICODE_SCRIPT_Nko, + UNICODE_SCRIPT_Nushu, + UNICODE_SCRIPT_Nyiakeng_Puachue_Hmong, + UNICODE_SCRIPT_Ogham, + UNICODE_SCRIPT_Ol_Chiki, + UNICODE_SCRIPT_Ol_Onal, + UNICODE_SCRIPT_Old_Hungarian, + UNICODE_SCRIPT_Old_Italic, + UNICODE_SCRIPT_Old_North_Arabian, + UNICODE_SCRIPT_Old_Permic, + UNICODE_SCRIPT_Old_Persian, + UNICODE_SCRIPT_Old_Sogdian, + UNICODE_SCRIPT_Old_South_Arabian, + UNICODE_SCRIPT_Old_Turkic, + UNICODE_SCRIPT_Old_Uyghur, + UNICODE_SCRIPT_Oriya, + UNICODE_SCRIPT_Osage, + UNICODE_SCRIPT_Osmanya, + UNICODE_SCRIPT_Pahawh_Hmong, + UNICODE_SCRIPT_Palmyrene, + UNICODE_SCRIPT_Pau_Cin_Hau, + UNICODE_SCRIPT_Phags_Pa, + UNICODE_SCRIPT_Phoenician, + UNICODE_SCRIPT_Psalter_Pahlavi, + UNICODE_SCRIPT_Rejang, + UNICODE_SCRIPT_Runic, + UNICODE_SCRIPT_Samaritan, + UNICODE_SCRIPT_Saurashtra, + UNICODE_SCRIPT_Sharada, + UNICODE_SCRIPT_Shavian, + UNICODE_SCRIPT_Siddham, + UNICODE_SCRIPT_Sidetic, + UNICODE_SCRIPT_SignWriting, + UNICODE_SCRIPT_Sinhala, + UNICODE_SCRIPT_Sogdian, + UNICODE_SCRIPT_Sora_Sompeng, + UNICODE_SCRIPT_Soyombo, + UNICODE_SCRIPT_Sundanese, + UNICODE_SCRIPT_Sunuwar, + UNICODE_SCRIPT_Syloti_Nagri, + UNICODE_SCRIPT_Syriac, + UNICODE_SCRIPT_Tagalog, + UNICODE_SCRIPT_Tagbanwa, + UNICODE_SCRIPT_Tai_Le, + UNICODE_SCRIPT_Tai_Tham, + UNICODE_SCRIPT_Tai_Viet, + UNICODE_SCRIPT_Tai_Yo, + UNICODE_SCRIPT_Takri, + UNICODE_SCRIPT_Tamil, + UNICODE_SCRIPT_Tangut, + UNICODE_SCRIPT_Telugu, + UNICODE_SCRIPT_Thaana, + UNICODE_SCRIPT_Thai, + UNICODE_SCRIPT_Tibetan, + UNICODE_SCRIPT_Tifinagh, + UNICODE_SCRIPT_Tirhuta, + UNICODE_SCRIPT_Tangsa, + UNICODE_SCRIPT_Todhri, + UNICODE_SCRIPT_Tolong_Siki, + UNICODE_SCRIPT_Toto, + UNICODE_SCRIPT_Tulu_Tigalari, + UNICODE_SCRIPT_Ugaritic, + UNICODE_SCRIPT_Vai, + UNICODE_SCRIPT_Vithkuqi, + UNICODE_SCRIPT_Wancho, + UNICODE_SCRIPT_Warang_Citi, + UNICODE_SCRIPT_Yezidi, + UNICODE_SCRIPT_Yi, + UNICODE_SCRIPT_Zanabazar_Square, + UNICODE_SCRIPT_COUNT, +} UnicodeScriptEnum; + +static const char unicode_script_name_table[] = + "Adlam,Adlm" "\0" + "Ahom,Ahom" "\0" + "Anatolian_Hieroglyphs,Hluw" "\0" + "Arabic,Arab" "\0" + "Armenian,Armn" "\0" + "Avestan,Avst" "\0" + "Balinese,Bali" "\0" + "Bamum,Bamu" "\0" + "Bassa_Vah,Bass" "\0" + "Batak,Batk" "\0" + "Bengali,Beng" "\0" + "Beria_Erfe,Berf" "\0" + "Bhaiksuki,Bhks" "\0" + "Bopomofo,Bopo" "\0" + "Brahmi,Brah" "\0" + "Braille,Brai" "\0" + "Buginese,Bugi" "\0" + "Buhid,Buhd" "\0" + "Canadian_Aboriginal,Cans" "\0" + "Carian,Cari" "\0" + "Caucasian_Albanian,Aghb" "\0" + "Chakma,Cakm" "\0" + "Cham,Cham" "\0" + "Cherokee,Cher" "\0" + "Chorasmian,Chrs" "\0" + "Common,Zyyy" "\0" + "Coptic,Copt,Qaac" "\0" + "Cuneiform,Xsux" "\0" + "Cypriot,Cprt" "\0" + "Cyrillic,Cyrl" "\0" + "Cypro_Minoan,Cpmn" "\0" + "Deseret,Dsrt" "\0" + "Devanagari,Deva" "\0" + "Dives_Akuru,Diak" "\0" + "Dogra,Dogr" "\0" + "Duployan,Dupl" "\0" + "Egyptian_Hieroglyphs,Egyp" "\0" + "Elbasan,Elba" "\0" + "Elymaic,Elym" "\0" + "Ethiopic,Ethi" "\0" + "Georgian,Geor" "\0" + "Glagolitic,Glag" "\0" + "Gothic,Goth" "\0" + "Garay,Gara" "\0" + "Grantha,Gran" "\0" + "Greek,Grek" "\0" + "Gujarati,Gujr" "\0" + "Gunjala_Gondi,Gong" "\0" + "Gurmukhi,Guru" "\0" + "Gurung_Khema,Gukh" "\0" + "Han,Hani" "\0" + "Hangul,Hang" "\0" + "Hanifi_Rohingya,Rohg" "\0" + "Hanunoo,Hano" "\0" + "Hatran,Hatr" "\0" + "Hebrew,Hebr" "\0" + "Hiragana,Hira" "\0" + "Imperial_Aramaic,Armi" "\0" + "Inherited,Zinh,Qaai" "\0" + "Inscriptional_Pahlavi,Phli" "\0" + "Inscriptional_Parthian,Prti" "\0" + "Javanese,Java" "\0" + "Kaithi,Kthi" "\0" + "Kannada,Knda" "\0" + "Katakana,Kana" "\0" + "Katakana_Or_Hiragana,Hrkt" "\0" + "Kawi,Kawi" "\0" + "Kayah_Li,Kali" "\0" + "Kharoshthi,Khar" "\0" + "Khmer,Khmr" "\0" + "Khojki,Khoj" "\0" + "Khitan_Small_Script,Kits" "\0" + "Khudawadi,Sind" "\0" + "Kirat_Rai,Krai" "\0" + "Lao,Laoo" "\0" + "Latin,Latn" "\0" + "Lepcha,Lepc" "\0" + "Limbu,Limb" "\0" + "Linear_A,Lina" "\0" + "Linear_B,Linb" "\0" + "Lisu,Lisu" "\0" + "Lycian,Lyci" "\0" + "Lydian,Lydi" "\0" + "Makasar,Maka" "\0" + "Mahajani,Mahj" "\0" + "Malayalam,Mlym" "\0" + "Mandaic,Mand" "\0" + "Manichaean,Mani" "\0" + "Marchen,Marc" "\0" + "Masaram_Gondi,Gonm" "\0" + "Medefaidrin,Medf" "\0" + "Meetei_Mayek,Mtei" "\0" + "Mende_Kikakui,Mend" "\0" + "Meroitic_Cursive,Merc" "\0" + "Meroitic_Hieroglyphs,Mero" "\0" + "Miao,Plrd" "\0" + "Modi,Modi" "\0" + "Mongolian,Mong" "\0" + "Mro,Mroo" "\0" + "Multani,Mult" "\0" + "Myanmar,Mymr" "\0" + "Nabataean,Nbat" "\0" + "Nag_Mundari,Nagm" "\0" + "Nandinagari,Nand" "\0" + "New_Tai_Lue,Talu" "\0" + "Newa,Newa" "\0" + "Nko,Nkoo" "\0" + "Nushu,Nshu" "\0" + "Nyiakeng_Puachue_Hmong,Hmnp" "\0" + "Ogham,Ogam" "\0" + "Ol_Chiki,Olck" "\0" + "Ol_Onal,Onao" "\0" + "Old_Hungarian,Hung" "\0" + "Old_Italic,Ital" "\0" + "Old_North_Arabian,Narb" "\0" + "Old_Permic,Perm" "\0" + "Old_Persian,Xpeo" "\0" + "Old_Sogdian,Sogo" "\0" + "Old_South_Arabian,Sarb" "\0" + "Old_Turkic,Orkh" "\0" + "Old_Uyghur,Ougr" "\0" + "Oriya,Orya" "\0" + "Osage,Osge" "\0" + "Osmanya,Osma" "\0" + "Pahawh_Hmong,Hmng" "\0" + "Palmyrene,Palm" "\0" + "Pau_Cin_Hau,Pauc" "\0" + "Phags_Pa,Phag" "\0" + "Phoenician,Phnx" "\0" + "Psalter_Pahlavi,Phlp" "\0" + "Rejang,Rjng" "\0" + "Runic,Runr" "\0" + "Samaritan,Samr" "\0" + "Saurashtra,Saur" "\0" + "Sharada,Shrd" "\0" + "Shavian,Shaw" "\0" + "Siddham,Sidd" "\0" + "Sidetic,Sidt" "\0" + "SignWriting,Sgnw" "\0" + "Sinhala,Sinh" "\0" + "Sogdian,Sogd" "\0" + "Sora_Sompeng,Sora" "\0" + "Soyombo,Soyo" "\0" + "Sundanese,Sund" "\0" + "Sunuwar,Sunu" "\0" + "Syloti_Nagri,Sylo" "\0" + "Syriac,Syrc" "\0" + "Tagalog,Tglg" "\0" + "Tagbanwa,Tagb" "\0" + "Tai_Le,Tale" "\0" + "Tai_Tham,Lana" "\0" + "Tai_Viet,Tavt" "\0" + "Tai_Yo,Tayo" "\0" + "Takri,Takr" "\0" + "Tamil,Taml" "\0" + "Tangut,Tang" "\0" + "Telugu,Telu" "\0" + "Thaana,Thaa" "\0" + "Thai,Thai" "\0" + "Tibetan,Tibt" "\0" + "Tifinagh,Tfng" "\0" + "Tirhuta,Tirh" "\0" + "Tangsa,Tnsa" "\0" + "Todhri,Todr" "\0" + "Tolong_Siki,Tols" "\0" + "Toto,Toto" "\0" + "Tulu_Tigalari,Tutg" "\0" + "Ugaritic,Ugar" "\0" + "Vai,Vaii" "\0" + "Vithkuqi,Vith" "\0" + "Wancho,Wcho" "\0" + "Warang_Citi,Wara" "\0" + "Yezidi,Yezi" "\0" + "Yi,Yiii" "\0" + "Zanabazar_Square,Zanb" "\0" +; + +static const uint8_t unicode_script_table[2818] = { + 0xc0, 0x1a, 0x99, 0x4c, 0x85, 0x1a, 0x99, 0x4c, + 0xae, 0x1a, 0x80, 0x4c, 0x8e, 0x1a, 0x80, 0x4c, + 0x84, 0x1a, 0x96, 0x4c, 0x80, 0x1a, 0x9e, 0x4c, + 0x80, 0x1a, 0xe1, 0x60, 0x4c, 0xa6, 0x1a, 0x84, + 0x4c, 0x84, 0x1a, 0x81, 0x0e, 0x93, 0x1a, 0xe0, + 0x0f, 0x3b, 0x83, 0x2e, 0x80, 0x1a, 0x82, 0x2e, + 0x01, 0x83, 0x2e, 0x80, 0x1a, 0x80, 0x2e, 0x03, + 0x80, 0x2e, 0x80, 0x1a, 0x80, 0x2e, 0x80, 0x1a, + 0x82, 0x2e, 0x00, 0x80, 0x2e, 0x00, 0x93, 0x2e, + 0x00, 0xbe, 0x2e, 0x8d, 0x1b, 0x8f, 0x2e, 0xe0, + 0x24, 0x1e, 0x81, 0x3b, 0xe0, 0x48, 0x1e, 0x00, + 0xa5, 0x05, 0x01, 0xb1, 0x05, 0x01, 0x82, 0x05, + 0x00, 0xb6, 0x38, 0x07, 0x9a, 0x38, 0x03, 0x85, + 0x38, 0x0a, 0x84, 0x04, 0x80, 0x1a, 0x85, 0x04, + 0x80, 0x1a, 0x8d, 0x04, 0x80, 0x1a, 0x82, 0x04, + 0x80, 0x1a, 0x9f, 0x04, 0x80, 0x1a, 0x89, 0x04, + 0x8a, 0x3b, 0x99, 0x04, 0x80, 0x3b, 0xe0, 0x0b, + 0x04, 0x80, 0x1a, 0xa1, 0x04, 0x8d, 0x93, 0x00, + 0xbb, 0x93, 0x01, 0x82, 0x93, 0xaf, 0x04, 0xb1, + 0x9e, 0x0d, 0xba, 0x6b, 0x01, 0x82, 0x6b, 0xad, + 0x85, 0x01, 0x8e, 0x85, 0x00, 0x9b, 0x57, 0x01, + 0x80, 0x57, 0x00, 0x8a, 0x93, 0x04, 0xa1, 0x04, + 0x04, 0xca, 0x04, 0x80, 0x1a, 0x9c, 0x04, 0xd0, + 0x21, 0x83, 0x3b, 0x8e, 0x21, 0x81, 0x1a, 0x99, + 0x21, 0x83, 0x0b, 0x00, 0x87, 0x0b, 0x01, 0x81, + 0x0b, 0x01, 0x95, 0x0b, 0x00, 0x86, 0x0b, 0x00, + 0x80, 0x0b, 0x02, 0x83, 0x0b, 0x01, 0x88, 0x0b, + 0x01, 0x81, 0x0b, 0x01, 0x83, 0x0b, 0x07, 0x80, + 0x0b, 0x03, 0x81, 0x0b, 0x00, 0x84, 0x0b, 0x01, + 0x98, 0x0b, 0x01, 0x82, 0x31, 0x00, 0x85, 0x31, + 0x03, 0x81, 0x31, 0x01, 0x95, 0x31, 0x00, 0x86, + 0x31, 0x00, 0x81, 0x31, 0x00, 0x81, 0x31, 0x00, + 0x81, 0x31, 0x01, 0x80, 0x31, 0x00, 0x84, 0x31, + 0x03, 0x81, 0x31, 0x01, 0x82, 0x31, 0x02, 0x80, + 0x31, 0x06, 0x83, 0x31, 0x00, 0x80, 0x31, 0x06, + 0x90, 0x31, 0x09, 0x82, 0x2f, 0x00, 0x88, 0x2f, + 0x00, 0x82, 0x2f, 0x00, 0x95, 0x2f, 0x00, 0x86, + 0x2f, 0x00, 0x81, 0x2f, 0x00, 0x84, 0x2f, 0x01, + 0x89, 0x2f, 0x00, 0x82, 0x2f, 0x00, 0x82, 0x2f, + 0x01, 0x80, 0x2f, 0x0e, 0x83, 0x2f, 0x01, 0x8b, + 0x2f, 0x06, 0x86, 0x2f, 0x00, 0x82, 0x7a, 0x00, + 0x87, 0x7a, 0x01, 0x81, 0x7a, 0x01, 0x95, 0x7a, + 0x00, 0x86, 0x7a, 0x00, 0x81, 0x7a, 0x00, 0x84, + 0x7a, 0x01, 0x88, 0x7a, 0x01, 0x81, 0x7a, 0x01, + 0x82, 0x7a, 0x06, 0x82, 0x7a, 0x03, 0x81, 0x7a, + 0x00, 0x84, 0x7a, 0x01, 0x91, 0x7a, 0x09, 0x81, + 0x9b, 0x00, 0x85, 0x9b, 0x02, 0x82, 0x9b, 0x00, + 0x83, 0x9b, 0x02, 0x81, 0x9b, 0x00, 0x80, 0x9b, + 0x00, 0x81, 0x9b, 0x02, 0x81, 0x9b, 0x02, 0x82, + 0x9b, 0x02, 0x8b, 0x9b, 0x03, 0x84, 0x9b, 0x02, + 0x82, 0x9b, 0x00, 0x83, 0x9b, 0x01, 0x80, 0x9b, + 0x05, 0x80, 0x9b, 0x0d, 0x94, 0x9b, 0x04, 0x8c, + 0x9d, 0x00, 0x82, 0x9d, 0x00, 0x96, 0x9d, 0x00, + 0x8f, 0x9d, 0x01, 0x88, 0x9d, 0x00, 0x82, 0x9d, + 0x00, 0x83, 0x9d, 0x06, 0x81, 0x9d, 0x00, 0x82, + 0x9d, 0x00, 0x81, 0x9d, 0x01, 0x83, 0x9d, 0x01, + 0x89, 0x9d, 0x06, 0x88, 0x9d, 0x8c, 0x40, 0x00, + 0x82, 0x40, 0x00, 0x96, 0x40, 0x00, 0x89, 0x40, + 0x00, 0x84, 0x40, 0x01, 0x88, 0x40, 0x00, 0x82, + 0x40, 0x00, 0x83, 0x40, 0x06, 0x81, 0x40, 0x04, + 0x82, 0x40, 0x00, 0x83, 0x40, 0x01, 0x89, 0x40, + 0x00, 0x82, 0x40, 0x0b, 0x8c, 0x56, 0x00, 0x82, + 0x56, 0x00, 0xb2, 0x56, 0x00, 0x82, 0x56, 0x00, + 0x85, 0x56, 0x03, 0x8f, 0x56, 0x01, 0x99, 0x56, + 0x00, 0x82, 0x8c, 0x00, 0x91, 0x8c, 0x02, 0x97, + 0x8c, 0x00, 0x88, 0x8c, 0x00, 0x80, 0x8c, 0x01, + 0x86, 0x8c, 0x02, 0x80, 0x8c, 0x03, 0x85, 0x8c, + 0x00, 0x80, 0x8c, 0x00, 0x87, 0x8c, 0x05, 0x89, + 0x8c, 0x01, 0x82, 0x8c, 0x0b, 0xb9, 0x9f, 0x03, + 0x80, 0x1a, 0x9b, 0x9f, 0x24, 0x81, 0x4b, 0x00, + 0x80, 0x4b, 0x00, 0x84, 0x4b, 0x00, 0x97, 0x4b, + 0x00, 0x80, 0x4b, 0x00, 0x96, 0x4b, 0x01, 0x84, + 0x4b, 0x00, 0x80, 0x4b, 0x00, 0x86, 0x4b, 0x00, + 0x89, 0x4b, 0x01, 0x83, 0x4b, 0x1f, 0xc7, 0xa0, + 0x00, 0xa3, 0xa0, 0x03, 0xa6, 0xa0, 0x00, 0xa3, + 0xa0, 0x00, 0x8e, 0xa0, 0x00, 0x86, 0xa0, 0x83, + 0x1a, 0x81, 0xa0, 0x24, 0xe0, 0x3f, 0x65, 0xa5, + 0x29, 0x00, 0x80, 0x29, 0x04, 0x80, 0x29, 0x01, + 0xaa, 0x29, 0x80, 0x1a, 0x83, 0x29, 0xe0, 0x9f, + 0x34, 0xc8, 0x28, 0x00, 0x83, 0x28, 0x01, 0x86, + 0x28, 0x00, 0x80, 0x28, 0x00, 0x83, 0x28, 0x01, + 0xa8, 0x28, 0x00, 0x83, 0x28, 0x01, 0xa0, 0x28, + 0x00, 0x83, 0x28, 0x01, 0x86, 0x28, 0x00, 0x80, + 0x28, 0x00, 0x83, 0x28, 0x01, 0x8e, 0x28, 0x00, + 0xb8, 0x28, 0x00, 0x83, 0x28, 0x01, 0xc2, 0x28, + 0x01, 0x9f, 0x28, 0x02, 0x99, 0x28, 0x05, 0xd5, + 0x18, 0x01, 0x85, 0x18, 0x01, 0xe2, 0x1f, 0x13, + 0x9c, 0x6e, 0x02, 0xca, 0x84, 0x82, 0x1a, 0x8a, + 0x84, 0x06, 0x95, 0x94, 0x08, 0x80, 0x94, 0x94, + 0x36, 0x81, 0x1a, 0x08, 0x93, 0x12, 0x0b, 0x8c, + 0x95, 0x00, 0x82, 0x95, 0x00, 0x81, 0x95, 0x0b, + 0xdd, 0x46, 0x01, 0x89, 0x46, 0x05, 0x89, 0x46, + 0x05, 0x81, 0x62, 0x81, 0x1a, 0x80, 0x62, 0x80, + 0x1a, 0x93, 0x62, 0x05, 0xd8, 0x62, 0x06, 0xaa, + 0x62, 0x04, 0xc5, 0x13, 0x09, 0x9e, 0x4e, 0x00, + 0x8b, 0x4e, 0x03, 0x8b, 0x4e, 0x03, 0x80, 0x4e, + 0x02, 0x8b, 0x4e, 0x9d, 0x96, 0x01, 0x84, 0x96, + 0x0a, 0xab, 0x69, 0x03, 0x99, 0x69, 0x05, 0x8a, + 0x69, 0x02, 0x81, 0x69, 0x9f, 0x46, 0x9b, 0x11, + 0x01, 0x81, 0x11, 0xbe, 0x97, 0x00, 0x9c, 0x97, + 0x01, 0x8a, 0x97, 0x05, 0x89, 0x97, 0x05, 0x8d, + 0x97, 0x01, 0xad, 0x3b, 0x01, 0x8b, 0x3b, 0x13, + 0xcc, 0x07, 0x00, 0xb1, 0x07, 0xbf, 0x90, 0xb3, + 0x0a, 0x07, 0x83, 0x0a, 0xb7, 0x4d, 0x02, 0x8e, + 0x4d, 0x02, 0x82, 0x4d, 0xaf, 0x6f, 0x8a, 0x1e, + 0x04, 0xaa, 0x29, 0x01, 0x82, 0x29, 0x87, 0x90, + 0x07, 0x82, 0x3b, 0x80, 0x1a, 0x8c, 0x3b, 0x80, + 0x1a, 0x86, 0x3b, 0x83, 0x1a, 0x80, 0x3b, 0x85, + 0x1a, 0x80, 0x3b, 0x82, 0x1a, 0x81, 0x3b, 0x80, + 0x1a, 0x04, 0xa5, 0x4c, 0x84, 0x2e, 0x80, 0x1e, + 0xb0, 0x4c, 0x84, 0x2e, 0x83, 0x4c, 0x84, 0x2e, + 0x8c, 0x4c, 0x80, 0x1e, 0xc5, 0x4c, 0x80, 0x2e, + 0xbf, 0x3b, 0xe0, 0x9f, 0x4c, 0x95, 0x2e, 0x01, + 0x85, 0x2e, 0x01, 0xa5, 0x2e, 0x01, 0x85, 0x2e, + 0x01, 0x87, 0x2e, 0x00, 0x80, 0x2e, 0x00, 0x80, + 0x2e, 0x00, 0x80, 0x2e, 0x00, 0x9e, 0x2e, 0x01, + 0xb4, 0x2e, 0x00, 0x8e, 0x2e, 0x00, 0x8d, 0x2e, + 0x01, 0x85, 0x2e, 0x00, 0x92, 0x2e, 0x01, 0x82, + 0x2e, 0x00, 0x88, 0x2e, 0x00, 0x8b, 0x1a, 0x81, + 0x3b, 0xd6, 0x1a, 0x00, 0x8a, 0x1a, 0x80, 0x4c, + 0x01, 0x8a, 0x1a, 0x80, 0x4c, 0x8e, 0x1a, 0x00, + 0x8c, 0x4c, 0x02, 0xa1, 0x1a, 0x0d, 0xa0, 0x3b, + 0x0e, 0xa5, 0x1a, 0x80, 0x2e, 0x82, 0x1a, 0x81, + 0x4c, 0x85, 0x1a, 0x80, 0x4c, 0x9a, 0x1a, 0x80, + 0x4c, 0x90, 0x1a, 0xa8, 0x4c, 0x82, 0x1a, 0x03, + 0xe2, 0x39, 0x1a, 0x15, 0x8a, 0x1a, 0x14, 0xe3, + 0x3f, 0x1a, 0xe0, 0x9f, 0x10, 0xe2, 0x13, 0x1a, + 0x01, 0xe0, 0x29, 0x1a, 0xdf, 0x2a, 0x9f, 0x4c, + 0xe0, 0x13, 0x1b, 0x04, 0x86, 0x1b, 0xa5, 0x29, + 0x00, 0x80, 0x29, 0x04, 0x80, 0x29, 0x01, 0xb7, + 0xa1, 0x06, 0x81, 0xa1, 0x0d, 0x80, 0xa1, 0x96, + 0x28, 0x08, 0x86, 0x28, 0x00, 0x86, 0x28, 0x00, + 0x86, 0x28, 0x00, 0x86, 0x28, 0x00, 0x86, 0x28, + 0x00, 0x86, 0x28, 0x00, 0x86, 0x28, 0x00, 0x86, + 0x28, 0x00, 0x9f, 0x1e, 0xdd, 0x1a, 0x21, 0x99, + 0x33, 0x00, 0xd8, 0x33, 0x0b, 0xe0, 0x75, 0x33, + 0x19, 0x94, 0x1a, 0x80, 0x33, 0x80, 0x1a, 0x80, + 0x33, 0x98, 0x1a, 0x88, 0x33, 0x83, 0x3b, 0x81, + 0x34, 0x87, 0x1a, 0x83, 0x33, 0x83, 0x1a, 0x00, + 0xd5, 0x39, 0x01, 0x81, 0x3b, 0x81, 0x1a, 0x82, + 0x39, 0x80, 0x1a, 0xd9, 0x41, 0x81, 0x1a, 0x82, + 0x41, 0x04, 0xaa, 0x0e, 0x00, 0xdd, 0x34, 0x00, + 0x8f, 0x1a, 0x9f, 0x0e, 0xa5, 0x1a, 0x08, 0x80, + 0x1a, 0x8f, 0x41, 0x9e, 0x34, 0x00, 0xbf, 0x1a, + 0x9e, 0x34, 0xd0, 0x1a, 0xae, 0x41, 0x80, 0x1a, + 0xd7, 0x41, 0xe0, 0x47, 0x1a, 0xf0, 0x09, 0x5f, + 0x33, 0xbf, 0x1a, 0xf0, 0x41, 0x9f, 0x33, 0xe4, + 0x2c, 0xae, 0x02, 0xb6, 0xae, 0x08, 0xaf, 0x51, + 0xe0, 0xcb, 0xa9, 0x13, 0xdf, 0x1e, 0xd7, 0x08, + 0x07, 0xa1, 0x1a, 0xe0, 0x05, 0x4c, 0x82, 0x1a, + 0xd1, 0x4c, 0x13, 0x8e, 0x4c, 0xac, 0x92, 0x02, + 0x89, 0x1a, 0x05, 0xb7, 0x80, 0x07, 0xc5, 0x86, + 0x07, 0x8b, 0x86, 0x05, 0x9f, 0x21, 0xad, 0x44, + 0x80, 0x1a, 0x80, 0x44, 0xa3, 0x83, 0x0a, 0x80, + 0x83, 0x9c, 0x34, 0x02, 0xcd, 0x3e, 0x00, 0x80, + 0x1a, 0x89, 0x3e, 0x03, 0x81, 0x3e, 0x9e, 0x65, + 0x00, 0xb6, 0x17, 0x08, 0x8d, 0x17, 0x01, 0x89, + 0x17, 0x01, 0x83, 0x17, 0x9f, 0x65, 0xc2, 0x98, + 0x17, 0x84, 0x98, 0x96, 0x5c, 0x09, 0x85, 0x28, + 0x01, 0x85, 0x28, 0x01, 0x85, 0x28, 0x08, 0x86, + 0x28, 0x00, 0x86, 0x28, 0x00, 0xaa, 0x4c, 0x80, + 0x1a, 0x88, 0x4c, 0x80, 0x2e, 0x83, 0x4c, 0x81, + 0x1a, 0x03, 0xcf, 0x18, 0xad, 0x5c, 0x01, 0x89, + 0x5c, 0x05, 0xf0, 0x1b, 0x43, 0x34, 0x0b, 0x96, + 0x34, 0x03, 0xb0, 0x34, 0x70, 0x10, 0xa3, 0xe1, + 0x0d, 0x33, 0x01, 0xe0, 0x09, 0x33, 0x25, 0x86, + 0x4c, 0x0b, 0x84, 0x05, 0x04, 0x99, 0x38, 0x00, + 0x84, 0x38, 0x00, 0x80, 0x38, 0x00, 0x81, 0x38, + 0x00, 0x81, 0x38, 0x00, 0x89, 0x38, 0xe1, 0x8d, + 0x04, 0x81, 0x1a, 0xe0, 0x2f, 0x04, 0x1f, 0x8f, + 0x04, 0x8f, 0x3b, 0x89, 0x1a, 0x05, 0x8d, 0x3b, + 0x81, 0x1e, 0xa2, 0x1a, 0x00, 0x92, 0x1a, 0x00, + 0x83, 0x1a, 0x03, 0x84, 0x04, 0x00, 0xe0, 0x26, + 0x04, 0x01, 0x80, 0x1a, 0x00, 0x9f, 0x1a, 0x99, + 0x4c, 0x85, 0x1a, 0x99, 0x4c, 0x8a, 0x1a, 0x89, + 0x41, 0x80, 0x1a, 0xac, 0x41, 0x81, 0x1a, 0x9e, + 0x34, 0x02, 0x85, 0x34, 0x01, 0x85, 0x34, 0x01, + 0x85, 0x34, 0x01, 0x82, 0x34, 0x02, 0x86, 0x1a, + 0x00, 0x86, 0x1a, 0x09, 0x84, 0x1a, 0x01, 0x8b, + 0x50, 0x00, 0x99, 0x50, 0x00, 0x92, 0x50, 0x00, + 0x81, 0x50, 0x00, 0x8e, 0x50, 0x01, 0x8d, 0x50, + 0x21, 0xe0, 0x1a, 0x50, 0x04, 0x82, 0x1a, 0x03, + 0xac, 0x1a, 0x02, 0x88, 0x1a, 0xce, 0x2e, 0x00, + 0x8c, 0x1a, 0x02, 0x80, 0x2e, 0x2e, 0xac, 0x1a, + 0x80, 0x3b, 0x60, 0x21, 0x9c, 0x52, 0x02, 0xb0, + 0x14, 0x0e, 0x80, 0x3b, 0x9a, 0x1a, 0x03, 0xa3, + 0x72, 0x08, 0x82, 0x72, 0x9a, 0x2b, 0x04, 0xaa, + 0x74, 0x04, 0x9d, 0xa8, 0x00, 0x80, 0xa8, 0xa3, + 0x75, 0x03, 0x8d, 0x75, 0x29, 0xcf, 0x20, 0xaf, + 0x88, 0x9d, 0x7c, 0x01, 0x89, 0x7c, 0x05, 0xa3, + 0x7b, 0x03, 0xa3, 0x7b, 0x03, 0xa7, 0x26, 0x07, + 0xb3, 0x15, 0x0a, 0x80, 0x15, 0x8a, 0xaa, 0x00, + 0x8e, 0xaa, 0x00, 0x86, 0xaa, 0x00, 0x81, 0xaa, + 0x00, 0x8a, 0xaa, 0x00, 0x8e, 0xaa, 0x00, 0x86, + 0xaa, 0x00, 0x81, 0xaa, 0x02, 0xb3, 0xa4, 0x0b, + 0xe0, 0xd6, 0x4f, 0x08, 0x95, 0x4f, 0x09, 0x87, + 0x4f, 0x17, 0x85, 0x4c, 0x00, 0xa9, 0x4c, 0x00, + 0x88, 0x4c, 0x44, 0x85, 0x1d, 0x01, 0x80, 0x1d, + 0x00, 0xab, 0x1d, 0x00, 0x81, 0x1d, 0x02, 0x80, + 0x1d, 0x01, 0x80, 0x1d, 0x95, 0x3a, 0x00, 0x88, + 0x3a, 0x9f, 0x7e, 0x9e, 0x66, 0x07, 0x88, 0x66, + 0x2f, 0x92, 0x37, 0x00, 0x81, 0x37, 0x04, 0x84, + 0x37, 0x9b, 0x81, 0x02, 0x80, 0x81, 0x99, 0x53, + 0x04, 0x80, 0x53, 0x99, 0x8a, 0x25, 0x9f, 0x5f, + 0x97, 0x5e, 0x03, 0x93, 0x5e, 0x01, 0xad, 0x5e, + 0x83, 0x45, 0x00, 0x81, 0x45, 0x04, 0x87, 0x45, + 0x00, 0x82, 0x45, 0x00, 0x9c, 0x45, 0x01, 0x82, + 0x45, 0x03, 0x89, 0x45, 0x06, 0x88, 0x45, 0x06, + 0x9f, 0x77, 0x9f, 0x73, 0x1f, 0xa6, 0x58, 0x03, + 0x8b, 0x58, 0x08, 0xb5, 0x06, 0x02, 0x86, 0x06, + 0x95, 0x3d, 0x01, 0x87, 0x3d, 0x92, 0x3c, 0x04, + 0x87, 0x3c, 0x91, 0x82, 0x06, 0x83, 0x82, 0x0b, + 0x86, 0x82, 0x4f, 0xc8, 0x78, 0x36, 0xb2, 0x71, + 0x0c, 0xb2, 0x71, 0x06, 0x85, 0x71, 0xa7, 0x35, + 0x07, 0x89, 0x35, 0x05, 0xa5, 0x2c, 0x02, 0x9c, + 0x2c, 0x07, 0x81, 0x2c, 0x60, 0x6f, 0x9e, 0x04, + 0x00, 0xa9, 0xad, 0x00, 0x82, 0xad, 0x01, 0x81, + 0xad, 0x0f, 0x85, 0x04, 0x07, 0x88, 0x04, 0x20, + 0x85, 0x04, 0xa7, 0x76, 0x07, 0xa9, 0x8d, 0x15, + 0x99, 0x79, 0x25, 0x9b, 0x19, 0x13, 0x96, 0x27, + 0x08, 0xcd, 0x0f, 0x03, 0xa3, 0x0f, 0x08, 0x80, + 0x0f, 0xc2, 0x3f, 0x09, 0x80, 0x3f, 0x01, 0x98, + 0x8e, 0x06, 0x89, 0x8e, 0x05, 0xb4, 0x16, 0x00, + 0x91, 0x16, 0x07, 0xa6, 0x55, 0x08, 0xdf, 0x87, + 0x00, 0x93, 0x8c, 0x0a, 0x91, 0x47, 0x00, 0xae, + 0x47, 0x3d, 0x86, 0x64, 0x00, 0x80, 0x64, 0x00, + 0x83, 0x64, 0x00, 0x8e, 0x64, 0x00, 0x8a, 0x64, + 0x05, 0xba, 0x49, 0x04, 0x89, 0x49, 0x05, 0x83, + 0x2d, 0x00, 0x87, 0x2d, 0x01, 0x81, 0x2d, 0x01, + 0x95, 0x2d, 0x00, 0x86, 0x2d, 0x00, 0x81, 0x2d, + 0x00, 0x84, 0x2d, 0x00, 0x80, 0x3b, 0x88, 0x2d, + 0x01, 0x81, 0x2d, 0x01, 0x82, 0x2d, 0x01, 0x80, + 0x2d, 0x05, 0x80, 0x2d, 0x04, 0x86, 0x2d, 0x01, + 0x86, 0x2d, 0x02, 0x84, 0x2d, 0x0a, 0x89, 0xa7, + 0x00, 0x80, 0xa7, 0x01, 0x80, 0xa7, 0x00, 0xa5, + 0xa7, 0x00, 0x89, 0xa7, 0x00, 0x80, 0xa7, 0x01, + 0x80, 0xa7, 0x00, 0x83, 0xa7, 0x00, 0x89, 0xa7, + 0x00, 0x81, 0xa7, 0x07, 0x81, 0xa7, 0x1c, 0xdb, + 0x6a, 0x00, 0x84, 0x6a, 0x1d, 0xc7, 0xa2, 0x07, + 0x89, 0xa2, 0x60, 0x45, 0xb5, 0x89, 0x01, 0xa5, + 0x89, 0x21, 0xc4, 0x61, 0x0a, 0x89, 0x61, 0x05, + 0x8c, 0x62, 0x12, 0xb9, 0x9a, 0x05, 0x89, 0x9a, + 0x05, 0x93, 0x65, 0x1b, 0x9a, 0x02, 0x01, 0x8e, + 0x02, 0x03, 0x96, 0x02, 0x60, 0x58, 0xbb, 0x23, + 0x60, 0x03, 0xd2, 0xac, 0x0b, 0x80, 0xac, 0x86, + 0x22, 0x01, 0x80, 0x22, 0x01, 0x87, 0x22, 0x00, + 0x81, 0x22, 0x00, 0x9d, 0x22, 0x00, 0x81, 0x22, + 0x01, 0x8b, 0x22, 0x08, 0x89, 0x22, 0x45, 0x87, + 0x68, 0x01, 0xad, 0x68, 0x01, 0x8a, 0x68, 0x1a, + 0xc7, 0xaf, 0x07, 0xd2, 0x8f, 0x0c, 0x8f, 0x13, + 0xb8, 0x7f, 0x06, 0x89, 0x21, 0x55, 0x87, 0x87, + 0x57, 0xa1, 0x91, 0x0d, 0x89, 0x91, 0x05, 0x88, + 0x0d, 0x00, 0xac, 0x0d, 0x00, 0x8d, 0x0d, 0x09, + 0x9c, 0x0d, 0x02, 0x9f, 0x59, 0x01, 0x95, 0x59, + 0x00, 0x8d, 0x59, 0x48, 0x86, 0x5a, 0x00, 0x81, + 0x5a, 0x00, 0xab, 0x5a, 0x02, 0x80, 0x5a, 0x00, + 0x81, 0x5a, 0x00, 0x88, 0x5a, 0x07, 0x89, 0x5a, + 0x05, 0x85, 0x30, 0x00, 0x81, 0x30, 0x00, 0xa4, + 0x30, 0x00, 0x81, 0x30, 0x00, 0x85, 0x30, 0x06, + 0x89, 0x30, 0x05, 0xab, 0xa5, 0x03, 0x89, 0xa5, + 0x60, 0x95, 0x98, 0x54, 0x06, 0x90, 0x43, 0x00, + 0xa8, 0x43, 0x02, 0x9c, 0x43, 0x54, 0x80, 0x51, + 0x0e, 0xb1, 0x9b, 0x0c, 0x80, 0x9b, 0xe3, 0x39, + 0x1c, 0x60, 0x05, 0xe0, 0x0e, 0x1c, 0x00, 0x84, + 0x1c, 0x0a, 0xe0, 0x63, 0x1c, 0x69, 0xeb, 0xe0, + 0x02, 0x1f, 0x0c, 0xe3, 0xf5, 0x25, 0x09, 0xef, + 0x3a, 0x25, 0x04, 0xe1, 0xe6, 0x03, 0x70, 0x0a, + 0x58, 0xb9, 0x32, 0x66, 0x65, 0xe1, 0xd8, 0x08, + 0x06, 0x9e, 0x63, 0x00, 0x89, 0x63, 0x03, 0x81, + 0x63, 0xce, 0xa3, 0x00, 0x89, 0xa3, 0x05, 0x9d, + 0x09, 0x01, 0x85, 0x09, 0x09, 0xc5, 0x7d, 0x09, + 0x89, 0x7d, 0x00, 0x86, 0x7d, 0x00, 0x94, 0x7d, + 0x04, 0x92, 0x7d, 0x61, 0x4f, 0xb9, 0x4a, 0x60, + 0x65, 0xda, 0x5b, 0x04, 0x98, 0x0c, 0x01, 0x98, + 0x0c, 0x2b, 0xca, 0x60, 0x03, 0xb8, 0x60, 0x06, + 0x90, 0x60, 0x3f, 0x80, 0x9c, 0x80, 0x6c, 0x81, + 0x33, 0x80, 0x48, 0x0a, 0x86, 0x33, 0x08, 0xf0, + 0x0a, 0x9f, 0x9c, 0xe1, 0x75, 0x48, 0x28, 0x80, + 0x48, 0x9e, 0x9c, 0x60, 0x00, 0xe0, 0x12, 0x9c, + 0x70, 0x11, 0x9c, 0x83, 0x41, 0x00, 0x86, 0x41, + 0x00, 0x81, 0x41, 0x00, 0x80, 0x41, 0xe0, 0xbe, + 0x39, 0x82, 0x41, 0x0e, 0x80, 0x39, 0x1c, 0x82, + 0x39, 0x01, 0x80, 0x41, 0x0d, 0x83, 0x41, 0x07, + 0xe1, 0x2b, 0x6c, 0x68, 0xa3, 0xe0, 0x0a, 0x24, + 0x04, 0x8c, 0x24, 0x02, 0x88, 0x24, 0x06, 0x89, + 0x24, 0x01, 0x83, 0x24, 0x83, 0x1a, 0x6e, 0xfb, + 0xe0, 0x9c, 0x1a, 0x02, 0xe1, 0x53, 0x1a, 0x05, + 0x96, 0x1a, 0x0e, 0x90, 0x1a, 0x0e, 0xad, 0x3b, + 0x01, 0x96, 0x3b, 0x08, 0xe0, 0x13, 0x1a, 0x3b, + 0xe0, 0x95, 0x1a, 0x09, 0xa6, 0x1a, 0x01, 0xbd, + 0x1a, 0x82, 0x3b, 0x90, 0x1a, 0x87, 0x3b, 0x81, + 0x1a, 0x86, 0x3b, 0x9d, 0x1a, 0x83, 0x3b, 0xbc, + 0x1a, 0x14, 0xc5, 0x2e, 0x60, 0x19, 0x93, 0x1a, + 0x0b, 0x93, 0x1a, 0x0b, 0xd6, 0x1a, 0x08, 0x98, + 0x1a, 0x60, 0x26, 0xd4, 0x1a, 0x00, 0xc6, 0x1a, + 0x00, 0x81, 0x1a, 0x01, 0x80, 0x1a, 0x01, 0x81, + 0x1a, 0x01, 0x83, 0x1a, 0x00, 0x8b, 0x1a, 0x00, + 0x80, 0x1a, 0x00, 0x86, 0x1a, 0x00, 0xc0, 0x1a, + 0x00, 0x83, 0x1a, 0x01, 0x87, 0x1a, 0x00, 0x86, + 0x1a, 0x00, 0x9b, 0x1a, 0x00, 0x83, 0x1a, 0x00, + 0x84, 0x1a, 0x00, 0x80, 0x1a, 0x02, 0x86, 0x1a, + 0x00, 0xe0, 0xf3, 0x1a, 0x01, 0xe0, 0xc3, 0x1a, + 0x01, 0xb1, 0x1a, 0xe2, 0x2b, 0x8b, 0x0e, 0x84, + 0x8b, 0x00, 0x8e, 0x8b, 0x63, 0xef, 0x9e, 0x4c, + 0x05, 0x85, 0x4c, 0x60, 0x74, 0x86, 0x2a, 0x00, + 0x90, 0x2a, 0x01, 0x86, 0x2a, 0x00, 0x81, 0x2a, + 0x00, 0x84, 0x2a, 0x04, 0xbd, 0x1e, 0x20, 0x80, + 0x1e, 0x60, 0x0f, 0xac, 0x6d, 0x02, 0x8d, 0x6d, + 0x01, 0x89, 0x6d, 0x03, 0x81, 0x6d, 0x60, 0xdf, + 0x9e, 0xa6, 0x10, 0xb9, 0xab, 0x04, 0x80, 0xab, + 0x61, 0x6f, 0xa9, 0x67, 0x60, 0x75, 0xaa, 0x70, + 0x03, 0x80, 0x70, 0x60, 0x5f, 0x9e, 0x99, 0x00, + 0x95, 0x99, 0x07, 0x81, 0x99, 0x60, 0x7f, 0x86, + 0x28, 0x00, 0x83, 0x28, 0x00, 0x81, 0x28, 0x00, + 0x8e, 0x28, 0x00, 0xe0, 0x64, 0x5d, 0x01, 0x8f, + 0x5d, 0x28, 0xcb, 0x01, 0x03, 0x89, 0x01, 0x03, + 0x81, 0x01, 0x62, 0xb0, 0xc3, 0x1a, 0x4b, 0xbc, + 0x1a, 0x60, 0x61, 0x83, 0x04, 0x00, 0x9a, 0x04, + 0x00, 0x81, 0x04, 0x00, 0x80, 0x04, 0x01, 0x80, + 0x04, 0x00, 0x89, 0x04, 0x00, 0x83, 0x04, 0x00, + 0x80, 0x04, 0x00, 0x80, 0x04, 0x05, 0x80, 0x04, + 0x03, 0x80, 0x04, 0x00, 0x80, 0x04, 0x00, 0x80, + 0x04, 0x00, 0x82, 0x04, 0x00, 0x81, 0x04, 0x00, + 0x80, 0x04, 0x01, 0x80, 0x04, 0x00, 0x80, 0x04, + 0x00, 0x80, 0x04, 0x00, 0x80, 0x04, 0x00, 0x80, + 0x04, 0x00, 0x81, 0x04, 0x00, 0x80, 0x04, 0x01, + 0x83, 0x04, 0x00, 0x86, 0x04, 0x00, 0x83, 0x04, + 0x00, 0x83, 0x04, 0x00, 0x80, 0x04, 0x00, 0x89, + 0x04, 0x00, 0x90, 0x04, 0x04, 0x82, 0x04, 0x00, + 0x84, 0x04, 0x00, 0x90, 0x04, 0x33, 0x81, 0x04, + 0x60, 0xad, 0xab, 0x1a, 0x03, 0xe0, 0x03, 0x1a, + 0x0b, 0x8e, 0x1a, 0x01, 0x8e, 0x1a, 0x00, 0x8e, + 0x1a, 0x00, 0xa4, 0x1a, 0x09, 0xe0, 0x4d, 0x1a, + 0x37, 0x99, 0x1a, 0x80, 0x39, 0x81, 0x1a, 0x0c, + 0xab, 0x1a, 0x03, 0x88, 0x1a, 0x06, 0x81, 0x1a, + 0x0d, 0x85, 0x1a, 0x60, 0x39, 0xe3, 0x78, 0x1a, + 0x02, 0x90, 0x1a, 0x02, 0x8c, 0x1a, 0x02, 0xe0, + 0x79, 0x1a, 0x05, 0x8b, 0x1a, 0x03, 0x80, 0x1a, + 0x0e, 0x8b, 0x1a, 0x03, 0xb7, 0x1a, 0x07, 0x89, + 0x1a, 0x05, 0xa7, 0x1a, 0x07, 0x9d, 0x1a, 0x01, + 0x8b, 0x1a, 0x03, 0x81, 0x1a, 0x0d, 0x88, 0x1a, + 0x26, 0xe0, 0xf7, 0x1a, 0x07, 0x8d, 0x1a, 0x01, + 0x8c, 0x1a, 0x02, 0x8a, 0x1a, 0x02, 0xb8, 0x1a, + 0x00, 0x80, 0x1a, 0x03, 0x8f, 0x1a, 0x01, 0x8b, + 0x1a, 0x03, 0x89, 0x1a, 0x06, 0xe0, 0x32, 0x1a, + 0x00, 0xe0, 0x06, 0x1a, 0x63, 0xa4, 0xf0, 0x96, + 0x7f, 0x33, 0x1f, 0xf0, 0x00, 0xbd, 0x33, 0x01, + 0xf0, 0x06, 0x2d, 0x33, 0x01, 0xf0, 0x0c, 0xd0, + 0x33, 0x0e, 0xe2, 0x0d, 0x33, 0x69, 0x41, 0xe1, + 0xbd, 0x33, 0x65, 0x81, 0xf0, 0x02, 0xea, 0x33, + 0x04, 0xf0, 0x10, 0xc9, 0x33, 0x7a, 0xbb, 0x26, + 0x80, 0x1a, 0x1d, 0xdf, 0x1a, 0x60, 0x1f, 0xe0, + 0x8f, 0x3b, +}; + +static const uint8_t unicode_script_ext_table[1278] = { + 0x80, 0x36, 0x00, 0x00, 0x10, 0x06, 0x14, 0x1b, + 0x24, 0x26, 0x29, 0x2a, 0x30, 0x2b, 0x2e, 0x33, + 0x4c, 0x53, 0x55, 0x74, 0x88, 0x81, 0x83, 0x00, + 0x00, 0x07, 0x0b, 0x1e, 0x21, 0x4c, 0x51, 0x9f, + 0xa6, 0x09, 0x00, 0x00, 0x02, 0x0e, 0x4c, 0x00, + 0x00, 0x02, 0x02, 0x0e, 0x4c, 0x00, 0x00, 0x00, + 0x02, 0x4c, 0x51, 0x08, 0x00, 0x00, 0x02, 0x4c, + 0x9f, 0x00, 0x00, 0x00, 0x02, 0x0e, 0x4c, 0x25, + 0x00, 0x00, 0x08, 0x18, 0x1b, 0x1e, 0x2e, 0x4c, + 0x74, 0x91, 0x96, 0x00, 0x08, 0x18, 0x1e, 0x2e, + 0x4c, 0x7b, 0x91, 0x96, 0xa4, 0x00, 0x04, 0x18, + 0x1e, 0x4c, 0xa1, 0x00, 0x05, 0x2a, 0x4c, 0x91, + 0x93, 0x9f, 0x00, 0x0b, 0x15, 0x18, 0x1b, 0x1e, + 0x2b, 0x2e, 0x4c, 0x7b, 0x93, 0xa1, 0xa4, 0x00, + 0x06, 0x1b, 0x26, 0x2a, 0x2b, 0x41, 0x4c, 0x00, + 0x05, 0x1e, 0x2e, 0x4c, 0x74, 0xa1, 0x00, 0x09, + 0x1b, 0x24, 0x38, 0x4c, 0x74, 0x93, 0x96, 0xa1, + 0xa4, 0x00, 0x0b, 0x05, 0x1e, 0x24, 0x2b, 0x2e, + 0x38, 0x4c, 0x74, 0x93, 0x96, 0xa1, 0x00, 0x02, + 0x4c, 0xa1, 0x00, 0x03, 0x24, 0x4c, 0x93, 0x00, + 0x04, 0x18, 0x1e, 0x4c, 0x7b, 0x00, 0x03, 0x18, + 0x4c, 0x96, 0x00, 0x02, 0x4c, 0x91, 0x00, 0x02, + 0x28, 0x4c, 0x00, 0x00, 0x00, 0x02, 0x4c, 0x91, + 0x00, 0x03, 0x1e, 0x4c, 0xa4, 0x00, 0x00, 0x00, + 0x04, 0x2e, 0x4c, 0x74, 0xa4, 0x0e, 0x00, 0x00, + 0x06, 0x18, 0x24, 0x41, 0x4c, 0x93, 0xa1, 0x00, + 0x04, 0x18, 0x24, 0x4c, 0x93, 0x00, 0x02, 0x4c, + 0x93, 0x06, 0x00, 0x00, 0x03, 0x4c, 0x91, 0x93, + 0x00, 0x02, 0x4c, 0x93, 0x00, 0x00, 0x00, 0x03, + 0x18, 0x4c, 0x93, 0x00, 0x07, 0x15, 0x18, 0x2b, + 0x4c, 0x91, 0x93, 0x9f, 0x0f, 0x00, 0x00, 0x01, + 0x2e, 0x01, 0x00, 0x00, 0x01, 0x2e, 0x11, 0x00, + 0x00, 0x02, 0x4c, 0x7b, 0x04, 0x00, 0x00, 0x03, + 0x15, 0x4c, 0xa4, 0x03, 0x00, 0x0c, 0x01, 0x4c, + 0x03, 0x00, 0x01, 0x02, 0x1b, 0x2e, 0x80, 0x8c, + 0x00, 0x00, 0x02, 0x1e, 0x74, 0x00, 0x02, 0x1e, + 0x2a, 0x01, 0x02, 0x1e, 0x4c, 0x00, 0x02, 0x1e, + 0x2a, 0x80, 0x80, 0x00, 0x00, 0x03, 0x05, 0x29, + 0x2a, 0x80, 0x01, 0x00, 0x00, 0x07, 0x04, 0x2c, + 0x6b, 0x35, 0x93, 0x9e, 0xad, 0x0d, 0x00, 0x00, + 0x07, 0x04, 0x2c, 0x6b, 0x35, 0x93, 0x9e, 0xad, + 0x00, 0x03, 0x04, 0x93, 0x9e, 0x01, 0x00, 0x00, + 0x08, 0x01, 0x04, 0x2c, 0x6b, 0x35, 0x93, 0x9e, + 0xad, 0x1f, 0x00, 0x00, 0x09, 0x01, 0x04, 0x57, + 0x58, 0x79, 0x82, 0x35, 0x8d, 0x93, 0x09, 0x00, + 0x0a, 0x02, 0x04, 0x93, 0x09, 0x00, 0x09, 0x03, + 0x04, 0x9e, 0xad, 0x05, 0x00, 0x00, 0x02, 0x04, + 0x93, 0x62, 0x00, 0x00, 0x02, 0x04, 0x35, 0x81, + 0xfb, 0x00, 0x00, 0x0f, 0x0b, 0x21, 0x2d, 0x2f, + 0x31, 0x40, 0x4c, 0x56, 0x68, 0x6a, 0x7a, 0x87, + 0x9b, 0x9d, 0xa2, 0x00, 0x0d, 0x0b, 0x21, 0x2d, + 0x2f, 0x31, 0x40, 0x4c, 0x56, 0x6a, 0x7a, 0x9b, + 0x9d, 0xa2, 0x10, 0x00, 0x00, 0x15, 0x0b, 0x21, + 0x23, 0x30, 0x5a, 0x2d, 0x2f, 0x31, 0x40, 0x55, + 0x56, 0x68, 0x70, 0x7a, 0x49, 0x8c, 0x92, 0x9a, + 0x9b, 0x9d, 0xa2, 0x00, 0x17, 0x0b, 0x21, 0x23, + 0x30, 0x5a, 0x2d, 0x2f, 0x32, 0x31, 0x40, 0x4e, + 0x55, 0x56, 0x68, 0x70, 0x7a, 0x49, 0x8c, 0x92, + 0x9a, 0x9b, 0x9d, 0xa2, 0x09, 0x04, 0x21, 0x23, + 0x3f, 0x55, 0x75, 0x00, 0x09, 0x03, 0x0b, 0x16, + 0x92, 0x75, 0x00, 0x09, 0x02, 0x31, 0x64, 0x75, + 0x00, 0x09, 0x02, 0x2f, 0x47, 0x80, 0x75, 0x00, + 0x0d, 0x02, 0x2d, 0x9b, 0x80, 0x71, 0x00, 0x09, + 0x03, 0x40, 0x68, 0xa7, 0x82, 0xcf, 0x00, 0x09, + 0x03, 0x16, 0x65, 0x96, 0x80, 0x30, 0x00, 0x00, + 0x03, 0x29, 0x2a, 0x4c, 0x85, 0x6e, 0x00, 0x02, + 0x01, 0x84, 0x46, 0x00, 0x01, 0x04, 0x12, 0x36, + 0x95, 0x94, 0x80, 0x4a, 0x00, 0x01, 0x02, 0x62, + 0x80, 0x00, 0x00, 0x00, 0x02, 0x62, 0x80, 0x84, + 0x49, 0x00, 0x00, 0x04, 0x0b, 0x21, 0x2d, 0x40, + 0x00, 0x01, 0x21, 0x00, 0x04, 0x0b, 0x21, 0x2d, + 0x40, 0x00, 0x03, 0x21, 0x2d, 0x40, 0x00, 0x01, + 0x21, 0x00, 0x05, 0x0b, 0x21, 0x6a, 0x9d, 0xa2, + 0x00, 0x03, 0x0b, 0x21, 0x9d, 0x00, 0x03, 0x21, + 0x6a, 0x87, 0x00, 0x04, 0x0b, 0x21, 0x6a, 0x9d, + 0x00, 0x02, 0x21, 0x87, 0x00, 0x06, 0x21, 0x40, + 0x56, 0x7a, 0x9b, 0x9d, 0x00, 0x01, 0x21, 0x01, + 0x02, 0x21, 0x87, 0x01, 0x01, 0x21, 0x00, 0x02, + 0x21, 0x87, 0x00, 0x02, 0x0b, 0x21, 0x00, 0x03, + 0x21, 0x6a, 0xa2, 0x05, 0x01, 0x21, 0x00, 0x03, + 0x21, 0x68, 0x6a, 0x00, 0x03, 0x0b, 0x21, 0x87, + 0x00, 0x02, 0x21, 0x6a, 0x00, 0x01, 0x21, 0x00, + 0x04, 0x0b, 0x21, 0x6a, 0x87, 0x03, 0x01, 0x21, + 0x00, 0x0b, 0x0b, 0x21, 0x2d, 0x40, 0x56, 0x68, + 0x7a, 0x8c, 0x9d, 0xa2, 0xa7, 0x00, 0x02, 0x21, + 0x2d, 0x00, 0x04, 0x21, 0x2d, 0x40, 0xa7, 0x01, + 0x02, 0x0b, 0x21, 0x00, 0x01, 0x0b, 0x01, 0x02, + 0x21, 0x2d, 0x00, 0x01, 0x68, 0x80, 0x44, 0x00, + 0x01, 0x01, 0x2e, 0x35, 0x00, 0x00, 0x03, 0x1e, + 0x4c, 0x93, 0x00, 0x00, 0x00, 0x01, 0x93, 0x81, + 0xb3, 0x00, 0x00, 0x03, 0x4c, 0x62, 0x80, 0x1e, + 0x00, 0x00, 0x02, 0x01, 0x04, 0x09, 0x00, 0x00, + 0x06, 0x14, 0x29, 0x2a, 0x71, 0x52, 0x78, 0x01, + 0x00, 0x00, 0x04, 0x14, 0x2e, 0x71, 0x5f, 0x80, + 0x11, 0x00, 0x00, 0x03, 0x21, 0x2d, 0x4c, 0x8c, + 0xa5, 0x00, 0x00, 0x02, 0x1b, 0x4c, 0x17, 0x00, + 0x00, 0x02, 0x06, 0x78, 0x00, 0x07, 0x06, 0x14, + 0x29, 0x71, 0x3f, 0x53, 0x85, 0x09, 0x00, 0x00, + 0x01, 0x24, 0x03, 0x00, 0x00, 0x03, 0x01, 0x04, + 0x71, 0x00, 0x00, 0x00, 0x02, 0x1e, 0x2a, 0x81, + 0x2b, 0x00, 0x0f, 0x02, 0x33, 0x9c, 0x00, 0x00, + 0x00, 0x07, 0x0e, 0x34, 0x33, 0x39, 0x41, 0x62, + 0xae, 0x00, 0x08, 0x0e, 0x34, 0x33, 0x39, 0x41, + 0x62, 0x80, 0xae, 0x00, 0x05, 0x0e, 0x34, 0x33, + 0x39, 0x41, 0x01, 0x00, 0x00, 0x01, 0x33, 0x00, + 0x00, 0x01, 0x08, 0x0e, 0x34, 0x33, 0x39, 0x41, + 0x62, 0xa0, 0xae, 0x01, 0x09, 0x0e, 0x34, 0x33, + 0x39, 0x41, 0x51, 0x62, 0xa0, 0xae, 0x05, 0x06, + 0x0e, 0x34, 0x33, 0x39, 0x41, 0xae, 0x00, 0x00, + 0x00, 0x05, 0x0e, 0x34, 0x33, 0x39, 0x41, 0x07, + 0x06, 0x0e, 0x34, 0x33, 0x39, 0x41, 0xae, 0x03, + 0x05, 0x0e, 0x34, 0x33, 0x39, 0x41, 0x09, 0x00, + 0x03, 0x02, 0x0e, 0x33, 0x01, 0x00, 0x00, 0x05, + 0x0e, 0x34, 0x33, 0x39, 0x41, 0x04, 0x02, 0x39, + 0x41, 0x00, 0x00, 0x00, 0x05, 0x0e, 0x34, 0x33, + 0x39, 0x41, 0x03, 0x00, 0x01, 0x03, 0x33, 0x39, + 0x41, 0x01, 0x01, 0x33, 0x58, 0x00, 0x03, 0x02, + 0x39, 0x41, 0x02, 0x00, 0x00, 0x02, 0x39, 0x41, + 0x59, 0x00, 0x00, 0x06, 0x0e, 0x34, 0x33, 0x39, + 0x41, 0xae, 0x00, 0x02, 0x39, 0x41, 0x80, 0x12, + 0x00, 0x0f, 0x01, 0x33, 0x1f, 0x00, 0x25, 0x01, + 0x33, 0x08, 0x00, 0x00, 0x02, 0x33, 0x9c, 0x2f, + 0x00, 0x27, 0x01, 0x33, 0x37, 0x00, 0x30, 0x01, + 0x33, 0x0e, 0x00, 0x0b, 0x01, 0x33, 0x32, 0x00, + 0x00, 0x01, 0x33, 0x57, 0x00, 0x18, 0x01, 0x33, + 0x09, 0x00, 0x04, 0x01, 0x33, 0x5f, 0x00, 0x1e, + 0x01, 0x33, 0xc0, 0x31, 0xef, 0x00, 0x00, 0x02, + 0x1e, 0x2a, 0x80, 0x0f, 0x00, 0x07, 0x02, 0x33, + 0x4c, 0x80, 0xa7, 0x00, 0x02, 0x10, 0x21, 0x23, + 0x2f, 0x31, 0x47, 0x40, 0x3f, 0x55, 0x56, 0x61, + 0x68, 0x87, 0x49, 0x9a, 0xa2, 0xa7, 0x02, 0x0f, + 0x21, 0x23, 0x2f, 0x31, 0x47, 0x40, 0x3f, 0x55, + 0x61, 0x68, 0x87, 0x49, 0x9a, 0xa2, 0xa7, 0x01, + 0x0b, 0x21, 0x23, 0x2f, 0x31, 0x47, 0x3f, 0x55, + 0x61, 0x49, 0x9a, 0xa2, 0x00, 0x0c, 0x21, 0x23, + 0x2f, 0x31, 0x47, 0x3f, 0x55, 0x61, 0x87, 0x49, + 0x9a, 0xa2, 0x00, 0x0b, 0x21, 0x23, 0x2f, 0x31, + 0x47, 0x3f, 0x55, 0x61, 0x49, 0x9a, 0xa2, 0x80, + 0x36, 0x00, 0x00, 0x03, 0x0b, 0x21, 0xa7, 0x00, + 0x00, 0x00, 0x02, 0x21, 0x9b, 0x39, 0x00, 0x00, + 0x03, 0x44, 0x4c, 0x65, 0x80, 0x1f, 0x00, 0x00, + 0x02, 0x11, 0x3e, 0xc0, 0x12, 0xed, 0x00, 0x01, + 0x02, 0x04, 0x6b, 0x80, 0x31, 0x00, 0x00, 0x02, + 0x04, 0x9e, 0x09, 0x00, 0x00, 0x02, 0x04, 0x9e, + 0x46, 0x00, 0x01, 0x05, 0x0e, 0x34, 0x33, 0x39, + 0x41, 0x80, 0x99, 0x00, 0x04, 0x06, 0x0e, 0x34, + 0x33, 0x39, 0x41, 0xae, 0x09, 0x00, 0x00, 0x02, + 0x39, 0x41, 0x2c, 0x00, 0x01, 0x02, 0x39, 0x41, + 0x80, 0xdf, 0x00, 0x01, 0x03, 0x1f, 0x1d, 0x50, + 0x00, 0x02, 0x1d, 0x50, 0x03, 0x00, 0x2c, 0x03, + 0x1d, 0x4f, 0x50, 0x02, 0x00, 0x08, 0x02, 0x1d, + 0x50, 0x81, 0x1f, 0x00, 0x1b, 0x02, 0x04, 0x1b, + 0x87, 0x75, 0x00, 0x00, 0x02, 0x58, 0x79, 0x87, + 0x8d, 0x00, 0x00, 0x02, 0x2d, 0x9b, 0x00, 0x00, + 0x00, 0x02, 0x2d, 0x9b, 0x36, 0x00, 0x01, 0x02, + 0x2d, 0x9b, 0x8c, 0x12, 0x00, 0x01, 0x02, 0x2d, + 0x9b, 0x00, 0x00, 0x00, 0x02, 0x2d, 0x9b, 0xc0, + 0x5c, 0x4b, 0x00, 0x03, 0x01, 0x24, 0x96, 0x3b, + 0x00, 0x11, 0x01, 0x33, 0x9e, 0x5d, 0x00, 0x01, + 0x01, 0x33, 0xce, 0xcd, 0x2d, 0x00, +}; + +static const uint8_t unicode_prop_Hyphen_table[28] = { + 0xac, 0x80, 0xfe, 0x80, 0x44, 0xdb, 0x80, 0x52, + 0x7a, 0x80, 0x48, 0x08, 0x81, 0x4e, 0x04, 0x80, + 0x42, 0xe2, 0x80, 0x60, 0xcd, 0x66, 0x80, 0x40, + 0xa8, 0x80, 0xd6, 0x80, +}; + +static const uint8_t unicode_prop_Other_Math_table[200] = { + 0xdd, 0x80, 0x43, 0x70, 0x11, 0x80, 0x99, 0x09, + 0x81, 0x5c, 0x1f, 0x80, 0x9a, 0x82, 0x8a, 0x80, + 0x9f, 0x83, 0x97, 0x81, 0x8d, 0x81, 0xc0, 0x8c, + 0x18, 0x11, 0x1c, 0x91, 0x03, 0x01, 0x89, 0x00, + 0x14, 0x28, 0x11, 0x09, 0x02, 0x05, 0x13, 0x24, + 0xca, 0x21, 0x18, 0x08, 0x08, 0x00, 0x21, 0x0b, + 0x0b, 0x91, 0x09, 0x00, 0x06, 0x00, 0x29, 0x41, + 0x21, 0x83, 0x40, 0xa7, 0x08, 0x80, 0x97, 0x80, + 0x90, 0x80, 0x41, 0xbc, 0x81, 0x8b, 0x88, 0x24, + 0x21, 0x09, 0x14, 0x8d, 0x00, 0x01, 0x85, 0x97, + 0x81, 0xb8, 0x00, 0x80, 0x9c, 0x83, 0x88, 0x81, + 0x41, 0x55, 0x81, 0x9e, 0x89, 0x41, 0x92, 0x95, + 0xbe, 0x83, 0x9f, 0x81, 0x60, 0xd4, 0x62, 0x00, + 0x03, 0x80, 0x40, 0xd2, 0x00, 0x80, 0x60, 0xd4, + 0xc0, 0xd4, 0x80, 0xc6, 0x01, 0x08, 0x09, 0x0b, + 0x80, 0x8b, 0x00, 0x06, 0x80, 0xc0, 0x03, 0x0f, + 0x06, 0x80, 0x9b, 0x03, 0x04, 0x00, 0x16, 0x80, + 0x41, 0x53, 0x81, 0x98, 0x80, 0x98, 0x80, 0x9e, + 0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, 0x80, 0x9e, + 0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, 0x07, 0x81, + 0xb1, 0x55, 0xff, 0x18, 0x9a, 0x01, 0x00, 0x08, + 0x80, 0x89, 0x03, 0x00, 0x00, 0x28, 0x18, 0x00, + 0x00, 0x02, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x0b, 0x06, 0x03, 0x03, 0x00, + 0x80, 0x89, 0x80, 0x90, 0x22, 0x04, 0x80, 0x90, +}; + +static const uint8_t unicode_prop_Other_Alphabetic_table[452] = { + 0x43, 0x44, 0x80, 0x9c, 0x8c, 0x42, 0x3f, 0x8d, + 0x00, 0x01, 0x01, 0x00, 0xc7, 0x8a, 0xaf, 0x8c, + 0x06, 0x8f, 0x80, 0xe4, 0x33, 0x19, 0x0b, 0x80, + 0xa2, 0x80, 0x9d, 0x8f, 0xe5, 0x8a, 0xe4, 0x0a, + 0x88, 0x02, 0x03, 0xe9, 0x80, 0xbb, 0x8b, 0x16, + 0x85, 0x93, 0xb5, 0x09, 0x8e, 0x01, 0x22, 0x89, + 0x81, 0x9c, 0x82, 0xb9, 0x31, 0x09, 0x81, 0x89, + 0x80, 0x89, 0x81, 0x9c, 0x82, 0xb9, 0x23, 0x09, + 0x0b, 0x80, 0x9d, 0x0a, 0x80, 0x8a, 0x82, 0xb9, + 0x38, 0x10, 0x81, 0x94, 0x81, 0x95, 0x13, 0x82, + 0xb9, 0x31, 0x09, 0x81, 0x88, 0x81, 0x89, 0x81, + 0x9d, 0x80, 0xba, 0x22, 0x10, 0x82, 0x89, 0x80, + 0xa7, 0x84, 0xb8, 0x30, 0x10, 0x17, 0x81, 0x8a, + 0x81, 0x9c, 0x82, 0xb9, 0x30, 0x10, 0x17, 0x81, + 0x8a, 0x81, 0x8e, 0x80, 0x8b, 0x83, 0xb9, 0x30, + 0x10, 0x82, 0x89, 0x80, 0x89, 0x81, 0x9c, 0x82, + 0xca, 0x28, 0x00, 0x87, 0x91, 0x81, 0xbc, 0x01, + 0x86, 0x91, 0x80, 0xe2, 0x01, 0x28, 0x81, 0x8f, + 0x80, 0x40, 0xa2, 0x92, 0x88, 0x8a, 0x80, 0xa3, + 0xed, 0x8b, 0x00, 0x0b, 0x96, 0x1b, 0x10, 0x11, + 0x32, 0x83, 0x8c, 0x8b, 0x00, 0x89, 0x83, 0x46, + 0x73, 0x81, 0x9d, 0x81, 0x9d, 0x81, 0x9d, 0x81, + 0xc1, 0x92, 0x40, 0xbb, 0x81, 0xa1, 0x80, 0xf5, + 0x8b, 0x83, 0x88, 0x40, 0xdd, 0x84, 0xb8, 0x89, + 0x81, 0x93, 0xc9, 0x81, 0x8a, 0x82, 0xb0, 0x84, + 0xaf, 0x8e, 0xbb, 0x82, 0x9d, 0x88, 0x09, 0xb8, + 0x8a, 0xb1, 0x92, 0x41, 0x9b, 0xa1, 0x46, 0xc0, + 0xb3, 0x48, 0xf5, 0x9f, 0x60, 0x78, 0x73, 0x87, + 0xa1, 0x81, 0x41, 0x61, 0x07, 0x80, 0x96, 0x84, + 0xd7, 0x81, 0xb1, 0x8f, 0x00, 0xb8, 0x80, 0xa5, + 0x84, 0x9b, 0x8b, 0xac, 0x83, 0xaf, 0x8b, 0xa4, + 0x80, 0xc2, 0x8d, 0x8b, 0x07, 0x81, 0xac, 0x82, + 0xb1, 0x00, 0x11, 0x0c, 0x80, 0xab, 0x24, 0x80, + 0x40, 0xec, 0x87, 0x60, 0x4f, 0x32, 0x80, 0x48, + 0x56, 0x84, 0x46, 0x85, 0x10, 0x0c, 0x83, 0x43, + 0x13, 0x83, 0xc0, 0x80, 0x41, 0x40, 0x81, 0xcc, + 0x82, 0x41, 0x02, 0x82, 0xb4, 0x8d, 0xac, 0x81, + 0x8a, 0x82, 0xac, 0x88, 0x88, 0x80, 0xbc, 0x82, + 0xa3, 0x8b, 0x91, 0x81, 0xb8, 0x82, 0xaf, 0x8c, + 0x8d, 0x81, 0xdb, 0x88, 0x08, 0x28, 0x08, 0x40, + 0x9c, 0x89, 0x96, 0x83, 0xb9, 0x31, 0x09, 0x81, + 0x89, 0x80, 0x89, 0x81, 0xd3, 0x88, 0x00, 0x08, + 0x03, 0x01, 0xe6, 0x8c, 0x02, 0xe9, 0x91, 0x40, + 0xec, 0x31, 0x86, 0x9c, 0x81, 0xd1, 0x8e, 0x00, + 0xe9, 0x8a, 0xe6, 0x8d, 0x41, 0x00, 0x8c, 0x40, + 0xf6, 0x28, 0x09, 0x0a, 0x00, 0x80, 0x40, 0x8d, + 0x31, 0x2b, 0x80, 0x9b, 0x89, 0xa9, 0x20, 0x83, + 0x91, 0x8a, 0xad, 0x8d, 0x40, 0xc7, 0x87, 0x40, + 0xc6, 0x38, 0x86, 0xd2, 0x95, 0x80, 0x8d, 0xf9, + 0x2a, 0x00, 0x08, 0x10, 0x02, 0x80, 0xc1, 0x20, + 0x08, 0x83, 0x41, 0x5b, 0x83, 0x88, 0x08, 0x80, + 0xaf, 0x32, 0x82, 0x60, 0x41, 0xdc, 0x90, 0x4e, + 0x1f, 0x00, 0xb6, 0x33, 0xdc, 0x81, 0x60, 0x4c, + 0xab, 0x80, 0x60, 0x23, 0x60, 0x30, 0x90, 0x0e, + 0x01, 0x04, 0xe3, 0x80, 0x46, 0x52, 0x01, 0x06, + 0x0c, 0x80, 0x42, 0x50, 0x80, 0x47, 0xe7, 0x99, + 0x85, 0x99, 0x85, 0x99, +}; + +static const uint8_t unicode_prop_Other_Lowercase_table[68] = { + 0x40, 0xa9, 0x80, 0x8e, 0x80, 0x41, 0xf4, 0x88, + 0x31, 0x9d, 0x84, 0xdf, 0x80, 0xb3, 0x80, 0x4d, + 0x80, 0x80, 0x4c, 0x2e, 0xbe, 0x8c, 0x80, 0xa1, + 0xa4, 0x42, 0xb0, 0x80, 0x8c, 0x80, 0x8f, 0x8c, + 0x40, 0xd2, 0x8f, 0x43, 0x4f, 0x99, 0x47, 0x91, + 0x81, 0x60, 0x7a, 0x1d, 0x81, 0x40, 0xd1, 0x80, + 0xff, 0x1a, 0x81, 0x43, 0x61, 0x83, 0x88, 0x80, + 0x60, 0x5c, 0x15, 0x01, 0x10, 0xa9, 0x80, 0x88, + 0x60, 0xd8, 0x74, 0xbd, +}; + +static const uint8_t unicode_prop_Other_Uppercase_table[15] = { + 0x60, 0x21, 0x5f, 0x8f, 0x43, 0x45, 0x99, 0x61, + 0xcc, 0x5f, 0x99, 0x85, 0x99, 0x85, 0x99, +}; + +static const uint8_t unicode_prop_Other_Grapheme_Extend_table[112] = { + 0x49, 0xbd, 0x80, 0x97, 0x80, 0x41, 0x65, 0x80, + 0x97, 0x80, 0xe5, 0x80, 0x97, 0x80, 0x40, 0xe7, + 0x00, 0x03, 0x08, 0x81, 0x88, 0x81, 0xe6, 0x80, + 0x97, 0x80, 0xf6, 0x80, 0x8e, 0x80, 0x49, 0x34, + 0x80, 0x9d, 0x80, 0x43, 0xff, 0x04, 0x00, 0x04, + 0x81, 0xe4, 0x80, 0xc6, 0x81, 0x44, 0x17, 0x80, + 0x50, 0x20, 0x81, 0x60, 0x79, 0x22, 0x80, 0xeb, + 0x80, 0x60, 0x55, 0xdc, 0x81, 0x52, 0x1f, 0x80, + 0xf3, 0x80, 0x41, 0x07, 0x80, 0x8d, 0x80, 0x88, + 0x80, 0xdf, 0x80, 0x88, 0x01, 0x00, 0x14, 0x80, + 0x40, 0xdf, 0x80, 0x8b, 0x80, 0x40, 0xf0, 0x80, + 0x41, 0x05, 0x80, 0x42, 0x78, 0x80, 0x8b, 0x80, + 0x46, 0x02, 0x80, 0x60, 0x50, 0xad, 0x81, 0x60, + 0x61, 0x72, 0x0d, 0x85, 0x6c, 0x2e, 0xac, 0xdf, +}; + +static const uint8_t unicode_prop_Other_Default_Ignorable_Code_Point_table[32] = { + 0x43, 0x4e, 0x80, 0x4e, 0x0e, 0x81, 0x46, 0x52, + 0x81, 0x48, 0xae, 0x80, 0x50, 0xfd, 0x80, 0x60, + 0xce, 0x3a, 0x80, 0xce, 0x88, 0x6d, 0x00, 0x06, + 0x00, 0x9d, 0xdf, 0xff, 0x40, 0xef, 0x4e, 0x0f, +}; + +static const uint8_t unicode_prop_Other_ID_Start_table[11] = { + 0x58, 0x84, 0x81, 0x48, 0x90, 0x80, 0x94, 0x80, + 0x4f, 0x6b, 0x81, +}; + +static const uint8_t unicode_prop_Other_ID_Continue_table[22] = { + 0x40, 0xb6, 0x80, 0x42, 0xce, 0x80, 0x4f, 0xe0, + 0x88, 0x46, 0x67, 0x80, 0x46, 0x30, 0x81, 0x50, + 0xec, 0x80, 0x60, 0xce, 0x68, 0x80, +}; + +static const uint8_t unicode_prop_Prepended_Concatenation_Mark_table[19] = { + 0x45, 0xff, 0x85, 0x40, 0xd6, 0x80, 0xb0, 0x80, + 0x41, 0x7f, 0x81, 0xcf, 0x80, 0x61, 0x07, 0xd9, + 0x80, 0x8e, 0x80, +}; + +static const uint8_t unicode_prop_XID_Start1_table[31] = { + 0x43, 0x79, 0x80, 0x4a, 0xb7, 0x80, 0xfe, 0x80, + 0x60, 0x21, 0xe6, 0x81, 0x60, 0xcb, 0xc0, 0x85, + 0x41, 0x95, 0x81, 0xf3, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x80, 0x41, 0x1e, 0x81, +}; + +static const uint8_t unicode_prop_XID_Continue1_table[23] = { + 0x43, 0x79, 0x80, 0x60, 0x2d, 0x1f, 0x81, 0x60, + 0xcb, 0xc0, 0x85, 0x41, 0x95, 0x81, 0xf3, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, +}; + +static const uint8_t unicode_prop_Changes_When_Titlecased1_table[22] = { + 0x41, 0xc3, 0x08, 0x08, 0x81, 0xa4, 0x81, 0x4e, + 0xdc, 0xaa, 0x0a, 0x4e, 0x87, 0x3f, 0x3f, 0x87, + 0x8b, 0x80, 0x8e, 0x80, 0xae, 0x80, +}; + +static const uint8_t unicode_prop_Changes_When_Casefolded1_table[29] = { + 0x41, 0xef, 0x80, 0x41, 0x9e, 0x80, 0x9e, 0x80, + 0x5a, 0xe4, 0x83, 0x40, 0xb5, 0x00, 0x00, 0x00, + 0x80, 0xde, 0x06, 0x06, 0x80, 0x8a, 0x09, 0x81, + 0x89, 0x10, 0x81, 0x8d, 0x80, +}; + +static const uint8_t unicode_prop_Changes_When_NFKC_Casefolded1_table[449] = { + 0x40, 0x9f, 0x06, 0x00, 0x01, 0x00, 0x01, 0x12, + 0x10, 0x82, 0xf3, 0x80, 0x8b, 0x80, 0x40, 0x84, + 0x01, 0x01, 0x80, 0xa2, 0x01, 0x80, 0x40, 0xbb, + 0x88, 0x9e, 0x29, 0x84, 0xda, 0x08, 0x81, 0x89, + 0x80, 0xa3, 0x04, 0x02, 0x04, 0x08, 0x07, 0x80, + 0x9e, 0x80, 0xa0, 0x82, 0x9c, 0x80, 0x42, 0x28, + 0x80, 0xd7, 0x83, 0x42, 0xde, 0x87, 0xfb, 0x08, + 0x80, 0xd2, 0x01, 0x80, 0xa1, 0x11, 0x80, 0x40, + 0xfc, 0x81, 0x42, 0xd4, 0x80, 0xfe, 0x80, 0xa7, + 0x81, 0xad, 0x80, 0xb5, 0x80, 0x88, 0x03, 0x03, + 0x03, 0x80, 0x8b, 0x80, 0x88, 0x00, 0x26, 0x80, + 0x90, 0x80, 0x88, 0x03, 0x03, 0x03, 0x80, 0x8b, + 0x80, 0x41, 0x41, 0x80, 0xe1, 0x81, 0x46, 0x52, + 0x81, 0xd4, 0x84, 0x45, 0x1b, 0x10, 0x8a, 0x80, + 0x91, 0x80, 0x9b, 0x8c, 0x80, 0xa1, 0xa4, 0x40, + 0xd5, 0x83, 0x40, 0xb5, 0x00, 0x00, 0x00, 0x80, + 0x99, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, + 0xb7, 0x05, 0x00, 0x13, 0x05, 0x11, 0x02, 0x0c, + 0x11, 0x00, 0x00, 0x0c, 0x15, 0x05, 0x08, 0x8f, + 0x00, 0x20, 0x8b, 0x12, 0x2a, 0x08, 0x0b, 0x00, + 0x07, 0x82, 0x8c, 0x06, 0x92, 0x81, 0x9a, 0x80, + 0x8c, 0x8a, 0x80, 0xd6, 0x18, 0x10, 0x8a, 0x01, + 0x0c, 0x0a, 0x00, 0x10, 0x11, 0x02, 0x06, 0x05, + 0x1c, 0x85, 0x8f, 0x8f, 0x8f, 0x88, 0x80, 0x40, + 0xa1, 0x08, 0x81, 0x40, 0xf7, 0x81, 0x41, 0x34, + 0xd5, 0x99, 0x9a, 0x45, 0x20, 0x80, 0xe6, 0x82, + 0xe4, 0x80, 0x41, 0x9e, 0x81, 0x40, 0xf0, 0x80, + 0x41, 0x2e, 0x80, 0xd2, 0x80, 0x8b, 0x40, 0xd5, + 0xa9, 0x80, 0xb4, 0x00, 0x82, 0xdf, 0x09, 0x80, + 0xde, 0x80, 0xb0, 0xdd, 0x82, 0x8d, 0xdf, 0x9e, + 0x80, 0xa7, 0x87, 0xae, 0x80, 0x41, 0x7f, 0x60, + 0x72, 0x9b, 0x81, 0x40, 0xd1, 0x80, 0xff, 0x1a, + 0x81, 0x43, 0x61, 0x83, 0x88, 0x80, 0x60, 0x4d, + 0x95, 0x41, 0x0d, 0x08, 0x00, 0x81, 0x89, 0x00, + 0x00, 0x09, 0x82, 0xc3, 0x81, 0xe9, 0xc2, 0x00, + 0x97, 0x04, 0x00, 0x01, 0x01, 0x80, 0xeb, 0xa0, + 0x41, 0x6a, 0x91, 0xbf, 0x81, 0xb5, 0xa7, 0x8c, + 0x82, 0x99, 0x95, 0x94, 0x81, 0x8b, 0x80, 0x92, + 0x03, 0x1a, 0x00, 0x80, 0x40, 0x86, 0x08, 0x80, + 0x9f, 0x99, 0x40, 0x83, 0x15, 0x0d, 0x0d, 0x0a, + 0x16, 0x06, 0x80, 0x88, 0x47, 0x87, 0x20, 0xa9, + 0x80, 0x88, 0x60, 0xb4, 0xe4, 0x83, 0x50, 0x31, + 0xa3, 0x44, 0x63, 0x86, 0x8d, 0x87, 0xbf, 0x85, + 0x42, 0x3e, 0xd4, 0x80, 0xc6, 0x01, 0x08, 0x09, + 0x0b, 0x80, 0x8b, 0x00, 0x06, 0x80, 0xc0, 0x03, + 0x0f, 0x06, 0x80, 0x9b, 0x03, 0x04, 0x00, 0x16, + 0x80, 0x41, 0x53, 0x81, 0x41, 0x23, 0x81, 0xb1, + 0x48, 0x2f, 0xbd, 0x4d, 0x91, 0x18, 0x9a, 0x01, + 0x00, 0x08, 0x80, 0x89, 0x03, 0x00, 0x00, 0x28, + 0x18, 0x00, 0x00, 0x02, 0x01, 0x00, 0x08, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x0b, 0x06, 0x03, + 0x03, 0x00, 0x80, 0x89, 0x80, 0x90, 0x22, 0x04, + 0x80, 0x90, 0x42, 0x43, 0x8a, 0x84, 0x9e, 0x80, + 0x9f, 0x99, 0x82, 0xa2, 0x80, 0xee, 0x82, 0x8c, + 0xab, 0x83, 0x88, 0x31, 0x49, 0x9d, 0x89, 0x60, + 0xfc, 0x05, 0x42, 0x1d, 0x6b, 0x05, 0xe1, 0x4f, + 0xff, +}; + +static const uint8_t unicode_prop_Basic_Emoji1_table[144] = { + 0x60, 0x23, 0x19, 0x81, 0x40, 0xcc, 0x1a, 0x01, + 0x80, 0x42, 0x08, 0x81, 0x94, 0x81, 0xb1, 0x8b, + 0xaa, 0x80, 0x92, 0x80, 0x8c, 0x07, 0x81, 0x90, + 0x0c, 0x0f, 0x04, 0x80, 0x94, 0x06, 0x08, 0x03, + 0x01, 0x06, 0x03, 0x81, 0x9b, 0x80, 0xa2, 0x00, + 0x03, 0x10, 0x80, 0xbc, 0x82, 0x97, 0x80, 0x8d, + 0x80, 0x43, 0x5a, 0x81, 0xb2, 0x03, 0x80, 0x61, + 0xc4, 0xad, 0x80, 0x40, 0xc9, 0x80, 0x40, 0xbd, + 0x01, 0x89, 0xe5, 0x80, 0x97, 0x80, 0x93, 0x01, + 0x20, 0x82, 0x94, 0x81, 0x40, 0xad, 0xa0, 0x8b, + 0x88, 0x80, 0xc5, 0x80, 0x95, 0x8b, 0xaa, 0x1c, + 0x8b, 0x90, 0x10, 0x82, 0xc6, 0x00, 0x80, 0x40, + 0xba, 0x81, 0xbe, 0x8c, 0x18, 0x97, 0x91, 0x80, + 0x99, 0x81, 0x8c, 0x80, 0xd5, 0xd4, 0xaf, 0xc5, + 0x28, 0x12, 0x0b, 0x13, 0x8a, 0x0e, 0x88, 0x40, + 0xe2, 0x8b, 0x18, 0x41, 0x1a, 0xae, 0x80, 0x89, + 0x80, 0x40, 0xb8, 0xef, 0x8c, 0x82, 0x8a, 0x82, + 0xb8, 0x00, 0x83, 0x8f, 0x81, 0x8b, 0x83, 0x89, +}; + +static const uint8_t unicode_prop_Basic_Emoji2_table[183] = { + 0x40, 0xa8, 0x03, 0x80, 0x5f, 0x8c, 0x80, 0x8b, + 0x80, 0x40, 0xd7, 0x80, 0x95, 0x80, 0xd9, 0x85, + 0x8e, 0x81, 0x41, 0x7c, 0x80, 0x40, 0xa5, 0x80, + 0x9c, 0x10, 0x0c, 0x82, 0x40, 0xc6, 0x80, 0x40, + 0xe6, 0x81, 0x89, 0x80, 0x88, 0x80, 0xb9, 0x0a, + 0x84, 0x88, 0x01, 0x05, 0x03, 0x01, 0x00, 0x09, + 0x02, 0x02, 0x0f, 0x14, 0x00, 0x80, 0x9b, 0x09, + 0x00, 0x08, 0x80, 0x91, 0x01, 0x80, 0x92, 0x00, + 0x18, 0x00, 0x0a, 0x05, 0x07, 0x81, 0x95, 0x05, + 0x00, 0x00, 0x80, 0x94, 0x05, 0x09, 0x01, 0x17, + 0x04, 0x09, 0x08, 0x01, 0x00, 0x00, 0x05, 0x02, + 0x80, 0x90, 0x81, 0x8e, 0x01, 0x80, 0x9a, 0x81, + 0xbb, 0x80, 0x41, 0x91, 0x81, 0x41, 0xce, 0x82, + 0x45, 0x27, 0x80, 0x8b, 0x80, 0x42, 0x58, 0x00, + 0x80, 0x61, 0xbe, 0xd5, 0x81, 0x8b, 0x81, 0x40, + 0x81, 0x80, 0xb3, 0x80, 0x40, 0xe8, 0x01, 0x88, + 0x88, 0x80, 0xc5, 0x80, 0x97, 0x08, 0x11, 0x81, + 0xaa, 0x1c, 0x8b, 0x92, 0x00, 0x00, 0x80, 0xc6, + 0x00, 0x80, 0x40, 0xba, 0x80, 0xca, 0x81, 0xa3, + 0x09, 0x86, 0x8c, 0x01, 0x19, 0x80, 0x93, 0x01, + 0x07, 0x81, 0x88, 0x04, 0x82, 0x8b, 0x17, 0x11, + 0x00, 0x03, 0x05, 0x02, 0x05, 0x80, 0x40, 0xcf, + 0x00, 0x82, 0x8f, 0x2a, 0x05, 0x01, 0x80, +}; + +static const uint8_t unicode_prop_RGI_Emoji_Flag_Sequence_table[128] = { + 0x0c, 0x00, 0x09, 0x00, 0x04, 0x01, 0x02, 0x06, + 0x03, 0x03, 0x01, 0x02, 0x01, 0x03, 0x07, 0x0d, + 0x18, 0x00, 0x09, 0x00, 0x00, 0x89, 0x08, 0x00, + 0x00, 0x81, 0x88, 0x83, 0x8c, 0x10, 0x00, 0x01, + 0x07, 0x08, 0x29, 0x10, 0x28, 0x00, 0x80, 0x8a, + 0x00, 0x0a, 0x00, 0x0e, 0x15, 0x18, 0x83, 0x89, + 0x06, 0x00, 0x81, 0x8d, 0x00, 0x12, 0x08, 0x00, + 0x03, 0x00, 0x24, 0x00, 0x05, 0x21, 0x00, 0x00, + 0x29, 0x90, 0x00, 0x02, 0x00, 0x08, 0x09, 0x00, + 0x08, 0x18, 0x8b, 0x80, 0x8c, 0x02, 0x19, 0x1a, + 0x11, 0x00, 0x00, 0x80, 0x9c, 0x80, 0x88, 0x02, + 0x00, 0x00, 0x02, 0x20, 0x88, 0x0a, 0x00, 0x03, + 0x01, 0x02, 0x05, 0x08, 0x00, 0x01, 0x09, 0x20, + 0x21, 0x18, 0x22, 0x00, 0x00, 0x00, 0x00, 0x18, + 0x28, 0x89, 0x80, 0x8b, 0x80, 0x90, 0x80, 0x92, + 0x80, 0x8d, 0x05, 0x80, 0x8a, 0x80, 0x88, 0x80, +}; + +static const uint8_t unicode_prop_Emoji_Keycap_Sequence_table[4] = { + 0xa2, 0x05, 0x04, 0x89, +}; + +static const uint8_t unicode_prop_ASCII_Hex_Digit_table[5] = { + 0xaf, 0x89, 0x35, 0x99, 0x85, +}; + +static const uint8_t unicode_prop_Bidi_Control_table[10] = { + 0x46, 0x1b, 0x80, 0x59, 0xf0, 0x81, 0x99, 0x84, + 0xb6, 0x83, +}; + +static const uint8_t unicode_prop_Dash_table[58] = { + 0xac, 0x80, 0x45, 0x5b, 0x80, 0xb2, 0x80, 0x4e, + 0x40, 0x80, 0x44, 0x04, 0x80, 0x48, 0x08, 0x85, + 0xbc, 0x80, 0xa6, 0x80, 0x8e, 0x80, 0x41, 0x85, + 0x80, 0x4c, 0x03, 0x01, 0x80, 0x9e, 0x0b, 0x80, + 0x9b, 0x80, 0x41, 0xbd, 0x80, 0x92, 0x80, 0xee, + 0x80, 0x60, 0xcd, 0x8f, 0x81, 0xa4, 0x80, 0x89, + 0x80, 0x40, 0xa8, 0x80, 0x4e, 0x5f, 0x80, 0x41, + 0x3d, 0x80, +}; + +static const uint8_t unicode_prop_Deprecated_table[23] = { + 0x41, 0x48, 0x80, 0x45, 0x28, 0x80, 0x49, 0x02, + 0x00, 0x80, 0x48, 0x28, 0x81, 0x48, 0xc4, 0x85, + 0x42, 0xb8, 0x81, 0x6d, 0xdc, 0xd5, 0x80, +}; + +static const uint8_t unicode_prop_Diacritic_table[447] = { + 0xdd, 0x00, 0x80, 0xc6, 0x05, 0x03, 0x01, 0x81, + 0x41, 0xf6, 0x40, 0x9e, 0x07, 0x25, 0x90, 0x0b, + 0x80, 0x88, 0x81, 0x40, 0xfc, 0x84, 0x40, 0xd0, + 0x80, 0xb6, 0xac, 0x00, 0x01, 0x01, 0x00, 0x40, + 0x82, 0x3b, 0x81, 0x40, 0x85, 0x0b, 0x0a, 0x82, + 0xc2, 0x9a, 0xda, 0x8a, 0xb9, 0x8a, 0xa1, 0x81, + 0xfd, 0x87, 0xa8, 0x89, 0x8f, 0x9b, 0xbc, 0x80, + 0x8f, 0x02, 0x83, 0x9b, 0x80, 0xc9, 0x80, 0x8f, + 0x80, 0xed, 0x80, 0x8f, 0x80, 0xed, 0x80, 0x8f, + 0x80, 0xae, 0x82, 0xbb, 0x80, 0x8f, 0x06, 0x80, + 0xf6, 0x80, 0xed, 0x80, 0x8f, 0x80, 0xed, 0x80, + 0x8f, 0x80, 0xec, 0x81, 0x8f, 0x80, 0xfb, 0x80, + 0xee, 0x80, 0x8b, 0x28, 0x80, 0xea, 0x80, 0x8c, + 0x84, 0xca, 0x81, 0x9a, 0x00, 0x00, 0x03, 0x81, + 0xc1, 0x10, 0x81, 0xbd, 0x80, 0xef, 0x00, 0x81, + 0xa7, 0x0b, 0x84, 0x98, 0x30, 0x80, 0x89, 0x81, + 0x42, 0xc0, 0x82, 0x43, 0xb3, 0x81, 0x9d, 0x80, + 0x40, 0x93, 0x8a, 0x88, 0x80, 0x41, 0x5a, 0x82, + 0x41, 0x23, 0x80, 0x93, 0x39, 0x80, 0xaf, 0x8e, + 0x81, 0x8a, 0x82, 0x8e, 0x81, 0x8b, 0xc7, 0x80, + 0x8e, 0x80, 0xa5, 0x88, 0xb5, 0x81, 0xb9, 0x80, + 0x8a, 0x81, 0xc1, 0x81, 0xbf, 0x85, 0xd1, 0x98, + 0x18, 0x28, 0x0a, 0xb1, 0xbe, 0xaf, 0xa3, 0x84, + 0x8b, 0xa4, 0x8a, 0x41, 0xbc, 0x00, 0x82, 0x8a, + 0x82, 0x8c, 0x82, 0x8c, 0x82, 0x8c, 0x81, 0x4c, + 0xef, 0x82, 0x41, 0x3c, 0x80, 0x41, 0xf9, 0x85, + 0xe8, 0x83, 0xde, 0x80, 0x60, 0x75, 0x71, 0x80, + 0x8b, 0x08, 0x80, 0x9b, 0x81, 0xd1, 0x81, 0x8d, + 0xa1, 0xe5, 0x82, 0xe5, 0x05, 0x81, 0x8b, 0x80, + 0xa4, 0x80, 0x40, 0x96, 0x80, 0x9a, 0x91, 0xb8, + 0x83, 0xa3, 0x80, 0xde, 0x80, 0x8b, 0x80, 0xa3, + 0x80, 0x40, 0x94, 0x82, 0xc0, 0x83, 0xb2, 0x80, + 0xe3, 0x84, 0x88, 0x82, 0xff, 0x81, 0x60, 0x4f, + 0x2f, 0x80, 0x43, 0x00, 0x8f, 0x41, 0x0d, 0x00, + 0x80, 0xae, 0x80, 0xac, 0x81, 0xc2, 0x80, 0x42, + 0xfb, 0x80, 0x44, 0x9e, 0x28, 0xa9, 0x80, 0x88, + 0x42, 0x7c, 0x13, 0x80, 0x40, 0xa4, 0x81, 0x42, + 0x3a, 0x85, 0xa5, 0x80, 0x99, 0x84, 0x41, 0x8b, + 0x01, 0x82, 0xc5, 0x8a, 0xb0, 0x83, 0x40, 0xbf, + 0x80, 0xa8, 0x80, 0xc7, 0x81, 0xf7, 0x81, 0xbd, + 0x80, 0xcb, 0x80, 0x88, 0x82, 0xe7, 0x81, 0x40, + 0xb1, 0x81, 0xcf, 0x81, 0x8f, 0x80, 0x97, 0x32, + 0x84, 0xd8, 0x10, 0x81, 0x8c, 0x81, 0xde, 0x02, + 0x80, 0xfa, 0x81, 0x40, 0xfa, 0x81, 0xfd, 0x80, + 0xf5, 0x81, 0xf2, 0x80, 0x41, 0x0c, 0x81, 0x41, + 0x01, 0x0b, 0x80, 0x40, 0x9b, 0x80, 0xd2, 0x80, + 0x91, 0x80, 0xd0, 0x80, 0x41, 0xa4, 0x80, 0x41, + 0x01, 0x00, 0x81, 0xd0, 0x80, 0xc0, 0x80, 0x41, + 0x66, 0x81, 0x96, 0x80, 0x54, 0xeb, 0x8e, 0x60, + 0x2c, 0xd8, 0x80, 0x49, 0xbf, 0x84, 0xba, 0x86, + 0x42, 0x33, 0x81, 0x42, 0x21, 0x90, 0xcf, 0x81, + 0x60, 0x3f, 0xfd, 0x18, 0x30, 0x81, 0x5f, 0x00, + 0xad, 0x81, 0x96, 0x42, 0x1f, 0x12, 0x2f, 0x39, + 0x86, 0x9d, 0x83, 0x4e, 0x81, 0xbd, 0x40, 0xc1, + 0x86, 0x41, 0x76, 0x80, 0xbc, 0x83, 0x42, 0xfd, + 0x81, 0x42, 0xdf, 0x86, 0xec, 0x10, 0x82, +}; + +static const uint8_t unicode_prop_Extender_table[116] = { + 0x40, 0xb6, 0x80, 0x42, 0x17, 0x81, 0x43, 0x6d, + 0x80, 0x41, 0xb8, 0x80, 0x42, 0x75, 0x80, 0x40, + 0x88, 0x80, 0xd8, 0x80, 0x42, 0xef, 0x80, 0xfe, + 0x80, 0x49, 0x42, 0x80, 0xb7, 0x80, 0x42, 0x62, + 0x80, 0x41, 0x8d, 0x80, 0xc3, 0x80, 0x53, 0x88, + 0x80, 0xaa, 0x84, 0xe6, 0x81, 0xdc, 0x82, 0x60, + 0x6f, 0x15, 0x80, 0x45, 0xf5, 0x80, 0x43, 0xc1, + 0x80, 0x95, 0x80, 0x40, 0x88, 0x80, 0xeb, 0x80, + 0x94, 0x81, 0x60, 0x54, 0x7a, 0x80, 0x48, 0x0f, + 0x81, 0x45, 0xca, 0x80, 0x9a, 0x03, 0x80, 0x44, + 0xc6, 0x80, 0x41, 0x24, 0x80, 0xf3, 0x81, 0x41, + 0xf1, 0x82, 0x44, 0xce, 0x80, 0x43, 0x3f, 0x80, + 0x60, 0x4d, 0x67, 0x81, 0x44, 0x9b, 0x08, 0x80, + 0x8d, 0x81, 0x60, 0x71, 0x47, 0x81, 0x44, 0xb0, + 0x80, 0x43, 0x53, 0x82, +}; + +static const uint8_t unicode_prop_Hex_Digit_table[12] = { + 0xaf, 0x89, 0x35, 0x99, 0x85, 0x60, 0xfe, 0xa8, + 0x89, 0x35, 0x99, 0x85, +}; + +static const uint8_t unicode_prop_IDS_Unary_Operator_table[4] = { + 0x60, 0x2f, 0xfd, 0x81, +}; + +static const uint8_t unicode_prop_IDS_Binary_Operator_table[8] = { + 0x60, 0x2f, 0xef, 0x09, 0x89, 0x41, 0xf0, 0x80, +}; + +static const uint8_t unicode_prop_IDS_Trinary_Operator_table[4] = { + 0x60, 0x2f, 0xf1, 0x81, +}; + +static const uint8_t unicode_prop_Ideographic_table[71] = { + 0x60, 0x30, 0x05, 0x81, 0x98, 0x88, 0x8d, 0x82, + 0x43, 0xc4, 0x59, 0xbf, 0xbf, 0x60, 0x51, 0xff, + 0x60, 0x58, 0xff, 0x41, 0x6d, 0x81, 0xe9, 0x60, + 0x75, 0x09, 0x80, 0x8c, 0x84, 0x88, 0x5c, 0xd5, + 0xa8, 0x9f, 0xe0, 0xf2, 0x60, 0x23, 0x7c, 0x41, + 0x8b, 0x60, 0x4d, 0x03, 0x60, 0xa6, 0xdf, 0x9f, + 0x51, 0x1d, 0x81, 0x56, 0x8d, 0x81, 0x5d, 0x30, + 0x8e, 0x42, 0x6d, 0x49, 0xa1, 0x42, 0x1d, 0x45, + 0xe1, 0x53, 0x4a, 0x84, 0x60, 0x21, 0x29, +}; + +static const uint8_t unicode_prop_Join_Control_table[4] = { + 0x60, 0x20, 0x0b, 0x81, +}; + +static const uint8_t unicode_prop_Logical_Order_Exception_table[15] = { + 0x4e, 0x3f, 0x84, 0xfa, 0x84, 0x4a, 0xef, 0x11, + 0x80, 0x60, 0x90, 0xf9, 0x09, 0x00, 0x81, +}; + +static const uint8_t unicode_prop_Modifier_Combining_Mark_table[16] = { + 0x46, 0x53, 0x09, 0x80, 0x40, 0x82, 0x05, 0x02, + 0x81, 0x41, 0xe0, 0x08, 0x12, 0x80, 0x9e, 0x80, +}; + +static const uint8_t unicode_prop_Noncharacter_Code_Point_table[71] = { + 0x60, 0xfd, 0xcf, 0x9f, 0x42, 0x0d, 0x81, 0x60, + 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, + 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, + 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, + 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, + 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, + 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, + 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, + 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, +}; + +static const uint8_t unicode_prop_Pattern_Syntax_table[58] = { + 0xa0, 0x8e, 0x89, 0x86, 0x99, 0x18, 0x80, 0x99, + 0x83, 0xa1, 0x30, 0x00, 0x08, 0x00, 0x0b, 0x03, + 0x02, 0x80, 0x96, 0x80, 0x9e, 0x80, 0x5f, 0x17, + 0x97, 0x87, 0x8e, 0x81, 0x92, 0x80, 0x89, 0x41, + 0x30, 0x42, 0xcf, 0x40, 0x9f, 0x42, 0x75, 0x9d, + 0x44, 0x6b, 0x41, 0xff, 0xff, 0x41, 0x80, 0x13, + 0x98, 0x8e, 0x80, 0x60, 0xcd, 0x0c, 0x81, 0x41, + 0x04, 0x81, +}; + +static const uint8_t unicode_prop_Pattern_White_Space_table[11] = { + 0x88, 0x84, 0x91, 0x80, 0xe3, 0x80, 0x5f, 0x87, + 0x81, 0x97, 0x81, +}; + +static const uint8_t unicode_prop_Quotation_Mark_table[31] = { + 0xa1, 0x03, 0x80, 0x40, 0x82, 0x80, 0x8e, 0x80, + 0x5f, 0x5b, 0x87, 0x98, 0x81, 0x4e, 0x06, 0x80, + 0x41, 0xc8, 0x83, 0x8c, 0x82, 0x60, 0xce, 0x20, + 0x83, 0x40, 0xbc, 0x03, 0x80, 0xd9, 0x81, +}; + +static const uint8_t unicode_prop_Radical_table[9] = { + 0x60, 0x2e, 0x7f, 0x99, 0x80, 0xd8, 0x8b, 0x40, + 0xd5, +}; + +static const uint8_t unicode_prop_Regional_Indicator_table[4] = { + 0x61, 0xf1, 0xe5, 0x99, +}; + +static const uint8_t unicode_prop_Sentence_Terminal_table[213] = { + 0xa0, 0x80, 0x8b, 0x80, 0x8f, 0x80, 0x45, 0x48, + 0x80, 0x40, 0x92, 0x82, 0x40, 0xb3, 0x80, 0xaa, + 0x82, 0x40, 0xf5, 0x80, 0xbc, 0x00, 0x02, 0x81, + 0x41, 0x24, 0x81, 0x46, 0xe3, 0x81, 0x43, 0x15, + 0x03, 0x81, 0x43, 0x04, 0x80, 0x40, 0xc5, 0x81, + 0x40, 0x9c, 0x81, 0xac, 0x04, 0x80, 0x41, 0x39, + 0x81, 0x41, 0x61, 0x83, 0x40, 0xa1, 0x81, 0x89, + 0x09, 0x81, 0x9c, 0x82, 0x40, 0xba, 0x81, 0xc0, + 0x81, 0x43, 0xa3, 0x80, 0x96, 0x81, 0x88, 0x82, + 0x4c, 0xae, 0x82, 0x41, 0x31, 0x80, 0x8c, 0x80, + 0x95, 0x81, 0x41, 0xac, 0x80, 0x60, 0x74, 0xfb, + 0x80, 0x41, 0x0d, 0x81, 0x40, 0xe2, 0x02, 0x80, + 0x41, 0x7d, 0x81, 0xd5, 0x81, 0xde, 0x80, 0x40, + 0x97, 0x81, 0x40, 0x92, 0x82, 0x40, 0x8f, 0x81, + 0x40, 0xf8, 0x80, 0x60, 0x52, 0x25, 0x01, 0x81, + 0xba, 0x02, 0x81, 0x40, 0xa8, 0x80, 0x8b, 0x80, + 0x8f, 0x80, 0xc0, 0x80, 0x4a, 0xf3, 0x81, 0x44, + 0xfc, 0x84, 0xab, 0x83, 0x40, 0xbc, 0x81, 0xf4, + 0x83, 0xfe, 0x82, 0x40, 0x80, 0x0d, 0x80, 0x8f, + 0x81, 0xd7, 0x08, 0x81, 0xeb, 0x80, 0x41, 0x29, + 0x81, 0xf4, 0x81, 0x41, 0x74, 0x0c, 0x8e, 0xe8, + 0x81, 0x40, 0xf8, 0x82, 0x42, 0x04, 0x00, 0x80, + 0x40, 0xfa, 0x81, 0xd6, 0x81, 0x41, 0xa3, 0x81, + 0x42, 0xb3, 0x81, 0xc9, 0x81, 0x60, 0x4b, 0x28, + 0x81, 0x40, 0x84, 0x80, 0xc0, 0x81, 0x8a, 0x80, + 0x42, 0x28, 0x81, 0x41, 0x27, 0x80, 0x60, 0x4e, + 0x05, 0x80, 0x5d, 0xe7, 0x80, +}; + +static const uint8_t unicode_prop_Soft_Dotted_table[79] = { + 0xe8, 0x81, 0x40, 0xc3, 0x80, 0x41, 0x18, 0x80, + 0x9d, 0x80, 0xb3, 0x80, 0x93, 0x80, 0x41, 0x3f, + 0x80, 0xe1, 0x00, 0x80, 0x59, 0x08, 0x80, 0xb2, + 0x80, 0x8c, 0x02, 0x80, 0x40, 0x83, 0x80, 0x40, + 0x9c, 0x80, 0x41, 0xa4, 0x80, 0x40, 0xd5, 0x81, + 0x4b, 0x31, 0x80, 0x61, 0xa7, 0xa4, 0x81, 0xb1, + 0x81, 0xb1, 0x81, 0xb1, 0x81, 0xb1, 0x81, 0xb1, + 0x81, 0xb1, 0x81, 0xb1, 0x81, 0xb1, 0x81, 0xb1, + 0x81, 0xb1, 0x81, 0xb1, 0x81, 0xb1, 0x81, 0x48, + 0x85, 0x80, 0x41, 0x30, 0x81, 0x99, 0x80, +}; + +static const uint8_t unicode_prop_Terminal_Punctuation_table[264] = { + 0xa0, 0x80, 0x89, 0x00, 0x80, 0x8a, 0x0a, 0x80, + 0x43, 0x3d, 0x07, 0x80, 0x42, 0x00, 0x80, 0xb8, + 0x80, 0xc7, 0x80, 0x8d, 0x00, 0x82, 0x40, 0xb3, + 0x80, 0xaa, 0x8a, 0x00, 0x40, 0xea, 0x81, 0xb5, + 0x28, 0x87, 0x9e, 0x80, 0x41, 0x04, 0x81, 0x44, + 0xf3, 0x81, 0x40, 0xab, 0x03, 0x85, 0x41, 0x36, + 0x81, 0x43, 0x14, 0x87, 0x43, 0x04, 0x80, 0xfb, + 0x82, 0xc6, 0x81, 0x40, 0x9c, 0x12, 0x80, 0xa6, + 0x19, 0x81, 0x41, 0x39, 0x81, 0x41, 0x61, 0x83, + 0x40, 0xa1, 0x81, 0x89, 0x08, 0x82, 0x9c, 0x82, + 0x40, 0xba, 0x84, 0xbd, 0x81, 0x43, 0xa3, 0x80, + 0x96, 0x81, 0x88, 0x82, 0x4c, 0xae, 0x82, 0x41, + 0x31, 0x80, 0x8c, 0x03, 0x80, 0x89, 0x00, 0x0a, + 0x81, 0x41, 0xab, 0x81, 0x60, 0x74, 0xfa, 0x81, + 0x41, 0x0c, 0x82, 0x40, 0xe2, 0x84, 0x41, 0x7d, + 0x81, 0xd5, 0x81, 0xde, 0x80, 0x40, 0x96, 0x82, + 0x40, 0x92, 0x82, 0xfe, 0x80, 0x8f, 0x81, 0x40, + 0xf8, 0x80, 0x60, 0x52, 0x25, 0x01, 0x81, 0xb8, + 0x10, 0x83, 0x40, 0xa8, 0x80, 0x89, 0x00, 0x80, + 0x8a, 0x0a, 0x80, 0xc0, 0x01, 0x80, 0x44, 0x39, + 0x80, 0xaf, 0x80, 0x44, 0x85, 0x80, 0x40, 0xc6, + 0x80, 0x41, 0x35, 0x81, 0x40, 0x97, 0x85, 0xc3, + 0x85, 0xd8, 0x83, 0x43, 0xb7, 0x84, 0xab, 0x83, + 0x40, 0xbc, 0x86, 0xef, 0x83, 0xfe, 0x82, 0x40, + 0x80, 0x0d, 0x80, 0x8f, 0x81, 0xd7, 0x84, 0xeb, + 0x80, 0x41, 0x29, 0x81, 0xf4, 0x82, 0x8b, 0x81, + 0x41, 0x65, 0x1a, 0x8e, 0xe8, 0x81, 0x40, 0xf8, + 0x82, 0x42, 0x04, 0x00, 0x80, 0x40, 0xfa, 0x81, + 0xd6, 0x0b, 0x81, 0x41, 0x9d, 0x82, 0xac, 0x80, + 0x42, 0x84, 0x81, 0xc9, 0x81, 0x45, 0x2a, 0x84, + 0x60, 0x45, 0xf8, 0x81, 0x40, 0x84, 0x80, 0xc0, + 0x82, 0x89, 0x80, 0x42, 0x28, 0x81, 0x41, 0x26, + 0x81, 0x60, 0x4e, 0x05, 0x80, 0x5d, 0xe6, 0x83, +}; + +static const uint8_t unicode_prop_Unified_Ideograph_table[46] = { + 0x60, 0x33, 0xff, 0x59, 0xbf, 0xbf, 0x60, 0x51, + 0xff, 0x60, 0x5a, 0x0d, 0x08, 0x00, 0x81, 0x89, + 0x00, 0x00, 0x09, 0x82, 0x61, 0x05, 0xd5, 0x60, + 0xa6, 0xdf, 0x9f, 0x51, 0x1d, 0x81, 0x56, 0x8d, + 0x81, 0x5d, 0x30, 0x8e, 0x42, 0x6d, 0x51, 0xa1, + 0x53, 0x4a, 0x84, 0x60, 0x21, 0x29, +}; + +static const uint8_t unicode_prop_Variation_Selector_table[13] = { + 0x58, 0x0a, 0x10, 0x80, 0x60, 0xe5, 0xef, 0x8f, + 0x6d, 0x02, 0xef, 0x40, 0xef, +}; + +static const uint8_t unicode_prop_Bidi_Mirrored_table[173] = { + 0xa7, 0x81, 0x91, 0x00, 0x80, 0x9b, 0x00, 0x80, + 0x9c, 0x00, 0x80, 0xac, 0x80, 0x8e, 0x80, 0x4e, + 0x7d, 0x83, 0x47, 0x5c, 0x81, 0x49, 0x9b, 0x81, + 0x89, 0x81, 0xb5, 0x81, 0x8d, 0x81, 0x40, 0xb0, + 0x80, 0x40, 0xbf, 0x1a, 0x2a, 0x02, 0x0a, 0x18, + 0x18, 0x00, 0x03, 0x88, 0x20, 0x80, 0x91, 0x23, + 0x88, 0x08, 0x00, 0x38, 0x9f, 0x0b, 0x20, 0x88, + 0x09, 0x92, 0x21, 0x88, 0x21, 0x0b, 0x97, 0x81, + 0x8f, 0x3b, 0x93, 0x0e, 0x81, 0x44, 0x3c, 0x8d, + 0xc9, 0x01, 0x18, 0x08, 0x14, 0x1c, 0x12, 0x8d, + 0x41, 0x92, 0x95, 0x0d, 0x80, 0x8d, 0x38, 0x35, + 0x10, 0x1c, 0x01, 0x0c, 0x18, 0x02, 0x09, 0x89, + 0x29, 0x81, 0x8b, 0x92, 0x03, 0x08, 0x00, 0x08, + 0x03, 0x21, 0x2a, 0x97, 0x81, 0x8a, 0x0b, 0x18, + 0x09, 0x0b, 0xaa, 0x0f, 0x80, 0xa7, 0x20, 0x00, + 0x14, 0x22, 0x18, 0x14, 0x00, 0x40, 0xff, 0x80, + 0x42, 0x02, 0x1a, 0x08, 0x81, 0x8d, 0x09, 0x89, + 0xaa, 0x87, 0x41, 0xaa, 0x89, 0x0f, 0x60, 0xce, + 0x3c, 0x2c, 0x81, 0x40, 0xa1, 0x81, 0x91, 0x00, + 0x80, 0x9b, 0x00, 0x80, 0x9c, 0x00, 0x00, 0x08, + 0x81, 0x60, 0xd7, 0x76, 0x80, 0xb8, 0x80, 0xb8, + 0x80, 0xb8, 0x80, 0xb8, 0x80, +}; + +static const uint8_t unicode_prop_Emoji_table[239] = { + 0xa2, 0x05, 0x04, 0x89, 0xee, 0x03, 0x80, 0x5f, + 0x8c, 0x80, 0x8b, 0x80, 0x40, 0xd7, 0x80, 0x95, + 0x80, 0xd9, 0x85, 0x8e, 0x81, 0x41, 0x6e, 0x81, + 0x8b, 0x80, 0x40, 0xa5, 0x80, 0x98, 0x8a, 0x1a, + 0x40, 0xc6, 0x80, 0x40, 0xe6, 0x81, 0x89, 0x80, + 0x88, 0x80, 0xb9, 0x18, 0x84, 0x88, 0x01, 0x01, + 0x09, 0x03, 0x01, 0x00, 0x09, 0x02, 0x02, 0x0f, + 0x14, 0x00, 0x04, 0x8b, 0x8a, 0x09, 0x00, 0x08, + 0x80, 0x91, 0x01, 0x81, 0x91, 0x28, 0x00, 0x0a, + 0x0c, 0x01, 0x0b, 0x81, 0x8a, 0x0c, 0x09, 0x04, + 0x08, 0x00, 0x81, 0x93, 0x0c, 0x28, 0x19, 0x03, + 0x01, 0x01, 0x28, 0x01, 0x00, 0x00, 0x05, 0x02, + 0x05, 0x80, 0x89, 0x81, 0x8e, 0x01, 0x03, 0x00, + 0x03, 0x10, 0x80, 0x8a, 0x81, 0xaf, 0x82, 0x88, + 0x80, 0x8d, 0x80, 0x8d, 0x80, 0x41, 0x73, 0x81, + 0x41, 0xce, 0x82, 0x92, 0x81, 0xb2, 0x03, 0x80, + 0x44, 0xd9, 0x80, 0x8b, 0x80, 0x42, 0x58, 0x00, + 0x80, 0x61, 0xbd, 0x69, 0x80, 0x40, 0xc9, 0x80, + 0x40, 0x9f, 0x81, 0x8b, 0x81, 0x8d, 0x01, 0x89, + 0xca, 0x99, 0x01, 0x96, 0x80, 0x93, 0x01, 0x88, + 0x94, 0x81, 0x40, 0xad, 0xa1, 0x81, 0xef, 0x09, + 0x02, 0x81, 0xd2, 0x0a, 0x80, 0x41, 0x06, 0x80, + 0xbe, 0x8a, 0x28, 0x97, 0x31, 0x0f, 0x8b, 0x01, + 0x19, 0x03, 0x81, 0x8c, 0x09, 0x07, 0x81, 0x88, + 0x04, 0x82, 0x8b, 0x17, 0x11, 0x00, 0x03, 0x05, + 0x02, 0x05, 0xd5, 0xaf, 0xc5, 0x27, 0x0b, 0x82, + 0x89, 0x10, 0x01, 0x10, 0x81, 0x89, 0x40, 0xe2, + 0x8b, 0x18, 0x41, 0x1a, 0xae, 0x80, 0x89, 0x80, + 0x40, 0xb8, 0xef, 0x8c, 0x82, 0x8a, 0x82, 0xb8, + 0x00, 0x83, 0x8f, 0x81, 0x8b, 0x83, 0x89, +}; + +static const uint8_t unicode_prop_Emoji_Component_table[28] = { + 0xa2, 0x05, 0x04, 0x89, 0x5f, 0xd2, 0x80, 0x40, + 0xd4, 0x80, 0x60, 0xdd, 0x2a, 0x80, 0x60, 0xf3, + 0xd5, 0x99, 0x41, 0xfa, 0x84, 0x45, 0xaf, 0x83, + 0x6c, 0x06, 0x6b, 0xdf, +}; + +static const uint8_t unicode_prop_Emoji_Modifier_table[4] = { + 0x61, 0xf3, 0xfa, 0x84, +}; + +static const uint8_t unicode_prop_Emoji_Modifier_Base_table[71] = { + 0x60, 0x26, 0x1c, 0x80, 0x40, 0xda, 0x80, 0x8f, + 0x83, 0x61, 0xcc, 0x76, 0x80, 0xbb, 0x11, 0x01, + 0x82, 0xf4, 0x09, 0x8a, 0x94, 0x92, 0x10, 0x1a, + 0x02, 0x30, 0x00, 0x97, 0x80, 0x40, 0xc8, 0x0b, + 0x80, 0x94, 0x03, 0x81, 0x40, 0xad, 0x12, 0x84, + 0xd2, 0x80, 0x8f, 0x82, 0x88, 0x80, 0x8a, 0x80, + 0x42, 0x3e, 0x01, 0x07, 0x3d, 0x80, 0x88, 0x89, + 0x0a, 0xb7, 0x80, 0xbc, 0x08, 0x08, 0x80, 0x90, + 0x10, 0x8c, 0x40, 0xe4, 0x82, 0xa9, 0x88, +}; + +static const uint8_t unicode_prop_Emoji_Presentation_table[145] = { + 0x60, 0x23, 0x19, 0x81, 0x40, 0xcc, 0x1a, 0x01, + 0x80, 0x42, 0x08, 0x81, 0x94, 0x81, 0xb1, 0x8b, + 0xaa, 0x80, 0x92, 0x80, 0x8c, 0x07, 0x81, 0x90, + 0x0c, 0x0f, 0x04, 0x80, 0x94, 0x06, 0x08, 0x03, + 0x01, 0x06, 0x03, 0x81, 0x9b, 0x80, 0xa2, 0x00, + 0x03, 0x10, 0x80, 0xbc, 0x82, 0x97, 0x80, 0x8d, + 0x80, 0x43, 0x5a, 0x81, 0xb2, 0x03, 0x80, 0x61, + 0xc4, 0xad, 0x80, 0x40, 0xc9, 0x80, 0x40, 0xbd, + 0x01, 0x89, 0xca, 0x99, 0x00, 0x97, 0x80, 0x93, + 0x01, 0x20, 0x82, 0x94, 0x81, 0x40, 0xad, 0xa0, + 0x8b, 0x88, 0x80, 0xc5, 0x80, 0x95, 0x8b, 0xaa, + 0x1c, 0x8b, 0x90, 0x10, 0x82, 0xc6, 0x00, 0x80, + 0x40, 0xba, 0x81, 0xbe, 0x8c, 0x18, 0x97, 0x91, + 0x80, 0x99, 0x81, 0x8c, 0x80, 0xd5, 0xd4, 0xaf, + 0xc5, 0x28, 0x12, 0x0b, 0x13, 0x8a, 0x0e, 0x88, + 0x40, 0xe2, 0x8b, 0x18, 0x41, 0x1a, 0xae, 0x80, + 0x89, 0x80, 0x40, 0xb8, 0xef, 0x8c, 0x82, 0x8a, + 0x82, 0xb8, 0x00, 0x83, 0x8f, 0x81, 0x8b, 0x83, + 0x89, +}; + +static const uint8_t unicode_prop_Extended_Pictographic_table[254] = { + 0x40, 0xa8, 0x03, 0x80, 0x5f, 0x8c, 0x80, 0x8b, + 0x80, 0x40, 0xd7, 0x80, 0x95, 0x80, 0xd9, 0x85, + 0x8e, 0x81, 0x41, 0x6e, 0x81, 0x8b, 0x80, 0x40, + 0xa5, 0x80, 0x98, 0x8a, 0x1a, 0x40, 0xc6, 0x80, + 0x40, 0xe6, 0x81, 0x89, 0x80, 0x88, 0x80, 0xb9, + 0x18, 0x84, 0x88, 0x01, 0x01, 0x09, 0x03, 0x01, + 0x00, 0x09, 0x02, 0x02, 0x0f, 0x14, 0x00, 0x04, + 0x8b, 0x8a, 0x09, 0x00, 0x08, 0x80, 0x91, 0x01, + 0x81, 0x91, 0x28, 0x00, 0x0a, 0x0c, 0x01, 0x0b, + 0x81, 0x8a, 0x0c, 0x09, 0x04, 0x08, 0x00, 0x81, + 0x93, 0x0c, 0x28, 0x19, 0x03, 0x01, 0x01, 0x28, + 0x01, 0x00, 0x00, 0x05, 0x02, 0x05, 0x80, 0x89, + 0x81, 0x8e, 0x01, 0x03, 0x00, 0x03, 0x10, 0x80, + 0x8a, 0x81, 0xaf, 0x82, 0x88, 0x80, 0x8d, 0x80, + 0x8d, 0x80, 0x41, 0x73, 0x81, 0x41, 0xce, 0x82, + 0x92, 0x81, 0xb2, 0x03, 0x80, 0x44, 0xd9, 0x80, + 0x8b, 0x80, 0x42, 0x58, 0x00, 0x80, 0x61, 0xbd, + 0x69, 0x80, 0xa6, 0x83, 0xe3, 0x8b, 0x8e, 0x81, + 0x8e, 0x80, 0x8d, 0x81, 0xa4, 0x89, 0xef, 0x81, + 0x8b, 0x81, 0x8d, 0x01, 0x89, 0x92, 0xb7, 0x9a, + 0x8e, 0x89, 0x80, 0x93, 0x01, 0x88, 0x03, 0x88, + 0x96, 0x85, 0x40, 0xbb, 0x81, 0xef, 0x09, 0x02, + 0x81, 0xd2, 0x0a, 0x03, 0x84, 0x40, 0xfd, 0x80, + 0xbe, 0x8a, 0x28, 0x97, 0x31, 0x0f, 0x8b, 0x01, + 0x19, 0x03, 0x81, 0x8c, 0x09, 0x07, 0x81, 0x88, + 0x04, 0x82, 0x8b, 0x17, 0x11, 0x00, 0x03, 0x05, + 0x02, 0x05, 0xd5, 0xaf, 0xc5, 0x27, 0x81, 0x90, + 0x10, 0x05, 0x81, 0x8c, 0x40, 0xd9, 0xa5, 0x8b, + 0x83, 0xb7, 0x87, 0x89, 0x85, 0xa7, 0x87, 0x9d, + 0x81, 0x8b, 0x19, 0x8d, 0x88, 0xa6, 0x8b, 0xae, + 0x80, 0x89, 0x80, 0x40, 0xb8, 0xd7, 0x87, 0x8d, + 0x40, 0x91, 0x40, 0xff, 0x43, 0xfd, +}; + +static const uint8_t unicode_prop_Default_Ignorable_Code_Point_table[51] = { + 0x40, 0xac, 0x80, 0x42, 0xa0, 0x80, 0x42, 0xcb, + 0x80, 0x4b, 0x41, 0x81, 0x46, 0x52, 0x81, 0xd4, + 0x84, 0x47, 0xfa, 0x84, 0x99, 0x84, 0xb0, 0x8f, + 0x50, 0xf3, 0x80, 0x60, 0xcc, 0x9a, 0x8f, 0x40, + 0xee, 0x80, 0x40, 0x9f, 0x80, 0xce, 0x88, 0x60, + 0xbc, 0xa6, 0x83, 0x54, 0xce, 0x87, 0x6c, 0x2e, + 0x84, 0x4f, 0xff, +}; + +typedef enum { + UNICODE_PROP_Hyphen, + UNICODE_PROP_Other_Math, + UNICODE_PROP_Other_Alphabetic, + UNICODE_PROP_Other_Lowercase, + UNICODE_PROP_Other_Uppercase, + UNICODE_PROP_Other_Grapheme_Extend, + UNICODE_PROP_Other_Default_Ignorable_Code_Point, + UNICODE_PROP_Other_ID_Start, + UNICODE_PROP_Other_ID_Continue, + UNICODE_PROP_Prepended_Concatenation_Mark, + UNICODE_PROP_ID_Continue1, + UNICODE_PROP_XID_Start1, + UNICODE_PROP_XID_Continue1, + UNICODE_PROP_Changes_When_Titlecased1, + UNICODE_PROP_Changes_When_Casefolded1, + UNICODE_PROP_Changes_When_NFKC_Casefolded1, + UNICODE_PROP_Basic_Emoji1, + UNICODE_PROP_Basic_Emoji2, + UNICODE_PROP_RGI_Emoji_Flag_Sequence, + UNICODE_PROP_Emoji_Keycap_Sequence, + UNICODE_PROP_ASCII_Hex_Digit, + UNICODE_PROP_Bidi_Control, + UNICODE_PROP_Dash, + UNICODE_PROP_Deprecated, + UNICODE_PROP_Diacritic, + UNICODE_PROP_Extender, + UNICODE_PROP_Hex_Digit, + UNICODE_PROP_IDS_Unary_Operator, + UNICODE_PROP_IDS_Binary_Operator, + UNICODE_PROP_IDS_Trinary_Operator, + UNICODE_PROP_Ideographic, + UNICODE_PROP_Join_Control, + UNICODE_PROP_Logical_Order_Exception, + UNICODE_PROP_Modifier_Combining_Mark, + UNICODE_PROP_Noncharacter_Code_Point, + UNICODE_PROP_Pattern_Syntax, + UNICODE_PROP_Pattern_White_Space, + UNICODE_PROP_Quotation_Mark, + UNICODE_PROP_Radical, + UNICODE_PROP_Regional_Indicator, + UNICODE_PROP_Sentence_Terminal, + UNICODE_PROP_Soft_Dotted, + UNICODE_PROP_Terminal_Punctuation, + UNICODE_PROP_Unified_Ideograph, + UNICODE_PROP_Variation_Selector, + UNICODE_PROP_White_Space, + UNICODE_PROP_Bidi_Mirrored, + UNICODE_PROP_Emoji, + UNICODE_PROP_Emoji_Component, + UNICODE_PROP_Emoji_Modifier, + UNICODE_PROP_Emoji_Modifier_Base, + UNICODE_PROP_Emoji_Presentation, + UNICODE_PROP_Extended_Pictographic, + UNICODE_PROP_Default_Ignorable_Code_Point, + UNICODE_PROP_ID_Start, + UNICODE_PROP_Case_Ignorable, + UNICODE_PROP_ASCII, + UNICODE_PROP_Alphabetic, + UNICODE_PROP_Any, + UNICODE_PROP_Assigned, + UNICODE_PROP_Cased, + UNICODE_PROP_Changes_When_Casefolded, + UNICODE_PROP_Changes_When_Casemapped, + UNICODE_PROP_Changes_When_Lowercased, + UNICODE_PROP_Changes_When_NFKC_Casefolded, + UNICODE_PROP_Changes_When_Titlecased, + UNICODE_PROP_Changes_When_Uppercased, + UNICODE_PROP_Grapheme_Base, + UNICODE_PROP_Grapheme_Extend, + UNICODE_PROP_ID_Continue, + UNICODE_PROP_ID_Compat_Math_Start, + UNICODE_PROP_ID_Compat_Math_Continue, + UNICODE_PROP_Lowercase, + UNICODE_PROP_Math, + UNICODE_PROP_Uppercase, + UNICODE_PROP_XID_Continue, + UNICODE_PROP_XID_Start, + UNICODE_PROP_Cased1, + UNICODE_PROP_InCB, + UNICODE_PROP_COUNT, +} UnicodePropertyEnum; + +static const char unicode_prop_name_table[] = + "ASCII_Hex_Digit,AHex" "\0" + "Bidi_Control,Bidi_C" "\0" + "Dash" "\0" + "Deprecated,Dep" "\0" + "Diacritic,Dia" "\0" + "Extender,Ext" "\0" + "Hex_Digit,Hex" "\0" + "IDS_Unary_Operator,IDSU" "\0" + "IDS_Binary_Operator,IDSB" "\0" + "IDS_Trinary_Operator,IDST" "\0" + "Ideographic,Ideo" "\0" + "Join_Control,Join_C" "\0" + "Logical_Order_Exception,LOE" "\0" + "Modifier_Combining_Mark,MCM" "\0" + "Noncharacter_Code_Point,NChar" "\0" + "Pattern_Syntax,Pat_Syn" "\0" + "Pattern_White_Space,Pat_WS" "\0" + "Quotation_Mark,QMark" "\0" + "Radical" "\0" + "Regional_Indicator,RI" "\0" + "Sentence_Terminal,STerm" "\0" + "Soft_Dotted,SD" "\0" + "Terminal_Punctuation,Term" "\0" + "Unified_Ideograph,UIdeo" "\0" + "Variation_Selector,VS" "\0" + "White_Space,space" "\0" + "Bidi_Mirrored,Bidi_M" "\0" + "Emoji" "\0" + "Emoji_Component,EComp" "\0" + "Emoji_Modifier,EMod" "\0" + "Emoji_Modifier_Base,EBase" "\0" + "Emoji_Presentation,EPres" "\0" + "Extended_Pictographic,ExtPict" "\0" + "Default_Ignorable_Code_Point,DI" "\0" + "ID_Start,IDS" "\0" + "Case_Ignorable,CI" "\0" + "ASCII" "\0" + "Alphabetic,Alpha" "\0" + "Any" "\0" + "Assigned" "\0" + "Cased" "\0" + "Changes_When_Casefolded,CWCF" "\0" + "Changes_When_Casemapped,CWCM" "\0" + "Changes_When_Lowercased,CWL" "\0" + "Changes_When_NFKC_Casefolded,CWKCF" "\0" + "Changes_When_Titlecased,CWT" "\0" + "Changes_When_Uppercased,CWU" "\0" + "Grapheme_Base,Gr_Base" "\0" + "Grapheme_Extend,Gr_Ext" "\0" + "ID_Continue,IDC" "\0" + "ID_Compat_Math_Start" "\0" + "ID_Compat_Math_Continue" "\0" + "Lowercase,Lower" "\0" + "Math" "\0" + "Uppercase,Upper" "\0" + "XID_Continue,XIDC" "\0" + "XID_Start,XIDS" "\0" +; + +static const uint8_t * const unicode_prop_table[] = { + unicode_prop_Hyphen_table, + unicode_prop_Other_Math_table, + unicode_prop_Other_Alphabetic_table, + unicode_prop_Other_Lowercase_table, + unicode_prop_Other_Uppercase_table, + unicode_prop_Other_Grapheme_Extend_table, + unicode_prop_Other_Default_Ignorable_Code_Point_table, + unicode_prop_Other_ID_Start_table, + unicode_prop_Other_ID_Continue_table, + unicode_prop_Prepended_Concatenation_Mark_table, + unicode_prop_ID_Continue1_table, + unicode_prop_XID_Start1_table, + unicode_prop_XID_Continue1_table, + unicode_prop_Changes_When_Titlecased1_table, + unicode_prop_Changes_When_Casefolded1_table, + unicode_prop_Changes_When_NFKC_Casefolded1_table, + unicode_prop_Basic_Emoji1_table, + unicode_prop_Basic_Emoji2_table, + unicode_prop_RGI_Emoji_Flag_Sequence_table, + unicode_prop_Emoji_Keycap_Sequence_table, + unicode_prop_ASCII_Hex_Digit_table, + unicode_prop_Bidi_Control_table, + unicode_prop_Dash_table, + unicode_prop_Deprecated_table, + unicode_prop_Diacritic_table, + unicode_prop_Extender_table, + unicode_prop_Hex_Digit_table, + unicode_prop_IDS_Unary_Operator_table, + unicode_prop_IDS_Binary_Operator_table, + unicode_prop_IDS_Trinary_Operator_table, + unicode_prop_Ideographic_table, + unicode_prop_Join_Control_table, + unicode_prop_Logical_Order_Exception_table, + unicode_prop_Modifier_Combining_Mark_table, + unicode_prop_Noncharacter_Code_Point_table, + unicode_prop_Pattern_Syntax_table, + unicode_prop_Pattern_White_Space_table, + unicode_prop_Quotation_Mark_table, + unicode_prop_Radical_table, + unicode_prop_Regional_Indicator_table, + unicode_prop_Sentence_Terminal_table, + unicode_prop_Soft_Dotted_table, + unicode_prop_Terminal_Punctuation_table, + unicode_prop_Unified_Ideograph_table, + unicode_prop_Variation_Selector_table, + unicode_prop_White_Space_table, + unicode_prop_Bidi_Mirrored_table, + unicode_prop_Emoji_table, + unicode_prop_Emoji_Component_table, + unicode_prop_Emoji_Modifier_table, + unicode_prop_Emoji_Modifier_Base_table, + unicode_prop_Emoji_Presentation_table, + unicode_prop_Extended_Pictographic_table, + unicode_prop_Default_Ignorable_Code_Point_table, + unicode_prop_ID_Start_table, + unicode_prop_Case_Ignorable_table, +}; + +static const uint16_t unicode_prop_len_table[] = { + countof(unicode_prop_Hyphen_table), + countof(unicode_prop_Other_Math_table), + countof(unicode_prop_Other_Alphabetic_table), + countof(unicode_prop_Other_Lowercase_table), + countof(unicode_prop_Other_Uppercase_table), + countof(unicode_prop_Other_Grapheme_Extend_table), + countof(unicode_prop_Other_Default_Ignorable_Code_Point_table), + countof(unicode_prop_Other_ID_Start_table), + countof(unicode_prop_Other_ID_Continue_table), + countof(unicode_prop_Prepended_Concatenation_Mark_table), + countof(unicode_prop_ID_Continue1_table), + countof(unicode_prop_XID_Start1_table), + countof(unicode_prop_XID_Continue1_table), + countof(unicode_prop_Changes_When_Titlecased1_table), + countof(unicode_prop_Changes_When_Casefolded1_table), + countof(unicode_prop_Changes_When_NFKC_Casefolded1_table), + countof(unicode_prop_Basic_Emoji1_table), + countof(unicode_prop_Basic_Emoji2_table), + countof(unicode_prop_RGI_Emoji_Flag_Sequence_table), + countof(unicode_prop_Emoji_Keycap_Sequence_table), + countof(unicode_prop_ASCII_Hex_Digit_table), + countof(unicode_prop_Bidi_Control_table), + countof(unicode_prop_Dash_table), + countof(unicode_prop_Deprecated_table), + countof(unicode_prop_Diacritic_table), + countof(unicode_prop_Extender_table), + countof(unicode_prop_Hex_Digit_table), + countof(unicode_prop_IDS_Unary_Operator_table), + countof(unicode_prop_IDS_Binary_Operator_table), + countof(unicode_prop_IDS_Trinary_Operator_table), + countof(unicode_prop_Ideographic_table), + countof(unicode_prop_Join_Control_table), + countof(unicode_prop_Logical_Order_Exception_table), + countof(unicode_prop_Modifier_Combining_Mark_table), + countof(unicode_prop_Noncharacter_Code_Point_table), + countof(unicode_prop_Pattern_Syntax_table), + countof(unicode_prop_Pattern_White_Space_table), + countof(unicode_prop_Quotation_Mark_table), + countof(unicode_prop_Radical_table), + countof(unicode_prop_Regional_Indicator_table), + countof(unicode_prop_Sentence_Terminal_table), + countof(unicode_prop_Soft_Dotted_table), + countof(unicode_prop_Terminal_Punctuation_table), + countof(unicode_prop_Unified_Ideograph_table), + countof(unicode_prop_Variation_Selector_table), + countof(unicode_prop_White_Space_table), + countof(unicode_prop_Bidi_Mirrored_table), + countof(unicode_prop_Emoji_table), + countof(unicode_prop_Emoji_Component_table), + countof(unicode_prop_Emoji_Modifier_table), + countof(unicode_prop_Emoji_Modifier_Base_table), + countof(unicode_prop_Emoji_Presentation_table), + countof(unicode_prop_Extended_Pictographic_table), + countof(unicode_prop_Default_Ignorable_Code_Point_table), + countof(unicode_prop_ID_Start_table), + countof(unicode_prop_Case_Ignorable_table), +}; + +typedef enum { + UNICODE_SEQUENCE_PROP_Basic_Emoji, + UNICODE_SEQUENCE_PROP_Emoji_Keycap_Sequence, + UNICODE_SEQUENCE_PROP_RGI_Emoji_Modifier_Sequence, + UNICODE_SEQUENCE_PROP_RGI_Emoji_Flag_Sequence, + UNICODE_SEQUENCE_PROP_RGI_Emoji_Tag_Sequence, + UNICODE_SEQUENCE_PROP_RGI_Emoji_ZWJ_Sequence, + UNICODE_SEQUENCE_PROP_RGI_Emoji, + UNICODE_SEQUENCE_PROP_COUNT, +} UnicodeSequencePropertyEnum; + +static const char unicode_sequence_prop_name_table[] = + "Basic_Emoji" "\0" + "Emoji_Keycap_Sequence" "\0" + "RGI_Emoji_Modifier_Sequence" "\0" + "RGI_Emoji_Flag_Sequence" "\0" + "RGI_Emoji_Tag_Sequence" "\0" + "RGI_Emoji_ZWJ_Sequence" "\0" + "RGI_Emoji" "\0" +; + +static const uint8_t unicode_rgi_emoji_tag_sequence[18] = { + 0x67, 0x62, 0x65, 0x6e, 0x67, 0x00, 0x67, 0x62, + 0x73, 0x63, 0x74, 0x00, 0x67, 0x62, 0x77, 0x6c, + 0x73, 0x00, +}; + +static const uint8_t unicode_rgi_emoji_zwj_sequence[2392] = { + 0x02, 0xb8, 0x19, 0x40, 0x86, 0x02, 0xd1, 0x39, + 0xb0, 0x19, 0x02, 0x26, 0x39, 0x42, 0x86, 0x02, + 0xb4, 0x36, 0x42, 0x86, 0x03, 0x68, 0x54, 0x64, + 0x87, 0x68, 0x54, 0x02, 0xdc, 0x39, 0x42, 0x86, + 0x02, 0xd1, 0x39, 0x73, 0x13, 0x02, 0x39, 0x39, + 0x40, 0x86, 0x02, 0x69, 0x34, 0xbd, 0x19, 0x03, + 0xb6, 0x36, 0x40, 0x86, 0xa1, 0x87, 0x03, 0x68, + 0x74, 0x1d, 0x19, 0x68, 0x74, 0x03, 0x68, 0x34, + 0xbd, 0x19, 0xa1, 0x87, 0x02, 0xf1, 0x7a, 0xf2, + 0x7a, 0x02, 0xca, 0x33, 0x42, 0x86, 0x02, 0x69, + 0x34, 0xb0, 0x19, 0x04, 0x68, 0x14, 0x68, 0x14, + 0x67, 0x14, 0x66, 0x14, 0x02, 0xf9, 0x26, 0x42, + 0x86, 0x03, 0x69, 0x74, 0x1d, 0x19, 0x69, 0x74, + 0x03, 0xd1, 0x19, 0xbc, 0x19, 0xa1, 0x87, 0x02, + 0x3c, 0x19, 0x40, 0x86, 0x02, 0x68, 0x34, 0xeb, + 0x13, 0x02, 0xc3, 0x33, 0xa1, 0x87, 0x02, 0x70, + 0x34, 0x40, 0x86, 0x02, 0xd4, 0x39, 0x42, 0x86, + 0x02, 0xcf, 0x39, 0x42, 0x86, 0x03, 0xd1, 0x79, + 0xef, 0x1a, 0xd1, 0x79, 0x03, 0x68, 0x74, 0xef, + 0x1a, 0x68, 0x74, 0x03, 0x69, 0x74, 0xef, 0x1a, + 0x69, 0x74, 0x02, 0x47, 0x36, 0x40, 0x86, 0x03, + 0x68, 0x74, 0x30, 0x14, 0x68, 0x74, 0x02, 0x39, + 0x39, 0x42, 0x86, 0x04, 0xd1, 0x79, 0x64, 0x87, + 0x8b, 0x14, 0xd1, 0x79, 0x03, 0x69, 0x74, 0x30, + 0x14, 0x69, 0x74, 0x02, 0xd1, 0x39, 0x95, 0x86, + 0x02, 0x68, 0x34, 0x93, 0x13, 0x02, 0x69, 0x34, + 0xed, 0x13, 0x02, 0xda, 0x39, 0x40, 0x86, 0x03, + 0x69, 0x34, 0xaf, 0x19, 0xa1, 0x87, 0x02, 0xd1, + 0x39, 0x93, 0x13, 0x03, 0xce, 0x39, 0x42, 0x86, + 0xa1, 0x87, 0x03, 0xd1, 0x79, 0x64, 0x87, 0xd1, + 0x79, 0x03, 0xc3, 0x33, 0x42, 0x86, 0xa1, 0x87, + 0x03, 0x69, 0x74, 0x1d, 0x19, 0x68, 0x74, 0x02, + 0x69, 0x34, 0x92, 0x16, 0x02, 0xd1, 0x39, 0x96, + 0x86, 0x04, 0x69, 0x14, 0x64, 0x87, 0x8b, 0x14, + 0x68, 0x14, 0x02, 0x47, 0x36, 0x42, 0x86, 0x02, + 0x68, 0x34, 0x7c, 0x13, 0x02, 0x86, 0x34, 0x42, + 0x86, 0x02, 0xd1, 0x39, 0x7c, 0x13, 0x02, 0x69, + 0x14, 0xa4, 0x13, 0x02, 0xda, 0x39, 0x42, 0x86, + 0x02, 0x37, 0x39, 0x40, 0x86, 0x02, 0xd1, 0x39, + 0x08, 0x87, 0x04, 0x68, 0x54, 0x64, 0x87, 0x8b, + 0x14, 0x68, 0x54, 0x02, 0x4d, 0x36, 0x40, 0x86, + 0x02, 0x68, 0x34, 0x2c, 0x15, 0x02, 0x69, 0x34, + 0xaf, 0x19, 0x02, 0x6e, 0x34, 0x40, 0x86, 0x02, + 0xcd, 0x39, 0x42, 0x86, 0x02, 0xd1, 0x39, 0x2c, + 0x15, 0x02, 0x6f, 0x14, 0x40, 0x86, 0x03, 0xd1, + 0x39, 0xbc, 0x19, 0xa1, 0x87, 0x02, 0x68, 0x34, + 0xa8, 0x13, 0x02, 0x69, 0x34, 0x73, 0x13, 0x04, + 0x69, 0x54, 0x64, 0x87, 0x8b, 0x14, 0x68, 0x54, + 0x02, 0x71, 0x34, 0x42, 0x86, 0x02, 0x45, 0x36, + 0x40, 0x86, 0x02, 0xd1, 0x39, 0xa8, 0x13, 0x03, + 0x69, 0x54, 0x64, 0x87, 0x68, 0x54, 0x03, 0x69, + 0x54, 0x64, 0x87, 0x69, 0x54, 0x03, 0xce, 0x39, + 0x40, 0x86, 0xa1, 0x87, 0x02, 0xd8, 0x39, 0x40, + 0x86, 0x03, 0xc3, 0x33, 0x40, 0x86, 0xa1, 0x87, + 0x02, 0x4d, 0x36, 0x42, 0x86, 0x02, 0xd1, 0x19, + 0x92, 0x16, 0x02, 0xd1, 0x39, 0xeb, 0x13, 0x02, + 0x68, 0x34, 0xbc, 0x14, 0x02, 0xd1, 0x39, 0xbc, + 0x14, 0x02, 0x3d, 0x39, 0x40, 0x86, 0x02, 0xb8, + 0x39, 0x42, 0x86, 0x02, 0xa3, 0x36, 0x40, 0x86, + 0x02, 0x75, 0x35, 0x40, 0x86, 0x02, 0xd8, 0x39, + 0x42, 0x86, 0x02, 0x69, 0x34, 0x93, 0x13, 0x02, + 0x35, 0x39, 0x40, 0x86, 0x02, 0x4b, 0x36, 0x40, + 0x86, 0x02, 0x3d, 0x39, 0x42, 0x86, 0x02, 0x38, + 0x39, 0x42, 0x86, 0x02, 0xa3, 0x36, 0x42, 0x86, + 0x03, 0x69, 0x14, 0x67, 0x14, 0x67, 0x14, 0x02, + 0xb6, 0x36, 0x40, 0x86, 0x02, 0x69, 0x34, 0x7c, + 0x13, 0x02, 0x75, 0x35, 0x42, 0x86, 0x02, 0xcc, + 0x93, 0x40, 0x86, 0x02, 0x6f, 0x34, 0x42, 0x86, + 0x02, 0xcc, 0x33, 0x40, 0x86, 0x03, 0xd1, 0x39, + 0xbd, 0x19, 0xa1, 0x87, 0x02, 0x87, 0x34, 0x40, + 0x86, 0x02, 0x82, 0x34, 0x40, 0x86, 0x02, 0x69, + 0x14, 0x3e, 0x13, 0x02, 0xd6, 0x39, 0x40, 0x86, + 0x02, 0x68, 0x14, 0xbd, 0x19, 0x02, 0x4b, 0x36, + 0x42, 0x86, 0x02, 0x46, 0x36, 0x42, 0x86, 0x02, + 0x69, 0x34, 0x2c, 0x15, 0x03, 0xb6, 0x36, 0x42, + 0x86, 0xa1, 0x87, 0x02, 0xc4, 0x33, 0x40, 0x86, + 0x02, 0x26, 0x19, 0x40, 0x86, 0x02, 0x69, 0x14, + 0xb0, 0x19, 0x02, 0xde, 0x19, 0x42, 0x86, 0x02, + 0x69, 0x34, 0xa8, 0x13, 0x02, 0xcc, 0x33, 0x42, + 0x86, 0x02, 0x82, 0x34, 0x42, 0x86, 0x02, 0xd1, + 0x19, 0x93, 0x13, 0x02, 0x81, 0x14, 0x42, 0x86, + 0x03, 0xd1, 0x79, 0x30, 0x14, 0xd1, 0x79, 0x02, + 0x68, 0x34, 0xbb, 0x14, 0x02, 0x69, 0x34, 0x95, + 0x86, 0x02, 0xd1, 0x39, 0xbb, 0x14, 0x02, 0x69, + 0x34, 0xeb, 0x13, 0x02, 0xd1, 0x39, 0x84, 0x13, + 0x02, 0x69, 0x34, 0xbc, 0x14, 0x04, 0x69, 0x54, + 0x64, 0x87, 0x8b, 0x14, 0x69, 0x54, 0x02, 0x26, + 0x39, 0x40, 0x86, 0x02, 0xb4, 0x36, 0x40, 0x86, + 0x02, 0x47, 0x16, 0x42, 0x86, 0x02, 0xdc, 0x39, + 0x40, 0x86, 0x02, 0xca, 0x33, 0x40, 0x86, 0x02, + 0xf9, 0x26, 0x40, 0x86, 0x02, 0x69, 0x34, 0x08, + 0x87, 0x03, 0x69, 0x14, 0x69, 0x14, 0x66, 0x14, + 0x03, 0xd1, 0x59, 0x1d, 0x19, 0xd1, 0x59, 0x02, + 0xd4, 0x39, 0x40, 0x86, 0x02, 0xcf, 0x39, 0x40, + 0x86, 0x02, 0x68, 0x34, 0xa4, 0x13, 0x02, 0xd1, + 0x39, 0xa4, 0x13, 0x02, 0xd1, 0x19, 0xa8, 0x13, + 0x02, 0xd7, 0x39, 0x42, 0x86, 0x03, 0x69, 0x34, + 0xbc, 0x19, 0xa1, 0x87, 0x02, 0x68, 0x14, 0xb0, + 0x19, 0x02, 0x3c, 0x39, 0x42, 0x86, 0x02, 0x68, + 0x14, 0x73, 0x13, 0x04, 0x69, 0x14, 0x69, 0x14, + 0x66, 0x14, 0x66, 0x14, 0x03, 0x68, 0x34, 0xaf, + 0x19, 0xa1, 0x87, 0x02, 0x68, 0x34, 0x80, 0x16, + 0x02, 0x73, 0x34, 0x42, 0x86, 0x02, 0xd1, 0x39, + 0x80, 0x16, 0x02, 0x68, 0x34, 0xb0, 0x19, 0x02, + 0x86, 0x34, 0x40, 0x86, 0x02, 0x38, 0x19, 0x42, + 0x86, 0x02, 0x69, 0x34, 0xbb, 0x14, 0x02, 0xb5, + 0x36, 0x42, 0x86, 0x02, 0xcd, 0x39, 0x40, 0x86, + 0x02, 0x68, 0x34, 0x27, 0x15, 0x02, 0x68, 0x34, + 0x95, 0x86, 0x03, 0x68, 0x14, 0x68, 0x14, 0x66, + 0x14, 0x02, 0x71, 0x34, 0x40, 0x86, 0x02, 0xd1, + 0x39, 0x27, 0x15, 0x02, 0x2e, 0x16, 0xa8, 0x14, + 0x02, 0xc3, 0x33, 0x42, 0x86, 0x02, 0x69, 0x14, + 0x66, 0x14, 0x02, 0x68, 0x34, 0x96, 0x86, 0x02, + 0xd1, 0x39, 0x70, 0x1a, 0x03, 0x69, 0x14, 0x64, + 0x87, 0x68, 0x14, 0x02, 0x69, 0x34, 0xa4, 0x13, + 0x02, 0xb8, 0x39, 0x40, 0x86, 0x02, 0x68, 0x34, + 0x3e, 0x13, 0x03, 0xd1, 0x19, 0xaf, 0x19, 0xa1, + 0x87, 0x02, 0xd1, 0x39, 0x3e, 0x13, 0x02, 0x68, + 0x34, 0xbd, 0x19, 0x02, 0xd1, 0x19, 0xbb, 0x14, + 0x02, 0xd1, 0x19, 0x95, 0x86, 0x02, 0xdb, 0x39, + 0x42, 0x86, 0x02, 0x38, 0x39, 0x40, 0x86, 0x02, + 0x69, 0x34, 0x80, 0x16, 0x02, 0x69, 0x14, 0xeb, + 0x13, 0x04, 0x68, 0x14, 0x69, 0x14, 0x67, 0x14, + 0x67, 0x14, 0x02, 0x6f, 0x34, 0x40, 0x86, 0x02, + 0x77, 0x34, 0x42, 0x86, 0x02, 0x46, 0x36, 0x40, + 0x86, 0x02, 0x68, 0x34, 0x92, 0x16, 0x02, 0x4e, + 0x36, 0x42, 0x86, 0x03, 0x69, 0x14, 0xbd, 0x19, + 0xa1, 0x87, 0x02, 0xde, 0x19, 0x40, 0x86, 0x02, + 0x69, 0x34, 0x27, 0x15, 0x03, 0xc3, 0x13, 0x40, + 0x86, 0xa1, 0x87, 0x02, 0x81, 0x14, 0x40, 0x86, + 0x03, 0xd1, 0x39, 0xaf, 0x19, 0xa1, 0x87, 0x02, + 0x68, 0x34, 0xbc, 0x19, 0x02, 0xd1, 0x19, 0x80, + 0x16, 0x02, 0xd9, 0x39, 0x42, 0x86, 0x02, 0xd1, + 0x39, 0xbc, 0x19, 0x02, 0xdc, 0x19, 0x42, 0x86, + 0x02, 0x68, 0x34, 0x73, 0x13, 0x02, 0x69, 0x34, + 0x3e, 0x13, 0x02, 0x47, 0x16, 0x40, 0x86, 0x02, + 0xd1, 0x39, 0xbd, 0x19, 0x02, 0x3e, 0x39, 0x42, + 0x86, 0x02, 0x69, 0x14, 0x95, 0x86, 0x02, 0x68, + 0x14, 0x96, 0x86, 0x03, 0x69, 0x34, 0xbd, 0x19, + 0xa1, 0x87, 0x02, 0xd7, 0x39, 0x40, 0x86, 0x02, + 0x45, 0x16, 0x42, 0x86, 0x02, 0x68, 0x34, 0xed, + 0x13, 0x03, 0x68, 0x34, 0xbc, 0x19, 0xa1, 0x87, + 0x02, 0xd1, 0x39, 0xed, 0x13, 0x02, 0x3c, 0x39, + 0x40, 0x86, 0x02, 0xd1, 0x19, 0x70, 0x1a, 0x02, + 0xd1, 0x39, 0x92, 0x16, 0x02, 0x73, 0x34, 0x40, + 0x86, 0x02, 0x38, 0x19, 0x40, 0x86, 0x02, 0xb5, + 0x36, 0x40, 0x86, 0x02, 0x68, 0x34, 0xaf, 0x19, + 0x02, 0xd1, 0x39, 0xaf, 0x19, 0x02, 0x69, 0x34, + 0xbc, 0x19, 0x02, 0xb6, 0x16, 0x42, 0x86, 0x02, + 0x26, 0x14, 0x25, 0x15, 0x02, 0xc3, 0x33, 0x40, + 0x86, 0x02, 0xdd, 0x39, 0x42, 0x86, 0x02, 0xcb, + 0x93, 0x42, 0x86, 0x02, 0xcb, 0x33, 0x42, 0x86, + 0x02, 0x81, 0x34, 0x42, 0x86, 0x02, 0xce, 0x39, + 0xa1, 0x87, 0x02, 0xdb, 0x39, 0x40, 0x86, 0x02, + 0x68, 0x34, 0x08, 0x87, 0x02, 0xd1, 0x19, 0xb0, + 0x19, 0x02, 0x77, 0x34, 0x40, 0x86, 0x02, 0x4e, + 0x36, 0x40, 0x86, 0x02, 0xce, 0x39, 0x42, 0x86, + 0x02, 0x4e, 0x16, 0x42, 0x86, 0x02, 0xd9, 0x39, + 0x40, 0x86, 0x02, 0xdc, 0x19, 0x40, 0x86, 0x02, + 0x3e, 0x39, 0x40, 0x86, 0x02, 0xb9, 0x39, 0x42, + 0x86, 0x02, 0xda, 0x19, 0x42, 0x86, 0x02, 0x42, + 0x16, 0x94, 0x81, 0x02, 0x45, 0x16, 0x40, 0x86, + 0x02, 0x69, 0x14, 0xbd, 0x19, 0x02, 0x70, 0x34, + 0x42, 0x86, 0x02, 0xce, 0x19, 0xa1, 0x87, 0x02, + 0xc3, 0x13, 0x42, 0x86, 0x02, 0x68, 0x14, 0x08, + 0x87, 0x02, 0xd1, 0x19, 0x7c, 0x13, 0x02, 0x68, + 0x14, 0x92, 0x16, 0x02, 0xb6, 0x16, 0x40, 0x86, + 0x02, 0x37, 0x39, 0x42, 0x86, 0x03, 0xce, 0x19, + 0x42, 0x86, 0xa1, 0x87, 0x03, 0x68, 0x14, 0x67, + 0x14, 0x67, 0x14, 0x02, 0xdd, 0x39, 0x40, 0x86, + 0x02, 0xcf, 0x19, 0x42, 0x86, 0x02, 0xd1, 0x19, + 0x2c, 0x15, 0x02, 0x4b, 0x13, 0xe9, 0x17, 0x02, + 0x68, 0x14, 0x67, 0x14, 0x02, 0xcb, 0x93, 0x40, + 0x86, 0x02, 0x6e, 0x34, 0x42, 0x86, 0x02, 0xcb, + 0x33, 0x40, 0x86, 0x02, 0x81, 0x34, 0x40, 0x86, + 0x02, 0xb6, 0x36, 0xa1, 0x87, 0x02, 0x45, 0x36, + 0x42, 0x86, 0x02, 0xb4, 0x16, 0x42, 0x86, 0x02, + 0x69, 0x14, 0x73, 0x13, 0x04, 0x69, 0x14, 0x69, + 0x14, 0x67, 0x14, 0x66, 0x14, 0x02, 0x35, 0x39, + 0x42, 0x86, 0x02, 0x68, 0x14, 0x93, 0x13, 0x02, + 0xb6, 0x36, 0x42, 0x86, 0x03, 0x68, 0x14, 0x69, + 0x14, 0x66, 0x14, 0x02, 0xce, 0x39, 0x40, 0x86, + 0x02, 0x4e, 0x16, 0x40, 0x86, 0x02, 0x87, 0x34, + 0x42, 0x86, 0x02, 0x86, 0x14, 0x42, 0x86, 0x02, + 0xd6, 0x39, 0x42, 0x86, 0x02, 0xc4, 0x33, 0x42, + 0x86, 0x02, 0x69, 0x34, 0x96, 0x86, 0x02, 0xb9, + 0x39, 0x40, 0x86, 0x02, 0x68, 0x14, 0xa8, 0x13, + 0x02, 0xd1, 0x19, 0x84, 0x13, 0x02, 0xda, 0x19, + 0x40, 0x86, 0x02, 0xd8, 0x19, 0x42, 0x86, 0x02, + 0xc3, 0x13, 0x40, 0x86, 0x02, 0xb9, 0x19, 0x42, + 0x86, 0x02, 0x3d, 0x19, 0x42, 0x86, 0x02, 0xcf, + 0x19, 0x40, 0x86, 0x04, 0x68, 0x14, 0x68, 0x14, + 0x67, 0x14, 0x67, 0x14, 0x03, 0xd1, 0x19, 0xd1, + 0x19, 0xd2, 0x19, 0x02, 0x68, 0x14, 0xbb, 0x14, + 0x02, 0x3b, 0x14, 0x44, 0x87, 0x02, 0xd1, 0x19, + 0x27, 0x15, 0x02, 0xb4, 0x16, 0x40, 0x86, 0x02, + 0xcd, 0x19, 0x42, 0x86, 0x02, 0xd3, 0x86, 0xa5, + 0x14, 0x02, 0x70, 0x14, 0x42, 0x86, 0x03, 0xb6, + 0x16, 0x42, 0x86, 0xa1, 0x87, 0x04, 0x69, 0x14, + 0x64, 0x87, 0x8b, 0x14, 0x69, 0x14, 0x02, 0x36, + 0x16, 0x2b, 0x93, 0x02, 0x68, 0x14, 0x80, 0x16, + 0x02, 0x86, 0x14, 0x40, 0x86, 0x02, 0x08, 0x14, + 0x1b, 0x0b, 0x02, 0xd1, 0x19, 0xbc, 0x19, 0x02, + 0xca, 0x13, 0x42, 0x86, 0x02, 0x41, 0x94, 0xe8, + 0x95, 0x02, 0xd8, 0x19, 0x40, 0x86, 0x02, 0xb9, + 0x19, 0x40, 0x86, 0x02, 0xd1, 0x19, 0xed, 0x13, + 0x02, 0xf9, 0x86, 0x42, 0x86, 0x03, 0xd1, 0x19, + 0xbd, 0x19, 0xa1, 0x87, 0x02, 0x3d, 0x19, 0x40, + 0x86, 0x02, 0xd6, 0x19, 0x42, 0x86, 0x03, 0x69, + 0x14, 0x66, 0x14, 0x66, 0x14, 0x02, 0xd1, 0x19, + 0xaf, 0x19, 0x03, 0x69, 0x14, 0x69, 0x14, 0x67, + 0x14, 0x02, 0xcd, 0x19, 0x40, 0x86, 0x02, 0x70, + 0x14, 0x40, 0x86, 0x03, 0x68, 0x14, 0xbc, 0x19, + 0xa1, 0x87, 0x02, 0x6e, 0x14, 0x42, 0x86, 0x02, + 0x69, 0x14, 0x92, 0x16, 0x03, 0x68, 0x14, 0x68, + 0x14, 0x67, 0x14, 0x02, 0x69, 0x14, 0x67, 0x14, + 0x02, 0x75, 0x95, 0x42, 0x86, 0x03, 0x69, 0x14, + 0x64, 0x87, 0x69, 0x14, 0x02, 0xd1, 0x19, 0xbc, + 0x14, 0x02, 0xdf, 0x19, 0x42, 0x86, 0x02, 0xca, + 0x13, 0x40, 0x86, 0x02, 0x82, 0x14, 0x42, 0x86, + 0x02, 0x69, 0x14, 0x93, 0x13, 0x02, 0x68, 0x14, + 0x7c, 0x13, 0x02, 0xf9, 0x86, 0x40, 0x86, 0x02, + 0xd6, 0x19, 0x40, 0x86, 0x02, 0x68, 0x14, 0x2c, + 0x15, 0x02, 0x69, 0x14, 0xa8, 0x13, 0x02, 0xd4, + 0x19, 0x42, 0x86, 0x04, 0x68, 0x14, 0x69, 0x14, + 0x66, 0x14, 0x66, 0x14, 0x02, 0x77, 0x14, 0x42, + 0x86, 0x02, 0x39, 0x19, 0x42, 0x86, 0x02, 0xd1, + 0x19, 0xa4, 0x13, 0x02, 0x6e, 0x14, 0x40, 0x86, + 0x03, 0xd1, 0x19, 0xd2, 0x19, 0xd2, 0x19, 0x02, + 0x69, 0x14, 0xbb, 0x14, 0x02, 0xd1, 0x19, 0x96, + 0x86, 0x02, 0x75, 0x95, 0x40, 0x86, 0x04, 0x68, + 0x14, 0x64, 0x87, 0x8b, 0x14, 0x68, 0x14, 0x02, + 0xd1, 0x19, 0x3e, 0x13, 0x02, 0xdf, 0x19, 0x40, + 0x86, 0x02, 0x82, 0x14, 0x40, 0x86, 0x02, 0x44, + 0x13, 0xeb, 0x17, 0x02, 0xdd, 0x19, 0x42, 0x86, + 0x03, 0x68, 0x14, 0xaf, 0x19, 0xa1, 0x87, 0x02, + 0x69, 0x14, 0x80, 0x16, 0x02, 0xa3, 0x16, 0x42, + 0x86, 0x02, 0x69, 0x14, 0x96, 0x86, 0x02, 0x46, + 0x16, 0x42, 0x86, 0x02, 0xb6, 0x16, 0xa1, 0x87, + 0x02, 0x68, 0x14, 0x27, 0x15, 0x02, 0x26, 0x14, + 0x1b, 0x0b, 0x02, 0xd4, 0x19, 0x40, 0x86, 0x02, + 0x77, 0x14, 0x40, 0x86, 0x02, 0x39, 0x19, 0x40, + 0x86, 0x02, 0x37, 0x19, 0x42, 0x86, 0x03, 0x69, + 0x14, 0x67, 0x14, 0x66, 0x14, 0x03, 0xc3, 0x13, + 0x42, 0x86, 0xa1, 0x87, 0x02, 0x68, 0x14, 0xbc, + 0x19, 0x02, 0xd1, 0x19, 0xeb, 0x13, 0x04, 0x69, + 0x14, 0x69, 0x14, 0x67, 0x14, 0x67, 0x14, 0x02, + 0xd1, 0x19, 0x08, 0x87, 0x02, 0x68, 0x14, 0xed, + 0x13, 0x03, 0x69, 0x14, 0xbc, 0x19, 0xa1, 0x87, + 0x02, 0xdd, 0x19, 0x40, 0x86, 0x02, 0xc3, 0x13, + 0xa1, 0x87, 0x03, 0x68, 0x14, 0x66, 0x14, 0x66, + 0x14, 0x03, 0x68, 0x14, 0x69, 0x14, 0x67, 0x14, + 0x02, 0xa3, 0x16, 0x40, 0x86, 0x02, 0xdb, 0x19, + 0x42, 0x86, 0x02, 0x68, 0x14, 0xaf, 0x19, 0x02, + 0x46, 0x16, 0x40, 0x86, 0x02, 0x35, 0x16, 0xab, + 0x14, 0x02, 0x68, 0x14, 0x95, 0x86, 0x02, 0x42, + 0x16, 0x95, 0x81, 0x02, 0xc4, 0x13, 0x42, 0x86, + 0x02, 0x15, 0x14, 0xba, 0x19, 0x03, 0xd1, 0x19, + 0x1d, 0x19, 0xd1, 0x19, 0x02, 0x69, 0x14, 0x08, + 0x87, 0x02, 0x69, 0x14, 0x7c, 0x13, 0x02, 0x37, + 0x19, 0x40, 0x86, 0x02, 0x73, 0x14, 0x42, 0x86, + 0x02, 0x69, 0x14, 0x2c, 0x15, 0x02, 0xb5, 0x16, + 0x42, 0x86, 0x02, 0x35, 0x19, 0x42, 0x86, 0x04, + 0x68, 0x14, 0x69, 0x14, 0x67, 0x14, 0x66, 0x14, + 0x02, 0x64, 0x87, 0x25, 0x15, 0x02, 0x64, 0x87, + 0x79, 0x1a, 0x02, 0x68, 0x14, 0xbc, 0x14, 0x03, + 0xce, 0x19, 0x40, 0x86, 0xa1, 0x87, 0x02, 0x87, + 0x14, 0x42, 0x86, 0x02, 0x4d, 0x16, 0x42, 0x86, + 0x04, 0x68, 0x14, 0x68, 0x14, 0x66, 0x14, 0x66, + 0x14, 0x02, 0xdb, 0x19, 0x40, 0x86, 0x02, 0xd9, + 0x19, 0x42, 0x86, 0x02, 0xc4, 0x13, 0x40, 0x86, + 0x02, 0xd1, 0x19, 0xbd, 0x19, 0x02, 0x68, 0x14, + 0xa4, 0x13, 0x02, 0x3e, 0x19, 0x42, 0x86, 0x02, + 0xf3, 0x93, 0xa7, 0x86, 0x03, 0x69, 0x14, 0xaf, + 0x19, 0xa1, 0x87, 0x02, 0xf3, 0x93, 0x08, 0x13, + 0x02, 0xd1, 0x19, 0xd2, 0x19, 0x02, 0x73, 0x14, + 0x40, 0x86, 0x02, 0xb5, 0x16, 0x40, 0x86, 0x02, + 0x35, 0x19, 0x40, 0x86, 0x02, 0x69, 0x14, 0x27, + 0x15, 0x02, 0xce, 0x19, 0x42, 0x86, 0x02, 0x71, + 0x14, 0x42, 0x86, 0x02, 0xd1, 0x19, 0x73, 0x13, + 0x02, 0x68, 0x14, 0x3e, 0x13, 0x02, 0xf4, 0x13, + 0x20, 0x86, 0x02, 0x87, 0x14, 0x40, 0x86, 0x03, + 0xb6, 0x16, 0x40, 0x86, 0xa1, 0x87, 0x02, 0x4d, + 0x16, 0x40, 0x86, 0x02, 0x69, 0x14, 0xbc, 0x19, + 0x02, 0x4b, 0x16, 0x42, 0x86, 0x02, 0xd9, 0x19, + 0x40, 0x86, 0x02, 0x3e, 0x19, 0x40, 0x86, 0x02, + 0x69, 0x14, 0xed, 0x13, 0x02, 0xd7, 0x19, 0x42, + 0x86, 0x02, 0xb8, 0x19, 0x42, 0x86, 0x03, 0x68, + 0x14, 0x67, 0x14, 0x66, 0x14, 0x02, 0x3c, 0x19, + 0x42, 0x86, 0x02, 0x68, 0x14, 0x66, 0x14, 0x03, + 0x68, 0x14, 0x64, 0x87, 0x68, 0x14, 0x02, 0x69, + 0x14, 0xaf, 0x19, 0x02, 0xce, 0x19, 0x40, 0x86, + 0x02, 0x71, 0x14, 0x40, 0x86, 0x02, 0x68, 0x14, + 0xeb, 0x13, 0x03, 0x68, 0x14, 0xbd, 0x19, 0xa1, + 0x87, 0x02, 0x6f, 0x14, 0x42, 0x86, 0x04, 0xd1, + 0x19, 0xd1, 0x19, 0xd2, 0x19, 0xd2, 0x19, 0x02, + 0x69, 0x14, 0xbc, 0x14, 0x02, 0xcc, 0x93, 0x42, + 0x86, 0x02, 0x4b, 0x16, 0x40, 0x86, 0x02, 0x26, + 0x19, 0x42, 0x86, 0x02, 0xd7, 0x19, 0x40, 0x86, +}; + diff --git a/engine/quickjs-ng/libunicode.c b/engine/quickjs-ng/libunicode.c new file mode 100644 index 000000000..6d539331e --- /dev/null +++ b/engine/quickjs-ng/libunicode.c @@ -0,0 +1,2074 @@ +/* + * Unicode utilities + * + * Copyright (c) 2017-2018 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include +#include +#include + +#include "cutils.h" +#include "libunicode.h" +#include "libunicode-table.h" + +// note: stored as 4 bit tag, not much room left +enum { + RUN_TYPE_U, + RUN_TYPE_L, + RUN_TYPE_UF, + RUN_TYPE_LF, + RUN_TYPE_UL, + RUN_TYPE_LSU, + RUN_TYPE_U2L_399_EXT2, + RUN_TYPE_UF_D20, + RUN_TYPE_UF_D1_EXT, + RUN_TYPE_U_EXT, + RUN_TYPE_LF_EXT, + RUN_TYPE_UF_EXT2, + RUN_TYPE_LF_EXT2, + RUN_TYPE_UF_EXT3, +}; + +static int lre_case_conv1(uint32_t c, int conv_type) +{ + uint32_t res[LRE_CC_RES_LEN_MAX]; + lre_case_conv(res, c, conv_type); + return res[0]; +} + +/* case conversion using the table entry 'idx' with value 'v' */ +static int lre_case_conv_entry(uint32_t *res, uint32_t c, int conv_type, uint32_t idx, uint32_t v) +{ + uint32_t code, data, type, a, is_lower; + is_lower = (conv_type != 0); + type = (v >> (32 - 17 - 7 - 4)) & 0xf; + data = ((v & 0xf) << 8) | case_conv_table2[idx]; + code = v >> (32 - 17); + switch(type) { + case RUN_TYPE_U: + case RUN_TYPE_L: + case RUN_TYPE_UF: + case RUN_TYPE_LF: + if (conv_type == (type & 1) || + (type >= RUN_TYPE_UF && conv_type == 2)) { + c = c - code + (case_conv_table1[data] >> (32 - 17)); + } + break; + case RUN_TYPE_UL: + a = c - code; + if ((a & 1) != (1 - is_lower)) + break; + c = (a ^ 1) + code; + break; + case RUN_TYPE_LSU: + a = c - code; + if (a == 1) { + c += 2 * is_lower - 1; + } else if (a == (1 - is_lower) * 2) { + c += (2 * is_lower - 1) * 2; + } + break; + case RUN_TYPE_U2L_399_EXT2: + if (!is_lower) { + res[0] = c - code + case_conv_ext[data >> 6]; + res[1] = 0x399; + return 2; + } else { + c = c - code + case_conv_ext[data & 0x3f]; + } + break; + case RUN_TYPE_UF_D20: + if (conv_type == 1) + break; + c = data + (conv_type == 2) * 0x20; + break; + case RUN_TYPE_UF_D1_EXT: + if (conv_type == 1) + break; + c = case_conv_ext[data] + (conv_type == 2); + break; + case RUN_TYPE_U_EXT: + case RUN_TYPE_LF_EXT: + if (is_lower != (type - RUN_TYPE_U_EXT)) + break; + c = case_conv_ext[data]; + break; + case RUN_TYPE_LF_EXT2: + if (!is_lower) + break; + res[0] = c - code + case_conv_ext[data >> 6]; + res[1] = case_conv_ext[data & 0x3f]; + return 2; + case RUN_TYPE_UF_EXT2: + if (conv_type == 1) + break; + res[0] = c - code + case_conv_ext[data >> 6]; + res[1] = case_conv_ext[data & 0x3f]; + if (conv_type == 2) { + /* convert to lower */ + res[0] = lre_case_conv1(res[0], 1); + res[1] = lre_case_conv1(res[1], 1); + } + return 2; + default: + case RUN_TYPE_UF_EXT3: + if (conv_type == 1) + break; + res[0] = case_conv_ext[data >> 8]; + res[1] = case_conv_ext[(data >> 4) & 0xf]; + res[2] = case_conv_ext[data & 0xf]; + if (conv_type == 2) { + /* convert to lower */ + res[0] = lre_case_conv1(res[0], 1); + res[1] = lre_case_conv1(res[1], 1); + res[2] = lre_case_conv1(res[2], 1); + } + return 3; + } + res[0] = c; + return 1; +} + +/* conv_type: + 0 = to upper + 1 = to lower + 2 = case folding (= to lower with modifications) +*/ +int lre_case_conv(uint32_t *res, uint32_t c, int conv_type) +{ + if (c < 128) { + if (conv_type) { + if (c >= 'A' && c <= 'Z') { + c = c - 'A' + 'a'; + } + } else { + if (c >= 'a' && c <= 'z') { + c = c - 'a' + 'A'; + } + } + } else { + uint32_t v, code, len; + int idx, idx_min, idx_max; + + idx_min = 0; + idx_max = countof(case_conv_table1) - 1; + while (idx_min <= idx_max) { + idx = (unsigned)(idx_max + idx_min) / 2; + v = case_conv_table1[idx]; + code = v >> (32 - 17); + len = (v >> (32 - 17 - 7)) & 0x7f; + if (c < code) { + idx_max = idx - 1; + } else if (c >= code + len) { + idx_min = idx + 1; + } else { + return lre_case_conv_entry(res, c, conv_type, idx, v); + } + } + } + res[0] = c; + return 1; +} + +static int lre_case_folding_entry(uint32_t c, uint32_t idx, uint32_t v, bool is_unicode) +{ + uint32_t res[LRE_CC_RES_LEN_MAX]; + int len; + + if (is_unicode) { + len = lre_case_conv_entry(res, c, 2, idx, v); + if (len == 1) { + c = res[0]; + } else { + /* handle the few specific multi-character cases (see + unicode_gen.c:dump_case_folding_special_cases()) */ + if (c == 0xfb06) { + c = 0xfb05; + } else if (c == 0x01fd3) { + c = 0x390; + } else if (c == 0x01fe3) { + c = 0x3b0; + } + } + } else { + if (likely(c < 128)) { + if (c >= 'a' && c <= 'z') + c = c - 'a' + 'A'; + } else { + /* legacy regexp: to upper case if single char >= 128 */ + len = lre_case_conv_entry(res, c, false, idx, v); + if (len == 1 && res[0] >= 128) + c = res[0]; + } + } + return c; +} + +/* JS regexp specific rules for case folding */ +int lre_canonicalize(uint32_t c, bool is_unicode) +{ + if (c < 128) { + /* fast case */ + if (is_unicode) { + if (c >= 'A' && c <= 'Z') { + c = c - 'A' + 'a'; + } + } else { + if (c >= 'a' && c <= 'z') { + c = c - 'a' + 'A'; + } + } + } else { + uint32_t v, code, len; + int idx, idx_min, idx_max; + + idx_min = 0; + idx_max = countof(case_conv_table1) - 1; + while (idx_min <= idx_max) { + idx = (unsigned)(idx_max + idx_min) / 2; + v = case_conv_table1[idx]; + code = v >> (32 - 17); + len = (v >> (32 - 17 - 7)) & 0x7f; + if (c < code) { + idx_max = idx - 1; + } else if (c >= code + len) { + idx_min = idx + 1; + } else { + return lre_case_folding_entry(c, idx, v, is_unicode); + } + } + } + return c; +} + +static uint32_t get_le24(const uint8_t *ptr) +{ + return ptr[0] | (ptr[1] << 8) | (ptr[2] << 16); +} + +#define UNICODE_INDEX_BLOCK_LEN 32 + +/* return -1 if not in table, otherwise the offset in the block */ +static int get_index_pos(uint32_t *pcode, uint32_t c, + const uint8_t *index_table, int index_table_len) +{ + uint32_t code, v; + int idx_min, idx_max, idx; + + idx_min = 0; + v = get_le24(index_table); + code = v & ((1 << 21) - 1); + if (c < code) { + *pcode = 0; + return 0; + } + idx_max = index_table_len - 1; + code = get_le24(index_table + idx_max * 3); + if (c >= code) + return -1; + /* invariant: tab[idx_min] <= c < tab2[idx_max] */ + while ((idx_max - idx_min) > 1) { + idx = (idx_max + idx_min) / 2; + v = get_le24(index_table + idx * 3); + code = v & ((1 << 21) - 1); + if (c < code) { + idx_max = idx; + } else { + idx_min = idx; + } + } + v = get_le24(index_table + idx_min * 3); + *pcode = v & ((1 << 21) - 1); + return (idx_min + 1) * UNICODE_INDEX_BLOCK_LEN + (v >> 21); +} + +static bool lre_is_in_table(uint32_t c, const uint8_t *table, + const uint8_t *index_table, int index_table_len) +{ + uint32_t code, b, bit; + int pos; + const uint8_t *p; + + pos = get_index_pos(&code, c, index_table, index_table_len); + if (pos < 0) + return false; /* outside the table */ + p = table + pos; + bit = 0; + for(;;) { + b = *p++; + if (b < 64) { + code += (b >> 3) + 1; + if (c < code) + return bit; + bit ^= 1; + code += (b & 7) + 1; + } else if (b >= 0x80) { + code += b - 0x80 + 1; + } else if (b < 0x60) { + code += (((b - 0x40) << 8) | p[0]) + 1; + p++; + } else { + code += (((b - 0x60) << 16) | (p[0] << 8) | p[1]) + 1; + p += 2; + } + if (c < code) + return bit; + bit ^= 1; + } +} + +bool lre_is_cased(uint32_t c) +{ + uint32_t v, code, len; + int idx, idx_min, idx_max; + + idx_min = 0; + idx_max = countof(case_conv_table1) - 1; + while (idx_min <= idx_max) { + idx = (unsigned)(idx_max + idx_min) / 2; + v = case_conv_table1[idx]; + code = v >> (32 - 17); + len = (v >> (32 - 17 - 7)) & 0x7f; + if (c < code) { + idx_max = idx - 1; + } else if (c >= code + len) { + idx_min = idx + 1; + } else { + return true; + } + } + return lre_is_in_table(c, unicode_prop_Cased1_table, + unicode_prop_Cased1_index, + sizeof(unicode_prop_Cased1_index) / 3); +} + +bool lre_is_case_ignorable(uint32_t c) +{ + return lre_is_in_table(c, unicode_prop_Case_Ignorable_table, + unicode_prop_Case_Ignorable_index, + sizeof(unicode_prop_Case_Ignorable_index) / 3); +} + +/* character range */ + +static __maybe_unused void cr_dump(CharRange *cr) +{ + int i; + for(i = 0; i < cr->len; i++) + printf("%d: 0x%04x\n", i, cr->points[i]); +} + +static void *cr_default_realloc(void *opaque, void *ptr, size_t size) +{ + return realloc(ptr, size); +} + +void cr_init(CharRange *cr, void *mem_opaque, DynBufReallocFunc *realloc_func) +{ + cr->len = cr->size = 0; + cr->points = NULL; + cr->mem_opaque = mem_opaque; + cr->realloc_func = realloc_func ? realloc_func : cr_default_realloc; +} + +void cr_free(CharRange *cr) +{ + cr->realloc_func(cr->mem_opaque, cr->points, 0); +} + +int cr_realloc(CharRange *cr, int size) +{ + int new_size; + uint32_t *new_buf; + + if (size > cr->size) { + new_size = max_int(size, cr->size * 3 / 2); + new_buf = cr->realloc_func(cr->mem_opaque, cr->points, + new_size * sizeof(cr->points[0])); + if (!new_buf) + return -1; + cr->points = new_buf; + cr->size = new_size; + } + return 0; +} + +int cr_copy(CharRange *cr, const CharRange *cr1) +{ + if (cr_realloc(cr, cr1->len)) + return -1; + memcpy(cr->points, cr1->points, sizeof(cr->points[0]) * cr1->len); + cr->len = cr1->len; + return 0; +} + +/* merge consecutive intervals and remove empty intervals */ +static void cr_compress(CharRange *cr) +{ + int i, j, k, len; + uint32_t *pt; + + pt = cr->points; + len = cr->len; + i = 0; + j = 0; + k = 0; + while ((i + 1) < len) { + if (pt[i] == pt[i + 1]) { + /* empty interval */ + i += 2; + } else { + j = i; + while ((j + 3) < len && pt[j + 1] == pt[j + 2]) + j += 2; + /* just copy */ + pt[k] = pt[i]; + pt[k + 1] = pt[j + 1]; + k += 2; + i = j + 2; + } + } + cr->len = k; +} + +/* union or intersection */ +int cr_op(CharRange *cr, const uint32_t *a_pt, int a_len, + const uint32_t *b_pt, int b_len, int op) +{ + int a_idx, b_idx, is_in; + uint32_t v; + + a_idx = 0; + b_idx = 0; + for(;;) { + /* get one more point from a or b in increasing order */ + if (a_idx < a_len && b_idx < b_len) { + if (a_pt[a_idx] < b_pt[b_idx]) { + goto a_add; + } else if (a_pt[a_idx] == b_pt[b_idx]) { + v = a_pt[a_idx]; + a_idx++; + b_idx++; + } else { + goto b_add; + } + } else if (a_idx < a_len) { + a_add: + v = a_pt[a_idx++]; + } else if (b_idx < b_len) { + b_add: + v = b_pt[b_idx++]; + } else { + break; + } + /* add the point if the in/out status changes */ + switch(op) { + case CR_OP_UNION: + is_in = (a_idx & 1) | (b_idx & 1); + break; + case CR_OP_INTER: + is_in = (a_idx & 1) & (b_idx & 1); + break; + case CR_OP_XOR: + is_in = (a_idx & 1) ^ (b_idx & 1); + break; + case CR_OP_SUB: + is_in = (a_idx & 1) & ((b_idx & 1) ^ 1); + break; + default: + abort(); + } + if (is_in != (cr->len & 1)) { + if (cr_add_point(cr, v)) + return -1; + } + } + cr_compress(cr); + return 0; +} + +int cr_op1(CharRange *cr, const uint32_t *b_pt, int b_len, int op) +{ + CharRange a = *cr; + int ret; + cr->len = 0; + cr->size = 0; + cr->points = NULL; + ret = cr_op(cr, a.points, a.len, b_pt, b_len, op); + cr_free(&a); + return ret; +} + +int cr_invert(CharRange *cr) +{ + int len; + len = cr->len; + if (cr_realloc(cr, len + 2)) + return -1; + memmove(cr->points + 1, cr->points, len * sizeof(cr->points[0])); + cr->points[0] = 0; + cr->points[len + 1] = UINT32_MAX; + cr->len = len + 2; + cr_compress(cr); + return 0; +} + +bool lre_is_id_start(uint32_t c) +{ + return lre_is_in_table(c, unicode_prop_ID_Start_table, + unicode_prop_ID_Start_index, + sizeof(unicode_prop_ID_Start_index) / 3); +} + +bool lre_is_id_continue(uint32_t c) +{ + return lre_is_id_start(c) || + lre_is_in_table(c, unicode_prop_ID_Continue1_table, + unicode_prop_ID_Continue1_index, + sizeof(unicode_prop_ID_Continue1_index) / 3); +} + +bool lre_is_white_space(uint32_t c) +{ + return lre_is_in_table(c, unicode_prop_White_Space_table, + unicode_prop_White_Space_index, + sizeof(unicode_prop_White_Space_index) / 3); +} + +/*---- lre codepoint categorizing functions ----*/ + +#define S UNICODE_C_SPACE +#define D UNICODE_C_DIGIT +#define X UNICODE_C_XDIGIT +#define U UNICODE_C_UPPER +#define L UNICODE_C_LOWER +#define _ UNICODE_C_UNDER +#define d UNICODE_C_DOLLAR + +uint8_t const lre_ctype_bits[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, S, S, S, S, S, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + + S, 0, 0, 0, d, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + X|D, X|D, X|D, X|D, X|D, X|D, X|D, X|D, + X|D, X|D, 0, 0, 0, 0, 0, 0, + + 0, X|U, X|U, X|U, X|U, X|U, X|U, U, + U, U, U, U, U, U, U, U, + U, U, U, U, U, U, U, U, + U, U, U, 0, 0, 0, 0, _, + + 0, X|L, X|L, X|L, X|L, X|L, X|L, L, + L, L, L, L, L, L, L, L, + L, L, L, L, L, L, L, L, + L, L, L, 0, 0, 0, 0, 0, + + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + + S, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, +}; + +#undef S +#undef D +#undef X +#undef U +#undef L +#undef _ +#undef d + +/* code point ranges for Zs,Zl or Zp property */ +static const uint16_t char_range_space[] = { + 10, + 0x0009, 0x000D + 1, + 0x0020, 0x0020 + 1, + 0x00A0, 0x00A0 + 1, + 0x1680, 0x1680 + 1, + 0x2000, 0x200A + 1, + /* 2028;LINE SEPARATOR;Zl;0;WS;;;;;N;;;;; */ + /* 2029;PARAGRAPH SEPARATOR;Zp;0;B;;;;;N;;;;; */ + 0x2028, 0x2029 + 1, + 0x202F, 0x202F + 1, + 0x205F, 0x205F + 1, + 0x3000, 0x3000 + 1, + /* FEFF;ZERO WIDTH NO-BREAK SPACE;Cf;0;BN;;;;;N;BYTE ORDER MARK;;;; */ + 0xFEFF, 0xFEFF + 1, +}; + +int lre_is_space_non_ascii(uint32_t c) +{ + size_t i, n; + + n = countof(char_range_space); + for(i = 5; i < n; i += 2) { + uint32_t low = char_range_space[i]; + uint32_t high = char_range_space[i + 1]; + if (c < low) + return false; + if (c < high) + return true; + } + return false; +} + + +#define UNICODE_DECOMP_LEN_MAX 18 + +typedef enum { + DECOMP_TYPE_C1, /* 16 bit char */ + DECOMP_TYPE_L1, /* 16 bit char table */ + DECOMP_TYPE_L2, + DECOMP_TYPE_L3, + DECOMP_TYPE_L4, + DECOMP_TYPE_L5, /* XXX: not used */ + DECOMP_TYPE_L6, /* XXX: could remove */ + DECOMP_TYPE_L7, /* XXX: could remove */ + DECOMP_TYPE_LL1, /* 18 bit char table */ + DECOMP_TYPE_LL2, + DECOMP_TYPE_S1, /* 8 bit char table */ + DECOMP_TYPE_S2, + DECOMP_TYPE_S3, + DECOMP_TYPE_S4, + DECOMP_TYPE_S5, + DECOMP_TYPE_I1, /* increment 16 bit char value */ + DECOMP_TYPE_I2_0, + DECOMP_TYPE_I2_1, + DECOMP_TYPE_I3_1, + DECOMP_TYPE_I3_2, + DECOMP_TYPE_I4_1, + DECOMP_TYPE_I4_2, + DECOMP_TYPE_B1, /* 16 bit base + 8 bit offset */ + DECOMP_TYPE_B2, + DECOMP_TYPE_B3, + DECOMP_TYPE_B4, + DECOMP_TYPE_B5, + DECOMP_TYPE_B6, + DECOMP_TYPE_B7, + DECOMP_TYPE_B8, + DECOMP_TYPE_B18, + DECOMP_TYPE_LS2, + DECOMP_TYPE_PAT3, + DECOMP_TYPE_S2_UL, + DECOMP_TYPE_LS2_UL, +} DecompTypeEnum; + +static uint32_t unicode_get_short_code(uint32_t c) +{ + static const uint16_t unicode_short_table[2] = { 0x2044, 0x2215 }; + + if (c < 0x80) + return c; + else if (c < 0x80 + 0x50) + return c - 0x80 + 0x300; + else + return unicode_short_table[c - 0x80 - 0x50]; +} + +static uint32_t unicode_get_lower_simple(uint32_t c) +{ + if (c < 0x100 || (c >= 0x410 && c <= 0x42f)) + c += 0x20; + else + c++; + return c; +} + +static uint16_t unicode_get16(const uint8_t *p) +{ + return p[0] | (p[1] << 8); +} + +static int unicode_decomp_entry(uint32_t *res, uint32_t c, + int idx, uint32_t code, uint32_t len, + uint32_t type) +{ + uint32_t c1; + int l, i, p; + const uint8_t *d; + + if (type == DECOMP_TYPE_C1) { + res[0] = unicode_decomp_table2[idx]; + return 1; + } else { + d = unicode_decomp_data + unicode_decomp_table2[idx]; + switch(type) { + case DECOMP_TYPE_L1: + case DECOMP_TYPE_L2: + case DECOMP_TYPE_L3: + case DECOMP_TYPE_L4: + case DECOMP_TYPE_L5: + case DECOMP_TYPE_L6: + case DECOMP_TYPE_L7: + l = type - DECOMP_TYPE_L1 + 1; + d += (c - code) * l * 2; + for(i = 0; i < l; i++) { + if ((res[i] = unicode_get16(d + 2 * i)) == 0) + return 0; + } + return l; + case DECOMP_TYPE_LL1: + case DECOMP_TYPE_LL2: + { + uint32_t k, p; + l = type - DECOMP_TYPE_LL1 + 1; + k = (c - code) * l; + p = len * l * 2; + for(i = 0; i < l; i++) { + c1 = unicode_get16(d + 2 * k) | + (((d[p + (k / 4)] >> ((k % 4) * 2)) & 3) << 16); + if (!c1) + return 0; + res[i] = c1; + k++; + } + } + return l; + case DECOMP_TYPE_S1: + case DECOMP_TYPE_S2: + case DECOMP_TYPE_S3: + case DECOMP_TYPE_S4: + case DECOMP_TYPE_S5: + l = type - DECOMP_TYPE_S1 + 1; + d += (c - code) * l; + for(i = 0; i < l; i++) { + if ((res[i] = unicode_get_short_code(d[i])) == 0) + return 0; + } + return l; + case DECOMP_TYPE_I1: + l = 1; + p = 0; + goto decomp_type_i; + case DECOMP_TYPE_I2_0: + case DECOMP_TYPE_I2_1: + case DECOMP_TYPE_I3_1: + case DECOMP_TYPE_I3_2: + case DECOMP_TYPE_I4_1: + case DECOMP_TYPE_I4_2: + l = 2 + ((type - DECOMP_TYPE_I2_0) >> 1); + p = ((type - DECOMP_TYPE_I2_0) & 1) + (l > 2); + decomp_type_i: + for(i = 0; i < l; i++) { + c1 = unicode_get16(d + 2 * i); + if (i == p) + c1 += c - code; + res[i] = c1; + } + return l; + case DECOMP_TYPE_B18: + l = 18; + goto decomp_type_b; + case DECOMP_TYPE_B1: + case DECOMP_TYPE_B2: + case DECOMP_TYPE_B3: + case DECOMP_TYPE_B4: + case DECOMP_TYPE_B5: + case DECOMP_TYPE_B6: + case DECOMP_TYPE_B7: + case DECOMP_TYPE_B8: + l = type - DECOMP_TYPE_B1 + 1; + decomp_type_b: + { + uint32_t c_min; + c_min = unicode_get16(d); + d += 2 + (c - code) * l; + for(i = 0; i < l; i++) { + c1 = d[i]; + if (c1 == 0xff) + c1 = 0x20; + else + c1 += c_min; + res[i] = c1; + } + } + return l; + case DECOMP_TYPE_LS2: + d += (c - code) * 3; + if (!(res[0] = unicode_get16(d))) + return 0; + res[1] = unicode_get_short_code(d[2]); + return 2; + case DECOMP_TYPE_PAT3: + res[0] = unicode_get16(d); + res[2] = unicode_get16(d + 2); + d += 4 + (c - code) * 2; + res[1] = unicode_get16(d); + return 3; + case DECOMP_TYPE_S2_UL: + case DECOMP_TYPE_LS2_UL: + c1 = c - code; + if (type == DECOMP_TYPE_S2_UL) { + d += c1 & ~1; + c = unicode_get_short_code(*d); + d++; + } else { + d += (c1 >> 1) * 3; + c = unicode_get16(d); + d += 2; + } + if (c1 & 1) + c = unicode_get_lower_simple(c); + res[0] = c; + res[1] = unicode_get_short_code(*d); + return 2; + } + } + return 0; +} + + +/* return the length of the decomposition (length <= + UNICODE_DECOMP_LEN_MAX) or 0 if no decomposition */ +static int unicode_decomp_char(uint32_t *res, uint32_t c, bool is_compat1) +{ + uint32_t v, type, is_compat, code, len; + int idx_min, idx_max, idx; + + idx_min = 0; + idx_max = countof(unicode_decomp_table1) - 1; + while (idx_min <= idx_max) { + idx = (idx_max + idx_min) / 2; + v = unicode_decomp_table1[idx]; + code = v >> (32 - 18); + len = (v >> (32 - 18 - 7)) & 0x7f; + // printf("idx=%d code=%05x len=%d\n", idx, code, len); + if (c < code) { + idx_max = idx - 1; + } else if (c >= code + len) { + idx_min = idx + 1; + } else { + is_compat = v & 1; + if (is_compat1 < is_compat) + break; + type = (v >> (32 - 18 - 7 - 6)) & 0x3f; + return unicode_decomp_entry(res, c, idx, code, len, type); + } + } + return 0; +} + +/* return 0 if no pair found */ +static int unicode_compose_pair(uint32_t c0, uint32_t c1) +{ + uint32_t code, len, type, v, idx1, d_idx, d_offset, ch; + int idx_min, idx_max, idx, d; + uint32_t pair[2]; + + idx_min = 0; + idx_max = countof(unicode_comp_table) - 1; + while (idx_min <= idx_max) { + idx = (idx_max + idx_min) / 2; + idx1 = unicode_comp_table[idx]; + + /* idx1 represent an entry of the decomposition table */ + d_idx = idx1 >> 6; + d_offset = idx1 & 0x3f; + v = unicode_decomp_table1[d_idx]; + code = v >> (32 - 18); + len = (v >> (32 - 18 - 7)) & 0x7f; + type = (v >> (32 - 18 - 7 - 6)) & 0x3f; + ch = code + d_offset; + unicode_decomp_entry(pair, ch, d_idx, code, len, type); + d = c0 - pair[0]; + if (d == 0) + d = c1 - pair[1]; + if (d < 0) { + idx_max = idx - 1; + } else if (d > 0) { + idx_min = idx + 1; + } else { + return ch; + } + } + return 0; +} + +/* return the combining class of character c (between 0 and 255) */ +static int unicode_get_cc(uint32_t c) +{ + uint32_t code, n, type, cc, c1, b; + int pos; + const uint8_t *p; + + pos = get_index_pos(&code, c, + unicode_cc_index, sizeof(unicode_cc_index) / 3); + if (pos < 0) + return 0; + p = unicode_cc_table + pos; + for(;;) { + b = *p++; + type = b >> 6; + n = b & 0x3f; + if (n < 48) { + } else if (n < 56) { + n = (n - 48) << 8; + n |= *p++; + n += 48; + } else { + n = (n - 56) << 8; + n |= *p++ << 8; + n |= *p++; + n += 48 + (1 << 11); + } + if (type <= 1) + p++; + c1 = code + n + 1; + if (c < c1) { + switch(type) { + case 0: + cc = p[-1]; + break; + case 1: + cc = p[-1] + c - code; + break; + case 2: + cc = 0; + break; + default: + case 3: + cc = 230; + break; + } + return cc; + } + code = c1; + } +} + +static void sort_cc(int *buf, int len) +{ + int i, j, k, cc, cc1, start, ch1; + + for(i = 0; i < len; i++) { + cc = unicode_get_cc(buf[i]); + if (cc != 0) { + start = i; + j = i + 1; + while (j < len) { + ch1 = buf[j]; + cc1 = unicode_get_cc(ch1); + if (cc1 == 0) + break; + k = j - 1; + while (k >= start) { + if (unicode_get_cc(buf[k]) <= cc1) + break; + buf[k + 1] = buf[k]; + k--; + } + buf[k + 1] = ch1; + j++; + } + i = j; + } + } +} + +static void to_nfd_rec(DynBuf *dbuf, + const int *src, int src_len, int is_compat) +{ + uint32_t c, v; + int i, l; + uint32_t res[UNICODE_DECOMP_LEN_MAX]; + + for(i = 0; i < src_len; i++) { + c = src[i]; + if (c >= 0xac00 && c < 0xd7a4) { + /* Hangul decomposition */ + c -= 0xac00; + dbuf_put_u32(dbuf, 0x1100 + c / 588); + dbuf_put_u32(dbuf, 0x1161 + (c % 588) / 28); + v = c % 28; + if (v != 0) + dbuf_put_u32(dbuf, 0x11a7 + v); + } else { + l = unicode_decomp_char(res, c, is_compat); + if (l) { + to_nfd_rec(dbuf, (int *)res, l, is_compat); + } else { + dbuf_put_u32(dbuf, c); + } + } + } +} + +/* return 0 if not found */ +static int compose_pair(uint32_t c0, uint32_t c1) +{ + /* Hangul composition */ + if (c0 >= 0x1100 && c0 < 0x1100 + 19 && + c1 >= 0x1161 && c1 < 0x1161 + 21) { + return 0xac00 + (c0 - 0x1100) * 588 + (c1 - 0x1161) * 28; + } else if (c0 >= 0xac00 && c0 < 0xac00 + 11172 && + (c0 - 0xac00) % 28 == 0 && + c1 >= 0x11a7 && c1 < 0x11a7 + 28) { + return c0 + c1 - 0x11a7; + } else { + return unicode_compose_pair(c0, c1); + } +} + +int unicode_normalize(uint32_t **pdst, const uint32_t *src, int src_len, + UnicodeNormalizationEnum n_type, + void *opaque, DynBufReallocFunc *realloc_func) +{ + int *buf, buf_len, i, p, starter_pos, cc, last_cc, out_len; + bool is_compat; + DynBuf dbuf_s, *dbuf = &dbuf_s; + + if (src_len == 0) { + *pdst = NULL; + return 0; + } + + is_compat = n_type >> 1; + + dbuf_init2(dbuf, opaque, realloc_func); + if (dbuf_claim(dbuf, sizeof(int) * src_len)) + goto fail; + + /* common case: latin1 is unaffected by NFC */ + if (n_type == UNICODE_NFC) { + for(i = 0; i < src_len; i++) { + if (src[i] >= 0x100) + goto not_latin1; + } + buf = (int *)dbuf->buf; + memcpy(buf, src, src_len * sizeof(int)); + *pdst = (uint32_t *)buf; + return src_len; + not_latin1: ; + } + + to_nfd_rec(dbuf, (const int *)src, src_len, is_compat); + if (dbuf_error(dbuf)) { + fail: + *pdst = NULL; + return -1; + } + buf = (int *)dbuf->buf; + buf_len = dbuf->size / sizeof(int); + + sort_cc(buf, buf_len); + + if (buf_len <= 1 || (n_type & 1) != 0) { + /* NFD / NFKD */ + *pdst = (uint32_t *)buf; + return buf_len; + } + + i = 1; + out_len = 1; + while (i < buf_len) { + /* find the starter character and test if it is blocked from + the character at 'i' */ + last_cc = unicode_get_cc(buf[i]); + starter_pos = out_len - 1; + while (starter_pos >= 0) { + cc = unicode_get_cc(buf[starter_pos]); + if (cc == 0) + break; + if (cc >= last_cc) + goto next; + last_cc = 256; + starter_pos--; + } + if (starter_pos >= 0 && + (p = compose_pair(buf[starter_pos], buf[i])) != 0) { + buf[starter_pos] = p; + i++; + } else { + next: + buf[out_len++] = buf[i++]; + } + } + *pdst = (uint32_t *)buf; + return out_len; +} + +/* char ranges for various unicode properties */ + +static int unicode_find_name(const char *name_table, const char *name) +{ + const char *p, *r; + int pos; + size_t name_len, len; + + p = name_table; + pos = 0; + name_len = strlen(name); + while (*p) { + for(;;) { + r = strchr(p, ','); + if (!r) + len = strlen(p); + else + len = r - p; + if (len == name_len && !memcmp(p, name, name_len)) + return pos; + p += len + 1; + if (!r) + break; + } + pos++; + } + return -1; +} + +/* 'cr' must be initialized and empty. Return 0 if OK, -1 if error, -2 + if not found */ +int unicode_script(CharRange *cr, + const char *script_name, bool is_ext) +{ + int script_idx; + const uint8_t *p, *p_end; + uint32_t c, c1, b, n, v, v_len, i, type; + CharRange cr1_s = { 0 }, *cr1 = NULL; + CharRange cr2_s = { 0 }, *cr2 = &cr2_s; + bool is_common; + + if (!strcmp(script_name, "Unknown") || !strcmp(script_name, "Zzzz")) { + /* "Unknown" (ISO 15924 code "Zzzz") is excluded from the name table; + it matches every character outside any script. */ + script_idx = UNICODE_SCRIPT_Unknown; + } else { + script_idx = unicode_find_name(unicode_script_name_table, script_name); + if (script_idx < 0) + return -2; + /* Note: the name table excludes the "Unknown" Script */ + script_idx += UNICODE_SCRIPT_Unknown + 1; + } + + is_common = (script_idx == UNICODE_SCRIPT_Common || + script_idx == UNICODE_SCRIPT_Inherited); + if (is_ext) { + cr1 = &cr1_s; + cr_init(cr1, cr->mem_opaque, cr->realloc_func); + cr_init(cr2, cr->mem_opaque, cr->realloc_func); + } else { + cr1 = cr; + } + + p = unicode_script_table; + p_end = unicode_script_table + countof(unicode_script_table); + c = 0; + while (p < p_end) { + b = *p++; + type = b >> 7; + n = b & 0x7f; + if (n < 96) { + } else if (n < 112) { + n = (n - 96) << 8; + n |= *p++; + n += 96; + } else { + n = (n - 112) << 16; + n |= *p++ << 8; + n |= *p++; + n += 96 + (1 << 12); + } + if (type == 0) + v = 0; + else + v = *p++; + c1 = c + n + 1; + /* only script-bearing ranges (type != 0); for Unknown, match every + such range and invert afterwards */ + if (type != 0 && + (v == script_idx || script_idx == UNICODE_SCRIPT_Unknown)) { + if (cr_add_interval(cr1, c, c1)) + goto fail; + } + c = c1; + } + + if (script_idx == UNICODE_SCRIPT_Unknown) { + /* Unknown is all the characters outside scripts */ + if (cr_invert(cr1)) + goto fail; + } + + if (is_ext) { + /* add the script extensions */ + p = unicode_script_ext_table; + p_end = unicode_script_ext_table + countof(unicode_script_ext_table); + c = 0; + while (p < p_end) { + b = *p++; + if (b < 128) { + n = b; + } else if (b < 128 + 64) { + n = (b - 128) << 8; + n |= *p++; + n += 128; + } else { + n = (b - 128 - 64) << 16; + n |= *p++ << 8; + n |= *p++; + n += 128 + (1 << 14); + } + c1 = c + n + 1; + v_len = *p++; + if (is_common) { + if (v_len != 0) { + if (cr_add_interval(cr2, c, c1)) + goto fail; + } + } else { + for(i = 0; i < v_len; i++) { + if (p[i] == script_idx) { + if (cr_add_interval(cr2, c, c1)) + goto fail; + break; + } + } + } + p += v_len; + c = c1; + } + if (is_common) { + /* remove all the characters with script extensions */ + if (cr_invert(cr2)) + goto fail; + if (cr_op(cr, cr1->points, cr1->len, cr2->points, cr2->len, + CR_OP_INTER)) + goto fail; + } else { + if (cr_op(cr, cr1->points, cr1->len, cr2->points, cr2->len, + CR_OP_UNION)) + goto fail; + } + cr_free(cr1); + cr_free(cr2); + } + return 0; + fail: + if (is_ext) { + cr_free(cr1); + cr_free(cr2); + } + goto fail; +} + +#define M(id) (1U << UNICODE_GC_ ## id) + +static int unicode_general_category1(CharRange *cr, uint32_t gc_mask) +{ + const uint8_t *p, *p_end; + uint32_t c, c0, b, n, v; + + p = unicode_gc_table; + p_end = unicode_gc_table + countof(unicode_gc_table); + c = 0; + while (p < p_end) { + b = *p++; + n = b >> 5; + v = b & 0x1f; + if (n == 7) { + n = *p++; + if (n < 128) { + n += 7; + } else if (n < 128 + 64) { + n = (n - 128) << 8; + n |= *p++; + n += 7 + 128; + } else { + n = (n - 128 - 64) << 16; + n |= *p++ << 8; + n |= *p++; + n += 7 + 128 + (1 << 14); + } + } + c0 = c; + c += n + 1; + if (v == 31) { + /* run of Lu / Ll */ + b = gc_mask & (M(Lu) | M(Ll)); + if (b != 0) { + if (b == (M(Lu) | M(Ll))) { + goto add_range; + } else { + c0 += ((gc_mask & M(Ll)) != 0); + for(; c0 < c; c0 += 2) { + if (cr_add_interval(cr, c0, c0 + 1)) + return -1; + } + } + } + } else if ((gc_mask >> v) & 1) { + add_range: + if (cr_add_interval(cr, c0, c)) + return -1; + } + } + return 0; +} + +static int unicode_prop1(CharRange *cr, int prop_idx) +{ + const uint8_t *p, *p_end; + uint32_t c, c0, b, bit; + + p = unicode_prop_table[prop_idx]; + p_end = p + unicode_prop_len_table[prop_idx]; + c = 0; + bit = 0; + while (p < p_end) { + c0 = c; + b = *p++; + if (b < 64) { + c += (b >> 3) + 1; + if (bit) { + if (cr_add_interval(cr, c0, c)) + return -1; + } + bit ^= 1; + c0 = c; + c += (b & 7) + 1; + } else if (b >= 0x80) { + c += b - 0x80 + 1; + } else if (b < 0x60) { + c += (((b - 0x40) << 8) | p[0]) + 1; + p++; + } else { + c += (((b - 0x60) << 16) | (p[0] << 8) | p[1]) + 1; + p += 2; + } + if (bit) { + if (cr_add_interval(cr, c0, c)) + return -1; + } + bit ^= 1; + } + return 0; +} + +#define CASE_U (1 << 0) +#define CASE_L (1 << 1) +#define CASE_F (1 << 2) + +/* use the case conversion table to generate range of characters. + CASE_U: set char if modified by uppercasing, + CASE_L: set char if modified by lowercasing, + CASE_F: set char if modified by case folding, + */ +static int unicode_case1(CharRange *cr, int case_mask) +{ +#define MR(x) (1 << RUN_TYPE_ ## x) + const uint32_t tab_run_mask[3] = { + MR(U) | MR(UF) | MR(UL) | MR(LSU) | MR(U2L_399_EXT2) | MR(UF_D20) | + MR(UF_D1_EXT) | MR(U_EXT) | MR(UF_EXT2) | MR(UF_EXT3), + + MR(L) | MR(LF) | MR(UL) | MR(LSU) | MR(U2L_399_EXT2) | MR(LF_EXT) | MR(LF_EXT2), + + MR(UF) | MR(LF) | MR(UL) | MR(LSU) | MR(U2L_399_EXT2) | MR(LF_EXT) | MR(LF_EXT2) | MR(UF_D20) | MR(UF_D1_EXT) | MR(LF_EXT) | MR(UF_EXT2) | MR(UF_EXT3), + }; +#undef MR + uint32_t mask, v, code, type, len, i, idx; + + if (case_mask == 0) + return 0; + mask = 0; + for(i = 0; i < 3; i++) { + if ((case_mask >> i) & 1) + mask |= tab_run_mask[i]; + } + for(idx = 0; idx < countof(case_conv_table1); idx++) { + v = case_conv_table1[idx]; + type = (v >> (32 - 17 - 7 - 4)) & 0xf; + code = v >> (32 - 17); + len = (v >> (32 - 17 - 7)) & 0x7f; + if ((mask >> type) & 1) { + // printf("%d: type=%d %04x %04x\n", idx, type, code, code + len - 1); + switch(type) { + case RUN_TYPE_UL: + if ((case_mask & CASE_U) && (case_mask & (CASE_L | CASE_F))) + goto def_case; + code += ((case_mask & CASE_U) != 0); + for(i = 0; i < len; i += 2) { + if (cr_add_interval(cr, code + i, code + i + 1)) + return -1; + } + break; + case RUN_TYPE_LSU: + if ((case_mask & CASE_U) && (case_mask & (CASE_L | CASE_F))) + goto def_case; + if (!(case_mask & CASE_U)) { + if (cr_add_interval(cr, code, code + 1)) + return -1; + } + if (cr_add_interval(cr, code + 1, code + 2)) + return -1; + if (case_mask & CASE_U) { + if (cr_add_interval(cr, code + 2, code + 3)) + return -1; + } + break; + default: + def_case: + if (cr_add_interval(cr, code, code + len)) + return -1; + break; + } + } + } + return 0; +} + +static int point_cmp(const void *p1, const void *p2, void *arg) +{ + uint32_t v1 = *(uint32_t *)p1; + uint32_t v2 = *(uint32_t *)p2; + return (v1 > v2) - (v1 < v2); +} + +static void cr_sort_and_remove_overlap(CharRange *cr) +{ + uint32_t start, end, start1, end1, i, j; + + /* the resulting ranges are not necessarily sorted and may overlap */ + rqsort(cr->points, cr->len / 2, sizeof(cr->points[0]) * 2, point_cmp, NULL); + j = 0; + for(i = 0; i < cr->len; ) { + start = cr->points[i]; + end = cr->points[i + 1]; + i += 2; + while (i < cr->len) { + start1 = cr->points[i]; + end1 = cr->points[i + 1]; + if (start1 > end) { + /* |------| + * |-------| */ + break; + } else if (end1 <= end) { + /* |------| + * |--| */ + i += 2; + } else { + /* |------| + * |-------| */ + end = end1; + i += 2; + } + } + cr->points[j] = start; + cr->points[j + 1] = end; + j += 2; + } + cr->len = j; +} + +/* canonicalize a character set using the JS regex case folding rules + (see lre_canonicalize()) */ +int cr_regexp_canonicalize(CharRange *cr, bool is_unicode) +{ + CharRange cr_inter, cr_mask, cr_result, cr_sub; + uint32_t v, code, len, i, idx, start, end, c, d_start, d_end, d; + + cr_init(&cr_mask, cr->mem_opaque, cr->realloc_func); + cr_init(&cr_inter, cr->mem_opaque, cr->realloc_func); + cr_init(&cr_result, cr->mem_opaque, cr->realloc_func); + cr_init(&cr_sub, cr->mem_opaque, cr->realloc_func); + + if (unicode_case1(&cr_mask, is_unicode ? CASE_F : CASE_U)) + goto fail; + if (cr_op(&cr_inter, cr_mask.points, cr_mask.len, cr->points, cr->len, CR_OP_INTER)) + goto fail; + + if (cr_invert(&cr_mask)) + goto fail; + if (cr_op(&cr_sub, cr_mask.points, cr_mask.len, cr->points, cr->len, CR_OP_INTER)) + goto fail; + + /* cr_inter = cr & cr_mask */ + /* cr_sub = cr & ~cr_mask */ + + /* use the case conversion table to compute the result */ + d_start = -1; + d_end = -1; + idx = 0; + v = case_conv_table1[idx]; + code = v >> (32 - 17); + len = (v >> (32 - 17 - 7)) & 0x7f; + for(i = 0; i < cr_inter.len; i += 2) { + start = cr_inter.points[i]; + end = cr_inter.points[i + 1]; + + for(c = start; c < end; c++) { + for(;;) { + if (c >= code && c < code + len) + break; + idx++; + assert(idx < countof(case_conv_table1)); + v = case_conv_table1[idx]; + code = v >> (32 - 17); + len = (v >> (32 - 17 - 7)) & 0x7f; + } + d = lre_case_folding_entry(c, idx, v, is_unicode); + /* try to merge with the current interval */ + if (d_start == -1) { + d_start = d; + d_end = d + 1; + } else if (d_end == d) { + d_end++; + } else { + cr_add_interval(&cr_result, d_start, d_end); + d_start = d; + d_end = d + 1; + } + } + } + if (d_start != -1) { + if (cr_add_interval(&cr_result, d_start, d_end)) + goto fail; + } + + /* the resulting ranges are not necessarily sorted and may overlap */ + cr_sort_and_remove_overlap(&cr_result); + + /* or with the character not affected by the case folding */ + cr->len = 0; + if (cr_op(cr, cr_result.points, cr_result.len, cr_sub.points, cr_sub.len, CR_OP_UNION)) + goto fail; + + cr_free(&cr_inter); + cr_free(&cr_mask); + cr_free(&cr_result); + cr_free(&cr_sub); + return 0; + fail: + cr_free(&cr_inter); + cr_free(&cr_mask); + cr_free(&cr_result); + cr_free(&cr_sub); + return -1; +} + +typedef enum { + POP_GC, + POP_PROP, + POP_CASE, + POP_UNION, + POP_INTER, + POP_XOR, + POP_INVERT, + POP_END, +} PropOPEnum; + +#define POP_STACK_LEN_MAX 4 + +static int unicode_prop_ops(CharRange *cr, ...) +{ + va_list ap; + CharRange stack[POP_STACK_LEN_MAX]; + int stack_len, op, ret, i; + uint32_t a; + + va_start(ap, cr); + stack_len = 0; + for(;;) { + op = va_arg(ap, int); + switch(op) { + case POP_GC: + assert(stack_len < POP_STACK_LEN_MAX); + a = va_arg(ap, int); + cr_init(&stack[stack_len++], cr->mem_opaque, cr->realloc_func); + if (unicode_general_category1(&stack[stack_len - 1], a)) + goto fail; + break; + case POP_PROP: + assert(stack_len < POP_STACK_LEN_MAX); + a = va_arg(ap, int); + cr_init(&stack[stack_len++], cr->mem_opaque, cr->realloc_func); + if (unicode_prop1(&stack[stack_len - 1], a)) + goto fail; + break; + case POP_CASE: + assert(stack_len < POP_STACK_LEN_MAX); + a = va_arg(ap, int); + cr_init(&stack[stack_len++], cr->mem_opaque, cr->realloc_func); + if (unicode_case1(&stack[stack_len - 1], a)) + goto fail; + break; + case POP_UNION: + case POP_INTER: + case POP_XOR: + { + CharRange *cr1, *cr2, *cr3; + assert(stack_len >= 2); + assert(stack_len < POP_STACK_LEN_MAX); + cr1 = &stack[stack_len - 2]; + cr2 = &stack[stack_len - 1]; + cr3 = &stack[stack_len++]; + cr_init(cr3, cr->mem_opaque, cr->realloc_func); + if (cr_op(cr3, cr1->points, cr1->len, + cr2->points, cr2->len, op - POP_UNION + CR_OP_UNION)) + goto fail; + cr_free(cr1); + cr_free(cr2); + *cr1 = *cr3; + stack_len -= 2; + } + break; + case POP_INVERT: + assert(stack_len >= 1); + if (cr_invert(&stack[stack_len - 1])) + goto fail; + break; + case POP_END: + goto done; + default: + abort(); + } + } + done: + va_end(ap); + assert(stack_len == 1); + ret = cr_copy(cr, &stack[0]); + cr_free(&stack[0]); + return ret; + fail: + va_end(ap); + for(i = 0; i < stack_len; i++) + cr_free(&stack[i]); + return -1; +} + +static const uint32_t unicode_gc_mask_table[] = { + M(Lu) | M(Ll) | M(Lt), /* LC */ + M(Lu) | M(Ll) | M(Lt) | M(Lm) | M(Lo), /* L */ + M(Mn) | M(Mc) | M(Me), /* M */ + M(Nd) | M(Nl) | M(No), /* N */ + M(Sm) | M(Sc) | M(Sk) | M(So), /* S */ + M(Pc) | M(Pd) | M(Ps) | M(Pe) | M(Pi) | M(Pf) | M(Po), /* P */ + M(Zs) | M(Zl) | M(Zp), /* Z */ + M(Cc) | M(Cf) | M(Cs) | M(Co) | M(Cn), /* C */ +}; + +/* 'cr' must be initialized and empty. Return 0 if OK, -1 if error, -2 + if not found */ +int unicode_general_category(CharRange *cr, const char *gc_name) +{ + int gc_idx; + uint32_t gc_mask; + + gc_idx = unicode_find_name(unicode_gc_name_table, gc_name); + if (gc_idx < 0) + return -2; + if (gc_idx <= UNICODE_GC_Co) { + gc_mask = (uint64_t)1 << gc_idx; + } else { + gc_mask = unicode_gc_mask_table[gc_idx - UNICODE_GC_LC]; + } + return unicode_general_category1(cr, gc_mask); +} + + +/* 'cr' must be initialized and empty. Return 0 if OK, -1 if error, -2 + if not found */ +/* Unicode "properties of strings" (e.g. \p{RGI_Emoji}) used by the v flag; + expands a string property into a list of code-point sequences via the + callback. Returns -2 if the property name is unknown. */ +#define SEQ_MAX_LEN 16 + +static int unicode_sequence_prop1(int seq_prop_idx, UnicodeSequencePropCB *cb, void *opaque, + CharRange *cr) +{ + int i, c, j; + uint32_t seq[SEQ_MAX_LEN]; + + switch(seq_prop_idx) { + case UNICODE_SEQUENCE_PROP_Basic_Emoji: + if (unicode_prop1(cr, UNICODE_PROP_Basic_Emoji1) < 0) + return -1; + for(i = 0; i < cr->len; i += 2) { + for(c = cr->points[i]; c < cr->points[i + 1]; c++) { + seq[0] = c; + cb(opaque, seq, 1); + } + } + + cr->len = 0; + + if (unicode_prop1(cr, UNICODE_PROP_Basic_Emoji2) < 0) + return -1; + for(i = 0; i < cr->len; i += 2) { + for(c = cr->points[i]; c < cr->points[i + 1]; c++) { + seq[0] = c; + seq[1] = 0xfe0f; + cb(opaque, seq, 2); + } + } + + break; + case UNICODE_SEQUENCE_PROP_RGI_Emoji_Modifier_Sequence: + if (unicode_prop1(cr, UNICODE_PROP_Emoji_Modifier_Base) < 0) + return -1; + for(i = 0; i < cr->len; i += 2) { + for(c = cr->points[i]; c < cr->points[i + 1]; c++) { + for(j = 0; j < 5; j++) { + seq[0] = c; + seq[1] = 0x1f3fb + j; + cb(opaque, seq, 2); + } + } + } + break; + case UNICODE_SEQUENCE_PROP_RGI_Emoji_Flag_Sequence: + if (unicode_prop1(cr, UNICODE_PROP_RGI_Emoji_Flag_Sequence) < 0) + return -1; + for(i = 0; i < cr->len; i += 2) { + for(c = cr->points[i]; c < cr->points[i + 1]; c++) { + int c0, c1; + c0 = c / 26; + c1 = c % 26; + seq[0] = 0x1F1E6 + c0; + seq[1] = 0x1F1E6 + c1; + cb(opaque, seq, 2); + } + } + break; + case UNICODE_SEQUENCE_PROP_RGI_Emoji_ZWJ_Sequence: + { + int len, code, pres, k, mod, mod_count, mod_pos[2], hc_pos, n_mod, n_hc, mod1; + int mod_idx, hc_idx, i0, i1; + const uint8_t *tab = unicode_rgi_emoji_zwj_sequence; + + for(i = 0; i < countof(unicode_rgi_emoji_zwj_sequence);) { + len = tab[i++]; + k = 0; + mod = 0; + mod_count = 0; + hc_pos = -1; + for(j = 0; j < len; j++) { + code = tab[i++]; + code |= tab[i++] << 8; + pres = code >> 15; + mod1 = (code >> 13) & 3; + code &= 0x1fff; + if (code < 0x1000) { + c = code + 0x2000; + } else { + c = 0x1f000 + (code - 0x1000); + } + if (c == 0x1f9b0) + hc_pos = k; + seq[k++] = c; + if (mod1 != 0) { + assert(mod_count < 2); + mod = mod1; + mod_pos[mod_count++] = k; + seq[k++] = 0; /* will be filled later */ + } + if (pres) { + seq[k++] = 0xfe0f; + } + if (j < len - 1) { + seq[k++] = 0x200d; + } + } + + /* genrate all the variants */ + switch(mod) { + case 1: + n_mod = 5; + break; + case 2: + n_mod = 25; + break; + case 3: + n_mod = 20; + break; + default: + n_mod = 1; + break; + } + if (hc_pos >= 0) + n_hc = 4; + else + n_hc = 1; + for(hc_idx = 0; hc_idx < n_hc; hc_idx++) { + for(mod_idx = 0; mod_idx < n_mod; mod_idx++) { + if (hc_pos >= 0) + seq[hc_pos] = 0x1f9b0 + hc_idx; + + switch(mod) { + case 1: + seq[mod_pos[0]] = 0x1f3fb + mod_idx; + break; + case 2: + case 3: + i0 = mod_idx / 5; + i1 = mod_idx % 5; + /* avoid identical values */ + if (mod == 3 && i0 >= i1) + i0++; + seq[mod_pos[0]] = 0x1f3fb + i0; + seq[mod_pos[1]] = 0x1f3fb + i1; + break; + default: + break; + } +#if 0 + for(j = 0; j < k; j++) + printf(" %04x", seq[j]); + printf("\n"); +#endif + cb(opaque, seq, k); + } + } + } + } + break; + case UNICODE_SEQUENCE_PROP_RGI_Emoji_Tag_Sequence: + { + for(i = 0; i < countof(unicode_rgi_emoji_tag_sequence);) { + j = 0; + seq[j++] = 0x1F3F4; + for(;;) { + c = unicode_rgi_emoji_tag_sequence[i++]; + if (c == 0x00) + break; + seq[j++] = 0xe0000 + c; + } + seq[j++] = 0xe007f; + cb(opaque, seq, j); + } + } + break; + case UNICODE_SEQUENCE_PROP_Emoji_Keycap_Sequence: + if (unicode_prop1(cr, UNICODE_PROP_Emoji_Keycap_Sequence) < 0) + return -1; + for(i = 0; i < cr->len; i += 2) { + for(c = cr->points[i]; c < cr->points[i + 1]; c++) { + seq[0] = c; + seq[1] = 0xfe0f; + seq[2] = 0x20e3; + cb(opaque, seq, 3); + } + } + break; + case UNICODE_SEQUENCE_PROP_RGI_Emoji: + /* all prevous sequences */ + for(i = UNICODE_SEQUENCE_PROP_Basic_Emoji; i <= UNICODE_SEQUENCE_PROP_RGI_Emoji_ZWJ_Sequence; i++) { + int ret; + ret = unicode_sequence_prop1(i, cb, opaque, cr); + if (ret < 0) + return ret; + cr->len = 0; + } + break; + default: + return -2; + } + return 0; +} + +/* build a unicode sequence property */ +/* return -2 if not found, -1 if other error. 'cr' is used as temporary memory. */ +int unicode_sequence_prop(const char *prop_name, UnicodeSequencePropCB *cb, void *opaque, + CharRange *cr) +{ + int seq_prop_idx; + seq_prop_idx = unicode_find_name(unicode_sequence_prop_name_table, prop_name); + if (seq_prop_idx < 0) + return -2; + return unicode_sequence_prop1(seq_prop_idx, cb, opaque, cr); +} + +int unicode_prop(CharRange *cr, const char *prop_name) +{ + int prop_idx, ret; + + prop_idx = unicode_find_name(unicode_prop_name_table, prop_name); + if (prop_idx < 0) + return -2; + prop_idx += UNICODE_PROP_ASCII_Hex_Digit; + + ret = 0; + switch(prop_idx) { + case UNICODE_PROP_ASCII: + if (cr_add_interval(cr, 0x00, 0x7f + 1)) + return -1; + break; + case UNICODE_PROP_Any: + if (cr_add_interval(cr, 0x00000, 0x10ffff + 1)) + return -1; + break; + case UNICODE_PROP_Assigned: + ret = unicode_prop_ops(cr, + POP_GC, M(Cn), + POP_INVERT, + POP_END); + break; + case UNICODE_PROP_Math: + ret = unicode_prop_ops(cr, + POP_GC, M(Sm), + POP_PROP, UNICODE_PROP_Other_Math, + POP_UNION, + POP_END); + break; + case UNICODE_PROP_Lowercase: + ret = unicode_prop_ops(cr, + POP_GC, M(Ll), + POP_PROP, UNICODE_PROP_Other_Lowercase, + POP_UNION, + POP_END); + break; + case UNICODE_PROP_Uppercase: + ret = unicode_prop_ops(cr, + POP_GC, M(Lu), + POP_PROP, UNICODE_PROP_Other_Uppercase, + POP_UNION, + POP_END); + break; + case UNICODE_PROP_Cased: + ret = unicode_prop_ops(cr, + POP_GC, M(Lu) | M(Ll) | M(Lt), + POP_PROP, UNICODE_PROP_Other_Uppercase, + POP_UNION, + POP_PROP, UNICODE_PROP_Other_Lowercase, + POP_UNION, + POP_END); + break; + case UNICODE_PROP_Alphabetic: + ret = unicode_prop_ops(cr, + POP_GC, M(Lu) | M(Ll) | M(Lt) | M(Lm) | M(Lo) | M(Nl), + POP_PROP, UNICODE_PROP_Other_Uppercase, + POP_UNION, + POP_PROP, UNICODE_PROP_Other_Lowercase, + POP_UNION, + POP_PROP, UNICODE_PROP_Other_Alphabetic, + POP_UNION, + POP_END); + break; + case UNICODE_PROP_Grapheme_Base: + ret = unicode_prop_ops(cr, + POP_GC, M(Cc) | M(Cf) | M(Cs) | M(Co) | M(Cn) | M(Zl) | M(Zp) | M(Me) | M(Mn), + POP_PROP, UNICODE_PROP_Other_Grapheme_Extend, + POP_UNION, + POP_INVERT, + POP_END); + break; + case UNICODE_PROP_Grapheme_Extend: + ret = unicode_prop_ops(cr, + POP_GC, M(Me) | M(Mn), + POP_PROP, UNICODE_PROP_Other_Grapheme_Extend, + POP_UNION, + POP_END); + break; + case UNICODE_PROP_XID_Start: + ret = unicode_prop_ops(cr, + POP_GC, M(Lu) | M(Ll) | M(Lt) | M(Lm) | M(Lo) | M(Nl), + POP_PROP, UNICODE_PROP_Other_ID_Start, + POP_UNION, + POP_PROP, UNICODE_PROP_Pattern_Syntax, + POP_PROP, UNICODE_PROP_Pattern_White_Space, + POP_UNION, + POP_PROP, UNICODE_PROP_XID_Start1, + POP_UNION, + POP_INVERT, + POP_INTER, + POP_END); + break; + case UNICODE_PROP_XID_Continue: + ret = unicode_prop_ops(cr, + POP_GC, M(Lu) | M(Ll) | M(Lt) | M(Lm) | M(Lo) | M(Nl) | + M(Mn) | M(Mc) | M(Nd) | M(Pc), + POP_PROP, UNICODE_PROP_Other_ID_Start, + POP_UNION, + POP_PROP, UNICODE_PROP_Other_ID_Continue, + POP_UNION, + POP_PROP, UNICODE_PROP_Pattern_Syntax, + POP_PROP, UNICODE_PROP_Pattern_White_Space, + POP_UNION, + POP_PROP, UNICODE_PROP_XID_Continue1, + POP_UNION, + POP_INVERT, + POP_INTER, + POP_END); + break; + case UNICODE_PROP_Changes_When_Uppercased: + ret = unicode_case1(cr, CASE_U); + break; + case UNICODE_PROP_Changes_When_Lowercased: + ret = unicode_case1(cr, CASE_L); + break; + case UNICODE_PROP_Changes_When_Casemapped: + ret = unicode_case1(cr, CASE_U | CASE_L | CASE_F); + break; + case UNICODE_PROP_Changes_When_Titlecased: + ret = unicode_prop_ops(cr, + POP_CASE, CASE_U, + POP_PROP, UNICODE_PROP_Changes_When_Titlecased1, + POP_XOR, + POP_END); + break; + case UNICODE_PROP_Changes_When_Casefolded: + ret = unicode_prop_ops(cr, + POP_CASE, CASE_F, + POP_PROP, UNICODE_PROP_Changes_When_Casefolded1, + POP_XOR, + POP_END); + break; + case UNICODE_PROP_Changes_When_NFKC_Casefolded: + ret = unicode_prop_ops(cr, + POP_CASE, CASE_F, + POP_PROP, UNICODE_PROP_Changes_When_NFKC_Casefolded1, + POP_XOR, + POP_END); + break; + /* we use the existing tables */ + case UNICODE_PROP_ID_Continue: + ret = unicode_prop_ops(cr, + POP_PROP, UNICODE_PROP_ID_Start, + POP_PROP, UNICODE_PROP_ID_Continue1, + POP_XOR, + POP_END); + break; + default: + if (prop_idx >= countof(unicode_prop_table)) + return -2; + ret = unicode_prop1(cr, prop_idx); + break; + } + return ret; +} diff --git a/engine/quickjs-ng/libunicode.h b/engine/quickjs-ng/libunicode.h new file mode 100644 index 000000000..66c0b3865 --- /dev/null +++ b/engine/quickjs-ng/libunicode.h @@ -0,0 +1,172 @@ +/* + * Unicode utilities + * + * Copyright (c) 2017-2018 Fabrice Bellard + * + * 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. + */ +#ifndef LIBUNICODE_H +#define LIBUNICODE_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define LRE_CC_RES_LEN_MAX 3 + +typedef enum { + UNICODE_NFC, + UNICODE_NFD, + UNICODE_NFKC, + UNICODE_NFKD, +} UnicodeNormalizationEnum; + +int lre_case_conv(uint32_t *res, uint32_t c, int conv_type); +int lre_canonicalize(uint32_t c, bool is_unicode); +bool lre_is_cased(uint32_t c); +bool lre_is_case_ignorable(uint32_t c); + +/* char ranges */ + +typedef struct { + int len; /* in points, always even */ + int size; + uint32_t *points; /* points sorted by increasing value */ + void *mem_opaque; + void *(*realloc_func)(void *opaque, void *ptr, size_t size); +} CharRange; + +typedef enum { + CR_OP_UNION, + CR_OP_INTER, + CR_OP_XOR, + CR_OP_SUB, +} CharRangeOpEnum; + +void cr_init(CharRange *cr, void *mem_opaque, void *(*realloc_func)(void *opaque, void *ptr, size_t size)); +void cr_free(CharRange *cr); +int cr_realloc(CharRange *cr, int size); +int cr_copy(CharRange *cr, const CharRange *cr1); + +static inline int cr_add_point(CharRange *cr, uint32_t v) +{ + if (cr->len >= cr->size) { + if (cr_realloc(cr, cr->len + 1)) + return -1; + } + cr->points[cr->len++] = v; + return 0; +} + +static inline int cr_add_interval(CharRange *cr, uint32_t c1, uint32_t c2) +{ + if ((cr->len + 2) > cr->size) { + if (cr_realloc(cr, cr->len + 2)) + return -1; + } + cr->points[cr->len++] = c1; + cr->points[cr->len++] = c2; + return 0; +} + +int cr_op1(CharRange *cr, const uint32_t *b_pt, int b_len, int op); + +static inline int cr_union_interval(CharRange *cr, uint32_t c1, uint32_t c2) +{ + uint32_t b_pt[2]; + b_pt[0] = c1; + b_pt[1] = c2 + 1; + return cr_op1(cr, b_pt, 2, CR_OP_UNION); +} + +int cr_op(CharRange *cr, const uint32_t *a_pt, int a_len, + const uint32_t *b_pt, int b_len, int op); + +int cr_invert(CharRange *cr); +int cr_regexp_canonicalize(CharRange *cr, bool is_unicode); + +bool lre_is_id_start(uint32_t c); +bool lre_is_id_continue(uint32_t c); +bool lre_is_white_space(uint32_t c); + +int unicode_normalize(uint32_t **pdst, const uint32_t *src, int src_len, + UnicodeNormalizationEnum n_type, + void *opaque, void *(*realloc_func)(void *opaque, void *ptr, size_t size)); + +/* Unicode character range functions */ + +int unicode_script(CharRange *cr, + const char *script_name, bool is_ext); +int unicode_general_category(CharRange *cr, const char *gc_name); +int unicode_prop(CharRange *cr, const char *prop_name); + +typedef void UnicodeSequencePropCB(void *opaque, const uint32_t *buf, int len); +int unicode_sequence_prop(const char *prop_name, UnicodeSequencePropCB *cb, void *opaque, + CharRange *cr); + +/* Code point type categories (ASCII fast path table) */ +enum { + UNICODE_C_SPACE = (1 << 0), + UNICODE_C_DIGIT = (1 << 1), + UNICODE_C_UPPER = (1 << 2), + UNICODE_C_LOWER = (1 << 3), + UNICODE_C_UNDER = (1 << 4), + UNICODE_C_DOLLAR = (1 << 5), + UNICODE_C_XDIGIT = (1 << 6), +}; +extern uint8_t const lre_ctype_bits[256]; + +static inline int lre_is_space_byte(uint8_t c) { + return lre_ctype_bits[c] & UNICODE_C_SPACE; +} + +static inline int lre_is_id_start_byte(uint8_t c) { + return lre_ctype_bits[c] & (UNICODE_C_UPPER | UNICODE_C_LOWER | + UNICODE_C_UNDER | UNICODE_C_DOLLAR); +} + +static inline int lre_is_id_continue_byte(uint8_t c) { + return lre_ctype_bits[c] & (UNICODE_C_UPPER | UNICODE_C_LOWER | + UNICODE_C_UNDER | UNICODE_C_DOLLAR | + UNICODE_C_DIGIT); +} + +static inline int lre_is_word_byte(uint8_t c) { + return lre_ctype_bits[c] & (UNICODE_C_UPPER | UNICODE_C_LOWER | + UNICODE_C_UNDER | UNICODE_C_DIGIT); +} + +int lre_is_space_non_ascii(uint32_t c); + +static inline int lre_is_space(uint32_t c) { + if (c < 256) + return lre_is_space_byte(c); + else + return lre_is_space_non_ascii(c); +} + +#ifdef __cplusplus +} /* extern "C" { */ +#endif + +#endif /* LIBUNICODE_H */ diff --git a/engine/quickjs-ng/list.h b/engine/quickjs-ng/list.h new file mode 100644 index 000000000..b8dd71681 --- /dev/null +++ b/engine/quickjs-ng/list.h @@ -0,0 +1,107 @@ +/* + * Linux klist like system + * + * Copyright (c) 2016-2017 Fabrice Bellard + * + * 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. + */ +#ifndef LIST_H +#define LIST_H + +#ifndef NULL +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +struct list_head { + struct list_head *prev; + struct list_head *next; +}; + +#define LIST_HEAD_INIT(el) { &(el), &(el) } + +/* return the pointer of type 'type *' containing 'el' as field 'member' */ +#define list_entry(el, type, member) container_of(el, type, member) + +static inline void init_list_head(struct list_head *head) +{ + head->prev = head; + head->next = head; +} + +/* insert 'el' between 'prev' and 'next' */ +static inline void __list_add(struct list_head *el, + struct list_head *prev, struct list_head *next) +{ + prev->next = el; + el->prev = prev; + el->next = next; + next->prev = el; +} + +/* add 'el' at the head of the list 'head' (= after element head) */ +static inline void list_add(struct list_head *el, struct list_head *head) +{ + __list_add(el, head, head->next); +} + +/* add 'el' at the end of the list 'head' (= before element head) */ +static inline void list_add_tail(struct list_head *el, struct list_head *head) +{ + __list_add(el, head->prev, head); +} + +static inline void list_del(struct list_head *el) +{ + struct list_head *prev, *next; + prev = el->prev; + next = el->next; + prev->next = next; + next->prev = prev; + el->prev = NULL; /* fail safe */ + el->next = NULL; /* fail safe */ +} + +static inline int list_empty(struct list_head *el) +{ + return el->next == el; +} + +#define list_for_each(el, head) \ + for(el = (head)->next; el != (head); el = el->next) + +#define list_for_each_safe(el, el1, head) \ + for(el = (head)->next, el1 = el->next; el != (head); \ + el = el1, el1 = el->next) + +#define list_for_each_prev(el, head) \ + for(el = (head)->prev; el != (head); el = el->prev) + +#define list_for_each_prev_safe(el, el1, head) \ + for(el = (head)->prev, el1 = el->prev; el != (head); \ + el = el1, el1 = el->prev) + +#ifdef __cplusplus +} /* extern "C" { */ +#endif + +#endif /* LIST_H */ diff --git a/engine/quickjs-ng/quickjs-atom.h b/engine/quickjs-ng/quickjs-atom.h new file mode 100644 index 000000000..6b8dde84f --- /dev/null +++ b/engine/quickjs-ng/quickjs-atom.h @@ -0,0 +1,280 @@ +/* + * QuickJS atom definitions + * + * Copyright (c) 2017-2018 Fabrice Bellard + * Copyright (c) 2017-2018 Charlie Gordon + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifdef DEF + +/* Note: first atoms are considered as keywords in the parser */ +DEF(null, "null") /* must be first */ +DEF(false, "false") +DEF(true, "true") +DEF(if, "if") +DEF(else, "else") +DEF(return, "return") +DEF(var, "var") +DEF(this, "this") +DEF(delete, "delete") +DEF(void, "void") +DEF(typeof, "typeof") +DEF(new, "new") +DEF(in, "in") +DEF(instanceof, "instanceof") +DEF(do, "do") +DEF(while, "while") +DEF(for, "for") +DEF(break, "break") +DEF(continue, "continue") +DEF(switch, "switch") +DEF(case, "case") +DEF(default, "default") +DEF(throw, "throw") +DEF(try, "try") +DEF(catch, "catch") +DEF(finally, "finally") +DEF(function, "function") +DEF(debugger, "debugger") +DEF(with, "with") +/* FutureReservedWord */ +DEF(class, "class") +DEF(const, "const") +DEF(enum, "enum") +DEF(export, "export") +DEF(extends, "extends") +DEF(import, "import") +DEF(super, "super") +DEF(using, "using") +/* FutureReservedWords when parsing strict mode code */ +DEF(implements, "implements") +DEF(interface, "interface") +DEF(let, "let") +DEF(package, "package") +DEF(private, "private") +DEF(protected, "protected") +DEF(public, "public") +DEF(static, "static") +DEF(yield, "yield") +DEF(await, "await") + +/* empty string */ +DEF(empty_string, "") +/* identifiers */ +DEF(keys, "keys") +DEF(size, "size") +DEF(length, "length") +DEF(message, "message") +DEF(cause, "cause") +DEF(errors, "errors") +DEF(error, "error") +DEF(suppressed, "suppressed") +DEF(stack, "stack") +DEF(name, "name") +DEF(toString, "toString") +DEF(toLocaleString, "toLocaleString") +DEF(valueOf, "valueOf") +DEF(eval, "eval") +DEF(prototype, "prototype") +DEF(constructor, "constructor") +DEF(configurable, "configurable") +DEF(writable, "writable") +DEF(enumerable, "enumerable") +DEF(value, "value") +DEF(get, "get") +DEF(set, "set") +DEF(of, "of") +DEF(__proto__, "__proto__") +DEF(undefined, "undefined") +DEF(number, "number") +DEF(boolean, "boolean") +DEF(string, "string") +DEF(object, "object") +DEF(symbol, "symbol") +DEF(integer, "integer") +DEF(unknown, "unknown") +DEF(arguments, "arguments") +DEF(callee, "callee") +DEF(caller, "caller") +DEF(_eval_, "") +DEF(_ret_, "") +DEF(_var_, "") +DEF(_arg_var_, "") +DEF(_with_, "") +DEF(_using_dispose_, "") +DEF(use, "use") +DEF(dispose, "dispose") +DEF(disposeAsync, "disposeAsync") +DEF(lastIndex, "lastIndex") +DEF(target, "target") +DEF(index, "index") +DEF(input, "input") +DEF(defineProperties, "defineProperties") +DEF(apply, "apply") +DEF(join, "join") +DEF(concat, "concat") +DEF(split, "split") +DEF(construct, "construct") +DEF(getPrototypeOf, "getPrototypeOf") +DEF(setPrototypeOf, "setPrototypeOf") +DEF(isExtensible, "isExtensible") +DEF(preventExtensions, "preventExtensions") +DEF(has, "has") +DEF(deleteProperty, "deleteProperty") +DEF(defineProperty, "defineProperty") +DEF(getOwnPropertyDescriptor, "getOwnPropertyDescriptor") +DEF(ownKeys, "ownKeys") +DEF(add, "add") +DEF(done, "done") +DEF(next, "next") +DEF(values, "values") +DEF(source, "source") +DEF(flags, "flags") +DEF(global, "global") +DEF(unicode, "unicode") +DEF(raw, "raw") +DEF(rawJSON, "rawJSON") +DEF(new_target, "new.target") +DEF(this_active_func, "this.active_func") +DEF(home_object, "") +DEF(computed_field, "") +DEF(static_computed_field, "") /* must come after computed_fields */ +DEF(class_fields_init, "") +DEF(brand, "") +DEF(hash_constructor, "#constructor") +DEF(as, "as") +DEF(from, "from") +DEF(fromAsync, "fromAsync") +DEF(meta, "meta") +DEF(_default_, "*default*") +DEF(_star_, "*") +DEF(Module, "Module") +DEF(then, "then") +DEF(resolve, "resolve") +DEF(reject, "reject") +DEF(promise, "promise") +DEF(proxy, "proxy") +DEF(revoke, "revoke") +DEF(async, "async") +DEF(exec, "exec") +DEF(groups, "groups") +DEF(indices, "indices") +DEF(status, "status") +DEF(reason, "reason") +DEF(globalThis, "globalThis") +DEF(bigint, "bigint") +DEF(not_equal, "not-equal") +DEF(timed_out, "timed-out") +DEF(ok, "ok") +DEF(toJSON, "toJSON") +DEF(maxByteLength, "maxByteLength") +DEF(zip, "zip") +DEF(zipKeyed, "zipKeyed") +/* class names */ +DEF(Object, "Object") +DEF(Array, "Array") +DEF(Error, "Error") +DEF(Number, "Number") +DEF(String, "String") +DEF(Boolean, "Boolean") +DEF(Symbol, "Symbol") +DEF(Arguments, "Arguments") +DEF(Math, "Math") +DEF(JSON, "JSON") +DEF(Date, "Date") +DEF(Function, "Function") +DEF(GeneratorFunction, "GeneratorFunction") +DEF(ForInIterator, "ForInIterator") +DEF(RegExp, "RegExp") +DEF(ArrayBuffer, "ArrayBuffer") +DEF(SharedArrayBuffer, "SharedArrayBuffer") +/* must keep same order as class IDs for typed arrays */ +DEF(Uint8ClampedArray, "Uint8ClampedArray") +DEF(Int8Array, "Int8Array") +DEF(Uint8Array, "Uint8Array") +DEF(Int16Array, "Int16Array") +DEF(Uint16Array, "Uint16Array") +DEF(Int32Array, "Int32Array") +DEF(Uint32Array, "Uint32Array") +DEF(BigInt64Array, "BigInt64Array") +DEF(BigUint64Array, "BigUint64Array") +DEF(Float16Array, "Float16Array") +DEF(Float32Array, "Float32Array") +DEF(Float64Array, "Float64Array") +DEF(DataView, "DataView") +DEF(BigInt, "BigInt") +DEF(WeakRef, "WeakRef") +DEF(FinalizationRegistry, "FinalizationRegistry") +DEF(Map, "Map") +DEF(Set, "Set") /* Map + 1 */ +DEF(WeakMap, "WeakMap") /* Map + 2 */ +DEF(WeakSet, "WeakSet") /* Map + 3 */ +DEF(Iterator, "Iterator") +DEF(IteratorConcat, "Iterator Concat") +DEF(IteratorHelper, "Iterator Helper") +DEF(IteratorWrap, "Iterator Wrap") +DEF(Map_Iterator, "Map Iterator") +DEF(Set_Iterator, "Set Iterator") +DEF(Array_Iterator, "Array Iterator") +DEF(String_Iterator, "String Iterator") +DEF(RegExp_String_Iterator, "RegExp String Iterator") +DEF(Generator, "Generator") +DEF(Proxy, "Proxy") +DEF(Promise, "Promise") +DEF(PromiseResolveFunction, "PromiseResolveFunction") +DEF(PromiseRejectFunction, "PromiseRejectFunction") +DEF(AsyncFunction, "AsyncFunction") +DEF(AsyncFunctionResolve, "AsyncFunctionResolve") +DEF(AsyncFunctionReject, "AsyncFunctionReject") +DEF(AsyncGeneratorFunction, "AsyncGeneratorFunction") +DEF(AsyncGenerator, "AsyncGenerator") +DEF(EvalError, "EvalError") +DEF(RangeError, "RangeError") +DEF(ReferenceError, "ReferenceError") +DEF(SyntaxError, "SyntaxError") +DEF(TypeError, "TypeError") +DEF(URIError, "URIError") +DEF(InternalError, "InternalError") +DEF(DOMException, "DOMException") +DEF(CallSite, "CallSite") +DEF(DisposableStack, "DisposableStack") +DEF(AsyncDisposableStack, "AsyncDisposableStack") +DEF(SuppressedError, "SuppressedError") +/* private symbols */ +DEF(Private_brand, "") +/* symbols */ +DEF(Symbol_toPrimitive, "Symbol.toPrimitive") +DEF(Symbol_iterator, "Symbol.iterator") +DEF(Symbol_match, "Symbol.match") +DEF(Symbol_matchAll, "Symbol.matchAll") +DEF(Symbol_replace, "Symbol.replace") +DEF(Symbol_search, "Symbol.search") +DEF(Symbol_split, "Symbol.split") +DEF(Symbol_toStringTag, "Symbol.toStringTag") +DEF(Symbol_isConcatSpreadable, "Symbol.isConcatSpreadable") +DEF(Symbol_hasInstance, "Symbol.hasInstance") +DEF(Symbol_species, "Symbol.species") +DEF(Symbol_unscopables, "Symbol.unscopables") +DEF(Symbol_asyncIterator, "Symbol.asyncIterator") +DEF(Symbol_dispose, "Symbol.dispose") +DEF(Symbol_asyncDispose, "Symbol.asyncDispose") + +#endif /* DEF */ diff --git a/engine/quickjs-ng/quickjs-c-atomics.h b/engine/quickjs-ng/quickjs-c-atomics.h new file mode 100644 index 000000000..8fc6b7203 --- /dev/null +++ b/engine/quickjs-ng/quickjs-c-atomics.h @@ -0,0 +1,54 @@ +/* + * QuickJS C atomics definitions + * + * Copyright (c) 2023 Marcin Kolny + * + * 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. + */ + +#if (defined(__GNUC__) || defined(__GNUG__)) && !defined(__clang__) + // Use GCC builtins for version < 4.9 +# if((__GNUC__ << 16) + __GNUC_MINOR__ < ((4) << 16) + 9) +# define GCC_BUILTIN_ATOMICS +# endif +#endif + +#ifdef GCC_BUILTIN_ATOMICS +#define atomic_fetch_add(obj, arg) \ + __atomic_fetch_add(obj, arg, __ATOMIC_SEQ_CST) +#define atomic_compare_exchange_strong(obj, expected, desired) \ + __atomic_compare_exchange_n(obj, expected, desired, 0, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST) +#define atomic_exchange(obj, desired) \ + __atomic_exchange_n (obj, desired, __ATOMIC_SEQ_CST) +#define atomic_load(obj) \ + __atomic_load_n(obj, __ATOMIC_SEQ_CST) +#define atomic_store(obj, desired) \ + __atomic_store_n(obj, desired, __ATOMIC_SEQ_CST) +#define atomic_fetch_or(obj, arg) \ + __atomic_fetch_or(obj, arg, __ATOMIC_SEQ_CST) +#define atomic_fetch_xor(obj, arg) \ + __atomic_fetch_xor(obj, arg, __ATOMIC_SEQ_CST) +#define atomic_fetch_and(obj, arg) \ + __atomic_fetch_and(obj, arg, __ATOMIC_SEQ_CST) +#define atomic_fetch_sub(obj, arg) \ + __atomic_fetch_sub(obj, arg, __ATOMIC_SEQ_CST) +#define _Atomic +#else +#include +#endif diff --git a/engine/quickjs-ng/quickjs-opcode.h b/engine/quickjs-ng/quickjs-opcode.h new file mode 100644 index 000000000..ec2a5ad91 --- /dev/null +++ b/engine/quickjs-ng/quickjs-opcode.h @@ -0,0 +1,377 @@ +/* + * QuickJS opcode definitions + * + * Copyright (c) 2017-2018 Fabrice Bellard + * Copyright (c) 2017-2018 Charlie Gordon + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifdef FMT +FMT(none) +FMT(none_int) +FMT(none_loc) +FMT(none_arg) +FMT(none_var_ref) +FMT(u8) +FMT(i8) +FMT(loc8) +FMT(const8) +FMT(label8) +FMT(u16) +FMT(i16) +FMT(label16) +FMT(npop) +FMT(npopx) +FMT(npop_u16) +FMT(loc) +FMT(arg) +FMT(var_ref) +FMT(u32) +FMT(u32x2) +FMT(i32) +FMT(const) +FMT(label) +FMT(atom) +FMT(atom_u8) +FMT(atom_u16) +FMT(atom_label_u8) +FMT(atom_label_u16) +FMT(label_u16) +#undef FMT +#endif /* FMT */ + +#ifdef DEF + +#ifndef def +#define def(id, size, n_pop, n_push, f) DEF(id, size, n_pop, n_push, f) +#endif + +DEF(invalid, 1, 0, 0, none) /* never emitted */ + +/* push values */ +DEF( push_i32, 5, 0, 1, i32) +DEF( push_const, 5, 0, 1, const) +DEF( fclosure, 5, 0, 1, const) /* must follow push_const */ +DEF(push_atom_value, 5, 0, 1, atom) +DEF( private_symbol, 5, 0, 1, atom) +DEF( undefined, 1, 0, 1, none) +DEF( null, 1, 0, 1, none) +DEF( push_this, 1, 0, 1, none) /* only used at the start of a function */ +DEF( push_false, 1, 0, 1, none) +DEF( push_true, 1, 0, 1, none) +DEF( object, 1, 0, 1, none) +DEF( special_object, 2, 0, 1, u8) /* only used at the start of a function */ +DEF( rest, 3, 0, 1, u16) /* only used at the start of a function */ + +DEF( drop, 1, 1, 0, none) /* a -> */ +DEF( nip, 1, 2, 1, none) /* a b -> b */ +DEF( nip1, 1, 3, 2, none) /* a b c -> b c */ +DEF( dup, 1, 1, 2, none) /* a -> a a */ +DEF( dup1, 1, 2, 3, none) /* a b -> a a b */ +DEF( dup2, 1, 2, 4, none) /* a b -> a b a b */ +DEF( dup3, 1, 3, 6, none) /* a b c -> a b c a b c */ +DEF( insert2, 1, 2, 3, none) /* obj a -> a obj a (dup_x1) */ +DEF( insert3, 1, 3, 4, none) /* obj prop a -> a obj prop a (dup_x2) */ +DEF( insert4, 1, 4, 5, none) /* this obj prop a -> a this obj prop a */ +DEF( perm3, 1, 3, 3, none) /* obj a b -> a obj b */ +DEF( perm4, 1, 4, 4, none) /* obj prop a b -> a obj prop b */ +DEF( perm5, 1, 5, 5, none) /* this obj prop a b -> a this obj prop b */ +DEF( swap, 1, 2, 2, none) /* a b -> b a */ +DEF( swap2, 1, 4, 4, none) /* a b c d -> c d a b */ +DEF( rot3l, 1, 3, 3, none) /* x a b -> a b x */ +DEF( rot3r, 1, 3, 3, none) /* a b x -> x a b */ +DEF( rot4l, 1, 4, 4, none) /* x a b c -> a b c x */ +DEF( rot5l, 1, 5, 5, none) /* x a b c d -> a b c d x */ + +DEF(call_constructor, 3, 2, 1, npop) /* func new.target args -> ret. arguments are not counted in n_pop */ +DEF( call, 3, 1, 1, npop) /* arguments are not counted in n_pop */ +DEF( tail_call, 3, 1, 0, npop) /* arguments are not counted in n_pop */ +DEF( call_method, 3, 2, 1, npop) /* arguments are not counted in n_pop */ +DEF(tail_call_method, 3, 2, 0, npop) /* arguments are not counted in n_pop */ +DEF( array_from, 3, 0, 1, npop) /* arguments are not counted in n_pop */ +DEF( apply, 3, 3, 1, u16) +DEF( return, 1, 1, 0, none) +DEF( return_undef, 1, 0, 0, none) +DEF(check_ctor_return, 1, 1, 2, none) +DEF( check_ctor, 1, 0, 0, none) +DEF( init_ctor, 1, 0, 1, none) +DEF( check_brand, 1, 2, 2, none) /* this_obj func -> this_obj func */ +DEF( add_brand, 1, 2, 0, none) /* this_obj home_obj -> */ +DEF( return_async, 1, 1, 0, none) +DEF( throw, 1, 1, 0, none) +DEF( throw_error, 6, 0, 0, atom_u8) +DEF( eval, 5, 1, 1, npop_u16) /* func args... -> ret_val */ +DEF( apply_eval, 3, 2, 1, u16) /* func array -> ret_eval */ +DEF( regexp, 1, 2, 1, none) /* create a RegExp object from the pattern and a + bytecode string */ +DEF( get_super, 1, 1, 1, none) +DEF( import, 1, 2, 1, none) /* dynamic module import */ + +DEF( get_var_undef, 5, 0, 1, atom) /* push undefined if the variable does not exist */ +DEF( get_var, 5, 0, 1, atom) /* throw an exception if the variable does not exist */ +DEF( put_var, 5, 1, 0, atom) /* must come after get_var */ +DEF( put_var_init, 5, 1, 0, atom) /* must come after put_var. Used to initialize a global lexical variable */ + +DEF( get_ref_value, 1, 2, 3, none) +DEF( put_ref_value, 1, 3, 0, none) + +DEF( define_var, 6, 0, 0, atom_u8) +DEF(check_define_var, 6, 0, 0, atom_u8) +DEF( define_func, 6, 1, 0, atom_u8) + +// order matters, see IC counterparts +DEF( get_field, 5, 1, 1, atom) +DEF( get_field2, 5, 1, 2, atom) +DEF( put_field, 5, 2, 0, atom) + +DEF( get_private_field, 1, 2, 1, none) /* obj prop -> value */ +DEF( put_private_field, 1, 3, 0, none) /* obj value prop -> */ +DEF(define_private_field, 1, 3, 1, none) /* obj prop value -> obj */ +DEF( get_array_el, 1, 2, 1, none) +DEF( get_array_el2, 1, 2, 2, none) /* obj prop -> obj value */ +DEF( put_array_el, 1, 3, 0, none) +DEF(get_super_value, 1, 3, 1, none) /* this obj prop -> value */ +DEF(put_super_value, 1, 4, 0, none) /* this obj prop value -> */ +DEF( define_field, 5, 2, 1, atom) +DEF( set_name, 5, 1, 1, atom) +DEF(set_name_computed, 1, 2, 2, none) +DEF( set_proto, 1, 2, 1, none) +DEF(set_home_object, 1, 2, 2, none) +DEF(define_array_el, 1, 3, 2, none) +DEF( append, 1, 3, 2, none) /* append enumerated object, update length */ +DEF(copy_data_properties, 2, 3, 3, u8) +DEF( define_method, 6, 2, 1, atom_u8) +DEF(define_method_computed, 2, 3, 1, u8) /* must come after define_method */ +DEF( define_class, 6, 2, 2, atom_u8) /* parent ctor -> ctor proto */ +DEF( define_class_computed, 6, 3, 3, atom_u8) /* field_name parent ctor -> field_name ctor proto (class with computed name) */ + +DEF( get_loc, 3, 0, 1, loc) +DEF( put_loc, 3, 1, 0, loc) /* must come after get_loc */ +DEF( set_loc, 3, 1, 1, loc) /* must come after put_loc */ +DEF( get_arg, 3, 0, 1, arg) +DEF( put_arg, 3, 1, 0, arg) /* must come after get_arg */ +DEF( set_arg, 3, 1, 1, arg) /* must come after put_arg */ +DEF( get_var_ref, 3, 0, 1, var_ref) +DEF( put_var_ref, 3, 1, 0, var_ref) /* must come after get_var_ref */ +DEF( set_var_ref, 3, 1, 1, var_ref) /* must come after put_var_ref */ +DEF(set_loc_uninitialized, 3, 0, 0, loc) +DEF( get_loc_check, 3, 0, 1, loc) +DEF( put_loc_check, 3, 1, 0, loc) /* must come after get_loc_check */ +DEF( put_loc_check_init, 3, 1, 0, loc) +DEF(get_var_ref_check, 3, 0, 1, var_ref) +DEF(put_var_ref_check, 3, 1, 0, var_ref) /* must come after get_var_ref_check */ +DEF(put_var_ref_check_init, 3, 1, 0, var_ref) +DEF( close_loc, 3, 0, 0, loc) +DEF( if_false, 5, 1, 0, label) +DEF( if_true, 5, 1, 0, label) /* must come after if_false */ +DEF( goto, 5, 0, 0, label) /* must come after if_true */ +DEF( catch, 5, 0, 1, label) +DEF( gosub, 5, 0, 0, label) /* used to execute the finally block */ +DEF( ret, 1, 1, 0, none) /* used to return from the finally block */ +DEF( nip_catch, 1, 2, 1, none) /* catch ... a -> a */ + +DEF( check_object, 1, 1, 1, none) +DEF( to_object, 1, 1, 1, none) +//DEF( to_string, 1, 1, 1, none) +DEF( to_propkey, 1, 1, 1, none) +DEF( to_propkey2, 1, 2, 2, none) + +DEF( with_get_var, 10, 1, 0, atom_label_u8) /* must be in the same order as scope_xxx */ +DEF( with_put_var, 10, 2, 1, atom_label_u8) /* must be in the same order as scope_xxx */ +DEF(with_delete_var, 10, 1, 0, atom_label_u8) /* must be in the same order as scope_xxx */ +DEF( with_make_ref, 10, 1, 0, atom_label_u8) /* must be in the same order as scope_xxx */ +DEF( with_get_ref, 10, 1, 0, atom_label_u8) /* must be in the same order as scope_xxx */ +DEF(with_get_ref_undef, 10, 1, 0, atom_label_u8) + +DEF( make_loc_ref, 7, 0, 2, atom_u16) +DEF( make_arg_ref, 7, 0, 2, atom_u16) +DEF(make_var_ref_ref, 7, 0, 2, atom_u16) +DEF( make_var_ref, 5, 0, 2, atom) + +DEF( for_in_start, 1, 1, 1, none) +DEF( for_of_start, 1, 1, 3, none) +DEF(for_await_of_start, 1, 1, 3, none) +DEF( for_in_next, 1, 1, 3, none) +DEF( for_of_next, 2, 3, 5, u8) +DEF(iterator_get_value_done, 1, 1, 2, none) +DEF( iterator_close, 1, 3, 0, none) +DEF( iterator_next, 1, 4, 4, none) +DEF( iterator_call, 2, 4, 5, u8) +DEF( initial_yield, 1, 0, 0, none) +DEF( yield, 1, 1, 2, none) +DEF( yield_star, 1, 1, 2, none) +DEF(async_yield_star, 1, 1, 2, none) +DEF( await, 1, 1, 1, none) + +/* arithmetic/logic operations */ +DEF( neg, 1, 1, 1, none) +DEF( plus, 1, 1, 1, none) +DEF( dec, 1, 1, 1, none) +DEF( inc, 1, 1, 1, none) +DEF( post_dec, 1, 1, 2, none) +DEF( post_inc, 1, 1, 2, none) +DEF( dec_loc, 2, 0, 0, loc8) +DEF( inc_loc, 2, 0, 0, loc8) +DEF( add_loc, 2, 1, 0, loc8) +DEF( not, 1, 1, 1, none) +DEF( lnot, 1, 1, 1, none) +DEF( typeof, 1, 1, 1, none) +DEF( delete, 1, 2, 1, none) +DEF( delete_var, 5, 0, 1, atom) + +/* warning: order matters (see js_parse_assign_expr) */ +DEF( mul, 1, 2, 1, none) +DEF( div, 1, 2, 1, none) +DEF( mod, 1, 2, 1, none) +DEF( add, 1, 2, 1, none) +DEF( sub, 1, 2, 1, none) +DEF( shl, 1, 2, 1, none) +DEF( sar, 1, 2, 1, none) +DEF( shr, 1, 2, 1, none) +DEF( and, 1, 2, 1, none) +DEF( xor, 1, 2, 1, none) +DEF( or, 1, 2, 1, none) +DEF( pow, 1, 2, 1, none) + +DEF( lt, 1, 2, 1, none) +DEF( lte, 1, 2, 1, none) +DEF( gt, 1, 2, 1, none) +DEF( gte, 1, 2, 1, none) +DEF( instanceof, 1, 2, 1, none) +DEF( in, 1, 2, 1, none) +DEF( eq, 1, 2, 1, none) +DEF( neq, 1, 2, 1, none) +DEF( strict_eq, 1, 2, 1, none) +DEF( strict_neq, 1, 2, 1, none) +DEF(is_undefined_or_null, 1, 1, 1, none) +DEF( private_in, 1, 2, 1, none) +DEF(push_bigint_i32, 5, 0, 1, i32) +DEF( using_dispose_init, 1, 0, 1, none) +DEF( using_dispose, 3, 1, 1, loc) +DEF(using_dispose_async, 3, 0, 1, loc) +DEF(using_dispose_merge, 1, 2, 1, none) +DEF( using_dispose_end, 1, 1, 0, none) +DEF( using_check, 2, 1, 2, u8) +/* must be the last non short and non temporary opcode */ +DEF( nop, 1, 0, 0, none) + +/* temporary opcodes: never emitted in the final bytecode */ + +def( enter_scope, 3, 0, 0, u16) /* emitted in phase 1, removed in phase 2 */ +def( leave_scope, 3, 0, 0, u16) /* emitted in phase 1, removed in phase 2 */ + +def( label, 5, 0, 0, label) /* emitted in phase 1, removed in phase 3 */ + +def(scope_get_var_undef, 7, 0, 1, atom_u16) /* emitted in phase 1, removed in phase 2 */ +def( scope_get_var, 7, 0, 1, atom_u16) /* emitted in phase 1, removed in phase 2 */ +def( scope_put_var, 7, 1, 0, atom_u16) /* emitted in phase 1, removed in phase 2 */ +def(scope_delete_var, 7, 0, 1, atom_u16) /* emitted in phase 1, removed in phase 2 */ +def( scope_make_ref, 11, 0, 2, atom_label_u16) /* emitted in phase 1, removed in phase 2 */ +def( scope_get_ref, 7, 0, 2, atom_u16) /* emitted in phase 1, removed in phase 2 */ +def(scope_put_var_init, 7, 0, 2, atom_u16) /* emitted in phase 1, removed in phase 2 */ +def(scope_get_private_field, 7, 1, 1, atom_u16) /* obj -> value, emitted in phase 1, removed in phase 2 */ +def(scope_get_private_field2, 7, 1, 2, atom_u16) /* obj -> obj value, emitted in phase 1, removed in phase 2 */ +def(scope_put_private_field, 7, 2, 0, atom_u16) /* obj value ->, emitted in phase 1, removed in phase 2 */ +def(scope_in_private_field, 7, 1, 1, atom_u16) /* obj -> res emitted in phase 1, removed in phase 2 */ +def(get_field_opt_chain, 5, 1, 1, atom) /* emitted in phase 1, removed in phase 2 */ +def(get_array_el_opt_chain, 1, 2, 1, none) /* emitted in phase 1, removed in phase 2 */ +def( set_class_name, 5, 1, 1, u32) /* emitted in phase 1, removed in phase 2 */ + +def( dispose_scope, 3, 0, 0, u16) /* emitted in phase 1, removed in phase 2 */ + +def( source_loc, 9, 0, 0, u32x2) /* emitted in phase 1, removed in phase 3 */ + +DEF( push_minus1, 1, 0, 1, none_int) +DEF( push_0, 1, 0, 1, none_int) +DEF( push_1, 1, 0, 1, none_int) +DEF( push_2, 1, 0, 1, none_int) +DEF( push_3, 1, 0, 1, none_int) +DEF( push_4, 1, 0, 1, none_int) +DEF( push_5, 1, 0, 1, none_int) +DEF( push_6, 1, 0, 1, none_int) +DEF( push_7, 1, 0, 1, none_int) +DEF( push_i8, 2, 0, 1, i8) +DEF( push_i16, 3, 0, 1, i16) +DEF( push_const8, 2, 0, 1, const8) +DEF( fclosure8, 2, 0, 1, const8) /* must follow push_const8 */ +DEF(push_empty_string, 1, 0, 1, none) + +DEF( get_loc8, 2, 0, 1, loc8) +DEF( put_loc8, 2, 1, 0, loc8) +DEF( set_loc8, 2, 1, 1, loc8) + +DEF( get_loc0_loc1, 1, 0, 2, none_loc) +DEF( get_loc0, 1, 0, 1, none_loc) +DEF( get_loc1, 1, 0, 1, none_loc) +DEF( get_loc2, 1, 0, 1, none_loc) +DEF( get_loc3, 1, 0, 1, none_loc) +DEF( put_loc0, 1, 1, 0, none_loc) +DEF( put_loc1, 1, 1, 0, none_loc) +DEF( put_loc2, 1, 1, 0, none_loc) +DEF( put_loc3, 1, 1, 0, none_loc) +DEF( set_loc0, 1, 1, 1, none_loc) +DEF( set_loc1, 1, 1, 1, none_loc) +DEF( set_loc2, 1, 1, 1, none_loc) +DEF( set_loc3, 1, 1, 1, none_loc) +DEF( get_arg0, 1, 0, 1, none_arg) +DEF( get_arg1, 1, 0, 1, none_arg) +DEF( get_arg2, 1, 0, 1, none_arg) +DEF( get_arg3, 1, 0, 1, none_arg) +DEF( put_arg0, 1, 1, 0, none_arg) +DEF( put_arg1, 1, 1, 0, none_arg) +DEF( put_arg2, 1, 1, 0, none_arg) +DEF( put_arg3, 1, 1, 0, none_arg) +DEF( set_arg0, 1, 1, 1, none_arg) +DEF( set_arg1, 1, 1, 1, none_arg) +DEF( set_arg2, 1, 1, 1, none_arg) +DEF( set_arg3, 1, 1, 1, none_arg) +DEF( get_var_ref0, 1, 0, 1, none_var_ref) +DEF( get_var_ref1, 1, 0, 1, none_var_ref) +DEF( get_var_ref2, 1, 0, 1, none_var_ref) +DEF( get_var_ref3, 1, 0, 1, none_var_ref) +DEF( put_var_ref0, 1, 1, 0, none_var_ref) +DEF( put_var_ref1, 1, 1, 0, none_var_ref) +DEF( put_var_ref2, 1, 1, 0, none_var_ref) +DEF( put_var_ref3, 1, 1, 0, none_var_ref) +DEF( set_var_ref0, 1, 1, 1, none_var_ref) +DEF( set_var_ref1, 1, 1, 1, none_var_ref) +DEF( set_var_ref2, 1, 1, 1, none_var_ref) +DEF( set_var_ref3, 1, 1, 1, none_var_ref) + +DEF( get_length, 1, 1, 1, none) + +DEF( if_false8, 2, 1, 0, label8) +DEF( if_true8, 2, 1, 0, label8) /* must come after if_false8 */ +DEF( goto8, 2, 0, 0, label8) /* must come after if_true8 */ +DEF( goto16, 3, 0, 0, label16) + +DEF( call0, 1, 1, 1, npopx) +DEF( call1, 1, 1, 1, npopx) +DEF( call2, 1, 1, 1, npopx) +DEF( call3, 1, 1, 1, npopx) + +DEF( is_undefined, 1, 1, 1, none) +DEF( is_null, 1, 1, 1, none) +DEF(typeof_is_undefined, 1, 1, 1, none) +DEF( typeof_is_function, 1, 1, 1, none) + +#undef DEF +#undef def +#endif /* DEF */ diff --git a/engine/quickjs-ng/quickjs.c b/engine/quickjs-ng/quickjs.c new file mode 100644 index 000000000..6ca030b9f --- /dev/null +++ b/engine/quickjs-ng/quickjs.c @@ -0,0 +1,64943 @@ +/* + * QuickJS Javascript Engine + * + * Copyright (c) 2017-2026 Fabrice Bellard + * Copyright (c) 2017-2025 Charlie Gordon + * Copyright (c) 2023-2026 Ben Noordhuis + * Copyright (c) 2023-2026 Saúl Ibarra Corretgé + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include +#include +#include +#include +#if !defined(_MSC_VER) +#include +#if defined(_WIN32) +#include +#endif +#endif +#if defined(_WIN32) +#include +#endif +#include +#include + +#include "cutils.h" +#include "list.h" +#include "quickjs.h" +#include "libregexp.h" +#include "dtoa.h" + +#if defined(EMSCRIPTEN) || defined(_MSC_VER) +#define DIRECT_DISPATCH 0 +#else +#define DIRECT_DISPATCH 1 +#endif + +#if defined(__APPLE__) +#define MALLOC_OVERHEAD 0 +#else +#define MALLOC_OVERHEAD 8 +#endif + +#if defined(__NEWLIB__) +#define NO_TM_GMTOFF +#endif + +#if defined(__sun) +#include +#define NO_TM_GMTOFF +#endif + +// atomic_store etc. are completely busted in recent versions of tcc; +// somehow the compiler forgets to load |ptr| into %rdi when calling +// the __atomic_*() helpers in its lib/stdatomic.c and lib/atomic.S +#if !defined(__TINYC__) && !defined(EMSCRIPTEN) && !defined(__wasi__) && !__STDC_NO_ATOMICS__ && !defined(__DJGPP) +#include "quickjs-c-atomics.h" +#define CONFIG_ATOMICS +#endif + +#ifndef __GNUC__ +#define __extension__ +#endif + +#ifndef NDEBUG +#define ENABLE_DUMPS +#endif + +//#define FORCE_GC_AT_MALLOC /* test the GC by forcing it before each object allocation */ + +#define check_dump_flag(rt, flag) ((rt->dump_flags & (flag +0)) == (flag +0)) + +#define STRINGIFY_(x) #x +#define STRINGIFY(x) STRINGIFY_(x) + +#define QJS_VERSION_STRING \ + STRINGIFY(QJS_VERSION_MAJOR) "." STRINGIFY(QJS_VERSION_MINOR) "." STRINGIFY(QJS_VERSION_PATCH) QJS_VERSION_SUFFIX + +const char* JS_GetVersion(void) { + return QJS_VERSION_STRING; +} + +#undef STRINFIGY_ +#undef STRINGIFY + +static inline JSValueConst *vc(JSValue *vals) +{ + return (JSValueConst *)vals; +} + +static inline JSValue unsafe_unconst(JSValueConst v) +{ +#ifdef JS_CHECK_JSVALUE + return (JSValue)v; +#else + return v; +#endif +} + +static inline JSValueConst safe_const(JSValue v) +{ +#ifdef JS_CHECK_JSVALUE + return (JSValueConst)v; +#else + return v; +#endif +} + +enum { + /* classid tag */ /* union usage | properties */ + JS_CLASS_OBJECT = 1, /* must be first */ + JS_CLASS_ARRAY, /* u.array | length */ + JS_CLASS_ERROR, + JS_CLASS_NUMBER, /* u.object_data */ + JS_CLASS_STRING, /* u.object_data */ + JS_CLASS_BOOLEAN, /* u.object_data */ + JS_CLASS_SYMBOL, /* u.object_data */ + JS_CLASS_ARGUMENTS, /* u.array | length */ + JS_CLASS_MAPPED_ARGUMENTS, /* | length */ + JS_CLASS_DATE, /* u.object_data */ + JS_CLASS_MODULE_NS, + JS_CLASS_C_FUNCTION, /* u.cfunc */ + JS_CLASS_BYTECODE_FUNCTION, /* u.func */ + JS_CLASS_BOUND_FUNCTION, /* u.bound_function */ + JS_CLASS_C_FUNCTION_DATA, /* u.c_function_data_record */ + JS_CLASS_C_CLOSURE, /* u.c_closure_record */ + JS_CLASS_GENERATOR_FUNCTION, /* u.func */ + JS_CLASS_FOR_IN_ITERATOR, /* u.for_in_iterator */ + JS_CLASS_REGEXP, /* u.regexp */ + JS_CLASS_ARRAY_BUFFER, /* u.array_buffer */ + JS_CLASS_SHARED_ARRAY_BUFFER, /* u.array_buffer */ + JS_CLASS_UINT8C_ARRAY, /* u.array (typed_array) */ + JS_CLASS_INT8_ARRAY, /* u.array (typed_array) */ + JS_CLASS_UINT8_ARRAY, /* u.array (typed_array) */ + JS_CLASS_INT16_ARRAY, /* u.array (typed_array) */ + JS_CLASS_UINT16_ARRAY, /* u.array (typed_array) */ + JS_CLASS_INT32_ARRAY, /* u.array (typed_array) */ + JS_CLASS_UINT32_ARRAY, /* u.array (typed_array) */ + JS_CLASS_BIG_INT64_ARRAY, /* u.array (typed_array) */ + JS_CLASS_BIG_UINT64_ARRAY, /* u.array (typed_array) */ + JS_CLASS_FLOAT16_ARRAY, /* u.array (typed_array) */ + JS_CLASS_FLOAT32_ARRAY, /* u.array (typed_array) */ + JS_CLASS_FLOAT64_ARRAY, /* u.array (typed_array) */ + JS_CLASS_DATAVIEW, /* u.typed_array */ + JS_CLASS_BIG_INT, /* u.object_data */ + JS_CLASS_MAP, /* u.map_state */ + JS_CLASS_SET, /* u.map_state */ + JS_CLASS_WEAKMAP, /* u.map_state */ + JS_CLASS_WEAKSET, /* u.map_state */ + JS_CLASS_ITERATOR, + JS_CLASS_ITERATOR_CONCAT, /* u.iterator_concat_data */ + JS_CLASS_ITERATOR_HELPER, /* u.iterator_helper_data */ + JS_CLASS_ITERATOR_WRAP, /* u.iterator_wrap_data */ + JS_CLASS_MAP_ITERATOR, /* u.map_iterator_data */ + JS_CLASS_SET_ITERATOR, /* u.map_iterator_data */ + JS_CLASS_ARRAY_ITERATOR, /* u.array_iterator_data */ + JS_CLASS_STRING_ITERATOR, /* u.array_iterator_data */ + JS_CLASS_REGEXP_STRING_ITERATOR, /* u.regexp_string_iterator_data */ + JS_CLASS_GENERATOR, /* u.generator_data */ + JS_CLASS_DISPOSABLE_STACK, + JS_CLASS_PROXY, /* u.proxy_data */ + JS_CLASS_PROMISE, /* u.promise_data */ + JS_CLASS_PROMISE_RESOLVE_FUNCTION, /* u.promise_function_data */ + JS_CLASS_PROMISE_REJECT_FUNCTION, /* u.promise_function_data */ + JS_CLASS_ASYNC_FUNCTION, /* u.func */ + JS_CLASS_ASYNC_FUNCTION_RESOLVE, /* u.async_function_data */ + JS_CLASS_ASYNC_FUNCTION_REJECT, /* u.async_function_data */ + JS_CLASS_ASYNC_FROM_SYNC_ITERATOR, /* u.async_from_sync_iterator_data */ + JS_CLASS_ASYNC_GENERATOR_FUNCTION, /* u.func */ + JS_CLASS_ASYNC_GENERATOR, /* u.async_generator_data */ + JS_CLASS_ASYNC_DISPOSABLE_STACK, + JS_CLASS_WEAK_REF, + JS_CLASS_FINALIZATION_REGISTRY, + JS_CLASS_DOM_EXCEPTION, + JS_CLASS_CALL_SITE, + JS_CLASS_RAWJSON, + + JS_CLASS_INIT_COUNT, /* last entry for predefined classes */ +}; + +/* number of typed array types */ +#define JS_TYPED_ARRAY_COUNT (JS_CLASS_FLOAT64_ARRAY - JS_CLASS_UINT8C_ARRAY + 1) +static uint8_t const typed_array_size_log2[JS_TYPED_ARRAY_COUNT]; +#define typed_array_size_log2(classid) (typed_array_size_log2[(classid)- JS_CLASS_UINT8C_ARRAY]) + +typedef enum JSErrorEnum { + JS_EVAL_ERROR, + JS_RANGE_ERROR, + JS_REFERENCE_ERROR, + JS_SYNTAX_ERROR, + JS_TYPE_ERROR, + JS_URI_ERROR, + JS_INTERNAL_ERROR, + JS_AGGREGATE_ERROR, + JS_SUPPRESSED_ERROR, + + JS_NATIVE_ERROR_COUNT, /* number of different NativeError objects */ + JS_PLAIN_ERROR = JS_NATIVE_ERROR_COUNT +} JSErrorEnum; + +#define JS_MAX_LOCAL_VARS 65535 +#define JS_STACK_SIZE_MAX 65534 +#define JS_STRING_LEN_MAX ((1 << 30) - 1) +// 1,024 bytes is about the cutoff point where it starts getting +// more profitable to ref slice than to copy +#define JS_STRING_SLICE_LEN_MAX 1024 // in bytes + +/* strings <= this length are not concatenated using ropes. if too + small, the rope memory overhead becomes high. */ +#define JS_STRING_ROPE_SHORT_LEN 512 +/* specific threshold for initial rope use */ +#define JS_STRING_ROPE_SHORT2_LEN 8192 +/* rope depth at which we rebalance */ +#define JS_STRING_ROPE_MAX_DEPTH 60 + +#define __exception __attribute__((warn_unused_result)) + +typedef struct JSShape JSShape; +typedef struct JSString JSString; +typedef struct JSString JSAtomStruct; +typedef struct JSStringRope JSStringRope; + +#define JS_VALUE_GET_OBJ(v) ((JSObject *)JS_VALUE_GET_PTR(v)) +#define JS_VALUE_GET_STRING(v) ((JSString *)JS_VALUE_GET_PTR(v)) +#define JS_VALUE_GET_STRING_ROPE(v) ((JSStringRope *)JS_VALUE_GET_PTR(v)) + +typedef enum { + JS_GC_PHASE_NONE, + JS_GC_PHASE_DECREF, + JS_GC_PHASE_REMOVE_CYCLES, +} JSGCPhaseEnum; + +typedef struct JSMallocState { + size_t malloc_count; + size_t malloc_size; + size_t malloc_limit; + void *opaque; /* user opaque */ +} JSMallocState; + +/* Small-block "arena" allocator. + js_{malloc,free,realloc,calloc}_rt serve allocations up to ~512 + bytes from size-classed 4KB arenas carved out of the underlying + JSMallocFunctions allocator, so the backing allocator (system malloc, + mimalloc, ...) sees roughly one request per arena refill instead of one per + object. */ + +#define JS_ARENA_ALIGN 8 +#define JS_ARENA_SIZE 4096 +#define JS_ARENA_BLOCK_SIZE_COUNT 31 +#define JS_ARENA_MAX_SMALL_SIZE 512 +#define JS_ARENA_FREE_NIL 0xffff + +#if defined(__SANITIZE_ADDRESS__) || defined(FORCE_GC_AT_MALLOC) +/* Route every allocation through the backing malloc so each block is seen + individually: ASan can then poison a freed block, and a GC-stress build + (FORCE_GC_AT_MALLOC) exposes prematurely-freed objects instead of the pool + silently recycling them. */ +#define JS_ARENA_LARGE_BLOCKS_ONLY 1 +#else +#define JS_ARENA_LARGE_BLOCKS_ONLY 0 +#endif + +/* 8-byte header preceding every user allocation. It carries the allocator + bookkeeping (block_idx/free_next + block_size_idx) and, the + GC/refcount fields (gc_obj_type/mark/ref_count) that would otherwise sit in + the object body. The 8-byte size keeps user_data JS_ARENA_ALIGN-aligned. */ +typedef struct JSMallocBlockHeader { + union { + uint16_t block_idx; /* JS_ARENA_FREE_NIL => large or zero-size block */ + uint16_t free_next; /* next free block index while on a free list */ + } u; + uint8_t block_size_idx; + /* GC/refcount header merged into the allocator header: + the object body keeps only JSGCObjectHeader.link (no ref_count/flags). */ + uint8_t gc_obj_type : 7; /* JSGCObjectTypeEnum for GC objects */ + uint8_t mark : 1; /* used by the cycle collector */ + int ref_count; + _Alignas(JS_ARENA_ALIGN) uint8_t user_data[]; +} JSMallocBlockHeader; + +typedef struct JSArena { + struct list_head free_link; /* in free_arena_list[idx] while not full */ + struct list_head link; /* in arena_list[idx] for the whole lifetime */ + uint8_t block_size_idx; + uint16_t n_used_blocks; + uint16_t n_blocks; + uint16_t first_free_block; /* JS_ARENA_FREE_NIL if none */ + _Alignas(JS_ARENA_ALIGN) uint8_t blocks[]; +} JSArena; + +typedef struct JSArenaState { + struct list_head arena_list[JS_ARENA_BLOCK_SIZE_COUNT]; + struct list_head free_arena_list[JS_ARENA_BLOCK_SIZE_COUNT]; + _Alignas(JS_ARENA_ALIGN) uint8_t zero_size_block[sizeof(JSMallocBlockHeader)]; +} JSArenaState; + +typedef struct JSRuntimeFinalizerState { + struct JSRuntimeFinalizerState *next; + JSRuntimeFinalizer *finalizer; + void *arg; +} JSRuntimeFinalizerState; + +typedef struct JSValueLink { + struct JSValueLink *next; + JSValueConst value; +} JSValueLink; + +typedef struct JSClass { + uint32_t class_id; /* 0 means free entry */ + JSAtom class_name; + JSClassFinalizer *finalizer; + JSClassGCMark *gc_mark; + JSClassCall *call; + /* pointers for exotic behavior, can be NULL if none are present */ + const JSClassExoticMethods *exotic; +} JSClass; + +struct JSRuntime { + JSMallocFunctions mf; + JSMallocState malloc_state; + JSArenaState arena_state; + const char *rt_info; + + int atom_hash_size; /* power of two */ + int atom_count; + int atom_size; + int atom_count_resize; /* resize hash table at this count */ + uint32_t *atom_hash; + JSAtomStruct **atom_array; + int atom_free_index; /* 0 = none */ + + JSClassID js_class_id_alloc; /* counter for user defined classes */ + int class_count; /* size of class_array */ + JSClass *class_array; + + struct list_head context_list; /* list of JSContext.link */ + /* list of JSGCObjectHeader.link. List of allocated GC objects (used + by the garbage collector) */ + struct list_head gc_obj_list; + /* list of JSGCObjectHeader.link. Used during JS_FreeValueRT() */ + struct list_head gc_zero_ref_count_list; + struct list_head tmp_obj_list; /* used during GC */ + JSGCPhaseEnum gc_phase : 8; + size_t malloc_gc_threshold; +#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS + struct list_head string_list; /* list of JSString.link */ +#endif + /* stack limitation */ + uintptr_t stack_size; /* in bytes, 0 if no limit */ + uintptr_t stack_top; + uintptr_t stack_limit; /* lower stack limit */ + + JSValue current_exception; + /* true if inside an out of memory error, to avoid recursing */ + bool in_out_of_memory; + /* true if inside build_backtrace, to avoid recursing */ + bool in_build_stack_trace; + /* true if inside JS_FreeRuntime */ + bool in_free; + + struct JSStackFrame *current_stack_frame; + + JSInterruptHandler *interrupt_handler; + void *interrupt_opaque; + + JSPromiseHook *promise_hook; + void *promise_hook_opaque; + // for smuggling the parent promise from js_promise_then + // to js_promise_constructor + JSValueLink *parent_promise; + + JSHostPromiseRejectionTracker *host_promise_rejection_tracker; + void *host_promise_rejection_tracker_opaque; + + struct list_head job_list; /* list of JSJobEntry.link */ + + bool module_normalize_has_attr; + union { + JSModuleNormalizeFunc *module_normalize_func; + JSModuleNormalizeFunc2 *module_normalize_func2; + } normalize_u; + bool module_loader_has_attr; + union { + JSModuleLoaderFunc *module_loader_func; + JSModuleLoaderFunc2 *module_loader_func2; + } u; + JSModuleCheckSupportedImportAttributes *module_check_attrs; + void *module_loader_opaque; + /* timestamp for internal use in module evaluation */ + int64_t module_async_evaluation_next_timestamp; + + /* used to allocate, free and clone SharedArrayBuffers */ + JSSharedArrayBufferFunctions sab_funcs; + + bool can_block; /* true if Atomics.wait can block */ + uint32_t dump_flags : 24; + + /* Shape hash table */ + int shape_hash_bits; + int shape_hash_size; + int shape_hash_count; /* number of hashed shapes */ + JSShape **shape_hash; + void *user_opaque; + void *libc_opaque; + JSRuntimeFinalizerState *finalizers; +}; + +typedef struct JSStackFrame { + struct JSStackFrame *prev_frame; /* NULL if first stack frame */ + JSValue cur_func; /* current function, JS_UNDEFINED if the frame is detached */ + JSValue *arg_buf; /* arguments */ + JSValue *var_buf; /* variables */ + struct JSVarRef **var_refs; /* references to arguments or local variables */ + uint8_t *cur_pc; /* only used in bytecode functions : PC of the + instruction after the call */ + uint16_t var_ref_count; /* number of var refs */ + uint16_t arg_count; + bool is_strict_mode; + bool is_constructor; /* true if invoked as a constructor (new) */ + /* only used in generators. Current stack pointer value. NULL if + the function is running. */ + JSValue *cur_sp; + /* only set for coroutine frames (async function / generator / + async generator): the GC object owning this heap-allocated frame, + NULL for ordinary C-stack frames. Lets a var_ref capturing one of + the coroutine's locals keep the (suspended) coroutine reachable by + the cycle collector. */ + struct JSGCObjectHeader *cur_gc_obj; +} JSStackFrame; + +typedef enum { + JS_GC_OBJ_TYPE_JS_OBJECT, + JS_GC_OBJ_TYPE_FUNCTION_BYTECODE, + JS_GC_OBJ_TYPE_SHAPE, + JS_GC_OBJ_TYPE_VAR_REF, + JS_GC_OBJ_TYPE_ASYNC_FUNCTION, + JS_GC_OBJ_TYPE_JS_CONTEXT, +} JSGCObjectTypeEnum; + +/* header for GC objects. GC objects are C data structures with a + reference count that can reference other GC objects. JS Objects are + a particular type of GC object. */ +struct JSGCObjectHeader { + /* ref_count/gc_obj_type/mark live in the allocator block header (js_rc(p), + the 8 bytes before every allocation); only the GC list link remains in + the object body. */ + struct list_head link; +}; + +typedef struct JSVarRef { + JSGCObjectHeader header; /* {link}; must come first so &p->header == p */ + uint8_t is_detached; + uint8_t is_lexical; /* only used with global variables */ + uint8_t is_const; /* only used with global variables */ + /* Set at creation for an open var_ref that captures a local of a + coroutine (async function / generator / async generator): such a + var_ref holds a counted reference to that coroutine's GC object and + is itself a GC object, so the cycle collector can see the closure -> + var_ref -> coroutine edge and keep the suspended coroutine (hence the + captured variable it points into) alive. The coroutine is recovered + as stack_frame->cur_gc_obj (valid while open). This is a *snapshot* of + cur_gc_obj != NULL taken at creation, NOT rederived at read time: + cur_gc_obj transitions NULL -> owner (it is set only after a + generator's initial resume), so var_refs created during the prologue + (e.g. mapped `arguments`) must stay non-coro even though cur_gc_obj is + later set. Cleared when the var_ref is detached; the ref is released on + detach/free. */ + uint8_t is_coro; + JSValue *pvalue; /* pointer to the value, either on the stack or + to 'value' */ + union { + JSValue value; /* used when is_detached = true */ + struct { + uint16_t var_ref_idx; /* index in JSStackFrame.var_refs[] */ + JSStackFrame *stack_frame; + }; /* used when is_detached = false */ + }; +} JSVarRef; + +/* Accessors for the reference count and GC mark/type. These fields live in the + arena block header (the 8 bytes before every allocation, reached via + js_rc()), not in the object body. The macros yield lvalues, and the argument + is always the object/header pointer (whose address equals the GC header's, + since the header is the first member). */ +static inline JSMallocBlockHeader *js_rc(const void *p) { + return (JSMallocBlockHeader *)((uint8_t *)(uintptr_t)p - offsetof(JSMallocBlockHeader, user_data)); +} +#define JS_REF_COUNT(p) (js_rc(p)->ref_count) +#define JS_GC_TYPE(p) (js_rc(p)->gc_obj_type) +#define JS_GC_MARK(p) (js_rc(p)->mark) + +/* bigint */ +typedef int32_t js_slimb_t; +typedef uint32_t js_limb_t; +typedef int64_t js_sdlimb_t; +typedef uint64_t js_dlimb_t; + +#define JS_LIMB_DIGITS 9 + +/* Must match the size of short_big_int in JSValueUnion */ +#define JS_LIMB_BITS 32 +#define JS_SHORT_BIG_INT_BITS JS_LIMB_BITS +#define JS_BIGINT_MAX_SIZE ((1024 * 1024) / JS_LIMB_BITS) /* in limbs */ +#define JS_SHORT_BIG_INT_MIN INT32_MIN +#define JS_SHORT_BIG_INT_MAX INT32_MAX + + +typedef struct JSBigInt { + uint32_t len; /* number of limbs, >= 1 */ + js_limb_t tab[]; /* two's complement representation, always + normalized so that 'len' is the minimum + possible length >= 1 */ +} JSBigInt; + +/* this bigint structure can hold a 64 bit integer */ +typedef struct { + js_limb_t big_int_buf[sizeof(JSBigInt) / sizeof(js_limb_t)]; /* for JSBigInt */ + /* must come just after */ + js_limb_t tab[(64 + JS_LIMB_BITS - 1) / JS_LIMB_BITS]; +} JSBigIntBuf; + +typedef enum { + JS_AUTOINIT_ID_PROTOTYPE, + JS_AUTOINIT_ID_MODULE_NS, + JS_AUTOINIT_ID_PROP, + JS_AUTOINIT_ID_BYTECODE, +} JSAutoInitIDEnum; + +enum { + JS_BUILTIN_ARRAY_FROMASYNC = 1, + JS_BUILTIN_ITERATOR_ZIP, + JS_BUILTIN_ITERATOR_ZIP_KEYED, +}; + +/* must be large enough to have a negligible runtime cost and small + enough to call the interrupt callback often. */ +#define JS_INTERRUPT_COUNTER_INIT 10000 + +struct JSContext { + JSGCObjectHeader header; /* must come first */ + JSRuntime *rt; + struct list_head link; + + uint16_t binary_object_count; + uint32_t binary_object_size : 31; + + /* true if the array prototype is "normal": + - no small index properties which are get/set or non writable + - its prototype is Object.prototype + - Object.prototype has no small index properties which are get/set or non writable + - the prototype of Object.prototype is null (always true as it is immutable) + */ + uint8_t std_array_prototype : 1; + + JSShape *array_shape; /* initial shape for Array objects */ + JSShape *arguments_shape; /* shape for arguments objects */ + JSShape *mapped_arguments_shape; /* shape for mapped arguments objects */ + JSShape *regexp_shape; /* shape for regexp objects */ + JSShape *regexp_result_shape; /* shape for regexp result objects */ + + JSValue *class_proto; + JSValue function_proto; + JSValue function_ctor; + JSValue array_ctor; + JSValue regexp_ctor; + JSValue promise_ctor; + JSValue native_error_proto[JS_NATIVE_ERROR_COUNT]; + JSValue error_ctor; + JSValue error_back_trace; + JSValue error_prepare_stack; + JSValue error_stack_trace_limit; + JSValue iterator_ctor; + JSValue iterator_ctor_getset; + JSValue iterator_proto; + JSValue async_iterator_proto; + JSValue array_proto_values; + JSValue throw_type_error; + JSValue eval_obj; + + JSValue global_obj; /* global object */ + JSValue global_var_obj; /* contains the global let/const definitions */ + + double time_origin; + + uint64_t random_state; + uint32_t hash_seed; + + /* when the counter reaches zero, JSRutime.interrupt_handler is called */ + int interrupt_counter; + + struct list_head loaded_modules; /* list of JSModuleDef.link */ + + /* if NULL, RegExp compilation is not supported */ + JSValue (*compile_regexp)(JSContext *ctx, JSValueConst pattern, + JSValueConst flags); + /* if NULL, eval is not supported */ + JSValue (*eval_internal)(JSContext *ctx, JSValueConst this_obj, + const char *input, size_t input_len, + const char *filename, int line, int flags, int scope_idx); + void *user_opaque; +}; + +typedef union JSFloat64Union { + double d; + uint64_t u64; + uint32_t u32[2]; +} JSFloat64Union; + +typedef enum { + JS_WEAK_REF_KIND_MAP, + JS_WEAK_REF_KIND_WEAK_REF, + JS_WEAK_REF_KIND_FINALIZATION_REGISTRY_ENTRY, +} JSWeakRefKindEnum; + +typedef struct JSWeakRefRecord { + JSWeakRefKindEnum kind; + struct JSWeakRefRecord *next_weak_ref; + union { + struct JSMapRecord *map_record; + struct JSWeakRefData *weak_ref_data; + struct JSFinRecEntry *fin_rec_entry; + } u; +} JSWeakRefRecord; + +typedef struct JSMapRecord { + int ref_count; /* used during enumeration to avoid freeing the record */ + bool empty; /* true if the record is deleted */ + struct JSMapState *map; + struct list_head link; + struct list_head hash_link; + JSValue key; + JSValue value; +} JSMapRecord; + +typedef struct JSMapState { + bool is_weak; /* true if WeakSet/WeakMap */ + struct list_head records; /* list of JSMapRecord.link */ + uint32_t record_count; + struct list_head *hash_table; + uint32_t hash_size; /* must be a power of two */ + uint32_t record_count_threshold; /* count at which a hash table + resize is needed */ +} JSMapState; + +enum +{ + JS_TO_STRING_IS_PROPERTY_KEY = 1 << 0, + JS_TO_STRING_NO_SIDE_EFFECTS = 1 << 1, +}; + +enum { + JS_ATOM_TYPE_STRING = 1, + JS_ATOM_TYPE_GLOBAL_SYMBOL, + JS_ATOM_TYPE_SYMBOL, + JS_ATOM_TYPE_PRIVATE, +}; + +enum { + JS_ATOM_HASH_SYMBOL, + JS_ATOM_HASH_PRIVATE, +}; + +typedef enum { + JS_ATOM_KIND_STRING, + JS_ATOM_KIND_SYMBOL, + JS_ATOM_KIND_PRIVATE, +} JSAtomKindEnum; + +typedef enum { + JS_STRING_KIND_NORMAL, + JS_STRING_KIND_SLICE, + JS_STRING_KIND_INDIRECT, +} JSStringKind; + +#define JS_ATOM_HASH_MASK ((1 << 28) - 1) + +struct JSString { + uint32_t len : 31; + uint32_t is_wide_char : 1; /* 0 = 8 bits, 1 = 16 bits characters */ + /* for JS_ATOM_TYPE_SYMBOL: hash = 0, atom_type = 3, + for JS_ATOM_TYPE_PRIVATE: hash = 1, atom_type = 3 + XXX: could change encoding to have one more bit in hash */ + uint32_t hash : 28; + uint32_t kind : 2; + uint32_t atom_type : 2; /* != 0 if atom, JS_ATOM_TYPE_x */ + uint32_t hash_next; /* atom_index for JS_ATOM_TYPE_SYMBOL */ + JSWeakRefRecord *first_weak_ref; +#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS + struct list_head link; /* string list */ +#endif +}; + +typedef struct JSStringSlice { + JSString *parent; + uint32_t start; // in bytes, not characters +} JSStringSlice; + +struct JSStringRope { + uint32_t len; + uint8_t is_wide_char; /* 0 = 8 bits, 1 = 16 bits characters */ + uint8_t depth; /* max depth of the rope tree */ + JSValue left; + JSValue right; /* might be the empty string */ +}; + +static inline void *strv(JSString *p) +{ + JSStringSlice *slice; + void **indirect; + + switch (p->kind) { + case JS_STRING_KIND_NORMAL: + return (void *)&p[1]; + case JS_STRING_KIND_SLICE: + slice = (void *)&p[1]; + return (char *)&slice->parent[1] + slice->start; + case JS_STRING_KIND_INDIRECT: + indirect = (void *)&p[1]; + return *indirect; + } + abort(); + return NULL; +} + +static inline uint8_t *str8(JSString *p) +{ + return strv(p); +} + +static inline uint16_t *str16(JSString *p) +{ + return strv(p); +} + +typedef enum { + JS_CLOSURE_LOCAL, /* 'var_idx' is the index of a local variable in the parent function */ + JS_CLOSURE_ARG, /* 'var_idx' is the index of an argument variable in the parent function */ + JS_CLOSURE_REF, /* 'var_idx' is the index of a closure variable in the parent function */ + JS_CLOSURE_GLOBAL_REF, /* 'var_idx' is the index of a closure variable in the parent + function referencing a global variable */ + JS_CLOSURE_GLOBAL_DECL, /* global variable declaration (eval code only) */ + JS_CLOSURE_GLOBAL, /* global variable (eval code only) */ + JS_CLOSURE_MODULE_DECL, /* definition of a module variable (eval code only) */ + JS_CLOSURE_MODULE_IMPORT, /* definition of a module import (eval code only) */ +} JSClosureTypeEnum; + +typedef struct JSClosureVar { + uint8_t closure_type : 3; /* see JSClosureTypeEnum */ + uint8_t is_lexical : 1; /* lexical variable */ + uint8_t is_const : 1; /* const variable (is_lexical = 1 if is_const = 1) */ + uint8_t var_kind : 4; /* see JSVarKindEnum */ + /* 7 bits available */ + uint16_t var_idx; /* JS_CLOSURE_LOCAL/JS_CLOSURE_ARG: index to a normal variable of the + parent function. otherwise: index to a closure + variable of the parent function */ + JSAtom var_name; +} JSClosureVar; + +#define ARG_SCOPE_INDEX 1 +#define ARG_SCOPE_END (-2) + +typedef struct JSVarScope { + int parent; /* index into fd->scopes of the enclosing scope */ + int first; /* index into fd->vars of the last variable in this scope */ + uint8_t has_using : 1; /* scope has using declarations */ + uint8_t is_await_using : 1; /* scope has await using declarations */ + int using_label_catch; /* label for catch handler (-1 if none) */ + int using_label_end; /* label for end of disposal block (-1 if none) */ +} JSVarScope; + +typedef enum { + /* XXX: add more variable kinds here instead of using bit fields */ + JS_VAR_NORMAL, + JS_VAR_FUNCTION_DECL, /* lexical var with function declaration */ + JS_VAR_NEW_FUNCTION_DECL, /* lexical var with async/generator + function declaration */ + JS_VAR_CATCH, + JS_VAR_FUNCTION_NAME, /* function expression name */ + JS_VAR_PRIVATE_FIELD, + JS_VAR_PRIVATE_METHOD, + JS_VAR_PRIVATE_GETTER, + JS_VAR_PRIVATE_SETTER, /* must come after JS_VAR_PRIVATE_GETTER */ + JS_VAR_PRIVATE_GETTER_SETTER, /* must come after JS_VAR_PRIVATE_SETTER */ + JS_VAR_USING, /* using declaration variable */ + JS_VAR_USING_METHOD, /* hidden local holding the cached dispose method + for the preceding JS_VAR_USING var (always + allocated immediately after it). */ +} JSVarKindEnum; + +/* XXX: could use a different structure in bytecode functions to save + memory */ +typedef struct JSVarDef { + JSAtom var_name; + /* index into fd->scopes of this variable lexical scope */ + int scope_level; + /* during compilation: + - if scope_level = 0: scope in which the variable is defined + - if scope_level != 0: index into fd->vars of the next + variable in the same or enclosing lexical scope + in a bytecode function: + index into fd->vars of the next + variable in the same or enclosing lexical scope + */ + int scope_next; + uint8_t is_const : 1; + uint8_t is_lexical : 1; + uint8_t is_captured : 1; + uint8_t is_static_private : 1; /* only used during private class field parsing */ + uint8_t var_kind : 4; /* see JSVarKindEnum */ + /* if is_captured = true, provides the index of the corresponding + JSVarRef on stack */ + uint16_t var_ref_idx; + /* only used during compilation: function pool index for lexical + variables with var_kind = + JS_VAR_FUNCTION_DECL/JS_VAR_NEW_FUNCTION_DECL or scope level of + the definition of the 'var' variables (they have scope_level = + 0) */ + int func_pool_idx; /* only used during compilation : index in + the constant pool for hoisted function + definition */ +} JSVarDef; + +/* for the encoding of the pc2line table */ +#define PC2LINE_BASE (-1) +#define PC2LINE_RANGE 5 +#define PC2LINE_OP_FIRST 1 +#define PC2LINE_DIFF_PC_MAX ((255 - PC2LINE_OP_FIRST) / PC2LINE_RANGE) + +typedef enum JSFunctionKindEnum { + JS_FUNC_NORMAL = 0, + JS_FUNC_GENERATOR = (1 << 0), + JS_FUNC_ASYNC = (1 << 1), + JS_FUNC_ASYNC_GENERATOR = (JS_FUNC_GENERATOR | JS_FUNC_ASYNC), +} JSFunctionKindEnum; + +typedef struct JSFunctionBytecode { + JSGCObjectHeader header; /* must come first */ + uint8_t is_strict_mode : 1; + uint8_t has_prototype : 1; /* true if a prototype field is necessary */ + uint8_t has_simple_parameter_list : 1; + uint8_t is_derived_class_constructor : 1; + /* true if home_object needs to be initialized */ + uint8_t need_home_object : 1; + uint8_t func_kind : 2; + uint8_t new_target_allowed : 1; + uint8_t super_call_allowed : 1; + uint8_t super_allowed : 1; + uint8_t arguments_allowed : 1; + uint8_t backtrace_barrier : 1; /* stop backtrace on this function */ + /* XXX: 5 bits available */ + uint8_t *byte_code_buf; /* (self pointer) */ + int byte_code_len; + JSAtom func_name; + JSVarDef *vardefs; /* arguments + local variables (arg_count + var_count) (self pointer) */ + JSClosureVar *closure_var; /* list of variables in the closure (self pointer) */ + uint16_t arg_count; + uint16_t var_count; + uint16_t defined_arg_count; /* for length function property */ + uint16_t stack_size; /* maximum stack size */ + uint16_t var_ref_count; /* number of local variable references */ + uint16_t closure_var_count; + int cpool_count; + JSContext *realm; /* function realm */ + JSValue *cpool; /* constant pool (self pointer) */ + JSAtom filename; + int line_num; + int col_num; + int source_len; + int pc2line_len; + uint8_t *pc2line_buf; + char *source; +} JSFunctionBytecode; + +typedef struct JSBoundFunction { + JSValue func_obj; + JSValue this_val; + int argc; + JSValue argv[]; +} JSBoundFunction; + +typedef enum JSIteratorKindEnum { + JS_ITERATOR_KIND_KEY, + JS_ITERATOR_KIND_VALUE, + JS_ITERATOR_KIND_KEY_AND_VALUE, +} JSIteratorKindEnum; + +typedef enum JSIteratorHelperKindEnum { + JS_ITERATOR_HELPER_KIND_CHUNKS, + JS_ITERATOR_HELPER_KIND_DROP, + JS_ITERATOR_HELPER_KIND_EVERY, + JS_ITERATOR_HELPER_KIND_FILTER, + JS_ITERATOR_HELPER_KIND_FIND, + JS_ITERATOR_HELPER_KIND_FLAT_MAP, + JS_ITERATOR_HELPER_KIND_FOR_EACH, + JS_ITERATOR_HELPER_KIND_MAP, + JS_ITERATOR_HELPER_KIND_SOME, + JS_ITERATOR_HELPER_KIND_TAKE, + JS_ITERATOR_HELPER_KIND_WINDOWS, +} JSIteratorHelperKindEnum; + +typedef struct JSForInIterator { + JSValue obj; + bool is_array; + uint32_t array_length; + uint32_t idx; +} JSForInIterator; + +typedef struct JSRegExp { + JSString *pattern; + JSString *bytecode; /* also contains the flags */ +} JSRegExp; + +typedef struct JSProxyData { + JSValue target; + JSValue handler; + uint8_t is_func; + uint8_t is_revoked; +} JSProxyData; + +typedef struct JSArrayBuffer { + int byte_length; /* 0 if detached */ + int max_byte_length; /* -1 if not resizable; >= byte_length otherwise */ + uint8_t detached; + uint8_t immutable; + uint8_t shared; /* if shared, the array buffer cannot be detached */ + uint8_t *data; /* NULL if detached */ + struct list_head array_list; + void *opaque; + JSReallocArrayBufferDataFunc *realloc_func; +} JSArrayBuffer; + +typedef struct JSTypedArray { + struct list_head link; /* link to arraybuffer */ + JSObject *obj; /* back pointer to the TypedArray/DataView object */ + JSObject *buffer; /* based array buffer */ + uint32_t offset; /* byte offset in the array buffer */ + uint32_t length; /* byte length in the array buffer */ + bool track_rab; /* auto-track length of backing array buffer */ +} JSTypedArray; + +typedef struct JSAsyncFunctionState { + JSValue this_val; /* 'this' generator argument */ + int argc; /* number of function arguments */ + bool throw_flag; /* used to throw an exception in JS_CallInternal() */ + JSStackFrame frame; +} JSAsyncFunctionState; + +/* XXX: could use an object instead to avoid the + JS_TAG_ASYNC_FUNCTION tag for the GC */ +typedef struct JSAsyncFunctionData { + JSGCObjectHeader header; /* must come first */ + JSValue resolving_funcs[2]; + bool is_active; /* true if the async function state is valid */ + JSAsyncFunctionState func_state; +} JSAsyncFunctionData; + +typedef struct JSReqModuleEntry { + JSAtom module_name; + JSModuleDef *module; /* used using resolution */ + JSValue attributes; /* JS_UNDEFINED or an object containing the attributes as key/value */ +} JSReqModuleEntry; + +typedef enum JSExportTypeEnum { + JS_EXPORT_TYPE_LOCAL, + JS_EXPORT_TYPE_INDIRECT, +} JSExportTypeEnum; + +typedef struct JSExportEntry { + union { + struct { + int var_idx; /* closure variable index */ + JSVarRef *var_ref; /* if != NULL, reference to the variable */ + } local; /* for local export */ + int req_module_idx; /* module for indirect export */ + } u; + JSExportTypeEnum export_type; + JSAtom local_name; /* '*' if export ns from. not used for local + export after compilation */ + JSAtom export_name; /* exported variable name */ +} JSExportEntry; + +typedef struct JSStarExportEntry { + int req_module_idx; /* in req_module_entries */ +} JSStarExportEntry; + +typedef struct JSImportEntry { + int var_idx; /* closure variable index */ + JSAtom import_name; + int req_module_idx; /* in req_module_entries */ +} JSImportEntry; + +typedef enum { + JS_MODULE_STATUS_UNLINKED, + JS_MODULE_STATUS_LINKING, + JS_MODULE_STATUS_LINKED, + JS_MODULE_STATUS_EVALUATING, + JS_MODULE_STATUS_EVALUATING_ASYNC, + JS_MODULE_STATUS_EVALUATED, +} JSModuleStatus; + +struct JSModuleDef { + JSAtom module_name; + struct list_head link; + + JSReqModuleEntry *req_module_entries; + int req_module_entries_count; + int req_module_entries_size; + + JSExportEntry *export_entries; + int export_entries_count; + int export_entries_size; + + JSStarExportEntry *star_export_entries; + int star_export_entries_count; + int star_export_entries_size; + + JSImportEntry *import_entries; + int import_entries_count; + int import_entries_size; + + /* import attributes the module was requested with, JS_UNDEFINED if + none; the same module name can be loaded with different attributes + (e.g. as both a JS module and a text module) */ + JSValue attributes; + + JSValue module_ns; + JSValue func_obj; /* only used for JS modules */ + JSModuleInitFunc *init_func; /* only used for C modules */ + bool has_tla; /* true if func_obj contains await */ + bool resolved; + bool func_created; + JSModuleStatus status : 8; + /* temp use during js_module_link() & js_module_evaluate() */ + int dfs_index, dfs_ancestor_index; + JSModuleDef *stack_prev; + /* temp use during js_module_evaluate() */ + JSModuleDef **async_parent_modules; + int async_parent_modules_count; + int async_parent_modules_size; + int pending_async_dependencies; + bool async_evaluation; + int64_t async_evaluation_timestamp; + JSModuleDef *cycle_root; + JSValue promise; /* corresponds to spec field: capability */ + JSValue resolving_funcs[2]; /* corresponds to spec field: capability */ + /* true if evaluation yielded an exception. It is saved in + eval_exception */ + bool eval_has_exception; + JSValue eval_exception; + JSValue meta_obj; /* for import.meta */ + JSValue private_value; /* private value for C modules */ +}; + +typedef struct JSJobEntry { + struct list_head link; + JSContext *ctx; + JSJobFunc *job_func; + int argc; + JSValue argv[]; +} JSJobEntry; + +typedef struct JSProperty { + union { + JSValue value; /* JS_PROP_NORMAL */ + struct { /* JS_PROP_GETSET */ + JSObject *getter; /* NULL if undefined */ + JSObject *setter; /* NULL if undefined */ + } getset; + JSVarRef *var_ref; /* JS_PROP_VARREF */ + struct { /* JS_PROP_AUTOINIT */ + /* in order to use only 2 pointers, we compress the realm + and the init function pointer */ + uintptr_t realm_and_id; /* realm and init_id (JS_AUTOINIT_ID_x) + in the 2 low bits */ + void *opaque; + } init; + } u; +} JSProperty; + +#define JS_PROP_INITIAL_SIZE 2 +#define JS_PROP_INITIAL_HASH_SIZE 4 /* must be a power of two */ + +typedef struct JSShapeProperty { + uint32_t hash_next : 26; /* 0 if last in list */ + uint32_t flags : 6; /* JS_PROP_XXX */ + JSAtom atom; /* JS_ATOM_NULL = free property entry */ +} JSShapeProperty; + +struct JSShape { + /* hash table of size hash_mask + 1 before the start of the + structure (see prop_hash_end()). */ + JSGCObjectHeader header; + /* true if the shape is inserted in the shape hash table. If not, + JSShape.hash is not valid */ + uint8_t is_hashed; + uint32_t hash; /* current hash value */ + uint32_t prop_hash_mask; + int prop_size; /* allocated properties */ + int prop_count; /* include deleted properties */ + int deleted_prop_count; + JSShape *shape_hash_next; /* in JSRuntime.shape_hash[h] list */ + JSObject *proto; + uint32_t hash_table[]; /* prop_hash_mask + 1 elements, then prop[prop_size] */ +}; + +struct JSObject { + /* ref_count/gc_obj_type/mark live in the allocator block header; the object + body keeps only the GC list link plus the object's own flags. */ + JSGCObjectHeader header; /* {link}; must come first so &p->header == p */ + uint8_t is_prototype : 1; /* object may be used as prototype */ + uint8_t extensible : 1; + uint8_t free_mark : 1; /* only used when freeing objects with cycles */ + uint8_t is_exotic : 1; /* true if object has exotic property handlers */ + uint8_t fast_array : 1; /* true if u.array is used for get/put (for JS_CLASS_ARRAY, JS_CLASS_ARGUMENTS, JS_CLASS_MAPPED_ARGUMENTS and typed arrays) */ + uint8_t is_constructor : 1; /* true if object is a constructor function */ + uint8_t is_uncatchable_error : 1; /* if true, error is not catchable */ + uint8_t tmp_mark : 1; /* used in JS_WriteObjectRec() */ + uint8_t is_HTMLDDA : 1; /* specific annex B IsHtmlDDA behavior */ + uint16_t class_id; /* see JS_CLASS_x */ + /* byte offsets: 16/24 */ + JSShape *shape; /* prototype and property names + flag */ + JSProperty *prop; /* array of properties */ + /* byte offsets: 24/40 */ + JSWeakRefRecord *first_weak_ref; + /* byte offsets: 28/48 */ + union { + void *opaque; + struct JSBoundFunction *bound_function; /* JS_CLASS_BOUND_FUNCTION */ + struct JSCFunctionDataRecord *c_function_data_record; /* JS_CLASS_C_FUNCTION_DATA */ + struct JSCClosureRecord *c_closure_record; /* JS_CLASS_C_CLOSURE */ + struct JSForInIterator *for_in_iterator; /* JS_CLASS_FOR_IN_ITERATOR */ + struct JSArrayBuffer *array_buffer; /* JS_CLASS_ARRAY_BUFFER, JS_CLASS_SHARED_ARRAY_BUFFER */ + struct JSTypedArray *typed_array; /* JS_CLASS_UINT8C_ARRAY..JS_CLASS_DATAVIEW */ + struct JSMapState *map_state; /* JS_CLASS_MAP..JS_CLASS_WEAKSET */ + struct JSMapIteratorData *map_iterator_data; /* JS_CLASS_MAP_ITERATOR, JS_CLASS_SET_ITERATOR */ + struct JSArrayIteratorData *array_iterator_data; /* JS_CLASS_ARRAY_ITERATOR, JS_CLASS_STRING_ITERATOR */ + struct JSRegExpStringIteratorData *regexp_string_iterator_data; /* JS_CLASS_REGEXP_STRING_ITERATOR */ + struct JSGeneratorData *generator_data; /* JS_CLASS_GENERATOR */ + struct JSIteratorConcatData *iterator_concat_data; /* JS_CLASS_ITERATOR_CONCAT */ + struct JSIteratorHelperData *iterator_helper_data; /* JS_CLASS_ITERATOR_HELPER */ + struct JSIteratorWrapData *iterator_wrap_data; /* JS_CLASS_ITERATOR_WRAP */ + struct JSProxyData *proxy_data; /* JS_CLASS_PROXY */ + struct JSPromiseData *promise_data; /* JS_CLASS_PROMISE */ + struct JSPromiseFunctionData *promise_function_data; /* JS_CLASS_PROMISE_RESOLVE_FUNCTION, JS_CLASS_PROMISE_REJECT_FUNCTION */ + struct JSAsyncFunctionData *async_function_data; /* JS_CLASS_ASYNC_FUNCTION_RESOLVE, JS_CLASS_ASYNC_FUNCTION_REJECT */ + struct JSAsyncFromSyncIteratorData *async_from_sync_iterator_data; /* JS_CLASS_ASYNC_FROM_SYNC_ITERATOR */ + struct JSAsyncGeneratorData *async_generator_data; /* JS_CLASS_ASYNC_GENERATOR */ + struct { /* JS_CLASS_BYTECODE_FUNCTION: 12/24 bytes */ + /* also used by JS_CLASS_GENERATOR_FUNCTION, JS_CLASS_ASYNC_FUNCTION and JS_CLASS_ASYNC_GENERATOR_FUNCTION */ + struct JSFunctionBytecode *function_bytecode; + JSVarRef **var_refs; + JSObject *home_object; /* for 'super' access */ + } func; + struct { /* JS_CLASS_C_FUNCTION: 12/20 bytes */ + JSContext *realm; + JSCFunctionType c_function; + uint8_t length; + uint8_t cproto; + int16_t magic; + } cfunc; + /* array part for fast arrays and typed arrays */ + struct { /* JS_CLASS_ARRAY, JS_CLASS_ARGUMENTS, JS_CLASS_MAPPED_ARGUMENTS, JS_CLASS_UINT8C_ARRAY..JS_CLASS_FLOAT64_ARRAY */ + union { + uint32_t size; /* JS_CLASS_ARRAY */ + struct JSTypedArray *typed_array; /* JS_CLASS_UINT8C_ARRAY..JS_CLASS_FLOAT64_ARRAY */ + } u1; + union { + JSValue *values; /* JS_CLASS_ARRAY, JS_CLASS_ARGUMENTS */ + JSVarRef **var_refs; /* JS_CLASS_MAPPED_ARGUMENTS */ + void *ptr; /* JS_CLASS_UINT8C_ARRAY..JS_CLASS_FLOAT64_ARRAY */ + int8_t *int8_ptr; /* JS_CLASS_INT8_ARRAY */ + uint8_t *uint8_ptr; /* JS_CLASS_UINT8_ARRAY, JS_CLASS_UINT8C_ARRAY */ + int16_t *int16_ptr; /* JS_CLASS_INT16_ARRAY */ + uint16_t *uint16_ptr; /* JS_CLASS_UINT16_ARRAY */ + int32_t *int32_ptr; /* JS_CLASS_INT32_ARRAY */ + uint32_t *uint32_ptr; /* JS_CLASS_UINT32_ARRAY */ + int64_t *int64_ptr; /* JS_CLASS_INT64_ARRAY */ + uint64_t *uint64_ptr; /* JS_CLASS_UINT64_ARRAY */ + uint16_t *fp16_ptr; /* JS_CLASS_FLOAT16_ARRAY */ + float *float_ptr; /* JS_CLASS_FLOAT32_ARRAY */ + double *double_ptr; /* JS_CLASS_FLOAT64_ARRAY */ + } u; + uint32_t count; /* <= 2^31-1. 0 for a detached typed array */ + } array; /* 12/20 bytes */ + JSRegExp regexp; /* JS_CLASS_REGEXP: 8/16 bytes */ + JSValue object_data; /* for JS_SetObjectData(): 8/16/16 bytes */ + } u; + /* byte sizes: 40/48/72 */ +}; + +typedef struct JSCallSiteData { + JSValue filename; + JSValue func; + JSValue func_name; + bool native; + bool constructor; + int line_num; + int col_num; +} JSCallSiteData; + +enum { + __JS_ATOM_NULL = JS_ATOM_NULL, +#define DEF(name, str) JS_ATOM_ ## name, +#include "quickjs-atom.h" +#undef DEF + JS_ATOM_END, +}; +#define JS_ATOM_LAST_KEYWORD JS_ATOM_using +#define JS_ATOM_LAST_STRICT_KEYWORD JS_ATOM_yield + +static const char js_atom_init[] = +#define DEF(name, str) str "\0" +#include "quickjs-atom.h" +#undef DEF +; + +typedef enum OPCodeFormat { +#define FMT(f) OP_FMT_ ## f, +#define DEF(id, size, n_pop, n_push, f) +#include "quickjs-opcode.h" +#undef DEF +#undef FMT +} OPCodeFormat; + +typedef enum OPCodeEnum { +#define FMT(f) +#define DEF(id, size, n_pop, n_push, f) OP_ ## id, +#define def(id, size, n_pop, n_push, f) +#include "quickjs-opcode.h" +#undef def +#undef DEF +#undef FMT + OP_COUNT, /* excluding temporary opcodes */ + /* temporary opcodes : overlap with the short opcodes */ + OP_TEMP_START = OP_nop + 1, + OP___dummy = OP_TEMP_START - 1, +#define FMT(f) +#define DEF(id, size, n_pop, n_push, f) +#define def(id, size, n_pop, n_push, f) OP_ ## id, +#include "quickjs-opcode.h" +#undef def +#undef DEF +#undef FMT + OP_TEMP_END, +} OPCodeEnum; + +static int JS_InitAtoms(JSRuntime *rt); +static JSAtom __JS_NewAtomInit(JSRuntime *rt, const char *str, int len, + int atom_type); +static void JS_FreeAtomStruct(JSRuntime *rt, JSAtomStruct *p); +static void free_function_bytecode(JSRuntime *rt, JSFunctionBytecode *b); +static JSValue js_call_c_function(JSContext *ctx, JSValueConst func_obj, + JSValueConst this_obj, + int argc, JSValueConst *argv, int flags); +static JSValue js_call_bound_function(JSContext *ctx, JSValueConst func_obj, + JSValueConst this_obj, + int argc, JSValueConst *argv, int flags); +static JSValue JS_CallInternal(JSContext *ctx, JSValueConst func_obj, + JSValueConst this_obj, JSValueConst new_target, + int argc, JSValueConst *argv, int flags); +static JSValue JS_CallConstructorInternal(JSContext *ctx, + JSValueConst func_obj, + JSValueConst new_target, + int argc, JSValueConst *argv, int flags); +static JSValue JS_CallFree(JSContext *ctx, JSValue func_obj, JSValueConst this_obj, + int argc, JSValueConst *argv); +static JSValue JS_InvokeFree(JSContext *ctx, JSValue this_val, JSAtom atom, + int argc, JSValueConst *argv); +static __exception int JS_ToArrayLengthFree(JSContext *ctx, uint32_t *plen, + JSValue val, bool is_array_ctor); +static JSValue JS_EvalObject(JSContext *ctx, JSValueConst this_obj, + JSValueConst val, int flags, int scope_idx); +static JSValue js_new_suppressed_error(JSContext *ctx, JSValueConst error, + JSValueConst suppressed); +static __maybe_unused void JS_DumpString(JSRuntime *rt, JSString *p); +static __maybe_unused void JS_DumpObjectHeader(JSRuntime *rt); +static __maybe_unused void JS_DumpObject(JSRuntime *rt, JSObject *p); +static __maybe_unused void JS_DumpGCObject(JSRuntime *rt, JSGCObjectHeader *p); +static __maybe_unused void JS_DumpValue(JSRuntime *rt, JSValueConst val); +static __maybe_unused void JS_DumpAtoms(JSRuntime *rt); +static __maybe_unused void JS_DumpShapes(JSRuntime *rt); + +static JSValue js_function_apply(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int magic); +static void js_array_finalizer(JSRuntime *rt, JSValueConst val); +static void js_array_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_mapped_arguments_finalizer(JSRuntime *rt, JSValueConst val); +static void js_mapped_arguments_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_object_data_finalizer(JSRuntime *rt, JSValueConst val); +static void js_object_data_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_c_function_finalizer(JSRuntime *rt, JSValueConst val); +static void js_c_function_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_bytecode_function_finalizer(JSRuntime *rt, JSValueConst val); +static void js_bytecode_function_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_bound_function_finalizer(JSRuntime *rt, JSValueConst val); +static void js_bound_function_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_for_in_iterator_finalizer(JSRuntime *rt, JSValueConst val); +static void js_for_in_iterator_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_regexp_finalizer(JSRuntime *rt, JSValueConst val); +static void js_array_buffer_finalizer(JSRuntime *rt, JSValueConst val); +static void js_typed_array_finalizer(JSRuntime *rt, JSValueConst val); +static void js_typed_array_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_proxy_finalizer(JSRuntime *rt, JSValueConst val); +static void js_proxy_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_map_finalizer(JSRuntime *rt, JSValueConst val); +static void js_map_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_map_iterator_finalizer(JSRuntime *rt, JSValueConst val); +static void js_map_iterator_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_array_iterator_finalizer(JSRuntime *rt, JSValueConst val); +static void js_array_iterator_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_iterator_concat_finalizer(JSRuntime *rt, JSValueConst val); +static void js_iterator_concat_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_iterator_helper_finalizer(JSRuntime *rt, JSValueConst val); +static void js_iterator_helper_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_iterator_wrap_finalizer(JSRuntime *rt, JSValueConst val); +static void js_iterator_wrap_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_regexp_string_iterator_finalizer(JSRuntime *rt, + JSValueConst val); +static void js_regexp_string_iterator_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_generator_finalizer(JSRuntime *rt, JSValueConst val); +static void js_generator_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_promise_finalizer(JSRuntime *rt, JSValueConst val); +static void js_promise_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_promise_resolve_function_finalizer(JSRuntime *rt, JSValueConst val); +static void js_promise_resolve_function_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_disposable_stack_finalizer(JSRuntime *rt, JSValueConst val); +static void js_disposable_stack_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); + +#define HINT_STRING 0 +#define HINT_NUMBER 1 +#define HINT_NONE 2 +#define HINT_FORCE_ORDINARY (1 << 4) // don't try Symbol.toPrimitive +static JSValue JS_ToPrimitiveFree(JSContext *ctx, JSValue val, int hint); +static JSValue JS_ToStringFree(JSContext *ctx, JSValue val); +static int JS_ToBoolFree(JSContext *ctx, JSValue val); +static int JS_ToInt32Free(JSContext *ctx, int32_t *pres, JSValue val); +static int JS_ToFloat64Free(JSContext *ctx, double *pres, JSValue val); +static int JS_ToUint8ClampFree(JSContext *ctx, int32_t *pres, JSValue val); +static JSValue JS_ToPropertyKeyInternal(JSContext *ctx, JSValueConst val, + int flags); +static JSValue js_new_string8_len(JSContext *ctx, const char *buf, int len); +static JSValue js_compile_regexp(JSContext *ctx, JSValueConst pattern, + JSValueConst flags); +static JSValue js_regexp_constructor_internal(JSContext *ctx, JSValueConst ctor, + JSValue pattern, JSValue bc); +static void gc_decref(JSRuntime *rt); +static int JS_NewClass1(JSRuntime *rt, JSClassID class_id, + const JSClassDef *class_def, JSAtom name); +static JSValue js_array_push(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int unshift); +static JSValue js_array_constructor(JSContext *ctx, JSValueConst new_target, + int argc, JSValueConst *argv); +static JSValue js_error_constructor(JSContext *ctx, JSValueConst new_target, + int argc, JSValueConst *argv, int magic); +static JSValue js_object_defineProperty(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int magic); +static uint64_t xorshift64star(uint64_t *pstate); + +typedef enum JSStrictEqModeEnum { + JS_EQ_STRICT, + JS_EQ_SAME_VALUE, + JS_EQ_SAME_VALUE_ZERO, +} JSStrictEqModeEnum; + +static bool js_strict_eq2(JSContext *ctx, JSValueConst op1, JSValueConst op2, + JSStrictEqModeEnum eq_mode); +static bool js_strict_eq(JSContext *ctx, JSValueConst op1, JSValueConst op2); +static bool js_same_value(JSContext *ctx, JSValueConst op1, JSValueConst op2); +static bool js_same_value_zero(JSContext *ctx, JSValueConst op1, JSValueConst op2); +static JSValue JS_ToObjectFree(JSContext *ctx, JSValue val); +static JSProperty *add_property(JSContext *ctx, + JSObject *p, JSAtom prop, int prop_flags); +static void free_property(JSRuntime *rt, JSProperty *pr, int prop_flags); +static int JS_ToBigInt64Free(JSContext *ctx, int64_t *pres, JSValue val); +static JSValue JS_ThrowStackOverflow(JSContext *ctx); +static JSValue JS_ThrowTypeErrorRevokedProxy(JSContext *ctx); +static JSValue js_proxy_getPrototypeOf(JSContext *ctx, JSValueConst obj); +static int js_proxy_setPrototypeOf(JSContext *ctx, JSValueConst obj, + JSValueConst proto_val, bool throw_flag); +static int js_proxy_isExtensible(JSContext *ctx, JSValueConst obj); +static int js_proxy_preventExtensions(JSContext *ctx, JSValueConst obj); +static int js_proxy_isArray(JSContext *ctx, JSValueConst obj); +static int JS_CreateProperty(JSContext *ctx, JSObject *p, + JSAtom prop, JSValueConst val, + JSValueConst getter, JSValueConst setter, + int flags); +static int js_string_memcmp(JSString *p1, JSString *p2, int len); +static void reset_weak_ref(JSRuntime *rt, JSWeakRefRecord **first_weak_ref); +static bool is_valid_weakref_target(JSValueConst val); +static void insert_weakref_record(JSValueConst target, + struct JSWeakRefRecord *wr); +static JSValue js_array_buffer_constructor3(JSContext *ctx, + JSValueConst new_target, + uint64_t len, uint64_t *max_len, + JSClassID class_id, + uint8_t *buf, + JSReallocArrayBufferDataFunc *realloc_func, + void *opaque, bool alloc_flag); +static void *js_array_buffer_realloc(JSRuntime *rt, void *opaque, void *ptr, + size_t size); +static JSArrayBuffer *js_get_array_buffer(JSContext *ctx, JSValueConst obj); +static bool array_buffer_is_resizable(const JSArrayBuffer *abuf); +static JSValue js_typed_array_constructor(JSContext *ctx, + JSValueConst this_val, + int argc, JSValueConst *argv, + int classid); +static JSValue js_typed_array_constructor_ta(JSContext *ctx, + JSValueConst new_target, + JSValueConst src_obj, + int classid, uint32_t len); +static bool is_typed_array(JSClassID class_id); +static bool typed_array_is_immutable(JSObject *p); +static bool typed_array_is_oob(JSObject *p); +static uint32_t typed_array_length(JSObject *p); +static int typed_array_init(JSContext *ctx, JSValue obj, JSValue buffer, + uint64_t offset, uint64_t len, bool track_rab); +static JSValue JS_ThrowTypeErrorDetachedArrayBuffer(JSContext *ctx); +static JSValue JS_ThrowTypeErrorImmutableArrayBuffer(JSContext *ctx); +static JSValue JS_ThrowTypeErrorArrayBufferOOB(JSContext *ctx); +static JSVarRef *get_var_ref(JSContext *ctx, JSStackFrame *sf, int var_idx, + bool is_arg); +static JSVarRef *js_create_var_ref(JSContext *ctx, bool is_gc_object); +static JSValue js_call_generator_function(JSContext *ctx, JSValueConst func_obj, + JSValueConst this_obj, + int argc, JSValueConst *argv, + int flags); +static void js_async_function_resolve_finalizer(JSRuntime *rt, + JSValueConst val); +static void js_async_function_resolve_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static JSValue JS_EvalInternal(JSContext *ctx, JSValueConst this_obj, + const char *input, size_t input_len, + const char *filename, int line, int flags, int scope_idx); +static void js_free_module_def(JSContext *ctx, JSModuleDef *m); +static int js_module_attributes_equal(JSContext *ctx, JSValueConst attr1, + JSValueConst attr2); +static void js_mark_module_def(JSRuntime *rt, JSModuleDef *m, + JS_MarkFunc *mark_func); +static JSValue js_import_meta(JSContext *ctx); +static JSValue js_dynamic_import(JSContext *ctx, JSValueConst specifier, + JSValueConst options); +static void free_var_ref(JSRuntime *rt, JSVarRef *var_ref); +static void js_async_function_free(JSRuntime *rt, JSAsyncFunctionData *s); +static void js_release_coro(JSRuntime *rt, JSGCObjectHeader *coro); +static JSValue js_new_promise_capability(JSContext *ctx, + JSValue *resolving_funcs, + JSValueConst ctor); +static __exception int perform_promise_then(JSContext *ctx, + JSValueConst promise, + JSValueConst *resolve_reject, + JSValueConst *cap_resolving_funcs); +static JSValue js_promise_resolve(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int magic); +static JSValue js_promise_then(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv); +static JSValue js_promise_resolve_thenable_job(JSContext *ctx, + int argc, JSValueConst *argv); +static bool js_string_eq(JSString *p1, JSString *p2); +static int js_string_compare(JSString *p1, JSString *p2); +static int JS_NumberIsInteger(JSContext *ctx, JSValueConst val); +static bool JS_NumberIsNegativeOrMinusZero(JSContext *ctx, JSValueConst val); +static JSValue JS_ToNumberFree(JSContext *ctx, JSValue val); +static int JS_GetOwnPropertyInternal(JSContext *ctx, JSPropertyDescriptor *desc, + JSObject *p, JSAtom prop); +static int JS_GetOwnPropertyFlagsInternal(JSContext *ctx, int *pflags, + JSObject *p, JSAtom prop); +static JSValue JS_GetOwnPropertyNames2(JSContext *ctx, JSValueConst obj1, + int flags, int kind); +static void js_free_desc(JSContext *ctx, JSPropertyDescriptor *desc); +static void async_func_mark(JSRuntime *rt, JSAsyncFunctionState *s, + JS_MarkFunc *mark_func); +static int JS_AddIntrinsicBasicObjects(JSContext *ctx); +static void js_free_shape(JSRuntime *rt, JSShape *sh); +static void js_free_shape_null(JSRuntime *rt, JSShape *sh); +static int js_shape_prepare_update(JSContext *ctx, JSObject *p, + JSShapeProperty **pprs); +static int init_shape_hash(JSRuntime *rt); +static __exception int js_get_length32(JSContext *ctx, uint32_t *pres, + JSValueConst obj); +static __exception int js_get_length64(JSContext *ctx, int64_t *pres, + JSValueConst obj); +static __exception int js_set_length64(JSContext *ctx, JSValueConst obj, + int64_t len); +static void free_arg_list(JSContext *ctx, JSValue *tab, uint32_t len); +static JSValue *build_arg_list(JSContext *ctx, uint32_t *plen, + JSValueConst array_arg); +static JSValue js_create_array(JSContext *ctx, int len, JSValueConst *tab); +static bool js_get_fast_array(JSContext *ctx, JSValue obj, + JSValue **arrpp, uint32_t *countp); +static int expand_fast_array(JSContext *ctx, JSObject *p, uint32_t new_len); +static JSValue JS_CreateAsyncFromSyncIterator(JSContext *ctx, + JSValue sync_iter); +static void js_c_function_data_finalizer(JSRuntime *rt, JSValueConst val); +static void js_c_function_data_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static JSValue js_call_c_function_data(JSContext *ctx, JSValueConst func_obj, + JSValueConst this_val, + int argc, JSValueConst *argv, int flags); +static void js_c_closure_finalizer(JSRuntime *rt, JSValueConst val); +static JSValue js_call_c_closure(JSContext *ctx, JSValueConst func_obj, + JSValueConst this_val, + int argc, JSValueConst *argv, int flags); +static JSAtom JS_ValueToAtomInternal(JSContext *ctx, JSValueConst val, + int flags); +static JSAtom js_symbol_to_atom(JSContext *ctx, JSValueConst val); +static void add_gc_object(JSRuntime *rt, JSGCObjectHeader *h, + JSGCObjectTypeEnum type); +static void remove_gc_object(JSGCObjectHeader *h); +static void js_async_function_free0(JSRuntime *rt, JSAsyncFunctionData *s); +static JSValue js_instantiate_prototype(JSContext *ctx, JSObject *p, JSAtom atom, void *opaque); +static JSValue js_module_ns_autoinit(JSContext *ctx, JSObject *p, JSAtom atom, + void *opaque); +static JSValue JS_InstantiateFunctionListItem2(JSContext *ctx, JSObject *p, + JSAtom atom, void *opaque); +static JSValue JS_NewObjectProtoList(JSContext *ctx, JSValueConst proto, + const JSCFunctionListEntry *fields, int n_fields); + +static void js_set_uncatchable_error(JSContext *ctx, JSValueConst val, + bool flag); + +static JSValue js_new_callsite(JSContext *ctx, JSCallSiteData *csd); +static void js_new_callsite_data(JSContext *ctx, JSCallSiteData *csd, JSStackFrame *sf); +static void js_new_callsite_data2(JSContext *ctx, JSCallSiteData *csd, const char *filename, int line_num, int col_num); +static int _JS_AddIntrinsicCallSite(JSContext *ctx); + +static void JS_SetOpaqueInternal(JSValueConst obj, void *opaque); + +static const JSClassExoticMethods js_arguments_exotic_methods; +static const JSClassExoticMethods js_string_exotic_methods; +static const JSClassExoticMethods js_proxy_exotic_methods; +static const JSClassExoticMethods js_module_ns_exotic_methods; + +static inline bool double_is_int32(double d) +{ + uint64_t u, e; + JSFloat64Union t; + + t.d = d; + u = t.u64; + + e = ((u >> 52) & 0x7FF) - 1023; + if (e > 30) { + // accept 0, INT32_MIN, reject too large, too small, nan, inf, -0 + return !u || (u == 0xc1e0000000000000); + } else { + // shift out sign, exponent and whole part bits + // value is fractional if remaining low bits are non-zero + return !(u << 12 << e); + } +} + +static JSValue js_float64(double d) +{ + return __JS_NewFloat64(d); +} + +static int compare_u32(uint32_t a, uint32_t b) +{ + return -(a < b) + (b < a); // -1, 0 or 1 +} + +static JSValue js_int32(int32_t v) +{ + return JS_MKVAL(JS_TAG_INT, v); +} + +static JSValue js_uint32(uint32_t v) +{ + if (v <= INT32_MAX) + return js_int32(v); + else + return js_float64(v); +} + +static JSValue js_int64(int64_t v) +{ + if (v >= INT32_MIN && v <= INT32_MAX) + return js_int32(v); + else + return js_float64(v); +} + +static JSValue js_number(double d) +{ + if (double_is_int32(d)) + return js_int32((int32_t)d); + else + return js_float64(d); +} + +/* If v is a number (int or float64), store it as a double and return true. + Used by the interpreter arithmetic fast paths to handle mixed int/float + operands inline instead of falling back to the slow path. */ +static inline bool js_arith_to_float64(JSValue v, double *pd) +{ + uint32_t tag = JS_VALUE_GET_TAG(v); + if (JS_TAG_IS_FLOAT64(tag)) + *pd = JS_VALUE_GET_FLOAT64(v); + else if (tag == JS_TAG_INT) + *pd = JS_VALUE_GET_INT(v); + else + return false; + return true; +} + +static JSValue __JS_NewShortBigInt(JSContext *ctx, int32_t d) +{ + (void)&ctx; + return JS_MKVAL(JS_TAG_SHORT_BIG_INT, d); +} + +JSValue JS_NewNumber(JSContext *ctx, double d) +{ + return js_number(d); +} + +static JSValue js_bool(bool v) +{ + return JS_MKVAL(JS_TAG_BOOL, (v != 0)); +} + +static JSValue js_dup(JSValueConst v) +{ + if (JS_VALUE_HAS_REF_COUNT(v)) { + void *p = JS_VALUE_GET_PTR(v); + JS_REF_COUNT(p)++; + } + return unsafe_unconst(v); +} + +JSValue JS_DupValue(JSContext *ctx, JSValueConst v) +{ + return js_dup(v); +} + +JSValue JS_DupValueRT(JSRuntime *rt, JSValueConst v) +{ + return js_dup(v); +} + +static void js_trigger_gc(JSRuntime *rt, size_t size) +{ + bool force_gc; +#ifdef FORCE_GC_AT_MALLOC + force_gc = true; +#else + force_gc = ((rt->malloc_state.malloc_size + size) > + rt->malloc_gc_threshold); +#endif + if (force_gc) { +#ifdef ENABLE_DUMPS // JS_DUMP_GC + if (check_dump_flag(rt, JS_DUMP_GC)) { + printf("GC: size=%zd\n", rt->malloc_state.malloc_size); + } +#endif + JS_RunGC(rt); + rt->malloc_gc_threshold = rt->malloc_state.malloc_size + + (rt->malloc_state.malloc_size >> 1); + } +} + +static size_t js_malloc_usable_size_unknown(const void *ptr) +{ + return 0; +} + +/* max overhead for size >= 64: 12.5% */ +static const uint16_t arena_block_sizes[JS_ARENA_BLOCK_SIZE_COUNT] = { + 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96, 104, 112, 120, 128, + 144, 160, 176, 192, 208, 224, 240, 256, + 288, 320, 352, 384, 416, 448, 480, 512, +}; + +static int arena_get_size_index(size_t size) +{ + if (size <= 16) + return 0; + else if (size <= 128) + return (size + 7) / 8 - 2; + else if (size <= 256) + return (size + 15) / 16 + 6; + else if (size <= 512) + return (size + 31) / 32 + 14; + else + return JS_ARENA_BLOCK_SIZE_COUNT; +} + +static inline JSMallocBlockHeader *arena_zero_block(JSRuntime *rt) +{ + return (JSMallocBlockHeader *)rt->arena_state.zero_size_block; +} + +static void js_arena_init(JSRuntime *rt) +{ + JSArenaState *s = &rt->arena_state; + int i; + arena_zero_block(rt)->u.block_idx = JS_ARENA_FREE_NIL; + for (i = 0; i < JS_ARENA_BLOCK_SIZE_COUNT; i++) { + init_list_head(&s->arena_list[i]); + init_list_head(&s->free_arena_list[i]); + } +} + +static inline void *arena_get_block(JSArena *ar, unsigned int idx, + unsigned int block_size) +{ + return ar->blocks + (size_t)idx * block_size; +} + +static no_inline JSArena *arena_new(JSRuntime *rt, int block_size_idx) +{ + JSMallocBlockHeader *b; + JSArena *ar; + int n_blocks, block_size, i; + + block_size = arena_block_sizes[block_size_idx]; + n_blocks = (JS_ARENA_SIZE - sizeof(JSArena)) / block_size; + ar = rt->mf.js_malloc(rt->malloc_state.opaque, + sizeof(JSArena) + (size_t)n_blocks * block_size); + if (!ar) + return NULL; + ar->block_size_idx = block_size_idx; + ar->n_blocks = n_blocks; + ar->n_used_blocks = 0; + ar->first_free_block = 0; + for (i = 0; i < n_blocks - 1; i++) { + b = arena_get_block(ar, i, block_size); + b->u.free_next = i + 1; + b->block_size_idx = block_size_idx; + } + b = arena_get_block(ar, n_blocks - 1, block_size); + b->u.free_next = JS_ARENA_FREE_NIL; + b->block_size_idx = block_size_idx; + list_add(&ar->link, &rt->arena_state.arena_list[block_size_idx]); + list_add(&ar->free_link, &rt->arena_state.free_arena_list[block_size_idx]); + return ar; +} + +static no_inline void *arena_malloc_large(JSRuntime *rt, size_t size) +{ + JSMallocBlockHeader *b; + b = rt->mf.js_malloc(rt->malloc_state.opaque, + sizeof(JSMallocBlockHeader) + size); + if (!b) + return NULL; + b->u.block_idx = JS_ARENA_FREE_NIL; + b->block_size_idx = 0xff; /* fail safe */ + return b->user_data; +} + +static no_inline void *arena_calloc_large(JSRuntime *rt, size_t size) +{ + JSMallocBlockHeader *b; + b = rt->mf.js_calloc(rt->malloc_state.opaque, 1, + sizeof(JSMallocBlockHeader) + size); + if (!b) + return NULL; + b->u.block_idx = JS_ARENA_FREE_NIL; + b->block_size_idx = 0xff; /* fail safe */ + return b->user_data; +} + +static void *js_arena_malloc(JSRuntime *rt, size_t size) +{ + size_t total_size; + + if (unlikely(size == 0)) + return arena_zero_block(rt)->user_data; + total_size = ((size + JS_ARENA_ALIGN - 1) & ~(size_t)(JS_ARENA_ALIGN - 1)) + + sizeof(JSMallocBlockHeader); + if (!JS_ARENA_LARGE_BLOCKS_ONLY && total_size <= JS_ARENA_MAX_SMALL_SIZE) { + int block_size_idx; + unsigned int block_idx, block_size; + JSMallocBlockHeader *b; + JSArena *ar; + struct list_head *el, *head; + + block_size_idx = arena_get_size_index(total_size); + block_size = arena_block_sizes[block_size_idx]; + head = &rt->arena_state.free_arena_list[block_size_idx]; + el = head->next; + if (unlikely(el == head)) { + ar = arena_new(rt, block_size_idx); + if (!ar) + return NULL; + } else { + ar = list_entry(el, JSArena, free_link); + } + block_idx = ar->first_free_block; + b = arena_get_block(ar, block_idx, block_size); + ar->first_free_block = b->u.free_next; + b->u.block_idx = block_idx; + ar->n_used_blocks++; + if (unlikely(ar->n_used_blocks == ar->n_blocks)) + list_del(&ar->free_link); + return b->user_data; + } else { + return arena_malloc_large(rt, size); + } +} + +static void js_arena_free(JSRuntime *rt, void *ptr) +{ + JSMallocBlockHeader *b; + + if (!ptr) + return; + b = container_of(ptr, JSMallocBlockHeader, user_data); + if (unlikely(b->u.block_idx == JS_ARENA_FREE_NIL)) { + /* large or zero-size block */ + if (b == arena_zero_block(rt)) { + /* nothing to do */ + } else { + rt->mf.js_free(rt->malloc_state.opaque, b); + } + } else { + unsigned int block_idx = b->u.block_idx; + unsigned int block_size_idx = b->block_size_idx; + unsigned int block_size = arena_block_sizes[block_size_idx]; + JSArena *ar = (JSArena *)((uint8_t *)b - + (size_t)block_size * block_idx - + sizeof(JSArena)); + b->u.free_next = ar->first_free_block; + ar->first_free_block = block_idx; + if (unlikely(ar->n_used_blocks == ar->n_blocks)) + list_add(&ar->free_link, + &rt->arena_state.free_arena_list[block_size_idx]); + ar->n_used_blocks--; + if (unlikely(ar->n_used_blocks == 0)) { + list_del(&ar->link); + list_del(&ar->free_link); + rt->mf.js_free(rt->malloc_state.opaque, ar); + } + } +} + +static size_t js_arena_usable_size(JSRuntime *rt, const void *ptr) +{ + const JSMallocBlockHeader *b; + + if (!ptr) + return 0; + b = container_of(ptr, JSMallocBlockHeader, user_data); + if (b->u.block_idx == JS_ARENA_FREE_NIL) { + if (b == arena_zero_block(rt)) { + return 0; + } else { + size_t size = rt->mf.js_malloc_usable_size(b); + if (size != 0) + size -= sizeof(JSMallocBlockHeader); + return size; + } + } else { + return arena_block_sizes[b->block_size_idx] - sizeof(JSMallocBlockHeader); + } +} + +static void *js_arena_realloc(JSRuntime *rt, void *ptr, size_t size) +{ + JSMallocBlockHeader *b; + + /* js_realloc_rt already handles ptr == NULL and size == 0 */ + b = container_of(ptr, JSMallocBlockHeader, user_data); + if (b->u.block_idx == JS_ARENA_FREE_NIL) { + if (b == arena_zero_block(rt)) { + return js_arena_malloc(rt, size); + } else { + JSMallocBlockHeader *nb; + nb = rt->mf.js_realloc(rt->malloc_state.opaque, b, + sizeof(JSMallocBlockHeader) + size); + if (!nb) + return NULL; + nb->u.block_idx = JS_ARENA_FREE_NIL; + nb->block_size_idx = 0xff; + return nb->user_data; + } + } else { + unsigned int block_size = arena_block_sizes[b->block_size_idx]; + size_t total_size, old_usable; + void *new_ptr; + + total_size = ((size + JS_ARENA_ALIGN - 1) & ~(size_t)(JS_ARENA_ALIGN - 1)) + + sizeof(JSMallocBlockHeader); + if (total_size <= block_size) + return ptr; /* still fits the current size class */ + new_ptr = js_arena_malloc(rt, size); + if (!new_ptr) + return NULL; + { + /* carry the merged GC/refcount fields to the relocated block */ + JSMallocBlockHeader *nb = container_of(new_ptr, JSMallocBlockHeader, user_data); + nb->gc_obj_type = b->gc_obj_type; + nb->mark = b->mark; + nb->ref_count = b->ref_count; + } + old_usable = block_size - sizeof(JSMallocBlockHeader); + if (size > old_usable) + size = old_usable; + memcpy(new_ptr, ptr, size); + js_arena_free(rt, ptr); + return new_ptr; + } +} + +static void *js_arena_calloc(JSRuntime *rt, size_t count, size_t size) +{ + size_t n = count * size; /* overflow already checked by js_calloc_rt */ + size_t total_size = ((n + JS_ARENA_ALIGN - 1) & ~(size_t)(JS_ARENA_ALIGN - 1)) + + sizeof(JSMallocBlockHeader); + if (!JS_ARENA_LARGE_BLOCKS_ONLY && total_size <= JS_ARENA_MAX_SMALL_SIZE) { + /* Small blocks are carved from recycled (dirty) arena memory, so they + must be zeroed explicitly. */ + void *ptr = js_arena_malloc(rt, n); + if (unlikely(!ptr)) + return NULL; + return memset(ptr, 0, n); + } + /* Large blocks come straight from the backing allocator, so let js_calloc + do the zeroing. */ + return arena_calloc_large(rt, n); +} + +/* free any arenas still mapped at runtime teardown (normally none: empty + arenas are released eagerly as their last block is freed) */ +static void js_arena_free_all(JSRuntime *rt) +{ + JSArenaState *s = &rt->arena_state; + struct list_head *el, *el1; + int i; + for (i = 0; i < JS_ARENA_BLOCK_SIZE_COUNT; i++) { + list_for_each_safe(el, el1, &s->arena_list[i]) { + JSArena *ar = list_entry(el, JSArena, link); + rt->mf.js_free(rt->malloc_state.opaque, ar); + } + init_list_head(&s->arena_list[i]); + init_list_head(&s->free_arena_list[i]); + } +} + +void *js_calloc_rt(JSRuntime *rt, size_t count, size_t size) +{ + void *ptr; + JSMallocState *s; + + /* Do not allocate zero bytes: behavior is platform dependent */ + assert(count != 0 && size != 0); + + if (size > 0) + if (unlikely(count != (count * size) / size)) + return NULL; + + s = &rt->malloc_state; + /* When malloc_limit is 0 (unlimited), malloc_limit - 1 will be SIZE_MAX. */ + if (unlikely(s->malloc_size + (count * size) > s->malloc_limit - 1)) + return NULL; + + ptr = js_arena_calloc(rt, count, size); + if (!ptr) + return NULL; + + s->malloc_count++; + s->malloc_size += js_arena_usable_size(rt, ptr) + MALLOC_OVERHEAD; + return ptr; +} + +void *js_malloc_rt(JSRuntime *rt, size_t size) +{ + void *ptr; + JSMallocState *s; + + /* Do not allocate zero bytes: behavior is platform dependent */ + if (unlikely(size == 0)) + return NULL; + + s = &rt->malloc_state; + /* When malloc_limit is 0 (unlimited), malloc_limit - 1 will be SIZE_MAX. */ + if (unlikely(s->malloc_size + size > s->malloc_limit - 1)) + return NULL; + + ptr = js_arena_malloc(rt, size); + if (!ptr) + return NULL; + + s->malloc_count++; + s->malloc_size += js_arena_usable_size(rt, ptr) + MALLOC_OVERHEAD; + return ptr; +} + +void js_free_rt(JSRuntime *rt, void *ptr) +{ + JSMallocState *s; + + if (!ptr) + return; + + s = &rt->malloc_state; + size_t free_size = js_arena_usable_size(rt, ptr) + MALLOC_OVERHEAD; + if (unlikely(free_size > s->malloc_size)) { + printf("js_free_rt: malloc_size underflow: freeing %zu but only %zu tracked\n", free_size, s->malloc_size); + abort(); + } + s->malloc_count--; + s->malloc_size -= free_size; + js_arena_free(rt, ptr); +} + +void *js_realloc_rt(JSRuntime *rt, void *ptr, size_t size) +{ + size_t old_size; + JSMallocState *s; + + if (!ptr) { + if (size == 0) + return NULL; + return js_malloc_rt(rt, size); + } + if (unlikely(size == 0)) { + js_free_rt(rt, ptr); + return NULL; + } + old_size = js_arena_usable_size(rt, ptr); + s = &rt->malloc_state; + /* When malloc_limit is 0 (unlimited), malloc_limit - 1 will be SIZE_MAX. */ + if (s->malloc_size + size - old_size > s->malloc_limit - 1) + return NULL; + + ptr = js_arena_realloc(rt, ptr, size); + if (!ptr) + return NULL; + + s->malloc_size += js_arena_usable_size(rt, ptr) - old_size; + return ptr; +} + +size_t js_malloc_usable_size_rt(JSRuntime *rt, const void *ptr) +{ + return js_arena_usable_size(rt, ptr); +} + +/** + * This used to be implemented as malloc + memset, but using calloc + * yields better performance in initial, bursty allocations, something useful + * for QuickJS. + * + * More information: https://github.com/quickjs-ng/quickjs/pull/519 + */ +void *js_mallocz_rt(JSRuntime *rt, size_t size) +{ + return js_calloc_rt(rt, 1, size); +} + +/* Throw out of memory in case of error */ +void *js_calloc(JSContext *ctx, size_t count, size_t size) +{ + void *ptr; + ptr = js_calloc_rt(ctx->rt, count, size); + if (unlikely(!ptr)) { + JS_ThrowOutOfMemory(ctx); + return NULL; + } + return ptr; +} + +/* Throw out of memory in case of error */ +void *js_malloc(JSContext *ctx, size_t size) +{ + void *ptr; + ptr = js_malloc_rt(ctx->rt, size); + if (unlikely(!ptr)) { + JS_ThrowOutOfMemory(ctx); + return NULL; + } + return ptr; +} + +/* Throw out of memory in case of error */ +void *js_mallocz(JSContext *ctx, size_t size) +{ + void *ptr; + ptr = js_mallocz_rt(ctx->rt, size); + if (unlikely(!ptr)) { + JS_ThrowOutOfMemory(ctx); + return NULL; + } + return ptr; +} + +void js_free(JSContext *ctx, void *ptr) +{ + js_free_rt(ctx->rt, ptr); +} + +/* Throw out of memory in case of error */ +void *js_realloc(JSContext *ctx, void *ptr, size_t size) +{ + void *ret; + ret = js_realloc_rt(ctx->rt, ptr, size); + if (unlikely(!ret && size != 0)) { + JS_ThrowOutOfMemory(ctx); + return NULL; + } + return ret; +} + +size_t js_malloc_usable_size(JSContext *ctx, const void *ptr) +{ + return js_malloc_usable_size_rt(ctx->rt, ptr); +} + +/* Throw out of memory exception in case of error */ +char *js_strndup(JSContext *ctx, const char *s, size_t n) +{ + char *ptr; + ptr = js_malloc(ctx, n + 1); + if (ptr) { + memcpy(ptr, s, n); + ptr[n] = '\0'; + } + return ptr; +} + +char *js_strdup(JSContext *ctx, const char *str) +{ + return js_strndup(ctx, str, strlen(str)); +} + +static no_inline int js_realloc_array(JSContext *ctx, void **parray, + int elem_size, int *psize, int req_size) +{ + int new_size; + void *new_array; + /* XXX: potential arithmetic overflow */ + new_size = max_int(req_size, *psize * 3 / 2); + assert(elem_size > 0 && new_size > 0); + new_array = js_realloc(ctx, *parray, (size_t)new_size * (size_t)elem_size); + if (!new_array) + return -1; + *psize = new_size; + *parray = new_array; + return 0; +} + +/* resize the array and update its size if req_size > *psize */ +static inline int js_resize_array(JSContext *ctx, void **parray, int elem_size, + int *psize, int req_size) +{ + if (unlikely(req_size > *psize)) + return js_realloc_array(ctx, parray, elem_size, psize, req_size); + else + return 0; +} + +static void *js_dbuf_realloc(void *ctx, void *ptr, size_t size) +{ + return js_realloc(ctx, ptr, size); +} + +static inline void js_dbuf_init(JSContext *ctx, DynBuf *s) +{ + dbuf_init2(s, ctx, js_dbuf_realloc); +} + +static inline int is_digit(int c) { + return c >= '0' && c <= '9'; +} + +static inline int string_get(JSString *p, int idx) { + return p->is_wide_char ? str16(p)[idx] : str8(p)[idx]; +} + +typedef struct JSClassShortDef { + JSAtom class_name; + JSClassFinalizer *finalizer; + JSClassGCMark *gc_mark; +} JSClassShortDef; + +static JSClassShortDef const js_std_class_def[] = { + { JS_ATOM_Object, NULL, NULL }, /* JS_CLASS_OBJECT */ + { JS_ATOM_Array, js_array_finalizer, js_array_mark }, /* JS_CLASS_ARRAY */ + { JS_ATOM_Error, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_ERROR */ + { JS_ATOM_Number, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_NUMBER */ + { JS_ATOM_String, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_STRING */ + { JS_ATOM_Boolean, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_BOOLEAN */ + { JS_ATOM_Symbol, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_SYMBOL */ + { JS_ATOM_Arguments, js_array_finalizer, js_array_mark }, /* JS_CLASS_ARGUMENTS */ + { JS_ATOM_Arguments, js_mapped_arguments_finalizer, js_mapped_arguments_mark }, /* JS_CLASS_MAPPED_ARGUMENTS */ + { JS_ATOM_Date, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_DATE */ + { JS_ATOM_Object, NULL, NULL }, /* JS_CLASS_MODULE_NS */ + { JS_ATOM_Function, js_c_function_finalizer, js_c_function_mark }, /* JS_CLASS_C_FUNCTION */ + { JS_ATOM_Function, js_bytecode_function_finalizer, js_bytecode_function_mark }, /* JS_CLASS_BYTECODE_FUNCTION */ + { JS_ATOM_Function, js_bound_function_finalizer, js_bound_function_mark }, /* JS_CLASS_BOUND_FUNCTION */ + { JS_ATOM_Function, js_c_function_data_finalizer, js_c_function_data_mark }, /* JS_CLASS_C_FUNCTION_DATA */ + { JS_ATOM_Function, js_c_closure_finalizer, NULL}, /* JS_CLASS_C_CLOSURE */ + { JS_ATOM_GeneratorFunction, js_bytecode_function_finalizer, js_bytecode_function_mark }, /* JS_CLASS_GENERATOR_FUNCTION */ + { JS_ATOM_ForInIterator, js_for_in_iterator_finalizer, js_for_in_iterator_mark }, /* JS_CLASS_FOR_IN_ITERATOR */ + { JS_ATOM_RegExp, js_regexp_finalizer, NULL }, /* JS_CLASS_REGEXP */ + { JS_ATOM_ArrayBuffer, js_array_buffer_finalizer, NULL }, /* JS_CLASS_ARRAY_BUFFER */ + { JS_ATOM_SharedArrayBuffer, js_array_buffer_finalizer, NULL }, /* JS_CLASS_SHARED_ARRAY_BUFFER */ + { JS_ATOM_Uint8ClampedArray, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_UINT8C_ARRAY */ + { JS_ATOM_Int8Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_INT8_ARRAY */ + { JS_ATOM_Uint8Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_UINT8_ARRAY */ + { JS_ATOM_Int16Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_INT16_ARRAY */ + { JS_ATOM_Uint16Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_UINT16_ARRAY */ + { JS_ATOM_Int32Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_INT32_ARRAY */ + { JS_ATOM_Uint32Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_UINT32_ARRAY */ + { JS_ATOM_BigInt64Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_BIG_INT64_ARRAY */ + { JS_ATOM_BigUint64Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_BIG_UINT64_ARRAY */ + { JS_ATOM_Float16Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_FLOAT16_ARRAY */ + { JS_ATOM_Float32Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_FLOAT32_ARRAY */ + { JS_ATOM_Float64Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_FLOAT64_ARRAY */ + { JS_ATOM_DataView, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_DATAVIEW */ + { JS_ATOM_BigInt, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_BIG_INT */ + { JS_ATOM_Map, js_map_finalizer, js_map_mark }, /* JS_CLASS_MAP */ + { JS_ATOM_Set, js_map_finalizer, js_map_mark }, /* JS_CLASS_SET */ + { JS_ATOM_WeakMap, js_map_finalizer, NULL }, /* JS_CLASS_WEAKMAP */ + { JS_ATOM_WeakSet, js_map_finalizer, NULL }, /* JS_CLASS_WEAKSET */ + { JS_ATOM_Iterator, NULL, NULL }, /* JS_CLASS_ITERATOR */ + { JS_ATOM_IteratorConcat, js_iterator_concat_finalizer, js_iterator_concat_mark }, /* JS_CLASS_ITERATOR_CONCAT */ + { JS_ATOM_IteratorHelper, js_iterator_helper_finalizer, js_iterator_helper_mark }, /* JS_CLASS_ITERATOR_HELPER */ + { JS_ATOM_IteratorWrap, js_iterator_wrap_finalizer, js_iterator_wrap_mark }, /* JS_CLASS_ITERATOR_WRAP */ + { JS_ATOM_Map_Iterator, js_map_iterator_finalizer, js_map_iterator_mark }, /* JS_CLASS_MAP_ITERATOR */ + { JS_ATOM_Set_Iterator, js_map_iterator_finalizer, js_map_iterator_mark }, /* JS_CLASS_SET_ITERATOR */ + { JS_ATOM_Array_Iterator, js_array_iterator_finalizer, js_array_iterator_mark }, /* JS_CLASS_ARRAY_ITERATOR */ + { JS_ATOM_String_Iterator, js_array_iterator_finalizer, js_array_iterator_mark }, /* JS_CLASS_STRING_ITERATOR */ + { JS_ATOM_RegExp_String_Iterator, js_regexp_string_iterator_finalizer, js_regexp_string_iterator_mark }, /* JS_CLASS_REGEXP_STRING_ITERATOR */ + { JS_ATOM_Generator, js_generator_finalizer, js_generator_mark }, /* JS_CLASS_GENERATOR */ + { JS_ATOM_DisposableStack, js_disposable_stack_finalizer, js_disposable_stack_mark }, /* JS_CLASS_DISPOSABLE_STACK */ +}; + +static int init_class_range(JSRuntime *rt, JSClassShortDef const *tab, + int start, int count) +{ + JSClassDef cm_s, *cm = &cm_s; + int i, class_id; + + for(i = 0; i < count; i++) { + class_id = i + start; + memset(cm, 0, sizeof(*cm)); + cm->finalizer = tab[i].finalizer; + cm->gc_mark = tab[i].gc_mark; + if (JS_NewClass1(rt, class_id, cm, tab[i].class_name) < 0) + return -1; + } + return 0; +} + +/* Uses code from LLVM project. */ +static inline uintptr_t js_get_stack_pointer(void) +{ +#if defined(__clang__) || defined(__GNUC__) + return (uintptr_t)__builtin_frame_address(0); +#elif defined(_MSC_VER) + return (uintptr_t)_AddressOfReturnAddress(); +#else + char CharOnStack = 0; + // The volatile store here is intended to escape the local variable, to + // prevent the compiler from optimizing CharOnStack into anything other + // than a char on the stack. + // + // Tested on: MSVC 2015 - 2019, GCC 4.9 - 9, Clang 3.2 - 9, ICC 13 - 19. + char *volatile Ptr = &CharOnStack; + return (uintptr_t) Ptr; +#endif +} + +static inline bool js_check_stack_overflow(JSRuntime *rt, size_t alloca_size) +{ + uintptr_t sp; + sp = js_get_stack_pointer() - alloca_size; + return unlikely(sp < rt->stack_limit); +} + +JSRuntime *JS_NewRuntime2(const JSMallocFunctions *mf, void *opaque) +{ + JSRuntime *rt; + JSMallocState ms; + + memset(&ms, 0, sizeof(ms)); + ms.opaque = opaque; + ms.malloc_limit = 0; + + rt = mf->js_calloc(opaque, 1, sizeof(JSRuntime)); + if (!rt) + return NULL; + rt->mf = *mf; + if (!rt->mf.js_malloc_usable_size) { + /* use dummy function if none provided */ + rt->mf.js_malloc_usable_size = js_malloc_usable_size_unknown; + } + /* Inline what js_malloc_rt does since we cannot use it here. */ + ms.malloc_count++; + ms.malloc_size += rt->mf.js_malloc_usable_size(rt) + MALLOC_OVERHEAD; + rt->malloc_state = ms; + js_arena_init(rt); + rt->malloc_gc_threshold = 256 * 1024; + + init_list_head(&rt->context_list); + init_list_head(&rt->gc_obj_list); + init_list_head(&rt->gc_zero_ref_count_list); + rt->gc_phase = JS_GC_PHASE_NONE; + +#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS + init_list_head(&rt->string_list); +#endif + init_list_head(&rt->job_list); + + if (JS_InitAtoms(rt)) + goto fail; + + /* create the object, array and function classes */ + if (init_class_range(rt, js_std_class_def, JS_CLASS_OBJECT, + countof(js_std_class_def)) < 0) + goto fail; + rt->class_array[JS_CLASS_ARGUMENTS].exotic = &js_arguments_exotic_methods; + rt->class_array[JS_CLASS_MAPPED_ARGUMENTS].exotic = &js_arguments_exotic_methods; + rt->class_array[JS_CLASS_STRING].exotic = &js_string_exotic_methods; + rt->class_array[JS_CLASS_MODULE_NS].exotic = &js_module_ns_exotic_methods; + + rt->class_array[JS_CLASS_C_FUNCTION].call = js_call_c_function; + rt->class_array[JS_CLASS_C_FUNCTION_DATA].call = js_call_c_function_data; + rt->class_array[JS_CLASS_C_CLOSURE].call = js_call_c_closure; + rt->class_array[JS_CLASS_BOUND_FUNCTION].call = js_call_bound_function; + rt->class_array[JS_CLASS_GENERATOR_FUNCTION].call = js_call_generator_function; + if (init_shape_hash(rt)) + goto fail; + + rt->js_class_id_alloc = JS_CLASS_INIT_COUNT; + + rt->stack_size = JS_DEFAULT_STACK_SIZE; + + JS_UpdateStackTop(rt); + + rt->current_exception = JS_UNINITIALIZED; + + return rt; + fail: + JS_FreeRuntime(rt); + return NULL; +} + +void *JS_GetRuntimeOpaque(JSRuntime *rt) +{ + return rt->user_opaque; +} + +void JS_SetRuntimeOpaque(JSRuntime *rt, void *opaque) +{ + rt->user_opaque = opaque; +} + +int JS_AddRuntimeFinalizer(JSRuntime *rt, JSRuntimeFinalizer *finalizer, + void *arg) +{ + JSRuntimeFinalizerState *fs = js_malloc_rt(rt, sizeof(*fs)); + if (!fs) + return -1; + fs->next = rt->finalizers; + fs->finalizer = finalizer; + fs->arg = arg; + rt->finalizers = fs; + return 0; +} + +static void *js_def_calloc(void *opaque, size_t count, size_t size) +{ + return calloc(count, size); +} + +static void *js_def_malloc(void *opaque, size_t size) +{ + return malloc(size); +} + +static void js_def_free(void *opaque, void *ptr) +{ + free(ptr); +} + +static void *js_def_realloc(void *opaque, void *ptr, size_t size) +{ + return realloc(ptr, size); +} + +static const JSMallocFunctions def_malloc_funcs = { + js_def_calloc, + js_def_malloc, + js_def_free, + js_def_realloc, + js__malloc_usable_size +}; + +JSRuntime *JS_NewRuntime(void) +{ + return JS_NewRuntime2(&def_malloc_funcs, NULL); +} + +void JS_SetMemoryLimit(JSRuntime *rt, size_t limit) +{ + rt->malloc_state.malloc_limit = limit; +} + +void JS_SetDumpFlags(JSRuntime *rt, uint64_t flags) +{ +#ifdef ENABLE_DUMPS + rt->dump_flags = flags; +#endif +} + +uint64_t JS_GetDumpFlags(JSRuntime *rt) +{ +#ifdef ENABLE_DUMPS + return rt->dump_flags; +#else + return 0; +#endif +} + +size_t JS_GetGCThreshold(JSRuntime *rt) { + return rt->malloc_gc_threshold; +} + +/* use -1 to disable automatic GC */ +void JS_SetGCThreshold(JSRuntime *rt, size_t gc_threshold) +{ + rt->malloc_gc_threshold = gc_threshold; +} + +#define malloc(s) malloc_is_forbidden(s) +#define free(p) free_is_forbidden(p) +#define realloc(p,s) realloc_is_forbidden(p,s) + +void JS_SetInterruptHandler(JSRuntime *rt, JSInterruptHandler *cb, void *opaque) +{ + rt->interrupt_handler = cb; + rt->interrupt_opaque = opaque; +} + +void JS_SetCanBlock(JSRuntime *rt, bool can_block) +{ + rt->can_block = can_block; +} + +void JS_SetSharedArrayBufferFunctions(JSRuntime *rt, + const JSSharedArrayBufferFunctions *sf) +{ + rt->sab_funcs = *sf; +} + +/* return 0 if OK, < 0 if exception */ +int JS_EnqueueJob(JSContext *ctx, JSJobFunc *job_func, + int argc, JSValueConst *argv) +{ + JSRuntime *rt = ctx->rt; + JSJobEntry *e; + int i; + + assert(!rt->in_free); + + e = js_malloc(ctx, sizeof(*e) + argc * sizeof(JSValue)); + if (!e) + return -1; + e->ctx = ctx; + e->job_func = job_func; + e->argc = argc; + for(i = 0; i < argc; i++) { + e->argv[i] = js_dup(argv[i]); + } + list_add_tail(&e->link, &rt->job_list); + return 0; +} + +bool JS_IsJobPending(JSRuntime *rt) +{ + return !list_empty(&rt->job_list); +} + +JSContext *JS_GetPendingJobContext(JSRuntime *rt) +{ + if (JS_IsJobPending(rt)) { + return list_entry(rt->job_list.next, JSJobEntry, link)->ctx; + } + return NULL; +} + +/* return < 0 if exception, 0 if no job pending, 1 if a job was + executed successfully. the context of the job is stored in '*pctx' */ +int JS_ExecutePendingJob(JSRuntime *rt, JSContext **pctx) +{ + JSContext *ctx; + JSJobEntry *e; + JSValue res; + int i, ret; + + if (list_empty(&rt->job_list)) { + *pctx = NULL; + return 0; + } + + /* get the first pending job and execute it */ + e = list_entry(rt->job_list.next, JSJobEntry, link); + list_del(&e->link); + ctx = e->ctx; + res = e->job_func(e->ctx, e->argc, vc(e->argv)); + for(i = 0; i < e->argc; i++) + JS_FreeValue(ctx, e->argv[i]); + if (JS_IsException(res)) + ret = -1; + else + ret = 1; + JS_FreeValue(ctx, res); + js_free(ctx, e); + *pctx = ctx; + return ret; +} + +static inline uint32_t atom_get_free(const JSAtomStruct *p) +{ + return (uintptr_t)p >> 1; +} + +static inline bool atom_is_free(const JSAtomStruct *p) +{ + return (uintptr_t)p & 1; +} + +static inline JSAtomStruct *atom_set_free(uint32_t v) +{ + return (JSAtomStruct *)(((uintptr_t)v << 1) | 1); +} + +/* Note: the string contents are uninitialized */ +static JSString *js_alloc_string_rt(JSRuntime *rt, int max_len, int is_wide_char) +{ + JSString *str; + str = js_malloc_rt(rt, sizeof(JSString) + (max_len << is_wide_char) + 1 - is_wide_char); + if (unlikely(!str)) + return NULL; + JS_REF_COUNT(str) = 1; + str->is_wide_char = is_wide_char; + str->len = max_len; + str->kind = JS_STRING_KIND_NORMAL; + str->atom_type = 0; + str->hash = 0; /* optional but costless */ + str->hash_next = 0; /* optional */ +#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS + list_add_tail(&str->link, &rt->string_list); +#endif + return str; +} + +static JSString *js_alloc_string(JSContext *ctx, int max_len, int is_wide_char) +{ + JSString *p; + p = js_alloc_string_rt(ctx->rt, max_len, is_wide_char); + if (unlikely(!p)) { + JS_ThrowOutOfMemory(ctx); + return NULL; + } + return p; +} + +static inline void js_free_string0(JSRuntime *rt, JSString *str); + +/* same as JS_FreeValueRT() but faster */ +static inline void js_free_string(JSRuntime *rt, JSString *str) +{ + if (--JS_REF_COUNT(str) <= 0) + js_free_string0(rt, str); +} + +static inline void js_free_string0(JSRuntime *rt, JSString *str) +{ + JSStringSlice *slice; + + if (str->atom_type) { + JS_FreeAtomStruct(rt, str); + } else { +#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS + list_del(&str->link); +#endif + switch (str->kind) { + case JS_STRING_KIND_SLICE: + slice = (void *)&str[1]; + js_free_string(rt, slice->parent); // safe, recurses only 1 level + break; + case JS_STRING_KIND_INDIRECT: + js_free_rt(rt, strv(str)); + break; + } + js_free_rt(rt, str); + } +} + +void JS_SetRuntimeInfo(JSRuntime *rt, const char *s) +{ + if (rt) + rt->rt_info = s; +} + +void JS_FreeRuntime(JSRuntime *rt) +{ + struct list_head *el, *el1; + bool leak = false; + int i; + + rt->in_free = true; + JS_FreeValueRT(rt, rt->current_exception); + + list_for_each_safe(el, el1, &rt->job_list) { + JSJobEntry *e = list_entry(el, JSJobEntry, link); + for(i = 0; i < e->argc; i++) + JS_FreeValueRT(rt, e->argv[i]); + js_free_rt(rt, e); + } + init_list_head(&rt->job_list); + + JS_RunGC(rt); + +#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS + /* leaking objects */ + if (check_dump_flag(rt, JS_DUMP_LEAKS)) { + bool header_done; + JSGCObjectHeader *p; + int count; + + /* remove the internal refcounts to display only the object + referenced externally */ + list_for_each(el, &rt->gc_obj_list) { + p = list_entry(el, JSGCObjectHeader, link); + JS_GC_MARK(p) = 0; + } + gc_decref(rt); + + header_done = false; + list_for_each(el, &rt->gc_obj_list) { + p = list_entry(el, JSGCObjectHeader, link); + if (JS_REF_COUNT(p) != 0) { + if (!header_done) { + printf("Object leaks:\n"); + JS_DumpObjectHeader(rt); + header_done = true; + } + JS_DumpGCObject(rt, p); + leak = true; + } + } + + count = 0; + list_for_each(el, &rt->gc_obj_list) { + p = list_entry(el, JSGCObjectHeader, link); + if (JS_REF_COUNT(p) == 0) { + count++; + } + } + if (count != 0) + printf("Secondary object leaks: %d\n", count); + } +#endif + + assert(list_empty(&rt->gc_obj_list)); + + /* free the classes */ + for(i = 0; i < rt->class_count; i++) { + JSClass *cl = &rt->class_array[i]; + if (cl->class_id != 0) { + JS_FreeAtomRT(rt, cl->class_name); + } + } + js_free_rt(rt, rt->class_array); + +#ifdef ENABLE_DUMPS // JS_DUMP_ATOM_LEAKS + /* only the atoms defined in JS_InitAtoms() should be left */ + if (check_dump_flag(rt, JS_DUMP_ATOM_LEAKS)) { + bool header_done = false; + + for(i = 0; i < rt->atom_size; i++) { + JSAtomStruct *p = rt->atom_array[i]; + if (!atom_is_free(p) /* && p->str*/) { + if (i >= JS_ATOM_END || JS_REF_COUNT(p) != 1) { + if (!header_done) { + header_done = true; + if (rt->rt_info) { + printf("%s:1: atom leakage:", rt->rt_info); + } else { + printf("Atom leaks:\n" + " %6s %6s %s\n", + "ID", "REFCNT", "NAME"); + } + } + if (rt->rt_info) { + printf(" "); + } else { + printf(" %6u %6u ", i, JS_REF_COUNT(p)); + } + switch (p->atom_type) { + case JS_ATOM_TYPE_STRING: + JS_DumpString(rt, p); + break; + case JS_ATOM_TYPE_GLOBAL_SYMBOL: + printf("Symbol.for("); + JS_DumpString(rt, p); + printf(")"); + break; + case JS_ATOM_TYPE_SYMBOL: + if (p->hash == JS_ATOM_HASH_SYMBOL) { + printf("Symbol("); + JS_DumpString(rt, p); + printf(")"); + } else { + printf("Private("); + JS_DumpString(rt, p); + printf(")"); + } + break; + } + if (rt->rt_info) { + printf(":%u", JS_REF_COUNT(p)); + } else { + printf("\n"); + } + leak = true; + } + } + } + if (rt->rt_info && header_done) + printf("\n"); + } +#endif + + /* free the atoms */ + for(i = 0; i < rt->atom_size; i++) { + JSAtomStruct *p = rt->atom_array[i]; + if (!atom_is_free(p)) { +#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS + list_del(&p->link); +#endif + js_free_rt(rt, p); + } + } + js_free_rt(rt, rt->atom_array); + js_free_rt(rt, rt->atom_hash); + js_free_rt(rt, rt->shape_hash); +#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS + if (check_dump_flag(rt, JS_DUMP_LEAKS) && !list_empty(&rt->string_list)) { + if (rt->rt_info) { + printf("%s:1: string leakage:", rt->rt_info); + } else { + printf("String leaks:\n" + " %6s %s\n", + "REFCNT", "VALUE"); + } + list_for_each_safe(el, el1, &rt->string_list) { + JSString *str = list_entry(el, JSString, link); + if (rt->rt_info) { + printf(" "); + } else { + printf(" %6u ", JS_REF_COUNT(str)); + } + JS_DumpString(rt, str); + if (rt->rt_info) { + printf(":%u", JS_REF_COUNT(str)); + } else { + printf("\n"); + } + list_del(&str->link); + js_free_rt(rt, str); + } + if (rt->rt_info) + printf("\n"); + leak = true; + } +#endif + + while (rt->finalizers) { + JSRuntimeFinalizerState *fs = rt->finalizers; + rt->finalizers = fs->next; + fs->finalizer(rt, fs->arg); + js_free_rt(rt, fs); + } + +#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS + if (check_dump_flag(rt, JS_DUMP_LEAKS)) { + JSMallocState *s = &rt->malloc_state; + if (s->malloc_count > 1) { + if (rt->rt_info) + printf("%s:1: ", rt->rt_info); + printf("Memory leak: %zd bytes lost in %zd block%s\n", + s->malloc_size - sizeof(JSRuntime), + s->malloc_count - 1, &"s"[s->malloc_count == 2]); + leak = true; + } + } +#endif + + leak &= check_dump_flag(rt, JS_ABORT_ON_LEAKS); + + js_arena_free_all(rt); + + { + JSMallocState *ms = &rt->malloc_state; + rt->mf.js_free(ms->opaque, rt); + } + + if (leak) + abort(); +} + +JSContext *JS_NewContextRaw(JSRuntime *rt) +{ + JSContext *ctx; + int i; + + ctx = js_mallocz_rt(rt, sizeof(JSContext)); + if (!ctx) + return NULL; + JS_REF_COUNT(ctx) = 1; + add_gc_object(rt, &ctx->header, JS_GC_OBJ_TYPE_JS_CONTEXT); + + ctx->class_proto = js_malloc_rt(rt, sizeof(ctx->class_proto[0]) * + rt->class_count); + if (!ctx->class_proto) { + js_free_rt(rt, ctx); + return NULL; + } + ctx->rt = rt; + list_add_tail(&ctx->link, &rt->context_list); + for(i = 0; i < rt->class_count; i++) + ctx->class_proto[i] = JS_NULL; + ctx->array_ctor = JS_NULL; + ctx->iterator_ctor = JS_NULL; + ctx->iterator_ctor_getset = JS_NULL; + ctx->regexp_ctor = JS_NULL; + ctx->promise_ctor = JS_NULL; + ctx->error_ctor = JS_NULL; + ctx->error_back_trace = JS_UNDEFINED; + ctx->error_prepare_stack = JS_UNDEFINED; + ctx->error_stack_trace_limit = js_int32(10); + init_list_head(&ctx->loaded_modules); + // TODO(bnoordhuis) use getrandom() etc. + ctx->random_state = js__gettimeofday_us(); + // the state must be non zero + if (ctx->random_state == 0) + ctx->random_state = 1; + ctx->hash_seed = xorshift64star(&ctx->random_state); + + if (JS_AddIntrinsicBasicObjects(ctx)) { + JS_FreeContext(ctx); + return NULL; + } + return ctx; +} + +JSContext *JS_NewContext(JSRuntime *rt) +{ + JSContext *ctx; + ctx = JS_NewContextRaw(rt); + if (!ctx) + return NULL; + + if (JS_AddIntrinsicBaseObjects(ctx) || + JS_AddIntrinsicDate(ctx) || + JS_AddIntrinsicEval(ctx) || + JS_AddIntrinsicRegExp(ctx) || + JS_AddIntrinsicJSON(ctx) || + JS_AddIntrinsicProxy(ctx) || + JS_AddIntrinsicMapSet(ctx) || + JS_AddIntrinsicTypedArrays(ctx) || + JS_AddIntrinsicPromise(ctx) || + JS_AddIntrinsicWeakRef(ctx) || + JS_AddIntrinsicAToB(ctx) || + JS_AddPerformance(ctx)) { + JS_FreeContext(ctx); + return NULL; + } + + return ctx; +} + +void *JS_GetContextOpaque(JSContext *ctx) +{ + return ctx->user_opaque; +} + +void JS_SetContextOpaque(JSContext *ctx, void *opaque) +{ + ctx->user_opaque = opaque; +} + +/* set the new value and free the old value after (freeing the value + can reallocate the object data) */ +static inline void set_value(JSContext *ctx, JSValue *pval, JSValue new_val) +{ + JSValue old_val; + old_val = *pval; + *pval = new_val; + JS_FreeValue(ctx, old_val); +} + +void JS_SetClassProto(JSContext *ctx, JSClassID class_id, JSValue obj) +{ + assert(class_id < ctx->rt->class_count); + set_value(ctx, &ctx->class_proto[class_id], obj); +} + +JSValue JS_GetClassProto(JSContext *ctx, JSClassID class_id) +{ + assert(class_id < ctx->rt->class_count); + return js_dup(ctx->class_proto[class_id]); +} + +JSValue JS_GetFunctionProto(JSContext *ctx) +{ + return js_dup(ctx->function_proto); +} + +typedef enum JSFreeModuleEnum { + JS_FREE_MODULE_ALL, + JS_FREE_MODULE_NOT_RESOLVED, +} JSFreeModuleEnum; + +/* XXX: would be more efficient with separate module lists */ +static void js_free_modules(JSContext *ctx, JSFreeModuleEnum flag) +{ + struct list_head *el, *el1; + list_for_each_safe(el, el1, &ctx->loaded_modules) { + JSModuleDef *m = list_entry(el, JSModuleDef, link); + if (flag == JS_FREE_MODULE_ALL || + (flag == JS_FREE_MODULE_NOT_RESOLVED && !m->resolved)) { + js_free_module_def(ctx, m); + } + } +} + +JSContext *JS_DupContext(JSContext *ctx) +{ + JS_REF_COUNT(ctx)++; + return ctx; +} + +/* used by the GC */ +static void JS_MarkContext(JSRuntime *rt, JSContext *ctx, + JS_MarkFunc *mark_func) +{ + int i; + struct list_head *el; + + /* modules are not seen by the GC, so we directly mark the objects + referenced by each module */ + list_for_each(el, &ctx->loaded_modules) { + JSModuleDef *m = list_entry(el, JSModuleDef, link); + js_mark_module_def(rt, m, mark_func); + } + + JS_MarkValue(rt, ctx->global_obj, mark_func); + JS_MarkValue(rt, ctx->global_var_obj, mark_func); + + JS_MarkValue(rt, ctx->throw_type_error, mark_func); + JS_MarkValue(rt, ctx->eval_obj, mark_func); + + JS_MarkValue(rt, ctx->array_proto_values, mark_func); + for(i = 0; i < JS_NATIVE_ERROR_COUNT; i++) { + JS_MarkValue(rt, ctx->native_error_proto[i], mark_func); + } + JS_MarkValue(rt, ctx->error_ctor, mark_func); + JS_MarkValue(rt, ctx->error_back_trace, mark_func); + JS_MarkValue(rt, ctx->error_prepare_stack, mark_func); + JS_MarkValue(rt, ctx->error_stack_trace_limit, mark_func); + for(i = 0; i < rt->class_count; i++) { + JS_MarkValue(rt, ctx->class_proto[i], mark_func); + } + JS_MarkValue(rt, ctx->iterator_ctor, mark_func); + JS_MarkValue(rt, ctx->iterator_ctor_getset, mark_func); + JS_MarkValue(rt, ctx->async_iterator_proto, mark_func); + JS_MarkValue(rt, ctx->promise_ctor, mark_func); + JS_MarkValue(rt, ctx->array_ctor, mark_func); + JS_MarkValue(rt, ctx->regexp_ctor, mark_func); + JS_MarkValue(rt, ctx->function_ctor, mark_func); + JS_MarkValue(rt, ctx->function_proto, mark_func); + + if (ctx->array_shape) + mark_func(rt, &ctx->array_shape->header); + + if (ctx->arguments_shape) + mark_func(rt, &ctx->arguments_shape->header); + + if (ctx->mapped_arguments_shape) + mark_func(rt, &ctx->mapped_arguments_shape->header); + + if (ctx->regexp_shape) + mark_func(rt, &ctx->regexp_shape->header); + + if (ctx->regexp_result_shape) + mark_func(rt, &ctx->regexp_result_shape->header); +} + +void JS_FreeContext(JSContext *ctx) +{ + JSRuntime *rt = ctx->rt; + int i; + + if (--JS_REF_COUNT(ctx) > 0) + return; + assert(JS_REF_COUNT(ctx) == 0); + +#ifdef ENABLE_DUMPS // JS_DUMP_ATOMS + if (check_dump_flag(rt, JS_DUMP_ATOMS)) + JS_DumpAtoms(ctx->rt); +#endif +#ifdef ENABLE_DUMPS // JS_DUMP_SHAPES + if (check_dump_flag(rt, JS_DUMP_SHAPES)) + JS_DumpShapes(ctx->rt); +#endif +#ifdef ENABLE_DUMPS // JS_DUMP_OBJECTS + if (check_dump_flag(rt, JS_DUMP_OBJECTS)) { + struct list_head *el; + JSGCObjectHeader *p; + printf("JSObjects: {\n"); + JS_DumpObjectHeader(ctx->rt); + list_for_each(el, &rt->gc_obj_list) { + p = list_entry(el, JSGCObjectHeader, link); + JS_DumpGCObject(rt, p); + } + printf("}\n"); + } +#endif +#ifdef ENABLE_DUMPS // JS_DUMP_MEM + if (check_dump_flag(rt, JS_DUMP_MEM)) { + JSMemoryUsage stats; + JS_ComputeMemoryUsage(rt, &stats); + JS_DumpMemoryUsage(stdout, &stats, rt); + } +#endif + + js_free_modules(ctx, JS_FREE_MODULE_ALL); + + JS_FreeValue(ctx, ctx->global_obj); + JS_FreeValue(ctx, ctx->global_var_obj); + + JS_FreeValue(ctx, ctx->throw_type_error); + JS_FreeValue(ctx, ctx->eval_obj); + + JS_FreeValue(ctx, ctx->array_proto_values); + for(i = 0; i < JS_NATIVE_ERROR_COUNT; i++) { + JS_FreeValue(ctx, ctx->native_error_proto[i]); + } + JS_FreeValue(ctx, ctx->error_ctor); + JS_FreeValue(ctx, ctx->error_back_trace); + JS_FreeValue(ctx, ctx->error_prepare_stack); + JS_FreeValue(ctx, ctx->error_stack_trace_limit); + for(i = 0; i < rt->class_count; i++) { + JS_FreeValue(ctx, ctx->class_proto[i]); + } + js_free_rt(rt, ctx->class_proto); + JS_FreeValue(ctx, ctx->iterator_ctor); + JS_FreeValue(ctx, ctx->iterator_ctor_getset); + JS_FreeValue(ctx, ctx->async_iterator_proto); + JS_FreeValue(ctx, ctx->promise_ctor); + JS_FreeValue(ctx, ctx->array_ctor); + JS_FreeValue(ctx, ctx->regexp_ctor); + JS_FreeValue(ctx, ctx->function_ctor); + JS_FreeValue(ctx, ctx->function_proto); + + js_free_shape_null(ctx->rt, ctx->array_shape); + js_free_shape_null(ctx->rt, ctx->arguments_shape); + js_free_shape_null(ctx->rt, ctx->mapped_arguments_shape); + js_free_shape_null(ctx->rt, ctx->regexp_shape); + js_free_shape_null(ctx->rt, ctx->regexp_result_shape); + + list_del(&ctx->link); + remove_gc_object(&ctx->header); + js_free_rt(ctx->rt, ctx); +} + +JSRuntime *JS_GetRuntime(JSContext *ctx) +{ + return ctx->rt; +} + +static void update_stack_limit(JSRuntime *rt) +{ + if (rt->stack_size == 0 || rt->stack_top < rt->stack_size) { + rt->stack_limit = 0; /* no limit */ + } else { + rt->stack_limit = rt->stack_top - rt->stack_size; + } +} + +void JS_SetMaxStackSize(JSRuntime *rt, size_t stack_size) +{ + rt->stack_size = stack_size; + update_stack_limit(rt); +} + +void JS_UpdateStackTop(JSRuntime *rt) +{ + rt->stack_top = js_get_stack_pointer(); + update_stack_limit(rt); +} + +static inline bool is_strict_mode(JSContext *ctx) +{ + JSStackFrame *sf = ctx->rt->current_stack_frame; + return sf && sf->is_strict_mode; +} + +/* JSAtom support */ + +#define JS_ATOM_TAG_INT (1U << 31) +#define JS_ATOM_MAX_INT (JS_ATOM_TAG_INT - 1) +#define JS_ATOM_MAX ((1U << 30) - 1) + +/* return the max count from the hash size */ +#define JS_ATOM_COUNT_RESIZE(n) ((n) * 2) + +static inline bool __JS_AtomIsConst(JSAtom v) +{ + return (int32_t)v < JS_ATOM_END; +} + +static inline bool __JS_AtomIsTaggedInt(JSAtom v) +{ + return (v & JS_ATOM_TAG_INT) != 0; +} + +static inline JSAtom __JS_AtomFromUInt32(uint32_t v) +{ + return v | JS_ATOM_TAG_INT; +} + +static inline uint32_t __JS_AtomToUInt32(JSAtom atom) +{ + return atom & ~JS_ATOM_TAG_INT; +} + +static inline int is_num(int c) +{ + return c >= '0' && c <= '9'; +} + +/* return true if the string is a number n with 0 <= n <= 2^32-1 */ +static inline bool is_num_string(uint32_t *pval, JSString *p) +{ + uint32_t n; + uint64_t n64; + int c, i, len; + + len = p->len; + if (len == 0 || len > 10) + return false; + c = string_get(p, 0); + if (is_num(c)) { + if (c == '0') { + if (len != 1) + return false; + n = 0; + } else { + n = c - '0'; + for(i = 1; i < len; i++) { + c = string_get(p, i); + if (!is_num(c)) + return false; + n64 = (uint64_t)n * 10 + (c - '0'); + if ((n64 >> 32) != 0) + return false; + n = n64; + } + } + *pval = n; + return true; + } else { + return false; + } +} + +/* XXX: could use faster version ? */ +static inline uint32_t hash_string8(const uint8_t *str, size_t len, uint32_t h) +{ + size_t i; + + for(i = 0; i < len; i++) + h = h * 263 + str[i]; + return h ^ hash32(len); +} + +static inline uint32_t hash_string16(const uint16_t *str, + size_t len, uint32_t h) +{ + size_t i; + + for(i = 0; i < len; i++) + h = h * 263 + str[i]; + return h ^ hash32(len); +} + +static uint32_t hash_string(JSString *str, uint32_t h) +{ + if (str->is_wide_char) + h = hash_string16(str16(str), str->len, h); + else + h = hash_string8(str8(str), str->len, h); + return h; +} + +static uint32_t hash_string_rope(JSValueConst val, uint32_t h) +{ + if (JS_VALUE_GET_TAG(val) == JS_TAG_STRING) { + return hash_string(JS_VALUE_GET_STRING(val), h); + } else { + JSStringRope *r = JS_VALUE_GET_STRING_ROPE(val); + h = hash_string_rope(r->left, h); + return hash_string_rope(r->right, h); + } +} + +static __maybe_unused void JS_DumpString(JSRuntime *rt, JSString *p) +{ + int i, c, sep; + + if (p == NULL) { + printf(""); + return; + } + if (JS_REF_COUNT(p) != 1) + printf("%d", JS_REF_COUNT(p)); + if (p->is_wide_char) + putchar('L'); + sep = '\"'; + putchar(sep); + for(i = 0; i < p->len; i++) { + c = string_get(p, i); + if (c == sep || c == '\\') { + putchar('\\'); + putchar(c); + } else if (c >= ' ' && c <= 126) { + putchar(c); + } else if (c == '\n') { + putchar('\\'); + putchar('n'); + } else { + printf("\\u%04x", c); + } + } + putchar(sep); +} + +static __maybe_unused void JS_DumpAtoms(JSRuntime *rt) +{ + JSAtomStruct *p; + int h, i; + /* This only dumps hashed atoms, not JS_ATOM_TYPE_SYMBOL atoms */ + printf("JSAtom count=%d size=%d hash_size=%d:\n", + rt->atom_count, rt->atom_size, rt->atom_hash_size); + printf("JSAtom hash table: {\n"); + for(i = 0; i < rt->atom_hash_size; i++) { + h = rt->atom_hash[i]; + if (h) { + printf(" %d:", i); + while (h) { + p = rt->atom_array[h]; + printf(" "); + JS_DumpString(rt, p); + h = p->hash_next; + } + printf("\n"); + } + } + printf("}\n"); + printf("JSAtom table: {\n"); + for(i = 0; i < rt->atom_size; i++) { + p = rt->atom_array[i]; + if (!atom_is_free(p)) { + printf(" %d: { %d %08x ", i, p->atom_type, p->hash); + if (!(p->len == 0 && p->is_wide_char != 0)) + JS_DumpString(rt, p); + printf(" %d }\n", p->hash_next); + } + } + printf("}\n"); +} + +static int JS_ResizeAtomHash(JSRuntime *rt, int new_hash_size) +{ + JSAtomStruct *p; + uint32_t new_hash_mask, h, i, hash_next1, j, *new_hash; + + assert((new_hash_size & (new_hash_size - 1)) == 0); /* power of two */ + new_hash_mask = new_hash_size - 1; + new_hash = js_mallocz_rt(rt, sizeof(rt->atom_hash[0]) * new_hash_size); + if (!new_hash) + return -1; + for(i = 0; i < rt->atom_hash_size; i++) { + h = rt->atom_hash[i]; + while (h != 0) { + p = rt->atom_array[h]; + hash_next1 = p->hash_next; + /* add in new hash table */ + j = p->hash & new_hash_mask; + p->hash_next = new_hash[j]; + new_hash[j] = h; + h = hash_next1; + } + } + js_free_rt(rt, rt->atom_hash); + rt->atom_hash = new_hash; + rt->atom_hash_size = new_hash_size; + rt->atom_count_resize = JS_ATOM_COUNT_RESIZE(new_hash_size); + // JS_DumpAtoms(rt); + return 0; +} + +static int JS_InitAtoms(JSRuntime *rt) +{ + int i, len, atom_type; + const char *p; + + rt->atom_hash_size = 0; + rt->atom_hash = NULL; + rt->atom_count = 0; + rt->atom_size = 0; + rt->atom_free_index = 0; + if (JS_ResizeAtomHash(rt, 1024)) // there are at least 587 predefined atoms + return -1; + + p = js_atom_init; + for(i = 1; i < JS_ATOM_END; i++) { + if (i == JS_ATOM_Private_brand) + atom_type = JS_ATOM_TYPE_PRIVATE; + else if (i >= JS_ATOM_Symbol_toPrimitive) + atom_type = JS_ATOM_TYPE_SYMBOL; + else + atom_type = JS_ATOM_TYPE_STRING; + len = strlen(p); + if (__JS_NewAtomInit(rt, p, len, atom_type) == JS_ATOM_NULL) + return -1; + p = p + len + 1; + } + return 0; +} + +JSAtom JS_DupAtomRT(JSRuntime *rt, JSAtom v) +{ + JSAtomStruct *p; + + if (!__JS_AtomIsConst(v)) { + p = rt->atom_array[v]; + JS_REF_COUNT(p)++; + } + return v; +} + +JSAtom JS_DupAtom(JSContext *ctx, JSAtom v) +{ + JSRuntime *rt; + JSAtomStruct *p; + + if (!__JS_AtomIsConst(v)) { + rt = ctx->rt; + p = rt->atom_array[v]; + JS_REF_COUNT(p)++; + } + return v; +} + +static JSAtomKindEnum JS_AtomGetKind(JSContext *ctx, JSAtom v) +{ + JSRuntime *rt; + JSAtomStruct *p; + + rt = ctx->rt; + if (__JS_AtomIsTaggedInt(v)) + return JS_ATOM_KIND_STRING; + p = rt->atom_array[v]; + switch(p->atom_type) { + case JS_ATOM_TYPE_STRING: + return JS_ATOM_KIND_STRING; + case JS_ATOM_TYPE_GLOBAL_SYMBOL: + return JS_ATOM_KIND_SYMBOL; + case JS_ATOM_TYPE_SYMBOL: + switch(p->hash) { + case JS_ATOM_HASH_SYMBOL: + return JS_ATOM_KIND_SYMBOL; + case JS_ATOM_HASH_PRIVATE: + return JS_ATOM_KIND_PRIVATE; + default: + abort(); + } + default: + abort(); + } + return (JSAtomKindEnum){-1}; // pacify compiler +} + +static JSAtom js_get_atom_index(JSRuntime *rt, JSAtomStruct *p) +{ + uint32_t i = p->hash_next; /* atom_index */ + if (p->atom_type != JS_ATOM_TYPE_SYMBOL) { + JSAtomStruct *p1; + + i = rt->atom_hash[p->hash & (rt->atom_hash_size - 1)]; + p1 = rt->atom_array[i]; + while (p1 != p) { + assert(i != 0); + i = p1->hash_next; + p1 = rt->atom_array[i]; + } + } + return i; +} + +/* string case (internal). Return JS_ATOM_NULL if error. 'str' is + freed. */ +static JSAtom __JS_NewAtom(JSRuntime *rt, JSString *str, int atom_type) +{ + uint32_t h, h1, i; + JSAtomStruct *p; + int len; + + if (atom_type < JS_ATOM_TYPE_SYMBOL) { + /* str is not NULL */ + if (str->atom_type == atom_type) { + /* str is the atom, return its index */ + i = js_get_atom_index(rt, str); + /* reduce string refcount and increase atom's unless constant */ + if (__JS_AtomIsConst(i)) + JS_REF_COUNT(str)--; + return i; + } + /* try and locate an already registered atom */ + len = str->len; + h = hash_string(str, atom_type); + h &= JS_ATOM_HASH_MASK; + h1 = h & (rt->atom_hash_size - 1); + i = rt->atom_hash[h1]; + while (i != 0) { + p = rt->atom_array[i]; + if (p->hash == h && + p->atom_type == atom_type && + p->len == len && + js_string_memcmp(p, str, len) == 0) { + if (!__JS_AtomIsConst(i)) + JS_REF_COUNT(p)++; + goto done; + } + i = p->hash_next; + } + } else { + h1 = 0; /* avoid warning */ + if (atom_type == JS_ATOM_TYPE_SYMBOL) { + h = JS_ATOM_HASH_SYMBOL; + } else { + h = JS_ATOM_HASH_PRIVATE; + atom_type = JS_ATOM_TYPE_SYMBOL; + } + } + + if (rt->atom_free_index == 0) { + /* allow new atom entries */ + uint32_t new_size, start; + JSAtomStruct **new_array; + + /* alloc new with size progression 3/2: + 4 6 9 13 19 28 42 63 94 141 211 316 474 711 1066 1599 2398 3597 5395 8092 + preallocating space for predefined atoms (at least 504). + */ + new_size = max_int(711, rt->atom_size * 3 / 2); + if (new_size > JS_ATOM_MAX) + goto fail; + new_array = js_realloc_rt(rt, rt->atom_array, sizeof(*new_array) * new_size); + if (!new_array) + goto fail; + /* Note: the atom 0 is not used */ + start = rt->atom_size; + if (start == 0) { + /* JS_ATOM_NULL entry */ + p = js_mallocz_rt(rt, sizeof(JSAtomStruct)); + if (!p) { + js_free_rt(rt, new_array); + goto fail; + } + JS_REF_COUNT(p) = 1; /* not refcounted */ + p->atom_type = JS_ATOM_TYPE_SYMBOL; +#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS + list_add_tail(&p->link, &rt->string_list); +#endif + new_array[0] = p; + rt->atom_count++; + start = 1; + } + rt->atom_size = new_size; + rt->atom_array = new_array; + rt->atom_free_index = start; + for(i = start; i < new_size; i++) { + uint32_t next; + if (i == (new_size - 1)) + next = 0; + else + next = i + 1; + rt->atom_array[i] = atom_set_free(next); + } + } + + if (str) { + if (str->atom_type == 0) { + p = str; + p->atom_type = atom_type; + } else { + p = js_malloc_rt(rt, sizeof(JSString) + + (str->len << str->is_wide_char) + + 1 - str->is_wide_char); + if (unlikely(!p)) + goto fail; + JS_REF_COUNT(p) = 1; + p->is_wide_char = str->is_wide_char; + p->len = str->len; + p->kind = JS_STRING_KIND_NORMAL; +#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS + list_add_tail(&p->link, &rt->string_list); +#endif + memcpy(str8(p), str8(str), (str->len << str->is_wide_char) + + 1 - str->is_wide_char); + js_free_string(rt, str); + } + } else { + p = js_malloc_rt(rt, sizeof(JSAtomStruct)); /* empty wide string */ + if (!p) + return JS_ATOM_NULL; + JS_REF_COUNT(p) = 1; + p->is_wide_char = 1; /* Hack to represent NULL as a JSString */ + p->len = 0; + p->kind = JS_STRING_KIND_NORMAL; +#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS + list_add_tail(&p->link, &rt->string_list); +#endif + } + + /* use an already free entry */ + i = rt->atom_free_index; + rt->atom_free_index = atom_get_free(rt->atom_array[i]); + rt->atom_array[i] = p; + + p->hash = h; + p->hash_next = i; /* atom_index */ + p->atom_type = atom_type; + p->first_weak_ref = NULL; + + rt->atom_count++; + + if (atom_type != JS_ATOM_TYPE_SYMBOL) { + p->hash_next = rt->atom_hash[h1]; + rt->atom_hash[h1] = i; + if (unlikely(rt->atom_count >= rt->atom_count_resize)) + JS_ResizeAtomHash(rt, rt->atom_hash_size * 2); + } + + // JS_DumpAtoms(rt); + return i; + + fail: + i = JS_ATOM_NULL; + done: + if (str) + js_free_string(rt, str); + return i; +} + +// XXX: `str` must be pure ASCII. No UTF-8 encoded strings +// XXX: `str` must not be the string representation of a small integer +static JSAtom __JS_NewAtomInit(JSRuntime *rt, const char *str, int len, + int atom_type) +{ + JSString *p; + p = js_alloc_string_rt(rt, len, 0); + if (!p) + return JS_ATOM_NULL; + memcpy(str8(p), str, len); + str8(p)[len] = '\0'; + return __JS_NewAtom(rt, p, atom_type); +} + +// XXX: `str` must be raw 8-bit contents. No UTF-8 encoded strings +static JSAtom __JS_FindAtom(JSRuntime *rt, const char *str, size_t len, + int atom_type) +{ + uint32_t h, h1, i; + JSAtomStruct *p; + + h = hash_string8((const uint8_t *)str, len, JS_ATOM_TYPE_STRING); + h &= JS_ATOM_HASH_MASK; + h1 = h & (rt->atom_hash_size - 1); + i = rt->atom_hash[h1]; + while (i != 0) { + p = rt->atom_array[i]; + if (p->hash == h && + p->atom_type == JS_ATOM_TYPE_STRING && + p->len == len && + p->is_wide_char == 0 && + memcmp(str8(p), str, len) == 0) { + if (!__JS_AtomIsConst(i)) + JS_REF_COUNT(p)++; + return i; + } + i = p->hash_next; + } + return JS_ATOM_NULL; +} + +static void JS_FreeAtomStruct(JSRuntime *rt, JSAtomStruct *p) +{ + uint32_t i = p->hash_next; /* atom_index */ + if (p->atom_type != JS_ATOM_TYPE_SYMBOL) { + JSAtomStruct *p0, *p1; + uint32_t h0; + + h0 = p->hash & (rt->atom_hash_size - 1); + i = rt->atom_hash[h0]; + p1 = rt->atom_array[i]; + if (p1 == p) { + rt->atom_hash[h0] = p1->hash_next; + } else { + for(;;) { + assert(i != 0); + p0 = p1; + i = p1->hash_next; + p1 = rt->atom_array[i]; + if (p1 == p) { + p0->hash_next = p1->hash_next; + break; + } + } + } + } + /* insert in free atom list */ + rt->atom_array[i] = atom_set_free(rt->atom_free_index); + rt->atom_free_index = i; + if (unlikely(p->first_weak_ref)) { + reset_weak_ref(rt, &p->first_weak_ref); + } + /* free the string structure */ +#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS + list_del(&p->link); +#endif + js_free_rt(rt, p); + rt->atom_count--; + assert(rt->atom_count >= 0); +} + +static void __JS_FreeAtom(JSRuntime *rt, uint32_t i) +{ + JSAtomStruct *p; + + p = rt->atom_array[i]; + if (--JS_REF_COUNT(p) > 0) + return; + JS_FreeAtomStruct(rt, p); +} + +/* Warning: 'p' is freed */ +static JSAtom JS_NewAtomStr(JSContext *ctx, JSString *p) +{ + JSRuntime *rt = ctx->rt; + uint32_t n; + if (is_num_string(&n, p)) { + if (n <= JS_ATOM_MAX_INT) { + js_free_string(rt, p); + return __JS_AtomFromUInt32(n); + } + } + /* XXX: should generate an exception */ + return __JS_NewAtom(rt, p, JS_ATOM_TYPE_STRING); +} + +/* `str` may be pure ASCII or UTF-8 encoded */ +JSAtom JS_NewAtomLen(JSContext *ctx, const char *str, size_t len) +{ + JSValue val; + + if (len == 0 || !is_digit(*str)) { + // TODO(chqrlie): this does not work if `str` has UTF-8 encoded contents + // bug example: `({ "\u00c3\u00a9": 1 }).\u00e9` evaluates to `1`. + JSAtom atom = __JS_FindAtom(ctx->rt, str, len, JS_ATOM_TYPE_STRING); + if (atom) + return atom; + } + val = JS_NewStringLen(ctx, str, len); + if (JS_IsException(val)) + return JS_ATOM_NULL; + return JS_NewAtomStr(ctx, JS_VALUE_GET_STRING(val)); +} + +/* `str` may be pure ASCII or UTF-8 encoded */ +JSAtom JS_NewAtom(JSContext *ctx, const char *str) +{ + return JS_NewAtomLen(ctx, str, strlen(str)); +} + +JSAtom JS_NewAtomUInt32(JSContext *ctx, uint32_t n) +{ + if (n <= JS_ATOM_MAX_INT) { + return __JS_AtomFromUInt32(n); + } else { + char buf[16]; + size_t len = u32toa(buf, n); + JSValue val = js_new_string8_len(ctx, buf, len); + if (JS_IsException(val)) + return JS_ATOM_NULL; + return __JS_NewAtom(ctx->rt, JS_VALUE_GET_STRING(val), + JS_ATOM_TYPE_STRING); + } +} + +static JSAtom JS_NewAtomInt64(JSContext *ctx, int64_t n) +{ + if ((uint64_t)n <= JS_ATOM_MAX_INT) { + return __JS_AtomFromUInt32((uint32_t)n); + } else { + char buf[24]; + size_t len = i64toa(buf, n); + JSValue val = js_new_string8_len(ctx, buf, len); + if (JS_IsException(val)) + return JS_ATOM_NULL; + return __JS_NewAtom(ctx->rt, JS_VALUE_GET_STRING(val), + JS_ATOM_TYPE_STRING); + } +} + +/* 'p' is freed */ +static JSValue JS_NewSymbolInternal(JSContext *ctx, JSString *p, int atom_type) +{ + JSRuntime *rt = ctx->rt; + JSAtom atom; + atom = __JS_NewAtom(rt, p, atom_type); + if (atom == JS_ATOM_NULL) + return JS_ThrowOutOfMemory(ctx); + return JS_MKPTR(JS_TAG_SYMBOL, rt->atom_array[atom]); +} + +/* descr must be a non-numeric string atom */ +static JSValue JS_NewSymbolFromAtom(JSContext *ctx, JSAtom descr, + int atom_type) +{ + JSRuntime *rt = ctx->rt; + JSString *p; + + assert(!__JS_AtomIsTaggedInt(descr)); + assert(descr < rt->atom_size); + p = rt->atom_array[descr]; + js_dup(JS_MKPTR(JS_TAG_STRING, p)); + return JS_NewSymbolInternal(ctx, p, atom_type); +} + +/* `description` may be pure ASCII or UTF-8 encoded */ +static JSValue js_new_symbol(JSContext *ctx, const char *description, + int atom_type) +{ + if (description == NULL) { + if (atom_type != JS_ATOM_TYPE_GLOBAL_SYMBOL) { + /* Local symbol without description: Symbol() */ + return JS_NewSymbolInternal(ctx, NULL, atom_type); + } + /* Global symbol without description: Symbol.for() + Per ES spec, ToString(undefined) becomes "undefined" */ + description = "undefined"; + } + JSAtom atom = JS_NewAtom(ctx, description); + if (atom == JS_ATOM_NULL) + return JS_EXCEPTION; + JSValue symbol = JS_NewSymbolFromAtom(ctx, atom, atom_type); + JS_FreeAtom(ctx, atom); + return symbol; +} + +JSValue JS_NewSymbol(JSContext *ctx, const char *description, bool is_global) +{ + int atom_type; + + atom_type = JS_ATOM_TYPE_SYMBOL; + if (is_global) + atom_type = JS_ATOM_TYPE_GLOBAL_SYMBOL; + return js_new_symbol(ctx, description, atom_type); +} + +JSValue JS_NewPrivateSymbol(JSContext *ctx, const char *description) +{ + return js_new_symbol(ctx, description, JS_ATOM_TYPE_PRIVATE); +} + +#define ATOM_GET_STR_BUF_SIZE 64 + +static const char *JS_AtomGetStrRT(JSRuntime *rt, char *buf, int buf_size, + JSAtom atom) +{ + if (__JS_AtomIsTaggedInt(atom)) { + snprintf(buf, buf_size, "%u", __JS_AtomToUInt32(atom)); + } else if (atom == JS_ATOM_NULL) { + snprintf(buf, buf_size, ""); + } else if (atom >= rt->atom_size) { + assert(atom < rt->atom_size); + snprintf(buf, buf_size, "", atom); + } else { + JSAtomStruct *p = rt->atom_array[atom]; + *buf = '\0'; + if (atom_is_free(p)) { + snprintf(buf, buf_size, "", atom); + } else if (p != NULL) { + JSString *str = p; + if (str->is_wide_char) { + /* encode surrogates correctly */ + utf8_encode_buf16(buf, buf_size, str16(str), str->len); + } else { + utf8_encode_buf8(buf, buf_size, str8(str), str->len); + } + } + } + return buf; +} + +static const char *JS_AtomGetStr(JSContext *ctx, char *buf, int buf_size, JSAtom atom) +{ + return JS_AtomGetStrRT(ctx->rt, buf, buf_size, atom); +} + +static JSValue __JS_AtomToValue(JSContext *ctx, JSAtom atom, bool force_string) +{ + char buf[ATOM_GET_STR_BUF_SIZE]; + + if (__JS_AtomIsTaggedInt(atom)) { + size_t len = u32toa(buf, __JS_AtomToUInt32(atom)); + return js_new_string8_len(ctx, buf, len); + } else { + JSRuntime *rt = ctx->rt; + JSAtomStruct *p; + assert(atom < rt->atom_size); + p = rt->atom_array[atom]; + if (p->atom_type == JS_ATOM_TYPE_STRING) { + goto ret_string; + } else if (force_string) { + if (p->len == 0 && p->is_wide_char != 0) { + /* no description string */ + p = rt->atom_array[JS_ATOM_empty_string]; + } + ret_string: + return js_dup(JS_MKPTR(JS_TAG_STRING, p)); + } else { + return js_dup(JS_MKPTR(JS_TAG_SYMBOL, p)); + } + } +} + +JSValue JS_AtomToValue(JSContext *ctx, JSAtom atom) +{ + return __JS_AtomToValue(ctx, atom, false); +} + +JSValue JS_AtomToString(JSContext *ctx, JSAtom atom) +{ + return __JS_AtomToValue(ctx, atom, true); +} + +/* return true if the atom is an array index (i.e. 0 <= index <= + 2^32-2 and return its value */ +static bool JS_AtomIsArrayIndex(JSContext *ctx, uint32_t *pval, JSAtom atom) +{ + if (__JS_AtomIsTaggedInt(atom)) { + *pval = __JS_AtomToUInt32(atom); + return true; + } else { + JSRuntime *rt = ctx->rt; + JSAtomStruct *p; + uint32_t val; + + assert(atom < rt->atom_size); + p = rt->atom_array[atom]; + if (p->atom_type == JS_ATOM_TYPE_STRING && + is_num_string(&val, p) && val != -1) { + *pval = val; + return true; + } else { + *pval = 0; + return false; + } + } +} + +/* This test must be fast if atom is not a numeric index (e.g. a + method name). Return JS_UNDEFINED if not a numeric + index. JS_EXCEPTION can also be returned. */ +static JSValue JS_AtomIsNumericIndex1(JSContext *ctx, JSAtom atom) +{ + JSRuntime *rt = ctx->rt; + JSAtomStruct *p1; + JSString *p; + int c, len, ret; + JSValue num, str; + + if (__JS_AtomIsTaggedInt(atom)) + return js_int32(__JS_AtomToUInt32(atom)); + assert(atom < rt->atom_size); + p1 = rt->atom_array[atom]; + if (p1->atom_type != JS_ATOM_TYPE_STRING) + return JS_UNDEFINED; + p = p1; + len = p->len; + if (p->is_wide_char) { + const uint16_t *r = str16(p), *r_end = str16(p) + len; + if (r >= r_end) + return JS_UNDEFINED; + c = *r; + if (c == '-') { + if (r >= r_end) + return JS_UNDEFINED; + r++; + c = *r; + /* -0 case is specific */ + if (c == '0' && len == 2) + goto minus_zero; + } + /* XXX: should test NaN, but the tests do not check it */ + if (!is_num(c)) { + /* XXX: String should be normalized, therefore 8-bit only */ + const uint16_t nfinity16[7] = { 'n', 'f', 'i', 'n', 'i', 't', 'y' }; + if (!(c =='I' && (r_end - r) == 8 && + !memcmp(r + 1, nfinity16, sizeof(nfinity16)))) + return JS_UNDEFINED; + } + } else { + const uint8_t *r = str8(p), *r_end = str8(p) + len; + if (r >= r_end) + return JS_UNDEFINED; + c = *r; + if (c == '-') { + if (r >= r_end) + return JS_UNDEFINED; + r++; + c = *r; + /* -0 case is specific */ + if (c == '0' && len == 2) { + minus_zero: + return js_float64(-0.0); + } + } + if (!is_num(c)) { + if (!(c =='I' && (r_end - r) == 8 && + !memcmp(r + 1, "nfinity", 7))) + return JS_UNDEFINED; + } + } + /* this is ECMA CanonicalNumericIndexString primitive */ + num = JS_ToNumber(ctx, JS_MKPTR(JS_TAG_STRING, p)); + if (JS_IsException(num)) + return num; + str = JS_ToString(ctx, num); + if (JS_IsException(str)) { + JS_FreeValue(ctx, num); + return str; + } + ret = js_string_eq(p, JS_VALUE_GET_STRING(str)); + JS_FreeValue(ctx, str); + if (ret) { + return num; + } else { + JS_FreeValue(ctx, num); + return JS_UNDEFINED; + } +} + +/* return -1 if exception or true/false */ +static int JS_AtomIsNumericIndex(JSContext *ctx, JSAtom atom) +{ + JSValue num; + num = JS_AtomIsNumericIndex1(ctx, atom); + if (likely(JS_IsUndefined(num))) + return false; + if (JS_IsException(num)) + return -1; + JS_FreeValue(ctx, num); + return true; +} + +void JS_FreeAtom(JSContext *ctx, JSAtom v) +{ + if (!__JS_AtomIsConst(v)) + __JS_FreeAtom(ctx->rt, v); +} + +void JS_FreeAtomRT(JSRuntime *rt, JSAtom v) +{ + if (!__JS_AtomIsConst(v)) + __JS_FreeAtom(rt, v); +} + +/* return true if 'v' is a symbol with a string description */ +static bool JS_AtomSymbolHasDescription(JSContext *ctx, JSAtom v) +{ + JSRuntime *rt; + JSAtomStruct *p; + + rt = ctx->rt; + if (__JS_AtomIsTaggedInt(v)) + return false; + p = rt->atom_array[v]; + return (((p->atom_type == JS_ATOM_TYPE_SYMBOL && + p->hash == JS_ATOM_HASH_SYMBOL) || + p->atom_type == JS_ATOM_TYPE_GLOBAL_SYMBOL) && + !(p->len == 0 && p->is_wide_char != 0)); +} + +static __maybe_unused void print_atom(JSContext *ctx, JSAtom atom) +{ + char buf[ATOM_GET_STR_BUF_SIZE]; + const char *p; + int i; + + /* XXX: should handle embedded null characters */ + /* XXX: should move encoding code to JS_AtomGetStr */ + p = JS_AtomGetStr(ctx, buf, sizeof(buf), atom); + for (i = 0; p[i]; i++) { + int c = (unsigned char)p[i]; + if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || + (c == '_' || c == '$') || (c >= '0' && c <= '9' && i > 0))) + break; + } + if (i > 0 && p[i] == '\0') { + printf("%s", p); + } else { + putchar('"'); + printf("%.*s", i, p); + for (; p[i]; i++) { + int c = (unsigned char)p[i]; + if (c == '\"' || c == '\\') { + putchar('\\'); + putchar(c); + } else if (c >= ' ' && c <= 126) { + putchar(c); + } else if (c == '\n') { + putchar('\\'); + putchar('n'); + } else { + printf("\\u%04x", c); + } + } + putchar('\"'); + } +} + +/* free with JS_FreeCString() */ +const char *JS_AtomToCStringLen(JSContext *ctx, size_t *plen, JSAtom atom) +{ + JSValue str; + const char *cstr; + + str = JS_AtomToString(ctx, atom); + if (JS_IsException(str)) { + if (plen) + *plen = 0; + return NULL; + } + cstr = JS_ToCStringLen(ctx, plen, str); + JS_FreeValue(ctx, str); + return cstr; +} + +#ifndef QJS_DISABLE_PARSER + +/* return a string atom containing name concatenated with str1 */ +/* `str1` may be pure ASCII or UTF-8 encoded */ +// TODO(chqrlie): use string concatenation instead of UTF-8 conversion +static JSAtom js_atom_concat_str(JSContext *ctx, JSAtom name, const char *str1) +{ + JSValue str; + JSAtom atom; + const char *cstr; + char *cstr2; + size_t len, len1; + + str = JS_AtomToString(ctx, name); + if (JS_IsException(str)) + return JS_ATOM_NULL; + cstr = JS_ToCStringLen(ctx, &len, str); + if (!cstr) + goto fail; + len1 = strlen(str1); + cstr2 = js_malloc(ctx, len + len1 + 1); + if (!cstr2) + goto fail; + memcpy(cstr2, cstr, len); + memcpy(cstr2 + len, str1, len1); + cstr2[len + len1] = '\0'; + atom = JS_NewAtomLen(ctx, cstr2, len + len1); + js_free(ctx, cstr2); + JS_FreeCString(ctx, cstr); + JS_FreeValue(ctx, str); + return atom; + fail: + JS_FreeCString(ctx, cstr); + JS_FreeValue(ctx, str); + return JS_ATOM_NULL; +} + +static JSAtom js_atom_concat_num(JSContext *ctx, JSAtom name, uint32_t n) +{ + char buf[16]; + size_t len; + + len = u32toa(buf, n); + buf[len] = '\0'; + return js_atom_concat_str(ctx, name, buf); +} + +#endif // QJS_DISABLE_PARSER + +static inline bool JS_IsEmptyString(JSValueConst v) +{ + return JS_VALUE_GET_TAG(v) == JS_TAG_STRING && JS_VALUE_GET_STRING(v)->len == 0; +} + +/* JSClass support */ + +/* a new class ID is allocated if *pclass_id == 0, otherwise *pclass_id is left unchanged */ +JSClassID JS_NewClassID(JSRuntime *rt, JSClassID *pclass_id) +{ + JSClassID class_id = *pclass_id; + if (class_id == 0) { + class_id = rt->js_class_id_alloc++; + *pclass_id = class_id; + } + return class_id; +} + +JSClassID JS_GetClassID(JSValueConst v) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(v) != JS_TAG_OBJECT) + return JS_INVALID_CLASS_ID; + p = JS_VALUE_GET_OBJ(v); + return p->class_id; +} + +bool JS_IsRegisteredClass(JSRuntime *rt, JSClassID class_id) +{ + return (class_id < rt->class_count && + rt->class_array[class_id].class_id != 0); +} + +JSAtom JS_GetClassName(JSRuntime *rt, JSClassID class_id) +{ + if (JS_IsRegisteredClass(rt, class_id)) { + return JS_DupAtomRT(rt, rt->class_array[class_id].class_name); + } else { + return JS_ATOM_NULL; + } +} + +/* create a new object internal class. Return -1 if error, 0 if + OK. The finalizer can be NULL if none is needed. */ +static int JS_NewClass1(JSRuntime *rt, JSClassID class_id, + const JSClassDef *class_def, JSAtom name) +{ + int new_size, i; + JSClass *cl, *new_class_array; + struct list_head *el; + + if (class_id >= (1 << 16)) + return -1; + if (class_id < rt->class_count && + rt->class_array[class_id].class_id != 0) + return -1; + + if (class_id >= rt->class_count) { + new_size = max_int(JS_CLASS_INIT_COUNT, + max_int(class_id + 1, rt->class_count * 3 / 2)); + + /* reallocate the context class prototype array, if any */ + list_for_each(el, &rt->context_list) { + JSContext *ctx = list_entry(el, JSContext, link); + JSValue *new_tab; + new_tab = js_realloc_rt(rt, ctx->class_proto, + sizeof(ctx->class_proto[0]) * new_size); + if (!new_tab) + return -1; + for(i = rt->class_count; i < new_size; i++) + new_tab[i] = JS_NULL; + ctx->class_proto = new_tab; + } + /* reallocate the class array */ + new_class_array = js_realloc_rt(rt, rt->class_array, + sizeof(JSClass) * new_size); + if (!new_class_array) + return -1; + memset(new_class_array + rt->class_count, 0, + (new_size - rt->class_count) * sizeof(JSClass)); + rt->class_array = new_class_array; + rt->class_count = new_size; + } + cl = &rt->class_array[class_id]; + cl->class_id = class_id; + cl->class_name = JS_DupAtomRT(rt, name); + cl->finalizer = class_def->finalizer; + cl->gc_mark = class_def->gc_mark; + cl->call = class_def->call; + cl->exotic = class_def->exotic; + return 0; +} + +int JS_NewClass(JSRuntime *rt, JSClassID class_id, const JSClassDef *class_def) +{ + int ret, len; + JSAtom name; + + // XXX: class_def->class_name must be raw 8-bit contents. No UTF-8 encoded strings + len = strlen(class_def->class_name); + name = __JS_FindAtom(rt, class_def->class_name, len, JS_ATOM_TYPE_STRING); + if (name == JS_ATOM_NULL) { + name = __JS_NewAtomInit(rt, class_def->class_name, len, JS_ATOM_TYPE_STRING); + if (name == JS_ATOM_NULL) + return -1; + } + ret = JS_NewClass1(rt, class_id, class_def, name); + JS_FreeAtomRT(rt, name); + return ret; +} + +static inline JSValue js_empty_string(JSRuntime *rt) +{ + JSAtomStruct *p = rt->atom_array[JS_ATOM_empty_string]; + return js_dup(JS_MKPTR(JS_TAG_STRING, p)); +} + +// XXX: `buf` contains raw 8-bit data, no UTF-8 decoding is performed +// XXX: no special case for len == 0 +static JSValue js_new_string8_len(JSContext *ctx, const char *buf, int len) +{ + JSString *str; + str = js_alloc_string(ctx, len, 0); + if (!str) + return JS_EXCEPTION; + memcpy(str8(str), buf, len); + str8(str)[len] = '\0'; + return JS_MKPTR(JS_TAG_STRING, str); +} + +// XXX: `buf` contains raw 8-bit data, no UTF-8 decoding is performed +// XXX: no special case for the empty string +static inline JSValue js_new_string8(JSContext *ctx, const char *str) +{ + return js_new_string8_len(ctx, str, strlen(str)); +} + +static JSValue js_new_string16_len(JSContext *ctx, const uint16_t *buf, int len) +{ + JSString *str; + str = js_alloc_string(ctx, len, 1); + if (!str) + return JS_EXCEPTION; + memcpy(str16(str), buf, len * 2); + return JS_MKPTR(JS_TAG_STRING, str); +} + +static JSValue js_new_string_char(JSContext *ctx, uint16_t c) +{ + if (c < 0x100) { + char ch8 = c; + return js_new_string8_len(ctx, &ch8, 1); + } else { + uint16_t ch16 = c; + return js_new_string16_len(ctx, &ch16, 1); + } +} + +static JSValue js_sub_string(JSContext *ctx, JSString *p, int start, int end) +{ + JSStringSlice *slice; + JSString *q; + int len; + + len = end - start; + if (start == 0 && end == p->len) { + return js_dup(JS_MKPTR(JS_TAG_STRING, p)); + } + if (len <= 0) { + return js_empty_string(ctx->rt); + } + if (len > (JS_STRING_SLICE_LEN_MAX >> p->is_wide_char)) { + if (p->kind == JS_STRING_KIND_SLICE) { + slice = (void *)&p[1]; + p = slice->parent; + start += slice->start >> p->is_wide_char; // bytes -> chars + } + // allocate as 16 bit wide string to avoid wastage; + // js_alloc_string allocates 1 byte extra for 8 bit strings; + q = js_alloc_string(ctx, sizeof(*slice)/2, /*is_wide_char*/true); + if (!q) + return JS_EXCEPTION; + q->is_wide_char = p->is_wide_char; + q->kind = JS_STRING_KIND_SLICE; + q->len = len; + slice = (void *)&q[1]; + slice->parent = p; + slice->start = start << p->is_wide_char; // chars -> bytes + JS_REF_COUNT(p)++; + return JS_MKPTR(JS_TAG_STRING, q); + } + if (p->is_wide_char) { + JSString *str; + int i; + uint16_t c = 0; + for (i = start; i < end; i++) { + c |= str16(p)[i]; + } + if (c > 0xFF) + return js_new_string16_len(ctx, str16(p) + start, len); + + str = js_alloc_string(ctx, len, 0); + if (!str) + return JS_EXCEPTION; + for (i = 0; i < len; i++) { + str8(str)[i] = str16(p)[start + i]; + } + str8(str)[len] = '\0'; + return JS_MKPTR(JS_TAG_STRING, str); + } else { + return js_new_string8_len(ctx, (const char *)(str8(p) + start), len); + } +} + +typedef struct StringBuffer { + JSContext *ctx; + JSString *str; + int len; + int size; + int is_wide_char; + int error_status; +} StringBuffer; + +/* It is valid to call string_buffer_end() and all string_buffer functions even + if string_buffer_init() or another string_buffer function returns an error. + If the error_status is set, string_buffer_end() returns JS_EXCEPTION. + */ +static int string_buffer_init2(JSContext *ctx, StringBuffer *s, int size, + int is_wide) +{ + s->ctx = ctx; + s->size = size; + s->len = 0; + s->is_wide_char = is_wide; + s->error_status = 0; + s->str = js_alloc_string(ctx, size, is_wide); + if (unlikely(!s->str)) { + s->size = 0; + return s->error_status = -1; + } +#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS + /* the StringBuffer may reallocate the JSString, only link it at the end */ + list_del(&s->str->link); +#endif + return 0; +} + +static inline int string_buffer_init(JSContext *ctx, StringBuffer *s, int size) +{ + return string_buffer_init2(ctx, s, size, 0); +} + +static void string_buffer_free(StringBuffer *s) +{ + js_free(s->ctx, s->str); + s->str = NULL; +} + +static int string_buffer_set_error(StringBuffer *s) +{ + js_free(s->ctx, s->str); + s->str = NULL; + s->size = 0; + s->len = 0; + return s->error_status = -1; +} + +static no_inline int string_buffer_widen(StringBuffer *s, int size) +{ + JSString *str; + int i; + + if (s->error_status) + return -1; + + str = js_realloc(s->ctx, s->str, sizeof(JSString) + (size << 1)); + if (!str) + return string_buffer_set_error(s); + for(i = s->len; i-- > 0;) { + str16(str)[i] = str8(str)[i]; + } + s->is_wide_char = 1; + s->size = size; + s->str = str; + return 0; +} + +static no_inline int string_buffer_realloc(StringBuffer *s, int new_len, int c) +{ + JSString *new_str; + int new_size; + size_t new_size_bytes; + + if (s->error_status) + return -1; + + if (new_len > JS_STRING_LEN_MAX) { + JS_ThrowRangeError(s->ctx, "invalid string length"); + return string_buffer_set_error(s); + } + new_size = min_int(max_int(new_len, s->size * 3 / 2), JS_STRING_LEN_MAX); + if (!s->is_wide_char && c >= 0x100) { + return string_buffer_widen(s, new_size); + } + new_size_bytes = sizeof(JSString) + (new_size << s->is_wide_char) + 1 - s->is_wide_char; + new_str = js_realloc(s->ctx, s->str, new_size_bytes); + if (!new_str) + return string_buffer_set_error(s); + s->size = new_size; + s->str = new_str; + return 0; +} + +static no_inline int string_buffer_putc16_slow(StringBuffer *s, uint32_t c) +{ + if (unlikely(s->len >= s->size)) { + if (string_buffer_realloc(s, s->len + 1, c)) + return -1; + } + if (s->is_wide_char) { + str16(s->str)[s->len++] = c; + } else if (c < 0x100) { + str8(s->str)[s->len++] = c; + } else { + if (string_buffer_widen(s, s->size)) + return -1; + str16(s->str)[s->len++] = c; + } + return 0; +} + +/* 0 <= c <= 0xff */ +static int string_buffer_putc8(StringBuffer *s, uint32_t c) +{ + if (unlikely(s->len >= s->size)) { + if (string_buffer_realloc(s, s->len + 1, c)) + return -1; + } + if (s->is_wide_char) { + str16(s->str)[s->len++] = c; + } else { + str8(s->str)[s->len++] = c; + } + return 0; +} + +/* 0 <= c <= 0xffff */ +static int string_buffer_putc16(StringBuffer *s, uint32_t c) +{ + if (likely(s->len < s->size)) { + if (s->is_wide_char) { + str16(s->str)[s->len++] = c; + return 0; + } else if (c < 0x100) { + str8(s->str)[s->len++] = c; + return 0; + } + } + return string_buffer_putc16_slow(s, c); +} + +/* 0 <= c <= 0x10ffff */ +static no_inline int string_buffer_putc_slow(StringBuffer *s, uint32_t c) +{ + if (c >= 0x10000) { + /* surrogate pair */ + if (string_buffer_putc16(s, get_hi_surrogate(c))) + return -1; + c = get_lo_surrogate(c); + } + return string_buffer_putc16(s, c); +} + +/* 0 <= c <= 0x10ffff */ +static inline int string_buffer_putc(StringBuffer *s, uint32_t c) +{ + if (likely(s->len < s->size)) { + if (s->is_wide_char) { + if (c < 0x10000) { + str16(s->str)[s->len++] = c; + return 0; + } else if (s->len + 1 < s->size) { + /* surrogate pair */ + str16(s->str)[s->len++] = get_hi_surrogate(c); + str16(s->str)[s->len++] = get_lo_surrogate(c); + return 0; + } + } else if (c < 0x100) { + str8(s->str)[s->len++] = c; + return 0; + } + } + return string_buffer_putc_slow(s, c); +} + +static int string_getc(JSString *p, int *pidx) +{ + int idx, c, c1; + idx = *pidx; + if (p->is_wide_char) { + c = str16(p)[idx++]; + if (is_hi_surrogate(c) && idx < p->len) { + c1 = str16(p)[idx]; + if (is_lo_surrogate(c1)) { + c = from_surrogate(c, c1); + idx++; + } + } + } else { + c = str8(p)[idx++]; + } + *pidx = idx; + return c; +} + +static int string_buffer_write8(StringBuffer *s, const uint8_t *p, int len) +{ + int i; + + if (s->len + len > s->size) { + if (string_buffer_realloc(s, s->len + len, 0)) + return -1; + } + if (s->is_wide_char) { + for (i = 0; i < len; i++) { + str16(s->str)[s->len + i] = p[i]; + } + s->len += len; + } else { + memcpy(&str8(s->str)[s->len], p, len); + s->len += len; + } + return 0; +} + +static int string_buffer_write16(StringBuffer *s, const uint16_t *p, int len) +{ + int c = 0, i; + + for (i = 0; i < len; i++) { + c |= p[i]; + } + if (s->len + len > s->size) { + if (string_buffer_realloc(s, s->len + len, c)) + return -1; + } else if (!s->is_wide_char && c >= 0x100) { + if (string_buffer_widen(s, s->size)) + return -1; + } + if (s->is_wide_char) { + memcpy(&str16(s->str)[s->len], p, len << 1); + s->len += len; + } else { + for (i = 0; i < len; i++) { + str8(s->str)[s->len + i] = p[i]; + } + s->len += len; + } + return 0; +} + +/* appending an ASCII string */ +static int string_buffer_puts8(StringBuffer *s, const char *str) +{ + return string_buffer_write8(s, (const uint8_t *)str, strlen(str)); +} + +static int string_buffer_concat(StringBuffer *s, JSString *p, + uint32_t from, uint32_t to) +{ + if (to <= from) + return 0; + if (p->is_wide_char) + return string_buffer_write16(s, str16(p) + from, to - from); + else + return string_buffer_write8(s, str8(p) + from, to - from); +} + +static int string_buffer_concat_value(StringBuffer *s, JSValueConst v) +{ + JSString *p; + JSValue v1; + int res; + int tag; + + if (s->error_status) { + /* prevent exception overload */ + return -1; + } + tag = JS_VALUE_GET_TAG(v); + if (tag == JS_TAG_STRING_ROPE) { + /* recursively concatenate rope children */ + JSStringRope *r = JS_VALUE_GET_STRING_ROPE(v); + if (string_buffer_concat_value(s, r->left)) + return -1; + return string_buffer_concat_value(s, r->right); + } + if (unlikely(tag != JS_TAG_STRING)) { + v1 = JS_ToString(s->ctx, v); + if (JS_IsException(v1)) + return string_buffer_set_error(s); + p = JS_VALUE_GET_STRING(v1); + res = string_buffer_concat(s, p, 0, p->len); + JS_FreeValue(s->ctx, v1); + return res; + } + p = JS_VALUE_GET_STRING(v); + return string_buffer_concat(s, p, 0, p->len); +} + +static int string_buffer_concat_value_free(StringBuffer *s, JSValue v) +{ + JSString *p; + int res; + int tag; + + if (s->error_status) { + /* prevent exception overload */ + JS_FreeValue(s->ctx, v); + return -1; + } + tag = JS_VALUE_GET_TAG(v); + if (tag == JS_TAG_STRING_ROPE) { + /* concatenate rope (don't free since concat_value doesn't free) */ + res = string_buffer_concat_value(s, v); + JS_FreeValue(s->ctx, v); + return res; + } + if (unlikely(tag != JS_TAG_STRING)) { + v = JS_ToStringFree(s->ctx, v); + if (JS_IsException(v)) + return string_buffer_set_error(s); + } + p = JS_VALUE_GET_STRING(v); + res = string_buffer_concat(s, p, 0, p->len); + JS_FreeValue(s->ctx, v); + return res; +} + +static int string_buffer_fill(StringBuffer *s, int c, int count) +{ + /* XXX: optimize */ + if (s->len + count > s->size) { + if (string_buffer_realloc(s, s->len + count, c)) + return -1; + } + while (count-- > 0) { + if (string_buffer_putc16(s, c)) + return -1; + } + return 0; +} + +static JSValue string_buffer_end(StringBuffer *s) +{ + JSString *str; + str = s->str; + if (s->error_status) + return JS_EXCEPTION; + if (s->len == 0) { + js_free(s->ctx, str); + s->str = NULL; + return js_empty_string(s->ctx->rt); + } + if (s->len < s->size) { + /* smaller size so js_realloc should not fail, but OK if it does */ + /* XXX: should add some slack to avoid unnecessary calls */ + /* XXX: might need to use malloc+free to ensure smaller size */ + str = js_realloc_rt(s->ctx->rt, str, sizeof(JSString) + + (s->len << s->is_wide_char) + 1 - s->is_wide_char); + if (str == NULL) + str = s->str; + s->str = str; + } + if (!s->is_wide_char) + str8(str)[s->len] = 0; +#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS + list_add_tail(&str->link, &s->ctx->rt->string_list); +#endif + str->is_wide_char = s->is_wide_char; + str->len = s->len; + s->str = NULL; + return JS_MKPTR(JS_TAG_STRING, str); +} + +/* create a string from a UTF-8 buffer */ +JSValue JS_NewStringLen(JSContext *ctx, const char *buf, size_t buf_len) +{ + JSString *str; + size_t len; + int kind; + + if (unlikely(buf_len <= 0)) + return js_empty_string(ctx->rt); + + /* Compute string kind and length: 7-bit, 8-bit, 16-bit, 16-bit UTF-16 */ + kind = utf8_scan(buf, buf_len, &len); + if (unlikely(len > JS_STRING_LEN_MAX)) + return JS_ThrowRangeError(ctx, "invalid string length"); + + switch (kind) { + case UTF8_PLAIN_ASCII: + str = js_alloc_string(ctx, len, 0); + if (unlikely(!str)) + return JS_EXCEPTION; + memcpy(str8(str), buf, len); + str8(str)[len] = '\0'; + break; + case UTF8_NON_ASCII: + /* buf contains non-ASCII code-points, but limited to 8-bit values */ + str = js_alloc_string(ctx, len, 0); + if (unlikely(!str)) + return JS_EXCEPTION; + utf8_decode_buf8(str8(str), len + 1, buf, buf_len); + break; + default: + // This causes a potential problem in JS_ThrowError if message is invalid + //if (kind & UTF8_HAS_ERRORS) + // return JS_ThrowRangeError(ctx, "invalid UTF-8 sequence"); + str = js_alloc_string(ctx, len, 1); + if (unlikely(!str)) + return JS_EXCEPTION; + utf8_decode_buf16(str16(str), len, buf, buf_len); + break; + } + return JS_MKPTR(JS_TAG_STRING, str); +} + +JSValue JS_NewStringUTF16(JSContext *ctx, const uint16_t *buf, size_t len) +{ + JSString *str; + + if (unlikely(!len)) + return js_empty_string(ctx->rt); + if (unlikely(len > JS_STRING_LEN_MAX)) + return JS_ThrowRangeError(ctx, "invalid string length"); + + str = js_alloc_string(ctx, len, 1); + if (unlikely(!str)) + return JS_EXCEPTION; + memcpy(str16(str), buf, len * sizeof(*buf)); + return JS_MKPTR(JS_TAG_STRING, str); +} + +static JSValue JS_ConcatString3(JSContext *ctx, const char *str1, + JSValue str2, const char *str3) +{ + StringBuffer b_s, *b = &b_s; + int len1, len3; + JSString *p; + + if (unlikely(JS_VALUE_GET_TAG(str2) != JS_TAG_STRING)) { + str2 = JS_ToStringFree(ctx, str2); + if (JS_IsException(str2)) + goto fail; + } + p = JS_VALUE_GET_STRING(str2); + len1 = strlen(str1); + len3 = strlen(str3); + + if (string_buffer_init2(ctx, b, len1 + p->len + len3, p->is_wide_char)) + goto fail; + + string_buffer_write8(b, (const uint8_t *)str1, len1); + string_buffer_concat(b, p, 0, p->len); + string_buffer_write8(b, (const uint8_t *)str3, len3); + + JS_FreeValue(ctx, str2); + return string_buffer_end(b); + + fail: + JS_FreeValue(ctx, str2); + return JS_EXCEPTION; +} + +/* `str` may be pure ASCII or UTF-8 encoded */ +JSValue JS_NewAtomString(JSContext *ctx, const char *str) +{ + JSAtom atom = JS_NewAtom(ctx, str); + if (atom == JS_ATOM_NULL) + return JS_EXCEPTION; + JSValue val = JS_AtomToString(ctx, atom); + JS_FreeAtom(ctx, atom); + return val; +} + +static JSValue js_force_tostring(JSContext *ctx, JSValueConst val1) +{ + JSObject *p; + JSValue val; + + if (JS_VALUE_GET_TAG(val1) == JS_TAG_STRING) + return js_dup(val1); + val = JS_ToString(ctx, val1); + if (!JS_IsException(val)) + return val; + // Stringification can fail when there is an exception pending, + // e.g. a stack overflow InternalError. Special-case exception + // objects to make debugging easier, look up the .message property + // and stringify that. + if (JS_VALUE_GET_TAG(val1) != JS_TAG_OBJECT) + return JS_EXCEPTION; + p = JS_VALUE_GET_OBJ(val1); + if (p->class_id != JS_CLASS_ERROR) + return JS_EXCEPTION; + val = JS_GetProperty(ctx, val1, JS_ATOM_message); + if (JS_VALUE_GET_TAG(val) != JS_TAG_STRING) { + JS_FreeValue(ctx, val); + return JS_EXCEPTION; + } + return val; +} + +/* return (NULL, 0) if exception. */ +/* return pointer into a JSString with a live ref_count */ +/* cesu8 determines if non-BMP1 codepoints are encoded as 1 or 2 utf-8 sequences */ +const char *JS_ToCStringLen2(JSContext *ctx, size_t *plen, JSValueConst val1, + bool cesu8) +{ + JSValue val; + JSString *str, *str_new; + int pos, len, c, c1; + uint8_t *q; + + val = js_force_tostring(ctx, val1); + if (JS_IsException(val)) + goto fail; + str = JS_VALUE_GET_STRING(val); + len = str->len; + if (!str->is_wide_char) { + const uint8_t *src = str8(str); + int count; + + /* count the number of non-ASCII characters */ + /* Scanning the whole string is required for ASCII strings, + and computing the number of non-ASCII bytes is less expensive + than testing each byte, hence this method is faster for ASCII + strings, which is the most common case. + */ + count = 0; + for (pos = 0; pos < len; pos++) { + count += src[pos] >> 7; + } + if (count == 0 && str->kind == JS_STRING_KIND_NORMAL) { + if (plen) + *plen = len; + return (const char *)src; + } + str_new = js_alloc_string(ctx, len + count, 0); + if (!str_new) + goto fail; + q = str8(str_new); + for (pos = 0; pos < len; pos++) { + c = src[pos]; + if (c < 0x80) { + *q++ = c; + } else { + *q++ = (c >> 6) | 0xc0; + *q++ = (c & 0x3f) | 0x80; + } + } + } else { + const uint16_t *src = str16(str); + /* Allocate 3 bytes per 16 bit code point. Surrogate pairs may + produce 4 bytes but use 2 code points. + */ + str_new = js_alloc_string(ctx, len * 3, 0); + if (!str_new) + goto fail; + q = str8(str_new); + pos = 0; + while (pos < len) { + c = src[pos++]; + if (c < 0x80) { + *q++ = c; + } else { + if (is_hi_surrogate(c)) { + if (pos < len && !cesu8) { + c1 = src[pos]; + if (is_lo_surrogate(c1)) { + pos++; + c = from_surrogate(c, c1); + } else { + /* Keep unmatched surrogate code points */ + /* c = 0xfffd; */ /* error */ + } + } else { + /* Keep unmatched surrogate code points */ + /* c = 0xfffd; */ /* error */ + } + } + q += utf8_encode(q, c); + } + } + } + + *q = '\0'; + str_new->len = q - str8(str_new); + JS_FreeValue(ctx, val); + if (plen) + *plen = str_new->len; + return (const char *)str8(str_new); +fail: + if (plen) + *plen = 0; + return NULL; +} + +static void copy_str16(uint16_t *dst, JSString *p, int offset, int len) +{ + if (p->is_wide_char) { + memcpy(dst, str16(p) + offset, len * 2); + } else { + const uint8_t *src1 = str8(p) + offset; + int i; + + for(i = 0; i < len; i++) + dst[i] = src1[i]; + } +} + +const uint16_t *JS_ToCStringLenUTF16(JSContext *ctx, size_t *plen, + JSValueConst val1) +{ + JSString *p, *q; + JSValue v; + + v = js_force_tostring(ctx, val1); + if (JS_IsException(v)) + goto fail; + p = JS_VALUE_GET_STRING(v); + if (!(p->is_wide_char && p->kind == JS_STRING_KIND_NORMAL)) { + q = js_alloc_string(ctx, p->len, /*is_wide_char*/true); + if (!q) + goto fail; + copy_str16(str16(q), p, 0, p->len); + JS_FreeValue(ctx, v); + p = q; + } + if (plen) + *plen = p->len; + return str16(p); +fail: + JS_FreeValue(ctx, v); + if (plen) + *plen = 0; + return NULL; +} + +static void js_free_cstring(JSRuntime *rt, const void *ptr) +{ + if (!ptr) + return; + /* purposely removing constness */ + JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_STRING, (JSString *)ptr - 1)); +} + +void JS_FreeCString(JSContext *ctx, const char *ptr) +{ + return js_free_cstring(ctx->rt, ptr); +} + +void JS_FreeCStringRT(JSRuntime *rt, const char *ptr) +{ + return js_free_cstring(rt, ptr); +} + +void JS_FreeCStringUTF16(JSContext *ctx, const uint16_t *ptr) +{ + return js_free_cstring(ctx->rt, ptr); +} + +void JS_FreeCStringRT_UTF16(JSRuntime *rt, const uint16_t *ptr) +{ + return js_free_cstring(rt, ptr); +} + +static int memcmp16_8(const uint16_t *src1, const uint8_t *src2, int len) +{ + int c, i; + for(i = 0; i < len; i++) { + c = src1[i] - src2[i]; + if (c != 0) + return c; + } + return 0; +} + +static int memcmp16(const uint16_t *src1, const uint16_t *src2, int len) +{ + int c, i; + for(i = 0; i < len; i++) { + c = src1[i] - src2[i]; + if (c != 0) + return c; + } + return 0; +} + +static int js_string_memcmp(JSString *p1, JSString *p2, int len) +{ + int res; + + if (likely(!p1->is_wide_char)) { + if (likely(!p2->is_wide_char)) + res = memcmp(str8(p1), str8(p2), len); + else + res = -memcmp16_8(str16(p2), str8(p1), len); + } else { + if (!p2->is_wide_char) + res = memcmp16_8(str16(p1), str8(p2), len); + else + res = memcmp16(str16(p1), str16(p2), len); + } + return res; +} + +static bool js_string_eq(JSString *p1, JSString *p2) { + if (p1->len != p2->len) + return false; + return js_string_memcmp(p1, p2, p1->len) == 0; +} + +/* return < 0, 0 or > 0 */ +static int js_string_compare(JSString *p1, JSString *p2) +{ + int res, len; + len = min_int(p1->len, p2->len); + res = js_string_memcmp(p1, p2, len); + if (res == 0) + res = compare_u32(p1->len, p2->len); + return res; +} + +/* Rope string support functions */ + +static inline bool tag_is_string(int tag) +{ + return tag == JS_TAG_STRING || tag == JS_TAG_STRING_ROPE; +} + +static uint32_t string_rope_get_len(JSValueConst val) +{ + if (JS_VALUE_GET_TAG(val) == JS_TAG_STRING) + return JS_VALUE_GET_STRING(val)->len; + else + return JS_VALUE_GET_STRING_ROPE(val)->len; +} + +static int string_rope_get(JSValueConst val, uint32_t idx) +{ + if (JS_VALUE_GET_TAG(val) == JS_TAG_STRING) { + return string_get(JS_VALUE_GET_STRING(val), idx); + } else { + JSStringRope *r = JS_VALUE_GET_STRING_ROPE(val); + uint32_t len; + if (JS_VALUE_GET_TAG(r->left) == JS_TAG_STRING) + len = JS_VALUE_GET_STRING(r->left)->len; + else + len = JS_VALUE_GET_STRING_ROPE(r->left)->len; + if (idx < len) + return string_rope_get(r->left, idx); + else + return string_rope_get(r->right, idx - len); + } +} + +typedef struct { + JSValueConst stack[JS_STRING_ROPE_MAX_DEPTH]; + int stack_len; +} JSStringRopeIter; + +static void string_rope_iter_init(JSStringRopeIter *s, JSValueConst val) +{ + s->stack_len = 0; + s->stack[s->stack_len++] = val; +} + +/* iterate thru a rope and return the strings in order */ +static JSString *string_rope_iter_next(JSStringRopeIter *s) +{ + JSValueConst val; + JSStringRope *r; + + if (s->stack_len == 0) + return NULL; + val = s->stack[--s->stack_len]; + for(;;) { + if (JS_VALUE_GET_TAG(val) == JS_TAG_STRING) + return JS_VALUE_GET_STRING(val); + r = JS_VALUE_GET_STRING_ROPE(val); + assert(s->stack_len < JS_STRING_ROPE_MAX_DEPTH); + s->stack[s->stack_len++] = r->right; + val = r->left; + } +} + +/* compare two string values with position offsets */ +static int js_string_memcmp_pos(JSString *p1, uint32_t pos1, + JSString *p2, uint32_t pos2, uint32_t len) +{ + int res; + + if (likely(!p1->is_wide_char)) { + if (likely(!p2->is_wide_char)) + res = memcmp(str8(p1) + pos1, str8(p2) + pos2, len); + else + res = -memcmp16_8(str16(p2) + pos2, str8(p1) + pos1, len); + } else { + if (!p2->is_wide_char) + res = memcmp16_8(str16(p1) + pos1, str8(p2) + pos2, len); + else + res = memcmp16(str16(p1) + pos1, str16(p2) + pos2, len); + } + return res; +} + +static int js_string_rope_compare(JSValueConst op1, + JSValueConst op2, bool eq_only) +{ + uint32_t len1, len2, len, pos1, pos2, l; + int res; + JSStringRopeIter it1, it2; + JSString *p1, *p2; + + len1 = string_rope_get_len(op1); + len2 = string_rope_get_len(op2); + /* no need to go further for equality test if different length */ + if (eq_only && len1 != len2) + return 1; + len = min_uint32(len1, len2); + string_rope_iter_init(&it1, op1); + string_rope_iter_init(&it2, op2); + p1 = string_rope_iter_next(&it1); + p2 = string_rope_iter_next(&it2); + pos1 = 0; + pos2 = 0; + while (len != 0) { + l = min_uint32(p1->len - pos1, p2->len - pos2); + l = min_uint32(l, len); + res = js_string_memcmp_pos(p1, pos1, p2, pos2, l); + if (res != 0) + return res; + len -= l; + pos1 += l; + if (pos1 >= p1->len) { + p1 = string_rope_iter_next(&it1); + pos1 = 0; + } + pos2 += l; + if (pos2 >= p2->len) { + p2 = string_rope_iter_next(&it2); + pos2 = 0; + } + } + + if (len1 == len2) + res = 0; + else if (len1 < len2) + res = -1; + else + res = 1; + return res; +} + +/* forward declaration */ +static int string_buffer_concat_value(StringBuffer *s, JSValueConst v); +static JSValue js_rebalance_string_rope(JSContext *ctx, JSValueConst rope); + +/* op1 and op2 must be strings or string ropes */ +static JSValue js_new_string_rope(JSContext *ctx, JSValue op1, JSValue op2) +{ + uint32_t len; + int is_wide_char, depth; + JSStringRope *r; + JSValue res; + + if (JS_VALUE_GET_TAG(op1) == JS_TAG_STRING) { + JSString *p1 = JS_VALUE_GET_STRING(op1); + len = p1->len; + is_wide_char = p1->is_wide_char; + depth = 0; + } else { + JSStringRope *r1 = JS_VALUE_GET_STRING_ROPE(op1); + len = r1->len; + is_wide_char = r1->is_wide_char; + depth = r1->depth; + } + + if (JS_VALUE_GET_TAG(op2) == JS_TAG_STRING) { + JSString *p2 = JS_VALUE_GET_STRING(op2); + len += p2->len; + is_wide_char |= p2->is_wide_char; + } else { + JSStringRope *r2 = JS_VALUE_GET_STRING_ROPE(op2); + len += r2->len; + is_wide_char |= r2->is_wide_char; + depth = max_int(depth, r2->depth); + } + if (len > JS_STRING_LEN_MAX) { + JS_ThrowInternalError(ctx, "string too long"); + goto fail; + } + r = js_malloc(ctx, sizeof(*r)); + if (!r) + goto fail; + JS_REF_COUNT(r) = 1; + r->len = len; + r->is_wide_char = is_wide_char; + r->depth = depth + 1; + r->left = op1; + r->right = op2; + res = JS_MKPTR(JS_TAG_STRING_ROPE, r); + if (r->depth > JS_STRING_ROPE_MAX_DEPTH) { + JSValue res2; +#ifdef DUMP_ROPE_REBALANCE + printf("rebalance: initial depth=%d\n", r->depth); +#endif + res2 = js_rebalance_string_rope(ctx, res); +#ifdef DUMP_ROPE_REBALANCE + if (JS_VALUE_GET_TAG(res2) == JS_TAG_STRING_ROPE) + printf("rebalance: final depth=%d\n", JS_VALUE_GET_STRING_ROPE(res2)->depth); +#endif + JS_FreeValue(ctx, res); + return res2; + } else { + return res; + } + fail: + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + return JS_EXCEPTION; +} + +#define ROPE_N_BUCKETS 44 + +/* Fibonacci numbers starting from F_2 */ +static const uint32_t rope_bucket_len[ROPE_N_BUCKETS] = { + 1, 2, 3, 5, + 8, 13, 21, 34, + 55, 89, 144, 233, + 377, 610, 987, 1597, + 2584, 4181, 6765, 10946, + 17711, 28657, 46368, 75025, + 121393, 196418, 317811, 514229, + 832040, 1346269, 2178309, 3524578, + 5702887, 9227465, 14930352, 24157817, + 39088169, 63245986, 102334155, 165580141, + 267914296, 433494437, 701408733, 1134903170, /* > JS_STRING_LEN_MAX */ +}; + +static int js_rebalance_string_rope_rec(JSContext *ctx, JSValue *buckets, + JSValueConst val) +{ + if (JS_VALUE_GET_TAG(val) == JS_TAG_STRING) { + JSString *p = JS_VALUE_GET_STRING(val); + uint32_t len, i; + JSValue a, b; + + len = p->len; + if (len == 0) + return 0; /* nothing to do */ + /* find the bucket i so that rope_bucket_len[i] <= len < + rope_bucket_len[i + 1] and concatenate the ropes in the + buckets before */ + a = JS_NULL; + i = 0; + while (len >= rope_bucket_len[i + 1]) { + b = buckets[i]; + if (!JS_IsNull(b)) { + buckets[i] = JS_NULL; + if (JS_IsNull(a)) { + a = b; + } else { + a = js_new_string_rope(ctx, b, a); + if (JS_IsException(a)) + return -1; + } + } + i++; + } + if (!JS_IsNull(a)) { + a = js_new_string_rope(ctx, a, js_dup(val)); + if (JS_IsException(a)) + return -1; + } else { + a = js_dup(val); + } + while (!JS_IsNull(buckets[i])) { + a = js_new_string_rope(ctx, buckets[i], a); + buckets[i] = JS_NULL; + if (JS_IsException(a)) + return -1; + i++; + } + buckets[i] = a; + } else { + JSStringRope *r = JS_VALUE_GET_STRING_ROPE(val); + if (js_rebalance_string_rope_rec(ctx, buckets, r->left)) + return -1; + if (js_rebalance_string_rope_rec(ctx, buckets, r->right)) + return -1; + } + return 0; +} + +/* Return a new rope which is balanced. Algorithm from "Ropes: an + Alternative to Strings", Hans-J. Boehm, Russ Atkinson and Michael + Plass. */ +static JSValue js_rebalance_string_rope(JSContext *ctx, JSValueConst rope) +{ + JSValue buckets[ROPE_N_BUCKETS], a, b; + int i; + + for(i = 0; i < ROPE_N_BUCKETS; i++) + buckets[i] = JS_NULL; + if (js_rebalance_string_rope_rec(ctx, buckets, rope)) + goto fail; + a = JS_NULL; + for(i = 0; i < ROPE_N_BUCKETS; i++) { + b = buckets[i]; + if (!JS_IsNull(b)) { + buckets[i] = JS_NULL; + if (JS_IsNull(a)) { + a = b; + } else { + a = js_new_string_rope(ctx, b, a); + if (JS_IsException(a)) + goto fail; + } + } + } + /* fail safe */ + if (JS_IsNull(a)) + return JS_AtomToString(ctx, JS_ATOM_empty_string); + else + return a; + fail: + for(i = 0; i < ROPE_N_BUCKETS; i++) { + JS_FreeValue(ctx, buckets[i]); + } + return JS_EXCEPTION; +} + +/* 'rope' must be a rope. return a string and modify the rope so that + it won't need to be linearized again. */ +static JSValue js_linearize_string_rope(JSContext *ctx, JSValueConst rope) +{ + StringBuffer b_s, *b = &b_s; + JSStringRope *r; + JSValue ret; + + r = JS_VALUE_GET_STRING_ROPE(rope); + + /* check whether it is already linearized */ + if (JS_VALUE_GET_TAG(r->right) == JS_TAG_STRING && + JS_VALUE_GET_STRING(r->right)->len == 0) { + ret = js_dup(r->left); + return ret; + } + if (string_buffer_init2(ctx, b, r->len, r->is_wide_char)) + goto fail; + if (string_buffer_concat_value(b, rope)) + goto fail; + ret = string_buffer_end(b); + if (JS_REF_COUNT(r) > 1) { + /* update the rope so that it won't need to be linearized again */ + JS_FreeValue(ctx, r->left); + JS_FreeValue(ctx, r->right); + r->left = js_dup(ret); + r->right = JS_AtomToString(ctx, JS_ATOM_empty_string); + } + return ret; + fail: + return JS_EXCEPTION; +} + +/* flat string concatenation - used by rope when concatenating short strings */ +static JSValue JS_ConcatString2(JSContext *ctx, JSValue op1, JSValue op2); + +static JSValue JS_ConcatString1(JSContext *ctx, JSString *p1, JSString *p2) +{ + JSString *p; + uint32_t len; + int is_wide_char; + + len = p1->len + p2->len; + if (len > JS_STRING_LEN_MAX) + return JS_ThrowRangeError(ctx, "invalid string length"); + is_wide_char = p1->is_wide_char | p2->is_wide_char; + p = js_alloc_string(ctx, len, is_wide_char); + if (!p) + return JS_EXCEPTION; + if (!is_wide_char) { + memcpy(str8(p), str8(p1), p1->len); + memcpy(str8(p) + p1->len, str8(p2), p2->len); + str8(p)[len] = '\0'; + } else { + copy_str16(str16(p), p1, 0, p1->len); + copy_str16(str16(p) + p1->len, p2, 0, p2->len); + } + return JS_MKPTR(JS_TAG_STRING, p); +} + +/* flat string concatenation - op1 and op2 must be JS_TAG_STRING */ +static JSValue JS_ConcatString2(JSContext *ctx, JSValue op1, JSValue op2) +{ + JSValue ret; + JSString *p1, *p2; + + p1 = JS_VALUE_GET_STRING(op1); + p2 = JS_VALUE_GET_STRING(op2); + + /* XXX: could also check if p1 is empty */ + if (p2->len == 0) { + goto ret_op1; + } + if (JS_REF_COUNT(p1) == 1 && p1->is_wide_char == p2->is_wide_char + && js_malloc_usable_size(ctx, p1) >= sizeof(*p1) + ((p1->len + p2->len) << p2->is_wide_char) + 1 - p1->is_wide_char) { + /* Concatenate in place in available space at the end of p1 */ + if (p1->is_wide_char) { + memcpy(str16(p1) + p1->len, str16(p2), p2->len << 1); + p1->len += p2->len; + } else { + memcpy(str8(p1) + p1->len, str8(p2), p2->len); + p1->len += p2->len; + str8(p1)[p1->len] = '\0'; + } + ret_op1: + JS_FreeValue(ctx, op2); + return op1; + } + ret = JS_ConcatString1(ctx, p1, p2); + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + return ret; +} + +/* op1 and op2 are converted to strings. For convenience, op1 or op2 = + JS_EXCEPTION are accepted and return JS_EXCEPTION. */ +static JSValue JS_ConcatString(JSContext *ctx, JSValue op1, JSValue op2) +{ + JSString *p1, *p2; + + if (unlikely(!tag_is_string(JS_VALUE_GET_TAG(op1)))) { + op1 = JS_ToStringFree(ctx, op1); + if (JS_IsException(op1)) { + JS_FreeValue(ctx, op2); + return JS_EXCEPTION; + } + } + if (unlikely(!tag_is_string(JS_VALUE_GET_TAG(op2)))) { + op2 = JS_ToStringFree(ctx, op2); + if (JS_IsException(op2)) { + JS_FreeValue(ctx, op1); + return JS_EXCEPTION; + } + } + + /* normal concatenation for short strings */ + if (JS_VALUE_GET_TAG(op2) == JS_TAG_STRING) { + p2 = JS_VALUE_GET_STRING(op2); + if (p2->len == 0) { + JS_FreeValue(ctx, op2); + return op1; + } + if (p2->len <= JS_STRING_ROPE_SHORT_LEN) { + if (JS_VALUE_GET_TAG(op1) == JS_TAG_STRING) { + p1 = JS_VALUE_GET_STRING(op1); + if (p1->len <= JS_STRING_ROPE_SHORT2_LEN) { + return JS_ConcatString2(ctx, op1, op2); + } else { + return js_new_string_rope(ctx, op1, op2); + } + } else { + JSStringRope *r1; + r1 = JS_VALUE_GET_STRING_ROPE(op1); + if (JS_VALUE_GET_TAG(r1->right) == JS_TAG_STRING && + JS_VALUE_GET_STRING(r1->right)->len <= JS_STRING_ROPE_SHORT_LEN) { + JSValue val, ret; + val = JS_ConcatString2(ctx, js_dup(r1->right), op2); + if (JS_IsException(val)) { + JS_FreeValue(ctx, op1); + return JS_EXCEPTION; + } + ret = js_new_string_rope(ctx, js_dup(r1->left), val); + JS_FreeValue(ctx, op1); + return ret; + } + } + } + } else if (JS_VALUE_GET_TAG(op1) == JS_TAG_STRING) { + JSStringRope *r2; + p1 = JS_VALUE_GET_STRING(op1); + if (p1->len == 0) { + JS_FreeValue(ctx, op1); + return op2; + } + r2 = JS_VALUE_GET_STRING_ROPE(op2); + if (JS_VALUE_GET_TAG(r2->left) == JS_TAG_STRING && + JS_VALUE_GET_STRING(r2->left)->len <= JS_STRING_ROPE_SHORT_LEN) { + JSValue val, ret; + val = JS_ConcatString2(ctx, op1, js_dup(r2->left)); + if (JS_IsException(val)) { + JS_FreeValue(ctx, op2); + return JS_EXCEPTION; + } + ret = js_new_string_rope(ctx, val, js_dup(r2->right)); + JS_FreeValue(ctx, op2); + return ret; + } + } + return js_new_string_rope(ctx, op1, op2); +} + +/* Shape support */ + +static inline size_t get_shape_size(size_t hash_size, size_t prop_size) +{ + return hash_size * sizeof(uint32_t) + sizeof(JSShape) + + prop_size * sizeof(JSShapeProperty); +} + +static inline JSShape *get_shape_from_alloc(void *sh_alloc, size_t hash_size) +{ + (void)hash_size; + return (JSShape *)sh_alloc; /* shape sits at the allocation start */ +} + +/* one-past-the-end of the hash bucket array; buckets are addressed as + prop_hash_end(sh)[-h - 1] for h in [0, prop_hash_mask], in BOTH layouts. */ +static inline uint32_t *prop_hash_end(JSShape *sh) +{ + return sh->hash_table + sh->prop_hash_mask + 1; +} + +/* the JSShapeProperty array */ +static inline JSShapeProperty *get_shape_prop(JSShape *sh) +{ + return (JSShapeProperty *)(void *)(sh->hash_table + sh->prop_hash_mask + 1); +} + +static inline void *get_alloc_from_shape(JSShape *sh) +{ + return sh; /* shape sits at the allocation start */ +} + +static int init_shape_hash(JSRuntime *rt) +{ + rt->shape_hash_bits = 6; /* 64 shapes */ + rt->shape_hash_size = 1 << rt->shape_hash_bits; + rt->shape_hash_count = 0; + rt->shape_hash = js_mallocz_rt(rt, sizeof(rt->shape_hash[0]) * + rt->shape_hash_size); + if (!rt->shape_hash) + return -1; + return 0; +} + +static uintptr_t hash_atom(JSAtom atom, uintptr_t mask) +{ + int shift = 1 + clz_uintptr(mask+1); + return hash_uintptr(atom) >> shift; +} + +static uint32_t shape_hash(uint32_t h, uint32_t val) +{ + return hash32(h + val); +} + +/* truncate the shape hash to 'hash_bits' bits */ +static uint32_t get_shape_hash(uint32_t h, int hash_bits) +{ + return h >> (32 - hash_bits); +} + +static uint32_t shape_initial_hash(JSObject *proto) +{ + uint32_t h; + h = shape_hash(1, (uintptr_t)proto); + if (sizeof(proto) > 4) + h = shape_hash(h, (uint64_t)(uintptr_t)proto >> 32); + return h; +} + +static int resize_shape_hash(JSRuntime *rt, int new_shape_hash_bits) +{ + int new_shape_hash_size, i; + uint32_t h; + JSShape **new_shape_hash, *sh, *sh_next; + + new_shape_hash_size = 1 << new_shape_hash_bits; + new_shape_hash = js_mallocz_rt(rt, sizeof(rt->shape_hash[0]) * + new_shape_hash_size); + if (!new_shape_hash) + return -1; + for(i = 0; i < rt->shape_hash_size; i++) { + for(sh = rt->shape_hash[i]; sh != NULL; sh = sh_next) { + sh_next = sh->shape_hash_next; + h = get_shape_hash(sh->hash, new_shape_hash_bits); + sh->shape_hash_next = new_shape_hash[h]; + new_shape_hash[h] = sh; + } + } + js_free_rt(rt, rt->shape_hash); + rt->shape_hash_bits = new_shape_hash_bits; + rt->shape_hash_size = new_shape_hash_size; + rt->shape_hash = new_shape_hash; + return 0; +} + +static void js_shape_hash_link(JSRuntime *rt, JSShape *sh) +{ + uint32_t h; + h = get_shape_hash(sh->hash, rt->shape_hash_bits); + sh->shape_hash_next = rt->shape_hash[h]; + rt->shape_hash[h] = sh; + rt->shape_hash_count++; +} + +static void js_shape_hash_unlink(JSRuntime *rt, JSShape *sh) +{ + uint32_t h; + JSShape **psh; + + h = get_shape_hash(sh->hash, rt->shape_hash_bits); + psh = &rt->shape_hash[h]; + while (*psh != sh) + psh = &(*psh)->shape_hash_next; + *psh = sh->shape_hash_next; + rt->shape_hash_count--; +} + +/* create a new empty shape with prototype 'proto'. It is not hashed */ +static inline JSShape *js_new_shape_nohash(JSContext *ctx, JSObject *proto, + int hash_size, int prop_size) +{ + JSRuntime *rt = ctx->rt; + void *sh_alloc; + JSShape *sh; + + sh_alloc = js_malloc(ctx, get_shape_size(hash_size, prop_size)); + if (!sh_alloc) + return NULL; + sh = get_shape_from_alloc(sh_alloc, hash_size); + JS_REF_COUNT(sh) = 1; + add_gc_object(rt, &sh->header, JS_GC_OBJ_TYPE_SHAPE); + if (proto) + js_dup(JS_MKPTR(JS_TAG_OBJECT, proto)); + sh->proto = proto; + /* prop_hash_mask must be set before prop_hash_end(sh) is used, as the hash + location depends on it in the merged-header layout. */ + sh->prop_hash_mask = hash_size - 1; + memset(prop_hash_end(sh) - hash_size, 0, sizeof(prop_hash_end(sh)[0]) * + hash_size); + sh->prop_size = prop_size; + sh->prop_count = 0; + sh->deleted_prop_count = 0; + sh->is_hashed = false; + return sh; +} + +/* create a new empty shape with prototype 'proto' */ +static no_inline JSShape *js_new_shape2(JSContext *ctx, JSObject *proto, + int hash_size, int prop_size) +{ + JSRuntime *rt = ctx->rt; + JSShape *sh; + + /* resize the shape hash table if necessary */ + if (2 * (rt->shape_hash_count + 1) > rt->shape_hash_size) { + resize_shape_hash(rt, rt->shape_hash_bits + 1); + } + + sh = js_new_shape_nohash(ctx, proto, hash_size, prop_size); + if (!sh) + return NULL; + + /* insert in the hash table */ + sh->hash = shape_initial_hash(proto); + sh->is_hashed = true; + js_shape_hash_link(ctx->rt, sh); + return sh; +} + +static JSShape *js_new_shape(JSContext *ctx, JSObject *proto) +{ + return js_new_shape2(ctx, proto, JS_PROP_INITIAL_HASH_SIZE, + JS_PROP_INITIAL_SIZE); +} + +static JSObject *object_or_null(JSValueConst val) +{ + if (JS_TAG_OBJECT == JS_VALUE_GET_TAG(val)) + return JS_VALUE_GET_OBJ(val); + return NULL; +} + +static int add_shape_property(JSContext *ctx, JSShape **psh, + JSObject *p, JSAtom atom, int prop_flags); + +static JSShape *js_new_shape_with2(JSContext *ctx, JSObject *proto, + int prop_count, const JSShapeProperty props[]) { + JSShape *sh; + int i; + + sh = js_new_shape2(ctx, proto, JS_PROP_INITIAL_HASH_SIZE, prop_count); + if (sh) + for (i = 0; i < prop_count; i++) + if (add_shape_property(ctx, &sh, NULL, props[i].atom, props[i].flags)) + goto fail; + return sh; +fail: + js_free_shape(ctx->rt, sh); + return NULL; +} + +static int js_new_shape_with(JSContext *ctx, JSShape **psh, JSValueConst proto, + int prop_count, const JSShapeProperty props[]) { + *psh = js_new_shape_with2(ctx, object_or_null(proto), prop_count, props); + if (*psh) + return 0; + return -1; +} + +/* The shape is cloned. The new shape is not inserted in the shape + hash table */ +static JSShape *js_clone_shape(JSContext *ctx, JSShape *sh1) +{ + JSShape *sh; + void *sh_alloc, *sh_alloc1; + size_t size; + JSShapeProperty *pr; + uint32_t i, hash_size; + + hash_size = sh1->prop_hash_mask + 1; + size = get_shape_size(hash_size, sh1->prop_size); + sh_alloc = js_malloc(ctx, size); + if (!sh_alloc) + return NULL; + sh_alloc1 = get_alloc_from_shape(sh1); + memcpy(sh_alloc, sh_alloc1, size); + sh = get_shape_from_alloc(sh_alloc, hash_size); + JS_REF_COUNT(sh) = 1; + add_gc_object(ctx->rt, &sh->header, JS_GC_OBJ_TYPE_SHAPE); + sh->is_hashed = false; + if (sh->proto) { + js_dup(JS_MKPTR(JS_TAG_OBJECT, sh->proto)); + } + for(i = 0, pr = get_shape_prop(sh); i < sh->prop_count; i++, pr++) { + JS_DupAtom(ctx, pr->atom); + } + return sh; +} + +static JSShape *js_dup_shape(JSShape *sh) +{ + JS_REF_COUNT(sh)++; + return sh; +} + +static void js_free_shape0(JSRuntime *rt, JSShape *sh) +{ + uint32_t i; + JSShapeProperty *pr; + + assert(JS_REF_COUNT(sh) == 0); + if (sh->is_hashed) + js_shape_hash_unlink(rt, sh); + if (sh->proto != NULL) { + JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_OBJECT, sh->proto)); + } + pr = get_shape_prop(sh); + for(i = 0; i < sh->prop_count; i++) { + JS_FreeAtomRT(rt, pr->atom); + pr++; + } + remove_gc_object(&sh->header); + js_free_rt(rt, get_alloc_from_shape(sh)); +} + +static void js_free_shape(JSRuntime *rt, JSShape *sh) +{ + if (unlikely(--JS_REF_COUNT(sh) <= 0)) { + js_free_shape0(rt, sh); + } +} + +static void js_free_shape_null(JSRuntime *rt, JSShape *sh) +{ + if (sh) + js_free_shape(rt, sh); +} + +/* make space to hold at least 'count' properties */ +static no_inline int resize_properties(JSContext *ctx, JSShape **psh, + JSObject *p, uint32_t count) +{ + JSShape *sh; + uint32_t new_size, new_hash_size, new_hash_mask, i; + JSShapeProperty *pr; + void *sh_alloc; + intptr_t h; + + sh = *psh; + new_size = max_int(count, sh->prop_size * 3 / 2); + /* Reallocate prop array first to avoid crash or size inconsistency + in case of memory allocation failure */ + if (p) { + JSProperty *new_prop; + new_prop = js_realloc(ctx, p->prop, sizeof(new_prop[0]) * new_size); + if (unlikely(!new_prop)) + return -1; + p->prop = new_prop; + } + new_hash_size = sh->prop_hash_mask + 1; + while (new_hash_size < new_size) + new_hash_size = 2 * new_hash_size; + if (new_hash_size != (sh->prop_hash_mask + 1)) { + JSShape *old_sh; + /* resize the hash table and the properties */ + old_sh = sh; + sh_alloc = js_malloc(ctx, get_shape_size(new_hash_size, new_size)); + if (!sh_alloc) + return -1; + sh = get_shape_from_alloc(sh_alloc, new_hash_size); + list_del(&old_sh->header.link); + /* copy the shape header, then the properties. Their location relative + to the struct differs by layout, so copy via get_shape_prop(). */ + memcpy(sh, old_sh, sizeof(JSShape)); + list_add_tail(&sh->header.link, &ctx->rt->gc_obj_list); + /* the GC/refcount fields live in the block header, not the struct, so + the memcpy above did not carry them: transfer them explicitly */ + JS_REF_COUNT(sh) = JS_REF_COUNT(old_sh); + JS_GC_TYPE(sh) = JS_GC_TYPE(old_sh); + JS_GC_MARK(sh) = JS_GC_MARK(old_sh); + new_hash_mask = new_hash_size - 1; + sh->prop_hash_mask = new_hash_mask; + memcpy(get_shape_prop(sh), get_shape_prop(old_sh), + sizeof(JSShapeProperty) * old_sh->prop_count); + memset(prop_hash_end(sh) - new_hash_size, 0, + sizeof(prop_hash_end(sh)[0]) * new_hash_size); + for(i = 0, pr = get_shape_prop(sh); i < sh->prop_count; i++, pr++) { + if (pr->atom != JS_ATOM_NULL) { + h = hash_atom(pr->atom, new_hash_mask); + pr->hash_next = prop_hash_end(sh)[-h - 1]; + prop_hash_end(sh)[-h - 1] = i + 1; + } + } + js_free(ctx, get_alloc_from_shape(old_sh)); + } else { + /* only resize the properties */ + list_del(&sh->header.link); + sh_alloc = js_realloc(ctx, get_alloc_from_shape(sh), + get_shape_size(new_hash_size, new_size)); + if (unlikely(!sh_alloc)) { + /* insert again in the GC list */ + list_add_tail(&sh->header.link, &ctx->rt->gc_obj_list); + return -1; + } + sh = get_shape_from_alloc(sh_alloc, new_hash_size); + list_add_tail(&sh->header.link, &ctx->rt->gc_obj_list); + } + *psh = sh; + sh->prop_size = new_size; + return 0; +} + +/* remove the deleted properties. */ +static int compact_properties(JSContext *ctx, JSObject *p) +{ + JSShape *sh, *old_sh; + void *sh_alloc; + intptr_t h; + uint32_t new_hash_size, i, j, new_hash_mask, new_size; + JSShapeProperty *old_pr, *pr; + JSProperty *prop, *new_prop; + + sh = p->shape; + assert(!sh->is_hashed); + + new_size = max_int(JS_PROP_INITIAL_SIZE, + sh->prop_count - sh->deleted_prop_count); + assert(new_size <= sh->prop_size); + + new_hash_size = sh->prop_hash_mask + 1; + while ((new_hash_size / 2) >= new_size) + new_hash_size = new_hash_size / 2; + new_hash_mask = new_hash_size - 1; + + /* resize the hash table and the properties */ + old_sh = sh; + sh_alloc = js_malloc(ctx, get_shape_size(new_hash_size, new_size)); + if (!sh_alloc) + return -1; + sh = get_shape_from_alloc(sh_alloc, new_hash_size); + list_del(&old_sh->header.link); + memcpy(sh, old_sh, sizeof(JSShape)); + list_add_tail(&sh->header.link, &ctx->rt->gc_obj_list); + /* the GC/refcount fields live in the block header, not the struct, so the + memcpy above did not carry them: transfer them explicitly */ + JS_REF_COUNT(sh) = JS_REF_COUNT(old_sh); + JS_GC_TYPE(sh) = JS_GC_TYPE(old_sh); + JS_GC_MARK(sh) = JS_GC_MARK(old_sh); + + /* set the new hash mask before prop_hash_end()/get_shape_prop() are used, + as their locations depend on it in the merged-header layout */ + sh->prop_hash_mask = new_hash_mask; + memset(prop_hash_end(sh) - new_hash_size, 0, + sizeof(prop_hash_end(sh)[0]) * new_hash_size); + + j = 0; + old_pr = get_shape_prop(old_sh); + pr = get_shape_prop(sh); + prop = p->prop; + for(i = 0; i < sh->prop_count; i++) { + if (old_pr->atom != JS_ATOM_NULL) { + pr->atom = old_pr->atom; + pr->flags = old_pr->flags; + h = hash_atom(old_pr->atom, new_hash_mask); + pr->hash_next = prop_hash_end(sh)[-h - 1]; + prop_hash_end(sh)[-h - 1] = j + 1; + prop[j] = prop[i]; + j++; + pr++; + } + old_pr++; + } + assert(j == (sh->prop_count - sh->deleted_prop_count)); + sh->prop_hash_mask = new_hash_mask; + sh->prop_size = new_size; + sh->deleted_prop_count = 0; + sh->prop_count = j; + + p->shape = sh; + js_free(ctx, get_alloc_from_shape(old_sh)); + + /* reduce the size of the object properties */ + new_prop = js_realloc(ctx, p->prop, sizeof(new_prop[0]) * new_size); + if (new_prop) + p->prop = new_prop; + return 0; +} + +static int add_shape_property(JSContext *ctx, JSShape **psh, + JSObject *p, JSAtom atom, int prop_flags) +{ + JSRuntime *rt = ctx->rt; + JSShape *sh = *psh; + JSShapeProperty *pr, *prop; + uint32_t hash_mask, new_shape_hash = 0; + intptr_t h; + + /* update the shape hash */ + if (sh->is_hashed) { + js_shape_hash_unlink(rt, sh); + new_shape_hash = shape_hash(shape_hash(sh->hash, atom), prop_flags); + } + + if (unlikely(sh->prop_count >= sh->prop_size)) { + if (resize_properties(ctx, psh, p, sh->prop_count + 1)) { + /* in case of error, reinsert in the hash table. + sh is still valid if resize_properties() failed */ + if (sh->is_hashed) + js_shape_hash_link(rt, sh); + return -1; + } + sh = *psh; + } + if (sh->is_hashed) { + sh->hash = new_shape_hash; + js_shape_hash_link(rt, sh); + } + /* Initialize the new shape property. + The object property at p->prop[sh->prop_count] is uninitialized */ + prop = get_shape_prop(sh); + pr = &prop[sh->prop_count++]; + pr->atom = JS_DupAtom(ctx, atom); + pr->flags = prop_flags; + /* add in hash table */ + hash_mask = sh->prop_hash_mask; + h = hash_atom(atom, hash_mask); + pr->hash_next = prop_hash_end(sh)[-h - 1]; + prop_hash_end(sh)[-h - 1] = sh->prop_count; + return 0; +} + +/* find a hashed empty shape matching the prototype. Return NULL if + not found */ +static JSShape *find_hashed_shape_proto(JSRuntime *rt, JSObject *proto) +{ + JSShape *sh1; + uint32_t h, h1; + + h = shape_initial_hash(proto); + h1 = get_shape_hash(h, rt->shape_hash_bits); + for(sh1 = rt->shape_hash[h1]; sh1 != NULL; sh1 = sh1->shape_hash_next) { + if (sh1->hash == h && + sh1->proto == proto && + sh1->prop_count == 0) { + return sh1; + } + } + return NULL; +} + +/* find a hashed shape matching sh + (prop, prop_flags). Return NULL if + not found */ +static JSShape *find_hashed_shape_prop(JSRuntime *rt, JSShape *sh, + JSAtom atom, int prop_flags) +{ + JSShape *sh1; + uint32_t h, h1, i, n; + + h = sh->hash; + h = shape_hash(h, atom); + h = shape_hash(h, prop_flags); + h1 = get_shape_hash(h, rt->shape_hash_bits); + for(sh1 = rt->shape_hash[h1]; sh1 != NULL; sh1 = sh1->shape_hash_next) { + /* we test the hash first so that the rest is done only if the + shapes really match */ + if (sh1->hash == h && + sh1->proto == sh->proto && + sh1->prop_count == ((n = sh->prop_count) + 1)) { + for(i = 0; i < n; i++) { + if (unlikely(get_shape_prop(sh1)[i].atom != get_shape_prop(sh)[i].atom) || + unlikely(get_shape_prop(sh1)[i].flags != get_shape_prop(sh)[i].flags)) + goto next; + } + if (unlikely(get_shape_prop(sh1)[n].atom != atom) || + unlikely(get_shape_prop(sh1)[n].flags != prop_flags)) + goto next; + return sh1; + } + next: ; + } + return NULL; +} + +static __maybe_unused void JS_DumpShape(JSRuntime *rt, int i, JSShape *sh) +{ + char atom_buf[ATOM_GET_STR_BUF_SIZE]; + int j; + + /* XXX: should output readable class prototype */ + printf("%5d %3d%c %14p %5d %5d", i, + JS_REF_COUNT(sh), " *"[sh->is_hashed], + (void *)sh->proto, sh->prop_size, sh->prop_count); + for(j = 0; j < sh->prop_count; j++) { + printf(" %s", JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), + get_shape_prop(sh)[j].atom)); + } + printf("\n"); +} + +static __maybe_unused void JS_DumpShapes(JSRuntime *rt) +{ + int i; + JSShape *sh; + struct list_head *el; + JSObject *p; + JSGCObjectHeader *gp; + + printf("JSShapes: {\n"); + printf("%5s %4s %14s %5s %5s %s\n", "SLOT", "REFS", "PROTO", "SIZE", "COUNT", "PROPS"); + for(i = 0; i < rt->shape_hash_size; i++) { + for(sh = rt->shape_hash[i]; sh != NULL; sh = sh->shape_hash_next) { + JS_DumpShape(rt, i, sh); + assert(sh->is_hashed); + } + } + /* dump non-hashed shapes */ + list_for_each(el, &rt->gc_obj_list) { + gp = list_entry(el, JSGCObjectHeader, link); + if (JS_GC_TYPE(gp) == JS_GC_OBJ_TYPE_JS_OBJECT) { + p = (JSObject *)gp; + if (!p->shape->is_hashed) { + JS_DumpShape(rt, -1, p->shape); + } + } + } + printf("}\n"); +} + +/* 'props[]' is used to initialized the object properties. The number + of elements depends on the shape. */ +static JSValue JS_NewObjectFromShape(JSContext *ctx, JSShape *sh, JSClassID class_id, + JSProperty *props) +{ + JSObject *p; + int i; + + js_trigger_gc(ctx->rt, sizeof(JSObject)); + p = js_malloc(ctx, sizeof(JSObject)); + if (unlikely(!p)) + goto fail; + p->class_id = class_id; + p->extensible = true; + p->free_mark = 0; + p->is_exotic = 0; + p->fast_array = 0; + p->is_constructor = 0; + p->is_uncatchable_error = 0; + p->tmp_mark = 0; + p->is_HTMLDDA = 0; + p->is_prototype = 0; + p->first_weak_ref = NULL; + p->u.opaque = NULL; + p->shape = sh; + p->prop = js_malloc(ctx, sizeof(JSProperty) * sh->prop_size); + if (unlikely(!p->prop)) { + js_free(ctx, p); + fail: + if (props) { + JSShapeProperty *prs = get_shape_prop(sh); + for(i = 0; i < sh->prop_count; i++) { + free_property(ctx->rt, &props[i], prs->flags); + prs++; + } + } + js_free_shape(ctx->rt, sh); + return JS_EXCEPTION; + } + + switch(class_id) { + case JS_CLASS_OBJECT: + break; + case JS_CLASS_ARRAY: + { + JSProperty *pr; + p->is_exotic = 1; + p->fast_array = 1; + p->u.array.u.values = NULL; + p->u.array.count = 0; + p->u.array.u1.size = 0; + if (!props) { + /* XXX: remove */ + /* the length property is always the first one */ + if (likely(sh == ctx->array_shape)) { + pr = &p->prop[0]; + } else { + /* only used for the first array */ + /* cannot fail */ + pr = add_property(ctx, p, JS_ATOM_length, + JS_PROP_WRITABLE | JS_PROP_LENGTH); + } + pr->u.value = js_int32(0); + } + } + break; + case JS_CLASS_C_FUNCTION: + p->prop[0].u.value = JS_UNDEFINED; + break; + case JS_CLASS_ARGUMENTS: + case JS_CLASS_MAPPED_ARGUMENTS: + case JS_CLASS_UINT8C_ARRAY: + case JS_CLASS_INT8_ARRAY: + case JS_CLASS_UINT8_ARRAY: + case JS_CLASS_INT16_ARRAY: + case JS_CLASS_UINT16_ARRAY: + case JS_CLASS_INT32_ARRAY: + case JS_CLASS_UINT32_ARRAY: + case JS_CLASS_BIG_INT64_ARRAY: + case JS_CLASS_BIG_UINT64_ARRAY: + case JS_CLASS_FLOAT16_ARRAY: + case JS_CLASS_FLOAT32_ARRAY: + case JS_CLASS_FLOAT64_ARRAY: + p->is_exotic = 1; + p->fast_array = 1; + p->u.array.u.ptr = NULL; + p->u.array.count = 0; + break; + case JS_CLASS_DATAVIEW: + p->u.array.u.ptr = NULL; + p->u.array.count = 0; + break; + case JS_CLASS_ERROR: + case JS_CLASS_NUMBER: + case JS_CLASS_STRING: + case JS_CLASS_BOOLEAN: + case JS_CLASS_SYMBOL: + case JS_CLASS_DATE: + case JS_CLASS_BIG_INT: + p->u.object_data = JS_UNDEFINED; + goto set_exotic; + case JS_CLASS_REGEXP: + p->u.regexp.pattern = NULL; + p->u.regexp.bytecode = NULL; + goto set_exotic; + default: + set_exotic: + if (ctx->rt->class_array[class_id].exotic) { + p->is_exotic = 1; + } + break; + } + JS_REF_COUNT(p) = 1; + add_gc_object(ctx->rt, &p->header, JS_GC_OBJ_TYPE_JS_OBJECT); + if (props) { + for(i = 0; i < sh->prop_count; i++) + p->prop[i] = props[i]; + } + return JS_MKPTR(JS_TAG_OBJECT, p); +} + +/* WARNING: proto must be an object or JS_NULL */ +JSValue JS_NewObjectProtoClass(JSContext *ctx, JSValueConst proto_val, + JSClassID class_id) +{ + JSShape *sh; + JSObject *proto; + + proto = object_or_null(proto_val); + sh = find_hashed_shape_proto(ctx->rt, proto); + if (likely(sh)) { + sh = js_dup_shape(sh); + } else { + sh = js_new_shape(ctx, proto); + if (!sh) + return JS_EXCEPTION; + } + return JS_NewObjectFromShape(ctx, sh, class_id, NULL); +} + +/* WARNING: the shape is not hashed. It is used for objects where + factorizing the shape is not relevant (prototypes, constructors) */ +static JSValue JS_NewObjectProtoClassAlloc(JSContext *ctx, JSValueConst proto_val, + JSClassID class_id, int n_alloc_props) +{ + JSShape *sh; + JSObject *proto; + int hash_size, hash_bits; + + if (n_alloc_props <= JS_PROP_INITIAL_SIZE) { + n_alloc_props = JS_PROP_INITIAL_SIZE; + hash_size = JS_PROP_INITIAL_HASH_SIZE; + } else { + hash_bits = 32 - clz32(n_alloc_props - 1); /* ceil(log2(radix)) */ + hash_size = 1 << hash_bits; + } + proto = object_or_null(proto_val); + sh = js_new_shape_nohash(ctx, proto, hash_size, n_alloc_props); + if (!sh) + return JS_EXCEPTION; + return JS_NewObjectFromShape(ctx, sh, class_id, NULL); +} + +static int JS_SetObjectData(JSContext *ctx, JSValueConst obj, JSValue val) +{ + JSObject *p; + + if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { + p = JS_VALUE_GET_OBJ(obj); + switch(p->class_id) { + case JS_CLASS_NUMBER: + case JS_CLASS_STRING: + case JS_CLASS_BOOLEAN: + case JS_CLASS_SYMBOL: + case JS_CLASS_DATE: + case JS_CLASS_BIG_INT: + JS_FreeValue(ctx, p->u.object_data); + p->u.object_data = val; /* for JS_CLASS_STRING, 'val' must + be JS_TAG_STRING (and not a rope) */ + return 0; + } + } + JS_FreeValue(ctx, val); + if (!JS_IsException(obj)) + JS_ThrowTypeError(ctx, "invalid object type"); + return -1; +} + +JSValue JS_NewObjectClass(JSContext *ctx, JSClassID class_id) +{ + return JS_NewObjectProtoClass(ctx, ctx->class_proto[class_id], class_id); +} + +JSValue JS_NewObjectProto(JSContext *ctx, JSValueConst proto) +{ + return JS_NewObjectProtoClass(ctx, proto, JS_CLASS_OBJECT); +} + +JSValue JS_NewObjectFrom(JSContext *ctx, int count, const JSAtom *props, + const JSValue *values) +{ + JSValue obj; + int i; + + obj = JS_NewObject(ctx); + if (JS_IsException(obj)) + return JS_EXCEPTION; + for (i = 0; i < count; i++) + if (JS_SetProperty(ctx, obj, props[i], values[i]) < 0) + goto fail; + return obj; +fail: + for (/*empty*/; i < count; i++) + JS_FreeValue(ctx, values[i]); + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +JSValue JS_NewObjectFromStr(JSContext *ctx, int count, const char **props, + const JSValue *values) +{ + JSAtom atoms_s[16], *atoms = atoms_s; + JSValue ret; + int i; + + i = 0; + ret = JS_EXCEPTION; + if (count < 1) + goto out; + if (count > (int)countof(atoms_s)) { + atoms = js_malloc(ctx, count * sizeof(*atoms)); + if (!atoms) + return JS_EXCEPTION; + } + for (i = 0; i < count; i++) { + atoms[i] = JS_NewAtom(ctx, props[i]); + if (atoms[i] == JS_ATOM_NULL) + goto out; + } + ret = JS_NewObjectFrom(ctx, count, atoms, values); +out: + while (i-- > 0) + JS_FreeAtom(ctx, atoms[i]); + if (atoms != atoms_s) + js_free(ctx, atoms); + return ret; +} + +JSValue JS_NewArray(JSContext *ctx) +{ + return JS_NewObjectFromShape(ctx, js_dup_shape(ctx->array_shape), + JS_CLASS_ARRAY, NULL); +} + +// note: takes ownership of |values|, unlike js_create_array +JSValue JS_NewArrayFrom(JSContext *ctx, int count, const JSValue *values) +{ + JSObject *p; + JSValue obj; + int i; + + obj = JS_NewArray(ctx); + if (JS_IsException(obj)) + goto exception; + if (count > 0) { + p = JS_VALUE_GET_OBJ(obj); + if (expand_fast_array(ctx, p, count)) { + JS_FreeValue(ctx, obj); + goto exception; + } + p->u.array.count = count; + p->prop[0].u.value = js_int32(count); + memcpy(p->u.array.u.values, values, count * sizeof(*values)); + } + return obj; +exception: + for (i = 0; i < count; i++) + JS_FreeValue(ctx, values[i]); + return JS_EXCEPTION; +} + +JSValue JS_NewObject(JSContext *ctx) +{ + /* inline JS_NewObjectClass(ctx, JS_CLASS_OBJECT); */ + return JS_NewObjectProtoClass(ctx, ctx->class_proto[JS_CLASS_OBJECT], JS_CLASS_OBJECT); +} + +static void js_function_set_properties(JSContext *ctx, JSValue func_obj, + JSAtom name, int len) +{ + /* ES6 feature non compatible with ES5.1: length is configurable */ + JS_DefinePropertyValue(ctx, func_obj, JS_ATOM_length, js_int32(len), + JS_PROP_CONFIGURABLE); + JS_DefinePropertyValue(ctx, func_obj, JS_ATOM_name, + JS_AtomToString(ctx, name), JS_PROP_CONFIGURABLE); +} + +static bool js_class_has_bytecode(JSClassID class_id) +{ + return (class_id == JS_CLASS_BYTECODE_FUNCTION || + class_id == JS_CLASS_GENERATOR_FUNCTION || + class_id == JS_CLASS_ASYNC_FUNCTION || + class_id == JS_CLASS_ASYNC_GENERATOR_FUNCTION); +} + +/* return NULL without exception if not a function or no bytecode */ +static JSFunctionBytecode *JS_GetFunctionBytecode(JSValueConst val) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) + return NULL; + p = JS_VALUE_GET_OBJ(val); + if (!js_class_has_bytecode(p->class_id)) + return NULL; + return p->u.func.function_bytecode; +} + +static void js_method_set_home_object(JSContext *ctx, JSValue func_obj, + JSValue home_obj) +{ + JSObject *p, *p1; + JSFunctionBytecode *b; + + if (JS_VALUE_GET_TAG(func_obj) != JS_TAG_OBJECT) + return; + p = JS_VALUE_GET_OBJ(func_obj); + if (!js_class_has_bytecode(p->class_id)) + return; + b = p->u.func.function_bytecode; + if (b->need_home_object) { + p1 = p->u.func.home_object; + if (p1) { + JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p1)); + } + if (JS_VALUE_GET_TAG(home_obj) == JS_TAG_OBJECT) + p1 = JS_VALUE_GET_OBJ(js_dup(home_obj)); + else + p1 = NULL; + p->u.func.home_object = p1; + } +} + +static JSValue js_get_function_name(JSContext *ctx, JSAtom name) +{ + JSValue name_str; + + name_str = JS_AtomToString(ctx, name); + if (JS_AtomSymbolHasDescription(ctx, name)) { + name_str = JS_ConcatString3(ctx, "[", name_str, "]"); + } + return name_str; +} + +/* Modify the name of a method according to the atom and + 'flags'. 'flags' is a bitmask of JS_PROP_HAS_GET and + JS_PROP_HAS_SET. Also set the home object of the method. + Return < 0 if exception. */ +static int js_method_set_properties(JSContext *ctx, JSValue func_obj, + JSAtom name, int flags, JSValue home_obj) +{ + JSValue name_str; + + name_str = js_get_function_name(ctx, name); + if (flags & JS_PROP_HAS_GET) { + name_str = JS_ConcatString3(ctx, "get ", name_str, ""); + } else if (flags & JS_PROP_HAS_SET) { + name_str = JS_ConcatString3(ctx, "set ", name_str, ""); + } + if (JS_IsException(name_str)) + return -1; + if (JS_DefinePropertyValue(ctx, func_obj, JS_ATOM_name, name_str, + JS_PROP_CONFIGURABLE) < 0) + return -1; + js_method_set_home_object(ctx, func_obj, home_obj); + return 0; +} + +/* Note: at least 'length' arguments will be readable in 'argv' */ +/* `name` may be NULL, pure ASCII or UTF-8 encoded */ +JSValue JS_NewCFunction3(JSContext *ctx, JSCFunction *func, + const char *name, + int length, JSCFunctionEnum cproto, int magic, + JSValueConst proto_val, int n_fields) +{ + JSValue func_obj; + JSObject *p; + JSAtom name_atom; + + if (n_fields > 0) { + func_obj = JS_NewObjectProtoClassAlloc(ctx, proto_val, JS_CLASS_C_FUNCTION, n_fields); + } else { + func_obj = JS_NewObjectProtoClass(ctx, proto_val, JS_CLASS_C_FUNCTION); + } + if (JS_IsException(func_obj)) + return func_obj; + p = JS_VALUE_GET_OBJ(func_obj); + p->u.cfunc.realm = JS_DupContext(ctx); + p->u.cfunc.c_function.generic = func; + p->u.cfunc.length = length; + p->u.cfunc.cproto = cproto; + p->u.cfunc.magic = magic; + p->is_constructor = (cproto == JS_CFUNC_constructor || + cproto == JS_CFUNC_constructor_magic || + cproto == JS_CFUNC_constructor_or_func || + cproto == JS_CFUNC_constructor_or_func_magic); + name_atom = JS_ATOM_empty_string; + if (name && *name) { + name_atom = JS_NewAtom(ctx, name); + if (name_atom == JS_ATOM_NULL) { + JS_FreeValue(ctx, func_obj); + return JS_EXCEPTION; + } + } + js_function_set_properties(ctx, func_obj, name_atom, length); + JS_FreeAtom(ctx, name_atom); + return func_obj; +} + +/* Note: at least 'length' arguments will be readable in 'argv' */ +JSValue JS_NewCFunction2(JSContext *ctx, JSCFunction *func, + const char *name, + int length, JSCFunctionEnum cproto, int magic) +{ + return JS_NewCFunction3(ctx, func, name, length, cproto, magic, + ctx->function_proto, 0); +} + +typedef struct JSCFunctionDataRecord { + JSCFunctionData *func; + uint8_t length; + uint8_t data_len; + uint16_t magic; + JSValue data[]; +} JSCFunctionDataRecord; + +static void js_c_function_data_finalizer(JSRuntime *rt, JSValueConst val) +{ + JSCFunctionDataRecord *s = JS_GetOpaque(val, JS_CLASS_C_FUNCTION_DATA); + int i; + + if (s) { + for(i = 0; i < s->data_len; i++) { + JS_FreeValueRT(rt, s->data[i]); + } + js_free_rt(rt, s); + } +} + +static void js_c_function_data_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func) +{ + JSCFunctionDataRecord *s = JS_GetOpaque(val, JS_CLASS_C_FUNCTION_DATA); + int i; + + if (s) { + for(i = 0; i < s->data_len; i++) { + JS_MarkValue(rt, s->data[i], mark_func); + } + } +} + +static JSValue js_call_c_function_data(JSContext *ctx, JSValueConst func_obj, + JSValueConst this_val, + int argc, JSValueConst *argv, int flags) +{ + JSRuntime *rt = ctx->rt; + JSStackFrame sf_s, *sf = &sf_s, *prev_sf; + JSCFunctionDataRecord *s; + JSValueConst *arg_buf; + JSValue ret; + size_t stack_size; + int arg_count; + int i; + + s = JS_GetOpaque(func_obj, JS_CLASS_C_FUNCTION_DATA); + if (!s) + return JS_EXCEPTION; // can't really happen + arg_buf = argv; + arg_count = s->length; + if (unlikely(argc < arg_count)) { + stack_size = arg_count * sizeof(arg_buf[0]); + if (js_check_stack_overflow(rt, stack_size)) + return JS_ThrowStackOverflow(ctx); + arg_buf = alloca(stack_size); + for(i = 0; i < argc; i++) + arg_buf[i] = argv[i]; + for(i = argc; i < arg_count; i++) + arg_buf[i] = JS_UNDEFINED; + } + prev_sf = rt->current_stack_frame; + sf->prev_frame = prev_sf; + rt->current_stack_frame = sf; + // TODO(bnoordhuis) switch realms like js_call_c_function does + sf->is_strict_mode = false; + sf->is_constructor = (flags & JS_CALL_FLAG_CONSTRUCTOR) != 0; + sf->cur_func = unsafe_unconst(func_obj); + sf->arg_count = argc; + ret = s->func(ctx, this_val, argc, arg_buf, s->magic, vc(s->data)); + rt->current_stack_frame = sf->prev_frame; + return ret; +} + +JSValue JS_NewCFunctionData2(JSContext *ctx, JSCFunctionData *func, + const char *name, + int length, int magic, int data_len, + JSValueConst *data) +{ + JSCFunctionDataRecord *s; + JSAtom name_atom; + JSValue func_obj; + int i; + + func_obj = JS_NewObjectProtoClass(ctx, ctx->function_proto, + JS_CLASS_C_FUNCTION_DATA); + if (JS_IsException(func_obj)) + return func_obj; + s = js_malloc(ctx, sizeof(*s) + data_len * sizeof(JSValue)); + if (!s) { + JS_FreeValue(ctx, func_obj); + return JS_EXCEPTION; + } + s->func = func; + s->length = length; + s->data_len = data_len; + s->magic = magic; + for(i = 0; i < data_len; i++) + s->data[i] = js_dup(data[i]); + JS_SetOpaqueInternal(func_obj, s); + name_atom = JS_ATOM_empty_string; + if (name && *name) { + name_atom = JS_NewAtom(ctx, name); + if (name_atom == JS_ATOM_NULL) { + JS_FreeValue(ctx, func_obj); + return JS_EXCEPTION; + } + } + js_function_set_properties(ctx, func_obj, name_atom, length); + JS_FreeAtom(ctx, name_atom); + return func_obj; +} + +JSValue JS_NewCFunctionData(JSContext *ctx, JSCFunctionData *func, + int length, int magic, int data_len, + JSValueConst *data) +{ + return JS_NewCFunctionData2(ctx, func, NULL, length, magic, data_len, data); +} + +static JSContext *js_autoinit_get_realm(JSProperty *pr) +{ + return (JSContext *)(pr->u.init.realm_and_id & ~3); +} + +static JSAutoInitIDEnum js_autoinit_get_id(JSProperty *pr) +{ + return pr->u.init.realm_and_id & 3; +} + +static void js_autoinit_free(JSRuntime *rt, JSProperty *pr) +{ + JS_FreeContext(js_autoinit_get_realm(pr)); +} + +static void js_autoinit_mark(JSRuntime *rt, JSProperty *pr, + JS_MarkFunc *mark_func) +{ + mark_func(rt, &js_autoinit_get_realm(pr)->header); +} + +typedef struct JSCClosureRecord { + JSCClosure *func; + uint16_t length; + uint16_t magic; + void *opaque; + void (*opaque_finalize)(void *opaque); +} JSCClosureRecord; + +static void js_c_closure_finalizer(JSRuntime *rt, JSValueConst val) +{ + JSCClosureRecord *s = JS_GetOpaque(val, JS_CLASS_C_CLOSURE); + + if (s) { + if (s->opaque_finalize) + s->opaque_finalize(s->opaque); + + js_free_rt(rt, s); + } +} + +static JSValue js_call_c_closure(JSContext *ctx, JSValueConst func_obj, + JSValueConst this_val, + int argc, JSValueConst *argv, int flags) +{ + JSRuntime *rt = ctx->rt; + JSStackFrame sf_s, *sf = &sf_s, *prev_sf; + JSCClosureRecord *s = JS_GetOpaque(func_obj, JS_CLASS_C_CLOSURE); + JSValueConst *arg_buf; + JSValue ret; + int arg_count; + int i; + size_t stack_size; + + arg_buf = argv; + arg_count = s->length; + if (unlikely(argc < arg_count)) { + stack_size = arg_count * sizeof(arg_buf[0]); + if (js_check_stack_overflow(rt, stack_size)) + return JS_ThrowStackOverflow(ctx); + arg_buf = alloca(stack_size); + for (i = 0; i < argc; i++) + arg_buf[i] = argv[i]; + for (i = argc; i < arg_count; i++) + arg_buf[i] = JS_UNDEFINED; + } + + prev_sf = rt->current_stack_frame; + sf->prev_frame = prev_sf; + rt->current_stack_frame = sf; + // TODO(bnoordhuis) switch realms like js_call_c_function does + sf->is_strict_mode = false; + sf->is_constructor = (flags & JS_CALL_FLAG_CONSTRUCTOR) != 0; + sf->cur_func = unsafe_unconst(func_obj); + sf->arg_count = argc; + ret = s->func(ctx, this_val, argc, arg_buf, s->magic, s->opaque); + rt->current_stack_frame = sf->prev_frame; + + return ret; +} + +JSValue JS_NewCClosure(JSContext *ctx, JSCClosure *func, const char *name, + JSCClosureFinalizerFunc *opaque_finalize, + int length, int magic, void *opaque) +{ + JSCClosureRecord *s; + JSAtom name_atom; + JSValue func_obj; + + func_obj = JS_NewObjectProtoClass(ctx, ctx->function_proto, + JS_CLASS_C_CLOSURE); + if (JS_IsException(func_obj)) + return func_obj; + s = js_malloc(ctx, sizeof(*s)); + if (!s) { + JS_FreeValue(ctx, func_obj); + return JS_EXCEPTION; + } + s->func = func; + s->length = length; + s->magic = magic; + s->opaque = opaque; + s->opaque_finalize = opaque_finalize; + JS_SetOpaqueInternal(func_obj, s); + name_atom = JS_ATOM_empty_string; + if (name && *name) { + name_atom = JS_NewAtom(ctx, name); + if (name_atom == JS_ATOM_NULL) { + JS_FreeValue(ctx, func_obj); + return JS_EXCEPTION; + } + } + js_function_set_properties(ctx, func_obj, name_atom, length); + JS_FreeAtom(ctx, name_atom); + return func_obj; +} + +static void free_property(JSRuntime *rt, JSProperty *pr, int prop_flags) +{ + if (unlikely(prop_flags & JS_PROP_TMASK)) { + if ((prop_flags & JS_PROP_TMASK) == JS_PROP_GETSET) { + if (pr->u.getset.getter) + JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.getter)); + if (pr->u.getset.setter) + JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.setter)); + } else if ((prop_flags & JS_PROP_TMASK) == JS_PROP_VARREF) { + free_var_ref(rt, pr->u.var_ref); + } else if ((prop_flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { + js_autoinit_free(rt, pr); + } + } else { + JS_FreeValueRT(rt, pr->u.value); + } +} + +static inline JSShapeProperty *find_own_property1(JSObject *p, JSAtom atom) +{ + JSShape *sh; + JSShapeProperty *pr, *prop; + intptr_t h; + sh = p->shape; + h = hash_atom(atom, sh->prop_hash_mask); + h = prop_hash_end(sh)[-h - 1]; + prop = get_shape_prop(sh); + while (h) { + pr = &prop[h - 1]; + if (likely(pr->atom == atom)) { + return pr; + } + h = pr->hash_next; + } + return NULL; +} + +static inline JSShapeProperty *find_own_property(JSProperty **ppr, + JSObject *p, + JSAtom atom) +{ + JSShape *sh; + JSShapeProperty *pr, *prop; + intptr_t h; + sh = p->shape; + h = hash_atom(atom, sh->prop_hash_mask); + h = prop_hash_end(sh)[-h - 1]; + prop = get_shape_prop(sh); + while (h) { + pr = &prop[h - 1]; + if (likely(pr->atom == atom)) { + *ppr = &p->prop[h - 1]; + /* the compiler should be able to assume that pr != NULL here */ + return pr; + } + h = pr->hash_next; + } + *ppr = NULL; + return NULL; +} + +/* Release a counted reference an open var_ref held on the coroutine + (async function / generator / async generator) owning the frame it + points into. */ +static void js_release_coro(JSRuntime *rt, JSGCObjectHeader *coro) +{ + switch (JS_GC_TYPE(coro)) { + case JS_GC_OBJ_TYPE_ASYNC_FUNCTION: + js_async_function_free(rt, (JSAsyncFunctionData *)coro); + break; + case JS_GC_OBJ_TYPE_JS_OBJECT: /* generator / async generator */ + JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_OBJECT, (JSObject *)coro)); + break; + default: + abort(); + } +} + +static void free_var_ref(JSRuntime *rt, JSVarRef *var_ref) +{ + if (var_ref) { + assert(JS_REF_COUNT(var_ref) > 0); + if (--JS_REF_COUNT(var_ref) == 0) { + if (var_ref->is_detached) { + JS_FreeValueRT(rt, var_ref->value); + remove_gc_object(&var_ref->header); + } else { + JSStackFrame *sf = var_ref->stack_frame; + assert(sf->var_refs[var_ref->var_ref_idx] == var_ref); + sf->var_refs[var_ref->var_ref_idx] = NULL; + /* an open coroutine var_ref is itself a GC object and holds + a counted ref to its coroutine (sf->cur_gc_obj) */ + if (var_ref->is_coro) { + js_release_coro(rt, sf->cur_gc_obj); + remove_gc_object(&var_ref->header); + } + } + js_free_rt(rt, var_ref); + } + } +} + +static void js_array_finalizer(JSRuntime *rt, JSValueConst val) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + uint32_t i; + + for(i = 0; i < p->u.array.count; i++) { + JS_FreeValueRT(rt, p->u.array.u.values[i]); + } + js_free_rt(rt, p->u.array.u.values); +} + +static void js_array_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + uint32_t i; + + for(i = 0; i < p->u.array.count; i++) { + JS_MarkValue(rt, p->u.array.u.values[i], mark_func); + } +} + +static void js_object_data_finalizer(JSRuntime *rt, JSValueConst val) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JS_FreeValueRT(rt, p->u.object_data); + p->u.object_data = JS_UNDEFINED; +} + +static void js_object_data_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JS_MarkValue(rt, p->u.object_data, mark_func); +} + +static void js_c_function_finalizer(JSRuntime *rt, JSValueConst val) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + + if (p->u.cfunc.realm) + JS_FreeContext(p->u.cfunc.realm); +} + +static void js_c_function_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + + if (p->u.cfunc.realm) + mark_func(rt, &p->u.cfunc.realm->header); +} + +static void js_bytecode_function_finalizer(JSRuntime *rt, JSValueConst val) +{ + JSObject *p1, *p = JS_VALUE_GET_OBJ(val); + JSFunctionBytecode *b; + JSVarRef **var_refs; + int i; + + p1 = p->u.func.home_object; + if (p1) { + JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_OBJECT, p1)); + } + b = p->u.func.function_bytecode; + if (b) { + var_refs = p->u.func.var_refs; + if (var_refs) { + for(i = 0; i < b->closure_var_count; i++) + free_var_ref(rt, var_refs[i]); + js_free_rt(rt, var_refs); + } + JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_FUNCTION_BYTECODE, b)); + } +} + +static void js_bytecode_function_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSVarRef **var_refs = p->u.func.var_refs; + JSFunctionBytecode *b = p->u.func.function_bytecode; + int i; + + if (p->u.func.home_object) { + JS_MarkValue(rt, JS_MKPTR(JS_TAG_OBJECT, p->u.func.home_object), + mark_func); + } + if (b) { + if (var_refs) { + for(i = 0; i < b->closure_var_count; i++) { + JSVarRef *var_ref = var_refs[i]; + /* Detached var_refs are GC objects; open var_refs are GC + objects only when they capture a coroutine local (see + get_var_ref). Only those may be marked. */ + if (var_ref && (var_ref->is_detached || var_ref->is_coro)) { + mark_func(rt, &var_ref->header); + } + } + } + /* must mark the function bytecode because template objects may be + part of a cycle */ + JS_MarkValue(rt, JS_MKPTR(JS_TAG_FUNCTION_BYTECODE, b), mark_func); + } +} + +static void js_bound_function_finalizer(JSRuntime *rt, JSValueConst val) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSBoundFunction *bf = p->u.bound_function; + int i; + + JS_FreeValueRT(rt, bf->func_obj); + JS_FreeValueRT(rt, bf->this_val); + for(i = 0; i < bf->argc; i++) { + JS_FreeValueRT(rt, bf->argv[i]); + } + js_free_rt(rt, bf); +} + +static void js_bound_function_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSBoundFunction *bf = p->u.bound_function; + int i; + + JS_MarkValue(rt, bf->func_obj, mark_func); + JS_MarkValue(rt, bf->this_val, mark_func); + for(i = 0; i < bf->argc; i++) + JS_MarkValue(rt, bf->argv[i], mark_func); +} + +static void js_for_in_iterator_finalizer(JSRuntime *rt, JSValueConst val) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSForInIterator *it = p->u.for_in_iterator; + JS_FreeValueRT(rt, it->obj); + js_free_rt(rt, it); +} + +static void js_for_in_iterator_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSForInIterator *it = p->u.for_in_iterator; + JS_MarkValue(rt, it->obj, mark_func); +} + +static void free_object(JSRuntime *rt, JSObject *p) +{ + int i; + JSClassFinalizer *finalizer; + JSShape *sh; + JSShapeProperty *pr; + + p->free_mark = 1; /* used to tell the object is invalid when + freeing cycles */ + /* free all the fields */ + sh = p->shape; + pr = get_shape_prop(sh); + for(i = 0; i < sh->prop_count; i++) { + free_property(rt, &p->prop[i], pr->flags); + pr++; + } + js_free_rt(rt, p->prop); + /* as an optimization we destroy the shape immediately without + putting it in gc_zero_ref_count_list */ + js_free_shape(rt, sh); + + /* fail safe */ + p->shape = NULL; + p->prop = NULL; + + if (unlikely(p->first_weak_ref)) { + reset_weak_ref(rt, &p->first_weak_ref); + } + + finalizer = rt->class_array[p->class_id].finalizer; + if (finalizer) + (*finalizer)(rt, JS_MKPTR(JS_TAG_OBJECT, p)); + + /* fail safe */ + p->class_id = 0; + p->u.opaque = NULL; + p->u.func.var_refs = NULL; + p->u.func.home_object = NULL; + + remove_gc_object(&p->header); + if (rt->gc_phase == JS_GC_PHASE_REMOVE_CYCLES && JS_REF_COUNT(p) != 0) { + list_add_tail(&p->header.link, &rt->gc_zero_ref_count_list); + } else { + js_free_rt(rt, p); + } +} + +static void free_gc_object(JSRuntime *rt, JSGCObjectHeader *gp) +{ + switch(JS_GC_TYPE(gp)) { + case JS_GC_OBJ_TYPE_JS_OBJECT: + free_object(rt, (JSObject *)gp); + break; + case JS_GC_OBJ_TYPE_FUNCTION_BYTECODE: + free_function_bytecode(rt, (JSFunctionBytecode *)gp); + break; + default: + abort(); + } +} + +static void free_zero_refcount(JSRuntime *rt) +{ + struct list_head *el; + JSGCObjectHeader *p; + + rt->gc_phase = JS_GC_PHASE_DECREF; + for(;;) { + el = rt->gc_zero_ref_count_list.next; + if (el == &rt->gc_zero_ref_count_list) + break; + p = list_entry(el, JSGCObjectHeader, link); + assert(JS_REF_COUNT(p) == 0); + free_gc_object(rt, p); + } + rt->gc_phase = JS_GC_PHASE_NONE; +} + +/* called with the ref_count of 'v' reaches zero. */ +static void js_free_value_rt(JSRuntime *rt, JSValue v) +{ + uint32_t tag = JS_VALUE_GET_TAG(v); + +#ifdef ENABLE_DUMPS // JS_DUMP_FREE + if (check_dump_flag(rt, JS_DUMP_FREE)) { + /* Prevent invalid object access during GC */ + if ((rt->gc_phase != JS_GC_PHASE_REMOVE_CYCLES) + || (tag != JS_TAG_OBJECT && tag != JS_TAG_FUNCTION_BYTECODE)) { + printf("Freeing "); + if (tag == JS_TAG_OBJECT) { + JS_DumpObject(rt, JS_VALUE_GET_OBJ(v)); + } else { + JS_DumpValue(rt, v); + printf("\n"); + } + } + } +#endif + + switch(tag) { + case JS_TAG_STRING: + js_free_string0(rt, JS_VALUE_GET_STRING(v)); + break; + case JS_TAG_STRING_ROPE: + { + JSStringRope *p = JS_VALUE_GET_STRING_ROPE(v); + JS_FreeValueRT(rt, p->left); + JS_FreeValueRT(rt, p->right); + js_free_rt(rt, p); + } + break; + case JS_TAG_OBJECT: + case JS_TAG_FUNCTION_BYTECODE: + { + JSGCObjectHeader *p = JS_VALUE_GET_PTR(v); + if (rt->gc_phase != JS_GC_PHASE_REMOVE_CYCLES) { + list_del(&p->link); + list_add(&p->link, &rt->gc_zero_ref_count_list); + if (rt->gc_phase == JS_GC_PHASE_NONE) { + free_zero_refcount(rt); + } + } + } + break; + case JS_TAG_MODULE: + abort(); /* never freed here */ + break; + case JS_TAG_BIG_INT: + { + JSBigInt *p = JS_VALUE_GET_PTR(v); + js_free_rt(rt, p); + } + break; + case JS_TAG_SYMBOL: + { + JSAtomStruct *p = JS_VALUE_GET_PTR(v); + JS_FreeAtomStruct(rt, p); + } + break; + default: + printf("js_free_value_rt: unknown tag=%d\n", tag); + abort(); + } +} + +void JS_FreeValueRT(JSRuntime *rt, JSValue v) +{ + if (JS_VALUE_HAS_REF_COUNT(v)) { + void *p = JS_VALUE_GET_PTR(v); + if (--JS_REF_COUNT(p) <= 0) { + js_free_value_rt(rt, v); + } + } +} + +void JS_FreeValue(JSContext *ctx, JSValue v) +{ + JS_FreeValueRT(ctx->rt, v); +} + +/* garbage collection */ + +static void add_gc_object(JSRuntime *rt, JSGCObjectHeader *h, + JSGCObjectTypeEnum type) +{ + JS_GC_MARK(h) = 0; + JS_GC_TYPE(h) = type; + list_add_tail(&h->link, &rt->gc_obj_list); +} + +static void remove_gc_object(JSGCObjectHeader *h) +{ + list_del(&h->link); +} + +void JS_MarkValue(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func) +{ + if (JS_VALUE_HAS_REF_COUNT(val)) { + switch(JS_VALUE_GET_TAG(val)) { + case JS_TAG_OBJECT: + case JS_TAG_FUNCTION_BYTECODE: + mark_func(rt, JS_VALUE_GET_PTR(val)); + break; + default: + break; + } + } +} + +static void mark_weak_map_value(JSRuntime *rt, JSWeakRefRecord *first_weak_ref, JS_MarkFunc *mark_func) { + JSWeakRefRecord *wr; + JSMapRecord *mr; + JSMapState *s; + + for (wr = first_weak_ref; wr != NULL; wr = wr->next_weak_ref) { + if (wr->kind == JS_WEAK_REF_KIND_MAP) { + mr = wr->u.map_record; + s = mr->map; + assert(s->is_weak); + assert(!mr->empty); /* no iterator on WeakMap/WeakSet */ + JS_MarkValue(rt, mr->value, mark_func); + } + } +} + +static void mark_children(JSRuntime *rt, JSGCObjectHeader *gp, + JS_MarkFunc *mark_func) +{ + switch(JS_GC_TYPE(gp)) { + case JS_GC_OBJ_TYPE_JS_OBJECT: + { + JSObject *p = (JSObject *)gp; + JSShapeProperty *prs; + JSShape *sh; + int i; + sh = p->shape; + mark_func(rt, &sh->header); + /* mark all the fields */ + prs = get_shape_prop(sh); + for(i = 0; i < sh->prop_count; i++) { + JSProperty *pr = &p->prop[i]; + if (prs->atom != JS_ATOM_NULL) { + if (prs->flags & JS_PROP_TMASK) { + if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { + if (pr->u.getset.getter) + mark_func(rt, &pr->u.getset.getter->header); + if (pr->u.getset.setter) + mark_func(rt, &pr->u.getset.setter->header); + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { + if (pr->u.var_ref->is_detached || + pr->u.var_ref->is_coro) { + /* Note: the tag does not matter + provided it is a GC object */ + mark_func(rt, &pr->u.var_ref->header); + } + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { + js_autoinit_mark(rt, pr, mark_func); + } + } else { + JS_MarkValue(rt, pr->u.value, mark_func); + } + } + prs++; + } + + if (unlikely(p->first_weak_ref)) { + mark_weak_map_value(rt, p->first_weak_ref, mark_func); + } + + if (p->class_id != JS_CLASS_OBJECT) { + JSClassGCMark *gc_mark; + gc_mark = rt->class_array[p->class_id].gc_mark; + if (gc_mark) + gc_mark(rt, JS_MKPTR(JS_TAG_OBJECT, p), mark_func); + } + } + break; + case JS_GC_OBJ_TYPE_FUNCTION_BYTECODE: + /* the template objects can be part of a cycle */ + { + JSFunctionBytecode *b = (JSFunctionBytecode *)gp; + int i; + for(i = 0; i < b->cpool_count; i++) { + JS_MarkValue(rt, b->cpool[i], mark_func); + } + if (b->realm) + mark_func(rt, &b->realm->header); + } + break; + case JS_GC_OBJ_TYPE_VAR_REF: + { + JSVarRef *var_ref = (JSVarRef *)gp; + if (var_ref->is_detached) { + /* the var_ref owns its value */ + JS_MarkValue(rt, *var_ref->pvalue, mark_func); + } else { + /* open var_ref: the value lives in the coroutine's frame + and is marked by the coroutine itself; only keep that + coroutine reachable. (Open var_refs are GC objects only + when they capture a coroutine local.) */ + assert(var_ref->is_coro); + mark_func(rt, var_ref->stack_frame->cur_gc_obj); + } + } + break; + case JS_GC_OBJ_TYPE_ASYNC_FUNCTION: + { + JSAsyncFunctionData *s = (JSAsyncFunctionData *)gp; + if (s->is_active) + async_func_mark(rt, &s->func_state, mark_func); + JS_MarkValue(rt, s->resolving_funcs[0], mark_func); + JS_MarkValue(rt, s->resolving_funcs[1], mark_func); + } + break; + case JS_GC_OBJ_TYPE_SHAPE: + { + JSShape *sh = (JSShape *)gp; + if (sh->proto != NULL) { + mark_func(rt, &sh->proto->header); + } + } + break; + case JS_GC_OBJ_TYPE_JS_CONTEXT: + { + JSContext *ctx = (JSContext *)gp; + JS_MarkContext(rt, ctx, mark_func); + } + break; + default: + abort(); + } +} + +static void gc_decref_child(JSRuntime *rt, JSGCObjectHeader *p) +{ + assert(JS_REF_COUNT(p) > 0); + JS_REF_COUNT(p)--; + if (JS_REF_COUNT(p) == 0 && JS_GC_MARK(p) == 1) { + list_del(&p->link); + list_add_tail(&p->link, &rt->tmp_obj_list); + } +} + +static void gc_decref(JSRuntime *rt) +{ + struct list_head *el, *el1; + JSGCObjectHeader *p; + + init_list_head(&rt->tmp_obj_list); + + /* decrement the refcount of all the children of all the GC + objects and move the GC objects with zero refcount to + tmp_obj_list */ + list_for_each_safe(el, el1, &rt->gc_obj_list) { + p = list_entry(el, JSGCObjectHeader, link); + assert(JS_GC_MARK(p) == 0); + mark_children(rt, p, gc_decref_child); + JS_GC_MARK(p) = 1; + if (JS_REF_COUNT(p) == 0) { + list_del(&p->link); + list_add_tail(&p->link, &rt->tmp_obj_list); + } + } +} + +static void gc_scan_incref_child(JSRuntime *rt, JSGCObjectHeader *p) +{ + JS_REF_COUNT(p)++; + if (JS_REF_COUNT(p) == 1) { + /* ref_count was 0: remove from tmp_obj_list and add at the + end of gc_obj_list */ + list_del(&p->link); + list_add_tail(&p->link, &rt->gc_obj_list); + JS_GC_MARK(p) = 0; /* reset the mark for the next GC call */ + } +} + +static void gc_scan_incref_child2(JSRuntime *rt, JSGCObjectHeader *p) +{ + JS_REF_COUNT(p)++; +} + +static void gc_scan(JSRuntime *rt) +{ + struct list_head *el; + JSGCObjectHeader *p; + + /* keep the objects with a refcount > 0 and their children. */ + list_for_each(el, &rt->gc_obj_list) { + p = list_entry(el, JSGCObjectHeader, link); + assert(JS_REF_COUNT(p) > 0); + JS_GC_MARK(p) = 0; /* reset the mark for the next GC call */ + mark_children(rt, p, gc_scan_incref_child); + } + + /* restore the refcount of the objects to be deleted. */ + list_for_each(el, &rt->tmp_obj_list) { + p = list_entry(el, JSGCObjectHeader, link); + mark_children(rt, p, gc_scan_incref_child2); + } +} + +static void gc_free_cycles(JSRuntime *rt) +{ + struct list_head *el, *el1; + JSGCObjectHeader *p; +#ifdef ENABLE_DUMPS // JS_DUMP_GC_FREE + bool header_done = false; +#endif + + rt->gc_phase = JS_GC_PHASE_REMOVE_CYCLES; + + for(;;) { + el = rt->tmp_obj_list.next; + if (el == &rt->tmp_obj_list) + break; + p = list_entry(el, JSGCObjectHeader, link); + /* Only need to free the GC object associated with JS + values. The rest will be automatically removed because they + must be referenced by them. */ + switch(JS_GC_TYPE(p)) { + case JS_GC_OBJ_TYPE_JS_OBJECT: + case JS_GC_OBJ_TYPE_FUNCTION_BYTECODE: +#ifdef ENABLE_DUMPS // JS_DUMP_GC_FREE + if (check_dump_flag(rt, JS_DUMP_GC_FREE)) { + if (!header_done) { + printf("Freeing cycles:\n"); + JS_DumpObjectHeader(rt); + header_done = true; + } + JS_DumpGCObject(rt, p); + } +#endif + free_gc_object(rt, p); + break; + default: + list_del(&p->link); + list_add_tail(&p->link, &rt->gc_zero_ref_count_list); + break; + } + } + rt->gc_phase = JS_GC_PHASE_NONE; + + list_for_each_safe(el, el1, &rt->gc_zero_ref_count_list) { + p = list_entry(el, JSGCObjectHeader, link); + assert(JS_GC_TYPE(p) == JS_GC_OBJ_TYPE_JS_OBJECT || + JS_GC_TYPE(p) == JS_GC_OBJ_TYPE_FUNCTION_BYTECODE); + js_free_rt(rt, p); + } + + init_list_head(&rt->gc_zero_ref_count_list); +} + +void JS_RunGC(JSRuntime *rt) +{ + /* decrement the reference of the children of each object. mark = + 1 after this pass. */ + gc_decref(rt); + + /* keep the GC objects with a non zero refcount and their childs */ + gc_scan(rt); + + /* free the GC objects in a cycle */ + gc_free_cycles(rt); +} + +/* Return false if not an object or if the object has already been + freed (zombie objects are visible in finalizers when freeing + cycles). */ +bool JS_IsLiveObject(JSRuntime *rt, JSValueConst obj) +{ + JSObject *p; + if (!JS_IsObject(obj)) + return false; + p = JS_VALUE_GET_OBJ(obj); + return !p->free_mark; +} + +/* Compute memory used by various object types */ +/* XXX: poor man's approach to handling multiply referenced objects */ +typedef struct JSMemoryUsage_helper { + double memory_used_count; + double str_count; + double str_size; + int64_t js_func_count; + double js_func_size; + int64_t js_func_code_size; + int64_t js_func_pc2line_count; + int64_t js_func_pc2line_size; +} JSMemoryUsage_helper; + +static void compute_value_size(JSValue val, JSMemoryUsage_helper *hp); + +static void compute_jsstring_size(JSString *str, JSMemoryUsage_helper *hp) +{ + if (!str->atom_type) { /* atoms are handled separately */ + double s_ref_count = JS_REF_COUNT(str); + hp->str_count += 1 / s_ref_count; + hp->str_size += ((sizeof(*str) + (str->len << str->is_wide_char) + + 1 - str->is_wide_char) / s_ref_count); + } +} + +static void compute_bytecode_size(JSFunctionBytecode *b, JSMemoryUsage_helper *hp) +{ + int memory_used_count, js_func_size, i; + + memory_used_count = 0; + js_func_size = sizeof(*b); + if (b->vardefs) { + js_func_size += (b->arg_count + b->var_count) * sizeof(*b->vardefs); + } + if (b->cpool) { + js_func_size += b->cpool_count * sizeof(*b->cpool); + for (i = 0; i < b->cpool_count; i++) { + JSValue val = b->cpool[i]; + compute_value_size(val, hp); + } + } + if (b->closure_var) { + js_func_size += b->closure_var_count * sizeof(*b->closure_var); + } + if (b->byte_code_buf) { + hp->js_func_code_size += b->byte_code_len; + } + memory_used_count++; + js_func_size += b->source_len + 1; + if (b->pc2line_len) { + memory_used_count++; + hp->js_func_pc2line_count += 1; + hp->js_func_pc2line_size += b->pc2line_len; + } + hp->js_func_size += js_func_size; + hp->js_func_count += 1; + hp->memory_used_count += memory_used_count; +} + +static void compute_value_size(JSValue val, JSMemoryUsage_helper *hp) +{ + switch(JS_VALUE_GET_TAG(val)) { + case JS_TAG_STRING: + compute_jsstring_size(JS_VALUE_GET_STRING(val), hp); + break; + case JS_TAG_BIG_INT: + /* should track JSBigInt usage */ + break; + } +} + +void JS_ComputeMemoryUsage(JSRuntime *rt, JSMemoryUsage *s) +{ + struct list_head *el, *el1; + int i; + JSMemoryUsage_helper mem = { 0 }, *hp = &mem; + + memset(s, 0, sizeof(*s)); + s->malloc_count = rt->malloc_state.malloc_count; + s->malloc_size = rt->malloc_state.malloc_size; + s->malloc_limit = rt->malloc_state.malloc_limit; + + s->memory_used_count = 2; /* rt + rt->class_array */ + s->memory_used_size = sizeof(JSRuntime) + sizeof(JSClass) * rt->class_count; + + list_for_each(el, &rt->context_list) { + JSContext *ctx = list_entry(el, JSContext, link); + JSShape *sh = ctx->array_shape; + s->memory_used_count += 2; /* ctx + ctx->class_proto */ + s->memory_used_size += sizeof(JSContext) + + sizeof(JSValue) * rt->class_count; + s->binary_object_count += ctx->binary_object_count; + s->binary_object_size += ctx->binary_object_size; + + /* the hashed shapes are counted separately */ + if (sh && !sh->is_hashed) { + int hash_size = sh->prop_hash_mask + 1; + s->shape_count++; + s->shape_size += get_shape_size(hash_size, sh->prop_size); + } + list_for_each(el1, &ctx->loaded_modules) { + JSModuleDef *m = list_entry(el1, JSModuleDef, link); + s->memory_used_count += 1; + s->memory_used_size += sizeof(*m); + if (m->req_module_entries) { + s->memory_used_count += 1; + s->memory_used_size += m->req_module_entries_count * sizeof(*m->req_module_entries); + } + if (m->export_entries) { + s->memory_used_count += 1; + s->memory_used_size += m->export_entries_count * sizeof(*m->export_entries); + for (i = 0; i < m->export_entries_count; i++) { + JSExportEntry *me = &m->export_entries[i]; + if (me->export_type == JS_EXPORT_TYPE_LOCAL && me->u.local.var_ref) { + /* potential multiple count */ + s->memory_used_count += 1; + compute_value_size(me->u.local.var_ref->value, hp); + } + } + } + if (m->star_export_entries) { + s->memory_used_count += 1; + s->memory_used_size += m->star_export_entries_count * sizeof(*m->star_export_entries); + } + if (m->import_entries) { + s->memory_used_count += 1; + s->memory_used_size += m->import_entries_count * sizeof(*m->import_entries); + } + compute_value_size(m->module_ns, hp); + compute_value_size(m->func_obj, hp); + } + } + + list_for_each(el, &rt->gc_obj_list) { + JSGCObjectHeader *gp = list_entry(el, JSGCObjectHeader, link); + JSObject *p; + JSShape *sh; + JSShapeProperty *prs; + + /* XXX: could count the other GC object types too */ + if (JS_GC_TYPE(gp) == JS_GC_OBJ_TYPE_FUNCTION_BYTECODE) { + compute_bytecode_size((JSFunctionBytecode *)gp, hp); + continue; + } else if (JS_GC_TYPE(gp) != JS_GC_OBJ_TYPE_JS_OBJECT) { + continue; + } + p = (JSObject *)gp; + sh = p->shape; + s->obj_count++; + if (p->prop) { + s->memory_used_count++; + s->prop_size += sh->prop_size * sizeof(*p->prop); + s->prop_count += sh->prop_count; + prs = get_shape_prop(sh); + for(i = 0; i < sh->prop_count; i++) { + JSProperty *pr = &p->prop[i]; + if (prs->atom != JS_ATOM_NULL && !(prs->flags & JS_PROP_TMASK)) { + compute_value_size(pr->u.value, hp); + } + prs++; + } + } + /* the hashed shapes are counted separately */ + if (!sh->is_hashed) { + int hash_size = sh->prop_hash_mask + 1; + s->shape_count++; + s->shape_size += get_shape_size(hash_size, sh->prop_size); + } + + switch(p->class_id) { + case JS_CLASS_ARRAY: /* u.array | length */ + case JS_CLASS_ARGUMENTS: /* u.array | length */ + s->array_count++; + if (p->fast_array) { + s->fast_array_count++; + if (p->u.array.u.values) { + s->memory_used_count++; + s->memory_used_size += p->u.array.count * + sizeof(*p->u.array.u.values); + s->fast_array_elements += p->u.array.count; + for (i = 0; i < p->u.array.count; i++) { + compute_value_size(p->u.array.u.values[i], hp); + } + } + } + break; + case JS_CLASS_ERROR: /* u.object_data */ + case JS_CLASS_NUMBER: /* u.object_data */ + case JS_CLASS_STRING: /* u.object_data */ + case JS_CLASS_BOOLEAN: /* u.object_data */ + case JS_CLASS_SYMBOL: /* u.object_data */ + case JS_CLASS_DATE: /* u.object_data */ + case JS_CLASS_BIG_INT: /* u.object_data */ + compute_value_size(p->u.object_data, hp); + break; + case JS_CLASS_C_FUNCTION: /* u.cfunc */ + s->c_func_count++; + break; + case JS_CLASS_BYTECODE_FUNCTION: /* u.func */ + { + JSFunctionBytecode *b = p->u.func.function_bytecode; + JSVarRef **var_refs = p->u.func.var_refs; + /* home_object: object will be accounted for in list scan */ + if (var_refs) { + s->memory_used_count++; + s->js_func_size += b->closure_var_count * sizeof(*var_refs); + for (i = 0; i < b->closure_var_count; i++) { + if (var_refs[i]) { + double ref_count = JS_REF_COUNT(var_refs[i]); + s->memory_used_count += 1 / ref_count; + s->js_func_size += sizeof(*var_refs[i]) / ref_count; + /* handle non object closed values */ + if (var_refs[i]->pvalue == &var_refs[i]->value) { + /* potential multiple count */ + compute_value_size(var_refs[i]->value, hp); + } + } + } + } + } + break; + case JS_CLASS_BOUND_FUNCTION: /* u.bound_function */ + { + JSBoundFunction *bf = p->u.bound_function; + /* func_obj and this_val are objects */ + for (i = 0; i < bf->argc; i++) { + compute_value_size(bf->argv[i], hp); + } + s->memory_used_count += 1; + s->memory_used_size += sizeof(*bf) + bf->argc * sizeof(*bf->argv); + } + break; + case JS_CLASS_C_FUNCTION_DATA: /* u.c_function_data_record */ + { + JSCFunctionDataRecord *fd = p->u.c_function_data_record; + if (fd) { + for (i = 0; i < fd->data_len; i++) { + compute_value_size(fd->data[i], hp); + } + s->memory_used_count += 1; + s->memory_used_size += sizeof(*fd) + fd->data_len * sizeof(*fd->data); + } + } + break; + case JS_CLASS_C_CLOSURE: /* u.c_closure_record */ + { + JSCClosureRecord *c = p->u.c_closure_record; + if (c) { + s->memory_used_count += 1; + s->memory_used_size += sizeof(*c); + } + } + break; + case JS_CLASS_REGEXP: /* u.regexp */ + compute_jsstring_size(p->u.regexp.pattern, hp); + compute_jsstring_size(p->u.regexp.bytecode, hp); + break; + + case JS_CLASS_FOR_IN_ITERATOR: /* u.for_in_iterator */ + { + JSForInIterator *it = p->u.for_in_iterator; + if (it) { + compute_value_size(it->obj, hp); + s->memory_used_count += 1; + s->memory_used_size += sizeof(*it); + } + } + break; + case JS_CLASS_ARRAY_BUFFER: /* u.array_buffer */ + case JS_CLASS_SHARED_ARRAY_BUFFER: /* u.array_buffer */ + { + JSArrayBuffer *abuf = p->u.array_buffer; + if (abuf) { + s->memory_used_count += 1; + s->memory_used_size += sizeof(*abuf); + if (abuf->data) { + s->memory_used_count += 1; + s->memory_used_size += abuf->byte_length; + } + } + } + break; + case JS_CLASS_GENERATOR: /* u.generator_data */ + case JS_CLASS_UINT8C_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_INT8_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_UINT8_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_INT16_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_UINT16_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_INT32_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_UINT32_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_BIG_INT64_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_BIG_UINT64_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_FLOAT16_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_FLOAT32_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_FLOAT64_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_DATAVIEW: /* u.typed_array */ + case JS_CLASS_MAP: /* u.map_state */ + case JS_CLASS_SET: /* u.map_state */ + case JS_CLASS_WEAKMAP: /* u.map_state */ + case JS_CLASS_WEAKSET: /* u.map_state */ + case JS_CLASS_MAP_ITERATOR: /* u.map_iterator_data */ + case JS_CLASS_SET_ITERATOR: /* u.map_iterator_data */ + case JS_CLASS_ARRAY_ITERATOR: /* u.array_iterator_data */ + case JS_CLASS_STRING_ITERATOR: /* u.array_iterator_data */ + case JS_CLASS_PROXY: /* u.proxy_data */ + case JS_CLASS_PROMISE: /* u.promise_data */ + case JS_CLASS_PROMISE_RESOLVE_FUNCTION: /* u.promise_function_data */ + case JS_CLASS_PROMISE_REJECT_FUNCTION: /* u.promise_function_data */ + case JS_CLASS_ASYNC_FUNCTION_RESOLVE: /* u.async_function_data */ + case JS_CLASS_ASYNC_FUNCTION_REJECT: /* u.async_function_data */ + case JS_CLASS_ASYNC_FROM_SYNC_ITERATOR: /* u.async_from_sync_iterator_data */ + case JS_CLASS_ASYNC_GENERATOR: /* u.async_generator_data */ + /* TODO */ + default: + /* XXX: class definition should have an opaque block size */ + if (p->u.opaque) { + s->memory_used_count += 1; + } + break; + } + } + s->obj_size += s->obj_count * sizeof(JSObject); + + /* hashed shapes */ + s->memory_used_count++; /* rt->shape_hash */ + s->memory_used_size += sizeof(rt->shape_hash[0]) * rt->shape_hash_size; + for(i = 0; i < rt->shape_hash_size; i++) { + JSShape *sh; + for(sh = rt->shape_hash[i]; sh != NULL; sh = sh->shape_hash_next) { + int hash_size = sh->prop_hash_mask + 1; + s->shape_count++; + s->shape_size += get_shape_size(hash_size, sh->prop_size); + } + } + + /* atoms */ + s->memory_used_count += 2; /* rt->atom_array, rt->atom_hash */ + s->atom_count = rt->atom_count; + s->atom_size = sizeof(rt->atom_array[0]) * rt->atom_size + + sizeof(rt->atom_hash[0]) * rt->atom_hash_size; + for(i = 0; i < rt->atom_size; i++) { + JSAtomStruct *p = rt->atom_array[i]; + if (!atom_is_free(p)) { + s->atom_size += (sizeof(*p) + (p->len << p->is_wide_char) + + 1 - p->is_wide_char); + } + } + s->str_count = round(mem.str_count); + s->str_size = round(mem.str_size); + s->js_func_count = mem.js_func_count; + s->js_func_size = round(mem.js_func_size); + s->js_func_code_size = mem.js_func_code_size; + s->js_func_pc2line_count = mem.js_func_pc2line_count; + s->js_func_pc2line_size = mem.js_func_pc2line_size; + s->memory_used_count += round(mem.memory_used_count) + + s->atom_count + s->str_count + + s->obj_count + s->shape_count + + s->js_func_count + s->js_func_pc2line_count; + s->memory_used_size += s->atom_size + s->str_size + + s->obj_size + s->prop_size + s->shape_size + + s->js_func_size + s->js_func_code_size + s->js_func_pc2line_size; +} + +void JS_DumpMemoryUsage(FILE *fp, const JSMemoryUsage *s, JSRuntime *rt) +{ + fprintf(fp, "QuickJS-ng memory usage -- %s version, %d-bit, malloc limit: %"PRId64"\n\n", + JS_GetVersion(), (int)sizeof(void *) * 8, s->malloc_limit); + if (rt) { + static const struct { + const char *name; + size_t size; + } object_types[] = { + { "JSRuntime", sizeof(JSRuntime) }, + { "JSContext", sizeof(JSContext) }, + { "JSObject", sizeof(JSObject) }, + { "JSString", sizeof(JSString) }, + { "JSFunctionBytecode", sizeof(JSFunctionBytecode) }, + }; + int i, usage_size_ok = 0; + for(i = 0; i < countof(object_types); i++) { + unsigned int size = object_types[i].size; + void *p = js_malloc_rt(rt, size); + if (p) { + unsigned int size1 = js_malloc_usable_size_rt(rt, p); + if (size1 >= size) { + usage_size_ok = 1; + fprintf(fp, " %3u + %-2u %s\n", + size, size1 - size, object_types[i].name); + } + js_free_rt(rt, p); + } + } + if (!usage_size_ok) { + fprintf(fp, " malloc_usable_size unavailable\n"); + } + { + int obj_classes[JS_CLASS_INIT_COUNT + 1] = { 0 }; + int class_id; + struct list_head *el; + list_for_each(el, &rt->gc_obj_list) { + JSGCObjectHeader *gp = list_entry(el, JSGCObjectHeader, link); + JSObject *p; + if (JS_GC_TYPE(gp) == JS_GC_OBJ_TYPE_JS_OBJECT) { + p = (JSObject *)gp; + obj_classes[min_uint32(p->class_id, JS_CLASS_INIT_COUNT)]++; + } + } + fprintf(fp, "\n" "JSObject classes\n"); + if (obj_classes[0]) + fprintf(fp, " %5d %2.0d %s\n", obj_classes[0], 0, "none"); + for (class_id = 1; class_id < JS_CLASS_INIT_COUNT; class_id++) { + if (obj_classes[class_id] && class_id < rt->class_count) { + char buf[ATOM_GET_STR_BUF_SIZE]; + fprintf(fp, " %5d %2.0d %s\n", obj_classes[class_id], class_id, + JS_AtomGetStrRT(rt, buf, sizeof(buf), rt->class_array[class_id].class_name)); + } + } + if (obj_classes[JS_CLASS_INIT_COUNT]) + fprintf(fp, " %5d %2.0d %s\n", obj_classes[JS_CLASS_INIT_COUNT], 0, "other"); + } + fprintf(fp, "\n"); + } + fprintf(fp, "%-20s %8s %8s\n", "NAME", "COUNT", "SIZE"); + + if (s->malloc_count) { + fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per block)\n", + "memory allocated", s->malloc_count, s->malloc_size, + (double)s->malloc_size / s->malloc_count); + fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%d overhead, %0.1f average slack)\n", + "memory used", s->memory_used_count, s->memory_used_size, + MALLOC_OVERHEAD, ((double)(s->malloc_size - s->memory_used_size) / + s->memory_used_count)); + } + if (s->atom_count) { + fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per atom)\n", + "atoms", s->atom_count, s->atom_size, + (double)s->atom_size / s->atom_count); + } + if (s->str_count) { + fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per string)\n", + "strings", s->str_count, s->str_size, + (double)s->str_size / s->str_count); + } + if (s->obj_count) { + fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per object)\n", + "objects", s->obj_count, s->obj_size, + (double)s->obj_size / s->obj_count); + fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per object)\n", + " properties", s->prop_count, s->prop_size, + (double)s->prop_count / s->obj_count); + fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per shape)\n", + " shapes", s->shape_count, s->shape_size, + (double)s->shape_size / s->shape_count); + } + if (s->js_func_count) { + fprintf(fp, "%-20s %8"PRId64" %8"PRId64"\n", + "bytecode functions", s->js_func_count, s->js_func_size); + fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per function)\n", + " bytecode", s->js_func_count, s->js_func_code_size, + (double)s->js_func_code_size / s->js_func_count); + if (s->js_func_pc2line_count) { + fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per function)\n", + " pc2line", s->js_func_pc2line_count, + s->js_func_pc2line_size, + (double)s->js_func_pc2line_size / s->js_func_pc2line_count); + } + } + if (s->c_func_count) { + fprintf(fp, "%-20s %8"PRId64"\n", "C functions", s->c_func_count); + } + if (s->array_count) { + fprintf(fp, "%-20s %8"PRId64"\n", "arrays", s->array_count); + if (s->fast_array_count) { + fprintf(fp, "%-20s %8"PRId64"\n", " fast arrays", s->fast_array_count); + fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per fast array)\n", + " elements", s->fast_array_elements, + s->fast_array_elements * (int)sizeof(JSValue), + (double)s->fast_array_elements / s->fast_array_count); + } + } + if (s->binary_object_count) { + fprintf(fp, "%-20s %8"PRId64" %8"PRId64"\n", + "binary objects", s->binary_object_count, s->binary_object_size); + } +} + +JSValue JS_GetGlobalObject(JSContext *ctx) +{ + return js_dup(ctx->global_obj); +} + +/* WARNING: obj is freed */ +JSValue JS_Throw(JSContext *ctx, JSValue obj) +{ + JSRuntime *rt = ctx->rt; + JS_FreeValue(ctx, rt->current_exception); + rt->current_exception = obj; + return JS_EXCEPTION; +} + +/* return the pending exception (cannot be called twice). */ +JSValue JS_GetException(JSContext *ctx) +{ + JSValue val; + JSRuntime *rt = ctx->rt; + val = rt->current_exception; + rt->current_exception = JS_UNINITIALIZED; + return val; +} + +bool JS_HasException(JSContext *ctx) +{ + return !JS_IsUninitialized(ctx->rt->current_exception); +} + +static void dbuf_put_leb128(DynBuf *s, uint32_t v) +{ + uint32_t a; + for(;;) { + a = v & 0x7f; + v >>= 7; + if (v != 0) { + dbuf_putc(s, a | 0x80); + } else { + dbuf_putc(s, a); + break; + } + } +} + +static void dbuf_put_sleb128(DynBuf *s, int32_t v1) +{ + uint32_t v = v1; + dbuf_put_leb128(s, (2 * v) ^ -(v >> 31)); +} + +static int get_leb128(uint32_t *pval, const uint8_t *buf, + const uint8_t *buf_end) +{ + const uint8_t *ptr = buf; + uint32_t v, a, i; + v = 0; + for(i = 0; i < 5; i++) { + if (unlikely(ptr >= buf_end)) + break; + a = *ptr++; + v |= (a & 0x7f) << (i * 7); + if (!(a & 0x80)) { + *pval = v; + return ptr - buf; + } + } + *pval = 0; + return -1; +} + +static int get_sleb128(int32_t *pval, const uint8_t *buf, + const uint8_t *buf_end) +{ + int ret; + uint32_t val; + ret = get_leb128(&val, buf, buf_end); + if (ret < 0) { + *pval = 0; + return -1; + } + *pval = (val >> 1) ^ -(val & 1); + return ret; +} + +static int find_line_num(JSContext *ctx, JSFunctionBytecode *b, + uint32_t pc_value, int *col) +{ + const uint8_t *p_end, *p; + int new_line_num, new_col_num, line_num, col_num, pc, ret; + int32_t v; + unsigned int op; + + *col = 1; + p = b->pc2line_buf; + if (!p) + goto fail; + p_end = p + b->pc2line_len; + pc = 0; + line_num = b->line_num; + col_num = b->col_num; + while (p < p_end) { + op = *p++; + if (op == 0) { + uint32_t val; + ret = get_leb128(&val, p, p_end); + if (ret < 0) + goto fail; + pc += val; + p += ret; + ret = get_sleb128(&v, p, p_end); + if (ret < 0) + goto fail; + p += ret; + new_line_num = line_num + v; + } else { + op -= PC2LINE_OP_FIRST; + pc += (op / PC2LINE_RANGE); + new_line_num = line_num + (op % PC2LINE_RANGE) + PC2LINE_BASE; + } + ret = get_sleb128(&v, p, p_end); + if (ret < 0) + goto fail; + p += ret; + new_col_num = col_num + v; + if (pc_value < pc) + break; + line_num = new_line_num; + col_num = new_col_num; + } + *col = col_num; + return line_num; +fail: + /* should never happen */ + return b->line_num; +} + +/* in order to avoid executing arbitrary code during the stack trace + generation, we only look at simple 'name' properties containing a + string. */ +static const char *get_func_name(JSContext *ctx, JSValueConst func) +{ + JSProperty *pr; + JSShapeProperty *prs; + JSValue val; + + if (JS_VALUE_GET_TAG(func) != JS_TAG_OBJECT) + return NULL; + prs = find_own_property(&pr, JS_VALUE_GET_OBJ(func), JS_ATOM_name); + if (!prs) + return NULL; + if ((prs->flags & JS_PROP_TMASK) != JS_PROP_NORMAL) + return NULL; + val = pr->u.value; + if (JS_VALUE_GET_TAG(val) != JS_TAG_STRING) + return NULL; + return JS_ToCString(ctx, val); +} + +/* Note: it is important that no exception is returned by this function */ +static bool can_add_backtrace(JSValueConst obj) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) + return false; + p = JS_VALUE_GET_OBJ(obj); + if (p->class_id != JS_CLASS_DOM_EXCEPTION) + return false; + if (find_own_property1(p, JS_ATOM_stack)) + return false; + return true; +} + +/* Note: it is important that no exception is returned by this function */ +static bool can_store_error_stack(JSValueConst obj) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) + return false; + p = JS_VALUE_GET_OBJ(obj); + if (p->class_id != JS_CLASS_ERROR) + return false; + if (!JS_IsUndefined(p->u.object_data)) + return false; + if (find_own_property1(p, JS_ATOM_stack)) + return false; + return true; +} + +#define JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL (1 << 0) +/* only taken into account if filename is provided */ +#define JS_BACKTRACE_FLAG_SINGLE_LEVEL (1 << 1) +#define JS_BACKTRACE_FLAG_FILTER_FUNC (1 << 2) + +/* if filename != NULL, an additional level is added with the filename + and line number information (used for parse error). */ +static void build_backtrace(JSContext *ctx, JSValueConst error_val, + JSValueConst filter_func, const char *filename, + int line_num, int col_num, int backtrace_flags) +{ + JSStackFrame *sf, *sf_start; + JSValue stack, prepare, saved_exception, error_obj; + DynBuf dbuf; + const char *func_name_str; + const char *str1; + JSObject *p; + JSFunctionBytecode *b; + bool backtrace_barrier, has_prepare, has_filter_func; + JSRuntime *rt; + JSCallSiteData csd[64]; + uint32_t i; + double d; + int stack_trace_limit; + + rt = ctx->rt; + if (rt->in_build_stack_trace) + return; + rt->in_build_stack_trace = true; + error_obj = js_dup(error_val); + + // Save exception because conversion to double may fail. + saved_exception = JS_GetException(ctx); + + // Extract stack trace limit. + // Ignore error since it sets d to NAN anyway. + // coverity[check_return] + JS_ToFloat64(ctx, &d, ctx->error_stack_trace_limit); + if (isnan(d) || d < 0.0) + stack_trace_limit = 0; + else if (d > INT32_MAX) + stack_trace_limit = INT32_MAX; + else + stack_trace_limit = fabs(d); + + // Restore current exception. + JS_Throw(ctx, saved_exception); + saved_exception = JS_UNINITIALIZED; + + stack_trace_limit = min_int(stack_trace_limit, countof(csd)); + stack_trace_limit = max_int(stack_trace_limit, 0); + has_prepare = false; + has_filter_func = backtrace_flags & JS_BACKTRACE_FLAG_FILTER_FUNC; + i = 0; + + if (!JS_IsNull(ctx->error_ctor)) { + prepare = js_dup(ctx->error_prepare_stack); + has_prepare = JS_IsFunction(ctx, prepare); + } + + if (has_prepare) { + saved_exception = JS_GetException(ctx); + if (stack_trace_limit == 0) + goto done; + if (filename) + js_new_callsite_data2(ctx, &csd[i++], filename, line_num, col_num); + } else { + js_dbuf_init(ctx, &dbuf); + if (stack_trace_limit == 0) + goto done; + if (filename) { + i++; + dbuf_printf(&dbuf, " at %s", filename); + if (line_num != -1) + dbuf_printf(&dbuf, ":%d:%d", line_num, col_num); + dbuf_putc(&dbuf, '\n'); + } + } + + if (filename && (backtrace_flags & JS_BACKTRACE_FLAG_SINGLE_LEVEL)) + goto done; + + sf_start = rt->current_stack_frame; + + /* Find the frame we want to start from. Note that when a filter is used the filter + function will be the first, but we also specify we want to skip the first one. */ + if (has_filter_func) { + for (sf = sf_start; sf != NULL && i < stack_trace_limit; sf = sf->prev_frame) { + if (js_same_value(ctx, sf->cur_func, filter_func)) { + sf_start = sf; + break; + } + } + } + + for (sf = sf_start; sf != NULL && i < stack_trace_limit; sf = sf->prev_frame) { + if (backtrace_flags & JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL) { + backtrace_flags &= ~JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL; + continue; + } + + p = JS_VALUE_GET_OBJ(sf->cur_func); + b = NULL; + backtrace_barrier = false; + + if (js_class_has_bytecode(p->class_id)) { + b = p->u.func.function_bytecode; + backtrace_barrier = b->backtrace_barrier; + } + + if (has_prepare) { + js_new_callsite_data(ctx, &csd[i], sf); + } else { + /* func_name_str is UTF-8 encoded if needed */ + func_name_str = get_func_name(ctx, sf->cur_func); + if (!func_name_str || func_name_str[0] == '\0') + str1 = ""; + else + str1 = func_name_str; + dbuf_printf(&dbuf, " at %s", str1); + JS_FreeCString(ctx, func_name_str); + + if (b && sf->cur_pc) { + const char *atom_str; + int line_num1, col_num1; + uint32_t pc; + + pc = sf->cur_pc - b->byte_code_buf - 1; + line_num1 = find_line_num(ctx, b, pc, &col_num1); + atom_str = b->filename ? JS_AtomToCString(ctx, b->filename) : NULL; + dbuf_printf(&dbuf, " (%s", atom_str ? atom_str : ""); + JS_FreeCString(ctx, atom_str); + if (line_num1 != -1) + dbuf_printf(&dbuf, ":%d:%d", line_num1, col_num1); + dbuf_putc(&dbuf, ')'); + } else if (b) { + // FIXME(bnoordhuis) Missing `sf->cur_pc = pc` in bytecode + // handler in JS_CallInternal. Almost never user observable + // except with intercepting JS proxies that throw exceptions. + dbuf_printf(&dbuf, " (missing)"); + } else { + dbuf_printf(&dbuf, " (native)"); + } + dbuf_putc(&dbuf, '\n'); + } + i++; + + /* stop backtrace if JS_EVAL_FLAG_BACKTRACE_BARRIER was used */ + if (backtrace_barrier) + break; + } + done: + if (has_prepare) { + int j = 0, k; + stack = JS_NewArray(ctx); + if (JS_IsException(stack)) { + stack = JS_NULL; + } else { + for (; j < i; j++) { + JSValue v = js_new_callsite(ctx, &csd[j]); + if (JS_IsException(v)) + break; + if (JS_DefinePropertyValueUint32(ctx, stack, j, v, JS_PROP_C_W_E) < 0) + break; + } + } + // Clear the csd's we didn't use in case of error. + for (k = j; k < i; k++) { + JS_FreeValue(ctx, csd[k].filename); + JS_FreeValue(ctx, csd[k].func); + JS_FreeValue(ctx, csd[k].func_name); + } + JSValueConst args[] = { + error_obj, + stack, + }; + JSValue stack2 = JS_Call(ctx, prepare, ctx->error_ctor, countof(args), args); + JS_FreeValue(ctx, stack); + if (JS_IsException(stack2)) + stack = JS_NULL; + else + stack = stack2; + JS_FreeValue(ctx, prepare); + JS_Throw(ctx, saved_exception); + } else { + if (dbuf_error(&dbuf)) + stack = JS_NULL; + else + stack = JS_NewStringLen(ctx, (char *)dbuf.buf, dbuf.size); + dbuf_free(&dbuf); + } + + if (JS_IsUndefined(ctx->error_back_trace)) + ctx->error_back_trace = js_dup(stack); + if (has_filter_func) { + /* Error.captureStackTrace(target, ...): install an own data property + on the (possibly non-Error) target, shadowing the accessor */ + JS_DefinePropertyValue(ctx, error_obj, JS_ATOM_stack, stack, + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + } else if (can_store_error_stack(error_obj)) { + /* genuine Error instance: store as the [[ErrorData]] stack value */ + p = JS_VALUE_GET_OBJ(error_obj); + JS_FreeValue(ctx, p->u.object_data); + p->u.object_data = stack; + } else if (can_add_backtrace(error_obj)) { + /* DOMException and the like keep an own "stack" data property */ + JS_DefinePropertyValue(ctx, error_obj, JS_ATOM_stack, stack, + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + } else { + JS_FreeValue(ctx, stack); + } + + JS_FreeValue(ctx, error_obj); + rt->in_build_stack_trace = false; +} + +JSValue JS_NewError(JSContext *ctx) +{ + JSValue obj = JS_NewObjectClass(ctx, JS_CLASS_ERROR); + if (JS_IsException(obj)) + return JS_EXCEPTION; + build_backtrace(ctx, obj, JS_UNDEFINED, NULL, 0, 0, 0); + return obj; +} + +static JSValue JS_MakeError2(JSContext *ctx, JSErrorEnum error_num, + bool add_backtrace, const char *message) +{ + JSValue obj, msg; + + if (error_num == JS_PLAIN_ERROR) { + obj = JS_NewObjectClass(ctx, JS_CLASS_ERROR); + } else { + obj = JS_NewObjectProtoClass(ctx, ctx->native_error_proto[error_num], + JS_CLASS_ERROR); + } + if (JS_IsException(obj)) + return JS_EXCEPTION; + msg = JS_NewString(ctx, message); + if (JS_IsException(msg)) + msg = JS_NewString(ctx, "Invalid error message"); + if (!JS_IsException(msg)) { + JS_DefinePropertyValue(ctx, obj, JS_ATOM_message, msg, + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + } + if (add_backtrace) + build_backtrace(ctx, obj, JS_UNDEFINED, NULL, 0, 0, 0); + return obj; +} + +static JSValue JS_PRINTF_FORMAT_ATTR(4, 0) +JS_MakeError(JSContext *ctx, JSErrorEnum error_num, bool add_backtrace, + JS_PRINTF_FORMAT const char *fmt, va_list ap) +{ + char buf[256]; + + vsnprintf(buf, sizeof(buf), fmt, ap); + return JS_MakeError2(ctx, error_num, add_backtrace, buf); +} + +/* fmt and arguments may be pure ASCII or UTF-8 encoded contents */ +static JSValue JS_PRINTF_FORMAT_ATTR(4, 0) +JS_ThrowError2(JSContext *ctx, JSErrorEnum error_num, bool add_backtrace, + JS_PRINTF_FORMAT const char *fmt, va_list ap) +{ + JSValue obj; + + obj = JS_MakeError(ctx, error_num, add_backtrace, fmt, ap); + if (unlikely(JS_IsException(obj))) { + /* out of memory: throw JS_NULL to avoid recursing */ + obj = JS_NULL; + } + return JS_Throw(ctx, obj); +} + +static JSValue JS_PRINTF_FORMAT_ATTR(3, 0) +JS_ThrowError(JSContext *ctx, JSErrorEnum error_num, + JS_PRINTF_FORMAT const char *fmt, va_list ap) +{ + JSRuntime *rt = ctx->rt; + JSStackFrame *sf; + bool add_backtrace; + + /* the backtrace is added later if called from a bytecode function */ + sf = rt->current_stack_frame; + add_backtrace = !rt->in_out_of_memory && + (!sf || (JS_GetFunctionBytecode(sf->cur_func) == NULL)); + return JS_ThrowError2(ctx, error_num, add_backtrace, fmt, ap); +} + +#define JS_ERROR_MAP(X) \ + X(Internal, INTERNAL) \ + X(Plain, PLAIN) \ + X(Range, RANGE) \ + X(Reference, REFERENCE) \ + X(Syntax, SYNTAX) \ + X(Type, TYPE) \ + +#define X(lc, uc) \ + JSValue JS_PRINTF_FORMAT_ATTR(2, 3) \ + JS_New##lc##Error(JSContext *ctx, \ + JS_PRINTF_FORMAT const char *fmt, ...) \ + { \ + JSValue val; \ + va_list ap; \ + \ + va_start(ap, fmt); \ + val = JS_MakeError(ctx, JS_##uc##_ERROR, \ + /*add_backtrace*/true, fmt, ap); \ + va_end(ap); \ + return val; \ + } \ + JSValue JS_PRINTF_FORMAT_ATTR(2, 3) \ + JS_Throw##lc##Error(JSContext *ctx, \ + JS_PRINTF_FORMAT const char *fmt, ...) \ + { \ + JSValue val; \ + va_list ap; \ + \ + va_start(ap, fmt); \ + val = JS_ThrowError(ctx, JS_##uc##_ERROR, fmt, ap); \ + va_end(ap); \ + return val; \ + } \ + +JS_ERROR_MAP(X) + +#undef X +#undef JS_ERROR_MAP + +static int JS_PRINTF_FORMAT_ATTR(3, 4) JS_ThrowTypeErrorOrFalse(JSContext *ctx, int flags, JS_PRINTF_FORMAT const char *fmt, ...) +{ + va_list ap; + + if ((flags & JS_PROP_THROW) || + ((flags & JS_PROP_THROW_STRICT) && is_strict_mode(ctx))) { + va_start(ap, fmt); + JS_ThrowError(ctx, JS_TYPE_ERROR, fmt, ap); + va_end(ap); + return -1; + } else { + return false; + } +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-nonliteral" +#endif // __GNUC__ +static JSValue JS_ThrowTypeErrorAtom(JSContext *ctx, const char *fmt, JSAtom atom) +{ + char buf[ATOM_GET_STR_BUF_SIZE]; + JS_AtomGetStr(ctx, buf, sizeof(buf), atom); + return JS_ThrowTypeError(ctx, fmt, buf); +} + +static JSValue JS_ThrowSyntaxErrorAtom(JSContext *ctx, const char *fmt, JSAtom atom) +{ + char buf[ATOM_GET_STR_BUF_SIZE]; + JS_AtomGetStr(ctx, buf, sizeof(buf), atom); + return JS_ThrowSyntaxError(ctx, fmt, buf); +} +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop // ignored "-Wformat-nonliteral" +#endif // __GNUC__ + +static int JS_ThrowTypeErrorReadOnly(JSContext *ctx, int flags, JSAtom atom) +{ + if ((flags & JS_PROP_THROW) || + ((flags & JS_PROP_THROW_STRICT) && is_strict_mode(ctx))) { + JS_ThrowTypeErrorAtom(ctx, "'%s' is read-only", atom); + return -1; + } else { + return false; + } +} + +JSValue JS_ThrowOutOfMemory(JSContext *ctx) +{ + JSRuntime *rt = ctx->rt; + if (!rt->in_out_of_memory) { + rt->in_out_of_memory = true; + JS_ThrowInternalError(ctx, "out of memory"); + rt->in_out_of_memory = false; + } + return JS_EXCEPTION; +} + +static JSValue JS_ThrowStackOverflow(JSContext *ctx) +{ + return JS_ThrowRangeError(ctx, "Maximum call stack size exceeded"); +} + +static JSValue JS_ThrowTypeErrorNotAConstructor(JSContext *ctx, + JSValueConst func_obj) +{ + JSObject *p; + JSAtom name; + + if (JS_TAG_OBJECT != JS_VALUE_GET_TAG(func_obj)) + goto fini; + p = JS_VALUE_GET_OBJ(func_obj); + if (!js_class_has_bytecode(p->class_id)) + goto fini; + name = p->u.func.function_bytecode->func_name; + if (name == JS_ATOM_NULL) + goto fini; + return JS_ThrowTypeErrorAtom(ctx, "%s is not a constructor", name); +fini: + return JS_ThrowTypeError(ctx, "not a constructor"); +} + +static JSValue JS_ThrowTypeErrorNotAFunction(JSContext *ctx) +{ + return JS_ThrowTypeError(ctx, "not a function"); +} + +static JSValue JS_ThrowTypeErrorNotAnObject(JSContext *ctx) +{ + return JS_ThrowTypeError(ctx, "not an object"); +} + +static JSValue JS_ThrowTypeErrorNotASymbol(JSContext *ctx) +{ + return JS_ThrowTypeError(ctx, "not a symbol"); +} + +static JSValue JS_ThrowReferenceErrorNotDefined(JSContext *ctx, JSAtom name) +{ + char buf[ATOM_GET_STR_BUF_SIZE]; + return JS_ThrowReferenceError(ctx, "%s is not defined", + JS_AtomGetStr(ctx, buf, sizeof(buf), name)); +} + +static JSValue JS_ThrowReferenceErrorUninitialized(JSContext *ctx, JSAtom name) +{ + char buf[ATOM_GET_STR_BUF_SIZE]; + return JS_ThrowReferenceError(ctx, "%s is not initialized", + name == JS_ATOM_NULL ? "lexical variable" : + JS_AtomGetStr(ctx, buf, sizeof(buf), name)); +} + +static JSValue JS_ThrowReferenceErrorUninitialized2(JSContext *ctx, + JSFunctionBytecode *b, + int idx, bool is_ref) +{ + JSAtom atom = JS_ATOM_NULL; + if (is_ref) { + atom = b->closure_var[idx].var_name; + } else { + /* not present if the function is stripped and contains no eval() */ + if (b->vardefs) + atom = b->vardefs[b->arg_count + idx].var_name; + } + return JS_ThrowReferenceErrorUninitialized(ctx, atom); +} + +static JSValue JS_ThrowTypeErrorInvalidClass(JSContext *ctx, int class_id) +{ + JSRuntime *rt = ctx->rt; + JSAtom name; + name = rt->class_array[class_id].class_name; + return JS_ThrowTypeErrorAtom(ctx, "%s object expected", name); +} + +static void JS_ThrowInterrupted(JSContext *ctx) +{ + JS_ThrowInternalError(ctx, "interrupted"); + JS_SetUncatchableError(ctx, ctx->rt->current_exception); +} + +static no_inline __exception int __js_poll_interrupts(JSContext *ctx) +{ + JSRuntime *rt = ctx->rt; + ctx->interrupt_counter = JS_INTERRUPT_COUNTER_INIT; + if (rt->interrupt_handler) { + if (rt->interrupt_handler(rt, rt->interrupt_opaque)) { + JS_ThrowInterrupted(ctx); + return -1; + } + } + return 0; +} + +static inline __exception int js_poll_interrupts(JSContext *ctx) +{ + if (unlikely(--ctx->interrupt_counter <= 0)) { + return __js_poll_interrupts(ctx); + } else { + return 0; + } +} + +/* return -1 (exception) or true/false */ +static int JS_SetPrototypeInternal(JSContext *ctx, JSValueConst obj, + JSValueConst proto_val, bool throw_flag) +{ + JSObject *proto, *p, *p1; + JSShape *sh; + + if (throw_flag) { + if (JS_VALUE_GET_TAG(obj) == JS_TAG_NULL || + JS_VALUE_GET_TAG(obj) == JS_TAG_UNDEFINED) + goto not_obj; + } else { + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) + goto not_obj; + } + p = JS_VALUE_GET_OBJ(obj); + if (JS_VALUE_GET_TAG(proto_val) != JS_TAG_OBJECT) { + if (JS_VALUE_GET_TAG(proto_val) != JS_TAG_NULL) { + not_obj: + JS_ThrowTypeErrorNotAnObject(ctx); + return -1; + } + proto = NULL; + } else { + proto = JS_VALUE_GET_OBJ(proto_val); + } + + if (throw_flag && JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) + return true; + + if (unlikely(p->class_id == JS_CLASS_PROXY)) + return js_proxy_setPrototypeOf(ctx, obj, proto_val, throw_flag); + sh = p->shape; + if (sh->proto == proto) + return true; + if (p == JS_VALUE_GET_OBJ(ctx->class_proto[JS_CLASS_OBJECT])) { + if (throw_flag) { + JS_ThrowTypeError(ctx, "'Immutable prototype object \'Object.prototype\' cannot have their prototype set'"); + return -1; + } + return false; + } + if (!p->extensible) { + if (throw_flag) { + JS_ThrowTypeError(ctx, "object is not extensible"); + return -1; + } else { + return false; + } + } + if (proto) { + /* check if there is a cycle */ + p1 = proto; + do { + if (p1 == p) { + if (throw_flag) { + JS_ThrowTypeError(ctx, "circular prototype chain"); + return -1; + } else { + return false; + } + } + /* Note: for Proxy objects, proto is NULL */ + p1 = p1->shape->proto; + } while (p1 != NULL); + js_dup(proto_val); + } + + if (js_shape_prepare_update(ctx, p, NULL)) + return -1; + sh = p->shape; + if (sh->proto) + JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, sh->proto)); + sh->proto = proto; + if (proto) + proto->is_prototype = true; + if (p->is_prototype) { + /* track modification of Array.prototype */ + if (unlikely(p == JS_VALUE_GET_OBJ(ctx->class_proto[JS_CLASS_ARRAY]))) { + ctx->std_array_prototype = false; + } + } + return true; +} + +/* return -1 (exception) or true/false */ +int JS_SetPrototype(JSContext *ctx, JSValueConst obj, JSValueConst proto_val) +{ + return JS_SetPrototypeInternal(ctx, obj, proto_val, true); +} + +/* Only works for primitive types, otherwise return JS_NULL. */ +static JSValueConst JS_GetPrototypePrimitive(JSContext *ctx, JSValueConst val) +{ + JSValue ret; + switch(JS_VALUE_GET_NORM_TAG(val)) { + case JS_TAG_SHORT_BIG_INT: + case JS_TAG_BIG_INT: + ret = ctx->class_proto[JS_CLASS_BIG_INT]; + break; + case JS_TAG_INT: + case JS_TAG_FLOAT64: + ret = ctx->class_proto[JS_CLASS_NUMBER]; + break; + case JS_TAG_BOOL: + ret = ctx->class_proto[JS_CLASS_BOOLEAN]; + break; + case JS_TAG_STRING: + case JS_TAG_STRING_ROPE: + ret = ctx->class_proto[JS_CLASS_STRING]; + break; + case JS_TAG_SYMBOL: + ret = ctx->class_proto[JS_CLASS_SYMBOL]; + break; + case JS_TAG_OBJECT: + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + default: + ret = JS_NULL; + break; + } + return ret; +} + +/* Return an Object, JS_NULL or JS_EXCEPTION in case of Proxy object. */ +JSValue JS_GetPrototype(JSContext *ctx, JSValueConst obj) +{ + JSValue val; + if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { + JSObject *p; + p = JS_VALUE_GET_OBJ(obj); + if (unlikely(p->class_id == JS_CLASS_PROXY)) { + val = js_proxy_getPrototypeOf(ctx, obj); + } else { + p = p->shape->proto; + if (!p) + val = JS_NULL; + else + val = js_dup(JS_MKPTR(JS_TAG_OBJECT, p)); + } + } else { + val = js_dup(JS_GetPrototypePrimitive(ctx, obj)); + } + return val; +} + +static JSValue JS_GetPrototypeFree(JSContext *ctx, JSValue obj) +{ + JSValue obj1; + obj1 = JS_GetPrototype(ctx, obj); + JS_FreeValue(ctx, obj); + return obj1; +} + +int JS_GetLength(JSContext *ctx, JSValueConst obj, int64_t *pres) { + return js_get_length64(ctx, pres, obj); +} + +int JS_SetLength(JSContext *ctx, JSValueConst obj, int64_t len) { + return js_set_length64(ctx, obj, len); +} + +/* return true, false or (-1) in case of exception */ +static int JS_OrdinaryIsInstanceOf(JSContext *ctx, JSValueConst val, + JSValueConst obj) +{ + JSValue obj_proto; + JSObject *proto; + const JSObject *p, *proto1; + int ret; + + if (!JS_IsFunction(ctx, obj)) + return false; + p = JS_VALUE_GET_OBJ(obj); + if (p->class_id == JS_CLASS_BOUND_FUNCTION) { + JSBoundFunction *s = p->u.bound_function; + return JS_IsInstanceOf(ctx, val, s->func_obj); + } + + /* Only explicitly boxed values are instances of constructors */ + if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) + return false; + obj_proto = JS_GetProperty(ctx, obj, JS_ATOM_prototype); + if (JS_VALUE_GET_TAG(obj_proto) != JS_TAG_OBJECT) { + if (!JS_IsException(obj_proto)) + JS_ThrowTypeError(ctx, "operand 'prototype' property is not an object"); + ret = -1; + goto done; + } + proto = JS_VALUE_GET_OBJ(obj_proto); + p = JS_VALUE_GET_OBJ(val); + for(;;) { + proto1 = p->shape->proto; + if (!proto1) { + /* slow case if proxy in the prototype chain */ + if (unlikely(p->class_id == JS_CLASS_PROXY)) { + JSValue obj1; + obj1 = js_dup(JS_MKPTR(JS_TAG_OBJECT, (JSObject *)p)); + for(;;) { + obj1 = JS_GetPrototypeFree(ctx, obj1); + if (JS_IsException(obj1)) { + ret = -1; + break; + } + if (JS_IsNull(obj1)) { + ret = false; + break; + } + if (proto == JS_VALUE_GET_OBJ(obj1)) { + JS_FreeValue(ctx, obj1); + ret = true; + break; + } + /* must check for timeout to avoid infinite loop */ + if (js_poll_interrupts(ctx)) { + JS_FreeValue(ctx, obj1); + ret = -1; + break; + } + } + } else { + ret = false; + } + break; + } + p = proto1; + if (proto == p) { + ret = true; + break; + } + } +done: + JS_FreeValue(ctx, obj_proto); + return ret; +} + +/* return true, false or (-1) in case of exception */ +int JS_IsInstanceOf(JSContext *ctx, JSValueConst val, JSValueConst obj) +{ + JSValue method; + + if (!JS_IsObject(obj)) + goto fail; + method = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_hasInstance); + if (JS_IsException(method)) + return -1; + if (!JS_IsNull(method) && !JS_IsUndefined(method)) { + JSValue ret; + ret = JS_CallFree(ctx, method, obj, 1, &val); + return JS_ToBoolFree(ctx, ret); + } + + /* legacy case */ + if (!JS_IsFunction(ctx, obj)) { + fail: + JS_ThrowTypeError(ctx, "invalid 'instanceof' right operand"); + return -1; + } + return JS_OrdinaryIsInstanceOf(ctx, val, obj); +} + +#include "builtin-array-fromasync.h" +#include "builtin-iterator-zip-keyed.h" +#include "builtin-iterator-zip.h" + +// like Function.prototype.call but monkey patch-proof +static JSValue js_call_function(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + return JS_Call(ctx, argv[1], argv[0], argc-2, argv+2); +} + +// returns enumerable and non-enumerable strings *and* symbols +static JSValue js_getOwnPropertyKeys(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int flags = JS_GPN_STRING_MASK|JS_GPN_SYMBOL_MASK; + return JS_GetOwnPropertyNames2(ctx, argv[0], flags, JS_ITERATOR_KIND_KEY); +} + +static JSValue js_hasOwnEnumProperty(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSObject *p; + JSAtom key; + int flags, res; + + if (JS_TAG_OBJECT != JS_VALUE_GET_TAG(argv[0])) + return JS_ThrowTypeErrorNotAnObject(ctx); + p = JS_VALUE_GET_OBJ(argv[0]); + key = JS_ValueToAtomInternal(ctx, argv[1], JS_TO_STRING_NO_SIDE_EFFECTS); + if (key == JS_ATOM_NULL) + return JS_EXCEPTION; + res = JS_GetOwnPropertyFlagsInternal(ctx, &flags, p, key); + JS_FreeAtom(ctx, key); + if (res < 0) + return JS_EXCEPTION; + if (res > 0 && (flags & JS_PROP_ENUMERABLE)) + return JS_TRUE; + return JS_FALSE; +} + +// note: takes ownership of |argv| +static JSValue js_bytecode_eval(JSContext *ctx, const uint8_t *bytecode, + size_t len, int argc, JSValue *argv) +{ + JSValue obj, fun, result; + int i; + + obj = JS_ReadObject(ctx, bytecode, len, JS_READ_OBJ_BYTECODE); + if (JS_IsException(obj)) + return JS_EXCEPTION; + fun = JS_EvalFunction(ctx, obj); + if (JS_IsException(fun)) + return JS_EXCEPTION; + assert(JS_IsFunction(ctx, fun)); + result = JS_Call(ctx, fun, JS_UNDEFINED, argc, vc(argv)); + for (i = 0; i < argc; i++) + JS_FreeValue(ctx, argv[i]); + JS_FreeValue(ctx, fun); + if (JS_SetPrototypeInternal(ctx, result, ctx->function_proto, + /*throw_flag*/true) < 0) { + JS_FreeValue(ctx, result); + return JS_EXCEPTION; + } + return result; +} + +static JSValue js_bytecode_autoinit(JSContext *ctx, JSObject *p, JSAtom atom, + void *opaque) +{ + switch ((uintptr_t)opaque) { + default: + abort(); + case JS_BUILTIN_ARRAY_FROMASYNC: + { + JSValue argv[] = { + JS_NewCFunction(ctx, js_array_constructor, "Array", 0), + JS_NewCFunctionMagic(ctx, js_error_constructor, "TypeError", + 1, JS_CFUNC_constructor_or_func_magic, + JS_TYPE_ERROR), + JS_AtomToValue(ctx, JS_ATOM_Symbol_asyncIterator), + JS_NewCFunctionMagic(ctx, js_object_defineProperty, + "Object.defineProperty", 3, + JS_CFUNC_generic_magic, 0), + JS_AtomToValue(ctx, JS_ATOM_Symbol_iterator), + }; + return js_bytecode_eval(ctx, qjsc_builtin_array_fromasync, + sizeof(qjsc_builtin_array_fromasync), + countof(argv), argv); + } + case JS_BUILTIN_ITERATOR_ZIP: + { + JSValue argv[] = { + js_dup(ctx->class_proto[JS_CLASS_ITERATOR_HELPER]), + JS_NewCFunctionMagic(ctx, js_error_constructor, "InternalError", + 1, JS_CFUNC_constructor_or_func_magic, + JS_INTERNAL_ERROR), + JS_NewCFunctionMagic(ctx, js_error_constructor, "TypeError", + 1, JS_CFUNC_constructor_or_func_magic, + JS_TYPE_ERROR), + JS_NewCFunction(ctx, js_call_function, "call", 2), + JS_AtomToValue(ctx, JS_ATOM_Symbol_iterator), + }; + JSValue result = js_bytecode_eval(ctx, qjsc_builtin_iterator_zip, + sizeof(qjsc_builtin_iterator_zip), + countof(argv), argv); + JS_SetConstructorBit(ctx, result, false); + return result; + } + case JS_BUILTIN_ITERATOR_ZIP_KEYED: + { + JSValue argv[] = { + js_dup(ctx->class_proto[JS_CLASS_ITERATOR_HELPER]), + JS_NewCFunctionMagic(ctx, js_error_constructor, "InternalError", + 1, JS_CFUNC_constructor_or_func_magic, + JS_INTERNAL_ERROR), + JS_NewCFunctionMagic(ctx, js_error_constructor, "TypeError", + 1, JS_CFUNC_constructor_or_func_magic, + JS_TYPE_ERROR), + JS_NewCFunction(ctx, js_call_function, "call", 2), + JS_NewCFunction(ctx, js_hasOwnEnumProperty, + "hasOwnEnumProperty", 2), + JS_NewCFunction(ctx, js_getOwnPropertyKeys, + "getOwnPropertyKeys", 1), + JS_AtomToValue(ctx, JS_ATOM_Symbol_iterator), + }; + JSValue result = js_bytecode_eval(ctx, qjsc_builtin_iterator_zip_keyed, + sizeof(qjsc_builtin_iterator_zip_keyed), + countof(argv), argv); + JS_SetConstructorBit(ctx, result, false); + return result; + } + } + return JS_UNDEFINED; +} + +/* return the value associated to the autoinit property or an exception */ +typedef JSValue JSAutoInitFunc(JSContext *ctx, JSObject *p, JSAtom atom, void *opaque); + +static JSAutoInitFunc *const js_autoinit_func_table[] = { + js_instantiate_prototype, /* JS_AUTOINIT_ID_PROTOTYPE */ + js_module_ns_autoinit, /* JS_AUTOINIT_ID_MODULE_NS */ + JS_InstantiateFunctionListItem2, /* JS_AUTOINIT_ID_PROP */ + js_bytecode_autoinit, /* JS_AUTOINIT_ID_BYTECODE */ +}; + +/* warning: 'prs' is reallocated after it */ +static int JS_AutoInitProperty(JSContext *ctx, JSObject *p, JSAtom prop, + JSProperty *pr, JSShapeProperty *prs) +{ + JSValue val; + JSContext *realm; + JSAutoInitFunc *func; + + if (js_shape_prepare_update(ctx, p, &prs)) + return -1; + + realm = js_autoinit_get_realm(pr); + func = js_autoinit_func_table[js_autoinit_get_id(pr)]; + /* 'func' shall not modify the object properties 'pr' */ + val = func(realm, p, prop, pr->u.init.opaque); + js_autoinit_free(ctx->rt, pr); + prs->flags &= ~JS_PROP_TMASK; + pr->u.value = JS_UNDEFINED; + if (JS_IsException(val)) + return -1; + pr->u.value = val; + return 0; +} + +static JSValue JS_GetPropertyInternal(JSContext *ctx, JSValueConst obj, + JSAtom prop, JSValueConst this_obj, + bool throw_ref_error) +{ + JSObject *p; + JSProperty *pr; + JSShapeProperty *prs; + uint32_t tag; + + tag = JS_VALUE_GET_TAG(obj); + if (unlikely(tag != JS_TAG_OBJECT)) { + switch(tag) { + case JS_TAG_NULL: + return JS_ThrowTypeErrorAtom(ctx, "cannot read property '%s' of null", prop); + case JS_TAG_UNDEFINED: + return JS_ThrowTypeErrorAtom(ctx, "cannot read property '%s' of undefined", prop); + case JS_TAG_EXCEPTION: + return JS_EXCEPTION; + case JS_TAG_STRING: + { + JSString *p1 = JS_VALUE_GET_STRING(obj); + if (__JS_AtomIsTaggedInt(prop)) { + uint32_t idx, ch; + idx = __JS_AtomToUInt32(prop); + if (idx < p1->len) { + ch = string_get(p1, idx); + return js_new_string_char(ctx, ch); + } + } else if (prop == JS_ATOM_length) { + return js_int32(p1->len); + } + } + break; + case JS_TAG_STRING_ROPE: + { + JSStringRope *r = JS_VALUE_GET_STRING_ROPE(obj); + if (__JS_AtomIsTaggedInt(prop)) { + uint32_t idx, ch; + idx = __JS_AtomToUInt32(prop); + if (idx < r->len) { + ch = string_rope_get(obj, idx); + return js_new_string_char(ctx, ch); + } + } else if (prop == JS_ATOM_length) { + return js_int32(r->len); + } + } + break; + default: + break; + } + /* cannot raise an exception */ + p = JS_VALUE_GET_OBJ(JS_GetPrototypePrimitive(ctx, obj)); + if (!p) + return JS_UNDEFINED; + } else { + p = JS_VALUE_GET_OBJ(obj); + } + + for(;;) { + prs = find_own_property(&pr, p, prop); + if (prs) { + /* found */ + if (unlikely(prs->flags & JS_PROP_TMASK)) { + if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { + if (unlikely(!pr->u.getset.getter)) { + return JS_UNDEFINED; + } else { + JSValue func = JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.getter); + /* Note: the field could be removed in the getter */ + func = js_dup(func); + return JS_CallFree(ctx, func, this_obj, 0, NULL); + } + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { + JSValue val = *pr->u.var_ref->pvalue; + if (unlikely(JS_IsUninitialized(val))) + return JS_ThrowReferenceErrorUninitialized(ctx, prs->atom); + return js_dup(val); + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { + /* Instantiate property and retry */ + if (JS_AutoInitProperty(ctx, p, prop, pr, prs)) + return JS_EXCEPTION; + continue; + } + } else { + return js_dup(pr->u.value); + } + } + if (unlikely(p->is_exotic)) { + /* exotic behaviors */ + if (p->fast_array) { + if (__JS_AtomIsTaggedInt(prop)) { + uint32_t idx = __JS_AtomToUInt32(prop); + if (idx < p->u.array.count) { + /* we avoid duplicating the code */ + return JS_GetPropertyUint32(ctx, JS_MKPTR(JS_TAG_OBJECT, p), idx); + } else if (is_typed_array(p->class_id)) { + return JS_UNDEFINED; + } + } else if (is_typed_array(p->class_id)) { + int ret; + ret = JS_AtomIsNumericIndex(ctx, prop); + if (ret != 0) { + if (ret < 0) + return JS_EXCEPTION; + return JS_UNDEFINED; + } + } + } else { + const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; + if (em) { + if (em->get_property) { + JSValue obj1, retval; + /* XXX: should pass throw_ref_error */ + /* Note: if 'p' is a prototype, it can be + freed in the called function */ + obj1 = js_dup(JS_MKPTR(JS_TAG_OBJECT, p)); + retval = em->get_property(ctx, obj1, prop, this_obj); + JS_FreeValue(ctx, obj1); + return retval; + } + if (em->get_own_property) { + JSPropertyDescriptor desc; + int ret; + JSValue obj1; + + /* Note: if 'p' is a prototype, it can be + freed in the called function */ + obj1 = js_dup(JS_MKPTR(JS_TAG_OBJECT, p)); + ret = em->get_own_property(ctx, &desc, obj1, prop); + JS_FreeValue(ctx, obj1); + if (ret < 0) + return JS_EXCEPTION; + if (ret) { + if (desc.flags & JS_PROP_GETSET) { + JS_FreeValue(ctx, desc.setter); + return JS_CallFree(ctx, desc.getter, this_obj, 0, NULL); + } else { + return desc.value; + } + } + } + } + } + } + p = p->shape->proto; + if (!p) + break; + } + if (unlikely(throw_ref_error)) { + return JS_ThrowReferenceErrorNotDefined(ctx, prop); + } else { + return JS_UNDEFINED; + } +} + +JSValue JS_GetProperty(JSContext *ctx, JSValueConst this_obj, JSAtom prop) +{ + return JS_GetPropertyInternal(ctx, this_obj, prop, this_obj, false); +} + +static JSValue JS_ThrowTypeErrorPrivateNotFound(JSContext *ctx, JSAtom atom) +{ + return JS_ThrowTypeErrorAtom(ctx, "private class field '%s' does not exist", + atom); +} + +/* Per the nonextensible-applies-to-private proposal, private fields cannot + be added to non-extensible objects. Note that a Proxy's JSObject stays + extensible (its trap reflects the target), so private fields can still be + stamped onto Proxies. */ +static int JS_DefinePrivateField(JSContext *ctx, JSValueConst obj, + JSValue name, JSValue val) +{ + JSObject *p; + JSShapeProperty *prs; + JSProperty *pr; + JSAtom prop; + + if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) { + JS_ThrowTypeErrorNotAnObject(ctx); + goto fail; + } + /* safety check */ + if (unlikely(JS_VALUE_GET_TAG(name) != JS_TAG_SYMBOL)) { + JS_ThrowTypeErrorNotASymbol(ctx); + goto fail; + } + prop = js_symbol_to_atom(ctx, name); + p = JS_VALUE_GET_OBJ(obj); + if (unlikely(!p->extensible)) { + JS_ThrowTypeError(ctx, "object is not extensible"); + goto fail; + } + prs = find_own_property(&pr, p, prop); + if (prs) { + JS_ThrowTypeErrorAtom(ctx, "private class field '%s' already exists", + prop); + goto fail; + } + pr = add_property(ctx, p, prop, JS_PROP_C_W_E); + if (unlikely(!pr)) { + fail: + JS_FreeValue(ctx, val); + return -1; + } + pr->u.value = val; + return 0; +} + +static JSValue JS_GetPrivateField(JSContext *ctx, JSValueConst obj, + JSValueConst name) +{ + JSObject *p; + JSShapeProperty *prs; + JSProperty *pr; + JSAtom prop; + + if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) + return JS_ThrowTypeErrorNotAnObject(ctx); + /* safety check */ + if (unlikely(JS_VALUE_GET_TAG(name) != JS_TAG_SYMBOL)) + return JS_ThrowTypeErrorNotASymbol(ctx); + prop = js_symbol_to_atom(ctx, name); + p = JS_VALUE_GET_OBJ(obj); + prs = find_own_property(&pr, p, prop); + if (!prs) { + JS_ThrowTypeErrorPrivateNotFound(ctx, prop); + return JS_EXCEPTION; + } + return js_dup(pr->u.value); +} + +static int JS_SetPrivateField(JSContext *ctx, JSValueConst obj, + JSValueConst name, JSValue val) +{ + JSObject *p; + JSShapeProperty *prs; + JSProperty *pr; + JSAtom prop; + + if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) { + JS_ThrowTypeErrorNotAnObject(ctx); + goto fail; + } + /* safety check */ + if (unlikely(JS_VALUE_GET_TAG(name) != JS_TAG_SYMBOL)) { + JS_ThrowTypeErrorNotASymbol(ctx); + goto fail; + } + prop = js_symbol_to_atom(ctx, name); + p = JS_VALUE_GET_OBJ(obj); + prs = find_own_property(&pr, p, prop); + if (!prs) { + JS_ThrowTypeErrorPrivateNotFound(ctx, prop); + fail: + JS_FreeValue(ctx, val); + return -1; + } + set_value(ctx, &pr->u.value, val); + return 0; +} + +/* add a private brand field to 'home_obj' if not already present and + if obj is != null add a private brand to it */ +static int JS_AddBrand(JSContext *ctx, JSValueConst obj, JSValueConst home_obj) +{ + JSObject *p, *p1; + JSShapeProperty *prs; + JSProperty *pr; + JSValue brand; + JSAtom brand_atom; + + if (unlikely(JS_VALUE_GET_TAG(home_obj) != JS_TAG_OBJECT)) { + JS_ThrowTypeErrorNotAnObject(ctx); + return -1; + } + p = JS_VALUE_GET_OBJ(home_obj); + prs = find_own_property(&pr, p, JS_ATOM_Private_brand); + if (!prs) { + /* if the brand is not present, add it */ + brand = JS_NewSymbolFromAtom(ctx, JS_ATOM_brand, JS_ATOM_TYPE_PRIVATE); + if (JS_IsException(brand)) + return -1; + pr = add_property(ctx, p, JS_ATOM_Private_brand, JS_PROP_C_W_E); + if (!pr) { + JS_FreeValue(ctx, brand); + return -1; + } + pr->u.value = js_dup(brand); + } else { + brand = js_dup(pr->u.value); + } + brand_atom = js_symbol_to_atom(ctx, brand); + + if (JS_IsObject(obj)) { + p1 = JS_VALUE_GET_OBJ(obj); + if (unlikely(!p1->extensible)) { + JS_FreeAtom(ctx, brand_atom); + JS_ThrowTypeError(ctx, "object is not extensible"); + return -1; + } + prs = find_own_property(&pr, p1, brand_atom); + if (unlikely(prs)) { + JS_FreeAtom(ctx, brand_atom); + JS_ThrowTypeError(ctx, "private method is already present"); + return -1; + } + pr = add_property(ctx, p1, brand_atom, JS_PROP_C_W_E); + JS_FreeAtom(ctx, brand_atom); + if (!pr) + return -1; + pr->u.value = JS_UNDEFINED; + } else { + JS_FreeAtom(ctx, brand_atom); + } + + return 0; +} + +/* return a boolean telling if the brand of the home object of 'func' + is present on 'obj' or -1 in case of exception */ +static int JS_CheckBrand(JSContext *ctx, JSValue obj, JSValue func) +{ + JSObject *p, *p1, *home_obj; + JSShapeProperty *prs; + JSProperty *pr; + JSValue brand; + + /* get the home object of 'func' */ + if (unlikely(JS_VALUE_GET_TAG(func) != JS_TAG_OBJECT)) + goto not_obj; + p1 = JS_VALUE_GET_OBJ(func); + if (!js_class_has_bytecode(p1->class_id)) + goto not_obj; + home_obj = p1->u.func.home_object; + if (!home_obj) + goto not_obj; + prs = find_own_property(&pr, home_obj, JS_ATOM_Private_brand); + if (!prs) { + JS_ThrowTypeError(ctx, "expecting private field"); + return -1; + } + brand = pr->u.value; + /* safety check */ + if (unlikely(JS_VALUE_GET_TAG(brand) != JS_TAG_SYMBOL)) + goto not_obj; + + /* get the brand array of 'obj' */ + if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) { + not_obj: + JS_ThrowTypeErrorNotAnObject(ctx); + return -1; + } + p = JS_VALUE_GET_OBJ(obj); + prs = find_own_property(&pr, p, js_symbol_to_atom(ctx, brand)); + return (prs != NULL); +} + +static uint32_t js_string_obj_get_length(JSContext *ctx, JSValueConst obj) +{ + JSObject *p; + JSString *p1; + uint32_t len = 0; + + /* This is a class exotic method: obj class_id is JS_CLASS_STRING */ + p = JS_VALUE_GET_OBJ(obj); + if (JS_VALUE_GET_TAG(p->u.object_data) == JS_TAG_STRING) { + p1 = JS_VALUE_GET_STRING(p->u.object_data); + len = p1->len; + } + return len; +} + +static int num_keys_cmp(const void *p1, const void *p2, void *opaque) +{ + JSContext *ctx = opaque; + JSAtom atom1 = ((const JSPropertyEnum *)p1)->atom; + JSAtom atom2 = ((const JSPropertyEnum *)p2)->atom; + uint32_t v1, v2; + bool atom1_is_integer, atom2_is_integer; + + atom1_is_integer = JS_AtomIsArrayIndex(ctx, &v1, atom1); + atom2_is_integer = JS_AtomIsArrayIndex(ctx, &v2, atom2); + assert(atom1_is_integer && atom2_is_integer); + if (v1 < v2) + return -1; + else if (v1 == v2) + return 0; + else + return 1; +} + +static void js_free_prop_enum(JSContext *ctx, JSPropertyEnum *tab, uint32_t len) +{ + uint32_t i; + if (tab) { + for(i = 0; i < len; i++) + JS_FreeAtom(ctx, tab[i].atom); + js_free(ctx, tab); + } +} + +/* return < 0 in case if exception, 0 if OK. ptab and its atoms must + be freed by the user. */ +static int __exception JS_GetOwnPropertyNamesInternal(JSContext *ctx, + JSPropertyEnum **ptab, + uint32_t *plen, + JSObject *p, int flags) +{ + int i, j; + JSShape *sh; + JSShapeProperty *prs; + JSPropertyEnum *tab_atom, *tab_exotic; + JSAtom atom; + uint32_t num_keys_count, str_keys_count, sym_keys_count, atom_count; + uint32_t num_index, str_index, sym_index, exotic_count, exotic_keys_count; + bool is_enumerable, num_sorted; + uint32_t num_key; + JSAtomKindEnum kind; + + /* clear pointer for consistency in case of failure */ + *ptab = NULL; + *plen = 0; + + /* compute the number of returned properties */ + num_keys_count = 0; + str_keys_count = 0; + sym_keys_count = 0; + exotic_keys_count = 0; + exotic_count = 0; + tab_exotic = NULL; + sh = p->shape; + for(i = 0, prs = get_shape_prop(sh); i < sh->prop_count; i++, prs++) { + atom = prs->atom; + if (atom != JS_ATOM_NULL) { + is_enumerable = ((prs->flags & JS_PROP_ENUMERABLE) != 0); + kind = JS_AtomGetKind(ctx, atom); + if ((!(flags & JS_GPN_ENUM_ONLY) || is_enumerable) && + ((flags >> kind) & 1) != 0) { + /* need to raise an exception in case of the module + name space (implicit GetOwnProperty) */ + if (unlikely((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) && + (flags & (JS_GPN_SET_ENUM | JS_GPN_ENUM_ONLY))) { + JSVarRef *var_ref = p->prop[i].u.var_ref; + if (unlikely(JS_IsUninitialized(*var_ref->pvalue))) { + JS_ThrowReferenceErrorUninitialized(ctx, prs->atom); + return -1; + } + } + if (JS_AtomIsArrayIndex(ctx, &num_key, atom)) { + num_keys_count++; + } else if (kind == JS_ATOM_KIND_STRING) { + str_keys_count++; + } else { + sym_keys_count++; + } + } + } + } + + if (p->is_exotic) { + if (p->fast_array) { + if (flags & JS_GPN_STRING_MASK) { + num_keys_count += p->u.array.count; + } + } else if (p->class_id == JS_CLASS_STRING) { + if (flags & JS_GPN_STRING_MASK) { + num_keys_count += js_string_obj_get_length(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); + } + } else { + const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; + if (em && em->get_own_property_names) { + if (em->get_own_property_names(ctx, &tab_exotic, &exotic_count, + JS_MKPTR(JS_TAG_OBJECT, p))) + return -1; + for(i = 0; i < exotic_count; i++) { + atom = tab_exotic[i].atom; + kind = JS_AtomGetKind(ctx, atom); + if (((flags >> kind) & 1) != 0) { + is_enumerable = false; + if (flags & (JS_GPN_SET_ENUM | JS_GPN_ENUM_ONLY)) { + int desc_flags, res; + /* set the "is_enumerable" field if necessary */ + res = JS_GetOwnPropertyFlagsInternal(ctx, &desc_flags, p, atom); + if (res < 0) { + js_free_prop_enum(ctx, tab_exotic, exotic_count); + return -1; + } + if (res) { + is_enumerable = + ((desc_flags & JS_PROP_ENUMERABLE) != 0); + } + tab_exotic[i].is_enumerable = is_enumerable; + } + if (!(flags & JS_GPN_ENUM_ONLY) || is_enumerable) { + exotic_keys_count++; + } + } + } + } + } + } + + /* fill them */ + + atom_count = num_keys_count + str_keys_count + sym_keys_count + exotic_keys_count; + /* avoid allocating 0 bytes */ + tab_atom = js_malloc(ctx, sizeof(tab_atom[0]) * max_int(atom_count, 1)); + if (!tab_atom) { + js_free_prop_enum(ctx, tab_exotic, exotic_count); + return -1; + } + + num_index = 0; + str_index = num_keys_count; + sym_index = str_index + str_keys_count; + + num_sorted = true; + sh = p->shape; + for(i = 0, prs = get_shape_prop(sh); i < sh->prop_count; i++, prs++) { + atom = prs->atom; + if (atom != JS_ATOM_NULL) { + is_enumerable = ((prs->flags & JS_PROP_ENUMERABLE) != 0); + kind = JS_AtomGetKind(ctx, atom); + if ((!(flags & JS_GPN_ENUM_ONLY) || is_enumerable) && + ((flags >> kind) & 1) != 0) { + if (JS_AtomIsArrayIndex(ctx, &num_key, atom)) { + j = num_index++; + num_sorted = false; + } else if (kind == JS_ATOM_KIND_STRING) { + j = str_index++; + } else { + j = sym_index++; + } + tab_atom[j].atom = JS_DupAtom(ctx, atom); + tab_atom[j].is_enumerable = is_enumerable; + } + } + } + + if (p->is_exotic) { + int len; + if (p->fast_array) { + if (flags & JS_GPN_STRING_MASK) { + len = p->u.array.count; + goto add_array_keys; + } + } else if (p->class_id == JS_CLASS_STRING) { + if (flags & JS_GPN_STRING_MASK) { + len = js_string_obj_get_length(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); + add_array_keys: + for(i = 0; i < len; i++) { + tab_atom[num_index].atom = __JS_AtomFromUInt32(i); + if (tab_atom[num_index].atom == JS_ATOM_NULL) { + js_free_prop_enum(ctx, tab_atom, num_index); + return -1; + } + tab_atom[num_index].is_enumerable = true; + num_index++; + } + } + } else { + /* Note: exotic keys are not reordered and comes after the object own properties. */ + for(i = 0; i < exotic_count; i++) { + atom = tab_exotic[i].atom; + is_enumerable = tab_exotic[i].is_enumerable; + kind = JS_AtomGetKind(ctx, atom); + if ((!(flags & JS_GPN_ENUM_ONLY) || is_enumerable) && + ((flags >> kind) & 1) != 0) { + tab_atom[sym_index].atom = atom; + tab_atom[sym_index].is_enumerable = is_enumerable; + sym_index++; + } else { + JS_FreeAtom(ctx, atom); + } + } + js_free(ctx, tab_exotic); + } + } + + assert(num_index == num_keys_count); + assert(str_index == num_keys_count + str_keys_count); + assert(sym_index == atom_count); + + if (num_keys_count != 0 && !num_sorted) { + rqsort(tab_atom, num_keys_count, sizeof(tab_atom[0]), num_keys_cmp, + ctx); + } + *ptab = tab_atom; + *plen = atom_count; + return 0; +} + +int JS_GetOwnPropertyNames(JSContext *ctx, JSPropertyEnum **ptab, + uint32_t *plen, JSValueConst obj, int flags) +{ + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) { + JS_ThrowTypeErrorNotAnObject(ctx); + return -1; + } + return JS_GetOwnPropertyNamesInternal(ctx, ptab, plen, + JS_VALUE_GET_OBJ(obj), flags); +} + +/* Return -1 if exception, + false if the property does not exist, true if it exists. If true is + returned, the property descriptor 'desc' is filled present. */ +static int JS_GetOwnPropertyInternal2(JSContext *ctx, JSPropertyDescriptor *desc, + JSObject *p, JSAtom prop, int *pflags) +{ + JSShapeProperty *prs; + JSProperty *pr; + int flags_only = (desc == NULL && pflags != NULL); + +retry: + prs = find_own_property(&pr, p, prop); + if (prs) { + if (desc) { + desc->flags = prs->flags & JS_PROP_C_W_E; + desc->getter = JS_UNDEFINED; + desc->setter = JS_UNDEFINED; + desc->value = JS_UNDEFINED; + if (unlikely(prs->flags & JS_PROP_TMASK)) { + if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { + desc->flags |= JS_PROP_GETSET; + if (pr->u.getset.getter) + desc->getter = js_dup(JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.getter)); + if (pr->u.getset.setter) + desc->setter = js_dup(JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.setter)); + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { + JSValue val = *pr->u.var_ref->pvalue; + if (unlikely(JS_IsUninitialized(val))) { + JS_ThrowReferenceErrorUninitialized(ctx, prs->atom); + return -1; + } + desc->value = js_dup(val); + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { + /* Instantiate property and retry */ + if (JS_AutoInitProperty(ctx, p, prop, pr, prs)) + return -1; + goto retry; + } + } else { + desc->value = js_dup(pr->u.value); + } + } else { + if (pflags) { + *pflags = prs->flags & JS_PROP_C_W_E; + if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) + *pflags |= JS_PROP_GETSET; + } + /* for consistency, send the exception even if desc is NULL */ + if (unlikely((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF)) { + if (unlikely(JS_IsUninitialized(*pr->u.var_ref->pvalue))) { + JS_ThrowReferenceErrorUninitialized(ctx, prs->atom); + return -1; + } + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { + /* nothing to do: delay instantiation until actual value and/or attributes are read */ + } + } + return true; + } + if (p->is_exotic) { + if (p->fast_array) { + /* specific case for fast arrays */ + if (__JS_AtomIsTaggedInt(prop)) { + uint32_t idx; + idx = __JS_AtomToUInt32(prop); + if (idx < p->u.array.count) { + if (desc) { + desc->flags = JS_PROP_WRITABLE | JS_PROP_ENUMERABLE | + JS_PROP_CONFIGURABLE; + desc->getter = JS_UNDEFINED; + desc->setter = JS_UNDEFINED; + desc->value = JS_GetPropertyUint32(ctx, JS_MKPTR(JS_TAG_OBJECT, p), idx); + } else if (flags_only) { + *pflags = JS_PROP_WRITABLE | JS_PROP_ENUMERABLE | + JS_PROP_CONFIGURABLE; + } + return true; + } + } + } else { + const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; + if (em && em->get_own_property) { + if (flags_only) { + /* Call the exotic handler with a temporary desc, + extract flags, and free the values. For Proxy + objects, the JS trap runs regardless, so the + dup+free overhead is negligible. */ + JSPropertyDescriptor d; + int ret = em->get_own_property(ctx, &d, + JS_MKPTR(JS_TAG_OBJECT, p), prop); + if (ret > 0) { + *pflags = d.flags; + js_free_desc(ctx, &d); + } + return ret; + } + return em->get_own_property(ctx, desc, + JS_MKPTR(JS_TAG_OBJECT, p), prop); + } + } + } + return false; +} + +static int JS_GetOwnPropertyInternal(JSContext *ctx, JSPropertyDescriptor *desc, + JSObject *p, JSAtom prop) +{ + return JS_GetOwnPropertyInternal2(ctx, desc, p, prop, NULL); +} + +/* Same as JS_GetOwnPropertyInternal but only returns flags, not + value/getter/setter. Avoids unnecessary js_dup() for callers that + only need the property flags. */ +static int JS_GetOwnPropertyFlagsInternal(JSContext *ctx, int *pflags, + JSObject *p, JSAtom prop) +{ + return JS_GetOwnPropertyInternal2(ctx, NULL, p, prop, pflags); +} + +int JS_GetOwnProperty(JSContext *ctx, JSPropertyDescriptor *desc, + JSValueConst obj, JSAtom prop) +{ + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) { + JS_ThrowTypeErrorNotAnObject(ctx); + return -1; + } + return JS_GetOwnPropertyInternal(ctx, desc, JS_VALUE_GET_OBJ(obj), prop); +} + +void JS_FreePropertyEnum(JSContext *ctx, JSPropertyEnum *tab, + uint32_t len) +{ + js_free_prop_enum(ctx, tab, len); +} + +/* return -1 if exception (Proxy object only) or true/false */ +int JS_IsExtensible(JSContext *ctx, JSValueConst obj) +{ + JSObject *p; + + if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) + return false; + p = JS_VALUE_GET_OBJ(obj); + if (unlikely(p->class_id == JS_CLASS_PROXY)) + return js_proxy_isExtensible(ctx, obj); + else + return p->extensible; +} + +/* return -1 if exception (Proxy object only) or true/false */ +int JS_PreventExtensions(JSContext *ctx, JSValueConst obj) +{ + JSObject *p; + + if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) + return false; + p = JS_VALUE_GET_OBJ(obj); + if (unlikely(p->class_id == JS_CLASS_PROXY)) + return js_proxy_preventExtensions(ctx, obj); + if (is_typed_array(p->class_id)) { + JSTypedArray *ta = p->u.typed_array; + JSArrayBuffer *abuf = ta->buffer->u.array_buffer; + if (ta->track_rab || + (array_buffer_is_resizable(abuf) && !abuf->shared)) + { + return false; + } + } + p->extensible = false; + return true; +} + +/* return -1 if exception otherwise true or false */ +int JS_HasProperty(JSContext *ctx, JSValueConst obj, JSAtom prop) +{ + JSObject *p; + int ret; + JSValue obj1; + + if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) + return false; + p = JS_VALUE_GET_OBJ(obj); + for(;;) { + if (p->is_exotic) { + const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; + if (em && em->has_property) { + /* has_property can free the prototype */ + obj1 = js_dup(JS_MKPTR(JS_TAG_OBJECT, p)); + ret = em->has_property(ctx, obj1, prop); + JS_FreeValue(ctx, obj1); + return ret; + } + } + /* JS_GetOwnPropertyInternal can free the prototype */ + js_dup(JS_MKPTR(JS_TAG_OBJECT, p)); + ret = JS_GetOwnPropertyInternal(ctx, NULL, p, prop); + JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); + if (ret != 0) + return ret; + if (is_typed_array(p->class_id)) { + ret = JS_AtomIsNumericIndex(ctx, prop); + if (ret != 0) { + if (ret < 0) + return -1; + return false; + } + } + p = p->shape->proto; + if (!p) + break; + } + return false; +} + +/* val must be a symbol */ +static JSAtom js_symbol_to_atom(JSContext *ctx, JSValueConst val) +{ + JSAtomStruct *p = JS_VALUE_GET_PTR(val); + return js_get_atom_index(ctx->rt, p); +} + +/* return JS_ATOM_NULL in case of exception */ +static JSAtom JS_ValueToAtomInternal(JSContext *ctx, JSValueConst val, + int flags) +{ + JSAtom atom; + uint32_t tag; + tag = JS_VALUE_GET_TAG(val); + if (tag == JS_TAG_INT && + (uint32_t)JS_VALUE_GET_INT(val) <= JS_ATOM_MAX_INT) { + /* fast path for integer values */ + atom = __JS_AtomFromUInt32(JS_VALUE_GET_INT(val)); + } else if (tag == JS_TAG_SYMBOL) { + JSAtomStruct *p = JS_VALUE_GET_PTR(val); + atom = JS_DupAtom(ctx, js_get_atom_index(ctx->rt, p)); + } else { + JSValue str; + str = JS_ToPropertyKeyInternal(ctx, val, flags); + if (JS_IsException(str)) + return JS_ATOM_NULL; + if (JS_VALUE_GET_TAG(str) == JS_TAG_SYMBOL) { + atom = js_symbol_to_atom(ctx, str); + } else { + atom = JS_NewAtomStr(ctx, JS_VALUE_GET_STRING(str)); + } + } + return atom; +} + +JSAtom JS_ValueToAtom(JSContext *ctx, JSValueConst val) +{ + return JS_ValueToAtomInternal(ctx, val, /*flags*/0); +} + +static bool js_get_fast_array_element(JSContext *ctx, JSObject *p, + uint32_t idx, JSValue *pval) +{ + switch(p->class_id) { + case JS_CLASS_ARRAY: + case JS_CLASS_ARGUMENTS: + if (unlikely(idx >= p->u.array.count)) return false; + *pval = js_dup(p->u.array.u.values[idx]); + return true; + case JS_CLASS_MAPPED_ARGUMENTS: + if (unlikely(idx >= p->u.array.count)) return false; + *pval = js_dup(*p->u.array.u.var_refs[idx]->pvalue); + return true; + case JS_CLASS_INT8_ARRAY: + if (unlikely(idx >= p->u.array.count)) return false; + *pval = js_int32(p->u.array.u.int8_ptr[idx]); + return true; + case JS_CLASS_UINT8C_ARRAY: + case JS_CLASS_UINT8_ARRAY: + if (unlikely(idx >= p->u.array.count)) return false; + *pval = js_int32(p->u.array.u.uint8_ptr[idx]); + return true; + case JS_CLASS_INT16_ARRAY: + if (unlikely(idx >= p->u.array.count)) return false; + *pval = js_int32(p->u.array.u.int16_ptr[idx]); + return true; + case JS_CLASS_UINT16_ARRAY: + if (unlikely(idx >= p->u.array.count)) return false; + *pval = js_int32(p->u.array.u.uint16_ptr[idx]); + return true; + case JS_CLASS_INT32_ARRAY: + if (unlikely(idx >= p->u.array.count)) return false; + *pval = js_int32(p->u.array.u.int32_ptr[idx]); + return true; + case JS_CLASS_UINT32_ARRAY: + if (unlikely(idx >= p->u.array.count)) return false; + *pval = js_uint32(p->u.array.u.uint32_ptr[idx]); + return true; + case JS_CLASS_BIG_INT64_ARRAY: + if (unlikely(idx >= p->u.array.count)) return false; + *pval = JS_NewBigInt64(ctx, p->u.array.u.int64_ptr[idx]); + return true; + case JS_CLASS_BIG_UINT64_ARRAY: + if (unlikely(idx >= p->u.array.count)) return false; + *pval = JS_NewBigUint64(ctx, p->u.array.u.uint64_ptr[idx]); + return true; + case JS_CLASS_FLOAT16_ARRAY: + if (unlikely(idx >= p->u.array.count)) return false; + *pval = js_float64(fromfp16(p->u.array.u.fp16_ptr[idx])); + return true; + case JS_CLASS_FLOAT32_ARRAY: + if (unlikely(idx >= p->u.array.count)) return false; + *pval = js_float64(p->u.array.u.float_ptr[idx]); + return true; + case JS_CLASS_FLOAT64_ARRAY: + if (unlikely(idx >= p->u.array.count)) return false; + *pval = js_float64(p->u.array.u.double_ptr[idx]); + return true; + default: + return false; + } +} + +JSValue JS_GetPropertyValue(JSContext *ctx, JSValueConst this_obj, + JSValue prop) +{ + JSAtom atom; + JSValue ret; + uint32_t tag; + + tag = JS_VALUE_GET_TAG(this_obj); + if (likely(tag == JS_TAG_OBJECT)) { + if (JS_VALUE_GET_TAG(prop) == JS_TAG_INT) { + JSObject *p = JS_VALUE_GET_OBJ(this_obj); + uint32_t idx = JS_VALUE_GET_INT(prop); + JSValue val; + /* fast path for array and typed array access */ + if (js_get_fast_array_element(ctx, p, idx, &val)) + return val; + } + } else if (unlikely(tag == JS_TAG_NULL || tag == JS_TAG_UNDEFINED)) { + // per spec: not allowed to call ToPropertyKey before ToObject + // so we must ensure to not invoke JS anything that's observable + // from JS code + atom = JS_ValueToAtomInternal(ctx, prop, JS_TO_STRING_NO_SIDE_EFFECTS); + JS_FreeValue(ctx, prop); + if (unlikely(atom == JS_ATOM_NULL)) + return JS_EXCEPTION; + if (tag == JS_TAG_NULL) { + JS_ThrowTypeErrorAtom(ctx, "cannot read property '%s' of null", atom); + } else { + JS_ThrowTypeErrorAtom(ctx, "cannot read property '%s' of undefined", atom); + } + JS_FreeAtom(ctx, atom); + return JS_EXCEPTION; + } + atom = JS_ValueToAtom(ctx, prop); + JS_FreeValue(ctx, prop); + if (unlikely(atom == JS_ATOM_NULL)) + return JS_EXCEPTION; + ret = JS_GetProperty(ctx, this_obj, atom); + JS_FreeAtom(ctx, atom); + return ret; +} + +JSValue JS_GetPropertyUint32(JSContext *ctx, JSValueConst this_obj, + uint32_t idx) +{ + return JS_GetPropertyInt64(ctx, this_obj, idx); +} + +/* Check if an object has a generalized numeric property. Return value: + -1 for exception, *pval set to JS_EXCEPTION + true if property exists, stored into *pval, + false if property does not exist. *pval set to JS_UNDEFINED. + */ +static int JS_TryGetPropertyInt64(JSContext *ctx, JSValueConst obj, int64_t idx, JSValue *pval) +{ + JSValue val; + JSAtom prop; + int present; + + if (likely(JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT && + (uint64_t)idx <= INT32_MAX)) { + /* fast path for array and typed array access */ + JSObject *p = JS_VALUE_GET_OBJ(obj); + if (js_get_fast_array_element(ctx, p, idx, pval)) + return true; + } + val = JS_EXCEPTION; + present = -1; + prop = JS_NewAtomInt64(ctx, idx); + if (likely(prop != JS_ATOM_NULL)) { + present = JS_HasProperty(ctx, obj, prop); + if (present > 0) { + val = JS_GetProperty(ctx, obj, prop); + if (unlikely(JS_IsException(val))) + present = -1; + } else if (present == false) { + val = JS_UNDEFINED; + } + JS_FreeAtom(ctx, prop); + } + *pval = val; + return present; +} + +JSValue JS_GetPropertyInt64(JSContext *ctx, JSValueConst obj, int64_t idx) +{ + JSAtom prop; + JSValue val; + + if (likely(JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT && + (uint64_t)idx <= INT32_MAX)) { + /* fast path for array and typed array access */ + JSObject *p = JS_VALUE_GET_OBJ(obj); + if (js_get_fast_array_element(ctx, p, idx, &val)) + return val; + } + prop = JS_NewAtomInt64(ctx, idx); + if (prop == JS_ATOM_NULL) + return JS_EXCEPTION; + + val = JS_GetProperty(ctx, obj, prop); + JS_FreeAtom(ctx, prop); + return val; +} + +/* `prop` may be pure ASCII or UTF-8 encoded */ +JSValue JS_GetPropertyStr(JSContext *ctx, JSValueConst this_obj, + const char *prop) +{ + JSAtom atom; + JSValue ret; + atom = JS_NewAtom(ctx, prop); + if (atom == JS_ATOM_NULL) + return JS_EXCEPTION; + ret = JS_GetProperty(ctx, this_obj, atom); + JS_FreeAtom(ctx, atom); + return ret; +} + +/* Note: the property value is not initialized. Return NULL if memory + error. */ +static JSProperty *add_property(JSContext *ctx, + JSObject *p, JSAtom prop, int prop_flags) +{ + JSShape *sh, *new_sh; + + if (unlikely(p->is_prototype)) { + /* track addition of small integer properties to + Array.prototype and Object.prototype */ + if (unlikely((p == JS_VALUE_GET_OBJ(ctx->class_proto[JS_CLASS_ARRAY]) || + p == JS_VALUE_GET_OBJ(ctx->class_proto[JS_CLASS_OBJECT])) && + __JS_AtomIsTaggedInt(prop))) { + ctx->std_array_prototype = false; + } + } + sh = p->shape; + if (sh->is_hashed) { + /* try to find an existing shape */ + new_sh = find_hashed_shape_prop(ctx->rt, sh, prop, prop_flags); + if (new_sh) { + /* matching shape found: use it */ + /* the property array may need to be resized */ + if (new_sh->prop_size != sh->prop_size) { + JSProperty *new_prop; + new_prop = js_realloc(ctx, p->prop, sizeof(p->prop[0]) * + new_sh->prop_size); + if (!new_prop) + return NULL; + p->prop = new_prop; + } + p->shape = js_dup_shape(new_sh); + js_free_shape(ctx->rt, sh); + return &p->prop[new_sh->prop_count - 1]; + } else if (JS_REF_COUNT(sh) != 1) { + /* if the shape is shared, clone it */ + new_sh = js_clone_shape(ctx, sh); + if (!new_sh) + return NULL; + /* hash the cloned shape */ + new_sh->is_hashed = true; + js_shape_hash_link(ctx->rt, new_sh); + js_free_shape(ctx->rt, p->shape); + p->shape = new_sh; + } + } + assert(JS_REF_COUNT(p->shape) == 1); + if (add_shape_property(ctx, &p->shape, p, prop, prop_flags)) + return NULL; + return &p->prop[p->shape->prop_count - 1]; +} + +/* can be called on JS_CLASS_ARRAY, JS_CLASS_ARGUMENTS or + JS_CLASS_MAPPED_ARGUMENTS objects. return < 0 if memory alloc + error. */ +static no_inline __exception int convert_fast_array_to_array(JSContext *ctx, + JSObject *p) +{ + JSProperty *pr; + JSShape *sh; + uint32_t i, len, new_count; + + /* track modification of Array.prototype */ + if (unlikely(p == JS_VALUE_GET_OBJ(ctx->class_proto[JS_CLASS_ARRAY]))) { + ctx->std_array_prototype = false; + } + if (js_shape_prepare_update(ctx, p, NULL)) + return -1; + len = p->u.array.count; + /* resize the properties once to simplify the error handling */ + sh = p->shape; + new_count = sh->prop_count + len; + if (new_count > sh->prop_size) { + if (resize_properties(ctx, &p->shape, p, new_count)) + return -1; + } + + if (p->class_id == JS_CLASS_MAPPED_ARGUMENTS) { + JSVarRef **tab = p->u.array.u.var_refs; + for(i = 0; i < len; i++) { + /* add_property cannot fail here but + __JS_AtomFromUInt32(i) fails for i > INT32_MAX */ + pr = add_property(ctx, p, __JS_AtomFromUInt32(i), JS_PROP_C_W_E | JS_PROP_VARREF); + pr->u.var_ref = *tab++; + } + } else { + JSValue *tab = p->u.array.u.values; + for(i = 0; i < len; i++) { + /* add_property cannot fail here but + __JS_AtomFromUInt32(i) fails for i > INT32_MAX */ + pr = add_property(ctx, p, __JS_AtomFromUInt32(i), JS_PROP_C_W_E); + pr->u.value = *tab++; + } + } + js_free(ctx, p->u.array.u.values); + p->u.array.count = 0; + p->u.array.u.values = NULL; /* fail safe */ + p->u.array.u1.size = 0; + p->fast_array = 0; + return 0; +} + +static int delete_property(JSContext *ctx, JSObject *p, JSAtom atom) +{ + JSShape *sh; + JSShapeProperty *pr, *lpr, *prop; + JSProperty *pr1; + uint32_t lpr_idx; + intptr_t h, h1; + + redo: + sh = p->shape; + h1 = hash_atom(atom, sh->prop_hash_mask); + h = prop_hash_end(sh)[-h1 - 1]; + prop = get_shape_prop(sh); + lpr = NULL; + lpr_idx = 0; /* prevent warning */ + while (h != 0) { + pr = &prop[h - 1]; + if (likely(pr->atom == atom)) { + /* found ! */ + if (!(pr->flags & JS_PROP_CONFIGURABLE)) + return false; + /* realloc the shape if needed */ + if (lpr) + lpr_idx = lpr - get_shape_prop(sh); + if (js_shape_prepare_update(ctx, p, &pr)) + return -1; + sh = p->shape; + /* remove property */ + if (lpr) { + lpr = &get_shape_prop(sh)[lpr_idx]; + lpr->hash_next = pr->hash_next; + } else { + prop_hash_end(sh)[-h1 - 1] = pr->hash_next; + } + sh->deleted_prop_count++; + /* free the entry */ + pr1 = &p->prop[h - 1]; + free_property(ctx->rt, pr1, pr->flags); + JS_FreeAtom(ctx, pr->atom); + /* put default values */ + pr->flags = 0; + pr->atom = JS_ATOM_NULL; + pr1->u.value = JS_UNDEFINED; + + /* compact the properties if too many deleted properties */ + if (sh->deleted_prop_count >= 8 && + sh->deleted_prop_count >= ((unsigned)sh->prop_count / 2)) { + compact_properties(ctx, p); + } + return true; + } + lpr = pr; + h = pr->hash_next; + } + + if (p->is_exotic) { + if (p->fast_array) { + uint32_t idx; + if (JS_AtomIsArrayIndex(ctx, &idx, atom) && + idx < p->u.array.count) { + if (p->class_id == JS_CLASS_ARRAY || + p->class_id == JS_CLASS_ARGUMENTS || + p->class_id == JS_CLASS_MAPPED_ARGUMENTS) { + if (convert_fast_array_to_array(ctx, p)) + return -1; + goto redo; + } else { + return false; + } + } + } else { + const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; + if (em && em->delete_property) { + return em->delete_property(ctx, JS_MKPTR(JS_TAG_OBJECT, p), atom); + } + } + } + /* not found */ + return true; +} + +static int call_setter(JSContext *ctx, JSObject *setter, + JSValueConst this_obj, JSValue val, int flags) +{ + JSValue ret, func; + if (likely(setter)) { + func = JS_MKPTR(JS_TAG_OBJECT, setter); + /* Note: the field could be removed in the setter */ + func = js_dup(func); + ret = JS_CallFree(ctx, func, this_obj, 1, vc(&val)); + JS_FreeValue(ctx, val); + if (JS_IsException(ret)) + return -1; + JS_FreeValue(ctx, ret); + return true; + } else { + JS_FreeValue(ctx, val); + if ((flags & JS_PROP_THROW) || + ((flags & JS_PROP_THROW_STRICT) && is_strict_mode(ctx))) { + JS_ThrowTypeError(ctx, "no setter for property"); + return -1; + } + return false; + } +} + +/* set the array length and remove the array elements if necessary. */ +static int set_array_length(JSContext *ctx, JSObject *p, JSValue val, + int flags) +{ + uint32_t len, idx, cur_len; + int i, ret; + + /* Note: this call can reallocate the properties of 'p' */ + ret = JS_ToArrayLengthFree(ctx, &len, val, false); + if (ret) + return -1; + /* JS_ToArrayLengthFree() must be done before the read-only test */ + if (unlikely(!(get_shape_prop(p->shape)[0].flags & JS_PROP_WRITABLE))) + return JS_ThrowTypeErrorReadOnly(ctx, flags, JS_ATOM_length); + + if (likely(p->fast_array)) { + uint32_t old_len = p->u.array.count; + if (len < old_len) { + for(i = len; i < old_len; i++) { + JS_FreeValue(ctx, p->u.array.u.values[i]); + p->u.array.u.values[i] = JS_UNDEFINED; + } + p->u.array.count = len; + } + p->prop[0].u.value = js_uint32(len); + } else { + /* Note: length is always a uint32 because the object is an + array */ + JS_ToUint32(ctx, &cur_len, p->prop[0].u.value); + if (len < cur_len) { + uint32_t d; + JSShape *sh; + JSShapeProperty *pr; + + d = cur_len - len; + sh = p->shape; + if (d <= sh->prop_count) { + JSAtom atom; + + /* faster to iterate */ + while (cur_len > len) { + atom = JS_NewAtomUInt32(ctx, cur_len - 1); + ret = delete_property(ctx, p, atom); + JS_FreeAtom(ctx, atom); + if (unlikely(!ret)) { + /* unlikely case: property is not + configurable */ + break; + } + cur_len--; + } + } else { + /* faster to iterate thru all the properties. Need two + passes in case one of the property is not + configurable */ + cur_len = len; + for(i = 0, pr = get_shape_prop(sh); i < sh->prop_count; + i++, pr++) { + if (pr->atom != JS_ATOM_NULL && + JS_AtomIsArrayIndex(ctx, &idx, pr->atom)) { + if (idx >= cur_len && + !(pr->flags & JS_PROP_CONFIGURABLE)) { + cur_len = idx + 1; + } + } + } + + for(i = 0, pr = get_shape_prop(sh); i < sh->prop_count; + i++, pr++) { + if (pr->atom != JS_ATOM_NULL && + JS_AtomIsArrayIndex(ctx, &idx, pr->atom)) { + if (idx >= cur_len) { + /* remove the property */ + delete_property(ctx, p, pr->atom); + /* WARNING: the shape may have been modified */ + sh = p->shape; + pr = &get_shape_prop(sh)[i]; + } + } + } + } + } else { + cur_len = len; + } + set_value(ctx, &p->prop[0].u.value, js_uint32(cur_len)); + if (unlikely(cur_len > len)) { + return JS_ThrowTypeErrorOrFalse(ctx, flags, "not configurable"); + } + } + return true; +} + +/* return -1 if exception */ +static int expand_fast_array(JSContext *ctx, JSObject *p, uint32_t new_len) +{ + uint32_t old_size, new_size; + JSValue *new_array_prop; + + if (unlikely(new_len > (uint32_t)INT32_MAX)) { + JS_ThrowOutOfMemory(ctx); + return -1; + } + + old_size = p->u.array.u1.size; + new_size = old_size + old_size/2; + if (new_size < old_size) { + JS_ThrowOutOfMemory(ctx); + return -1; + } + new_size = max_uint32(new_len, new_size); + new_array_prop = js_realloc(ctx, p->u.array.u.values, sizeof(JSValue) * new_size); + if (!new_array_prop) + return -1; + p->u.array.u.values = new_array_prop; + p->u.array.u1.size = new_size; + return 0; +} + +/* Preconditions: 'p' must be of class JS_CLASS_ARRAY, p->fast_array = + true and p->extensible = true */ +static int add_fast_array_element(JSContext *ctx, JSObject *p, + JSValue val, int flags) +{ + uint32_t new_len, array_len; + /* extend the array by one */ + /* XXX: convert to slow array if new_len > 2^31-1 elements */ + new_len = p->u.array.count + 1; + /* update the length if necessary. We assume that if the length is + not an integer, then if it >= 2^31. */ + if (likely(JS_VALUE_GET_TAG(p->prop[0].u.value) == JS_TAG_INT)) { + array_len = JS_VALUE_GET_INT(p->prop[0].u.value); + if (new_len > array_len) { + if (unlikely(!(get_shape_prop(p->shape)->flags & JS_PROP_WRITABLE))) { + JS_FreeValue(ctx, val); + return JS_ThrowTypeErrorReadOnly(ctx, flags, JS_ATOM_length); + } + p->prop[0].u.value = js_int32(new_len); + } + } + if (unlikely(new_len > p->u.array.u1.size)) { + if (expand_fast_array(ctx, p, new_len)) { + JS_FreeValue(ctx, val); + return -1; + } + } + p->u.array.u.values[new_len - 1] = val; + p->u.array.count = new_len; + return true; +} + +/* Allocate a new fast array initialized to JS_UNDEFINED. Its maximum + size is 2^31-1 elements. For convenience, 'len' is a 64 bit + integer. */ +static JSValue js_allocate_fast_array(JSContext *ctx, int64_t len) +{ + JSValue arr; + JSObject *p; + int i; + + if (len > INT32_MAX) + return JS_ThrowRangeError(ctx, "invalid array length"); + arr = JS_NewArray(ctx); + if (JS_IsException(arr)) + return arr; + if (len > 0) { + p = JS_VALUE_GET_OBJ(arr); + if (expand_fast_array(ctx, p, len) < 0) { + JS_FreeValue(ctx, arr); + return JS_EXCEPTION; + } + p->u.array.count = len; + for(i = 0; i < len; i++) + p->u.array.u.values[i] = JS_UNDEFINED; + /* update the 'length' field */ + set_value(ctx, &p->prop[0].u.value, js_int32(len)); + } + return arr; +} + +static void js_free_desc(JSContext *ctx, JSPropertyDescriptor *desc) +{ + JS_FreeValue(ctx, desc->getter); + JS_FreeValue(ctx, desc->setter); + JS_FreeValue(ctx, desc->value); +} + +/* return -1 in case of exception or true or false. Warning: 'val' is + freed by the function. 'flags' is a bitmask of JS_PROP_NO_ADD, + JS_PROP_THROW or JS_PROP_THROW_STRICT. If JS_PROP_NO_ADD is set, + the new property is not added and an error is raised. + 'obj' must be an object when obj != this_obj. + */ +static int JS_SetPropertyInternal2(JSContext *ctx, JSValueConst obj, JSAtom prop, + JSValue val, JSValueConst this_obj, int flags) +{ + JSObject *p, *p1; + JSShapeProperty *prs; + JSProperty *pr; + JSPropertyDescriptor desc; + int desc_flags; + int ret; + + switch(JS_VALUE_GET_TAG(this_obj)) { + case JS_TAG_NULL: + JS_ThrowTypeErrorAtom(ctx, "cannot set property '%s' of null", prop); + goto fail; + case JS_TAG_UNDEFINED: + JS_ThrowTypeErrorAtom(ctx, "cannot set property '%s' of undefined", prop); + goto fail; + case JS_TAG_OBJECT: + p = JS_VALUE_GET_OBJ(this_obj); + p1 = JS_VALUE_GET_OBJ(obj); + if (p == p1) + break; + goto retry2; + default: + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) + obj = JS_GetPrototypePrimitive(ctx, obj); + p = NULL; + p1 = JS_VALUE_GET_OBJ(obj); + goto prototype_lookup; + } + +retry: + prs = find_own_property(&pr, p1, prop); + if (prs) { + if (likely((prs->flags & (JS_PROP_TMASK | JS_PROP_WRITABLE | + JS_PROP_LENGTH)) == JS_PROP_WRITABLE)) { + /* fast case */ + set_value(ctx, &pr->u.value, val); + return true; + } else if (prs->flags & JS_PROP_LENGTH) { + assert(p->class_id == JS_CLASS_ARRAY); + assert(prop == JS_ATOM_length); + return set_array_length(ctx, p, val, flags); + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { + return call_setter(ctx, pr->u.getset.setter, this_obj, val, flags); + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { + /* JS_PROP_WRITABLE is always true for variable + references, but they are write protected in module name + spaces. */ + if (p->class_id == JS_CLASS_MODULE_NS) + goto read_only_prop; + set_value(ctx, pr->u.var_ref->pvalue, val); + return true; + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { + /* Instantiate property and retry (potentially useless) */ + if (JS_AutoInitProperty(ctx, p, prop, pr, prs)) + goto fail; + goto retry; + } else { + goto read_only_prop; + } + } + + for(;;) { + if (p1->is_exotic) { + if (p1->fast_array) { + if (__JS_AtomIsTaggedInt(prop)) { + uint32_t idx = __JS_AtomToUInt32(prop); + if (idx < p1->u.array.count) { + if (unlikely(p == p1)) + return JS_SetPropertyValue(ctx, this_obj, js_int32(idx), val, flags); + else + break; + } else if (is_typed_array(p1->class_id)) { + goto typed_array_oob; + } + } else if (is_typed_array(p1->class_id)) { + ret = JS_AtomIsNumericIndex(ctx, prop); + if (ret != 0) { + if (ret < 0) + goto fail; + typed_array_oob: + /* [[Set]] (10.4.5.5): an out-of-bounds or non-canonical + integer index only coerces the value (step i, via + TypedArraySetElement) when the receiver is the typed + array itself. With any other receiver the invalid + index is dropped (step ii) without evaluating the + value. p is the receiver object (NULL for a primitive + receiver), p1 is the typed array. */ + if (p == p1) { + // evaluate value for side effects + if (p1->class_id == JS_CLASS_BIG_INT64_ARRAY || + p1->class_id == JS_CLASS_BIG_UINT64_ARRAY) { + int64_t v; + if (JS_ToBigInt64Free(ctx, &v, val)) + return -1; + } else { + val = JS_ToNumberFree(ctx, val); + JS_FreeValue(ctx, val); + if (JS_IsException(val)) + return -1; + } + } else { + JS_FreeValue(ctx, val); + } + return true; + } + } + } else { + const JSClassExoticMethods *em = ctx->rt->class_array[p1->class_id].exotic; + if (em) { + JSValue obj1; + if (em->set_property) { + /* set_property can free the prototype */ + obj1 = js_dup(JS_MKPTR(JS_TAG_OBJECT, p1)); + ret = em->set_property(ctx, obj1, prop, + val, this_obj, flags); + JS_FreeValue(ctx, obj1); + JS_FreeValue(ctx, val); + return ret; + } + if (em->get_own_property) { + /* get_own_property can free the prototype */ + obj1 = js_dup(JS_MKPTR(JS_TAG_OBJECT, p1)); + ret = em->get_own_property(ctx, &desc, + obj1, prop); + JS_FreeValue(ctx, obj1); + if (ret < 0) + goto fail; + if (ret) { + if (desc.flags & JS_PROP_GETSET) { + JSObject *setter; + if (JS_IsUndefined(desc.setter)) + setter = NULL; + else + setter = JS_VALUE_GET_OBJ(desc.setter); + ret = call_setter(ctx, setter, this_obj, val, flags); + JS_FreeValue(ctx, desc.getter); + JS_FreeValue(ctx, desc.setter); + return ret; + } else { + JS_FreeValue(ctx, desc.value); + if (!(desc.flags & JS_PROP_WRITABLE)) + goto read_only_prop; + if (likely(p == p1)) { + ret = JS_DefineProperty(ctx, this_obj, prop, val, + JS_UNDEFINED, JS_UNDEFINED, + JS_PROP_HAS_VALUE); + JS_FreeValue(ctx, val); + return ret; + } else { + break; + } + } + } + } + } + } + } + p1 = p1->shape->proto; + prototype_lookup: + if (!p1) + break; + + retry2: + prs = find_own_property(&pr, p1, prop); + if (prs) { + if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { + return call_setter(ctx, pr->u.getset.setter, this_obj, val, flags); + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { + /* Instantiate property and retry (potentially useless) */ + if (JS_AutoInitProperty(ctx, p1, prop, pr, prs)) + return -1; + goto retry2; + } else if (!(prs->flags & JS_PROP_WRITABLE)) { + goto read_only_prop; + } else { + break; + } + } + } + + if (unlikely(flags & JS_PROP_NO_ADD)) { + JS_ThrowReferenceErrorNotDefined(ctx, prop); + goto fail; + } + + if (unlikely(!p)) { + ret = JS_ThrowTypeErrorOrFalse(ctx, flags, "not an object"); + goto done; + } + + if (p == JS_VALUE_GET_OBJ(obj)) { + if (unlikely(!p->extensible)) { + ret = JS_ThrowTypeErrorOrFalse(ctx, flags, "object is not extensible"); + goto done; + } + if (p->is_exotic) { + if (p->class_id == JS_CLASS_ARRAY && p->fast_array && + __JS_AtomIsTaggedInt(prop)) { + uint32_t idx = __JS_AtomToUInt32(prop); + if (idx == p->u.array.count) { + /* fast case */ + return add_fast_array_element(ctx, p, val, flags); + } + } + goto generic_create_prop; + } else { + pr = add_property(ctx, p, prop, JS_PROP_C_W_E); + if (!pr) + goto fail; + pr->u.value = val; + return true; + } + } + + // TODO(bnoordhuis) return JSProperty slot and update in place + // when plain property (not is_exotic/setter/etc.) to avoid + // calling find_own_property() thrice? + ret = JS_GetOwnPropertyFlagsInternal(ctx, &desc_flags, p, prop); + if (ret < 0) + goto fail; + + if (ret) { + if (desc_flags & JS_PROP_GETSET) { + ret = JS_ThrowTypeErrorOrFalse(ctx, flags, "setter is forbidden"); + goto done; + } else if (!(desc_flags & JS_PROP_WRITABLE) || + p->class_id == JS_CLASS_MODULE_NS) { + read_only_prop: + ret = JS_ThrowTypeErrorReadOnly(ctx, flags, prop); + goto done; + } + ret = JS_DefineProperty(ctx, this_obj, prop, val, + JS_UNDEFINED, JS_UNDEFINED, + JS_PROP_HAS_VALUE); + } else { + if (unlikely(!p->extensible)) { + ret = JS_ThrowTypeErrorOrFalse(ctx, flags, "object is not extensible"); + goto done; + } + generic_create_prop: + ret = JS_CreateProperty(ctx, p, prop, val, JS_UNDEFINED, JS_UNDEFINED, + flags | + JS_PROP_HAS_VALUE | + JS_PROP_HAS_ENUMERABLE | + JS_PROP_HAS_WRITABLE | + JS_PROP_HAS_CONFIGURABLE | + JS_PROP_C_W_E); + } + +done: + JS_FreeValue(ctx, val); + return ret; +fail: + JS_FreeValue(ctx, val); + return -1; +} + +static int JS_SetPropertyInternal(JSContext *ctx, JSValueConst obj, JSAtom prop, + JSValue val, int flags) +{ + return JS_SetPropertyInternal2(ctx, obj, prop, val, obj, flags); +} + +int JS_SetProperty(JSContext *ctx, JSValueConst this_obj, JSAtom prop, JSValue val) +{ + return JS_SetPropertyInternal(ctx, this_obj, prop, val, JS_PROP_THROW); +} + +/* flags can be JS_PROP_THROW or JS_PROP_THROW_STRICT */ +int JS_SetPropertyValue(JSContext *ctx, JSValueConst this_obj, + JSValue prop, JSValue val, int flags) +{ + if (likely(JS_VALUE_GET_TAG(this_obj) == JS_TAG_OBJECT && + JS_VALUE_GET_TAG(prop) == JS_TAG_INT)) { + JSObject *p; + uint32_t idx; + double d; + int32_t v; + + /* fast path for array access */ + p = JS_VALUE_GET_OBJ(this_obj); + idx = JS_VALUE_GET_INT(prop); + switch(p->class_id) { + case JS_CLASS_ARRAY: + if (unlikely(idx >= (uint32_t)p->u.array.count)) { + /* fast path to add an element to the array */ + if (unlikely(idx != (uint32_t)p->u.array.count || + !p->fast_array || + !p->extensible || + p->shape->proto != JS_VALUE_GET_OBJ(ctx->class_proto[JS_CLASS_ARRAY]) || + !ctx->std_array_prototype)) { + goto slow_path; + } + /* add element */ + return add_fast_array_element(ctx, p, val, flags); + } + set_value(ctx, &p->u.array.u.values[idx], val); + break; + case JS_CLASS_ARGUMENTS: + if (unlikely(idx >= (uint32_t)p->u.array.count)) + goto slow_path; + set_value(ctx, &p->u.array.u.values[idx], val); + break; + case JS_CLASS_MAPPED_ARGUMENTS: + if (unlikely(idx >= (uint32_t)p->u.array.count)) + goto slow_path; + set_value(ctx, p->u.array.u.var_refs[idx]->pvalue, val); + break; + case JS_CLASS_UINT8C_ARRAY: + if (JS_ToUint8ClampFree(ctx, &v, val)) + goto ta_cvt_fail; + if (typed_array_is_immutable(p)) + goto ta_immutable; + /* Note: the conversion can detach the typed array, so the + array bound check must be done after */ + if (unlikely(idx >= (uint32_t)p->u.array.count)) + goto ta_out_of_bound; + p->u.array.u.uint8_ptr[idx] = v; + break; + case JS_CLASS_INT8_ARRAY: + case JS_CLASS_UINT8_ARRAY: + if (JS_ToInt32Free(ctx, &v, val)) + goto ta_cvt_fail; + if (typed_array_is_immutable(p)) + goto ta_immutable; + if (unlikely(idx >= (uint32_t)p->u.array.count)) + goto ta_out_of_bound; + p->u.array.u.uint8_ptr[idx] = v; + break; + case JS_CLASS_INT16_ARRAY: + case JS_CLASS_UINT16_ARRAY: + if (JS_ToInt32Free(ctx, &v, val)) + goto ta_cvt_fail; + if (typed_array_is_immutable(p)) + goto ta_immutable; + if (unlikely(idx >= (uint32_t)p->u.array.count)) + goto ta_out_of_bound; + p->u.array.u.uint16_ptr[idx] = v; + break; + case JS_CLASS_INT32_ARRAY: + case JS_CLASS_UINT32_ARRAY: + if (JS_ToInt32Free(ctx, &v, val)) + goto ta_cvt_fail; + if (typed_array_is_immutable(p)) + goto ta_immutable; + if (unlikely(idx >= (uint32_t)p->u.array.count)) + goto ta_out_of_bound; + p->u.array.u.uint32_ptr[idx] = v; + break; + case JS_CLASS_BIG_INT64_ARRAY: + case JS_CLASS_BIG_UINT64_ARRAY: + /* XXX: need specific conversion function */ + { + int64_t v; + if (JS_ToBigInt64Free(ctx, &v, val)) + goto ta_cvt_fail; + if (typed_array_is_immutable(p)) + goto ta_immutable; + if (unlikely(idx >= (uint32_t)p->u.array.count)) + goto ta_out_of_bound; + p->u.array.u.uint64_ptr[idx] = v; + } + break; + case JS_CLASS_FLOAT16_ARRAY: + if (JS_ToFloat64Free(ctx, &d, val)) + goto ta_cvt_fail; + if (typed_array_is_immutable(p)) + goto ta_immutable; + if (unlikely(idx >= (uint32_t)p->u.array.count)) + goto ta_out_of_bound; + p->u.array.u.fp16_ptr[idx] = tofp16(d); + break; + case JS_CLASS_FLOAT32_ARRAY: + if (JS_ToFloat64Free(ctx, &d, val)) + goto ta_cvt_fail; + if (typed_array_is_immutable(p)) + goto ta_immutable; + if (unlikely(idx >= (uint32_t)p->u.array.count)) + goto ta_out_of_bound; + p->u.array.u.float_ptr[idx] = d; + break; + case JS_CLASS_FLOAT64_ARRAY: + if (JS_ToFloat64Free(ctx, &d, val)) { + ta_cvt_fail: + if (flags & JS_PROP_REFLECT_DEFINE_PROPERTY) { + JS_FreeValue(ctx, JS_GetException(ctx)); + return false; + } + return -1; + } + if (typed_array_is_immutable(p)) { + ta_immutable: + return false; + } + if (unlikely(idx >= (uint32_t)p->u.array.count)) { + ta_out_of_bound: + if (typed_array_is_oob(p)) + if (flags & JS_PROP_DEFINE_PROPERTY) + return JS_ThrowTypeErrorOrFalse(ctx, flags, "out-of-bound numeric index"); + return true; // per spec: no OOB exception + } + p->u.array.u.double_ptr[idx] = d; + break; + default: + goto slow_path; + } + return true; + } else { + JSAtom atom; + int ret; + slow_path: + atom = JS_ValueToAtom(ctx, prop); + JS_FreeValue(ctx, prop); + if (unlikely(atom == JS_ATOM_NULL)) { + JS_FreeValue(ctx, val); + return -1; + } + ret = JS_SetPropertyInternal(ctx, this_obj, atom, val, flags); + JS_FreeAtom(ctx, atom); + return ret; + } +} + +int JS_SetPropertyUint32(JSContext *ctx, JSValueConst this_obj, + uint32_t idx, JSValue val) +{ + return JS_SetPropertyValue(ctx, this_obj, js_uint32(idx), val, + JS_PROP_THROW); +} + +int JS_SetPropertyInt64(JSContext *ctx, JSValueConst this_obj, + int64_t idx, JSValue val) +{ + JSAtom prop; + int res; + + if ((uint64_t)idx <= INT32_MAX) { + /* fast path for fast arrays */ + return JS_SetPropertyValue(ctx, this_obj, js_int32(idx), val, + JS_PROP_THROW); + } + prop = JS_NewAtomInt64(ctx, idx); + if (prop == JS_ATOM_NULL) { + JS_FreeValue(ctx, val); + return -1; + } + res = JS_SetProperty(ctx, this_obj, prop, val); + JS_FreeAtom(ctx, prop); + return res; +} + +/* `prop` may be pure ASCII or UTF-8 encoded */ +int JS_SetPropertyStr(JSContext *ctx, JSValueConst this_obj, + const char *prop, JSValue val) +{ + JSAtom atom; + int ret; + atom = JS_NewAtom(ctx, prop); + if (atom == JS_ATOM_NULL) { + JS_FreeValue(ctx, val); + return -1; + } + ret = JS_SetPropertyInternal(ctx, this_obj, atom, val, JS_PROP_THROW); + JS_FreeAtom(ctx, atom); + return ret; +} + +/* compute the property flags. For each flag: (JS_PROP_HAS_x forces + it, otherwise def_flags is used) + Note: makes assumption about the bit pattern of the flags +*/ +static int get_prop_flags(int flags, int def_flags) +{ + int mask; + mask = (flags >> JS_PROP_HAS_SHIFT) & JS_PROP_C_W_E; + return (flags & mask) | (def_flags & ~mask); +} + +static int JS_CreateProperty(JSContext *ctx, JSObject *p, + JSAtom prop, JSValueConst val, + JSValueConst getter, JSValueConst setter, + int flags) +{ + JSProperty *pr; + int ret, prop_flags; + + /* add a new property or modify an existing exotic one */ + if (p->is_exotic) { + if (p->class_id == JS_CLASS_ARRAY) { + uint32_t idx, len; + + if (p->fast_array) { + if (__JS_AtomIsTaggedInt(prop)) { + idx = __JS_AtomToUInt32(prop); + if (idx == p->u.array.count) { + if (!p->extensible) + goto not_extensible; + if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) + goto convert_to_array; + prop_flags = get_prop_flags(flags, 0); + if (prop_flags != JS_PROP_C_W_E) + goto convert_to_array; + return add_fast_array_element(ctx, p, + js_dup(val), flags); + } else { + goto convert_to_array; + } + } else if (JS_AtomIsArrayIndex(ctx, &idx, prop)) { + /* convert the fast array to normal array */ + convert_to_array: + if (convert_fast_array_to_array(ctx, p)) + return -1; + goto generic_array; + } + } else if (JS_AtomIsArrayIndex(ctx, &idx, prop)) { + JSProperty *plen; + JSShapeProperty *pslen; + generic_array: + /* update the length field */ + plen = &p->prop[0]; + JS_ToUint32(ctx, &len, plen->u.value); + if ((idx + 1) > len) { + pslen = get_shape_prop(p->shape); + if (unlikely(!(pslen->flags & JS_PROP_WRITABLE))) + return JS_ThrowTypeErrorReadOnly(ctx, flags, JS_ATOM_length); + /* XXX: should update the length after defining + the property */ + len = idx + 1; + set_value(ctx, &plen->u.value, js_uint32(len)); + } + } + } else if (is_typed_array(p->class_id)) { + ret = JS_AtomIsNumericIndex(ctx, prop); + if (ret != 0) { + if (ret < 0) + return -1; + return JS_ThrowTypeErrorOrFalse(ctx, flags, "cannot create numeric index in typed array"); + } + } else if (!(flags & JS_PROP_NO_EXOTIC)) { + const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; + if (em) { + if (em->define_own_property) { + return em->define_own_property(ctx, JS_MKPTR(JS_TAG_OBJECT, p), + prop, val, getter, setter, flags); + } + ret = JS_IsExtensible(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); + if (ret < 0) + return -1; + if (!ret) + goto not_extensible; + } + } + } + + if (!p->extensible) { + not_extensible: + return JS_ThrowTypeErrorOrFalse(ctx, flags, "object is not extensible"); + } + + if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) { + prop_flags = (flags & (JS_PROP_CONFIGURABLE | JS_PROP_ENUMERABLE)) | + JS_PROP_GETSET; + } else { + prop_flags = flags & JS_PROP_C_W_E; + } + pr = add_property(ctx, p, prop, prop_flags); + if (unlikely(!pr)) + return -1; + if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) { + pr->u.getset.getter = NULL; + if ((flags & JS_PROP_HAS_GET) && JS_IsFunction(ctx, getter)) { + pr->u.getset.getter = + JS_VALUE_GET_OBJ(js_dup(getter)); + } + pr->u.getset.setter = NULL; + if ((flags & JS_PROP_HAS_SET) && JS_IsFunction(ctx, setter)) { + pr->u.getset.setter = + JS_VALUE_GET_OBJ(js_dup(setter)); + } + } else { + if (flags & JS_PROP_HAS_VALUE) { + pr->u.value = js_dup(val); + } else { + pr->u.value = JS_UNDEFINED; + } + } + return true; +} + +/* return false if not OK */ +static bool check_define_prop_flags(int prop_flags, int flags) +{ + bool has_accessor, is_getset; + + if (!(prop_flags & JS_PROP_CONFIGURABLE)) { + if ((flags & (JS_PROP_HAS_CONFIGURABLE | JS_PROP_CONFIGURABLE)) == + (JS_PROP_HAS_CONFIGURABLE | JS_PROP_CONFIGURABLE)) { + return false; + } + if ((flags & JS_PROP_HAS_ENUMERABLE) && + (flags & JS_PROP_ENUMERABLE) != (prop_flags & JS_PROP_ENUMERABLE)) + return false; + } + if (flags & (JS_PROP_HAS_VALUE | JS_PROP_HAS_WRITABLE | + JS_PROP_HAS_GET | JS_PROP_HAS_SET)) { + if (!(prop_flags & JS_PROP_CONFIGURABLE)) { + has_accessor = ((flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) != 0); + is_getset = ((prop_flags & JS_PROP_TMASK) == JS_PROP_GETSET); + if (has_accessor != is_getset) + return false; + if (!has_accessor && !is_getset && !(prop_flags & JS_PROP_WRITABLE)) { + /* not writable: cannot set the writable bit */ + if ((flags & (JS_PROP_HAS_WRITABLE | JS_PROP_WRITABLE)) == + (JS_PROP_HAS_WRITABLE | JS_PROP_WRITABLE)) + return false; + } + } + } + return true; +} + +/* ensure that the shape can be safely modified */ +static int js_shape_prepare_update(JSContext *ctx, JSObject *p, + JSShapeProperty **pprs) +{ + JSShape *sh; + uint32_t idx = 0; /* prevent warning */ + + sh = p->shape; + if (sh->is_hashed) { + if (JS_REF_COUNT(sh) != 1) { + if (pprs) + idx = *pprs - get_shape_prop(sh); + /* clone the shape (the resulting one is no longer hashed) */ + sh = js_clone_shape(ctx, sh); + if (!sh) + return -1; + js_free_shape(ctx->rt, p->shape); + p->shape = sh; + if (pprs) + *pprs = &get_shape_prop(sh)[idx]; + } else { + js_shape_hash_unlink(ctx->rt, sh); + sh->is_hashed = false; + } + } + return 0; +} + +static int js_update_property_flags(JSContext *ctx, JSObject *p, + JSShapeProperty **pprs, int flags) +{ + if (flags != (*pprs)->flags) { + if (js_shape_prepare_update(ctx, p, pprs)) + return -1; + (*pprs)->flags = flags; + } + return 0; +} + +/* allowed flags: + JS_PROP_CONFIGURABLE, JS_PROP_WRITABLE, JS_PROP_ENUMERABLE + JS_PROP_HAS_GET, JS_PROP_HAS_SET, JS_PROP_HAS_VALUE, + JS_PROP_HAS_CONFIGURABLE, JS_PROP_HAS_WRITABLE, JS_PROP_HAS_ENUMERABLE, + JS_PROP_THROW, JS_PROP_NO_EXOTIC. + If JS_PROP_THROW is set, return an exception instead of false. + if JS_PROP_NO_EXOTIC is set, do not call the exotic + define_own_property callback. + return -1 (exception), false or true. +*/ +int JS_DefineProperty(JSContext *ctx, JSValueConst this_obj, + JSAtom prop, JSValueConst val, + JSValueConst getter, JSValueConst setter, int flags) +{ + JSObject *p; + JSShapeProperty *prs; + JSProperty *pr; + int mask, res; + + if (JS_VALUE_GET_TAG(this_obj) != JS_TAG_OBJECT) { + JS_ThrowTypeErrorNotAnObject(ctx); + return -1; + } + p = JS_VALUE_GET_OBJ(this_obj); + + redo_prop_update: + prs = find_own_property(&pr, p, prop); + if (prs) { + /* the range of the Array length property is always tested before */ + if ((prs->flags & JS_PROP_LENGTH) && (flags & JS_PROP_HAS_VALUE)) { + uint32_t array_length; + if (JS_ToArrayLengthFree(ctx, &array_length, + js_dup(val), false)) { + return -1; + } + /* this code relies on the fact that Uint32 are never allocated */ + val = js_uint32(array_length); + /* prs may have been modified */ + prs = find_own_property(&pr, p, prop); + assert(prs != NULL); + } + /* property already exists */ + if (!check_define_prop_flags(prs->flags, flags)) { + not_configurable: + return JS_ThrowTypeErrorOrFalse(ctx, flags, "property is not configurable"); + } + + if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { + /* Instantiate property and retry */ + if (JS_AutoInitProperty(ctx, p, prop, pr, prs)) + return -1; + goto redo_prop_update; + } + + if (flags & (JS_PROP_HAS_VALUE | JS_PROP_HAS_WRITABLE | + JS_PROP_HAS_GET | JS_PROP_HAS_SET)) { + if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) { + JSObject *new_getter, *new_setter; + + if (JS_IsFunction(ctx, getter)) { + new_getter = JS_VALUE_GET_OBJ(getter); + } else { + new_getter = NULL; + } + if (JS_IsFunction(ctx, setter)) { + new_setter = JS_VALUE_GET_OBJ(setter); + } else { + new_setter = NULL; + } + + if ((prs->flags & JS_PROP_TMASK) != JS_PROP_GETSET) { + if (js_shape_prepare_update(ctx, p, &prs)) + return -1; + /* convert to getset */ + if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { + free_var_ref(ctx->rt, pr->u.var_ref); + } else { + JS_FreeValue(ctx, pr->u.value); + } + prs->flags = (prs->flags & + (JS_PROP_CONFIGURABLE | JS_PROP_ENUMERABLE)) | + JS_PROP_GETSET; + pr->u.getset.getter = NULL; + pr->u.getset.setter = NULL; + } else { + if (!(prs->flags & JS_PROP_CONFIGURABLE)) { + if ((flags & JS_PROP_HAS_GET) && + new_getter != pr->u.getset.getter) { + goto not_configurable; + } + if ((flags & JS_PROP_HAS_SET) && + new_setter != pr->u.getset.setter) { + goto not_configurable; + } + } + } + if (flags & JS_PROP_HAS_GET) { + if (pr->u.getset.getter) + JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.getter)); + if (new_getter) + js_dup(getter); + pr->u.getset.getter = new_getter; + } + if (flags & JS_PROP_HAS_SET) { + if (pr->u.getset.setter) + JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.setter)); + if (new_setter) + js_dup(setter); + pr->u.getset.setter = new_setter; + } + } else { + if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { + /* convert to data descriptor */ + if (js_shape_prepare_update(ctx, p, &prs)) + return -1; + if (pr->u.getset.getter) + JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.getter)); + if (pr->u.getset.setter) + JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.setter)); + prs->flags &= ~(JS_PROP_TMASK | JS_PROP_WRITABLE); + pr->u.value = JS_UNDEFINED; + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { + /* Note: JS_PROP_VARREF is always writable */ + } else { + if ((prs->flags & (JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE)) == 0 && + (flags & JS_PROP_HAS_VALUE)) { + if (!js_same_value(ctx, val, pr->u.value)) { + goto not_configurable; + } else { + return true; + } + } + } + if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { + if (flags & JS_PROP_HAS_VALUE) { + if (p->class_id == JS_CLASS_MODULE_NS) { + /* JS_PROP_WRITABLE is always true for variable + references, but they are write protected in module name + spaces. */ + if (!js_same_value(ctx, val, *pr->u.var_ref->pvalue)) + goto not_configurable; + } + /* update the reference */ + set_value(ctx, pr->u.var_ref->pvalue, js_dup(val)); + } + /* if writable is set to false, no longer a + reference (for mapped arguments) */ + if ((flags & (JS_PROP_HAS_WRITABLE | JS_PROP_WRITABLE)) == JS_PROP_HAS_WRITABLE) { + JSValue val1; + if (js_shape_prepare_update(ctx, p, &prs)) + return -1; + val1 = js_dup(*pr->u.var_ref->pvalue); + free_var_ref(ctx->rt, pr->u.var_ref); + pr->u.value = val1; + prs->flags &= ~(JS_PROP_TMASK | JS_PROP_WRITABLE); + } + } else if (prs->flags & JS_PROP_LENGTH) { + if (flags & JS_PROP_HAS_VALUE) { + /* Note: no JS code is executable because + 'val' is guaranted to be a Uint32 */ + res = set_array_length(ctx, p, js_dup(val), flags); + } else { + res = true; + } + /* still need to reset the writable flag if + needed. The JS_PROP_LENGTH is kept because the + Uint32 test is still done if the length + property is read-only. */ + if ((flags & (JS_PROP_HAS_WRITABLE | JS_PROP_WRITABLE)) == + JS_PROP_HAS_WRITABLE) { + prs = get_shape_prop(p->shape); + if (js_update_property_flags(ctx, p, &prs, + prs->flags & ~JS_PROP_WRITABLE)) + return -1; + } + return res; + } else { + if (flags & JS_PROP_HAS_VALUE) { + JS_FreeValue(ctx, pr->u.value); + pr->u.value = js_dup(val); + } + if (flags & JS_PROP_HAS_WRITABLE) { + if (js_update_property_flags(ctx, p, &prs, + (prs->flags & ~JS_PROP_WRITABLE) | + (flags & JS_PROP_WRITABLE))) + return -1; + } + } + } + } + mask = 0; + if (flags & JS_PROP_HAS_CONFIGURABLE) + mask |= JS_PROP_CONFIGURABLE; + if (flags & JS_PROP_HAS_ENUMERABLE) + mask |= JS_PROP_ENUMERABLE; + if (js_update_property_flags(ctx, p, &prs, + (prs->flags & ~mask) | (flags & mask))) + return -1; + return true; + } + + /* handle modification of fast array elements */ + if (p->fast_array) { + uint32_t idx; + uint32_t prop_flags; + if (p->class_id == JS_CLASS_ARRAY) { + if (__JS_AtomIsTaggedInt(prop)) { + idx = __JS_AtomToUInt32(prop); + if (idx < p->u.array.count) { + prop_flags = get_prop_flags(flags, JS_PROP_C_W_E); + if (prop_flags != JS_PROP_C_W_E) + goto convert_to_slow_array; + if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) { + convert_to_slow_array: + if (convert_fast_array_to_array(ctx, p)) + return -1; + else + goto redo_prop_update; + } + if (flags & JS_PROP_HAS_VALUE) { + set_value(ctx, &p->u.array.u.values[idx], js_dup(val)); + } + return true; + } + } + } else if (is_typed_array(p->class_id)) { + JSValue num; + int ret; + + if (!__JS_AtomIsTaggedInt(prop)) { + /* slow path with to handle all numeric indexes */ + num = JS_AtomIsNumericIndex1(ctx, prop); + if (JS_IsUndefined(num)) + goto typed_array_done; + if (JS_IsException(num)) + return -1; + ret = JS_NumberIsInteger(ctx, num); + if (ret < 0) { + JS_FreeValue(ctx, num); + return -1; + } + if (!ret) { + JS_FreeValue(ctx, num); + return JS_ThrowTypeErrorOrFalse(ctx, flags, "non integer index in typed array"); + } + ret = JS_NumberIsNegativeOrMinusZero(ctx, num); + JS_FreeValue(ctx, num); + if (ret) { + return JS_ThrowTypeErrorOrFalse(ctx, flags, "negative index in typed array"); + } + if (!__JS_AtomIsTaggedInt(prop)) + goto typed_array_oob; + } + idx = __JS_AtomToUInt32(prop); + /* if the typed array is detached, p->u.array.count = 0 */ + if (idx >= p->u.array.count) { + typed_array_oob: + return JS_ThrowTypeErrorOrFalse(ctx, flags, "out-of-bound index in typed array"); + } + prop_flags = get_prop_flags(flags, JS_PROP_ENUMERABLE | JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET) || + prop_flags != (JS_PROP_ENUMERABLE | JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE)) { + return JS_ThrowTypeErrorOrFalse(ctx, flags, "invalid descriptor flags"); + } + if (flags & JS_PROP_HAS_VALUE) { + return JS_SetPropertyValue(ctx, this_obj, js_int32(idx), js_dup(val), flags); + } + return true; + typed_array_done: ; + } + } + + return JS_CreateProperty(ctx, p, prop, val, getter, setter, flags); +} + +static int JS_DefineAutoInitProperty(JSContext *ctx, JSValueConst this_obj, + JSAtom prop, JSAutoInitIDEnum id, + void *opaque, int flags) +{ + JSObject *p; + JSProperty *pr; + + if (JS_VALUE_GET_TAG(this_obj) != JS_TAG_OBJECT) + return false; + + p = JS_VALUE_GET_OBJ(this_obj); + + if (find_own_property(&pr, p, prop)) { + /* property already exists */ + abort(); + return false; + } + + /* Specialized CreateProperty */ + pr = add_property(ctx, p, prop, (flags & JS_PROP_C_W_E) | JS_PROP_AUTOINIT); + if (unlikely(!pr)) + return -1; + pr->u.init.realm_and_id = (uintptr_t)JS_DupContext(ctx); + assert((pr->u.init.realm_and_id & 3) == 0); + assert(id <= 3); + pr->u.init.realm_and_id |= id; + pr->u.init.opaque = opaque; + return true; +} + +/* Like JS_DefinePropertyValue but borrows val (does not free it) */ +static int JS_DefinePropertyValueConst(JSContext *ctx, JSValueConst this_obj, + JSAtom prop, JSValueConst val, int flags) +{ + return JS_DefineProperty(ctx, this_obj, prop, val, JS_UNDEFINED, JS_UNDEFINED, + flags | JS_PROP_HAS_VALUE | JS_PROP_HAS_CONFIGURABLE | + JS_PROP_HAS_WRITABLE | JS_PROP_HAS_ENUMERABLE); +} + +/* shortcut to add or redefine a new property value */ +int JS_DefinePropertyValue(JSContext *ctx, JSValueConst this_obj, + JSAtom prop, JSValue val, int flags) +{ + int ret; + ret = JS_DefinePropertyValueConst(ctx, this_obj, prop, val, flags); + JS_FreeValue(ctx, val); + return ret; +} + +int JS_DefinePropertyValueValue(JSContext *ctx, JSValueConst this_obj, + JSValue prop, JSValue val, int flags) +{ + JSAtom atom; + int ret; + atom = JS_ValueToAtom(ctx, prop); + JS_FreeValue(ctx, prop); + if (unlikely(atom == JS_ATOM_NULL)) { + JS_FreeValue(ctx, val); + return -1; + } + ret = JS_DefinePropertyValue(ctx, this_obj, atom, val, flags); + JS_FreeAtom(ctx, atom); + return ret; +} + +int JS_DefinePropertyValueUint32(JSContext *ctx, JSValueConst this_obj, + uint32_t idx, JSValue val, int flags) +{ + return JS_DefinePropertyValueValue(ctx, this_obj, js_uint32(idx), + val, flags); +} + +int JS_DefinePropertyValueInt64(JSContext *ctx, JSValueConst this_obj, + int64_t idx, JSValue val, int flags) +{ + return JS_DefinePropertyValueValue(ctx, this_obj, js_int64(idx), + val, flags); +} + +/* `prop` may be pure ASCII or UTF-8 encoded */ +int JS_DefinePropertyValueStr(JSContext *ctx, JSValueConst this_obj, + const char *prop, JSValue val, int flags) +{ + JSAtom atom; + int ret; + atom = JS_NewAtom(ctx, prop); + if (atom == JS_ATOM_NULL) { + JS_FreeValue(ctx, val); + return -1; + } + ret = JS_DefinePropertyValue(ctx, this_obj, atom, val, flags); + JS_FreeAtom(ctx, atom); + return ret; +} + +/* shortcut to add getter & setter */ +int JS_DefinePropertyGetSet(JSContext *ctx, JSValueConst this_obj, + JSAtom prop, JSValue getter, JSValue setter, + int flags) +{ + int ret; + ret = JS_DefineProperty(ctx, this_obj, prop, JS_UNDEFINED, getter, setter, + flags | JS_PROP_HAS_GET | JS_PROP_HAS_SET | + JS_PROP_HAS_CONFIGURABLE | JS_PROP_HAS_ENUMERABLE); + JS_FreeValue(ctx, getter); + JS_FreeValue(ctx, setter); + return ret; +} + +static int JS_CreateDataPropertyUint32(JSContext *ctx, JSValueConst this_obj, + int64_t idx, JSValue val, int flags) +{ + return JS_DefinePropertyValueValue(ctx, this_obj, js_int64(idx), + val, flags | JS_PROP_CONFIGURABLE | + JS_PROP_ENUMERABLE | JS_PROP_WRITABLE); +} + +/* Like JS_DefinePropertyValueInt64 but borrows val (does not free it) */ +static int JS_DefinePropertyValueInt64Const(JSContext *ctx, JSValueConst this_obj, + int64_t idx, JSValueConst val, int flags) +{ + JSAtom atom; + int ret; + atom = JS_ValueToAtom(ctx, js_int64(idx)); + if (unlikely(atom == JS_ATOM_NULL)) + return -1; + ret = JS_DefinePropertyValueConst(ctx, this_obj, atom, val, flags); + JS_FreeAtom(ctx, atom); + return ret; +} + +/* Like JS_CreateDataPropertyUint32 but borrows val (does not free it) */ +static int JS_CreateDataPropertyUint32Const(JSContext *ctx, JSValueConst this_obj, + int64_t idx, JSValueConst val, int flags) +{ + return JS_DefinePropertyValueInt64Const(ctx, this_obj, idx, val, + flags | JS_PROP_CONFIGURABLE | + JS_PROP_ENUMERABLE | JS_PROP_WRITABLE); +} + + +/* return true if 'obj' has a non empty 'name' string */ +static bool js_object_has_name(JSContext *ctx, JSValue obj) +{ + JSProperty *pr; + JSShapeProperty *prs; + JSValue val; + JSString *p; + + prs = find_own_property(&pr, JS_VALUE_GET_OBJ(obj), JS_ATOM_name); + if (!prs) + return false; + if ((prs->flags & JS_PROP_TMASK) != JS_PROP_NORMAL) + return true; + val = pr->u.value; + if (JS_VALUE_GET_TAG(val) != JS_TAG_STRING) + return true; + p = JS_VALUE_GET_STRING(val); + return (p->len != 0); +} + +static int JS_DefineObjectName(JSContext *ctx, JSValue obj, + JSAtom name, int flags) +{ + if (name != JS_ATOM_NULL + && JS_IsObject(obj) + && !js_object_has_name(ctx, obj) + && JS_DefinePropertyValue(ctx, obj, JS_ATOM_name, JS_AtomToString(ctx, name), flags) < 0) { + return -1; + } + return 0; +} + +static int JS_DefineObjectNameComputed(JSContext *ctx, JSValue obj, + JSValue str, int flags) +{ + if (JS_IsObject(obj) && + !js_object_has_name(ctx, obj)) { + JSAtom prop; + JSValue name_str; + prop = JS_ValueToAtom(ctx, str); + if (prop == JS_ATOM_NULL) + return -1; + name_str = js_get_function_name(ctx, prop); + JS_FreeAtom(ctx, prop); + if (JS_IsException(name_str)) + return -1; + if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_name, name_str, flags) < 0) + return -1; + } + return 0; +} + +#define DEFINE_GLOBAL_LEX_VAR (1 << 7) +#define DEFINE_GLOBAL_FUNC_VAR (1 << 6) + +static JSValue JS_ThrowSyntaxErrorVarRedeclaration(JSContext *ctx, JSAtom prop) +{ + return JS_ThrowSyntaxErrorAtom(ctx, "redeclaration of '%s'", prop); +} + +/* flags is 0, DEFINE_GLOBAL_LEX_VAR or DEFINE_GLOBAL_FUNC_VAR */ +/* XXX: could support exotic global object. */ +static int JS_CheckDefineGlobalVar(JSContext *ctx, JSAtom prop, int flags) +{ + JSObject *p; + JSShapeProperty *prs; + + p = JS_VALUE_GET_OBJ(ctx->global_obj); + prs = find_own_property1(p, prop); + /* XXX: should handle JS_PROP_AUTOINIT */ + if (flags & DEFINE_GLOBAL_LEX_VAR) { + if (prs && !(prs->flags & JS_PROP_CONFIGURABLE)) + goto fail_redeclaration; + } else { + if (!prs && !p->extensible) + goto define_error; + if (flags & DEFINE_GLOBAL_FUNC_VAR) { + if (prs) { + if (!(prs->flags & JS_PROP_CONFIGURABLE) && + ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET || + ((prs->flags & (JS_PROP_WRITABLE | JS_PROP_ENUMERABLE)) != + (JS_PROP_WRITABLE | JS_PROP_ENUMERABLE)))) { + define_error: + JS_ThrowTypeErrorAtom(ctx, "cannot define variable '%s'", + prop); + return -1; + } + } + } + } + /* check if there already is a lexical declaration */ + p = JS_VALUE_GET_OBJ(ctx->global_var_obj); + prs = find_own_property1(p, prop); + if (prs) { + fail_redeclaration: + JS_ThrowSyntaxErrorVarRedeclaration(ctx, prop); + return -1; + } + return 0; +} + +/* def_flags is (0, DEFINE_GLOBAL_LEX_VAR) | + JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE */ +/* XXX: could support exotic global object. */ +static int JS_DefineGlobalVar(JSContext *ctx, JSAtom prop, int def_flags) +{ + JSObject *p; + JSShapeProperty *prs; + JSProperty *pr; + JSValue val; + int flags; + + if (def_flags & DEFINE_GLOBAL_LEX_VAR) { + p = JS_VALUE_GET_OBJ(ctx->global_var_obj); + flags = JS_PROP_ENUMERABLE | (def_flags & JS_PROP_WRITABLE) | + JS_PROP_CONFIGURABLE; + val = JS_UNINITIALIZED; + } else { + p = JS_VALUE_GET_OBJ(ctx->global_obj); + flags = JS_PROP_ENUMERABLE | JS_PROP_WRITABLE | + (def_flags & JS_PROP_CONFIGURABLE); + val = JS_UNDEFINED; + } + prs = find_own_property1(p, prop); + if (prs) + return 0; + if (!p->extensible) + return 0; + pr = add_property(ctx, p, prop, flags); + if (unlikely(!pr)) + return -1; + pr->u.value = val; + return 0; +} + +/* 'def_flags' is 0 or JS_PROP_CONFIGURABLE. */ +/* XXX: could support exotic global object. */ +static int JS_DefineGlobalFunction(JSContext *ctx, JSAtom prop, + JSValue func, int def_flags) +{ + + JSObject *p; + JSShapeProperty *prs; + int flags; + + p = JS_VALUE_GET_OBJ(ctx->global_obj); + prs = find_own_property1(p, prop); + flags = JS_PROP_HAS_VALUE | JS_PROP_THROW; + if (!prs || (prs->flags & JS_PROP_CONFIGURABLE)) { + flags |= JS_PROP_ENUMERABLE | JS_PROP_WRITABLE | def_flags | + JS_PROP_HAS_CONFIGURABLE | JS_PROP_HAS_WRITABLE | JS_PROP_HAS_ENUMERABLE; + } + if (JS_DefineProperty(ctx, ctx->global_obj, prop, func, + JS_UNDEFINED, JS_UNDEFINED, flags) < 0) + return -1; + return 0; +} + +static JSValue JS_GetGlobalVar(JSContext *ctx, JSAtom prop, + bool throw_ref_error) +{ + JSObject *p; + JSShapeProperty *prs; + JSProperty *pr; + + /* no exotic behavior is possible in global_var_obj */ + p = JS_VALUE_GET_OBJ(ctx->global_var_obj); + prs = find_own_property(&pr, p, prop); + if (prs) { + /* XXX: should handle JS_PROP_TMASK properties */ + if (unlikely(JS_IsUninitialized(pr->u.value))) + return JS_ThrowReferenceErrorUninitialized(ctx, prs->atom); + return js_dup(pr->u.value); + } + + /* fast path */ + p = JS_VALUE_GET_OBJ(ctx->global_obj); + prs = find_own_property(&pr, p, prop); + if (prs) { + if (likely((prs->flags & JS_PROP_TMASK) == 0)) + return js_dup(pr->u.value); + } + return JS_GetPropertyInternal(ctx, ctx->global_obj, prop, + ctx->global_obj, throw_ref_error); +} + +/* construct a reference to a global variable */ +static int JS_GetGlobalVarRef(JSContext *ctx, JSAtom prop, JSValue *sp) +{ + JSObject *p; + JSShapeProperty *prs; + JSProperty *pr; + + /* no exotic behavior is possible in global_var_obj */ + p = JS_VALUE_GET_OBJ(ctx->global_var_obj); + prs = find_own_property(&pr, p, prop); + if (prs) { + /* XXX: should handle JS_PROP_AUTOINIT properties? */ + /* XXX: conformance: do these tests in + OP_put_var_ref/OP_get_var_ref ? */ + if (unlikely(JS_IsUninitialized(pr->u.value))) { + JS_ThrowReferenceErrorUninitialized(ctx, prs->atom); + return -1; + } + if (unlikely(!(prs->flags & JS_PROP_WRITABLE))) { + return JS_ThrowTypeErrorReadOnly(ctx, JS_PROP_THROW, prop); + } + sp[0] = js_dup(ctx->global_var_obj); + } else { + int ret; + ret = JS_HasProperty(ctx, ctx->global_obj, prop); + if (ret < 0) + return -1; + if (ret) { + sp[0] = js_dup(ctx->global_obj); + } else { + sp[0] = JS_UNDEFINED; + } + } + sp[1] = JS_AtomToValue(ctx, prop); + return 0; +} + +/* flag = 0: normal variable write + flag = 1: initialize lexical variable +*/ +static inline int JS_SetGlobalVar(JSContext *ctx, JSAtom prop, JSValue val, + int flag) +{ + JSObject *p; + JSShapeProperty *prs; + JSProperty *pr; + int ret; + + /* no exotic behavior is possible in global_var_obj */ + p = JS_VALUE_GET_OBJ(ctx->global_var_obj); + prs = find_own_property(&pr, p, prop); + if (prs) { + /* XXX: should handle JS_PROP_AUTOINIT properties? */ + if (flag != 1) { + if (unlikely(JS_IsUninitialized(pr->u.value))) { + JS_FreeValue(ctx, val); + JS_ThrowReferenceErrorUninitialized(ctx, prs->atom); + return -1; + } + if (unlikely(!(prs->flags & JS_PROP_WRITABLE))) { + JS_FreeValue(ctx, val); + return JS_ThrowTypeErrorReadOnly(ctx, JS_PROP_THROW, prop); + } + } + set_value(ctx, &pr->u.value, val); + return 0; + } + + p = JS_VALUE_GET_OBJ(ctx->global_obj); + prs = find_own_property(&pr, p, prop); + if (prs) { + if (likely((prs->flags & (JS_PROP_TMASK | JS_PROP_WRITABLE | + JS_PROP_LENGTH)) == JS_PROP_WRITABLE)) { + /* fast path */ + set_value(ctx, &pr->u.value, val); + return 0; + } + } + /* slow path */ + ret = JS_HasProperty(ctx, ctx->global_obj, prop); + if (ret < 0) { + JS_FreeValue(ctx, val); + return -1; + } + if (ret == 0 && is_strict_mode(ctx)) { + JS_FreeValue(ctx, val); + JS_ThrowReferenceErrorNotDefined(ctx, prop); + return -1; + } + return JS_SetPropertyInternal(ctx, ctx->global_obj, prop, val, + JS_PROP_THROW_STRICT); +} + +/* return -1, false or true */ +static int JS_DeleteGlobalVar(JSContext *ctx, JSAtom prop) +{ + JSProperty *pr; + JSObject *p; + + /* 9.1.1.4.7 DeleteBinding ( N ) */ + p = JS_VALUE_GET_OBJ(ctx->global_var_obj); + if (find_own_property(&pr, p, prop)) + return false; /* lexical variables cannot be deleted */ + p = JS_VALUE_GET_OBJ(ctx->global_obj); + return delete_property(ctx, p, prop); +} + +/* return -1, false or true. return false if not configurable or + invalid object. return -1 in case of exception. + flags can be 0, JS_PROP_THROW or JS_PROP_THROW_STRICT */ +int JS_DeleteProperty(JSContext *ctx, JSValueConst obj, JSAtom prop, int flags) +{ + JSValue obj1; + JSObject *p; + int res; + + obj1 = JS_ToObject(ctx, obj); + if (JS_IsException(obj1)) + return -1; + p = JS_VALUE_GET_OBJ(obj1); + res = delete_property(ctx, p, prop); + JS_FreeValue(ctx, obj1); + if (res != false) + return res; + if ((flags & JS_PROP_THROW) || + ((flags & JS_PROP_THROW_STRICT) && is_strict_mode(ctx))) { + JS_ThrowTypeError(ctx, "could not delete property"); + return -1; + } + return false; +} + +int JS_DeletePropertyInt64(JSContext *ctx, JSValueConst obj, int64_t idx, int flags) +{ + JSAtom prop; + int res; + + if ((uint64_t)idx <= JS_ATOM_MAX_INT) { + /* fast path for fast arrays */ + return JS_DeleteProperty(ctx, obj, __JS_AtomFromUInt32(idx), flags); + } + prop = JS_NewAtomInt64(ctx, idx); + if (prop == JS_ATOM_NULL) + return -1; + res = JS_DeleteProperty(ctx, obj, prop, flags); + JS_FreeAtom(ctx, prop); + return res; +} + +bool JS_IsFunction(JSContext *ctx, JSValueConst val) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) + return false; + p = JS_VALUE_GET_OBJ(val); + switch(p->class_id) { + case JS_CLASS_BYTECODE_FUNCTION: + return true; + case JS_CLASS_PROXY: + return p->u.proxy_data->is_func; + default: + return (ctx->rt->class_array[p->class_id].call != NULL); + } +} + +bool JS_IsAsyncFunction(JSValueConst val) +{ + return JS_CLASS_ASYNC_FUNCTION == JS_GetClassID(val); +} + +static bool JS_IsCFunction(JSContext *ctx, JSValueConst val, JSCFunction *func, + int magic) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) + return false; + p = JS_VALUE_GET_OBJ(val); + if (p->class_id == JS_CLASS_C_FUNCTION) + return (p->u.cfunc.c_function.generic == func && p->u.cfunc.magic == magic); + else + return false; +} + +bool JS_IsConstructor(JSContext *ctx, JSValueConst val) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) + return false; + p = JS_VALUE_GET_OBJ(val); + return p->is_constructor; +} + +bool JS_SetConstructorBit(JSContext *ctx, JSValueConst func_obj, bool val) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(func_obj) != JS_TAG_OBJECT) + return false; + p = JS_VALUE_GET_OBJ(func_obj); + p->is_constructor = val; + return true; +} + +bool JS_IsRegExp(JSValueConst val) +{ + return JS_CLASS_REGEXP == JS_GetClassID(val); +} + +bool JS_IsMap(JSValueConst val) +{ + return JS_CLASS_MAP == JS_GetClassID(val); +} + +bool JS_IsSet(JSValueConst val) +{ + return JS_CLASS_SET == JS_GetClassID(val); +} + +bool JS_IsWeakRef(JSValueConst val) +{ + return JS_CLASS_WEAK_REF == JS_GetClassID(val); +} + +bool JS_IsWeakSet(JSValueConst val) +{ + return JS_CLASS_WEAKSET == JS_GetClassID(val); +} + +bool JS_IsWeakMap(JSValueConst val) +{ + return JS_CLASS_WEAKMAP == JS_GetClassID(val); +} + +bool JS_IsDataView(JSValueConst val) +{ + return JS_CLASS_DATAVIEW == JS_GetClassID(val); +} + +bool JS_IsError(JSValueConst val) +{ + return JS_CLASS_ERROR == JS_GetClassID(val); +} + +/* used to avoid catching interrupt exceptions */ +bool JS_IsUncatchableError(JSValueConst val) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) + return false; + p = JS_VALUE_GET_OBJ(val); + return p->class_id == JS_CLASS_ERROR && p->is_uncatchable_error; +} + +static void js_set_uncatchable_error(JSContext *ctx, JSValueConst val, bool flag) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) + return; + p = JS_VALUE_GET_OBJ(val); + if (p->class_id == JS_CLASS_ERROR) + p->is_uncatchable_error = flag; +} + +void JS_SetUncatchableError(JSContext *ctx, JSValueConst val) +{ + js_set_uncatchable_error(ctx, val, true); +} + +void JS_ClearUncatchableError(JSContext *ctx, JSValueConst val) +{ + js_set_uncatchable_error(ctx, val, false); +} + +void JS_ResetUncatchableError(JSContext *ctx) +{ + js_set_uncatchable_error(ctx, ctx->rt->current_exception, false); +} + +int JS_SetOpaque(JSValueConst obj, void *opaque) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { + p = JS_VALUE_GET_OBJ(obj); + // User code can't set the opaque of internal objects. + if (p->class_id >= JS_CLASS_INIT_COUNT) { + p->u.opaque = opaque; + return 0; + } + } + + return -1; +} + +/* |obj| must be a JSObject of an internal class. */ +static void JS_SetOpaqueInternal(JSValueConst obj, void *opaque) +{ + JSObject *p; + assert(JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT); + p = JS_VALUE_GET_OBJ(obj); + assert(p->class_id < JS_CLASS_INIT_COUNT); + p->u.opaque = opaque; +} + +/* return NULL if not an object of class class_id */ +void *JS_GetOpaque(JSValueConst obj, JSClassID class_id) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) + return NULL; + p = JS_VALUE_GET_OBJ(obj); + if (p->class_id != class_id) + return NULL; + return p->u.opaque; +} + +void *JS_GetOpaque2(JSContext *ctx, JSValueConst obj, JSClassID class_id) +{ + void *p = JS_GetOpaque(obj, class_id); + if (unlikely(!p)) { + JS_ThrowTypeErrorInvalidClass(ctx, class_id); + } + return p; +} + +void *JS_GetAnyOpaque(JSValueConst obj, JSClassID *class_id) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) { + *class_id = 0; + return NULL; + } + p = JS_VALUE_GET_OBJ(obj); + *class_id = p->class_id; + return p->u.opaque; +} + +static JSValue JS_ToPrimitiveFree(JSContext *ctx, JSValue val, int hint) +{ + int i; + bool force_ordinary; + + JSAtom method_name; + JSValue method, ret; + if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) + return val; + force_ordinary = hint & HINT_FORCE_ORDINARY; + hint &= ~HINT_FORCE_ORDINARY; + if (!force_ordinary) { + method = JS_GetProperty(ctx, val, JS_ATOM_Symbol_toPrimitive); + if (JS_IsException(method)) + goto exception; + /* ECMA says *If exoticToPrim is not undefined* but tests in + test262 use null as a non callable converter */ + if (!JS_IsUndefined(method) && !JS_IsNull(method)) { + JSAtom atom; + JSValue arg; + switch(hint) { + case HINT_STRING: + atom = JS_ATOM_string; + break; + case HINT_NUMBER: + atom = JS_ATOM_number; + break; + default: + case HINT_NONE: + atom = JS_ATOM_default; + break; + } + arg = JS_AtomToString(ctx, atom); + ret = JS_CallFree(ctx, method, val, 1, vc(&arg)); + JS_FreeValue(ctx, arg); + if (JS_IsException(ret)) + goto exception; + JS_FreeValue(ctx, val); + if (JS_VALUE_GET_TAG(ret) != JS_TAG_OBJECT) + return ret; + JS_FreeValue(ctx, ret); + return JS_ThrowTypeError(ctx, "toPrimitive"); + } + } + if (hint != HINT_STRING) + hint = HINT_NUMBER; + for(i = 0; i < 2; i++) { + if ((i ^ hint) == 0) { + method_name = JS_ATOM_toString; + } else { + method_name = JS_ATOM_valueOf; + } + method = JS_GetProperty(ctx, val, method_name); + if (JS_IsException(method)) + goto exception; + if (JS_IsFunction(ctx, method)) { + ret = JS_CallFree(ctx, method, val, 0, NULL); + if (JS_IsException(ret)) + goto exception; + if (JS_VALUE_GET_TAG(ret) != JS_TAG_OBJECT) { + JS_FreeValue(ctx, val); + return ret; + } + JS_FreeValue(ctx, ret); + } else { + JS_FreeValue(ctx, method); + } + } + JS_ThrowTypeError(ctx, "toPrimitive"); +exception: + JS_FreeValue(ctx, val); + return JS_EXCEPTION; +} + +static JSValue JS_ToPrimitive(JSContext *ctx, JSValueConst val, int hint) +{ + return JS_ToPrimitiveFree(ctx, js_dup(val), hint); +} + +void JS_SetIsHTMLDDA(JSContext *ctx, JSValueConst obj) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) + return; + p = JS_VALUE_GET_OBJ(obj); + p->is_HTMLDDA = true; +} + +static inline bool JS_IsHTMLDDA(JSContext *ctx, JSValueConst obj) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) + return false; + p = JS_VALUE_GET_OBJ(obj); + return p->is_HTMLDDA; +} + +static int JS_ToBoolFree(JSContext *ctx, JSValue val) +{ + uint32_t tag = JS_VALUE_GET_TAG(val); + switch(tag) { + case JS_TAG_INT: + return JS_VALUE_GET_INT(val) != 0; + case JS_TAG_BOOL: + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + return JS_VALUE_GET_INT(val); + case JS_TAG_EXCEPTION: + return -1; + case JS_TAG_STRING: + { + bool ret = JS_VALUE_GET_STRING(val)->len != 0; + JS_FreeValue(ctx, val); + return ret; + } + case JS_TAG_STRING_ROPE: + { + bool ret = JS_VALUE_GET_STRING_ROPE(val)->len != 0; + JS_FreeValue(ctx, val); + return ret; + } + case JS_TAG_SHORT_BIG_INT: + return JS_VALUE_GET_SHORT_BIG_INT(val) != 0; + case JS_TAG_BIG_INT: + { + JSBigInt *p = JS_VALUE_GET_PTR(val); + bool ret; + int i; + + /* fail safe: we assume it is not necessarily + normalized. Beginning from the MSB ensures that the + test is fast. */ + ret = false; + for(i = p->len - 1; i >= 0; i--) { + if (p->tab[i] != 0) { + ret = true; + break; + } + } + JS_FreeValue(ctx, val); + return ret; + } + case JS_TAG_OBJECT: + { + JSObject *p = JS_VALUE_GET_OBJ(val); + bool ret = !p->is_HTMLDDA; + JS_FreeValue(ctx, val); + return ret; + } + break; + default: + if (JS_TAG_IS_FLOAT64(tag)) { + double d = JS_VALUE_GET_FLOAT64(val); + return !isnan(d) && d != 0; + } else { + JS_FreeValue(ctx, val); + return true; + } + } +} + +int JS_ToBool(JSContext *ctx, JSValueConst val) +{ + return JS_ToBoolFree(ctx, js_dup(val)); +} + +/* pc points to pure ASCII or UTF-8, null terminated contents */ +static int skip_spaces(const char *pc) +{ + const uint8_t *p, *p_next, *p_start; + uint32_t c; + + p = p_start = (const uint8_t *)pc; + for (;;) { + c = *p++; + if (c < 0x80) { + if (!((c >= 0x09 && c <= 0x0d) || (c == 0x20))) + break; + } else { + c = utf8_decode(p - 1, &p_next); + /* no need to test for invalid UTF-8, 0xFFFD is not a space */ + if (!lre_is_space(c)) + break; + p = p_next; + } + } + return p - 1 - p_start; +} + +static inline int js_to_digit(int c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + else if (c >= 'A' && c <= 'Z') + return c - 'A' + 10; + else if (c >= 'a' && c <= 'z') + return c - 'a' + 10; + else + return 36; +} + +/* bigint support */ + +#define ADDC(res, carry_out, op1, op2, carry_in) \ +do { \ + js_limb_t __v, __a, __k, __k1; \ + __v = (op1); \ + __a = __v + (op2); \ + __k1 = __a < __v; \ + __k = (carry_in); \ + __a = __a + __k; \ + carry_out = (__a < __k) | __k1; \ + res = __a; \ +} while (0) + +/* a != 0 */ +static inline js_limb_t js_limb_clz(js_limb_t a) +{ + if (!a) + return JS_LIMB_BITS; + return clz32(a); +} + +static js_limb_t js_mp_add(js_limb_t *res, const js_limb_t *op1, const js_limb_t *op2, + js_limb_t n, js_limb_t carry) +{ + int i; + for(i = 0;i < n; i++) { + ADDC(res[i], carry, op1[i], op2[i], carry); + } + return carry; +} + +static js_limb_t js_mp_sub(js_limb_t *res, const js_limb_t *op1, const js_limb_t *op2, + int n, js_limb_t carry) +{ + int i; + js_limb_t k, a, v, k1; + + k = carry; + for(i=0;i v; + v = a - k; + k = (v > a) | k1; + res[i] = v; + } + return k; +} + +/* compute 0 - op2. carry = 0 or 1. */ +static js_limb_t js_mp_neg(js_limb_t *res, const js_limb_t *op2, int n) +{ + int i; + js_limb_t v, carry; + + carry = 1; + for(i=0;i> JS_LIMB_BITS; + } + return l; +} + +static js_limb_t js_mp_div1(js_limb_t *tabr, const js_limb_t *taba, js_limb_t n, + js_limb_t b, js_limb_t r) +{ + js_slimb_t i; + js_dlimb_t a1; + for(i = n - 1; i >= 0; i--) { + a1 = ((js_dlimb_t)r << JS_LIMB_BITS) | taba[i]; + tabr[i] = a1 / b; + r = a1 % b; + } + return r; +} + +/* tabr[] += taba[] * b, return the high word. */ +static js_limb_t js_mp_add_mul1(js_limb_t *tabr, const js_limb_t *taba, js_limb_t n, + js_limb_t b) +{ + js_limb_t i, l; + js_dlimb_t t; + + l = 0; + for(i = 0; i < n; i++) { + t = (js_dlimb_t)taba[i] * (js_dlimb_t)b + l + tabr[i]; + tabr[i] = t; + l = t >> JS_LIMB_BITS; + } + return l; +} + +/* size of the result : op1_size + op2_size. */ +static void js_mp_mul_basecase(js_limb_t *result, + const js_limb_t *op1, js_limb_t op1_size, + const js_limb_t *op2, js_limb_t op2_size) +{ + int i; + js_limb_t r; + + result[op1_size] = js_mp_mul1(result, op1, op1_size, op2[0], 0); + for(i=1;i> JS_LIMB_BITS); + } + return l; +} + +/* WARNING: d must be >= 2^(JS_LIMB_BITS-1) */ +static inline js_limb_t js_udiv1norm_init(js_limb_t d) +{ + js_limb_t a0, a1; + a1 = -d - 1; + a0 = -1; + return (((js_dlimb_t)a1 << JS_LIMB_BITS) | a0) / d; +} + +/* return the quotient and the remainder in '*pr'of 'a1*2^JS_LIMB_BITS+a0 + / d' with 0 <= a1 < d. */ +static inline js_limb_t js_udiv1norm(js_limb_t *pr, js_limb_t a1, js_limb_t a0, + js_limb_t d, js_limb_t d_inv) +{ + js_limb_t n1m, n_adj, q, r, ah; + js_dlimb_t a; + n1m = ((js_slimb_t)a0 >> (JS_LIMB_BITS - 1)); + n_adj = a0 + (n1m & d); + a = (js_dlimb_t)d_inv * (a1 - n1m) + n_adj; + q = (a >> JS_LIMB_BITS) + a1; + /* compute a - q * r and update q so that the remainder is\ + between 0 and d - 1 */ + a = ((js_dlimb_t)a1 << JS_LIMB_BITS) | a0; + a = a - (js_dlimb_t)q * d - d; + ah = a >> JS_LIMB_BITS; + q += 1 + ah; + r = (js_limb_t)a + (ah & d); + *pr = r; + return q; +} + +#define UDIV1NORM_THRESHOLD 3 + +/* b must be >= 1 << (JS_LIMB_BITS - 1) */ +static js_limb_t js_mp_div1norm(js_limb_t *tabr, const js_limb_t *taba, js_limb_t n, + js_limb_t b, js_limb_t r) +{ + js_slimb_t i; + + if (n >= UDIV1NORM_THRESHOLD) { + js_limb_t b_inv; + b_inv = js_udiv1norm_init(b); + for(i = n - 1; i >= 0; i--) { + tabr[i] = js_udiv1norm(&r, r, taba[i], b, b_inv); + } + } else { + js_dlimb_t a1; + for(i = n - 1; i >= 0; i--) { + a1 = ((js_dlimb_t)r << JS_LIMB_BITS) | taba[i]; + tabr[i] = a1 / b; + r = a1 % b; + } + } + return r; +} + +/* base case division: divides taba[0..na-1] by tabb[0..nb-1]. tabb[nb + - 1] must be >= 1 << (JS_LIMB_BITS - 1). na - nb must be >= 0. 'taba' + is modified and contains the remainder (nb limbs). tabq[0..na-nb] + contains the quotient with tabq[na - nb] <= 1. */ +static void js_mp_divnorm(js_limb_t *tabq, js_limb_t *taba, js_limb_t na, + const js_limb_t *tabb, js_limb_t nb) +{ + js_limb_t r, a, c, q, v, b1, b1_inv, n, dummy_r; + int i, j; + + b1 = tabb[nb - 1]; + if (nb == 1) { + taba[0] = js_mp_div1norm(tabq, taba, na, b1, 0); + return; + } + n = na - nb; + + if (n >= UDIV1NORM_THRESHOLD) + b1_inv = js_udiv1norm_init(b1); + else + b1_inv = 0; + + /* first iteration: the quotient is only 0 or 1 */ + q = 1; + for(j = nb - 1; j >= 0; j--) { + if (taba[n + j] != tabb[j]) { + if (taba[n + j] < tabb[j]) + q = 0; + break; + } + } + tabq[n] = q; + if (q) { + js_mp_sub(taba + n, taba + n, tabb, nb, 0); + } + + for(i = n - 1; i >= 0; i--) { + if (unlikely(taba[i + nb] >= b1)) { + q = -1; + } else if (b1_inv) { + q = js_udiv1norm(&dummy_r, taba[i + nb], taba[i + nb - 1], b1, b1_inv); + } else { + js_dlimb_t al; + al = ((js_dlimb_t)taba[i + nb] << JS_LIMB_BITS) | taba[i + nb - 1]; + q = al / b1; + r = al % b1; + } + r = js_mp_sub_mul1(taba + i, tabb, nb, q); + + v = taba[i + nb]; + a = v - r; + c = (a > v); + taba[i + nb] = a; + + if (c != 0) { + /* negative result */ + for(;;) { + q--; + c = js_mp_add(taba + i, taba + i, tabb, nb, 0); + /* propagate carry and test if positive result */ + if (c != 0) { + if (++taba[i + nb] == 0) { + break; + } + } + } + } + tabq[i] = q; + } +} + +/* 1 <= shift <= JS_LIMB_BITS - 1 */ +static js_limb_t js_mp_shl(js_limb_t *tabr, const js_limb_t *taba, int n, + int shift) +{ + int i; + js_limb_t l, v; + l = 0; + for(i = 0; i < n; i++) { + v = taba[i]; + tabr[i] = (v << shift) | l; + l = v >> (JS_LIMB_BITS - shift); + } + return l; +} + +/* r = (a + high*B^n) >> shift. Return the remainder r (0 <= r < 2^shift). + 1 <= shift <= LIMB_BITS - 1 */ +static js_limb_t js_mp_shr(js_limb_t *tab_r, const js_limb_t *tab, int n, + int shift, js_limb_t high) +{ + int i; + js_limb_t l, a; + + l = high; + for(i = n - 1; i >= 0; i--) { + a = tab[i]; + tab_r[i] = (a >> shift) | (l << (JS_LIMB_BITS - shift)); + l = a; + } + return l & (((js_limb_t)1 << shift) - 1); +} + +static JSBigInt *js_bigint_new(JSContext *ctx, int len) +{ + JSBigInt *r; + if (len > JS_BIGINT_MAX_SIZE) { + JS_ThrowRangeError(ctx, "BigInt is too large to allocate"); + return NULL; + } + r = js_malloc(ctx, sizeof(JSBigInt) + len * sizeof(js_limb_t)); + if (!r) + return NULL; + JS_REF_COUNT(r) = 1; + r->len = len; + return r; +} + +static JSBigInt *js_bigint_set_si(JSBigIntBuf *buf, js_slimb_t a) +{ + JSBigInt *r = (JSBigInt *)buf->big_int_buf; + /* r points into a stack JSBigIntBuf, not an arena block, so it has no + block-header ref_count slot; this temp is never refcounted/freed. */ + r->len = 1; + r->tab[0] = a; + return r; +} + +static JSBigInt *js_bigint_set_si64(JSBigIntBuf *buf, int64_t a) +{ + JSBigInt *r = (JSBigInt *)buf->big_int_buf; + /* stack JSBigIntBuf: no block-header ref_count slot (see js_bigint_set_si) */ + if (a >= INT32_MIN && a <= INT32_MAX) { + r->len = 1; + r->tab[0] = a; + } else { + r->len = 2; + r->tab[0] = a; + r->tab[1] = a >> JS_LIMB_BITS; + } + return r; +} + +/* val must be a short big int */ +static JSBigInt *js_bigint_set_short(JSBigIntBuf *buf, JSValueConst val) +{ + return js_bigint_set_si(buf, JS_VALUE_GET_SHORT_BIG_INT(val)); +} + +static __maybe_unused void js_bigint_dump1(JSContext *ctx, const char *str, + const js_limb_t *tab, int len) +{ + int i; + printf("%s: ", str); + for(i = len - 1; i >= 0; i--) { + printf(" %08x", tab[i]); + } + printf("\n"); +} + +static __maybe_unused void js_bigint_dump(JSContext *ctx, const char *str, + const JSBigInt *p) +{ + js_bigint_dump1(ctx, str, p->tab, p->len); +} + +static JSBigInt *js_bigint_new_si(JSContext *ctx, js_slimb_t a) +{ + JSBigInt *r; + r = js_bigint_new(ctx, 1); + if (!r) + return NULL; + r->tab[0] = a; + return r; +} + +static JSBigInt *js_bigint_new_si64(JSContext *ctx, int64_t a) +{ + if (a >= INT32_MIN && a <= INT32_MAX) { + return js_bigint_new_si(ctx, a); + } else { + JSBigInt *r; + r = js_bigint_new(ctx, 2); + if (!r) + return NULL; + r->tab[0] = a; + r->tab[1] = a >> 32; + return r; + } +} + +static JSBigInt *js_bigint_new_ui64(JSContext *ctx, uint64_t a) +{ + if (a <= INT64_MAX) { + return js_bigint_new_si64(ctx, a); + } else { + JSBigInt *r; + r = js_bigint_new(ctx, (65 + JS_LIMB_BITS - 1) / JS_LIMB_BITS); + if (!r) + return NULL; + r->tab[0] = a; + r->tab[1] = a >> 32; + r->tab[2] = 0; + return r; + } +} + +static JSBigInt *js_bigint_new_di(JSContext *ctx, js_sdlimb_t a) +{ + JSBigInt *r; + if (a == (js_slimb_t)a) { + r = js_bigint_new(ctx, 1); + if (!r) + return NULL; + r->tab[0] = a; + } else { + r = js_bigint_new(ctx, 2); + if (!r) + return NULL; + r->tab[0] = a; + r->tab[1] = a >> JS_LIMB_BITS; + } + return r; +} + +/* Remove redundant high order limbs. Warning: 'a' may be + reallocated. Can never fail. +*/ +static JSBigInt *js_bigint_normalize1(JSContext *ctx, JSBigInt *a, int l) +{ + js_limb_t v; + + assert(JS_REF_COUNT(a) == 1); + while (l > 1) { + v = a->tab[l - 1]; + if ((v != 0 && v != -1) || + (v & 1) != (a->tab[l - 2] >> (JS_LIMB_BITS - 1))) { + break; + } + l--; + } + if (l != a->len) { + JSBigInt *a1; + /* realloc to reduce the size */ + a->len = l; + a1 = js_realloc(ctx, a, sizeof(JSBigInt) + l * sizeof(js_limb_t)); + if (a1) + a = a1; + } + return a; +} + +static JSBigInt *js_bigint_normalize(JSContext *ctx, JSBigInt *a) +{ + return js_bigint_normalize1(ctx, a, a->len); +} + +/* return 0 or 1 depending on the sign */ +static inline int js_bigint_sign(const JSBigInt *a) +{ + return a->tab[a->len - 1] >> (JS_LIMB_BITS - 1); +} + +static js_slimb_t js_bigint_get_si_sat(const JSBigInt *a) +{ + if (a->len == 1) { + return a->tab[0]; + } else { + if (js_bigint_sign(a)) + return INT32_MIN; + else + return INT32_MAX; + } +} + +/* add the op1 limb */ +static JSBigInt *js_bigint_extend(JSContext *ctx, JSBigInt *r, + js_limb_t op1) +{ + int n2 = r->len; + if ((op1 != 0 && op1 != -1) || + (op1 & 1) != r->tab[n2 - 1] >> (JS_LIMB_BITS - 1)) { + JSBigInt *r1; + r1 = js_realloc(ctx, r, + sizeof(JSBigInt) + (n2 + 1) * sizeof(js_limb_t)); + if (!r1) { + js_free(ctx, r); + return NULL; + } + r = r1; + r->len = n2 + 1; + r->tab[n2] = op1; + } else { + /* otherwise still need to normalize the result */ + r = js_bigint_normalize(ctx, r); + } + return r; +} + +/* return NULL in case of error. Compute a + b (b_neg = 0) or a - b + (b_neg = 1) */ +/* XXX: optimize */ +static JSBigInt *js_bigint_add(JSContext *ctx, const JSBigInt *a, + const JSBigInt *b, int b_neg) +{ + JSBigInt *r; + int n1, n2, i; + js_limb_t carry, op1, op2, a_sign, b_sign; + + n2 = max_int(a->len, b->len); + n1 = min_int(a->len, b->len); + r = js_bigint_new(ctx, n2); + if (!r) + return NULL; + /* XXX: optimize */ + /* common part */ + carry = b_neg; + for(i = 0; i < n1; i++) { + op1 = a->tab[i]; + op2 = b->tab[i] ^ (-b_neg); + ADDC(r->tab[i], carry, op1, op2, carry); + } + a_sign = -js_bigint_sign(a); + b_sign = (-js_bigint_sign(b)) ^ (-b_neg); + /* part with sign extension of one operand */ + if (a->len > b->len) { + for(i = n1; i < n2; i++) { + op1 = a->tab[i]; + ADDC(r->tab[i], carry, op1, b_sign, carry); + } + } else if (a->len < b->len) { + for(i = n1; i < n2; i++) { + op2 = b->tab[i] ^ (-b_neg); + ADDC(r->tab[i], carry, a_sign, op2, carry); + } + } + + /* part with sign extension for both operands. Extend the result + if necessary */ + return js_bigint_extend(ctx, r, a_sign + b_sign + carry); +} + +/* XXX: optimize */ +static JSBigInt *js_bigint_neg(JSContext *ctx, const JSBigInt *a) +{ + JSBigIntBuf buf; + JSBigInt *b; + b = js_bigint_set_si(&buf, 0); + return js_bigint_add(ctx, b, a, 1); +} + +static JSBigInt *js_bigint_mul(JSContext *ctx, const JSBigInt *a, + const JSBigInt *b) +{ + JSBigInt *r; + + r = js_bigint_new(ctx, a->len + b->len); + if (!r) + return NULL; + js_mp_mul_basecase(r->tab, a->tab, a->len, b->tab, b->len); + /* correct the result if negative operands (no overflow is + possible) */ + if (js_bigint_sign(a)) + js_mp_sub(r->tab + a->len, r->tab + a->len, b->tab, b->len, 0); + if (js_bigint_sign(b)) + js_mp_sub(r->tab + b->len, r->tab + b->len, a->tab, a->len, 0); + return js_bigint_normalize(ctx, r); +} + +/* return the division or the remainder. 'b' must be != 0. return NULL + in case of exception (division by zero or memory error) */ +static JSBigInt *js_bigint_divrem(JSContext *ctx, const JSBigInt *a, + const JSBigInt *b, bool is_rem) +{ + JSBigInt *r, *q; + js_limb_t *tabb, h; + int na, nb, a_sign, b_sign, shift; + + if (b->len == 1 && b->tab[0] == 0) { + JS_ThrowRangeError(ctx, "BigInt division by zero"); + return NULL; + } + + a_sign = js_bigint_sign(a); + b_sign = js_bigint_sign(b); + na = a->len; + nb = b->len; + + r = js_bigint_new(ctx, na + 2); + if (!r) + return NULL; + if (a_sign) { + js_mp_neg(r->tab, a->tab, na); + } else { + memcpy(r->tab, a->tab, na * sizeof(a->tab[0])); + } + /* normalize */ + while (na > 1 && r->tab[na - 1] == 0) + na--; + + tabb = js_malloc(ctx, nb * sizeof(tabb[0])); + if (!tabb) { + js_free(ctx, r); + return NULL; + } + if (b_sign) { + js_mp_neg(tabb, b->tab, nb); + } else { + memcpy(tabb, b->tab, nb * sizeof(tabb[0])); + } + /* normalize */ + while (nb > 1 && tabb[nb - 1] == 0) + nb--; + + /* trivial case if 'a' is small */ + if (na < nb) { + js_free(ctx, r); + js_free(ctx, tabb); + if (is_rem) { + /* r = a */ + r = js_bigint_new(ctx, a->len); + if (!r) + return NULL; + memcpy(r->tab, a->tab, a->len * sizeof(a->tab[0])); + return r; + } else { + /* q = 0 */ + return js_bigint_new_si(ctx, 0); + } + } + + /* normalize 'b' */ + shift = js_limb_clz(tabb[nb - 1]); + if (shift != 0) { + js_mp_shl(tabb, tabb, nb, shift); + h = js_mp_shl(r->tab, r->tab, na, shift); + if (h != 0) + r->tab[na++] = h; + } + + q = js_bigint_new(ctx, na - nb + 2); /* one more limb for the sign */ + if (!q) { + js_free(ctx, r); + js_free(ctx, tabb); + return NULL; + } + + // js_bigint_dump1(ctx, "a", r->tab, na); + // js_bigint_dump1(ctx, "b", tabb, nb); + js_mp_divnorm(q->tab, r->tab, na, tabb, nb); + js_free(ctx, tabb); + + if (is_rem) { + js_free(ctx, q); + if (shift != 0) + js_mp_shr(r->tab, r->tab, nb, shift, 0); + r->tab[nb++] = 0; + if (a_sign) + js_mp_neg(r->tab, r->tab, nb); + r = js_bigint_normalize1(ctx, r, nb); + return r; + } else { + js_free(ctx, r); + q->tab[na - nb + 1] = 0; + if (a_sign ^ b_sign) { + js_mp_neg(q->tab, q->tab, q->len); + } + q = js_bigint_normalize(ctx, q); + return q; + } +} + +/* and, or, xor */ +static JSBigInt *js_bigint_logic(JSContext *ctx, const JSBigInt *a, + const JSBigInt *b, OPCodeEnum op) +{ + JSBigInt *r; + js_limb_t b_sign; + int a_len, b_len, i; + + if (a->len < b->len) { + const JSBigInt *tmp; + tmp = a; + a = b; + b = tmp; + } + /* a_len >= b_len */ + a_len = a->len; + b_len = b->len; + b_sign = -js_bigint_sign(b); + + r = js_bigint_new(ctx, a_len); + if (!r) + return NULL; + switch(op) { + case OP_or: + for(i = 0; i < b_len; i++) { + r->tab[i] = a->tab[i] | b->tab[i]; + } + for(i = b_len; i < a_len; i++) { + r->tab[i] = a->tab[i] | b_sign; + } + break; + case OP_and: + for(i = 0; i < b_len; i++) { + r->tab[i] = a->tab[i] & b->tab[i]; + } + for(i = b_len; i < a_len; i++) { + r->tab[i] = a->tab[i] & b_sign; + } + break; + case OP_xor: + for(i = 0; i < b_len; i++) { + r->tab[i] = a->tab[i] ^ b->tab[i]; + } + for(i = b_len; i < a_len; i++) { + r->tab[i] = a->tab[i] ^ b_sign; + } + break; + default: + abort(); + } + return js_bigint_normalize(ctx, r); +} + +static JSBigInt *js_bigint_not(JSContext *ctx, const JSBigInt *a) +{ + JSBigInt *r; + int i; + + r = js_bigint_new(ctx, a->len); + if (!r) + return NULL; + for(i = 0; i < a->len; i++) { + r->tab[i] = ~a->tab[i]; + } + /* no normalization is needed */ + return r; +} + +static JSBigInt *js_bigint_shl(JSContext *ctx, const JSBigInt *a, + unsigned int shift1) +{ + int d, i, shift; + JSBigInt *r; + js_limb_t l; + + if (a->len == 1 && a->tab[0] == 0) + return js_bigint_new_si(ctx, 0); /* zero case */ + d = shift1 / JS_LIMB_BITS; + shift = shift1 % JS_LIMB_BITS; + r = js_bigint_new(ctx, a->len + d); + if (!r) + return NULL; + for(i = 0; i < d; i++) + r->tab[i] = 0; + if (shift == 0) { + for(i = 0; i < a->len; i++) { + r->tab[i + d] = a->tab[i]; + } + } else { + l = js_mp_shl(r->tab + d, a->tab, a->len, shift); + if (js_bigint_sign(a)) + l |= (js_limb_t)(-1) << shift; + r = js_bigint_extend(ctx, r, l); + } + return r; +} + +static JSBigInt *js_bigint_shr(JSContext *ctx, const JSBigInt *a, + unsigned int shift1) +{ + int d, i, shift, a_sign, n1; + JSBigInt *r; + + d = shift1 / JS_LIMB_BITS; + shift = shift1 % JS_LIMB_BITS; + a_sign = js_bigint_sign(a); + if (d >= a->len) + return js_bigint_new_si(ctx, -a_sign); + n1 = a->len - d; + r = js_bigint_new(ctx, n1); + if (!r) + return NULL; + if (shift == 0) { + for(i = 0; i < n1; i++) { + r->tab[i] = a->tab[i + d]; + } + /* no normalization is needed */ + } else { + js_mp_shr(r->tab, a->tab + d, n1, shift, -a_sign); + r = js_bigint_normalize(ctx, r); + } + return r; +} + +static JSBigInt *js_bigint_pow(JSContext *ctx, const JSBigInt *a, JSBigInt *b) +{ + uint32_t e; + int n_bits, i; + JSBigInt *r, *r1; + + /* b must be >= 0 */ + if (js_bigint_sign(b)) { + JS_ThrowRangeError(ctx, "BigInt negative exponent"); + return NULL; + } + if (b->len == 1 && b->tab[0] == 0) { + /* a^0 = 1 */ + return js_bigint_new_si(ctx, 1); + } else if (a->len == 1) { + js_limb_t v; + bool is_neg; + + v = a->tab[0]; + if (v <= 1) + return js_bigint_new_si(ctx, v); + else if (v == -1) + return js_bigint_new_si(ctx, 1 - 2 * (b->tab[0] & 1)); + is_neg = (js_slimb_t)v < 0; + if (is_neg) + v = -v; + if ((v & (v - 1)) == 0) { + uint64_t e1; + int n; + /* v = 2^n */ + n = JS_LIMB_BITS - 1 - js_limb_clz(v); + if (b->len > 1) + goto overflow; + if (b->tab[0] > INT32_MAX) + goto overflow; + e = b->tab[0]; + e1 = (uint64_t)e * n; + if (e1 > JS_BIGINT_MAX_SIZE * JS_LIMB_BITS) + goto overflow; + e = e1; + if (is_neg) + is_neg = b->tab[0] & 1; + r = js_bigint_new(ctx, + (e + JS_LIMB_BITS + 1 - is_neg) / JS_LIMB_BITS); + if (!r) + return NULL; + memset(r->tab, 0, sizeof(r->tab[0]) * r->len); + r->tab[e / JS_LIMB_BITS] = + (js_limb_t)(1 - 2 * is_neg) << (e % JS_LIMB_BITS); + return r; + } + } + if (b->len > 1) + goto overflow; + if (b->tab[0] > INT32_MAX) + goto overflow; + e = b->tab[0]; + n_bits = 32 - clz32(e); + + r = js_bigint_new(ctx, a->len); + if (!r) + return NULL; + memcpy(r->tab, a->tab, a->len * sizeof(a->tab[0])); + for(i = n_bits - 2; i >= 0; i--) { + r1 = js_bigint_mul(ctx, r, r); + if (!r1) + return NULL; + js_free(ctx, r); + r = r1; + if ((e >> i) & 1) { + r1 = js_bigint_mul(ctx, r, a); + if (!r1) + return NULL; + js_free(ctx, r); + r = r1; + } + } + return r; + overflow: + JS_ThrowRangeError(ctx, "BigInt is too large"); + return NULL; +} + +/* return (mant, exp) so that abs(a) ~ mant*2^(exp - (limb_bits - + 1). a must be != 0. */ +static uint64_t js_bigint_get_mant_exp(JSContext *ctx, + int *pexp, const JSBigInt *a) +{ + js_limb_t t[4 - JS_LIMB_BITS / 32], carry, v, low_bits; + int n1, n2, sgn, shift, i, j, e; + uint64_t a1, a0; + + n2 = 4 - JS_LIMB_BITS / 32; + n1 = a->len - n2; + sgn = js_bigint_sign(a); + + /* low_bits != 0 if there are a non zero low bit in abs(a) */ + low_bits = 0; + carry = sgn; + for(i = 0; i < n1; i++) { + v = (a->tab[i] ^ (-sgn)) + carry; + carry = v < carry; + low_bits |= v; + } + /* get the n2 high limbs of abs(a) */ + for(j = 0; j < n2; j++) { + i = j + n1; + if (i < 0) { + v = 0; + } else { + v = (a->tab[i] ^ (-sgn)) + carry; + carry = v < carry; + } + t[j] = v; + } + + a1 = ((uint64_t)t[2] << 32) | t[1]; + a0 = (uint64_t)t[0] << 32; + a0 |= (low_bits != 0); + /* normalize */ + { + shift = clz64(a1); + if (shift != 0) { + a1 = (a1 << shift) | (a0 >> (64 - shift)); + a0 <<= shift; + } + } + a1 |= (a0 != 0); /* keep the bits for the final rounding */ + /* compute the exponent */ + e = a->len * JS_LIMB_BITS - shift - 1; + *pexp = e; + return a1; +} + +/* shift left with round to nearest, ties to even. n >= 1 */ +static uint64_t shr_rndn(uint64_t a, int n) +{ + uint64_t addend = ((a >> n) & 1) + ((1 << (n - 1)) - 1); + return (a + addend) >> n; +} + +/* convert to float64 with round to nearest, ties to even. Return + +/-infinity if too large. */ +static double js_bigint_to_float64(JSContext *ctx, const JSBigInt *a) +{ + int sgn, e; + uint64_t mant; + + if (a->len == 1) { + /* fast case, including zero */ + return (double)(js_slimb_t)a->tab[0]; + } + + sgn = js_bigint_sign(a); + mant = js_bigint_get_mant_exp(ctx, &e, a); + if (e > 1023) { + /* overflow: return infinity */ + mant = 0; + e = 1024; + } else { + mant = (mant >> 1) | (mant & 1); /* avoid overflow in rounding */ + mant = shr_rndn(mant, 10); + /* rounding can cause an overflow */ + if (mant >= ((uint64_t)1 << 53)) { + mant >>= 1; + e++; + } + mant &= (((uint64_t)1 << 52) - 1); + } + return uint64_as_float64(((uint64_t)sgn << 63) | + ((uint64_t)(e + 1023) << 52) | + mant); +} + +/* return (1, NULL) if not an integer, (2, NULL) if NaN or Infinity, + (0, n) if an integer, (0, NULL) in case of memory error */ +static JSBigInt *js_bigint_from_float64(JSContext *ctx, int *pres, double a1) +{ + uint64_t a = float64_as_uint64(a1); + int sgn, e, shift; + uint64_t mant; + JSBigIntBuf buf; + JSBigInt *r; + + sgn = a >> 63; + e = (a >> 52) & ((1 << 11) - 1); + mant = a & (((uint64_t)1 << 52) - 1); + if (e == 2047) { + /* NaN, Infinity */ + *pres = 2; + return NULL; + } + if (e == 0 && mant == 0) { + /* zero */ + *pres = 0; + return js_bigint_new_si(ctx, 0); + } + e -= 1023; + /* 0 < a < 1 : not an integer */ + if (e < 0) + goto not_an_integer; + mant |= (uint64_t)1 << 52; + if (e < 52) { + shift = 52 - e; + /* check that there is no fractional part */ + if (mant & (((uint64_t)1 << shift) - 1)) { + not_an_integer: + *pres = 1; + return NULL; + } + mant >>= shift; + e = 0; + } else { + e -= 52; + } + if (sgn) + mant = -mant; + /* the integer is mant*2^e */ + r = js_bigint_set_si64(&buf, (int64_t)mant); + *pres = 0; + return js_bigint_shl(ctx, r, e); +} + +/* return -1, 0, 1 or (2) (unordered) */ +static int js_bigint_float64_cmp(JSContext *ctx, const JSBigInt *a, + double b) +{ + int b_sign, a_sign, e, f; + uint64_t mant, b1, a_mant; + + b1 = float64_as_uint64(b); + b_sign = b1 >> 63; + e = (b1 >> 52) & ((1 << 11) - 1); + mant = b1 & (((uint64_t)1 << 52) - 1); + a_sign = js_bigint_sign(a); + if (e == 2047) { + if (mant != 0) { + /* NaN */ + return 2; + } else { + /* +/- infinity */ + return 2 * b_sign - 1; + } + } else if (e == 0 && mant == 0) { + /* b = +/-0 */ + if (a->len == 1 && a->tab[0] == 0) + return 0; + else + return 1 - 2 * a_sign; + } else if (a->len == 1 && a->tab[0] == 0) { + /* a = 0, b != 0 */ + return 2 * b_sign - 1; + } else if (a_sign != b_sign) { + return 1 - 2 * a_sign; + } else { + e -= 1023; + /* Note: handling denormals is not necessary because we + compare to integers hence f >= 0 */ + /* compute f so that 2^f <= abs(a) < 2^(f+1) */ + a_mant = js_bigint_get_mant_exp(ctx, &f, a); + if (f != e) { + if (f < e) + return -1; + else + return 1; + } else { + mant = (mant | ((uint64_t)1 << 52)) << 11; /* align to a_mant */ + if (a_mant < mant) + return 2 * a_sign - 1; + else if (a_mant > mant) + return 1 - 2 * a_sign; + else + return 0; + } + } +} + +/* return -1, 0 or 1 */ +static int js_bigint_cmp(JSContext *ctx, const JSBigInt *a, + const JSBigInt *b) +{ + int a_sign, b_sign, res, i; + a_sign = js_bigint_sign(a); + b_sign = js_bigint_sign(b); + if (a_sign != b_sign) { + res = 1 - 2 * a_sign; + } else { + /* we assume the numbers are normalized */ + if (a->len != b->len) { + if (a->len < b->len) + res = 2 * a_sign - 1; + else + res = 1 - 2 * a_sign; + } else { + res = 0; + for(i = a->len -1; i >= 0; i--) { + if (a->tab[i] != b->tab[i]) { + if (a->tab[i] < b->tab[i]) + res = -1; + else + res = 1; + break; + } + } + } + } + return res; +} + +/* contains 10^i */ +static const js_limb_t js_pow_dec[JS_LIMB_DIGITS + 1] = { + 1U, + 10U, + 100U, + 1000U, + 10000U, + 100000U, + 1000000U, + 10000000U, + 100000000U, + 1000000000U, +}; + +/* syntax: [-]digits in base radix. Return NULL if memory error. radix + = 10, 2, 8 or 16. */ +static JSBigInt *js_bigint_from_string(JSContext *ctx, + const char *str, int radix) +{ + const char *p = str; + size_t n_digits1; + int is_neg, n_digits, n_limbs, len, log2_radix, n_bits, i; + JSBigInt *r; + js_limb_t v, c, h; + + is_neg = 0; + if (*p == '-') { + is_neg = 1; + p++; + } + while (*p == '0') + p++; + n_digits1 = strlen(p); + /* the real check for overflox is done js_bigint_new(). Here + we just avoid integer overflow */ + if (n_digits1 > JS_BIGINT_MAX_SIZE * JS_LIMB_BITS) { + JS_ThrowRangeError(ctx, "BigInt is too large to allocate"); + return NULL; + } + n_digits = n_digits1; + log2_radix = 32 - clz32(radix - 1); /* ceil(log2(radix)) */ + /* compute the maximum number of limbs */ + if (radix == 10) { + n_bits = (n_digits * 27 + 7) / 8; /* >= ceil(n_digits * log2(10)) */ + } else { + n_bits = n_digits * log2_radix; + } + /* we add one extra bit for the sign */ + n_limbs = max_int(1, n_bits / JS_LIMB_BITS + 1); + r = js_bigint_new(ctx, n_limbs); + if (!r) + return NULL; + if (radix == 10) { + int digits_per_limb = JS_LIMB_DIGITS; + len = 1; + r->tab[0] = 0; + for(;;) { + /* XXX: slow */ + v = 0; + for(i = 0; i < digits_per_limb; i++) { + c = js_to_digit(*p); + if (c >= radix) + break; + p++; + v = v * 10 + c; + } + if (i == 0) + break; + if (len == 1 && r->tab[0] == 0) { + r->tab[0] = v; + } else { + h = js_mp_mul1(r->tab, r->tab, len, js_pow_dec[i], v); + if (h != 0) { + r->tab[len++] = h; + } + } + } + /* add one extra limb to have the correct sign*/ + if ((r->tab[len - 1] >> (JS_LIMB_BITS - 1)) != 0) + r->tab[len++] = 0; + r->len = len; + } else { + unsigned int bit_pos, shift, pos; + + /* power of two base: no multiplication is needed */ + r->len = n_limbs; + memset(r->tab, 0, sizeof(r->tab[0]) * n_limbs); + for(i = 0; i < n_digits; i++) { + c = js_to_digit(p[n_digits - 1 - i]); + assert(c < radix); + bit_pos = i * log2_radix; + shift = bit_pos & (JS_LIMB_BITS - 1); + pos = bit_pos / JS_LIMB_BITS; + r->tab[pos] |= c << shift; + /* if log2_radix does not divide JS_LIMB_BITS, needed an + additional op */ + if (shift + log2_radix > JS_LIMB_BITS) { + r->tab[pos + 1] |= c >> (JS_LIMB_BITS - shift); + } + } + } + r = js_bigint_normalize(ctx, r); + /* XXX: could do it in place */ + if (is_neg) { + JSBigInt *r1; + r1 = js_bigint_neg(ctx, r); + js_free(ctx, r); + r = r1; + } + return r; +} + +/* 2 <= base <= 36 */ +static char const digits[36] = { + '0','1','2','3','4','5','6','7','8','9', + 'a','b','c','d','e','f','g','h','i','j', + 'k','l','m','n','o','p','q','r','s','t', + 'u','v','w','x','y','z' +}; + +/* special version going backwards */ +/* XXX: use dtoa.c */ +static char *js_u64toa(char *q, int64_t n, unsigned int base) +{ + int digit; + if (base == 10) { + /* division by known base uses multiplication */ + do { + digit = (uint64_t)n % 10; + n = (uint64_t)n / 10; + *--q = '0' + digit; + } while (n != 0); + } else { + do { + digit = (uint64_t)n % base; + n = (uint64_t)n / base; + *--q = digits[digit]; + } while (n != 0); + } + return q; +} + +/* len >= 1. 2 <= radix <= 36 */ +static char *js_limb_to_a(char *q, js_limb_t n, unsigned int radix, int len) +{ + int digit, i; + + if (radix == 10) { + /* specific case with constant divisor */ + /* XXX: optimize */ + for(i = 0; i < len; i++) { + digit = (js_limb_t)n % 10; + n = (js_limb_t)n / 10; + *--q = digit + '0'; + } + } else { + for(i = 0; i < len; i++) { + digit = (js_limb_t)n % radix; + n = (js_limb_t)n / radix; + *--q = digits[digit]; + } + } + return q; +} + +#define JS_RADIX_MAX 36 + +static const uint8_t js_digits_per_limb_table[JS_RADIX_MAX - 1] = { +32,20,16,13,12,11,10,10, 9, 9, 8, 8, 8, 8, 8, 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, +}; + +static const js_limb_t js_radix_base_table[JS_RADIX_MAX - 1] = { + 0x00000000, 0xcfd41b91, 0x00000000, 0x48c27395, + 0x81bf1000, 0x75db9c97, 0x40000000, 0xcfd41b91, + 0x3b9aca00, 0x8c8b6d2b, 0x19a10000, 0x309f1021, + 0x57f6c100, 0x98c29b81, 0x00000000, 0x18754571, + 0x247dbc80, 0x3547667b, 0x4c4b4000, 0x6b5a6e1d, + 0x94ace180, 0xcaf18367, 0x0b640000, 0x0e8d4a51, + 0x1269ae40, 0x17179149, 0x1cb91000, 0x23744899, + 0x2b73a840, 0x34e63b41, 0x40000000, 0x4cfa3cc1, + 0x5c13d840, 0x6d91b519, 0x81bf1000, +}; + +static JSValue js_bigint_to_string1(JSContext *ctx, JSValueConst val, int radix) +{ + if (JS_VALUE_GET_TAG(val) == JS_TAG_SHORT_BIG_INT) { + char buf[66]; + int len; + len = i64toa_radix(buf, JS_VALUE_GET_SHORT_BIG_INT(val), radix); + return js_new_string8_len(ctx, buf, len); + } else { + JSBigInt *r, *tmp = NULL; + char *buf, *q, *buf_end; + int is_neg, n_bits, log2_radix, n_digits; + bool is_binary_radix; + JSValue res; + + assert(JS_VALUE_GET_TAG(val) == JS_TAG_BIG_INT); + r = JS_VALUE_GET_PTR(val); + if (r->len == 1 && r->tab[0] == 0) { + /* '0' case */ + return js_new_string8_len(ctx, "0", 1); + } + is_binary_radix = ((radix & (radix - 1)) == 0); + is_neg = js_bigint_sign(r); + if (is_neg) { + tmp = js_bigint_neg(ctx, r); + if (!tmp) + return JS_EXCEPTION; + r = tmp; + } else if (!is_binary_radix) { + /* need to modify 'r' */ + tmp = js_bigint_new(ctx, r->len); + if (!tmp) + return JS_EXCEPTION; + memcpy(tmp->tab, r->tab, r->len * sizeof(r->tab[0])); + r = tmp; + } + log2_radix = 31 - clz32(radix); /* floor(log2(radix)) */ + n_bits = r->len * JS_LIMB_BITS - js_limb_clz(r->tab[r->len - 1]); + /* n_digits is exact only if radix is a power of + two. Otherwise it is >= the exact number of digits */ + n_digits = (n_bits + log2_radix - 1) / log2_radix; + /* XXX: could directly build the JSString */ + buf = js_malloc(ctx, n_digits + is_neg + 1); + if (!buf) { + js_free(ctx, tmp); + return JS_EXCEPTION; + } + q = buf + n_digits + is_neg + 1; + *--q = '\0'; + buf_end = q; + if (!is_binary_radix) { + int len; + js_limb_t radix_base, v; + radix_base = js_radix_base_table[radix - 2]; + len = r->len; + for(;;) { + /* remove leading zero limbs */ + while (len > 1 && r->tab[len - 1] == 0) + len--; + if (len == 1 && r->tab[0] < radix_base) { + v = r->tab[0]; + if (v != 0) { + q = js_u64toa(q, v, radix); + } + break; + } else { + v = js_mp_div1(r->tab, r->tab, len, radix_base, 0); + q = js_limb_to_a(q, v, radix, js_digits_per_limb_table[radix - 2]); + } + } + } else { + int i, shift; + unsigned int bit_pos, pos, c; + + /* radix is a power of two */ + for(i = 0; i < n_digits; i++) { + bit_pos = i * log2_radix; + pos = bit_pos / JS_LIMB_BITS; + shift = bit_pos % JS_LIMB_BITS; + c = r->tab[pos] >> shift; + if ((shift + log2_radix) > JS_LIMB_BITS && + (pos + 1) < r->len) { + c |= r->tab[pos + 1] << (JS_LIMB_BITS - shift); + } + c &= (radix - 1); + *--q = digits[c]; + } + } + if (is_neg) + *--q = '-'; + js_free(ctx, tmp); + res = js_new_string8_len(ctx, q, buf_end - q); + js_free(ctx, buf); + return res; + } +} + +/* if possible transform a BigInt to short big and free it, otherwise + return a normal bigint */ +static JSValue JS_CompactBigInt(JSContext *ctx, JSBigInt *p) +{ + JSValue res; + if (p->len == 1) { + res = __JS_NewShortBigInt(ctx, (js_slimb_t)p->tab[0]); + js_free(ctx, p); + return res; + } else { + return JS_MKPTR(JS_TAG_BIG_INT, p); + } +} + +#define ATOD_INT_ONLY (1 << 0) +/* accept Oo and Ob prefixes in addition to 0x prefix if radix = 0 */ +#define ATOD_ACCEPT_BIN_OCT (1 << 2) +/* accept O prefix as octal if radix == 0 and properly formed (Annex B) */ +#define ATOD_ACCEPT_LEGACY_OCTAL (1 << 4) +/* accept _ between digits as a digit separator */ +#define ATOD_ACCEPT_UNDERSCORES (1 << 5) +/* allow a suffix to override the type */ +#define ATOD_ACCEPT_SUFFIX (1 << 6) +/* default type */ +#define ATOD_TYPE_MASK (3 << 7) +#define ATOD_TYPE_FLOAT64 (0 << 7) +#define ATOD_TYPE_BIG_INT (1 << 7) +/* accept -0x1 */ +#define ATOD_ACCEPT_PREFIX_AFTER_SIGN (1 << 10) + +/* return an exception in case of memory error. Return JS_NAN if + invalid syntax */ +/* XXX: directly use js_atod() */ +static JSValue js_atof(JSContext *ctx, const char *str, const char **pp, + int radix, int flags) +{ + const char *p, *p_start; + int sep, is_neg; + bool is_float, has_legacy_octal; + int atod_type = flags & ATOD_TYPE_MASK; + char buf1[64], *buf; + int i, j, len; + bool buf_allocated = false; + JSValue val; + JSATODTempMem atod_mem; + + /* optional separator between digits */ + sep = (flags & ATOD_ACCEPT_UNDERSCORES) ? '_' : 256; + has_legacy_octal = false; + + p = str; + p_start = p; + is_neg = 0; + if (p[0] == '+') { + p++; + p_start++; + if (!(flags & ATOD_ACCEPT_PREFIX_AFTER_SIGN)) + goto no_radix_prefix; + } else if (p[0] == '-') { + p++; + p_start++; + is_neg = 1; + if (!(flags & ATOD_ACCEPT_PREFIX_AFTER_SIGN)) + goto no_radix_prefix; + } + if (p[0] == '0') { + if ((p[1] == 'x' || p[1] == 'X') && + (radix == 0 || radix == 16)) { + p += 2; + radix = 16; + } else if ((p[1] == 'o' || p[1] == 'O') && + radix == 0 && (flags & ATOD_ACCEPT_BIN_OCT)) { + p += 2; + radix = 8; + } else if ((p[1] == 'b' || p[1] == 'B') && + radix == 0 && (flags & ATOD_ACCEPT_BIN_OCT)) { + p += 2; + radix = 2; + } else if ((p[1] >= '0' && p[1] <= '9') && + radix == 0 && (flags & ATOD_ACCEPT_LEGACY_OCTAL)) { + int i; + has_legacy_octal = true; + sep = 256; + for (i = 1; (p[i] >= '0' && p[i] <= '7'); i++) + continue; + if (p[i] == '8' || p[i] == '9') + goto no_prefix; + p += 1; + radix = 8; + } else { + goto no_prefix; + } + /* there must be a digit after the prefix */ + if (js_to_digit((uint8_t)*p) >= radix) + goto fail; + no_prefix: ; + } else { + no_radix_prefix: + if (!(flags & ATOD_INT_ONLY) && + (atod_type == ATOD_TYPE_FLOAT64) && + js__strstart(p, "Infinity", &p)) { + double d = INFINITY; + if (is_neg) + d = -d; + val = js_float64(d); + goto done; + } + } + if (radix == 0) + radix = 10; + is_float = false; + p_start = p; + while (js_to_digit((uint8_t)*p) < radix + || (*p == sep && (radix != 10 || + p != p_start + 1 || p[-1] != '0') && + js_to_digit((uint8_t)p[1]) < radix)) { + p++; + } + if (!(flags & ATOD_INT_ONLY) && radix == 10) { + if (*p == '.' && (p > p_start || js_to_digit((uint8_t)p[1]) < radix)) { + is_float = true; + p++; + if (*p == sep) + goto fail; + while (js_to_digit((uint8_t)*p) < radix || + (*p == sep && js_to_digit((uint8_t)p[1]) < radix)) + p++; + } + if (p > p_start && (*p == 'e' || *p == 'E')) { + const char *p1 = p + 1; + is_float = true; + if (*p1 == '+') { + p1++; + } else if (*p1 == '-') { + p1++; + } + if (is_digit((uint8_t)*p1)) { + p = p1 + 1; + while (is_digit((uint8_t)*p) || (*p == sep && is_digit((uint8_t)p[1]))) + p++; + } + } + } + if (p == p_start) + goto fail; + + buf = buf1; + buf_allocated = false; + len = p - p_start; + if (unlikely((len + 2) > sizeof(buf1))) { + buf = js_malloc_rt(ctx->rt, len + 2); /* no exception raised */ + if (!buf) + goto mem_error; + buf_allocated = true; + } + /* remove the separators and the radix prefixes */ + j = 0; + if (is_neg) + buf[j++] = '-'; + for (i = 0; i < len; i++) { + if (p_start[i] != '_') + buf[j++] = p_start[i]; + } + buf[j] = '\0'; + + if (flags & ATOD_ACCEPT_SUFFIX) { + if (*p == 'n') { + p++; + atod_type = ATOD_TYPE_BIG_INT; + } + } + + switch(atod_type) { + case ATOD_TYPE_FLOAT64: + { + double d; + d = js_atod(buf, NULL, radix, is_float ? 0 : JS_ATOD_INT_ONLY, + &atod_mem); + /* return int or float64 */ + val = js_number(d); + } + break; + case ATOD_TYPE_BIG_INT: + { + JSBigInt *r; + if (has_legacy_octal || is_float) + goto fail; + r = js_bigint_from_string(ctx, buf, radix); + if (!r) { + val = JS_EXCEPTION; + goto done; + } + val = JS_CompactBigInt(ctx, r); + } + break; + default: + abort(); + } + +done: + if (buf_allocated) + js_free_rt(ctx->rt, buf); + if (pp) + *pp = p; + return val; + fail: + val = JS_NAN; + goto done; + mem_error: + val = JS_ThrowOutOfMemory(ctx); + goto done; +} + +typedef enum JSToNumberHintEnum { + TON_FLAG_NUMBER, + TON_FLAG_NUMERIC, +} JSToNumberHintEnum; + +static JSValue JS_ToNumberHintFree(JSContext *ctx, JSValue val, + JSToNumberHintEnum flag) +{ + uint32_t tag; + JSValue ret; + + redo: + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_BIG_INT: + case JS_TAG_SHORT_BIG_INT: + if (flag != TON_FLAG_NUMERIC) { + JS_FreeValue(ctx, val); + return JS_ThrowTypeError(ctx, "cannot convert BigInt to number"); + } + ret = val; + break; + case JS_TAG_FLOAT64: + case JS_TAG_INT: + case JS_TAG_EXCEPTION: + ret = val; + break; + case JS_TAG_BOOL: + case JS_TAG_NULL: + ret = js_int32(JS_VALUE_GET_INT(val)); + break; + case JS_TAG_UNDEFINED: + ret = JS_NAN; + break; + case JS_TAG_OBJECT: + val = JS_ToPrimitiveFree(ctx, val, HINT_NUMBER); + if (JS_IsException(val)) + return JS_EXCEPTION; + goto redo; + case JS_TAG_STRING: + case JS_TAG_STRING_ROPE: + { + const char *str; + const char *p; + size_t len; + + str = JS_ToCStringLen(ctx, &len, val); + JS_FreeValue(ctx, val); + if (!str) + return JS_EXCEPTION; + p = str; + p += skip_spaces(p); + if ((p - str) == len) { + ret = JS_NewInt32(ctx, 0); + } else { + int flags = ATOD_ACCEPT_BIN_OCT; + ret = js_atof(ctx, p, &p, 0, flags); + if (!JS_IsException(ret)) { + p += skip_spaces(p); + if ((p - str) != len) { + JS_FreeValue(ctx, ret); + ret = JS_NAN; + } + } + } + JS_FreeCString(ctx, str); + } + break; + case JS_TAG_SYMBOL: + JS_FreeValue(ctx, val); + return JS_ThrowTypeError(ctx, "cannot convert symbol to number"); + default: + JS_FreeValue(ctx, val); + ret = JS_NAN; + break; + } + return ret; +} + +static JSValue JS_ToNumberFree(JSContext *ctx, JSValue val) +{ + return JS_ToNumberHintFree(ctx, val, TON_FLAG_NUMBER); +} + +static JSValue JS_ToNumericFree(JSContext *ctx, JSValue val) +{ + return JS_ToNumberHintFree(ctx, val, TON_FLAG_NUMERIC); +} + +static JSValue JS_ToNumeric(JSContext *ctx, JSValueConst val) +{ + return JS_ToNumericFree(ctx, js_dup(val)); +} + +static __exception int __JS_ToFloat64Free(JSContext *ctx, double *pres, + JSValue val) +{ + double d; + uint32_t tag; + + val = JS_ToNumberFree(ctx, val); + if (JS_IsException(val)) + goto fail; + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_INT: + d = JS_VALUE_GET_INT(val); + break; + case JS_TAG_FLOAT64: + d = JS_VALUE_GET_FLOAT64(val); + break; + default: + abort(); + } + *pres = d; + return 0; +fail: + *pres = NAN; + return -1; +} + +static inline int JS_ToFloat64Free(JSContext *ctx, double *pres, JSValue val) +{ + uint32_t tag; + + tag = JS_VALUE_GET_TAG(val); + if (tag <= JS_TAG_NULL) { + *pres = JS_VALUE_GET_INT(val); + return 0; + } else if (JS_TAG_IS_FLOAT64(tag)) { + *pres = JS_VALUE_GET_FLOAT64(val); + return 0; + } else { + return __JS_ToFloat64Free(ctx, pres, val); + } +} + +int JS_ToFloat64(JSContext *ctx, double *pres, JSValueConst val) +{ + return JS_ToFloat64Free(ctx, pres, js_dup(val)); +} + +JSValue JS_ToNumber(JSContext *ctx, JSValueConst val) +{ + return JS_ToNumberFree(ctx, js_dup(val)); +} + +/* same as JS_ToNumber() but return 0 in case of NaN/Undefined */ +static __maybe_unused JSValue JS_ToIntegerFree(JSContext *ctx, JSValue val) +{ + uint32_t tag; + JSValue ret; + + redo: + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_INT: + case JS_TAG_BOOL: + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + ret = js_int32(JS_VALUE_GET_INT(val)); + break; + case JS_TAG_FLOAT64: + { + double d = JS_VALUE_GET_FLOAT64(val); + if (isnan(d)) { + ret = js_int32(0); + } else { + /* convert -0 to +0 */ + d = trunc(d) + 0.0; + ret = js_number(d); + } + } + break; + default: + val = JS_ToNumberFree(ctx, val); + if (JS_IsException(val)) + return val; + goto redo; + } + return ret; +} + +/* Note: the integer value is satured to 32 bits */ +static int JS_ToInt32SatFree(JSContext *ctx, int *pres, JSValue val) +{ + uint32_t tag; + int ret; + + redo: + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_INT: + case JS_TAG_BOOL: + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + ret = JS_VALUE_GET_INT(val); + break; + case JS_TAG_EXCEPTION: + *pres = 0; + return -1; + case JS_TAG_FLOAT64: + { + double d = JS_VALUE_GET_FLOAT64(val); + if (isnan(d)) { + ret = 0; + } else { + if (d < INT32_MIN) + ret = INT32_MIN; + else if (d > INT32_MAX) + ret = INT32_MAX; + else + ret = (int)d; + } + } + break; + default: + val = JS_ToNumberFree(ctx, val); + if (JS_IsException(val)) { + *pres = 0; + return -1; + } + goto redo; + } + *pres = ret; + return 0; +} + +static int JS_ToInt32Sat(JSContext *ctx, int *pres, JSValueConst val) +{ + return JS_ToInt32SatFree(ctx, pres, js_dup(val)); +} + +static int JS_ToInt32Clamp(JSContext *ctx, int *pres, JSValueConst val, + int min, int max, int min_offset) +{ + int res = JS_ToInt32SatFree(ctx, pres, js_dup(val)); + if (res == 0) { + if (*pres < min) { + *pres += min_offset; + if (*pres < min) + *pres = min; + } else { + if (*pres > max) + *pres = max; + } + } + return res; +} + +static int JS_ToInt64SatFree(JSContext *ctx, int64_t *pres, JSValue val) +{ + uint32_t tag; + + redo: + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_INT: + case JS_TAG_BOOL: + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + *pres = JS_VALUE_GET_INT(val); + return 0; + case JS_TAG_EXCEPTION: + *pres = 0; + return -1; + case JS_TAG_FLOAT64: + { + double d = JS_VALUE_GET_FLOAT64(val); + if (isnan(d)) { + *pres = 0; + } else { + if (d < INT64_MIN) + *pres = INT64_MIN; + else if (d >= 0x1p63) + *pres = INT64_MAX; + else + *pres = (int64_t)d; + } + } + return 0; + default: + val = JS_ToNumberFree(ctx, val); + if (JS_IsException(val)) { + *pres = 0; + return -1; + } + goto redo; + } +} + +int JS_ToInt64Sat(JSContext *ctx, int64_t *pres, JSValueConst val) +{ + return JS_ToInt64SatFree(ctx, pres, js_dup(val)); +} + +int JS_ToInt64Clamp(JSContext *ctx, int64_t *pres, JSValueConst val, + int64_t min, int64_t max, int64_t neg_offset) +{ + int res = JS_ToInt64SatFree(ctx, pres, js_dup(val)); + if (res == 0) { + if (*pres < 0) + *pres += neg_offset; + if (*pres < min) + *pres = min; + else if (*pres > max) + *pres = max; + } + return res; +} + +/* Same as JS_ToInt32Free() but with a 64 bit result. Return (<0, 0) + in case of exception */ +static int JS_ToInt64Free(JSContext *ctx, int64_t *pres, JSValue val) +{ + uint32_t tag; + int64_t ret; + + redo: + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_INT: + case JS_TAG_BOOL: + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + ret = JS_VALUE_GET_INT(val); + break; + case JS_TAG_FLOAT64: + { + JSFloat64Union u; + double d; + int e; + d = JS_VALUE_GET_FLOAT64(val); + u.d = d; + /* we avoid doing fmod(x, 2^64) */ + e = (u.u64 >> 52) & 0x7ff; + if (likely(e <= (1023 + 62))) { + /* fast case */ + ret = (int64_t)d; + } else if (e <= (1023 + 62 + 53)) { + uint64_t v; + /* remainder modulo 2^64 */ + v = (u.u64 & (((uint64_t)1 << 52) - 1)) | ((uint64_t)1 << 52); + ret = v << ((e - 1023) - 52); + /* take the sign into account */ + if (u.u64 >> 63) + if (ret != INT64_MIN) + ret = -ret; + } else { + ret = 0; /* also handles NaN and +inf */ + } + } + break; + default: + val = JS_ToNumberFree(ctx, val); + if (JS_IsException(val)) { + *pres = 0; + return -1; + } + goto redo; + } + *pres = ret; + return 0; +} + +int JS_ToInt64(JSContext *ctx, int64_t *pres, JSValueConst val) +{ + return JS_ToInt64Free(ctx, pres, js_dup(val)); +} + +int JS_ToInt64Ext(JSContext *ctx, int64_t *pres, JSValueConst val) +{ + if (JS_IsBigInt(val)) + return JS_ToBigInt64(ctx, pres, val); + else + return JS_ToInt64(ctx, pres, val); +} + +/* return (<0, 0) in case of exception */ +static int JS_ToInt32Free(JSContext *ctx, int32_t *pres, JSValue val) +{ + uint32_t tag; + int32_t ret; + + redo: + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_INT: + case JS_TAG_BOOL: + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + ret = JS_VALUE_GET_INT(val); + break; + case JS_TAG_FLOAT64: + { + JSFloat64Union u; + double d; + int e; + d = JS_VALUE_GET_FLOAT64(val); + u.d = d; + /* we avoid doing fmod(x, 2^32) */ + e = (u.u64 >> 52) & 0x7ff; + if (likely(e <= (1023 + 30))) { + /* fast case */ + ret = (int32_t)d; + } else if (e <= (1023 + 30 + 53)) { + uint64_t v; + /* remainder modulo 2^32 */ + v = (u.u64 & (((uint64_t)1 << 52) - 1)) | ((uint64_t)1 << 52); + v = v << ((e - 1023) - 52 + 32); + ret = v >> 32; + /* take the sign into account */ + if (u.u64 >> 63) + if (ret != INT32_MIN) + ret = -ret; + } else { + ret = 0; /* also handles NaN and +inf */ + } + } + break; + default: + val = JS_ToNumberFree(ctx, val); + if (JS_IsException(val)) { + *pres = 0; + return -1; + } + goto redo; + } + *pres = ret; + return 0; +} + +int JS_ToInt32(JSContext *ctx, int32_t *pres, JSValueConst val) +{ + return JS_ToInt32Free(ctx, pres, js_dup(val)); +} + +static inline int JS_ToUint32Free(JSContext *ctx, uint32_t *pres, JSValue val) +{ + return JS_ToInt32Free(ctx, (int32_t *)pres, val); +} + +static int JS_ToUint8ClampFree(JSContext *ctx, int32_t *pres, JSValue val) +{ + uint32_t tag; + int res; + + redo: + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_INT: + case JS_TAG_BOOL: + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + res = JS_VALUE_GET_INT(val); + res = max_int(0, min_int(255, res)); + break; + case JS_TAG_FLOAT64: + { + double d = JS_VALUE_GET_FLOAT64(val); + if (isnan(d)) { + res = 0; + } else { + if (d < 0) + res = 0; + else if (d > 255) + res = 255; + else + res = lrint(d); + } + } + break; + default: + val = JS_ToNumberFree(ctx, val); + if (JS_IsException(val)) { + *pres = 0; + return -1; + } + goto redo; + } + *pres = res; + return 0; +} + +static __exception int JS_ToArrayLengthFree(JSContext *ctx, uint32_t *plen, + JSValue val, bool is_array_ctor) +{ + uint32_t tag, len; + + tag = JS_VALUE_GET_TAG(val); + switch(tag) { + case JS_TAG_INT: + case JS_TAG_BOOL: + case JS_TAG_NULL: + { + int v; + v = JS_VALUE_GET_INT(val); + if (v < 0) + goto fail; + len = v; + } + break; + default: + if (JS_TAG_IS_FLOAT64(tag)) { + double d; + d = JS_VALUE_GET_FLOAT64(val); + if (!(d >= 0 && d <= UINT32_MAX)) + goto fail; + len = (uint32_t)d; + if (len != d) + goto fail; + } else { + uint32_t len1; + + if (is_array_ctor) { + val = JS_ToNumberFree(ctx, val); + if (JS_IsException(val)) + return -1; + /* cannot recurse because val is a number */ + if (JS_ToArrayLengthFree(ctx, &len, val, true)) + return -1; + } else { + /* legacy behavior: must do the conversion twice and compare */ + if (JS_ToUint32(ctx, &len, val)) { + JS_FreeValue(ctx, val); + return -1; + } + val = JS_ToNumberFree(ctx, val); + if (JS_IsException(val)) + return -1; + /* cannot recurse because val is a number */ + if (JS_ToArrayLengthFree(ctx, &len1, val, false)) + return -1; + if (len1 != len) { + fail: + JS_ThrowRangeError(ctx, "invalid array length"); + return -1; + } + } + } + break; + } + *plen = len; + return 0; +} + +#define MAX_SAFE_INTEGER (((int64_t)1 << 53) - 1) + +static bool is_safe_integer(double d) +{ + return isfinite(d) && floor(d) == d && + fabs(d) <= (double)MAX_SAFE_INTEGER; +} + +int JS_ToIndex(JSContext *ctx, uint64_t *plen, JSValueConst val) +{ + int64_t v; + if (JS_ToInt64Sat(ctx, &v, val)) + return -1; + if (v < 0 || v > MAX_SAFE_INTEGER) { + JS_ThrowRangeError(ctx, "invalid array index"); + *plen = 0; + return -1; + } + *plen = v; + return 0; +} + +/* convert a value to a length between 0 and MAX_SAFE_INTEGER. + return -1 for exception */ +static __exception int JS_ToLengthFree(JSContext *ctx, int64_t *plen, + JSValue val) +{ + int res = JS_ToInt64Clamp(ctx, plen, val, 0, MAX_SAFE_INTEGER, 0); + JS_FreeValue(ctx, val); + return res; +} + +/* Note: can return an exception */ +static int JS_NumberIsInteger(JSContext *ctx, JSValueConst val) +{ + double d; + if (!JS_IsNumber(val)) + return false; + if (unlikely(JS_ToFloat64(ctx, &d, val))) + return -1; + return isfinite(d) && floor(d) == d; +} + +static bool JS_NumberIsNegativeOrMinusZero(JSContext *ctx, JSValueConst val) +{ + uint32_t tag; + + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_INT: + { + int v; + v = JS_VALUE_GET_INT(val); + return (v < 0); + } + case JS_TAG_FLOAT64: + { + JSFloat64Union u; + u.d = JS_VALUE_GET_FLOAT64(val); + return (u.u64 >> 63); + } + case JS_TAG_SHORT_BIG_INT: + return (JS_VALUE_GET_SHORT_BIG_INT(val) < 0); + case JS_TAG_BIG_INT: + { + JSBigInt *p = JS_VALUE_GET_PTR(val); + return js_bigint_sign(p); + } + default: + return false; + } +} + +static JSValue js_bigint_to_string(JSContext *ctx, JSValueConst val) +{ + return js_bigint_to_string1(ctx, val, 10); +} + +/*---- floating point number to string conversions ----*/ + +static JSValue js_dtoa2(JSContext *ctx, + double d, int radix, int n_digits, int flags) +{ + char static_buf[128], *buf, *tmp_buf; + int len, len_max; + JSValue res; + JSDTOATempMem dtoa_mem; + len_max = js_dtoa_max_len(d, radix, n_digits, flags); + + /* longer buffer may be used if radix != 10 */ + if (len_max > sizeof(static_buf) - 1) { + tmp_buf = js_malloc(ctx, len_max + 1); + if (!tmp_buf) + return JS_EXCEPTION; + buf = tmp_buf; + } else { + tmp_buf = NULL; + buf = static_buf; + } + len = js_dtoa(buf, d, radix, n_digits, flags, &dtoa_mem); + res = js_new_string8_len(ctx, buf, len); + js_free(ctx, tmp_buf); + return res; +} + +static JSValue JS_ToStringInternal(JSContext *ctx, JSValueConst val, + int flags) +{ + uint32_t tag; + char buf[32]; + size_t len; + + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_STRING: + return js_dup(val); + case JS_TAG_STRING_ROPE: + return js_linearize_string_rope(ctx, val); + case JS_TAG_INT: + len = i32toa(buf, JS_VALUE_GET_INT(val)); + return js_new_string8_len(ctx, buf, len); + case JS_TAG_BOOL: + return JS_AtomToString(ctx, JS_VALUE_GET_BOOL(val) ? + JS_ATOM_true : JS_ATOM_false); + case JS_TAG_NULL: + return JS_AtomToString(ctx, JS_ATOM_null); + case JS_TAG_UNDEFINED: + return JS_AtomToString(ctx, JS_ATOM_undefined); + case JS_TAG_EXCEPTION: + return JS_EXCEPTION; + case JS_TAG_OBJECT: + if (flags & JS_TO_STRING_NO_SIDE_EFFECTS) { + return js_new_string8(ctx, "{}"); + } else { + JSValue val1, ret; + val1 = JS_ToPrimitive(ctx, val, HINT_STRING); + if (JS_IsException(val1)) + return val1; + ret = JS_ToStringInternal(ctx, val1, flags); + JS_FreeValue(ctx, val1); + return ret; + } + break; + case JS_TAG_FUNCTION_BYTECODE: + return js_new_string8(ctx, "[function bytecode]"); + case JS_TAG_SYMBOL: + if (flags & JS_TO_STRING_IS_PROPERTY_KEY) { + return js_dup(val); + } else { + return JS_ThrowTypeError(ctx, "cannot convert symbol to string"); + } + case JS_TAG_FLOAT64: + return js_dtoa2(ctx, JS_VALUE_GET_FLOAT64(val), 10, 0, + JS_DTOA_FORMAT_FREE); + case JS_TAG_SHORT_BIG_INT: + case JS_TAG_BIG_INT: + return js_bigint_to_string(ctx, val); + case JS_TAG_UNINITIALIZED: + return js_new_string8(ctx, "[uninitialized]"); + default: + return js_new_string8(ctx, "[unsupported type]"); + } +} + +JSValue JS_ToString(JSContext *ctx, JSValueConst val) +{ + return JS_ToStringInternal(ctx, val, /*flags*/0); +} + +static JSValue JS_ToStringFree(JSContext *ctx, JSValue val) +{ + if (JS_VALUE_GET_TAG(val) == JS_TAG_STRING) + return val; + JSValue ret = JS_ToString(ctx, val); + JS_FreeValue(ctx, val); + return ret; +} + +static JSValue JS_ToLocaleStringFree(JSContext *ctx, JSValue val) +{ + if (JS_IsUndefined(val) || JS_IsNull(val)) + return JS_ToStringFree(ctx, val); + return JS_InvokeFree(ctx, val, JS_ATOM_toLocaleString, 0, NULL); +} + +static JSValue JS_ToPropertyKeyInternal(JSContext *ctx, JSValueConst val, + int flags) +{ + return JS_ToStringInternal(ctx, val, flags | JS_TO_STRING_IS_PROPERTY_KEY); +} + +JSValue JS_ToPropertyKey(JSContext *ctx, JSValueConst val) +{ + return JS_ToPropertyKeyInternal(ctx, val, /*flags*/0); +} + +static JSValue JS_ToStringCheckObject(JSContext *ctx, JSValueConst val) +{ + uint32_t tag = JS_VALUE_GET_TAG(val); + if (tag == JS_TAG_NULL || tag == JS_TAG_UNDEFINED) + return JS_ThrowTypeError(ctx, "null or undefined are forbidden"); + return JS_ToString(ctx, val); +} + +static JSValue JS_ToQuotedString(JSContext *ctx, JSValueConst val1) +{ + JSValue val; + JSString *p; + int i; + uint32_t c; + StringBuffer b_s, *b = &b_s; + char buf[16]; + + val = JS_ToStringCheckObject(ctx, val1); + if (JS_IsException(val)) + return val; + p = JS_VALUE_GET_STRING(val); + + if (string_buffer_init(ctx, b, p->len + 2)) + goto fail; + + if (string_buffer_putc8(b, '\"')) + goto fail; + for(i = 0; i < p->len; ) { + c = string_getc(p, &i); + switch(c) { + case '\t': + c = 't'; + goto quote; + case '\r': + c = 'r'; + goto quote; + case '\n': + c = 'n'; + goto quote; + case '\b': + c = 'b'; + goto quote; + case '\f': + c = 'f'; + goto quote; + case '\"': + case '\\': + quote: + if (string_buffer_putc8(b, '\\')) + goto fail; + if (string_buffer_putc8(b, c)) + goto fail; + break; + default: + if (c < 32 || is_surrogate(c)) { + snprintf(buf, sizeof(buf), "\\u%04x", c); + if (string_buffer_write8(b, (uint8_t*)buf, 6)) + goto fail; + } else { + if (string_buffer_putc(b, c)) + goto fail; + } + break; + } + } + if (string_buffer_putc8(b, '\"')) + goto fail; + JS_FreeValue(ctx, val); + return string_buffer_end(b); + fail: + JS_FreeValue(ctx, val); + string_buffer_free(b); + return JS_EXCEPTION; +} + +static __maybe_unused void JS_DumpObjectHeader(JSRuntime *rt) +{ + printf("%14s %4s %4s %14s %10s %s\n", + "ADDRESS", "REFS", "SHRF", "PROTO", "CLASS", "PROPS"); +} + +/* for debug only: dump an object without side effect */ +static __maybe_unused void JS_DumpObject(JSRuntime *rt, JSObject *p) +{ + uint32_t i; + char atom_buf[ATOM_GET_STR_BUF_SIZE]; + JSShape *sh; + JSShapeProperty *prs; + JSProperty *pr; + bool is_first = true; + + /* XXX: should encode atoms with special characters */ + sh = p->shape; /* the shape can be NULL while freeing an object */ + printf("%14p %4d ", + (void *)p, + JS_REF_COUNT(p)); + if (sh) { + printf("%3d%c %14p ", + JS_REF_COUNT(sh), + " *"[sh->is_hashed], + (void *)sh->proto); + } else { + printf("%3s %14s ", "-", "-"); + } + printf("%10s ", + JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), rt->class_array[p->class_id].class_name)); + if (p->is_exotic && p->fast_array) { + printf("[ "); + for(i = 0; i < p->u.array.count; i++) { + if (i != 0) + printf(", "); + switch (p->class_id) { + case JS_CLASS_ARRAY: + case JS_CLASS_ARGUMENTS: + JS_DumpValue(rt, p->u.array.u.values[i]); + break; + case JS_CLASS_UINT8C_ARRAY: + case JS_CLASS_INT8_ARRAY: + case JS_CLASS_UINT8_ARRAY: + case JS_CLASS_INT16_ARRAY: + case JS_CLASS_UINT16_ARRAY: + case JS_CLASS_INT32_ARRAY: + case JS_CLASS_UINT32_ARRAY: + case JS_CLASS_BIG_INT64_ARRAY: + case JS_CLASS_BIG_UINT64_ARRAY: + case JS_CLASS_FLOAT16_ARRAY: + case JS_CLASS_FLOAT32_ARRAY: + case JS_CLASS_FLOAT64_ARRAY: + { + int size = 1 << typed_array_size_log2(p->class_id); + const uint8_t *b = p->u.array.u.uint8_ptr + i * size; + while (size-- > 0) + printf("%02X", *b++); + } + break; + } + } + printf(" ] "); + } + + if (sh) { + printf("{ "); + for(i = 0, prs = get_shape_prop(sh); i < sh->prop_count; i++, prs++) { + if (prs->atom != JS_ATOM_NULL) { + pr = &p->prop[i]; + if (!is_first) + printf(", "); + printf("%s: ", + JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), prs->atom)); + if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { + printf("[getset %p %p]", (void *)pr->u.getset.getter, + (void *)pr->u.getset.setter); + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { + printf("[varref %p]", (void *)pr->u.var_ref); + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { + printf("[autoinit %p %d %p]", + (void *)js_autoinit_get_realm(pr), + js_autoinit_get_id(pr), + (void *)pr->u.init.opaque); + } else { + JS_DumpValue(rt, pr->u.value); + } + is_first = false; + } + } + printf(" }"); + } + + if (js_class_has_bytecode(p->class_id)) { + JSFunctionBytecode *b = p->u.func.function_bytecode; + JSVarRef **var_refs; + if (b->closure_var_count) { + var_refs = p->u.func.var_refs; + printf(" Closure:"); + for(i = 0; i < b->closure_var_count; i++) { + printf(" "); + JS_DumpValue(rt, var_refs[i]->value); + } + if (p->u.func.home_object) { + printf(" HomeObject: "); + JS_DumpValue(rt, JS_MKPTR(JS_TAG_OBJECT, p->u.func.home_object)); + } + } + } + printf("\n"); +} + +static __maybe_unused void JS_DumpGCObject(JSRuntime *rt, JSGCObjectHeader *p) +{ + if (JS_GC_TYPE(p) == JS_GC_OBJ_TYPE_JS_OBJECT) { + JS_DumpObject(rt, (JSObject *)p); + } else { + printf("%14p %4d ", + (void *)p, + JS_REF_COUNT(p)); + switch(JS_GC_TYPE(p)) { + case JS_GC_OBJ_TYPE_FUNCTION_BYTECODE: + printf("[function bytecode]"); + break; + case JS_GC_OBJ_TYPE_SHAPE: + printf("[shape]"); + break; + case JS_GC_OBJ_TYPE_VAR_REF: + printf("[var_ref]"); + break; + case JS_GC_OBJ_TYPE_ASYNC_FUNCTION: + printf("[async_function]"); + break; + case JS_GC_OBJ_TYPE_JS_CONTEXT: + printf("[js_context]"); + break; + default: + printf("[unknown %d]", JS_GC_TYPE(p)); + break; + } + printf("\n"); + } +} + +static __maybe_unused void JS_DumpValue(JSRuntime *rt, JSValueConst val) +{ + uint32_t tag = JS_VALUE_GET_NORM_TAG(val); + const char *str; + + switch(tag) { + case JS_TAG_INT: + printf("%d", JS_VALUE_GET_INT(val)); + break; + case JS_TAG_BOOL: + if (JS_VALUE_GET_BOOL(val)) + str = "true"; + else + str = "false"; + goto print_str; + case JS_TAG_NULL: + str = "null"; + goto print_str; + case JS_TAG_EXCEPTION: + str = "exception"; + goto print_str; + case JS_TAG_UNINITIALIZED: + str = "uninitialized"; + goto print_str; + case JS_TAG_UNDEFINED: + str = "undefined"; + print_str: + printf("%s", str); + break; + case JS_TAG_FLOAT64: + printf("%.14g", JS_VALUE_GET_FLOAT64(val)); + break; + case JS_TAG_SHORT_BIG_INT: + printf("%" PRId64 "n", (int64_t)JS_VALUE_GET_SHORT_BIG_INT(val)); + break; + case JS_TAG_BIG_INT: + { + JSBigInt *p = JS_VALUE_GET_PTR(val); + int sgn, i; + /* In order to avoid allocations we just dump the limbs */ + sgn = js_bigint_sign(p); + if (sgn) + printf("BigInt.asIntN(%d,", p->len * JS_LIMB_BITS); + printf("0x"); + for(i = p->len - 1; i >= 0; i--) { + if (i != p->len - 1) + printf("_"); + printf("%08x", p->tab[i]); + } + printf("n"); + if (sgn) + printf(")"); + } + break; + case JS_TAG_STRING: + { + JSString *p; + p = JS_VALUE_GET_STRING(val); + JS_DumpString(rt, p); + } + break; + case JS_TAG_STRING_ROPE: + { + JSStringRope *r = JS_VALUE_GET_STRING_ROPE(val); + printf("[rope len=%d depth=%d]", r->len, r->depth); + } + break; + case JS_TAG_FUNCTION_BYTECODE: + { + JSFunctionBytecode *b = JS_VALUE_GET_PTR(val); + char buf[ATOM_GET_STR_BUF_SIZE]; + if (b->func_name) { + printf("[bytecode %s]", JS_AtomGetStrRT(rt, buf, sizeof(buf), b->func_name)); + } else { + printf("[bytecode (anonymous)]"); + } + } + break; + case JS_TAG_OBJECT: + { + JSObject *p = JS_VALUE_GET_OBJ(val); + JSAtom atom = rt->class_array[p->class_id].class_name; + char atom_buf[ATOM_GET_STR_BUF_SIZE]; + printf("[%s %p]", + JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), atom), (void *)p); + } + break; + case JS_TAG_SYMBOL: + { + JSAtomStruct *p = JS_VALUE_GET_PTR(val); + char atom_buf[ATOM_GET_STR_BUF_SIZE]; + printf("Symbol(%s)", + JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), js_get_atom_index(rt, p))); + } + break; + case JS_TAG_MODULE: + printf("[module]"); + break; + default: + printf("[unknown tag %d]", tag); + break; + } +} + +bool JS_IsArray(JSValueConst val) +{ + if (JS_VALUE_GET_TAG(val) == JS_TAG_OBJECT) { + JSObject *p = JS_VALUE_GET_OBJ(val); + return p->class_id == JS_CLASS_ARRAY; + } + return false; +} + +/* return -1 if exception (proxy case) or true/false */ +static int js_is_array(JSContext *ctx, JSValueConst val) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(val) == JS_TAG_OBJECT) { + p = JS_VALUE_GET_OBJ(val); + if (unlikely(p->class_id == JS_CLASS_PROXY)) + return js_proxy_isArray(ctx, val); + else + return p->class_id == JS_CLASS_ARRAY; + } else { + return false; + } +} + +static double js_math_pow(double a, double b) +{ + double d; + + if (unlikely(!isfinite(b)) && fabs(a) == 1) { + /* not compatible with IEEE 754 */ + d = NAN; + } else { + JS_X87_FPCW_SAVE_AND_ADJUST(fpcw); + d = pow(a, b); + JS_X87_FPCW_RESTORE(fpcw); + } + return d; +} + +JSValue JS_NewBigInt64(JSContext *ctx, int64_t v) +{ + if (v >= JS_SHORT_BIG_INT_MIN && v <= JS_SHORT_BIG_INT_MAX) { + return __JS_NewShortBigInt(ctx, v); + } else { + JSBigInt *p; + p = js_bigint_new_si64(ctx, v); + if (!p) + return JS_EXCEPTION; + return JS_MKPTR(JS_TAG_BIG_INT, p); + } +} + +JSValue JS_NewBigUint64(JSContext *ctx, uint64_t v) +{ + if (v <= JS_SHORT_BIG_INT_MAX) { + return __JS_NewShortBigInt(ctx, v); + } else { + JSBigInt *p; + p = js_bigint_new_ui64(ctx, v); + if (!p) + return JS_EXCEPTION; + return JS_MKPTR(JS_TAG_BIG_INT, p); + } +} + +/* return NaN if bad bigint literal */ +static JSValue JS_StringToBigInt(JSContext *ctx, JSValue val) +{ + const char *str, *p; + size_t len; + int flags; + + str = JS_ToCStringLen(ctx, &len, val); + JS_FreeValue(ctx, val); + if (!str) + return JS_EXCEPTION; + p = str; + p += skip_spaces(p); + if ((p - str) == len) { + val = JS_NewBigInt64(ctx, 0); + } else { + flags = ATOD_INT_ONLY | ATOD_ACCEPT_BIN_OCT | ATOD_TYPE_BIG_INT; + val = js_atof(ctx, p, &p, 0, flags); + p += skip_spaces(p); + if (!JS_IsException(val)) { + if ((p - str) != len) { + JS_FreeValue(ctx, val); + val = JS_NAN; + } + } + } + JS_FreeCString(ctx, str); + return val; +} + +static JSValue JS_StringToBigIntErr(JSContext *ctx, JSValue val) +{ + val = JS_StringToBigInt(ctx, val); + if (JS_VALUE_IS_NAN(val)) + return JS_ThrowSyntaxError(ctx, "invalid BigInt literal"); + return val; +} + +/* JS Numbers are not allowed */ +static JSValue JS_ToBigIntFree(JSContext *ctx, JSValue val) +{ + uint32_t tag; + + redo: + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_SHORT_BIG_INT: + case JS_TAG_BIG_INT: + break; + case JS_TAG_INT: + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + case JS_TAG_FLOAT64: + goto fail; + case JS_TAG_BOOL: + val = __JS_NewShortBigInt(ctx, JS_VALUE_GET_INT(val)); + break; + case JS_TAG_STRING: + case JS_TAG_STRING_ROPE: + val = JS_StringToBigIntErr(ctx, val); + if (JS_IsException(val)) + return val; + goto redo; + case JS_TAG_OBJECT: + val = JS_ToPrimitiveFree(ctx, val, HINT_NUMBER); + if (JS_IsException(val)) + return val; + goto redo; + default: + fail: + JS_FreeValue(ctx, val); + return JS_ThrowTypeError(ctx, "cannot convert to bigint"); + } + return val; +} + +static JSValue JS_ToBigInt(JSContext *ctx, JSValueConst val) +{ + return JS_ToBigIntFree(ctx, js_dup(val)); +} + +/* XXX: merge with JS_ToInt64Free with a specific flag */ +static int JS_ToBigInt64Free(JSContext *ctx, int64_t *pres, JSValue val) +{ + uint64_t res; + + val = JS_ToBigIntFree(ctx, val); + if (JS_IsException(val)) { + *pres = 0; + return -1; + } + if (JS_VALUE_GET_TAG(val) == JS_TAG_SHORT_BIG_INT) { + res = JS_VALUE_GET_SHORT_BIG_INT(val); + } else { + JSBigInt *p = JS_VALUE_GET_PTR(val); + /* return the value mod 2^64 */ + res = p->tab[0]; + if (p->len >= 2) + res |= (uint64_t)p->tab[1] << 32; + JS_FreeValue(ctx, val); + } + *pres = res; + return 0; +} + +int JS_ToBigInt64(JSContext *ctx, int64_t *pres, JSValueConst val) +{ + return JS_ToBigInt64Free(ctx, pres, js_dup(val)); +} + +int JS_ToBigUint64(JSContext *ctx, uint64_t *pres, JSValueConst val) +{ + return JS_ToBigInt64Free(ctx, (int64_t *)pres, js_dup(val)); +} + +static no_inline __exception int js_unary_arith_slow(JSContext *ctx, + JSValue *sp, + OPCodeEnum op) +{ + JSValue op1; + int v; + uint32_t tag; + JSBigIntBuf buf1; + JSBigInt *p1; + + op1 = sp[-1]; + /* fast path for float64 */ + if (JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op1))) + goto handle_float64; + op1 = JS_ToNumericFree(ctx, op1); + if (JS_IsException(op1)) + goto exception; + tag = JS_VALUE_GET_TAG(op1); + switch(tag) { + case JS_TAG_INT: + { + int64_t v64; + v64 = JS_VALUE_GET_INT(op1); + switch(op) { + case OP_inc: + case OP_dec: + v = 2 * (op - OP_dec) - 1; + v64 += v; + break; + case OP_plus: + break; + case OP_neg: + if (v64 == 0) { + sp[-1] = js_float64(-0.0); + return 0; + } else { + v64 = -v64; + } + break; + default: + abort(); + } + sp[-1] = js_int64(v64); + } + break; + case JS_TAG_SHORT_BIG_INT: + { + int64_t v; + v = JS_VALUE_GET_SHORT_BIG_INT(op1); + switch(op) { + case OP_plus: + JS_ThrowTypeError(ctx, "bigint argument with unary +"); + goto exception; + case OP_inc: + if (v == JS_SHORT_BIG_INT_MAX) + goto bigint_slow_case; + sp[-1] = __JS_NewShortBigInt(ctx, v + 1); + break; + case OP_dec: + if (v == JS_SHORT_BIG_INT_MIN) + goto bigint_slow_case; + sp[-1] = __JS_NewShortBigInt(ctx, v - 1); + break; + case OP_neg: + v = JS_VALUE_GET_SHORT_BIG_INT(op1); + if (v == JS_SHORT_BIG_INT_MIN) { + bigint_slow_case: + p1 = js_bigint_set_short(&buf1, op1); + goto bigint_slow_case1; + } + sp[-1] = __JS_NewShortBigInt(ctx, -v); + break; + default: + abort(); + } + } + break; + case JS_TAG_BIG_INT: + { + JSBigInt *r; + p1 = JS_VALUE_GET_PTR(op1); + bigint_slow_case1: + switch(op) { + case OP_plus: + JS_ThrowTypeError(ctx, "bigint argument with unary +"); + JS_FreeValue(ctx, op1); + goto exception; + case OP_inc: + case OP_dec: + { + JSBigIntBuf buf2; + JSBigInt *p2; + p2 = js_bigint_set_si(&buf2, 2 * (op - OP_dec) - 1); + r = js_bigint_add(ctx, p1, p2, 0); + } + break; + case OP_neg: + r = js_bigint_neg(ctx, p1); + break; + case OP_not: + r = js_bigint_not(ctx, p1); + break; + default: + abort(); + } + JS_FreeValue(ctx, op1); + if (!r) + goto exception; + sp[-1] = JS_CompactBigInt(ctx, r); + } + break; + default: + handle_float64: + { + double d; + d = JS_VALUE_GET_FLOAT64(op1); + switch(op) { + case OP_inc: + case OP_dec: + v = 2 * (op - OP_dec) - 1; + d += v; + break; + case OP_plus: + break; + case OP_neg: + d = -d; + break; + default: + abort(); + } + sp[-1] = js_float64(d); + } + break; + } + return 0; + exception: + sp[-1] = JS_UNDEFINED; + return -1; +} + +static __exception int js_post_inc_slow(JSContext *ctx, + JSValue *sp, OPCodeEnum op) +{ + JSValue op1; + + /* XXX: allow custom operators */ + op1 = sp[-1]; + op1 = JS_ToNumericFree(ctx, op1); + if (JS_IsException(op1)) { + sp[-1] = JS_UNDEFINED; + return -1; + } + sp[-1] = op1; + sp[0] = js_dup(op1); + return js_unary_arith_slow(ctx, sp + 1, op - OP_post_dec + OP_dec); +} + +static no_inline int js_not_slow(JSContext *ctx, JSValue *sp) +{ + JSValue op1; + + op1 = sp[-1]; + op1 = JS_ToNumericFree(ctx, op1); + if (JS_IsException(op1)) + goto exception; + if (JS_VALUE_GET_TAG(op1) == JS_TAG_SHORT_BIG_INT) { + sp[-1] = __JS_NewShortBigInt(ctx, ~JS_VALUE_GET_SHORT_BIG_INT(op1)); + } else if (JS_VALUE_GET_TAG(op1) == JS_TAG_BIG_INT) { + JSBigInt *r; + r = js_bigint_not(ctx, JS_VALUE_GET_PTR(op1)); + JS_FreeValue(ctx, op1); + if (!r) + goto exception; + sp[-1] = JS_CompactBigInt(ctx, r); + } else { + int32_t v1; + if (unlikely(JS_ToInt32Free(ctx, &v1, op1))) + goto exception; + sp[-1] = js_int32(~v1); + } + return 0; + exception: + sp[-1] = JS_UNDEFINED; + return -1; +} + +static no_inline __exception int js_binary_arith_slow(JSContext *ctx, JSValue *sp, + OPCodeEnum op) +{ + JSValue op1, op2; + uint32_t tag1, tag2; + double d1, d2; + + op1 = sp[-2]; + op2 = sp[-1]; + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + /* fast path for float operations */ + if (tag1 == JS_TAG_FLOAT64 && tag2 == JS_TAG_FLOAT64) { + d1 = JS_VALUE_GET_FLOAT64(op1); + d2 = JS_VALUE_GET_FLOAT64(op2); + goto handle_float64; + } + /* fast path for short big int operations */ + if (tag1 == JS_TAG_SHORT_BIG_INT && tag2 == JS_TAG_SHORT_BIG_INT) { + js_slimb_t v1, v2; + js_sdlimb_t v; + v1 = JS_VALUE_GET_SHORT_BIG_INT(op1); + v2 = JS_VALUE_GET_SHORT_BIG_INT(op2); + switch(op) { + case OP_sub: + v = (js_sdlimb_t)v1 - (js_sdlimb_t)v2; + break; + case OP_mul: + v = (js_sdlimb_t)v1 * (js_sdlimb_t)v2; + break; + case OP_div: + if (v2 == 0 || + ((js_limb_t)v1 == (js_limb_t)1 << (JS_LIMB_BITS - 1) && + v2 == -1)) { + goto slow_big_int; + } + sp[-2] = __JS_NewShortBigInt(ctx, v1 / v2); + return 0; + case OP_mod: + if (v2 == 0 || + ((js_limb_t)v1 == (js_limb_t)1 << (JS_LIMB_BITS - 1) && + v2 == -1)) { + goto slow_big_int; + } + sp[-2] = __JS_NewShortBigInt(ctx, v1 % v2); + return 0; + case OP_pow: + goto slow_big_int; + default: + abort(); + } + if (likely(v >= JS_SHORT_BIG_INT_MIN && v <= JS_SHORT_BIG_INT_MAX)) { + sp[-2] = __JS_NewShortBigInt(ctx, v); + } else { + JSBigInt *r = js_bigint_new_di(ctx, v); + if (!r) + goto exception; + sp[-2] = JS_MKPTR(JS_TAG_BIG_INT, r); + } + return 0; + } + op1 = JS_ToNumericFree(ctx, op1); + if (JS_IsException(op1)) { + JS_FreeValue(ctx, op2); + goto exception; + } + op2 = JS_ToNumericFree(ctx, op2); + if (JS_IsException(op2)) { + JS_FreeValue(ctx, op1); + goto exception; + } + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + + if (tag1 == JS_TAG_INT && tag2 == JS_TAG_INT) { + int32_t v1, v2; + int64_t v; + v1 = JS_VALUE_GET_INT(op1); + v2 = JS_VALUE_GET_INT(op2); + switch(op) { + case OP_sub: + v = (int64_t)v1 - (int64_t)v2; + break; + case OP_mul: + v = (int64_t)v1 * (int64_t)v2; + if (v == 0 && (v1 | v2) < 0) { + sp[-2] = js_float64(-0.0); + return 0; + } + break; + case OP_div: + { + JS_X87_FPCW_SAVE_AND_ADJUST(fpcw); + sp[-2] = js_number((double)v1 / (double)v2); + JS_X87_FPCW_RESTORE(fpcw); + } + return 0; + case OP_mod: + if (v1 < 0 || v2 <= 0) { + JS_X87_FPCW_SAVE_AND_ADJUST(fpcw); + sp[-2] = js_number(fmod(v1, v2)); + JS_X87_FPCW_RESTORE(fpcw); + return 0; + } else { + v = (int64_t)v1 % (int64_t)v2; + } + break; + case OP_pow: + sp[-2] = js_number(js_math_pow(v1, v2)); + return 0; + default: + abort(); + } + sp[-2] = js_int64(v); + } else if ((tag1 == JS_TAG_SHORT_BIG_INT || tag1 == JS_TAG_BIG_INT) && + (tag2 == JS_TAG_SHORT_BIG_INT || tag2 == JS_TAG_BIG_INT)) { + JSBigInt *p1, *p2, *r; + JSBigIntBuf buf1, buf2; + slow_big_int: + /* bigint result */ + if (JS_VALUE_GET_TAG(op1) == JS_TAG_SHORT_BIG_INT) + p1 = js_bigint_set_short(&buf1, op1); + else + p1 = JS_VALUE_GET_PTR(op1); + if (JS_VALUE_GET_TAG(op2) == JS_TAG_SHORT_BIG_INT) + p2 = js_bigint_set_short(&buf2, op2); + else + p2 = JS_VALUE_GET_PTR(op2); + switch(op) { + case OP_add: + r = js_bigint_add(ctx, p1, p2, 0); + break; + case OP_sub: + r = js_bigint_add(ctx, p1, p2, 1); + break; + case OP_mul: + r = js_bigint_mul(ctx, p1, p2); + break; + case OP_div: + r = js_bigint_divrem(ctx, p1, p2, false); + break; + case OP_mod: + r = js_bigint_divrem(ctx, p1, p2, true); + break; + case OP_pow: + r = js_bigint_pow(ctx, p1, p2); + break; + default: + abort(); + } + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + if (!r) + goto exception; + sp[-2] = JS_CompactBigInt(ctx, r); + } else { + double dr; + /* float64 result */ + if (JS_ToFloat64Free(ctx, &d1, op1)) { + JS_FreeValue(ctx, op2); + goto exception; + } + if (JS_ToFloat64Free(ctx, &d2, op2)) + goto exception; + handle_float64: + JS_X87_FPCW_SAVE_AND_ADJUST(fpcw); + switch(op) { + case OP_sub: + dr = d1 - d2; + break; + case OP_mul: + dr = d1 * d2; + break; + case OP_div: + dr = d1 / d2; + break; + case OP_mod: + dr = fmod(d1, d2); + break; + case OP_pow: + dr = js_math_pow(d1, d2); + break; + default: + abort(); + } + JS_X87_FPCW_RESTORE(fpcw); + sp[-2] = js_float64(dr); + } + return 0; + exception: + sp[-2] = JS_UNDEFINED; + sp[-1] = JS_UNDEFINED; + return -1; +} + +static no_inline __exception int js_add_slow(JSContext *ctx, JSValue *sp) +{ + JSValue op1, op2; + uint32_t tag1, tag2; + + op1 = sp[-2]; + op2 = sp[-1]; + + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + /* fast path for float64 */ + if (tag1 == JS_TAG_FLOAT64 && tag2 == JS_TAG_FLOAT64) { + double d1, d2; + d1 = JS_VALUE_GET_FLOAT64(op1); + d2 = JS_VALUE_GET_FLOAT64(op2); + sp[-2] = js_float64(d1 + d2); + return 0; + } + /* fast path for short bigint */ + if (tag1 == JS_TAG_SHORT_BIG_INT && tag2 == JS_TAG_SHORT_BIG_INT) { + js_slimb_t v1, v2; + js_sdlimb_t v; + v1 = JS_VALUE_GET_SHORT_BIG_INT(op1); + v2 = JS_VALUE_GET_SHORT_BIG_INT(op2); + v = (js_sdlimb_t)v1 + (js_sdlimb_t)v2; + if (likely(v >= JS_SHORT_BIG_INT_MIN && v <= JS_SHORT_BIG_INT_MAX)) { + sp[-2] = __JS_NewShortBigInt(ctx, v); + } else { + JSBigInt *r = js_bigint_new_di(ctx, v); + if (!r) + goto exception; + sp[-2] = JS_MKPTR(JS_TAG_BIG_INT, r); + } + return 0; + } + + if (tag1 == JS_TAG_OBJECT || tag2 == JS_TAG_OBJECT) { + op1 = JS_ToPrimitiveFree(ctx, op1, HINT_NONE); + if (JS_IsException(op1)) { + JS_FreeValue(ctx, op2); + goto exception; + } + + op2 = JS_ToPrimitiveFree(ctx, op2, HINT_NONE); + if (JS_IsException(op2)) { + JS_FreeValue(ctx, op1); + goto exception; + } + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + } + + if (tag_is_string(tag1) || tag_is_string(tag2)) { + sp[-2] = JS_ConcatString(ctx, op1, op2); + if (JS_IsException(sp[-2])) + goto exception; + return 0; + } + + op1 = JS_ToNumericFree(ctx, op1); + if (JS_IsException(op1)) { + JS_FreeValue(ctx, op2); + goto exception; + } + op2 = JS_ToNumericFree(ctx, op2); + if (JS_IsException(op2)) { + JS_FreeValue(ctx, op1); + goto exception; + } + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + + if (tag1 == JS_TAG_INT && tag2 == JS_TAG_INT) { + int32_t v1, v2; + int64_t v; + v1 = JS_VALUE_GET_INT(op1); + v2 = JS_VALUE_GET_INT(op2); + v = (int64_t)v1 + (int64_t)v2; + sp[-2] = js_int64(v); + } else if ((tag1 == JS_TAG_BIG_INT || tag1 == JS_TAG_SHORT_BIG_INT) && + (tag2 == JS_TAG_BIG_INT || tag2 == JS_TAG_SHORT_BIG_INT)) { + JSBigInt *p1, *p2, *r; + JSBigIntBuf buf1, buf2; + /* bigint result */ + if (JS_VALUE_GET_TAG(op1) == JS_TAG_SHORT_BIG_INT) + p1 = js_bigint_set_short(&buf1, op1); + else + p1 = JS_VALUE_GET_PTR(op1); + if (JS_VALUE_GET_TAG(op2) == JS_TAG_SHORT_BIG_INT) + p2 = js_bigint_set_short(&buf2, op2); + else + p2 = JS_VALUE_GET_PTR(op2); + r = js_bigint_add(ctx, p1, p2, 0); + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + if (!r) + goto exception; + sp[-2] = JS_CompactBigInt(ctx, r); + } else { + double d1, d2; + /* float64 result */ + if (JS_ToFloat64Free(ctx, &d1, op1)) { + JS_FreeValue(ctx, op2); + goto exception; + } + if (JS_ToFloat64Free(ctx, &d2, op2)) + goto exception; + JS_X87_FPCW_SAVE_AND_ADJUST(fpcw); + sp[-2] = js_float64(d1 + d2); + JS_X87_FPCW_RESTORE(fpcw); + } + return 0; + exception: + sp[-2] = JS_UNDEFINED; + sp[-1] = JS_UNDEFINED; + return -1; +} + +static no_inline __exception int js_binary_logic_slow(JSContext *ctx, + JSValue *sp, + OPCodeEnum op) +{ + JSValue op1, op2; + uint32_t tag1, tag2; + uint32_t v1, v2, r; + + op1 = sp[-2]; + op2 = sp[-1]; + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + + if (tag1 == JS_TAG_SHORT_BIG_INT && tag2 == JS_TAG_SHORT_BIG_INT) { + js_slimb_t v1, v2, v; + js_sdlimb_t vd; + v1 = JS_VALUE_GET_SHORT_BIG_INT(op1); + v2 = JS_VALUE_GET_SHORT_BIG_INT(op2); + /* bigint fast path */ + switch(op) { + case OP_and: + v = v1 & v2; + break; + case OP_or: + v = v1 | v2; + break; + case OP_xor: + v = v1 ^ v2; + break; + case OP_sar: + if (v2 > (JS_LIMB_BITS - 1)) { + goto slow_big_int; + } else if (v2 < 0) { + if (v2 < -(JS_LIMB_BITS - 1)) + goto slow_big_int; + v2 = -v2; + goto bigint_shl; + } + bigint_sar: + v = v1 >> v2; + break; + case OP_shl: + if (v2 > (JS_LIMB_BITS - 1)) { + goto slow_big_int; + } else if (v2 < 0) { + if (v2 < -(JS_LIMB_BITS - 1)) + goto slow_big_int; + v2 = -v2; + goto bigint_sar; + } + bigint_shl: + vd = (js_dlimb_t)v1 << v2; + if (likely(vd >= JS_SHORT_BIG_INT_MIN && + vd <= JS_SHORT_BIG_INT_MAX)) { + v = vd; + } else { + JSBigInt *r = js_bigint_new_di(ctx, vd); + if (!r) + goto exception; + sp[-2] = JS_MKPTR(JS_TAG_BIG_INT, r); + return 0; + } + break; + default: + abort(); + } + sp[-2] = __JS_NewShortBigInt(ctx, v); + return 0; + } + op1 = JS_ToNumericFree(ctx, op1); + if (JS_IsException(op1)) { + JS_FreeValue(ctx, op2); + goto exception; + } + op2 = JS_ToNumericFree(ctx, op2); + if (JS_IsException(op2)) { + JS_FreeValue(ctx, op1); + goto exception; + } + + tag1 = JS_VALUE_GET_TAG(op1); + tag2 = JS_VALUE_GET_TAG(op2); + if ((tag1 == JS_TAG_BIG_INT || tag1 == JS_TAG_SHORT_BIG_INT) && + (tag2 == JS_TAG_BIG_INT || tag2 == JS_TAG_SHORT_BIG_INT)) { + JSBigInt *p1, *p2, *r; + JSBigIntBuf buf1, buf2; + slow_big_int: + /* buf2 is zero-initialized here (after the label, so it also runs on + the goto slow_big_int paths) to silence a -Wmaybe-uninitialized + false positive: GCC cannot prove buf2 is initialized through the + inlined js_bigint_get_si_sat() -> js_bigint_sign() read below. */ + memset(&buf2, 0, sizeof(buf2)); + if (JS_VALUE_GET_TAG(op1) == JS_TAG_SHORT_BIG_INT) + p1 = js_bigint_set_short(&buf1, op1); + else + p1 = JS_VALUE_GET_PTR(op1); + if (JS_VALUE_GET_TAG(op2) == JS_TAG_SHORT_BIG_INT) + p2 = js_bigint_set_short(&buf2, op2); + else + p2 = JS_VALUE_GET_PTR(op2); + switch(op) { + case OP_and: + case OP_or: + case OP_xor: + r = js_bigint_logic(ctx, p1, p2, op); + break; + case OP_shl: + case OP_sar: + { + js_slimb_t shift; + shift = js_bigint_get_si_sat(p2); + if (shift > INT32_MAX) + shift = INT32_MAX; + else if (shift < -INT32_MAX) + shift = -INT32_MAX; + if (op == OP_sar) + shift = -shift; + if (shift >= 0) + r = js_bigint_shl(ctx, p1, shift); + else + r = js_bigint_shr(ctx, p1, -shift); + } + break; + default: + abort(); + } + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + if (!r) + goto exception; + sp[-2] = JS_CompactBigInt(ctx, r); + } else { + if (unlikely(JS_ToInt32Free(ctx, (int32_t *)&v1, op1))) { + JS_FreeValue(ctx, op2); + goto exception; + } + if (unlikely(JS_ToInt32Free(ctx, (int32_t *)&v2, op2))) + goto exception; + switch(op) { + case OP_shl: + r = v1 << (v2 & 0x1f); + break; + case OP_sar: + r = (int)v1 >> (v2 & 0x1f); + break; + case OP_and: + r = v1 & v2; + break; + case OP_or: + r = v1 | v2; + break; + case OP_xor: + r = v1 ^ v2; + break; + default: + abort(); + } + sp[-2] = js_int32(r); + } + return 0; + exception: + sp[-2] = JS_UNDEFINED; + sp[-1] = JS_UNDEFINED; + return -1; +} + +/* op1 must be a bigint or int. */ +static JSBigInt *JS_ToBigIntBuf(JSContext *ctx, JSBigIntBuf *buf1, + JSValue op1) +{ + JSBigInt *p1; + + switch(JS_VALUE_GET_TAG(op1)) { + case JS_TAG_INT: + p1 = js_bigint_set_si(buf1, JS_VALUE_GET_INT(op1)); + break; + case JS_TAG_SHORT_BIG_INT: + p1 = js_bigint_set_short(buf1, op1); + break; + case JS_TAG_BIG_INT: + p1 = JS_VALUE_GET_PTR(op1); + break; + default: + abort(); + } + return p1; +} + +/* op1 and op2 must be numeric types and at least one must be a + bigint. No exception is generated. */ +static int js_compare_bigint(JSContext *ctx, OPCodeEnum op, + JSValue op1, JSValue op2) +{ + int res, val, tag1, tag2; + JSBigIntBuf buf1, buf2; + JSBigInt *p1, *p2; + + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + if ((tag1 == JS_TAG_SHORT_BIG_INT || tag1 == JS_TAG_INT) && + (tag2 == JS_TAG_SHORT_BIG_INT || tag2 == JS_TAG_INT)) { + /* fast path */ + js_slimb_t v1, v2; + if (tag1 == JS_TAG_INT) + v1 = JS_VALUE_GET_INT(op1); + else + v1 = JS_VALUE_GET_SHORT_BIG_INT(op1); + if (tag2 == JS_TAG_INT) + v2 = JS_VALUE_GET_INT(op2); + else + v2 = JS_VALUE_GET_SHORT_BIG_INT(op2); + val = (v1 > v2) - (v1 < v2); + } else { + if (tag1 == JS_TAG_FLOAT64) { + p2 = JS_ToBigIntBuf(ctx, &buf2, op2); + val = js_bigint_float64_cmp(ctx, p2, JS_VALUE_GET_FLOAT64(op1)); + if (val == 2) + goto unordered; + val = -val; + } else if (tag2 == JS_TAG_FLOAT64) { + p1 = JS_ToBigIntBuf(ctx, &buf1, op1); + val = js_bigint_float64_cmp(ctx, p1, JS_VALUE_GET_FLOAT64(op2)); + if (val == 2) { + unordered: + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + return false; + } + } else { + p1 = JS_ToBigIntBuf(ctx, &buf1, op1); + p2 = JS_ToBigIntBuf(ctx, &buf2, op2); + val = js_bigint_cmp(ctx, p1, p2); + } + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + } + + switch(op) { + case OP_lt: + res = val < 0; + break; + case OP_lte: + res = val <= 0; + break; + case OP_gt: + res = val > 0; + break; + case OP_gte: + res = val >= 0; + break; + case OP_eq: + res = val == 0; + break; + default: + abort(); + } + return res; +} + +static no_inline int js_relational_slow(JSContext *ctx, JSValue *sp, + OPCodeEnum op) +{ + JSValue op1, op2; + int res; + uint32_t tag1, tag2; + + op1 = sp[-2]; + op2 = sp[-1]; + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + + op1 = JS_ToPrimitiveFree(ctx, op1, HINT_NUMBER); + if (JS_IsException(op1)) { + JS_FreeValue(ctx, op2); + goto exception; + } + op2 = JS_ToPrimitiveFree(ctx, op2, HINT_NUMBER); + if (JS_IsException(op2)) { + JS_FreeValue(ctx, op1); + goto exception; + } + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + + if (tag_is_string(tag1) && tag_is_string(tag2)) { + if (tag1 == JS_TAG_STRING && tag2 == JS_TAG_STRING) { + res = js_string_compare(JS_VALUE_GET_STRING(op1), + JS_VALUE_GET_STRING(op2)); + } else { + res = js_string_rope_compare(op1, op2, false); + } + switch(op) { + case OP_lt: + res = (res < 0); + break; + case OP_lte: + res = (res <= 0); + break; + case OP_gt: + res = (res > 0); + break; + default: + case OP_gte: + res = (res >= 0); + break; + } + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + } else if ((tag1 <= JS_TAG_NULL || tag1 == JS_TAG_FLOAT64) && + (tag2 <= JS_TAG_NULL || tag2 == JS_TAG_FLOAT64)) { + /* fast path for float64/int */ + goto float64_compare; + } else { + if ((((tag1 == JS_TAG_BIG_INT || tag1 == JS_TAG_SHORT_BIG_INT) && + tag2 == JS_TAG_STRING) || + ((tag2 == JS_TAG_BIG_INT || tag2 == JS_TAG_SHORT_BIG_INT) && + tag1 == JS_TAG_STRING))) { + if (tag1 == JS_TAG_STRING) { + op1 = JS_StringToBigInt(ctx, op1); + if (JS_VALUE_GET_TAG(op1) != JS_TAG_BIG_INT && + JS_VALUE_GET_TAG(op1) != JS_TAG_SHORT_BIG_INT) + goto invalid_bigint_string; + } + if (tag2 == JS_TAG_STRING) { + op2 = JS_StringToBigInt(ctx, op2); + if (JS_VALUE_GET_TAG(op2) != JS_TAG_BIG_INT && + JS_VALUE_GET_TAG(op2) != JS_TAG_SHORT_BIG_INT) { + invalid_bigint_string: + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + res = false; + goto done; + } + } + } else { + op1 = JS_ToNumericFree(ctx, op1); + if (JS_IsException(op1)) { + JS_FreeValue(ctx, op2); + goto exception; + } + op2 = JS_ToNumericFree(ctx, op2); + if (JS_IsException(op2)) { + JS_FreeValue(ctx, op1); + goto exception; + } + } + + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + + if (tag1 == JS_TAG_BIG_INT || tag1 == JS_TAG_SHORT_BIG_INT || + tag2 == JS_TAG_BIG_INT || tag2 == JS_TAG_SHORT_BIG_INT) { + res = js_compare_bigint(ctx, op, op1, op2); + } else { + double d1, d2; + + float64_compare: + /* can use floating point comparison */ + if (tag1 == JS_TAG_FLOAT64) { + d1 = JS_VALUE_GET_FLOAT64(op1); + } else { + d1 = JS_VALUE_GET_INT(op1); + } + if (tag2 == JS_TAG_FLOAT64) { + d2 = JS_VALUE_GET_FLOAT64(op2); + } else { + d2 = JS_VALUE_GET_INT(op2); + } + switch(op) { + case OP_lt: + res = (d1 < d2); /* if NaN return false */ + break; + case OP_lte: + res = (d1 <= d2); /* if NaN return false */ + break; + case OP_gt: + res = (d1 > d2); /* if NaN return false */ + break; + default: + case OP_gte: + res = (d1 >= d2); /* if NaN return false */ + break; + } + } + } + done: + sp[-2] = js_bool(res); + return 0; + exception: + sp[-2] = JS_UNDEFINED; + sp[-1] = JS_UNDEFINED; + return -1; +} + +static bool tag_is_number(uint32_t tag) +{ + return (tag == JS_TAG_INT || + tag == JS_TAG_FLOAT64 || + tag == JS_TAG_BIG_INT || tag == JS_TAG_SHORT_BIG_INT); +} + +static no_inline __exception int js_eq_slow(JSContext *ctx, JSValue *sp, + bool is_neq) +{ + JSValue op1, op2; + int res; + uint32_t tag1, tag2; + + op1 = sp[-2]; + op2 = sp[-1]; + redo: + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + if (tag_is_number(tag1) && tag_is_number(tag2)) { + if (tag1 == JS_TAG_INT && tag2 == JS_TAG_INT) { + res = JS_VALUE_GET_INT(op1) == JS_VALUE_GET_INT(op2); + } else if ((tag1 == JS_TAG_FLOAT64 && + (tag2 == JS_TAG_INT || tag2 == JS_TAG_FLOAT64)) || + (tag2 == JS_TAG_FLOAT64 && + (tag1 == JS_TAG_INT || tag1 == JS_TAG_FLOAT64))) { + double d1, d2; + if (tag1 == JS_TAG_FLOAT64) { + d1 = JS_VALUE_GET_FLOAT64(op1); + } else { + d1 = JS_VALUE_GET_INT(op1); + } + if (tag2 == JS_TAG_FLOAT64) { + d2 = JS_VALUE_GET_FLOAT64(op2); + } else { + d2 = JS_VALUE_GET_INT(op2); + } + res = (d1 == d2); + } else { + res = js_compare_bigint(ctx, OP_eq, op1, op2); + if (res < 0) + goto exception; + } + } else if (tag1 == tag2) { + res = js_strict_eq2(ctx, op1, op2, JS_EQ_STRICT); + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + } else if ((tag1 == JS_TAG_NULL && tag2 == JS_TAG_UNDEFINED) || + (tag2 == JS_TAG_NULL && tag1 == JS_TAG_UNDEFINED)) { + res = true; + } else if ((tag_is_string(tag1) && tag_is_number(tag2)) || + (tag_is_string(tag2) && tag_is_number(tag1))) { + + if (tag1 == JS_TAG_BIG_INT || tag1 == JS_TAG_SHORT_BIG_INT || + tag2 == JS_TAG_BIG_INT || tag2 == JS_TAG_SHORT_BIG_INT) { + if (tag_is_string(tag1)) { + op1 = JS_StringToBigInt(ctx, op1); + if (JS_VALUE_GET_TAG(op1) != JS_TAG_BIG_INT && + JS_VALUE_GET_TAG(op1) != JS_TAG_SHORT_BIG_INT) + goto invalid_bigint_string; + } + if (tag_is_string(tag2)) { + op2 = JS_StringToBigInt(ctx, op2); + if (JS_VALUE_GET_TAG(op2) != JS_TAG_BIG_INT && + JS_VALUE_GET_TAG(op2) != JS_TAG_SHORT_BIG_INT ) { + invalid_bigint_string: + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + res = false; + goto done; + } + } + } else { + op1 = JS_ToNumericFree(ctx, op1); + if (JS_IsException(op1)) { + JS_FreeValue(ctx, op2); + goto exception; + } + op2 = JS_ToNumericFree(ctx, op2); + if (JS_IsException(op2)) { + JS_FreeValue(ctx, op1); + goto exception; + } + } + res = js_strict_eq(ctx, op1, op2); + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + } else if (tag1 == JS_TAG_BOOL) { + op1 = js_int32(JS_VALUE_GET_INT(op1)); + goto redo; + } else if (tag2 == JS_TAG_BOOL) { + op2 = js_int32(JS_VALUE_GET_INT(op2)); + goto redo; + } else if ((tag1 == JS_TAG_OBJECT && + (tag_is_number(tag2) || tag_is_string(tag2) || tag2 == JS_TAG_SYMBOL)) || + (tag2 == JS_TAG_OBJECT && + (tag_is_number(tag1) || tag_is_string(tag1) || tag1 == JS_TAG_SYMBOL))) { + op1 = JS_ToPrimitiveFree(ctx, op1, HINT_NONE); + if (JS_IsException(op1)) { + JS_FreeValue(ctx, op2); + goto exception; + } + op2 = JS_ToPrimitiveFree(ctx, op2, HINT_NONE); + if (JS_IsException(op2)) { + JS_FreeValue(ctx, op1); + goto exception; + } + goto redo; + } else { + /* IsHTMLDDA object is equivalent to undefined for '==' and '!=' */ + if ((JS_IsHTMLDDA(ctx, op1) && + (tag2 == JS_TAG_NULL || tag2 == JS_TAG_UNDEFINED)) || + (JS_IsHTMLDDA(ctx, op2) && + (tag1 == JS_TAG_NULL || tag1 == JS_TAG_UNDEFINED))) { + res = true; + } else { + res = false; + } + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + } + done: + sp[-2] = js_bool(res ^ is_neq); + return 0; + exception: + sp[-2] = JS_UNDEFINED; + sp[-1] = JS_UNDEFINED; + return -1; +} + +static no_inline int js_shr_slow(JSContext *ctx, JSValue *sp) +{ + JSValue op1, op2; + uint32_t v1, v2, r; + + op1 = sp[-2]; + op2 = sp[-1]; + op1 = JS_ToNumericFree(ctx, op1); + if (JS_IsException(op1)) { + JS_FreeValue(ctx, op2); + goto exception; + } + op2 = JS_ToNumericFree(ctx, op2); + if (JS_IsException(op2)) { + JS_FreeValue(ctx, op1); + goto exception; + } + + if (JS_VALUE_GET_TAG(op1) == JS_TAG_BIG_INT || + JS_VALUE_GET_TAG(op1) == JS_TAG_SHORT_BIG_INT || + JS_VALUE_GET_TAG(op2) == JS_TAG_BIG_INT || + JS_VALUE_GET_TAG(op2) == JS_TAG_SHORT_BIG_INT) { + JS_ThrowTypeError(ctx, "BigInt operands are forbidden for >>>"); + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + goto exception; + } + /* cannot give an exception */ + JS_ToUint32Free(ctx, &v1, op1); + JS_ToUint32Free(ctx, &v2, op2); + r = v1 >> (v2 & 0x1f); + sp[-2] = js_uint32(r); + return 0; + exception: + sp[-2] = JS_UNDEFINED; + sp[-1] = JS_UNDEFINED; + return -1; +} + +static bool js_strict_eq2(JSContext *ctx, JSValueConst op1, JSValueConst op2, + JSStrictEqModeEnum eq_mode) +{ + bool res; + int tag1, tag2; + double d1, d2; + + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + switch(tag1) { + case JS_TAG_BOOL: + if (tag1 != tag2) { + res = false; + } else { + res = JS_VALUE_GET_INT(op1) == JS_VALUE_GET_INT(op2); + } + break; + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + res = (tag1 == tag2); + break; + case JS_TAG_STRING: + case JS_TAG_STRING_ROPE: + { + if (!tag_is_string(tag2)) { + res = false; + } else if (tag1 == JS_TAG_STRING && tag2 == JS_TAG_STRING) { + res = js_string_eq(JS_VALUE_GET_STRING(op1), + JS_VALUE_GET_STRING(op2)); + } else { + res = (js_string_rope_compare(op1, op2, true) == 0); + } + } + break; + case JS_TAG_SYMBOL: + { + JSAtomStruct *p1, *p2; + if (tag1 != tag2) { + res = false; + } else { + p1 = JS_VALUE_GET_PTR(op1); + p2 = JS_VALUE_GET_PTR(op2); + res = (p1 == p2); + } + } + break; + case JS_TAG_OBJECT: + if (tag1 != tag2) + res = false; + else + res = JS_VALUE_GET_OBJ(op1) == JS_VALUE_GET_OBJ(op2); + break; + case JS_TAG_INT: + d1 = JS_VALUE_GET_INT(op1); + if (tag2 == JS_TAG_INT) { + d2 = JS_VALUE_GET_INT(op2); + goto number_test; + } else if (tag2 == JS_TAG_FLOAT64) { + d2 = JS_VALUE_GET_FLOAT64(op2); + goto number_test; + } else { + res = false; + } + break; + case JS_TAG_FLOAT64: + d1 = JS_VALUE_GET_FLOAT64(op1); + if (tag2 == JS_TAG_FLOAT64) { + d2 = JS_VALUE_GET_FLOAT64(op2); + } else if (tag2 == JS_TAG_INT) { + d2 = JS_VALUE_GET_INT(op2); + } else { + res = false; + break; + } + number_test: + if (unlikely(eq_mode >= JS_EQ_SAME_VALUE)) { + JSFloat64Union u1, u2; + /* NaN is not always normalized, so this test is necessary */ + if (isnan(d1) || isnan(d2)) { + res = isnan(d1) == isnan(d2); + } else if (eq_mode == JS_EQ_SAME_VALUE_ZERO) { + res = (d1 == d2); /* +0 == -0 */ + } else { + u1.d = d1; + u2.d = d2; + res = (u1.u64 == u2.u64); /* +0 != -0 */ + } + } else { + res = (d1 == d2); /* if NaN return false and +0 == -0 */ + } + break; + case JS_TAG_SHORT_BIG_INT: + case JS_TAG_BIG_INT: + { + JSBigIntBuf buf1, buf2; + JSBigInt *p1, *p2; + + if (tag2 != JS_TAG_SHORT_BIG_INT && + tag2 != JS_TAG_BIG_INT) { + res = false; + break; + } + + if (JS_VALUE_GET_TAG(op1) == JS_TAG_SHORT_BIG_INT) + p1 = js_bigint_set_short(&buf1, op1); + else + p1 = JS_VALUE_GET_PTR(op1); + if (JS_VALUE_GET_TAG(op2) == JS_TAG_SHORT_BIG_INT) + p2 = js_bigint_set_short(&buf2, op2); + else + p2 = JS_VALUE_GET_PTR(op2); + res = (js_bigint_cmp(ctx, p1, p2) == 0); + } + break; + default: + res = false; + break; + } + return res; +} + +static bool js_strict_eq(JSContext *ctx, JSValueConst op1, JSValueConst op2) +{ + return js_strict_eq2(ctx, op1, op2, JS_EQ_STRICT); +} + +static bool js_same_value(JSContext *ctx, JSValueConst op1, JSValueConst op2) +{ + return js_strict_eq2(ctx, op1, op2, JS_EQ_SAME_VALUE); +} + +static bool js_same_value_zero(JSContext *ctx, JSValueConst op1, JSValueConst op2) +{ + return js_strict_eq2(ctx, op1, op2, JS_EQ_SAME_VALUE_ZERO); +} + +static no_inline int js_strict_eq_slow(JSContext *ctx, JSValue *sp, + bool is_neq) +{ + bool res; + res = js_strict_eq(ctx, sp[-2], sp[-1]); + JS_FreeValue(ctx, sp[-2]); + JS_FreeValue(ctx, sp[-1]); + sp[-2] = js_bool(res ^ is_neq); + return 0; +} + +static __exception int js_operator_in(JSContext *ctx, JSValue *sp) +{ + JSValue op1, op2; + JSAtom atom; + int ret; + + op1 = sp[-2]; + op2 = sp[-1]; + + if (JS_VALUE_GET_TAG(op2) != JS_TAG_OBJECT) { + JS_ThrowTypeError(ctx, "invalid 'in' operand"); + return -1; + } + atom = JS_ValueToAtom(ctx, op1); + if (unlikely(atom == JS_ATOM_NULL)) + return -1; + ret = JS_HasProperty(ctx, op2, atom); + JS_FreeAtom(ctx, atom); + if (ret < 0) + return -1; + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + sp[-2] = js_bool(ret); + return 0; +} + +static __exception int js_operator_private_in(JSContext *ctx, JSValue *sp) +{ + JSValue op1, op2; + int ret; + op1 = sp[-2]; /* object */ + op2 = sp[-1]; /* field name or method function */ + if (JS_VALUE_GET_TAG(op1) != JS_TAG_OBJECT) { + JS_ThrowTypeError(ctx, "invalid 'in' operand"); + return -1; + } + if (JS_IsObject(op2)) { + /* method: use the brand */ + ret = JS_CheckBrand(ctx, op1, op2); + if (ret < 0) + return -1; + } else { + JSAtom atom; + JSObject *p; + JSShapeProperty *prs; + JSProperty *pr; + /* field */ + atom = JS_ValueToAtom(ctx, op2); + if (unlikely(atom == JS_ATOM_NULL)) + return -1; + p = JS_VALUE_GET_OBJ(op1); + prs = find_own_property(&pr, p, atom); + JS_FreeAtom(ctx, atom); + ret = (prs != NULL); + } + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + sp[-2] = js_bool(ret); + return 0; +} + +static __exception int js_has_unscopable(JSContext *ctx, JSValue obj, + JSAtom atom) +{ + JSValue arr, val; + int ret; + + arr = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_unscopables); + if (JS_IsException(arr)) + return -1; + ret = 0; + if (JS_IsObject(arr)) { + val = JS_GetProperty(ctx, arr, atom); + ret = JS_ToBoolFree(ctx, val); + } + JS_FreeValue(ctx, arr); + return ret; +} + +static __exception int js_operator_instanceof(JSContext *ctx, JSValue *sp) +{ + JSValue op1, op2; + int ret; + + op1 = sp[-2]; + op2 = sp[-1]; + ret = JS_IsInstanceOf(ctx, op1, op2); + if (ret < 0) + return ret; + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + sp[-2] = js_bool(ret); + return 0; +} + +static __exception int js_operator_typeof(JSContext *ctx, JSValue op1) +{ + JSAtom atom; + uint32_t tag; + + tag = JS_VALUE_GET_NORM_TAG(op1); + switch(tag) { + case JS_TAG_SHORT_BIG_INT: + case JS_TAG_BIG_INT: + atom = JS_ATOM_bigint; + break; + case JS_TAG_INT: + case JS_TAG_FLOAT64: + atom = JS_ATOM_number; + break; + case JS_TAG_UNDEFINED: + atom = JS_ATOM_undefined; + break; + case JS_TAG_BOOL: + atom = JS_ATOM_boolean; + break; + case JS_TAG_STRING: + case JS_TAG_STRING_ROPE: + atom = JS_ATOM_string; + break; + case JS_TAG_OBJECT: + { + JSObject *p; + p = JS_VALUE_GET_OBJ(op1); + if (unlikely(p->is_HTMLDDA)) + atom = JS_ATOM_undefined; + else if (JS_IsFunction(ctx, op1)) + atom = JS_ATOM_function; + else + goto obj_type; + } + break; + case JS_TAG_NULL: + obj_type: + atom = JS_ATOM_object; + break; + case JS_TAG_SYMBOL: + atom = JS_ATOM_symbol; + break; + default: + atom = JS_ATOM_unknown; + break; + } + return atom; +} + +static __exception int js_operator_delete(JSContext *ctx, JSValue *sp) +{ + JSValue op1, op2; + JSAtom atom; + int ret; + + op1 = sp[-2]; + op2 = sp[-1]; + atom = JS_ValueToAtom(ctx, op2); + if (unlikely(atom == JS_ATOM_NULL)) + return -1; + ret = JS_DeleteProperty(ctx, op1, atom, JS_PROP_THROW_STRICT); + JS_FreeAtom(ctx, atom); + if (unlikely(ret < 0)) + return -1; + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + sp[-2] = js_bool(ret); + return 0; +} + +static JSValue js_throw_type_error(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSFunctionBytecode *b = JS_GetFunctionBytecode(this_val); + if (!b || b->is_strict_mode || !b->has_prototype) { + return JS_ThrowTypeError(ctx, "invalid property access"); + } + return JS_UNDEFINED; +} + +static JSValue js_function_proto_fileName(JSContext *ctx, + JSValueConst this_val) +{ + JSFunctionBytecode *b = JS_GetFunctionBytecode(this_val); + if (b) { + return JS_AtomToString(ctx, b->filename); + } + return JS_UNDEFINED; +} + +static JSValue js_function_proto_int32(JSContext *ctx, + JSValueConst this_val, + int magic) +{ + JSFunctionBytecode *b = JS_GetFunctionBytecode(this_val); + if (b) { + int *field = (int *) ((char *)b + magic); + return js_int32(*field); + } + return JS_UNDEFINED; +} + +static int js_arguments_define_own_property(JSContext *ctx, + JSValueConst this_obj, + JSAtom prop, JSValueConst val, + JSValueConst getter, + JSValueConst setter, int flags) +{ + JSObject *p; + uint32_t idx; + p = JS_VALUE_GET_OBJ(this_obj); + /* convert to normal array when redefining an existing numeric field */ + if (p->fast_array && JS_AtomIsArrayIndex(ctx, &idx, prop) && + idx < p->u.array.count) { + if (convert_fast_array_to_array(ctx, p)) + return -1; + } + /* run the default define own property */ + return JS_DefineProperty(ctx, this_obj, prop, val, getter, setter, + flags | JS_PROP_NO_EXOTIC); +} + +static const JSClassExoticMethods js_arguments_exotic_methods = { + .define_own_property = js_arguments_define_own_property, +}; + +static JSValue js_build_arguments(JSContext *ctx, int argc, JSValueConst *argv) +{ + JSValue val, *tab; + JSProperty props[3]; + JSObject *p; + int i; + + props[0].u.value = js_int32(argc); /* length */ + props[1].u.value = js_dup(ctx->array_proto_values); /* Symbol.iterator */ + props[2].u.getset.getter = JS_VALUE_GET_OBJ(js_dup(ctx->throw_type_error)); /* callee */ + props[2].u.getset.setter = JS_VALUE_GET_OBJ(js_dup(ctx->throw_type_error)); /* callee */ + + val = JS_NewObjectFromShape(ctx, js_dup_shape(ctx->arguments_shape), + JS_CLASS_ARGUMENTS, props); + if (JS_IsException(val)) + return val; + p = JS_VALUE_GET_OBJ(val); + + /* initialize the fast array part */ + tab = NULL; + if (argc > 0) { + tab = js_malloc(ctx, sizeof(tab[0]) * argc); + if (!tab) + goto fail; + for(i = 0; i < argc; i++) { + tab[i] = js_dup(argv[i]); + } + } + p->u.array.u.values = tab; + p->u.array.count = argc; + + return val; + fail: + JS_FreeValue(ctx, val); + return JS_EXCEPTION; +} + +#define GLOBAL_VAR_OFFSET 0x40000000 +#define ARGUMENT_VAR_OFFSET 0x20000000 + +static void js_mapped_arguments_finalizer(JSRuntime *rt, JSValueConst val) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + if (p->fast_array) { + JSVarRef **var_refs = p->u.array.u.var_refs; + int i; + if (var_refs) { + for(i = 0; i < p->u.array.count; i++) { + if (var_refs[i]) + free_var_ref(rt, var_refs[i]); + } + js_free_rt(rt, var_refs); + } + } +} + +static void js_mapped_arguments_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + if (p->fast_array) { + JSVarRef **var_refs = p->u.array.u.var_refs; + int i; + if (var_refs) { + for(i = 0; i < p->u.array.count; i++) { + /* mapped arguments hold a counted ref to each var_ref; the + ones that are GC objects (detached, or open capturing a + coroutine local) must be marked, like the other holders */ + JSVarRef *vr = var_refs[i]; + if (vr && (vr->is_detached || vr->is_coro)) + mark_func(rt, &vr->header); + } + } + } +} + +/* legacy arguments object: add references to the function arguments */ +static JSValue js_build_mapped_arguments(JSContext *ctx, int argc, + JSValueConst *argv, + JSStackFrame *sf, int arg_count) +{ + JSValue val; + JSProperty props[3]; + JSVarRef **tab, *var_ref; + JSObject *p; + int i, j; + + props[0].u.value = js_int32(argc); /* length */ + props[1].u.value = js_dup(ctx->array_proto_values); /* Symbol.iterator */ + props[2].u.value = js_dup(ctx->rt->current_stack_frame->cur_func); /* callee */ + + val = JS_NewObjectFromShape(ctx, js_dup_shape(ctx->mapped_arguments_shape), + JS_CLASS_MAPPED_ARGUMENTS, props); + if (JS_IsException(val)) + return val; + p = JS_VALUE_GET_OBJ(val); + + /* initialize the fast array part */ + tab = NULL; + if (argc > 0) { + tab = js_malloc(ctx, sizeof(tab[0]) * argc); + if (!tab) + goto fail; + for(i = 0; i < arg_count; i++) { + var_ref = get_var_ref(ctx, sf, i, true); + if (!var_ref) + goto fail1; + tab[i] = var_ref; + } + for(i = arg_count; i < argc; i++) { + var_ref = js_create_var_ref(ctx, true); + if (!var_ref) { + fail1: + for(j = 0; j < i; j++) + free_var_ref(ctx->rt, tab[j]); + js_free(ctx, tab); + goto fail; + } + var_ref->value = js_dup(argv[i]); + tab[i] = var_ref; + } + } + p->u.array.u.var_refs = tab; + p->u.array.count = argc; + return val; + fail: + JS_FreeValue(ctx, val); + return JS_EXCEPTION; +} + +static JSValue build_for_in_iterator(JSContext *ctx, JSValue obj) +{ + JSObject *p; + JSPropertyEnum *tab_atom; + int i; + JSValue enum_obj, obj1; + JSForInIterator *it; + uint32_t tag, tab_atom_count; + + tag = JS_VALUE_GET_TAG(obj); + if (tag != JS_TAG_OBJECT && tag != JS_TAG_NULL && tag != JS_TAG_UNDEFINED) { + obj = JS_ToObjectFree(ctx, obj); + } + + it = js_malloc(ctx, sizeof(*it)); + if (!it) { + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + enum_obj = JS_NewObjectProtoClass(ctx, JS_NULL, JS_CLASS_FOR_IN_ITERATOR); + if (JS_IsException(enum_obj)) { + js_free(ctx, it); + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + it->is_array = false; + it->obj = obj; + it->idx = 0; + p = JS_VALUE_GET_OBJ(enum_obj); + p->u.for_in_iterator = it; + + if (tag == JS_TAG_NULL || tag == JS_TAG_UNDEFINED) + return enum_obj; + + /* fast path: assume no enumerable properties in the prototype chain */ + obj1 = js_dup(obj); + for(;;) { + obj1 = JS_GetPrototypeFree(ctx, obj1); + if (JS_IsNull(obj1)) + break; + if (JS_IsException(obj1)) + goto fail; + if (JS_GetOwnPropertyNamesInternal(ctx, &tab_atom, &tab_atom_count, + JS_VALUE_GET_OBJ(obj1), + JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY)) { + JS_FreeValue(ctx, obj1); + goto fail; + } + js_free_prop_enum(ctx, tab_atom, tab_atom_count); + if (tab_atom_count != 0) { + JS_FreeValue(ctx, obj1); + goto slow_path; + } + /* must check for timeout to avoid infinite loop */ + if (js_poll_interrupts(ctx)) { + JS_FreeValue(ctx, obj1); + goto fail; + } + } + + p = JS_VALUE_GET_OBJ(obj); + + if (p->fast_array) { + JSShape *sh; + JSShapeProperty *prs; + /* check that there are no enumerable normal fields */ + sh = p->shape; + for(i = 0, prs = get_shape_prop(sh); i < sh->prop_count; i++, prs++) { + if (prs->flags & JS_PROP_ENUMERABLE) + goto normal_case; + } + /* for fast arrays, we only store the number of elements */ + it->is_array = true; + it->array_length = p->u.array.count; + } else { + normal_case: + if (JS_GetOwnPropertyNamesInternal(ctx, &tab_atom, &tab_atom_count, p, + JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY)) + goto fail; + for(i = 0; i < tab_atom_count; i++) { + JS_SetPropertyInternal(ctx, enum_obj, tab_atom[i].atom, JS_NULL, 0); + } + js_free_prop_enum(ctx, tab_atom, tab_atom_count); + } + return enum_obj; + + slow_path: + /* non enumerable properties hide the enumerables ones in the + prototype chain */ + obj1 = js_dup(obj); + for(;;) { + if (JS_GetOwnPropertyNamesInternal(ctx, &tab_atom, &tab_atom_count, + JS_VALUE_GET_OBJ(obj1), + JS_GPN_STRING_MASK | JS_GPN_SET_ENUM)) { + JS_FreeValue(ctx, obj1); + goto fail; + } + for(i = 0; i < tab_atom_count; i++) { + JS_DefinePropertyValue(ctx, enum_obj, tab_atom[i].atom, JS_NULL, + (tab_atom[i].is_enumerable ? + JS_PROP_ENUMERABLE : 0)); + } + js_free_prop_enum(ctx, tab_atom, tab_atom_count); + obj1 = JS_GetPrototypeFree(ctx, obj1); + if (JS_IsNull(obj1)) + break; + if (JS_IsException(obj1)) + goto fail; + /* must check for timeout to avoid infinite loop */ + if (js_poll_interrupts(ctx)) { + JS_FreeValue(ctx, obj1); + goto fail; + } + } + return enum_obj; + + fail: + JS_FreeValue(ctx, enum_obj); + return JS_EXCEPTION; +} + +/* obj -> enum_obj */ +static __exception int js_for_in_start(JSContext *ctx, JSValue *sp) +{ + sp[-1] = build_for_in_iterator(ctx, sp[-1]); + if (JS_IsException(sp[-1])) + return -1; + return 0; +} + +/* enum_obj -> enum_obj value done */ +static __exception int js_for_in_next(JSContext *ctx, JSValue *sp) +{ + JSValue enum_obj; + JSObject *p; + JSAtom prop; + JSForInIterator *it; + int ret; + + enum_obj = sp[-1]; + /* fail safe */ + if (JS_VALUE_GET_TAG(enum_obj) != JS_TAG_OBJECT) + goto done; + p = JS_VALUE_GET_OBJ(enum_obj); + if (p->class_id != JS_CLASS_FOR_IN_ITERATOR) + goto done; + it = p->u.for_in_iterator; + + for(;;) { + if (it->is_array) { + if (it->idx >= it->array_length) + goto done; + prop = __JS_AtomFromUInt32(it->idx); + it->idx++; + } else { + JSShape *sh = p->shape; + JSShapeProperty *prs; + if (it->idx >= sh->prop_count) + goto done; + prs = &get_shape_prop(sh)[it->idx]; + prop = prs->atom; + it->idx++; + if (prop == JS_ATOM_NULL || !(prs->flags & JS_PROP_ENUMERABLE)) + continue; + } + // check if the property was deleted unless we're dealing with a proxy + JSValue obj = it->obj; + if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { + JSObject *p = JS_VALUE_GET_OBJ(obj); + if (p->class_id == JS_CLASS_PROXY) + break; + } + ret = JS_HasProperty(ctx, obj, prop); + if (ret < 0) + return ret; + if (ret) + break; + } + /* return the property */ + sp[0] = JS_AtomToValue(ctx, prop); + sp[1] = JS_FALSE; + return 0; + done: + /* return the end */ + sp[0] = JS_UNDEFINED; + sp[1] = JS_TRUE; + return 0; +} + +static JSValue JS_GetIterator2(JSContext *ctx, JSValueConst obj, + JSValueConst method) +{ + JSValue enum_obj; + + enum_obj = JS_Call(ctx, method, obj, 0, NULL); + if (JS_IsException(enum_obj)) + return enum_obj; + if (!JS_IsObject(enum_obj)) { + JS_FreeValue(ctx, enum_obj); + return JS_ThrowTypeErrorNotAnObject(ctx); + } + return enum_obj; +} + +static JSValue JS_GetIterator(JSContext *ctx, JSValueConst obj, bool is_async) +{ + JSValue method, ret, sync_iter; + + if (is_async) { + method = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_asyncIterator); + if (JS_IsException(method)) + return method; + if (JS_IsUndefined(method) || JS_IsNull(method)) { + method = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_iterator); + if (JS_IsException(method)) + return method; + sync_iter = JS_GetIterator2(ctx, obj, method); + JS_FreeValue(ctx, method); + if (JS_IsException(sync_iter)) + return sync_iter; + ret = JS_CreateAsyncFromSyncIterator(ctx, sync_iter); + JS_FreeValue(ctx, sync_iter); + return ret; + } + } else { + method = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_iterator); + if (JS_IsException(method)) + return method; + } + if (!JS_IsFunction(ctx, method)) { + JS_FreeValue(ctx, method); + return JS_ThrowTypeError(ctx, "value is not iterable"); + } + ret = JS_GetIterator2(ctx, obj, method); + JS_FreeValue(ctx, method); + return ret; +} + +/* return *pdone = 2 if the iterator object is not parsed */ +static JSValue JS_IteratorNext2(JSContext *ctx, JSValueConst enum_obj, + JSValueConst method, + int argc, JSValueConst *argv, int *pdone) +{ + JSValue obj; + + /* fast path for the built-in iterators (avoid creating the + intermediate result object) */ + if (JS_IsObject(method)) { + JSObject *p = JS_VALUE_GET_OBJ(method); + if (p->class_id == JS_CLASS_C_FUNCTION && + p->u.cfunc.cproto == JS_CFUNC_iterator_next) { + JSCFunctionType func; + JSValueConst args[1]; + + /* in case the function expects one argument */ + if (argc == 0) { + args[0] = JS_UNDEFINED; + argv = args; + } + func = p->u.cfunc.c_function; + return func.iterator_next(ctx, enum_obj, argc, argv, + pdone, p->u.cfunc.magic); + } + } + obj = JS_Call(ctx, method, enum_obj, argc, argv); + if (JS_IsException(obj)) + goto fail; + if (!JS_IsObject(obj)) { + JS_FreeValue(ctx, obj); + JS_ThrowTypeError(ctx, "iterator must return an object"); + goto fail; + } + *pdone = 2; + return obj; + fail: + *pdone = false; + return JS_EXCEPTION; +} + +static JSValue JS_IteratorNext(JSContext *ctx, JSValueConst enum_obj, + JSValueConst method, + int argc, JSValueConst *argv, int *pdone) +{ + JSValue obj, value, done_val; + int done; + + obj = JS_IteratorNext2(ctx, enum_obj, method, argc, argv, &done); + if (JS_IsException(obj)) + goto fail; + if (likely(done == 0)) { + *pdone = false; + return obj; + } else if (done != 2) { + JS_FreeValue(ctx, obj); + *pdone = true; + return JS_UNDEFINED; + } else { + done_val = JS_GetProperty(ctx, obj, JS_ATOM_done); + if (JS_IsException(done_val)) + goto fail; + *pdone = JS_ToBoolFree(ctx, done_val); + value = JS_UNDEFINED; + if (!*pdone) { + value = JS_GetProperty(ctx, obj, JS_ATOM_value); + } + JS_FreeValue(ctx, obj); + return value; + } + fail: + JS_FreeValue(ctx, obj); + *pdone = false; + return JS_EXCEPTION; +} + +/* return < 0 in case of exception */ +static int JS_IteratorClose(JSContext *ctx, JSValueConst enum_obj, + bool is_exception_pending) +{ + JSValue method, ret, ex_obj; + int res; + + if (is_exception_pending) { + ex_obj = ctx->rt->current_exception; + ctx->rt->current_exception = JS_UNINITIALIZED; + res = -1; + } else { + ex_obj = JS_UNDEFINED; + res = 0; + } + method = JS_GetProperty(ctx, enum_obj, JS_ATOM_return); + if (JS_IsException(method)) { + res = -1; + goto done; + } + if (JS_IsUndefined(method) || JS_IsNull(method)) { + goto done; + } + ret = JS_CallFree(ctx, method, enum_obj, 0, NULL); + if (!is_exception_pending) { + if (JS_IsException(ret)) { + res = -1; + } else if (!JS_IsObject(ret)) { + JS_ThrowTypeErrorNotAnObject(ctx); + res = -1; + } + } + JS_FreeValue(ctx, ret); + done: + if (is_exception_pending) { + JS_Throw(ctx, ex_obj); + } + return res; +} + +/* obj -> enum_rec (3 slots) */ +static __exception int js_for_of_start(JSContext *ctx, JSValue *sp, + bool is_async) +{ + JSValue op1, obj, method; + op1 = sp[-1]; + obj = JS_GetIterator(ctx, op1, is_async); + if (JS_IsException(obj)) + return -1; + JS_FreeValue(ctx, op1); + sp[-1] = obj; + method = JS_GetProperty(ctx, obj, JS_ATOM_next); + if (JS_IsException(method)) + return -1; + sp[0] = method; + return 0; +} + +/* enum_rec [objs] -> enum_rec [objs] value done. There are 'offset' + objs. If 'done' is true or in case of exception, 'enum_rec' is set + to undefined. If 'done' is true, 'value' is always set to + undefined. */ +static __exception int js_for_of_next(JSContext *ctx, JSValue *sp, int offset) +{ + JSValue value = JS_UNDEFINED; + int done = 1; + + if (likely(!JS_IsUndefined(sp[offset]))) { + value = JS_IteratorNext(ctx, sp[offset], sp[offset + 1], 0, NULL, &done); + if (JS_IsException(value)) + done = -1; + if (done) { + /* value is JS_UNDEFINED or JS_EXCEPTION */ + /* replace the iteration object with undefined */ + JS_FreeValue(ctx, sp[offset]); + sp[offset] = JS_UNDEFINED; + if (done < 0) { + return -1; + } else { + JS_FreeValue(ctx, value); + value = JS_UNDEFINED; + } + } + } + sp[0] = value; + sp[1] = js_bool(done); + return 0; +} + +static JSValue JS_IteratorGetCompleteValue(JSContext *ctx, JSValue obj, + int *pdone) +{ + JSValue done_val, value; + int done; + done_val = JS_GetProperty(ctx, obj, JS_ATOM_done); + if (JS_IsException(done_val)) + goto fail; + done = JS_ToBoolFree(ctx, done_val); + value = JS_GetProperty(ctx, obj, JS_ATOM_value); + if (JS_IsException(value)) + goto fail; + *pdone = done; + return value; + fail: + *pdone = false; + return JS_EXCEPTION; +} + +static JSValue js_sync_dispose_wrapper(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, + int magic, JSValueConst *func_data); + +static __exception int js_op_using_check(JSContext *ctx, JSValueConst val, + int hint, JSValue *pmethod) +{ + JSValue method; + bool is_sync_fallback = false; + + *pmethod = JS_UNDEFINED; + if (JS_IsNull(val) || JS_IsUndefined(val)) + return 0; + if (!JS_IsObject(val)) { + JS_ThrowTypeErrorNotAnObject(ctx); + return -1; + } + if (hint == 1) { + method = JS_GetProperty(ctx, val, JS_ATOM_Symbol_asyncDispose); + if (JS_IsException(method)) + return -1; + if (JS_IsUndefined(method) || JS_IsNull(method)) { + JS_FreeValue(ctx, method); + method = JS_GetProperty(ctx, val, JS_ATOM_Symbol_dispose); + if (JS_IsException(method)) + return -1; + is_sync_fallback = true; + } + } else { + method = JS_GetProperty(ctx, val, JS_ATOM_Symbol_dispose); + if (JS_IsException(method)) + return -1; + } + if (JS_IsUndefined(method) || JS_IsNull(method)) { + JS_ThrowTypeError(ctx, "value is not disposable"); + return -1; + } + if (!JS_IsFunction(ctx, method)) { + JS_FreeValue(ctx, method); + JS_ThrowTypeError(ctx, "dispose method is not a function"); + return -1; + } + if (is_sync_fallback) { + JSValueConst data[1]; + JSValue wrapped; + data[0] = method; + wrapped = JS_NewCFunctionData(ctx, js_sync_dispose_wrapper, 0, 0, + 1, data); + JS_FreeValue(ctx, method); + if (JS_IsException(wrapped)) + return -1; + method = wrapped; + } + *pmethod = method; + return 0; +} + +static __exception int js_iterator_get_value_done(JSContext *ctx, JSValue *sp) +{ + JSValue obj, value; + int done; + obj = sp[-1]; + if (!JS_IsObject(obj)) { + JS_ThrowTypeError(ctx, "iterator must return an object"); + return -1; + } + value = JS_IteratorGetCompleteValue(ctx, obj, &done); + if (JS_IsException(value)) + return -1; + JS_FreeValue(ctx, obj); + sp[-1] = value; + sp[0] = js_bool(done); + if (done) { + /* Iterator exhausted via {done:true}: drop the iterator object (stack + layout at the for-await `next` call is iter_obj,next,catch_offset,result + so iter_obj is sp[-4]) so the trailing OP_iterator_close skips calling + return(). Mirrors js_for_of_next, which nulls the iterator on done. + Per spec AsyncIteratorClose must NOT run on normal completion. This op + is emitted only by the for-await-of loop, so the layout is fixed. */ + JS_FreeValue(ctx, sp[-4]); + sp[-4] = JS_UNDEFINED; + } + return 0; +} + +static JSValue js_create_iterator_result(JSContext *ctx, + JSValue val, + bool done) +{ + JSValue obj; + obj = JS_NewObject(ctx); + if (JS_IsException(obj)) { + JS_FreeValue(ctx, val); + return obj; + } + if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_value, + val, JS_PROP_C_W_E) < 0) { + goto fail; + } + if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_done, + js_bool(done), JS_PROP_C_W_E) < 0) { + fail: + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + return obj; +} + +static JSValue js_array_iterator_next(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, + int *pdone, int magic); + +static JSValue js_create_array_iterator(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int magic); + +static bool js_is_fast_array(JSContext *ctx, JSValue obj) +{ + /* Try and handle fast arrays explicitly */ + if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { + JSObject *p = JS_VALUE_GET_OBJ(obj); + if (p->class_id == JS_CLASS_ARRAY && p->fast_array) { + return true; + } + } + return false; +} + +/* Access an Array's internal JSValue array if available */ +static bool js_get_fast_array(JSContext *ctx, JSValue obj, + JSValue **arrpp, uint32_t *countp) +{ + /* Try and handle fast arrays explicitly */ + if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { + JSObject *p = JS_VALUE_GET_OBJ(obj); + if (p->class_id == JS_CLASS_ARRAY && p->fast_array) { + *countp = p->u.array.count; + *arrpp = p->u.array.u.values; + return true; + } + } + return false; +} + +static __exception int js_append_enumerate(JSContext *ctx, JSValue *sp) +{ + JSValue iterator, enumobj, method, value; + int is_array_iterator; + JSValue *arrp; + uint32_t i, count32, pos; + + if (JS_VALUE_GET_TAG(sp[-2]) != JS_TAG_INT) { + JS_ThrowInternalError(ctx, "invalid index for append"); + return -1; + } + + pos = JS_VALUE_GET_INT(sp[-2]); + + /* XXX: further optimisations: + - use ctx->array_proto_values? + - check if array_iterator_prototype next method is built-in and + avoid constructing actual iterator object? + - build this into js_for_of_start and use in all `for (x of o)` loops + */ + iterator = JS_GetProperty(ctx, sp[-1], JS_ATOM_Symbol_iterator); + if (JS_IsException(iterator)) + return -1; + /* Used to squelch a -Wcast-function-type warning. */ + JSCFunctionType ft = { .generic_magic = js_create_array_iterator }; + is_array_iterator = JS_IsCFunction(ctx, iterator, + ft.generic, + JS_ITERATOR_KIND_VALUE); + JS_FreeValue(ctx, iterator); + + enumobj = JS_GetIterator(ctx, sp[-1], false); + if (JS_IsException(enumobj)) + return -1; + method = JS_GetProperty(ctx, enumobj, JS_ATOM_next); + if (JS_IsException(method)) { + JS_FreeValue(ctx, enumobj); + return -1; + } + /* Used to squelch a -Wcast-function-type warning. */ + JSCFunctionType ft2 = { .iterator_next = js_array_iterator_next }; + if (is_array_iterator + && JS_IsCFunction(ctx, method, ft2.generic, 0) + && js_get_fast_array(ctx, sp[-1], &arrp, &count32)) { + uint32_t len; + if (js_get_length32(ctx, &len, sp[-1])) + goto exception; + /* if len > count32, the elements >= count32 might be read in + the prototypes and might have side effects */ + if (len != count32) + goto general_case; + /* Handle fast arrays explicitly */ + for (i = 0; i < count32; i++) { + if (JS_DefinePropertyValueUint32(ctx, sp[-3], pos++, + js_dup(arrp[i]), JS_PROP_C_W_E) < 0) + goto exception; + } + } else { + general_case: + for (;;) { + int done; + value = JS_IteratorNext(ctx, enumobj, method, 0, NULL, &done); + if (JS_IsException(value)) + goto exception; + if (done) { + /* value is JS_UNDEFINED */ + break; + } + if (JS_DefinePropertyValueUint32(ctx, sp[-3], pos++, value, JS_PROP_C_W_E) < 0) + goto exception; + } + } + /* Note: could raise an error if too many elements */ + sp[-2] = js_int32(pos); + JS_FreeValue(ctx, enumobj); + JS_FreeValue(ctx, method); + return 0; + +exception: + JS_IteratorClose(ctx, enumobj, true); + JS_FreeValue(ctx, enumobj); + JS_FreeValue(ctx, method); + return -1; +} + +static __exception int JS_CopyDataProperties(JSContext *ctx, + JSValue target, + JSValue source, + JSValue excluded, + bool setprop) +{ + JSPropertyEnum *tab_atom; + JSValue val; + uint32_t i, tab_atom_count; + JSObject *p; + JSObject *pexcl = NULL; + int ret, gpn_flags; + int desc_flags; + bool is_enumerable; + + if (JS_VALUE_GET_TAG(source) != JS_TAG_OBJECT) + return 0; + + if (JS_VALUE_GET_TAG(excluded) == JS_TAG_OBJECT) + pexcl = JS_VALUE_GET_OBJ(excluded); + + p = JS_VALUE_GET_OBJ(source); + + gpn_flags = JS_GPN_STRING_MASK | JS_GPN_SYMBOL_MASK | JS_GPN_ENUM_ONLY; + if (p->is_exotic) { + const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; + /* cannot use JS_GPN_ENUM_ONLY with e.g. proxies because it + introduces a visible change */ + if (em && em->get_own_property_names) { + gpn_flags &= ~JS_GPN_ENUM_ONLY; + } + } + if (JS_GetOwnPropertyNamesInternal(ctx, &tab_atom, &tab_atom_count, p, + gpn_flags)) + return -1; + + for (i = 0; i < tab_atom_count; i++) { + if (pexcl) { + ret = JS_GetOwnPropertyInternal(ctx, NULL, pexcl, tab_atom[i].atom); + if (ret) { + if (ret < 0) + goto exception; + continue; + } + } + if (!(gpn_flags & JS_GPN_ENUM_ONLY)) { + /* test if the property is enumerable */ + ret = JS_GetOwnPropertyFlagsInternal(ctx, &desc_flags, p, tab_atom[i].atom); + if (ret < 0) + goto exception; + if (!ret) + continue; + is_enumerable = (desc_flags & JS_PROP_ENUMERABLE) != 0; + if (!is_enumerable) + continue; + } + val = JS_GetProperty(ctx, source, tab_atom[i].atom); + if (JS_IsException(val)) + goto exception; + if (setprop) + ret = JS_SetProperty(ctx, target, tab_atom[i].atom, val); + else + ret = JS_DefinePropertyValue(ctx, target, tab_atom[i].atom, val, + JS_PROP_C_W_E); + if (ret < 0) + goto exception; + } + js_free_prop_enum(ctx, tab_atom, tab_atom_count); + return 0; + exception: + js_free_prop_enum(ctx, tab_atom, tab_atom_count); + return -1; +} + +/* only valid inside C functions */ +static JSValueConst JS_GetActiveFunction(JSContext *ctx) +{ + return ctx->rt->current_stack_frame->cur_func; +} + +/* create a detached var ref */ +static JSVarRef *js_create_var_ref(JSContext *ctx, bool is_gc_object) +{ + JSVarRef *var_ref; + var_ref = js_malloc(ctx, sizeof(JSVarRef)); + if (!var_ref) + return NULL; + JS_REF_COUNT(var_ref) = 1; + var_ref->is_detached = true; + var_ref->is_coro = false; + var_ref->value = JS_UNDEFINED; + var_ref->pvalue = &var_ref->value; + if (is_gc_object) + add_gc_object(ctx->rt, &var_ref->header, JS_GC_OBJ_TYPE_VAR_REF); + return var_ref; +} + +static JSVarRef *get_var_ref(JSContext *ctx, JSStackFrame *sf, int var_idx, + bool is_arg) +{ + JSObject *p; + JSFunctionBytecode *b; + JSVarRef *var_ref; + JSValue *pvalue; + int var_ref_idx; + JSVarDef *vd; + + p = JS_VALUE_GET_OBJ(sf->cur_func); + b = p->u.func.function_bytecode; + + if (is_arg) { + vd = &b->vardefs[var_idx]; + pvalue = &sf->arg_buf[var_idx]; + } else { + vd = &b->vardefs[b->arg_count + var_idx]; + pvalue = &sf->var_buf[var_idx]; + } + + /* If the variable is captured, use the pre-computed index for O(1) lookup */ + if (vd->is_captured) { + var_ref_idx = vd->var_ref_idx; + var_ref = sf->var_refs[var_ref_idx]; + if (var_ref) { + /* reference to the already created local variable */ + JS_REF_COUNT(var_ref)++; + return var_ref; + } + + /* create a new one */ + var_ref = js_malloc(ctx, sizeof(JSVarRef)); + if (!var_ref) + return NULL; + JS_REF_COUNT(var_ref) = 1; + var_ref->is_detached = false; + var_ref->is_lexical = false; + var_ref->is_const = false; + var_ref->var_ref_idx = var_ref_idx; + var_ref->stack_frame = sf; + sf->var_refs[var_ref_idx] = var_ref; + var_ref->pvalue = pvalue; + /* If this local belongs to a coroutine (async function, generator or + async generator), keep the coroutine reachable for as long as a + closure references the variable: make the open var_ref a GC object + holding a counted reference to the coroutine (sf->cur_gc_obj). + Otherwise (ordinary C-stack frame, cur_gc_obj == NULL) the running + function is a GC root and no extra bookkeeping is needed. Snapshot + the decision in is_coro now (cur_gc_obj may become non-NULL later); + the coroutine itself is recovered via stack_frame->cur_gc_obj. */ + var_ref->is_coro = (sf->cur_gc_obj != NULL); + if (sf->cur_gc_obj) { + JS_REF_COUNT(sf->cur_gc_obj)++; + add_gc_object(ctx->rt, &var_ref->header, JS_GC_OBJ_TYPE_VAR_REF); + } + return var_ref; + } else { + /* Variable is not captured (e.g., from eval closures on uncaptured vars). + Create a detached var_ref that holds a copy of the value. */ + var_ref = js_malloc(ctx, sizeof(JSVarRef)); + if (!var_ref) + return NULL; + JS_REF_COUNT(var_ref) = 1; + var_ref->is_detached = true; + var_ref->is_coro = false; + var_ref->value = js_dup(*pvalue); + var_ref->pvalue = &var_ref->value; + add_gc_object(ctx->rt, &var_ref->header, JS_GC_OBJ_TYPE_VAR_REF); + return var_ref; + } +} + +static JSValue js_closure2(JSContext *ctx, JSValue func_obj, + JSFunctionBytecode *b, + JSVarRef **cur_var_refs, + JSStackFrame *sf) +{ + JSObject *p; + JSVarRef **var_refs; + int i; + + p = JS_VALUE_GET_OBJ(func_obj); + p->u.func.function_bytecode = b; + p->u.func.home_object = NULL; + p->u.func.var_refs = NULL; + if (b->closure_var_count) { + var_refs = js_mallocz(ctx, sizeof(var_refs[0]) * b->closure_var_count); + if (!var_refs) + goto fail; + p->u.func.var_refs = var_refs; + for(i = 0; i < b->closure_var_count; i++) { + JSClosureVar *cv = &b->closure_var[i]; + JSVarRef *var_ref; + switch(cv->closure_type) { + case JS_CLOSURE_LOCAL: + /* reuse the existing variable reference if it already exists */ + var_ref = get_var_ref(ctx, sf, cv->var_idx, false); + break; + case JS_CLOSURE_ARG: + /* reuse the existing variable reference if it already exists */ + var_ref = get_var_ref(ctx, sf, cv->var_idx, true); + break; + case JS_CLOSURE_REF: + case JS_CLOSURE_GLOBAL_REF: + var_ref = cur_var_refs[cv->var_idx]; + JS_REF_COUNT(var_ref)++; + break; + default: + abort(); + } + if (!var_ref) + goto fail; + var_refs[i] = var_ref; + } + } + return func_obj; + fail: + /* bfunc is freed when func_obj is freed */ + JS_FreeValue(ctx, func_obj); + return JS_EXCEPTION; +} + +static JSValue js_instantiate_prototype(JSContext *ctx, JSObject *p, JSAtom atom, void *opaque) +{ + JSValue obj, this_val; + int ret; + + this_val = JS_MKPTR(JS_TAG_OBJECT, p); + obj = JS_NewObject(ctx); + if (JS_IsException(obj)) + return JS_EXCEPTION; + ret = JS_DefinePropertyValue(ctx, obj, JS_ATOM_constructor, + js_dup(this_val), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + if (ret < 0) { + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + return obj; +} + +static const uint16_t func_kind_to_class_id[] = { + [JS_FUNC_NORMAL] = JS_CLASS_BYTECODE_FUNCTION, + [JS_FUNC_GENERATOR] = JS_CLASS_GENERATOR_FUNCTION, + [JS_FUNC_ASYNC] = JS_CLASS_ASYNC_FUNCTION, + [JS_FUNC_ASYNC_GENERATOR] = JS_CLASS_ASYNC_GENERATOR_FUNCTION, +}; + +static JSValue js_closure(JSContext *ctx, JSValue bfunc, + JSVarRef **cur_var_refs, + JSStackFrame *sf) +{ + JSFunctionBytecode *b; + JSValue func_obj; + JSAtom name_atom; + + b = JS_VALUE_GET_PTR(bfunc); + func_obj = JS_NewObjectClass(ctx, func_kind_to_class_id[b->func_kind]); + if (JS_IsException(func_obj)) { + JS_FreeValue(ctx, bfunc); + return JS_EXCEPTION; + } + func_obj = js_closure2(ctx, func_obj, b, cur_var_refs, sf); + if (JS_IsException(func_obj)) { + /* bfunc has been freed */ + goto fail; + } + name_atom = b->func_name; + if (name_atom == JS_ATOM_NULL) + name_atom = JS_ATOM_empty_string; + js_function_set_properties(ctx, func_obj, name_atom, + b->defined_arg_count); + + if (b->func_kind & JS_FUNC_GENERATOR) { + JSValue proto; + int proto_class_id; + /* generators have a prototype field which is used as + prototype for the generator object */ + if (b->func_kind == JS_FUNC_ASYNC_GENERATOR) + proto_class_id = JS_CLASS_ASYNC_GENERATOR; + else + proto_class_id = JS_CLASS_GENERATOR; + proto = JS_NewObjectProto(ctx, ctx->class_proto[proto_class_id]); + if (JS_IsException(proto)) + goto fail; + JS_DefinePropertyValue(ctx, func_obj, JS_ATOM_prototype, proto, + JS_PROP_WRITABLE); + } else if (b->has_prototype) { + /* add the 'prototype' property: delay instantiation to avoid + creating cycles for every javascript function. The prototype + object is created on the fly when first accessed */ + JS_SetConstructorBit(ctx, func_obj, true); + JS_DefineAutoInitProperty(ctx, func_obj, JS_ATOM_prototype, + JS_AUTOINIT_ID_PROTOTYPE, NULL, + JS_PROP_WRITABLE); + } + return func_obj; + fail: + /* bfunc is freed when func_obj is freed */ + JS_FreeValue(ctx, func_obj); + return JS_EXCEPTION; +} + +#define JS_DEFINE_CLASS_HAS_HERITAGE (1 << 0) + +static int js_op_define_class(JSContext *ctx, JSValue *sp, + JSAtom class_name, int class_flags, + JSVarRef **cur_var_refs, + JSStackFrame *sf, bool is_computed_name) +{ + JSValue bfunc, parent_class, proto = JS_UNDEFINED; + JSValue ctor = JS_UNDEFINED, parent_proto = JS_UNDEFINED; + JSFunctionBytecode *b; + + parent_class = sp[-2]; + bfunc = sp[-1]; + + if (class_flags & JS_DEFINE_CLASS_HAS_HERITAGE) { + if (JS_IsNull(parent_class)) { + parent_proto = JS_NULL; + parent_class = js_dup(ctx->function_proto); + } else { + if (!JS_IsConstructor(ctx, parent_class)) { + JS_ThrowTypeError(ctx, "parent class must be constructor"); + goto fail; + } + parent_proto = JS_GetProperty(ctx, parent_class, JS_ATOM_prototype); + if (JS_IsException(parent_proto)) + goto fail; + if (!JS_IsNull(parent_proto) && !JS_IsObject(parent_proto)) { + JS_ThrowTypeError(ctx, "parent prototype must be an object or null"); + goto fail; + } + } + } else { + /* parent_class is JS_UNDEFINED in this case */ + parent_proto = js_dup(ctx->class_proto[JS_CLASS_OBJECT]); + parent_class = js_dup(ctx->function_proto); + } + proto = JS_NewObjectProto(ctx, parent_proto); + if (JS_IsException(proto)) + goto fail; + + b = JS_VALUE_GET_PTR(bfunc); + assert(b->func_kind == JS_FUNC_NORMAL); + ctor = JS_NewObjectProtoClass(ctx, parent_class, + JS_CLASS_BYTECODE_FUNCTION); + if (JS_IsException(ctor)) + goto fail; + ctor = js_closure2(ctx, ctor, b, cur_var_refs, sf); + bfunc = JS_UNDEFINED; + if (JS_IsException(ctor)) + goto fail; + js_method_set_home_object(ctx, ctor, proto); + JS_SetConstructorBit(ctx, ctor, true); + + JS_DefinePropertyValue(ctx, ctor, JS_ATOM_length, + js_int32(b->defined_arg_count), + JS_PROP_CONFIGURABLE); + + if (is_computed_name) { + if (JS_DefineObjectNameComputed(ctx, ctor, sp[-3], + JS_PROP_CONFIGURABLE) < 0) + goto fail; + } else { + if (JS_DefineObjectName(ctx, ctor, class_name, JS_PROP_CONFIGURABLE) < 0) + goto fail; + } + + /* the constructor property must be first. It can be overriden by + computed property names */ + if (JS_DefinePropertyValue(ctx, proto, JS_ATOM_constructor, + js_dup(ctor), + JS_PROP_CONFIGURABLE | + JS_PROP_WRITABLE | JS_PROP_THROW) < 0) + goto fail; + /* set the prototype property */ + if (JS_DefinePropertyValue(ctx, ctor, JS_ATOM_prototype, + js_dup(proto), JS_PROP_THROW) < 0) + goto fail; + + JS_FreeValue(ctx, parent_proto); + JS_FreeValue(ctx, parent_class); + + sp[-2] = ctor; + sp[-1] = proto; + return 0; + fail: + JS_FreeValue(ctx, parent_class); + JS_FreeValue(ctx, parent_proto); + JS_FreeValue(ctx, bfunc); + JS_FreeValue(ctx, proto); + JS_FreeValue(ctx, ctor); + sp[-2] = JS_UNDEFINED; + sp[-1] = JS_UNDEFINED; + return -1; +} + +static void close_var_ref(JSRuntime *rt, JSVarRef *var_ref) +{ + JSGCObjectHeader *coro; + /* Already closed. This can happen during reentrant coroutine teardown: + closing one var_ref can drop the coroutine's refcount to zero and + re-enter close_var_refs on the same frame. Once detached, the union + holds 'value' rather than stack_frame, so we must not touch it. */ + if (var_ref->is_detached) + return; + /* Read the coroutine (if any) before js_dup() overwrites the union + member that aliases stack_frame with 'value'. */ + coro = var_ref->is_coro ? var_ref->stack_frame->cur_gc_obj : NULL; + var_ref->value = js_dup(*var_ref->pvalue); + var_ref->pvalue = &var_ref->value; + /* the reference is no longer to a local variable */ + var_ref->is_detached = true; + var_ref->is_coro = false; + /* an open coroutine var_ref is already a GC object and holds a + reference to its coroutine; drop it now that it is detached. */ + if (coro) + js_release_coro(rt, coro); + else + add_gc_object(rt, &var_ref->header, JS_GC_OBJ_TYPE_VAR_REF); +} + +static void close_var_refs(JSRuntime *rt, JSStackFrame *sf) +{ + JSVarRef *var_ref; + int i; + + for (i = 0; i < sf->var_ref_count; i++) { + var_ref = sf->var_refs[i]; + if (var_ref) + close_var_ref(rt, var_ref); + } +} + +static void close_lexical_var(JSContext *ctx, JSFunctionBytecode *b, + JSStackFrame *sf, int var_idx) +{ + JSVarRef *var_ref; + int var_ref_idx; + + var_ref_idx = b->vardefs[b->arg_count + var_idx].var_ref_idx; + var_ref = sf->var_refs[var_ref_idx]; + if (var_ref) { + close_var_ref(ctx->rt, var_ref); + sf->var_refs[var_ref_idx] = NULL; + } +} + +#define JS_CALL_FLAG_COPY_ARGV (1 << 1) +#define JS_CALL_FLAG_GENERATOR (1 << 2) + +static JSValue js_call_c_function(JSContext *ctx, JSValueConst func_obj, + JSValueConst this_obj, + int argc, JSValueConst *argv, int flags) +{ + JSRuntime *rt = ctx->rt; + JSCFunctionType func; + JSObject *p; + JSStackFrame sf_s, *sf = &sf_s, *prev_sf; + JSValue ret_val; + JSValueConst *arg_buf; + int arg_count, i; + JSCFunctionEnum cproto; + + p = JS_VALUE_GET_OBJ(func_obj); + cproto = p->u.cfunc.cproto; + arg_count = p->u.cfunc.length; + + /* better to always check stack overflow */ + if (js_check_stack_overflow(rt, sizeof(arg_buf[0]) * arg_count)) + return JS_ThrowStackOverflow(ctx); + + prev_sf = rt->current_stack_frame; + sf->prev_frame = prev_sf; + rt->current_stack_frame = sf; + ctx = p->u.cfunc.realm; /* change the current realm */ + + sf->is_strict_mode = false; + sf->is_constructor = (flags & JS_CALL_FLAG_CONSTRUCTOR) != 0; + sf->cur_func = unsafe_unconst(func_obj); + sf->arg_count = argc; + arg_buf = argv; + + if (unlikely(argc < arg_count)) { + /* ensure that at least argc_count arguments are readable */ + arg_buf = alloca(sizeof(arg_buf[0]) * arg_count); + for(i = 0; i < argc; i++) + arg_buf[i] = argv[i]; + for(i = argc; i < arg_count; i++) + arg_buf[i] = JS_UNDEFINED; + sf->arg_count = arg_count; + } + sf->arg_buf = (JSValue *)arg_buf; + + func = p->u.cfunc.c_function; + switch(cproto) { + case JS_CFUNC_constructor: + case JS_CFUNC_constructor_or_func: + if (!(flags & JS_CALL_FLAG_CONSTRUCTOR)) { + if (cproto == JS_CFUNC_constructor) { + not_a_constructor: + ret_val = JS_ThrowTypeError(ctx, "must be called with new"); + break; + } else { + this_obj = JS_UNDEFINED; + } + } + /* here this_obj is new_target */ + /* fall thru */ + case JS_CFUNC_generic: + ret_val = func.generic(ctx, this_obj, argc, arg_buf); + break; + case JS_CFUNC_constructor_magic: + case JS_CFUNC_constructor_or_func_magic: + if (!(flags & JS_CALL_FLAG_CONSTRUCTOR)) { + if (cproto == JS_CFUNC_constructor_magic) { + goto not_a_constructor; + } else { + this_obj = JS_UNDEFINED; + } + } + /* fall thru */ + case JS_CFUNC_generic_magic: + ret_val = func.generic_magic(ctx, this_obj, argc, arg_buf, + p->u.cfunc.magic); + break; + case JS_CFUNC_getter: + ret_val = func.getter(ctx, this_obj); + break; + case JS_CFUNC_setter: + ret_val = func.setter(ctx, this_obj, arg_buf[0]); + break; + case JS_CFUNC_getter_magic: + ret_val = func.getter_magic(ctx, this_obj, p->u.cfunc.magic); + break; + case JS_CFUNC_setter_magic: + ret_val = func.setter_magic(ctx, this_obj, arg_buf[0], p->u.cfunc.magic); + break; + case JS_CFUNC_f_f: + { + double d1; + + if (unlikely(JS_ToFloat64(ctx, &d1, arg_buf[0]))) { + ret_val = JS_EXCEPTION; + break; + } + ret_val = js_number(func.f_f(d1)); + } + break; + case JS_CFUNC_f_f_f: + { + double d1, d2; + + if (unlikely(JS_ToFloat64(ctx, &d1, arg_buf[0]))) { + ret_val = JS_EXCEPTION; + break; + } + if (unlikely(JS_ToFloat64(ctx, &d2, arg_buf[1]))) { + ret_val = JS_EXCEPTION; + break; + } + ret_val = js_number(func.f_f_f(d1, d2)); + } + break; + case JS_CFUNC_iterator_next: + { + int done; + ret_val = func.iterator_next(ctx, this_obj, argc, arg_buf, + &done, p->u.cfunc.magic); + if (!JS_IsException(ret_val) && done != 2) { + ret_val = js_create_iterator_result(ctx, ret_val, done); + } + } + break; + default: + abort(); + } + + rt->current_stack_frame = sf->prev_frame; + return ret_val; +} + +static JSValue js_call_bound_function(JSContext *ctx, JSValueConst func_obj, + JSValueConst this_obj, + int argc, JSValueConst *argv, int flags) +{ + JSObject *p; + JSBoundFunction *bf; + JSValueConst *arg_buf, new_target; + int arg_count, i; + + p = JS_VALUE_GET_OBJ(func_obj); + bf = p->u.bound_function; + arg_count = bf->argc + argc; + if (js_check_stack_overflow(ctx->rt, sizeof(JSValue) * arg_count)) + return JS_ThrowStackOverflow(ctx); + arg_buf = alloca(sizeof(JSValue) * arg_count); + for(i = 0; i < bf->argc; i++) { + arg_buf[i] = bf->argv[i]; + } + for(i = 0; i < argc; i++) { + arg_buf[bf->argc + i] = argv[i]; + } + if (flags & JS_CALL_FLAG_CONSTRUCTOR) { + new_target = this_obj; + if (js_same_value(ctx, func_obj, new_target)) + new_target = bf->func_obj; + return JS_CallConstructor2(ctx, bf->func_obj, new_target, + arg_count, arg_buf); + } else { + return JS_Call(ctx, bf->func_obj, bf->this_val, + arg_count, arg_buf); + } +} + +/* argument of OP_special_object */ +typedef enum { + OP_SPECIAL_OBJECT_ARGUMENTS, + OP_SPECIAL_OBJECT_MAPPED_ARGUMENTS, + OP_SPECIAL_OBJECT_THIS_FUNC, + OP_SPECIAL_OBJECT_NEW_TARGET, + OP_SPECIAL_OBJECT_HOME_OBJECT, + OP_SPECIAL_OBJECT_VAR_OBJECT, + OP_SPECIAL_OBJECT_IMPORT_META, + OP_SPECIAL_OBJECT_NULL_PROTO, +} OPSpecialObjectEnum; + +#define FUNC_RET_AWAIT 0 +#define FUNC_RET_YIELD 1 +#define FUNC_RET_YIELD_STAR 2 + +#ifdef ENABLE_DUMPS // JS_DUMP_BYTECODE_* +static void dump_single_byte_code(JSContext *ctx, const uint8_t *pc, + JSFunctionBytecode *b, int start_pos); +static void print_func_name(JSFunctionBytecode *b); +#endif + +static bool needs_backtrace(JSValue exc) +{ + return can_store_error_stack(exc) || can_add_backtrace(exc); +} + +/* argv[] is modified if (flags & JS_CALL_FLAG_COPY_ARGV) = 0. */ +static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, + JSValueConst this_obj, JSValueConst new_target, + int argc, JSValueConst *argv, int flags) +{ + JSRuntime *rt = caller_ctx->rt; + JSContext *ctx; + JSObject *p; + JSFunctionBytecode *b; + JSStackFrame sf_s, *sf = &sf_s; + uint8_t *pc; + int opcode, arg_allocated_size, i; + JSValue *local_buf, *stack_buf, *var_buf, *arg_buf, *sp, ret_val, *pval; + JSVarRef **var_refs; + size_t alloca_size; + +#ifdef ENABLE_DUMPS // JS_DUMP_BYTECODE_STEP +#define DUMP_BYTECODE_OR_DONT(pc) \ + if (check_dump_flag(ctx->rt, JS_DUMP_BYTECODE_STEP)) dump_single_byte_code(ctx, pc, b, 0); +#else +#define DUMP_BYTECODE_OR_DONT(pc) +#endif + +#if !DIRECT_DISPATCH +#define SWITCH(pc) DUMP_BYTECODE_OR_DONT(pc) switch (opcode = *pc++) +#define CASE(op) case op +#define DEFAULT default +#define BREAK break +#else + __extension__ static const void * const dispatch_table[256] = { +#define DEF(id, size, n_pop, n_push, f) && case_OP_ ## id, +#define def(id, size, n_pop, n_push, f) +#include "quickjs-opcode.h" + [ OP_COUNT ... 255 ] = &&case_default + }; +#define SWITCH(pc) DUMP_BYTECODE_OR_DONT(pc) __extension__ ({ goto *dispatch_table[opcode = *pc++]; }); +#define CASE(op) case_ ## op +#define DEFAULT case_default +#define BREAK SWITCH(pc) +#endif + + if (js_poll_interrupts(caller_ctx)) + return JS_EXCEPTION; + if (unlikely(JS_VALUE_GET_TAG(func_obj) != JS_TAG_OBJECT)) { + if (flags & JS_CALL_FLAG_GENERATOR) { + JSAsyncFunctionState *s = JS_VALUE_GET_PTR(func_obj); + /* func_obj get contains a pointer to JSFuncAsyncState */ + /* the stack frame is already allocated */ + sf = &s->frame; + p = JS_VALUE_GET_OBJ(sf->cur_func); + b = p->u.func.function_bytecode; + ctx = b->realm; + var_refs = p->u.func.var_refs; + local_buf = arg_buf = sf->arg_buf; + var_buf = sf->var_buf; + stack_buf = sf->var_buf + b->var_count; + sp = sf->cur_sp; + sf->cur_sp = NULL; /* cur_sp is NULL if the function is running */ + pc = sf->cur_pc; + sf->prev_frame = rt->current_stack_frame; + rt->current_stack_frame = sf; + if (s->throw_flag) + goto exception; + else + goto restart; + } else { + goto not_a_function; + } + } + p = JS_VALUE_GET_OBJ(func_obj); + if (unlikely(p->class_id != JS_CLASS_BYTECODE_FUNCTION)) { + JSClassCall *call_func; + call_func = rt->class_array[p->class_id].call; + if (!call_func) { + not_a_function: + return JS_ThrowTypeErrorNotAFunction(caller_ctx); + } + return call_func(caller_ctx, func_obj, this_obj, argc, + argv, flags); + } + b = p->u.func.function_bytecode; + + if (unlikely(argc < b->arg_count || (flags & JS_CALL_FLAG_COPY_ARGV))) { + arg_allocated_size = b->arg_count; + } else { + arg_allocated_size = 0; + } + + alloca_size = sizeof(JSValue) * (arg_allocated_size + b->var_count + + b->stack_size) + + sizeof(JSVarRef *) * b->var_ref_count; + if (js_check_stack_overflow(rt, alloca_size)) + return JS_ThrowStackOverflow(caller_ctx); + + sf->is_strict_mode = b->is_strict_mode; + sf->is_constructor = (flags & JS_CALL_FLAG_CONSTRUCTOR) != 0; + arg_buf = (JSValue *)argv; + sf->arg_count = argc; + sf->cur_func = unsafe_unconst(func_obj); + var_refs = p->u.func.var_refs; + + local_buf = alloca(alloca_size); + if (unlikely(arg_allocated_size)) { + int n = min_int(argc, b->arg_count); + arg_buf = local_buf; + for(i = 0; i < n; i++) + arg_buf[i] = js_dup(argv[i]); + for(; i < b->arg_count; i++) + arg_buf[i] = JS_UNDEFINED; + sf->arg_count = b->arg_count; + } + var_buf = local_buf + arg_allocated_size; + sf->var_buf = var_buf; + sf->arg_buf = arg_buf; + + for(i = 0; i < b->var_count; i++) + var_buf[i] = JS_UNDEFINED; + + stack_buf = var_buf + b->var_count; + sf->var_refs = (JSVarRef **)(stack_buf + b->stack_size); + sf->var_ref_count = b->var_ref_count; + for(i = 0; i < b->var_ref_count; i++) + sf->var_refs[i] = NULL; + /* ordinary C-stack frame: not owned by a coroutine GC object */ + sf->cur_gc_obj = NULL; + sp = stack_buf; + pc = b->byte_code_buf; + /* sf->cur_pc must we set to pc before any recursive calls to JS_CallInternal. */ + sf->cur_pc = NULL; + sf->prev_frame = rt->current_stack_frame; + rt->current_stack_frame = sf; + ctx = b->realm; /* set the current realm */ + +#ifdef ENABLE_DUMPS // JS_DUMP_BYTECODE_STEP + if (check_dump_flag(ctx->rt, JS_DUMP_BYTECODE_STEP)) + print_func_name(b); +#endif + + restart: + for(;;) { + int call_argc; + JSValue *call_argv; + + SWITCH(pc) { + CASE(OP_push_i32): + *sp++ = js_int32(get_u32(pc)); + pc += 4; + BREAK; + CASE(OP_push_bigint_i32): + *sp++ = __JS_NewShortBigInt(ctx, (int)get_u32(pc)); + pc += 4; + BREAK; + CASE(OP_push_const): + *sp++ = js_dup(b->cpool[get_u32(pc)]); + pc += 4; + BREAK; + CASE(OP_push_minus1): + CASE(OP_push_0): + CASE(OP_push_1): + CASE(OP_push_2): + CASE(OP_push_3): + CASE(OP_push_4): + CASE(OP_push_5): + CASE(OP_push_6): + CASE(OP_push_7): + *sp++ = js_int32(opcode - OP_push_0); + BREAK; + CASE(OP_push_i8): + *sp++ = js_int32(get_i8(pc)); + pc += 1; + BREAK; + CASE(OP_push_i16): + *sp++ = js_int32(get_i16(pc)); + pc += 2; + BREAK; + CASE(OP_push_const8): + *sp++ = js_dup(b->cpool[*pc++]); + BREAK; + CASE(OP_fclosure8): + *sp++ = js_closure(ctx, js_dup(b->cpool[*pc++]), var_refs, sf); + if (unlikely(JS_IsException(sp[-1]))) + goto exception; + BREAK; + CASE(OP_push_empty_string): + *sp++ = js_empty_string(rt); + BREAK; + CASE(OP_get_length): + { + JSValue val, obj; + JSAtom atom; + JSObject *p; + JSProperty *pr; + JSShapeProperty *prs; + + atom = JS_ATOM_length; + + obj = sp[-1]; + if (likely(JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT)) { + p = JS_VALUE_GET_OBJ(obj); + for(;;) { + prs = find_own_property(&pr, p, atom); + if (prs) { + /* found */ + if (unlikely(prs->flags & JS_PROP_TMASK)) + goto get_length_slow_path; + val = js_dup(pr->u.value); + break; + } + if (unlikely(p->is_exotic)) { + obj = JS_MKPTR(JS_TAG_OBJECT, p); + goto get_length_slow_path; + } + p = p->shape->proto; + if (!p) { + val = JS_UNDEFINED; + break; + } + } + } else { + get_length_slow_path: + sf->cur_pc = pc; + val = JS_GetPropertyInternal(ctx, obj, atom, sp[-1], false); + if (unlikely(JS_IsException(val))) + goto exception; + } + JS_FreeValue(ctx, sp[-1]); + sp[-1] = val; + } + BREAK; + CASE(OP_push_atom_value): + *sp++ = JS_AtomToValue(ctx, get_u32(pc)); + pc += 4; + BREAK; + CASE(OP_undefined): + *sp++ = JS_UNDEFINED; + BREAK; + CASE(OP_null): + *sp++ = JS_NULL; + BREAK; + CASE(OP_push_this): + /* OP_push_this is only called at the start of a function */ + { + JSValue val; + if (!b->is_strict_mode) { + uint32_t tag = JS_VALUE_GET_TAG(this_obj); + if (likely(tag == JS_TAG_OBJECT)) + goto normal_this; + if (tag == JS_TAG_NULL || tag == JS_TAG_UNDEFINED) { + val = js_dup(ctx->global_obj); + } else { + val = JS_ToObject(ctx, this_obj); + if (JS_IsException(val)) + goto exception; + } + } else { + normal_this: + val = js_dup(this_obj); + } + *sp++ = val; + } + BREAK; + CASE(OP_push_false): + *sp++ = JS_FALSE; + BREAK; + CASE(OP_push_true): + *sp++ = JS_TRUE; + BREAK; + CASE(OP_object): + *sp++ = JS_NewObject(ctx); + if (unlikely(JS_IsException(sp[-1]))) + goto exception; + BREAK; + CASE(OP_special_object): + { + int arg = *pc++; + switch(arg) { + case OP_SPECIAL_OBJECT_ARGUMENTS: + *sp++ = js_build_arguments(ctx, argc, argv); + if (unlikely(JS_IsException(sp[-1]))) + goto exception; + break; + case OP_SPECIAL_OBJECT_MAPPED_ARGUMENTS: + *sp++ = js_build_mapped_arguments(ctx, argc, argv, + sf, min_int(argc, b->arg_count)); + if (unlikely(JS_IsException(sp[-1]))) + goto exception; + break; + case OP_SPECIAL_OBJECT_THIS_FUNC: + *sp++ = js_dup(sf->cur_func); + break; + case OP_SPECIAL_OBJECT_NEW_TARGET: + *sp++ = js_dup(new_target); + break; + case OP_SPECIAL_OBJECT_HOME_OBJECT: + { + JSObject *p1; + p1 = p->u.func.home_object; + if (unlikely(!p1)) + *sp++ = JS_UNDEFINED; + else + *sp++ = js_dup(JS_MKPTR(JS_TAG_OBJECT, p1)); + } + break; + case OP_SPECIAL_OBJECT_VAR_OBJECT: + *sp++ = JS_NewObjectProto(ctx, JS_NULL); + if (unlikely(JS_IsException(sp[-1]))) + goto exception; + break; + case OP_SPECIAL_OBJECT_IMPORT_META: + *sp++ = js_import_meta(ctx); + if (unlikely(JS_IsException(sp[-1]))) + goto exception; + break; + case OP_SPECIAL_OBJECT_NULL_PROTO: + *sp++ = JS_NewObjectProtoClass(ctx, JS_NULL, JS_CLASS_OBJECT); + if (unlikely(JS_IsException(sp[-1]))) + goto exception; + break; + default: + abort(); + } + } + BREAK; + CASE(OP_rest): + { + int i, n, first = get_u16(pc); + pc += 2; + i = min_int(first, argc); + n = argc - i; + *sp++ = js_create_array(ctx, n, n ? &argv[i] : NULL); + if (unlikely(JS_IsException(sp[-1]))) + goto exception; + } + BREAK; + + CASE(OP_drop): + JS_FreeValue(ctx, sp[-1]); + sp--; + BREAK; + CASE(OP_nip): + JS_FreeValue(ctx, sp[-2]); + sp[-2] = sp[-1]; + sp--; + BREAK; + CASE(OP_nip1): /* a b c -> b c */ + JS_FreeValue(ctx, sp[-3]); + sp[-3] = sp[-2]; + sp[-2] = sp[-1]; + sp--; + BREAK; + CASE(OP_dup): + sp[0] = js_dup(sp[-1]); + sp++; + BREAK; + CASE(OP_dup2): /* a b -> a b a b */ + sp[0] = js_dup(sp[-2]); + sp[1] = js_dup(sp[-1]); + sp += 2; + BREAK; + CASE(OP_dup3): /* a b c -> a b c a b c */ + sp[0] = js_dup(sp[-3]); + sp[1] = js_dup(sp[-2]); + sp[2] = js_dup(sp[-1]); + sp += 3; + BREAK; + CASE(OP_dup1): /* a b -> a a b */ + sp[0] = sp[-1]; + sp[-1] = js_dup(sp[-2]); + sp++; + BREAK; + CASE(OP_insert2): /* obj a -> a obj a (dup_x1) */ + sp[0] = sp[-1]; + sp[-1] = sp[-2]; + sp[-2] = js_dup(sp[0]); + sp++; + BREAK; + CASE(OP_insert3): /* obj prop a -> a obj prop a (dup_x2) */ + sp[0] = sp[-1]; + sp[-1] = sp[-2]; + sp[-2] = sp[-3]; + sp[-3] = js_dup(sp[0]); + sp++; + BREAK; + CASE(OP_insert4): /* this obj prop a -> a this obj prop a */ + sp[0] = sp[-1]; + sp[-1] = sp[-2]; + sp[-2] = sp[-3]; + sp[-3] = sp[-4]; + sp[-4] = js_dup(sp[0]); + sp++; + BREAK; + CASE(OP_perm3): /* obj a b -> a obj b (213) */ + { + JSValue tmp; + tmp = sp[-2]; + sp[-2] = sp[-3]; + sp[-3] = tmp; + } + BREAK; + CASE(OP_rot3l): /* x a b -> a b x (231) */ + { + JSValue tmp; + tmp = sp[-3]; + sp[-3] = sp[-2]; + sp[-2] = sp[-1]; + sp[-1] = tmp; + } + BREAK; + CASE(OP_rot4l): /* x a b c -> a b c x */ + { + JSValue tmp; + tmp = sp[-4]; + sp[-4] = sp[-3]; + sp[-3] = sp[-2]; + sp[-2] = sp[-1]; + sp[-1] = tmp; + } + BREAK; + CASE(OP_rot5l): /* x a b c d -> a b c d x */ + { + JSValue tmp; + tmp = sp[-5]; + sp[-5] = sp[-4]; + sp[-4] = sp[-3]; + sp[-3] = sp[-2]; + sp[-2] = sp[-1]; + sp[-1] = tmp; + } + BREAK; + CASE(OP_rot3r): /* a b x -> x a b (312) */ + { + JSValue tmp; + tmp = sp[-1]; + sp[-1] = sp[-2]; + sp[-2] = sp[-3]; + sp[-3] = tmp; + } + BREAK; + CASE(OP_perm4): /* obj prop a b -> a obj prop b */ + { + JSValue tmp; + tmp = sp[-2]; + sp[-2] = sp[-3]; + sp[-3] = sp[-4]; + sp[-4] = tmp; + } + BREAK; + CASE(OP_perm5): /* this obj prop a b -> a this obj prop b */ + { + JSValue tmp; + tmp = sp[-2]; + sp[-2] = sp[-3]; + sp[-3] = sp[-4]; + sp[-4] = sp[-5]; + sp[-5] = tmp; + } + BREAK; + CASE(OP_swap): /* a b -> b a */ + { + JSValue tmp; + tmp = sp[-2]; + sp[-2] = sp[-1]; + sp[-1] = tmp; + } + BREAK; + CASE(OP_swap2): /* a b c d -> c d a b */ + { + JSValue tmp1, tmp2; + tmp1 = sp[-4]; + tmp2 = sp[-3]; + sp[-4] = sp[-2]; + sp[-3] = sp[-1]; + sp[-2] = tmp1; + sp[-1] = tmp2; + } + BREAK; + + CASE(OP_fclosure): + { + JSValue bfunc = js_dup(b->cpool[get_u32(pc)]); + pc += 4; + *sp++ = js_closure(ctx, bfunc, var_refs, sf); + if (unlikely(JS_IsException(sp[-1]))) + goto exception; + } + BREAK; + CASE(OP_call0): + CASE(OP_call1): + CASE(OP_call2): + CASE(OP_call3): + call_argc = opcode - OP_call0; + goto has_call_argc; + CASE(OP_call): + CASE(OP_tail_call): + { + call_argc = get_u16(pc); + pc += 2; + goto has_call_argc; + has_call_argc: + call_argv = sp - call_argc; + sf->cur_pc = pc; + ret_val = JS_CallInternal(ctx, call_argv[-1], JS_UNDEFINED, + JS_UNDEFINED, call_argc, + vc(call_argv), 0); + if (unlikely(JS_IsException(ret_val))) + goto exception; + if (opcode == OP_tail_call) + goto done; + for(i = -1; i < call_argc; i++) + JS_FreeValue(ctx, call_argv[i]); + sp -= call_argc + 1; + *sp++ = ret_val; + } + BREAK; + CASE(OP_call_constructor): + { + call_argc = get_u16(pc); + pc += 2; + call_argv = sp - call_argc; + sf->cur_pc = pc; + ret_val = JS_CallConstructorInternal(ctx, call_argv[-2], + call_argv[-1], call_argc, + vc(call_argv), 0); + if (unlikely(JS_IsException(ret_val))) + goto exception; + for(i = -2; i < call_argc; i++) + JS_FreeValue(ctx, call_argv[i]); + sp -= call_argc + 2; + *sp++ = ret_val; + } + BREAK; + CASE(OP_call_method): + CASE(OP_tail_call_method): + { + call_argc = get_u16(pc); + pc += 2; + call_argv = sp - call_argc; + sf->cur_pc = pc; + ret_val = JS_CallInternal(ctx, call_argv[-1], call_argv[-2], + JS_UNDEFINED, call_argc, + vc(call_argv), 0); + if (unlikely(JS_IsException(ret_val))) + goto exception; + if (opcode == OP_tail_call_method) + goto done; + for(i = -2; i < call_argc; i++) + JS_FreeValue(ctx, call_argv[i]); + sp -= call_argc + 2; + *sp++ = ret_val; + } + BREAK; + CASE(OP_array_from): + { + call_argc = get_u16(pc); + pc += 2; + call_argv = sp - call_argc; + ret_val = JS_NewArrayFrom(ctx, call_argc, call_argv); + sp -= call_argc; + if (unlikely(JS_IsException(ret_val))) + goto exception; + *sp++ = ret_val; + } + BREAK; + + CASE(OP_apply): + { + int magic; + magic = get_u16(pc); + pc += 2; + sf->cur_pc = pc; + + ret_val = js_function_apply(ctx, sp[-3], 2, vc(&sp[-2]), magic); + if (unlikely(JS_IsException(ret_val))) + goto exception; + JS_FreeValue(ctx, sp[-3]); + JS_FreeValue(ctx, sp[-2]); + JS_FreeValue(ctx, sp[-1]); + sp -= 3; + *sp++ = ret_val; + } + BREAK; + CASE(OP_return): + ret_val = *--sp; + goto done; + CASE(OP_return_undef): + ret_val = JS_UNDEFINED; + goto done; + + CASE(OP_check_ctor_return): + /* return true if 'this' should be returned */ + if (!JS_IsObject(sp[-1])) { + if (!JS_IsUndefined(sp[-1])) { + JS_ThrowTypeError(caller_ctx, "derived class constructor must return an object or undefined"); + goto exception; + } + sp[0] = JS_TRUE; + } else { + sp[0] = JS_FALSE; + } + sp++; + BREAK; + CASE(OP_check_ctor): + if (JS_IsUndefined(new_target)) { + non_ctor_call: + JS_ThrowTypeError(ctx, "class constructors must be invoked with 'new'"); + goto exception; + } + BREAK; + CASE(OP_init_ctor): + { + JSValue super, ret; + sf->cur_pc = pc; + if (JS_IsUndefined(new_target)) + goto non_ctor_call; + super = JS_GetPrototype(ctx, func_obj); + if (JS_IsException(super)) + goto exception; + ret = JS_CallConstructor2(ctx, super, new_target, argc, argv); + JS_FreeValue(ctx, super); + if (JS_IsException(ret)) + goto exception; + *sp++ = ret; + } + BREAK; + CASE(OP_check_brand): + { + int ret = JS_CheckBrand(ctx, sp[-2], sp[-1]); + if (ret < 0) + goto exception; + if (!ret) { + JS_ThrowTypeError(ctx, "invalid brand on object"); + goto exception; + } + } + BREAK; + CASE(OP_add_brand): + if (JS_AddBrand(ctx, sp[-2], sp[-1]) < 0) + goto exception; + JS_FreeValue(ctx, sp[-2]); + JS_FreeValue(ctx, sp[-1]); + sp -= 2; + BREAK; + + CASE(OP_throw): + JS_Throw(ctx, *--sp); + goto exception; + + CASE(OP_throw_error): +#define JS_THROW_VAR_RO 0 +#define JS_THROW_VAR_REDECL 1 +#define JS_THROW_VAR_UNINITIALIZED 2 +#define JS_THROW_ERROR_DELETE_SUPER 3 +#define JS_THROW_ERROR_ITERATOR_THROW 4 + { + JSAtom atom; + int type; + atom = get_u32(pc); + type = pc[4]; + pc += 5; + if (type == JS_THROW_VAR_RO) + JS_ThrowTypeErrorReadOnly(ctx, JS_PROP_THROW, atom); + else + if (type == JS_THROW_VAR_REDECL) + JS_ThrowSyntaxErrorVarRedeclaration(ctx, atom); + else + if (type == JS_THROW_VAR_UNINITIALIZED) + JS_ThrowReferenceErrorUninitialized(ctx, atom); + else + if (type == JS_THROW_ERROR_DELETE_SUPER) + JS_ThrowReferenceError(ctx, "unsupported reference to 'super'"); + else + if (type == JS_THROW_ERROR_ITERATOR_THROW) + JS_ThrowTypeError(ctx, "iterator does not have a throw method"); + else + JS_ThrowInternalError(ctx, "invalid throw var type %d", type); + } + goto exception; + + CASE(OP_eval): + { + JSValue obj; + int scope_idx; + call_argc = get_u16(pc); + scope_idx = get_u16(pc + 2) - 1; + pc += 4; + call_argv = sp - call_argc; + sf->cur_pc = pc; + if (js_same_value(ctx, call_argv[-1], ctx->eval_obj)) { + if (call_argc >= 1) + obj = call_argv[0]; + else + obj = JS_UNDEFINED; + ret_val = JS_EvalObject(ctx, JS_UNDEFINED, obj, + JS_EVAL_TYPE_DIRECT, scope_idx); + } else { + ret_val = JS_CallInternal(ctx, call_argv[-1], JS_UNDEFINED, + JS_UNDEFINED, call_argc, + vc(call_argv), 0); + } + if (unlikely(JS_IsException(ret_val))) + goto exception; + for(i = -1; i < call_argc; i++) + JS_FreeValue(ctx, call_argv[i]); + sp -= call_argc + 1; + *sp++ = ret_val; + } + BREAK; + /* could merge with OP_apply */ + CASE(OP_apply_eval): + { + int scope_idx; + uint32_t len; + JSValue *tab; + JSValue obj; + + scope_idx = get_u16(pc) - 1; + pc += 2; + sf->cur_pc = pc; + tab = build_arg_list(ctx, &len, sp[-1]); + if (!tab) + goto exception; + if (js_same_value(ctx, sp[-2], ctx->eval_obj)) { + if (len >= 1) + obj = tab[0]; + else + obj = JS_UNDEFINED; + ret_val = JS_EvalObject(ctx, JS_UNDEFINED, obj, + JS_EVAL_TYPE_DIRECT, scope_idx); + } else { + ret_val = JS_Call(ctx, sp[-2], JS_UNDEFINED, len, vc(tab)); + } + free_arg_list(ctx, tab, len); + if (unlikely(JS_IsException(ret_val))) + goto exception; + JS_FreeValue(ctx, sp[-2]); + JS_FreeValue(ctx, sp[-1]); + sp -= 2; + *sp++ = ret_val; + } + BREAK; + + CASE(OP_regexp): + { + sp[-2] = js_regexp_constructor_internal(ctx, JS_UNDEFINED, + sp[-2], sp[-1]); + sp--; + if (JS_IsException(sp[-1])) + goto exception; + } + BREAK; + + CASE(OP_get_super): + { + JSValue proto; + proto = JS_GetPrototype(ctx, sp[-1]); + if (JS_IsException(proto)) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp[-1] = proto; + } + BREAK; + + CASE(OP_import): + { + JSValue val; + sf->cur_pc = pc; + val = js_dynamic_import(ctx, sp[-2], sp[-1]); + if (JS_IsException(val)) + goto exception; + JS_FreeValue(ctx, sp[-2]); + JS_FreeValue(ctx, sp[-1]); + sp--; + sp[-1] = val; + } + BREAK; + + CASE(OP_get_var_undef): + CASE(OP_get_var): + { + JSValue val; + JSAtom atom; + atom = get_u32(pc); + pc += 4; + sf->cur_pc = pc; + + val = JS_GetGlobalVar(ctx, atom, opcode - OP_get_var_undef); + if (unlikely(JS_IsException(val))) + goto exception; + *sp++ = val; + } + BREAK; + + CASE(OP_put_var): + CASE(OP_put_var_init): + { + int ret; + JSAtom atom; + atom = get_u32(pc); + pc += 4; + sf->cur_pc = pc; + + ret = JS_SetGlobalVar(ctx, atom, sp[-1], opcode - OP_put_var); + sp--; + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + + CASE(OP_check_define_var): + { + JSAtom atom; + int flags; + atom = get_u32(pc); + flags = pc[4]; + pc += 5; + if (JS_CheckDefineGlobalVar(ctx, atom, flags)) + goto exception; + } + BREAK; + CASE(OP_define_var): + { + JSAtom atom; + int flags; + atom = get_u32(pc); + flags = pc[4]; + pc += 5; + if (JS_DefineGlobalVar(ctx, atom, flags)) + goto exception; + } + BREAK; + CASE(OP_define_func): + { + JSAtom atom; + int flags; + atom = get_u32(pc); + flags = pc[4]; + pc += 5; + if (JS_DefineGlobalFunction(ctx, atom, sp[-1], flags)) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp--; + } + BREAK; + + CASE(OP_get_loc): + { + int idx; + idx = get_u16(pc); + pc += 2; + sp[0] = js_dup(var_buf[idx]); + sp++; + } + BREAK; + CASE(OP_put_loc): + { + int idx; + idx = get_u16(pc); + pc += 2; + set_value(ctx, &var_buf[idx], sp[-1]); + sp--; + } + BREAK; + CASE(OP_set_loc): + { + int idx; + idx = get_u16(pc); + pc += 2; + set_value(ctx, &var_buf[idx], js_dup(sp[-1])); + } + BREAK; + CASE(OP_get_arg): + { + int idx; + idx = get_u16(pc); + pc += 2; + sp[0] = js_dup(arg_buf[idx]); + sp++; + } + BREAK; + CASE(OP_put_arg): + { + int idx; + idx = get_u16(pc); + pc += 2; + set_value(ctx, &arg_buf[idx], sp[-1]); + sp--; + } + BREAK; + CASE(OP_set_arg): + { + int idx; + idx = get_u16(pc); + pc += 2; + set_value(ctx, &arg_buf[idx], js_dup(sp[-1])); + } + BREAK; + + CASE(OP_get_loc8): *sp++ = js_dup(var_buf[*pc++]); BREAK; + CASE(OP_put_loc8): set_value(ctx, &var_buf[*pc++], *--sp); BREAK; + CASE(OP_set_loc8): set_value(ctx, &var_buf[*pc++], js_dup(sp[-1])); BREAK; + + // Observation: get_loc0 and get_loc1 are individually very + // frequent opcodes _and_ they are very often paired together, + // making them ideal candidates for opcode fusion. + CASE(OP_get_loc0_loc1): + *sp++ = js_dup(var_buf[0]); + *sp++ = js_dup(var_buf[1]); + BREAK; + + CASE(OP_get_loc0): *sp++ = js_dup(var_buf[0]); BREAK; + CASE(OP_get_loc1): *sp++ = js_dup(var_buf[1]); BREAK; + CASE(OP_get_loc2): *sp++ = js_dup(var_buf[2]); BREAK; + CASE(OP_get_loc3): *sp++ = js_dup(var_buf[3]); BREAK; + CASE(OP_put_loc0): set_value(ctx, &var_buf[0], *--sp); BREAK; + CASE(OP_put_loc1): set_value(ctx, &var_buf[1], *--sp); BREAK; + CASE(OP_put_loc2): set_value(ctx, &var_buf[2], *--sp); BREAK; + CASE(OP_put_loc3): set_value(ctx, &var_buf[3], *--sp); BREAK; + CASE(OP_set_loc0): set_value(ctx, &var_buf[0], js_dup(sp[-1])); BREAK; + CASE(OP_set_loc1): set_value(ctx, &var_buf[1], js_dup(sp[-1])); BREAK; + CASE(OP_set_loc2): set_value(ctx, &var_buf[2], js_dup(sp[-1])); BREAK; + CASE(OP_set_loc3): set_value(ctx, &var_buf[3], js_dup(sp[-1])); BREAK; + CASE(OP_get_arg0): *sp++ = js_dup(arg_buf[0]); BREAK; + CASE(OP_get_arg1): *sp++ = js_dup(arg_buf[1]); BREAK; + CASE(OP_get_arg2): *sp++ = js_dup(arg_buf[2]); BREAK; + CASE(OP_get_arg3): *sp++ = js_dup(arg_buf[3]); BREAK; + CASE(OP_put_arg0): set_value(ctx, &arg_buf[0], *--sp); BREAK; + CASE(OP_put_arg1): set_value(ctx, &arg_buf[1], *--sp); BREAK; + CASE(OP_put_arg2): set_value(ctx, &arg_buf[2], *--sp); BREAK; + CASE(OP_put_arg3): set_value(ctx, &arg_buf[3], *--sp); BREAK; + CASE(OP_set_arg0): set_value(ctx, &arg_buf[0], js_dup(sp[-1])); BREAK; + CASE(OP_set_arg1): set_value(ctx, &arg_buf[1], js_dup(sp[-1])); BREAK; + CASE(OP_set_arg2): set_value(ctx, &arg_buf[2], js_dup(sp[-1])); BREAK; + CASE(OP_set_arg3): set_value(ctx, &arg_buf[3], js_dup(sp[-1])); BREAK; + CASE(OP_get_var_ref0): *sp++ = js_dup(*var_refs[0]->pvalue); BREAK; + CASE(OP_get_var_ref1): *sp++ = js_dup(*var_refs[1]->pvalue); BREAK; + CASE(OP_get_var_ref2): *sp++ = js_dup(*var_refs[2]->pvalue); BREAK; + CASE(OP_get_var_ref3): *sp++ = js_dup(*var_refs[3]->pvalue); BREAK; + CASE(OP_put_var_ref0): set_value(ctx, var_refs[0]->pvalue, *--sp); BREAK; + CASE(OP_put_var_ref1): set_value(ctx, var_refs[1]->pvalue, *--sp); BREAK; + CASE(OP_put_var_ref2): set_value(ctx, var_refs[2]->pvalue, *--sp); BREAK; + CASE(OP_put_var_ref3): set_value(ctx, var_refs[3]->pvalue, *--sp); BREAK; + CASE(OP_set_var_ref0): set_value(ctx, var_refs[0]->pvalue, js_dup(sp[-1])); BREAK; + CASE(OP_set_var_ref1): set_value(ctx, var_refs[1]->pvalue, js_dup(sp[-1])); BREAK; + CASE(OP_set_var_ref2): set_value(ctx, var_refs[2]->pvalue, js_dup(sp[-1])); BREAK; + CASE(OP_set_var_ref3): set_value(ctx, var_refs[3]->pvalue, js_dup(sp[-1])); BREAK; + + CASE(OP_get_var_ref): + { + int idx; + JSValue val; + idx = get_u16(pc); + pc += 2; + val = *var_refs[idx]->pvalue; + sp[0] = js_dup(val); + sp++; + } + BREAK; + CASE(OP_put_var_ref): + { + int idx; + idx = get_u16(pc); + pc += 2; + set_value(ctx, var_refs[idx]->pvalue, sp[-1]); + sp--; + } + BREAK; + CASE(OP_set_var_ref): + { + int idx; + idx = get_u16(pc); + pc += 2; + set_value(ctx, var_refs[idx]->pvalue, js_dup(sp[-1])); + } + BREAK; + CASE(OP_get_var_ref_check): + { + int idx; + JSValue val; + idx = get_u16(pc); + pc += 2; + val = *var_refs[idx]->pvalue; + if (unlikely(JS_IsUninitialized(val))) { + JS_ThrowReferenceErrorUninitialized2(ctx, b, idx, true); + goto exception; + } + sp[0] = js_dup(val); + sp++; + } + BREAK; + CASE(OP_put_var_ref_check): + { + int idx; + idx = get_u16(pc); + pc += 2; + if (unlikely(JS_IsUninitialized(*var_refs[idx]->pvalue))) { + JS_ThrowReferenceErrorUninitialized2(ctx, b, idx, true); + goto exception; + } + set_value(ctx, var_refs[idx]->pvalue, sp[-1]); + sp--; + } + BREAK; + CASE(OP_put_var_ref_check_init): + { + int idx; + idx = get_u16(pc); + pc += 2; + if (unlikely(!JS_IsUninitialized(*var_refs[idx]->pvalue))) { + JS_ThrowReferenceErrorUninitialized2(ctx, b, idx, true); + goto exception; + } + set_value(ctx, var_refs[idx]->pvalue, sp[-1]); + sp--; + } + BREAK; + CASE(OP_set_loc_uninitialized): + { + int idx; + idx = get_u16(pc); + pc += 2; + set_value(ctx, &var_buf[idx], JS_UNINITIALIZED); + } + BREAK; + CASE(OP_get_loc_check): + { + int idx; + idx = get_u16(pc); + pc += 2; + if (unlikely(JS_IsUninitialized(var_buf[idx]))) { + JS_ThrowReferenceErrorUninitialized2(caller_ctx, b, idx, + false); + goto exception; + } + sp[0] = js_dup(var_buf[idx]); + sp++; + } + BREAK; + CASE(OP_put_loc_check): + { + int idx; + idx = get_u16(pc); + pc += 2; + if (unlikely(JS_IsUninitialized(var_buf[idx]))) { + JS_ThrowReferenceErrorUninitialized2(caller_ctx, b, idx, + false); + goto exception; + } + set_value(ctx, &var_buf[idx], sp[-1]); + sp--; + } + BREAK; + CASE(OP_put_loc_check_init): + { + int idx; + idx = get_u16(pc); + pc += 2; + if (unlikely(!JS_IsUninitialized(var_buf[idx]))) { + JS_ThrowReferenceError(caller_ctx, + "'this' can be initialized only once"); + goto exception; + } + set_value(ctx, &var_buf[idx], sp[-1]); + sp--; + } + BREAK; + CASE(OP_close_loc): + { + int idx; + idx = get_u16(pc); + pc += 2; + close_lexical_var(ctx, b, sf, idx); + } + BREAK; + + CASE(OP_make_loc_ref): + CASE(OP_make_arg_ref): + CASE(OP_make_var_ref_ref): + { + JSVarRef *var_ref; + JSProperty *pr; + JSAtom atom; + int idx; + atom = get_u32(pc); + idx = get_u16(pc + 4); + pc += 6; + *sp++ = JS_NewObjectProto(ctx, JS_NULL); + if (unlikely(JS_IsException(sp[-1]))) + goto exception; + if (opcode == OP_make_var_ref_ref) { + var_ref = var_refs[idx]; + JS_REF_COUNT(var_ref)++; + } else { + var_ref = get_var_ref(ctx, sf, idx, opcode == OP_make_arg_ref); + if (!var_ref) + goto exception; + } + pr = add_property(ctx, JS_VALUE_GET_OBJ(sp[-1]), atom, + JS_PROP_WRITABLE | JS_PROP_VARREF); + if (!pr) { + free_var_ref(rt, var_ref); + goto exception; + } + pr->u.var_ref = var_ref; + *sp++ = JS_AtomToValue(ctx, atom); + } + BREAK; + CASE(OP_make_var_ref): + { + JSAtom atom; + atom = get_u32(pc); + pc += 4; + + if (JS_GetGlobalVarRef(ctx, atom, sp)) + goto exception; + sp += 2; + } + BREAK; + + CASE(OP_goto): + pc += (int32_t)get_u32(pc); + if (unlikely(js_poll_interrupts(ctx))) + goto exception; + BREAK; + CASE(OP_goto16): + pc += (int16_t)get_u16(pc); + if (unlikely(js_poll_interrupts(ctx))) + goto exception; + BREAK; + CASE(OP_goto8): + pc += (int8_t)pc[0]; + if (unlikely(js_poll_interrupts(ctx))) + goto exception; + BREAK; + CASE(OP_if_true): + { + int res; + JSValue op1; + + op1 = sp[-1]; + pc += 4; + if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_UNDEFINED) { + res = JS_VALUE_GET_INT(op1); + } else { + res = JS_ToBoolFree(ctx, op1); + } + sp--; + if (res) { + pc += (int32_t)get_u32(pc - 4) - 4; + } + if (unlikely(js_poll_interrupts(ctx))) + goto exception; + } + BREAK; + CASE(OP_if_false): + { + int res; + JSValue op1; + + op1 = sp[-1]; + pc += 4; + if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_UNDEFINED) { + res = JS_VALUE_GET_INT(op1); + } else { + res = JS_ToBoolFree(ctx, op1); + } + sp--; + if (!res) { + pc += (int32_t)get_u32(pc - 4) - 4; + } + if (unlikely(js_poll_interrupts(ctx))) + goto exception; + } + BREAK; + CASE(OP_if_true8): + { + int res; + JSValue op1; + + op1 = sp[-1]; + pc += 1; + if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_UNDEFINED) { + res = JS_VALUE_GET_INT(op1); + } else { + res = JS_ToBoolFree(ctx, op1); + } + sp--; + if (res) { + pc += (int8_t)pc[-1] - 1; + } + if (unlikely(js_poll_interrupts(ctx))) + goto exception; + } + BREAK; + CASE(OP_if_false8): + { + int res; + JSValue op1; + + op1 = sp[-1]; + pc += 1; + if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_UNDEFINED) { + res = JS_VALUE_GET_INT(op1); + } else { + res = JS_ToBoolFree(ctx, op1); + } + sp--; + if (!res) { + pc += (int8_t)pc[-1] - 1; + } + if (unlikely(js_poll_interrupts(ctx))) + goto exception; + } + BREAK; + CASE(OP_catch): + { + int32_t diff; + diff = get_u32(pc); + sp[0] = JS_NewCatchOffset(ctx, pc + diff - b->byte_code_buf); + sp++; + pc += 4; + } + BREAK; + CASE(OP_gosub): + { + int32_t diff; + diff = get_u32(pc); + /* XXX: should have a different tag to avoid security flaw */ + sp[0] = js_int32(pc + 4 - b->byte_code_buf); + sp++; + pc += diff; + } + BREAK; + CASE(OP_ret): + { + JSValue op1; + uint32_t pos; + op1 = sp[-1]; + if (unlikely(JS_VALUE_GET_TAG(op1) != JS_TAG_INT)) + goto ret_fail; + pos = JS_VALUE_GET_INT(op1); + if (unlikely(pos >= b->byte_code_len)) { + ret_fail: + JS_ThrowInternalError(ctx, "invalid ret value"); + goto exception; + } + sp--; + pc = b->byte_code_buf + pos; + } + BREAK; + + CASE(OP_for_in_start): + sf->cur_pc = pc; + if (js_for_in_start(ctx, sp)) + goto exception; + BREAK; + CASE(OP_for_in_next): + sf->cur_pc = pc; + if (js_for_in_next(ctx, sp)) + goto exception; + sp += 2; + BREAK; + CASE(OP_for_of_start): + sf->cur_pc = pc; + if (js_for_of_start(ctx, sp, false)) + goto exception; + sp += 1; + *sp++ = JS_NewCatchOffset(ctx, 0); + BREAK; + CASE(OP_for_of_next): + { + int offset = -3 - pc[0]; + pc += 1; + sf->cur_pc = pc; + if (js_for_of_next(ctx, sp, offset)) + goto exception; + sp += 2; + } + BREAK; + CASE(OP_for_await_of_start): + sf->cur_pc = pc; + if (js_for_of_start(ctx, sp, true)) + goto exception; + sp += 1; + *sp++ = JS_NewCatchOffset(ctx, 0); + BREAK; + CASE(OP_iterator_get_value_done): + sf->cur_pc = pc; + if (js_iterator_get_value_done(ctx, sp)) + goto exception; + sp += 1; + BREAK; + CASE(OP_check_object): + if (unlikely(!JS_IsObject(sp[-1]))) { + JS_ThrowTypeErrorNotAnObject(ctx); + goto exception; + } + BREAK; + + CASE(OP_iterator_close): + /* iter_obj next catch_offset -> */ + sp--; /* drop the catch offset to avoid getting caught by exception */ + JS_FreeValue(ctx, sp[-1]); /* drop the next method */ + sp--; + if (!JS_IsUndefined(sp[-1])) { + sf->cur_pc = pc; + if (JS_IteratorClose(ctx, sp[-1], false)) + goto exception; + JS_FreeValue(ctx, sp[-1]); + } + sp--; + BREAK; + CASE(OP_nip_catch): + { + JSValue ret_val; + /* catch_offset ... ret_val -> ret_eval */ + ret_val = *--sp; + while (sp > stack_buf && + JS_VALUE_GET_TAG(sp[-1]) != JS_TAG_CATCH_OFFSET) { + JS_FreeValue(ctx, *--sp); + } + if (unlikely(sp == stack_buf)) { + JS_ThrowInternalError(ctx, "nip_catch"); + JS_FreeValue(ctx, ret_val); + goto exception; + } + sp[-1] = ret_val; + } + BREAK; + + CASE(OP_using_dispose_init): + sp[0] = JS_UNINITIALIZED; + sp++; + BREAK; + + CASE(OP_using_dispose): + { + int idx; + JSValueConst val, method; + JSValue ret, error_state; + + idx = get_u16(pc); + pc += 2; + val = var_buf[idx]; + method = var_buf[idx + 1]; + error_state = sp[-1]; + + if (JS_IsNull(val) || JS_IsUndefined(val) || + JS_IsUninitialized(val)) { + /* null/undefined (spec-permitted) or uninitialized + (declaration threw before assignment). */ + BREAK; + } + sf->cur_pc = pc; + ret = JS_Call(ctx, method, val, 0, NULL); + if (JS_IsException(ret)) { + JSValue new_error = JS_GetException(ctx); + if (!JS_IsUninitialized(error_state)) { + JSValue se; + se = js_new_suppressed_error(ctx, new_error, + error_state); + JS_FreeValue(ctx, new_error); + JS_FreeValue(ctx, error_state); + if (JS_IsException(se)) { + sp[-1] = JS_GetException(ctx); + } else { + sp[-1] = se; + } + } else { + sp[-1] = new_error; + } + } else { + JS_FreeValue(ctx, ret); + } + } + BREAK; + + CASE(OP_using_dispose_async): + { + int idx; + JSValueConst val, method; + JSValue ret; + + idx = get_u16(pc); + pc += 2; + val = var_buf[idx]; + method = var_buf[idx + 1]; + + if (JS_IsNull(val) || JS_IsUndefined(val) || + JS_IsUninitialized(val)) { + sp[0] = JS_UNDEFINED; + sp++; + BREAK; + } + sf->cur_pc = pc; + ret = JS_Call(ctx, method, val, 0, NULL); + if (JS_IsException(ret)) + goto exception; + sp[0] = ret; + sp++; + } + BREAK; + + CASE(OP_using_dispose_merge): + { + JSValue new_error = sp[-1]; + JSValue error_state = sp[-2]; + sp--; + if (!JS_IsUninitialized(error_state)) { + JSValue se = js_new_suppressed_error(ctx, new_error, + error_state); + JS_FreeValue(ctx, new_error); + JS_FreeValue(ctx, error_state); + if (JS_IsException(se)) { + sp[-1] = JS_GetException(ctx); + } else { + sp[-1] = se; + } + } else { + sp[-1] = new_error; + } + } + BREAK; + + CASE(OP_using_dispose_end): + { + JSValue error_state = sp[-1]; + sp--; + if (!JS_IsUninitialized(error_state)) { + JS_Throw(ctx, error_state); + goto exception; + } + } + BREAK; + + CASE(OP_using_check): + { + int hint = pc[0]; + JSValue method; + pc += 1; + sf->cur_pc = pc; + if (js_op_using_check(ctx, sp[-1], hint, &method)) + goto exception; + sp[0] = method; + sp++; + } + BREAK; + + CASE(OP_iterator_next): + /* stack: iter_obj next catch_offset val */ + { + JSValue ret; + sf->cur_pc = pc; + ret = JS_Call(ctx, sp[-3], sp[-4], 1, vc(sp - 1)); + if (JS_IsException(ret)) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp[-1] = ret; + } + BREAK; + + CASE(OP_iterator_call): + /* stack: iter_obj next catch_offset val */ + { + JSValue method, ret; + bool ret_flag; + int flags; + flags = *pc++; + sf->cur_pc = pc; + method = JS_GetProperty(ctx, sp[-4], (flags & 1) ? + JS_ATOM_throw : JS_ATOM_return); + if (JS_IsException(method)) + goto exception; + if (JS_IsUndefined(method) || JS_IsNull(method)) { + ret_flag = true; + } else { + if (flags & 2) { + /* no argument */ + ret = JS_CallFree(ctx, method, sp[-4], + 0, NULL); + } else { + ret = JS_CallFree(ctx, method, sp[-4], + 1, vc(sp - 1)); + } + if (JS_IsException(ret)) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp[-1] = ret; + ret_flag = false; + } + sp[0] = js_bool(ret_flag); + sp += 1; + } + BREAK; + + CASE(OP_lnot): + { + int res; + JSValue op1; + + op1 = sp[-1]; + if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_UNDEFINED) { + res = JS_VALUE_GET_INT(op1) != 0; + } else { + res = JS_ToBoolFree(ctx, op1); + } + sp[-1] = js_bool(!res); + } + BREAK; + + CASE(OP_get_field): + { + JSValue val, obj; + JSAtom atom; + JSObject *p; + JSProperty *pr; + JSShapeProperty *prs; + + atom = get_u32(pc); + pc += 4; + + obj = sp[-1]; + if (likely(JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT)) { + p = JS_VALUE_GET_OBJ(obj); + for(;;) { + prs = find_own_property(&pr, p, atom); + if (prs) { + /* found */ + if (unlikely(prs->flags & JS_PROP_TMASK)) + goto get_field_slow_path; + val = js_dup(pr->u.value); + break; + } + if (unlikely(p->is_exotic)) { + /* XXX: should avoid the slow path for arrays + and typed arrays by ensuring that 'prop' is + not numeric */ + obj = JS_MKPTR(JS_TAG_OBJECT, p); + goto get_field_slow_path; + } + p = p->shape->proto; + if (!p) { + val = JS_UNDEFINED; + break; + } + } + } else { + get_field_slow_path: + sf->cur_pc = pc; + val = JS_GetPropertyInternal(ctx, obj, atom, sp[-1], false); + if (unlikely(JS_IsException(val))) + goto exception; + } + JS_FreeValue(ctx, sp[-1]); + sp[-1] = val; + } + BREAK; + + CASE(OP_get_field2): + { + JSValue val, obj; + JSAtom atom; + JSObject *p; + JSProperty *pr; + JSShapeProperty *prs; + + atom = get_u32(pc); + pc += 4; + + obj = sp[-1]; + if (likely(JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT)) { + p = JS_VALUE_GET_OBJ(obj); + for(;;) { + prs = find_own_property(&pr, p, atom); + if (prs) { + /* found */ + if (unlikely(prs->flags & JS_PROP_TMASK)) + goto get_field2_slow_path; + val = js_dup(pr->u.value); + break; + } + if (unlikely(p->is_exotic)) { + /* XXX: should avoid the slow path for arrays + and typed arrays by ensuring that 'prop' is + not numeric */ + obj = JS_MKPTR(JS_TAG_OBJECT, p); + goto get_field2_slow_path; + } + p = p->shape->proto; + if (!p) { + val = JS_UNDEFINED; + break; + } + } + } else { + get_field2_slow_path: + sf->cur_pc = pc; + val = JS_GetPropertyInternal(ctx, obj, atom, sp[-1], false); + if (unlikely(JS_IsException(val))) + goto exception; + } + *sp++ = val; + } + BREAK; + + CASE(OP_put_field): + { + int ret; + JSValue obj; + JSAtom atom; + JSObject *p; + JSProperty *pr; + JSShapeProperty *prs; + + atom = get_u32(pc); + pc += 4; + + obj = sp[-2]; + if (likely(JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT)) { + p = JS_VALUE_GET_OBJ(obj); + prs = find_own_property(&pr, p, atom); + if (!prs) + goto put_field_slow_path; + if (likely((prs->flags & (JS_PROP_TMASK | JS_PROP_WRITABLE | + JS_PROP_LENGTH)) == JS_PROP_WRITABLE)) { + /* fast path */ + set_value(ctx, &pr->u.value, sp[-1]); + } else { + goto put_field_slow_path; + } + JS_FreeValue(ctx, obj); + sp -= 2; + } else { + put_field_slow_path: + sf->cur_pc = pc; + ret = JS_SetPropertyInternal2(ctx, obj, atom, sp[-1], obj, + JS_PROP_THROW_STRICT); + JS_FreeValue(ctx, obj); + sp -= 2; + if (unlikely(ret < 0)) + goto exception; + } + } + BREAK; + + CASE(OP_private_symbol): + { + JSAtom atom; + JSValue val; + + atom = get_u32(pc); + pc += 4; + val = JS_NewSymbolFromAtom(ctx, atom, JS_ATOM_TYPE_PRIVATE); + if (JS_IsException(val)) + goto exception; + *sp++ = val; + } + BREAK; + + CASE(OP_get_private_field): + { + JSValue val; + sf->cur_pc = pc; + val = JS_GetPrivateField(ctx, sp[-2], sp[-1]); + JS_FreeValue(ctx, sp[-1]); + JS_FreeValue(ctx, sp[-2]); + sp[-2] = val; + sp--; + if (unlikely(JS_IsException(val))) + goto exception; + } + BREAK; + + CASE(OP_put_private_field): + { + int ret; + sf->cur_pc = pc; + ret = JS_SetPrivateField(ctx, sp[-3], sp[-1], sp[-2]); + JS_FreeValue(ctx, sp[-3]); + JS_FreeValue(ctx, sp[-1]); + sp -= 3; + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + + CASE(OP_define_private_field): + { + int ret; + ret = JS_DefinePrivateField(ctx, sp[-3], sp[-2], sp[-1]); + JS_FreeValue(ctx, sp[-2]); + sp -= 2; + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + + CASE(OP_define_field): + { + int ret; + JSAtom atom; + atom = get_u32(pc); + pc += 4; + + ret = JS_DefinePropertyValue(ctx, sp[-2], atom, sp[-1], + JS_PROP_C_W_E | JS_PROP_THROW); + sp--; + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + + CASE(OP_set_name): + { + int ret; + JSAtom atom; + atom = get_u32(pc); + pc += 4; + + ret = JS_DefineObjectName(ctx, sp[-1], atom, JS_PROP_CONFIGURABLE); + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + CASE(OP_set_name_computed): + { + int ret; + ret = JS_DefineObjectNameComputed(ctx, sp[-1], sp[-2], JS_PROP_CONFIGURABLE); + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + CASE(OP_set_proto): + { + JSValue proto; + proto = sp[-1]; + if (JS_IsObject(proto) || JS_IsNull(proto)) { + if (JS_SetPrototypeInternal(ctx, sp[-2], proto, true) < 0) + goto exception; + } + JS_FreeValue(ctx, proto); + sp--; + } + BREAK; + CASE(OP_set_home_object): + js_method_set_home_object(ctx, sp[-1], sp[-2]); + BREAK; + CASE(OP_define_method): + CASE(OP_define_method_computed): + { + JSValue getter, setter, value; + JSValue obj; + JSAtom atom; + int flags, ret, op_flags; + bool is_computed; +#define OP_DEFINE_METHOD_METHOD 0 +#define OP_DEFINE_METHOD_GETTER 1 +#define OP_DEFINE_METHOD_SETTER 2 +#define OP_DEFINE_METHOD_ENUMERABLE 4 + + is_computed = (opcode == OP_define_method_computed); + if (is_computed) { + atom = JS_ValueToAtom(ctx, sp[-2]); + if (unlikely(atom == JS_ATOM_NULL)) + goto exception; + opcode += OP_define_method - OP_define_method_computed; + } else { + atom = get_u32(pc); + pc += 4; + } + op_flags = *pc++; + + obj = sp[-2 - is_computed]; + flags = JS_PROP_HAS_CONFIGURABLE | JS_PROP_CONFIGURABLE | + JS_PROP_HAS_ENUMERABLE | JS_PROP_THROW; + if (op_flags & OP_DEFINE_METHOD_ENUMERABLE) + flags |= JS_PROP_ENUMERABLE; + op_flags &= 3; + value = JS_UNDEFINED; + getter = JS_UNDEFINED; + setter = JS_UNDEFINED; + if (op_flags == OP_DEFINE_METHOD_METHOD) { + value = sp[-1]; + flags |= JS_PROP_HAS_VALUE | JS_PROP_HAS_WRITABLE | JS_PROP_WRITABLE; + } else if (op_flags == OP_DEFINE_METHOD_GETTER) { + getter = sp[-1]; + flags |= JS_PROP_HAS_GET; + } else { + setter = sp[-1]; + flags |= JS_PROP_HAS_SET; + } + ret = js_method_set_properties(ctx, sp[-1], atom, flags, obj); + if (ret >= 0) { + ret = JS_DefineProperty(ctx, obj, atom, value, + getter, setter, flags); + } + JS_FreeValue(ctx, sp[-1]); + if (is_computed) { + JS_FreeAtom(ctx, atom); + JS_FreeValue(ctx, sp[-2]); + } + sp -= 1 + is_computed; + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + + CASE(OP_define_class): + CASE(OP_define_class_computed): + { + int class_flags; + JSAtom atom; + + atom = get_u32(pc); + class_flags = pc[4]; + pc += 5; + if (js_op_define_class(ctx, sp, atom, class_flags, + var_refs, sf, + (opcode == OP_define_class_computed)) < 0) + goto exception; + } + BREAK; + + CASE(OP_get_array_el): + { + JSValue val; + + /* fast path: regular/typed array element by int index */ + if (likely(JS_VALUE_GET_TAG(sp[-2]) == JS_TAG_OBJECT && + JS_VALUE_GET_TAG(sp[-1]) == JS_TAG_INT)) { + JSObject *p = JS_VALUE_GET_OBJ(sp[-2]); + uint32_t idx = JS_VALUE_GET_INT(sp[-1]); + if (likely(p->class_id == JS_CLASS_ARRAY && + idx < p->u.array.count)) { + val = js_dup(p->u.array.u.values[idx]); + JS_FreeValue(ctx, sp[-2]); + sp[-2] = val; + sp--; + BREAK; + } + if (js_get_fast_array_element(ctx, p, idx, &val)) { + JS_FreeValue(ctx, sp[-2]); + sp[-2] = val; + sp--; + BREAK; + } + } + sf->cur_pc = pc; + val = JS_GetPropertyValue(ctx, sp[-2], sp[-1]); + JS_FreeValue(ctx, sp[-2]); + sp[-2] = val; + sp--; + if (unlikely(JS_IsException(val))) + goto exception; + } + BREAK; + + CASE(OP_get_array_el2): + { + JSValue val; + + /* fast path: regular/typed array element by int index */ + if (likely(JS_VALUE_GET_TAG(sp[-2]) == JS_TAG_OBJECT && + JS_VALUE_GET_TAG(sp[-1]) == JS_TAG_INT)) { + JSObject *p = JS_VALUE_GET_OBJ(sp[-2]); + uint32_t idx = JS_VALUE_GET_INT(sp[-1]); + if (likely(p->class_id == JS_CLASS_ARRAY && + idx < p->u.array.count)) { + sp[-1] = js_dup(p->u.array.u.values[idx]); + BREAK; + } + if (js_get_fast_array_element(ctx, p, idx, &val)) { + sp[-1] = val; + BREAK; + } + } + sf->cur_pc = pc; + val = JS_GetPropertyValue(ctx, sp[-2], sp[-1]); + sp[-1] = val; + if (unlikely(JS_IsException(val))) + goto exception; + } + BREAK; + + CASE(OP_get_ref_value): + { + JSValue val; + sf->cur_pc = pc; + if (unlikely(JS_IsUndefined(sp[-2]))) { + JSAtom atom = JS_ValueToAtom(ctx, sp[-1]); + if (atom != JS_ATOM_NULL) { + JS_ThrowReferenceErrorNotDefined(ctx, atom); + JS_FreeAtom(ctx, atom); + } + goto exception; + } + val = JS_GetPropertyValue(ctx, sp[-2], + js_dup(sp[-1])); + if (unlikely(JS_IsException(val))) + goto exception; + sp[0] = val; + sp++; + } + BREAK; + + CASE(OP_get_super_value): + { + JSValue val; + JSAtom atom; + sf->cur_pc = pc; + atom = JS_ValueToAtom(ctx, sp[-1]); + if (unlikely(atom == JS_ATOM_NULL)) + goto exception; + val = JS_GetPropertyInternal(ctx, sp[-2], atom, sp[-3], false); + JS_FreeAtom(ctx, atom); + if (unlikely(JS_IsException(val))) + goto exception; + JS_FreeValue(ctx, sp[-1]); + JS_FreeValue(ctx, sp[-2]); + JS_FreeValue(ctx, sp[-3]); + sp[-3] = val; + sp -= 2; + } + BREAK; + + CASE(OP_put_array_el): + { + int ret; + JSValue val; + uint32_t idx; + JSObject *p; + + val = sp[-1]; + if (likely(JS_VALUE_GET_TAG(sp[-2]) == JS_TAG_INT)) { + idx = JS_VALUE_GET_INT(sp[-2]); + if (likely(JS_VALUE_GET_TAG(sp[-3]) == JS_TAG_OBJECT)) { + p = JS_VALUE_GET_OBJ(sp[-3]); + if (likely(p->class_id == JS_CLASS_ARRAY && + idx < (uint32_t)p->u.array.count)) { + set_value(ctx, &p->u.array.u.values[idx], val); + JS_FreeValue(ctx, sp[-3]); + sp -= 3; + BREAK; + } + if (likely(p->class_id == JS_CLASS_ARRAY && + idx == (uint32_t)p->u.array.count && + p->fast_array && + p->extensible && + p->shape->proto == JS_VALUE_GET_OBJ(ctx->class_proto[JS_CLASS_ARRAY]) && + ctx->std_array_prototype)) { + /* fast path to add an element */ + uint32_t array_len; + if (likely(JS_VALUE_GET_TAG(p->prop[0].u.value) == JS_TAG_INT)) { + uint32_t new_len = idx + 1; + array_len = JS_VALUE_GET_INT(p->prop[0].u.value); + if (likely(new_len <= p->u.array.u1.size)) { + p->u.array.u.values[idx] = val; + p->u.array.count = new_len; + if (new_len > array_len) + p->prop[0].u.value = js_int32(new_len); + JS_FreeValue(ctx, sp[-3]); + sp -= 3; + BREAK; + } + } + } + } + } + sf->cur_pc = pc; + ret = JS_SetPropertyValue(ctx, sp[-3], sp[-2], sp[-1], JS_PROP_THROW_STRICT); + JS_FreeValue(ctx, sp[-3]); + sp -= 3; + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + + CASE(OP_put_ref_value): + { + int ret, flags; + sf->cur_pc = pc; + flags = JS_PROP_THROW_STRICT; + if (unlikely(JS_IsUndefined(sp[-3]))) { + if (is_strict_mode(ctx)) { + JSAtom atom = JS_ValueToAtom(ctx, sp[-2]); + if (atom != JS_ATOM_NULL) { + JS_ThrowReferenceErrorNotDefined(ctx, atom); + JS_FreeAtom(ctx, atom); + } + goto exception; + } else { + sp[-3] = js_dup(ctx->global_obj); + } + } else { + if (is_strict_mode(ctx)) + flags |= JS_PROP_NO_ADD; + } + ret = JS_SetPropertyValue(ctx, sp[-3], sp[-2], sp[-1], flags); + JS_FreeValue(ctx, sp[-3]); + sp -= 3; + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + + CASE(OP_put_super_value): + { + int ret; + JSAtom atom; + sf->cur_pc = pc; + if (JS_VALUE_GET_TAG(sp[-3]) != JS_TAG_OBJECT) { + JS_ThrowTypeErrorNotAnObject(ctx); + goto exception; + } + atom = JS_ValueToAtom(ctx, sp[-2]); + if (unlikely(atom == JS_ATOM_NULL)) + goto exception; + ret = JS_SetPropertyInternal2(ctx, + sp[-3], atom, + sp[-1], sp[-4], + JS_PROP_THROW_STRICT); + JS_FreeAtom(ctx, atom); + JS_FreeValue(ctx, sp[-4]); + JS_FreeValue(ctx, sp[-3]); + JS_FreeValue(ctx, sp[-2]); + sp -= 4; + if (ret < 0) + goto exception; + } + BREAK; + + CASE(OP_define_array_el): + { + int ret; + ret = JS_DefinePropertyValueValue(ctx, sp[-3], js_dup(sp[-2]), sp[-1], + JS_PROP_C_W_E | JS_PROP_THROW); + sp -= 1; + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + + CASE(OP_append): /* array pos enumobj -- array pos */ + { + sf->cur_pc = pc; + if (js_append_enumerate(ctx, sp)) + goto exception; + JS_FreeValue(ctx, *--sp); + } + BREAK; + + CASE(OP_copy_data_properties): /* target source excludeList */ + { + /* stack offsets (-1 based): + 2 bits for target, + 3 bits for source, + 2 bits for exclusionList */ + int mask; + + mask = *pc++; + sf->cur_pc = pc; + if (JS_CopyDataProperties(ctx, sp[-1 - (mask & 3)], + sp[-1 - ((mask >> 2) & 7)], + sp[-1 - ((mask >> 5) & 7)], 0)) + goto exception; + } + BREAK; + + CASE(OP_add): + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + int64_t r; + r = (int64_t)JS_VALUE_GET_INT(op1) + JS_VALUE_GET_INT(op2); + if (unlikely(r < INT32_MIN || r > INT32_MAX)) + sp[-2] = js_float64(r); + else + sp[-2] = js_int32(r); + sp--; + } else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2)) { + JS_X87_FPCW_SAVE_AND_ADJUST(fpcw); + sp[-2] = js_float64(JS_VALUE_GET_FLOAT64(op1) + + JS_VALUE_GET_FLOAT64(op2)); + JS_X87_FPCW_RESTORE(fpcw); + sp--; + } else if (JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op1)) || + JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op2))) { + /* mixed int/float; the BOTH_FLOAT fast path above is unchanged */ + double d1, d2; + if (!js_arith_to_float64(op1, &d1) || + !js_arith_to_float64(op2, &d2)) + goto add_slow_case; + JS_X87_FPCW_SAVE_AND_ADJUST(fpcw); + sp[-2] = js_float64(d1 + d2); + JS_X87_FPCW_RESTORE(fpcw); + sp--; + } else { + add_slow_case: + sf->cur_pc = pc; + if (js_add_slow(ctx, sp)) + goto exception; + sp--; + } + } + BREAK; + CASE(OP_add_loc): + { + JSValue *pv; + int idx; + idx = *pc; + pc += 1; + + pv = &var_buf[idx]; + if (likely(JS_VALUE_IS_BOTH_INT(*pv, sp[-1]))) { + int64_t r; + r = (int64_t)JS_VALUE_GET_INT(*pv) + + JS_VALUE_GET_INT(sp[-1]); + if (unlikely((int)r != r)) + *pv = __JS_NewFloat64((double)r); + else + *pv = js_int32(r); + sp--; + } else if (JS_VALUE_GET_TAG(*pv) == JS_TAG_STRING) { + JSValue op1; + op1 = sp[-1]; + sp--; + sf->cur_pc = pc; + op1 = JS_ToPrimitiveFree(ctx, op1, HINT_NONE); + if (JS_IsException(op1)) + goto exception; + op1 = JS_ConcatString(ctx, js_dup(*pv), op1); + if (JS_IsException(op1)) + goto exception; + set_value(ctx, pv, op1); + } else { + JSValue ops[2]; + /* In case of exception, js_add_slow frees ops[0] + and ops[1], so we must duplicate *pv */ + sf->cur_pc = pc; + ops[0] = js_dup(*pv); + ops[1] = sp[-1]; + sp--; + if (js_add_slow(ctx, ops + 2)) + goto exception; + set_value(ctx, pv, ops[0]); + } + } + BREAK; + CASE(OP_sub): + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + int64_t r; + r = (int64_t)JS_VALUE_GET_INT(op1) - JS_VALUE_GET_INT(op2); + if (unlikely((int)r != r)) + sp[-2] = __JS_NewFloat64((double)r); + else + sp[-2] = js_int32(r); + sp--; + } else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2)) { + JS_X87_FPCW_SAVE_AND_ADJUST(fpcw); + sp[-2] = js_float64(JS_VALUE_GET_FLOAT64(op1) - + JS_VALUE_GET_FLOAT64(op2)); + JS_X87_FPCW_RESTORE(fpcw); + sp--; + } else if (JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op1)) || + JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op2))) { + /* mixed int/float; the BOTH_FLOAT fast path above is unchanged */ + double d1, d2; + if (!js_arith_to_float64(op1, &d1) || + !js_arith_to_float64(op2, &d2)) + goto binary_arith_slow; + JS_X87_FPCW_SAVE_AND_ADJUST(fpcw); + sp[-2] = js_float64(d1 - d2); + JS_X87_FPCW_RESTORE(fpcw); + sp--; + } else { + goto binary_arith_slow; + } + } + BREAK; + CASE(OP_mul): + { + JSValue op1, op2; + double d; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + int32_t v1, v2; + int64_t r; + v1 = JS_VALUE_GET_INT(op1); + v2 = JS_VALUE_GET_INT(op2); + r = (int64_t)v1 * v2; + if (unlikely((int)r != r)) { + d = (double)r; + goto mul_fp_res; + } + /* need to test zero case for -0 result */ + if (unlikely(r == 0 && (v1 | v2) < 0)) { + d = -0.0; + goto mul_fp_res; + } + sp[-2] = js_int32(r); + sp--; + } else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2)) { + JS_X87_FPCW_SAVE_AND_ADJUST(fpcw); + d = JS_VALUE_GET_FLOAT64(op1) * JS_VALUE_GET_FLOAT64(op2); + JS_X87_FPCW_RESTORE(fpcw); + mul_fp_res: + sp[-2] = js_float64(d); + sp--; + } else if (JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op1)) || + JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op2))) { + /* mixed int/float; the BOTH_FLOAT fast path above is unchanged */ + double d1, d2; + if (!js_arith_to_float64(op1, &d1) || + !js_arith_to_float64(op2, &d2)) + goto binary_arith_slow; + JS_X87_FPCW_SAVE_AND_ADJUST(fpcw); + d = d1 * d2; + JS_X87_FPCW_RESTORE(fpcw); + goto mul_fp_res; + } else { + goto binary_arith_slow; + } + } + BREAK; + CASE(OP_div): + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + int v1, v2; + v1 = JS_VALUE_GET_INT(op1); + v2 = JS_VALUE_GET_INT(op2); + sp[-2] = js_number((double)v1 / (double)v2); + sp--; + } else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2)) { + JS_X87_FPCW_SAVE_AND_ADJUST(fpcw); + sp[-2] = js_number(JS_VALUE_GET_FLOAT64(op1) / + JS_VALUE_GET_FLOAT64(op2)); + JS_X87_FPCW_RESTORE(fpcw); + sp--; + } else if (JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op1)) || + JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op2))) { + /* mixed int/float; the BOTH_FLOAT fast path above is unchanged */ + double d1, d2; + if (!js_arith_to_float64(op1, &d1) || + !js_arith_to_float64(op2, &d2)) + goto binary_arith_slow; + JS_X87_FPCW_SAVE_AND_ADJUST(fpcw); + sp[-2] = js_number(d1 / d2); + JS_X87_FPCW_RESTORE(fpcw); + sp--; + } else { + goto binary_arith_slow; + } + } + BREAK; + CASE(OP_mod): + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + int v1, v2, r; + v1 = JS_VALUE_GET_INT(op1); + v2 = JS_VALUE_GET_INT(op2); + /* We must avoid v2 = 0, v1 = INT32_MIN and v2 = + -1 and the cases where the result is -0. */ + if (unlikely(v1 < 0 || v2 <= 0)) + goto binary_arith_slow; + r = v1 % v2; + sp[-2] = js_int32(r); + sp--; + } else { + goto binary_arith_slow; + } + } + BREAK; + CASE(OP_pow): + binary_arith_slow: + sf->cur_pc = pc; + if (js_binary_arith_slow(ctx, sp, opcode)) + goto exception; + sp--; + BREAK; + + CASE(OP_plus): + { + JSValue op1; + uint32_t tag; + op1 = sp[-1]; + tag = JS_VALUE_GET_TAG(op1); + if (tag == JS_TAG_INT || JS_TAG_IS_FLOAT64(tag)) { + } else { + sf->cur_pc = pc; + if (js_unary_arith_slow(ctx, sp, opcode)) + goto exception; + } + } + BREAK; + CASE(OP_neg): + { + JSValue op1; + uint32_t tag; + int val; + double d; + op1 = sp[-1]; + tag = JS_VALUE_GET_TAG(op1); + if (tag == JS_TAG_INT) { + val = JS_VALUE_GET_INT(op1); + /* Note: -0 cannot be expressed as integer */ + if (unlikely(val == 0)) { + d = -0.0; + goto neg_fp_res; + } + if (unlikely(val == INT32_MIN)) { + d = -(double)val; + goto neg_fp_res; + } + sp[-1] = js_int32(-val); + } else if (JS_TAG_IS_FLOAT64(tag)) { + d = -JS_VALUE_GET_FLOAT64(op1); + neg_fp_res: + sp[-1] = js_float64(d); + } else { + sf->cur_pc = pc; + if (js_unary_arith_slow(ctx, sp, opcode)) + goto exception; + } + } + BREAK; + CASE(OP_inc): + { + JSValue op1; + int val; + op1 = sp[-1]; + if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { + val = JS_VALUE_GET_INT(op1); + if (unlikely(val == INT32_MAX)) + goto inc_slow; + sp[-1] = js_int32(val + 1); + } else { + inc_slow: + sf->cur_pc = pc; + if (js_unary_arith_slow(ctx, sp, opcode)) + goto exception; + } + } + BREAK; + CASE(OP_dec): + { + JSValue op1; + int val; + op1 = sp[-1]; + if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { + val = JS_VALUE_GET_INT(op1); + if (unlikely(val == INT32_MIN)) + goto dec_slow; + sp[-1] = js_int32(val - 1); + } else { + dec_slow: + sf->cur_pc = pc; + if (js_unary_arith_slow(ctx, sp, opcode)) + goto exception; + } + } + BREAK; + CASE(OP_post_inc): + { + JSValue op1; + int val; + op1 = sp[-1]; + if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { + val = JS_VALUE_GET_INT(op1); + if (unlikely(val == INT32_MAX)) + goto post_inc_slow; + sp[0] = js_int32(val + 1); + } else { + post_inc_slow: + sf->cur_pc = pc; + if (js_post_inc_slow(ctx, sp, opcode)) + goto exception; + } + sp++; + } + BREAK; + CASE(OP_post_dec): + { + JSValue op1; + int val; + op1 = sp[-1]; + if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { + val = JS_VALUE_GET_INT(op1); + if (unlikely(val == INT32_MIN)) + goto post_dec_slow; + sp[0] = js_int32(val - 1); + } else { + post_dec_slow: + sf->cur_pc = pc; + if (js_post_inc_slow(ctx, sp, opcode)) + goto exception; + } + sp++; + } + BREAK; + CASE(OP_inc_loc): + { + JSValue op1; + int val; + int idx; + idx = *pc; + pc += 1; + + op1 = var_buf[idx]; + if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { + val = JS_VALUE_GET_INT(op1); + if (unlikely(val == INT32_MAX)) + goto inc_loc_slow; + var_buf[idx] = js_int32(val + 1); + } else { + inc_loc_slow: + sf->cur_pc = pc; + /* must duplicate otherwise the variable value may + be destroyed before JS code accesses it */ + op1 = js_dup(op1); + if (js_unary_arith_slow(ctx, &op1 + 1, OP_inc)) + goto exception; + set_value(ctx, &var_buf[idx], op1); + } + } + BREAK; + CASE(OP_dec_loc): + { + JSValue op1; + int val; + int idx; + idx = *pc; + pc += 1; + + op1 = var_buf[idx]; + if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { + val = JS_VALUE_GET_INT(op1); + if (unlikely(val == INT32_MIN)) + goto dec_loc_slow; + var_buf[idx] = js_int32(val - 1); + } else { + dec_loc_slow: + sf->cur_pc = pc; + /* must duplicate otherwise the variable value may + be destroyed before JS code accesses it */ + op1 = js_dup(op1); + if (js_unary_arith_slow(ctx, &op1 + 1, OP_dec)) + goto exception; + set_value(ctx, &var_buf[idx], op1); + } + } + BREAK; + CASE(OP_not): + { + JSValue op1; + op1 = sp[-1]; + if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { + sp[-1] = js_int32(~JS_VALUE_GET_INT(op1)); + } else { + sf->cur_pc = pc; + if (js_not_slow(ctx, sp)) + goto exception; + } + } + BREAK; + + CASE(OP_shl): + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + uint32_t v1, v2; + v1 = JS_VALUE_GET_INT(op1); + v2 = JS_VALUE_GET_INT(op2) & 0x1f; + sp[-2] = js_int32(v1 << v2); + sp--; + } else { + sf->cur_pc = pc; + if (js_binary_logic_slow(ctx, sp, opcode)) + goto exception; + sp--; + } + } + BREAK; + CASE(OP_shr): + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + uint32_t v2; + v2 = JS_VALUE_GET_INT(op2); + v2 &= 0x1f; + sp[-2] = js_uint32((uint32_t)JS_VALUE_GET_INT(op1) >> v2); + sp--; + } else { + sf->cur_pc = pc; + if (js_shr_slow(ctx, sp)) + goto exception; + sp--; + } + } + BREAK; + CASE(OP_sar): + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + uint32_t v2; + v2 = JS_VALUE_GET_INT(op2); + if (unlikely(v2 > 0x1f)) { + v2 &= 0x1f; + } + sp[-2] = js_int32((int)JS_VALUE_GET_INT(op1) >> v2); + sp--; + } else { + sf->cur_pc = pc; + if (js_binary_logic_slow(ctx, sp, opcode)) + goto exception; + sp--; + } + } + BREAK; + CASE(OP_and): + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + sp[-2] = js_int32(JS_VALUE_GET_INT(op1) & JS_VALUE_GET_INT(op2)); + sp--; + } else { + sf->cur_pc = pc; + if (js_binary_logic_slow(ctx, sp, opcode)) + goto exception; + sp--; + } + } + BREAK; + CASE(OP_or): + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + sp[-2] = js_int32(JS_VALUE_GET_INT(op1) | JS_VALUE_GET_INT(op2)); + sp--; + } else { + sf->cur_pc = pc; + if (js_binary_logic_slow(ctx, sp, opcode)) + goto exception; + sp--; + } + } + BREAK; + CASE(OP_xor): + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + sp[-2] = js_int32(JS_VALUE_GET_INT(op1) ^ JS_VALUE_GET_INT(op2)); + sp--; + } else { + sf->cur_pc = pc; + if (js_binary_logic_slow(ctx, sp, opcode)) + goto exception; + sp--; + } + } + BREAK; + + +#define OP_CMP(opcode, binary_op, slow_call) \ + CASE(opcode): \ + { \ + JSValue op1, op2; \ + op1 = sp[-2]; \ + op2 = sp[-1]; \ + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { \ + sp[-2] = js_bool(JS_VALUE_GET_INT(op1) binary_op JS_VALUE_GET_INT(op2)); \ + sp--; \ + } else { \ + sf->cur_pc = pc; \ + if (slow_call) \ + goto exception; \ + sp--; \ + } \ + } \ + BREAK + + OP_CMP(OP_lt, <, js_relational_slow(ctx, sp, opcode)); + OP_CMP(OP_lte, <=, js_relational_slow(ctx, sp, opcode)); + OP_CMP(OP_gt, >, js_relational_slow(ctx, sp, opcode)); + OP_CMP(OP_gte, >=, js_relational_slow(ctx, sp, opcode)); + OP_CMP(OP_eq, ==, js_eq_slow(ctx, sp, 0)); + OP_CMP(OP_neq, !=, js_eq_slow(ctx, sp, 1)); + OP_CMP(OP_strict_eq, ==, js_strict_eq_slow(ctx, sp, 0)); + OP_CMP(OP_strict_neq, !=, js_strict_eq_slow(ctx, sp, 1)); + + CASE(OP_in): + sf->cur_pc = pc; + if (js_operator_in(ctx, sp)) + goto exception; + sp--; + BREAK; + CASE(OP_private_in): + if (js_operator_private_in(ctx, sp)) + goto exception; + sp--; + BREAK; + CASE(OP_instanceof): + sf->cur_pc = pc; + if (js_operator_instanceof(ctx, sp)) + goto exception; + sp--; + BREAK; + CASE(OP_typeof): + { + JSValue op1; + JSAtom atom; + + op1 = sp[-1]; + atom = js_operator_typeof(ctx, op1); + JS_FreeValue(ctx, op1); + sp[-1] = JS_AtomToString(ctx, atom); + } + BREAK; + CASE(OP_delete): + sf->cur_pc = pc; + if (js_operator_delete(ctx, sp)) + goto exception; + sp--; + BREAK; + CASE(OP_delete_var): + { + JSAtom atom; + int ret; + + atom = get_u32(pc); + pc += 4; + + sf->cur_pc = pc; + ret = JS_DeleteGlobalVar(ctx, atom); + if (unlikely(ret < 0)) + goto exception; + *sp++ = js_bool(ret); + } + BREAK; + + CASE(OP_to_object): + if (JS_VALUE_GET_TAG(sp[-1]) != JS_TAG_OBJECT) { + sf->cur_pc = pc; + ret_val = JS_ToObject(ctx, sp[-1]); + if (JS_IsException(ret_val)) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp[-1] = ret_val; + } + BREAK; + + CASE(OP_to_propkey): + switch (JS_VALUE_GET_TAG(sp[-1])) { + case JS_TAG_INT: + case JS_TAG_STRING: + case JS_TAG_SYMBOL: + break; + default: + sf->cur_pc = pc; + ret_val = JS_ToPropertyKey(ctx, sp[-1]); + if (JS_IsException(ret_val)) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp[-1] = ret_val; + break; + } + BREAK; + + CASE(OP_to_propkey2): + /* must be tested first */ + if (unlikely(JS_IsUndefined(sp[-2]) || JS_IsNull(sp[-2]))) { + JS_ThrowTypeError(ctx, "value has no property"); + goto exception; + } + switch (JS_VALUE_GET_TAG(sp[-1])) { + case JS_TAG_INT: + case JS_TAG_STRING: + case JS_TAG_SYMBOL: + break; + default: + sf->cur_pc = pc; + ret_val = JS_ToPropertyKey(ctx, sp[-1]); + if (JS_IsException(ret_val)) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp[-1] = ret_val; + break; + } + BREAK; + CASE(OP_with_get_var): + CASE(OP_with_put_var): + CASE(OP_with_delete_var): + CASE(OP_with_make_ref): + CASE(OP_with_get_ref): + CASE(OP_with_get_ref_undef): + { + JSAtom atom; + int32_t diff; + JSValue obj, val; + int ret, is_with; + atom = get_u32(pc); + diff = get_u32(pc + 4); + is_with = pc[8]; + pc += 9; + sf->cur_pc = pc; + + obj = sp[-1]; + ret = JS_HasProperty(ctx, obj, atom); + if (unlikely(ret < 0)) + goto exception; + if (ret) { + if (is_with) { + ret = js_has_unscopable(ctx, obj, atom); + if (unlikely(ret < 0)) + goto exception; + if (ret) + goto no_with; + } + switch (opcode) { + case OP_with_get_var: + ret = JS_HasProperty(ctx, obj, atom); + if (unlikely(ret < 0)) + goto exception; + + if (ret == 0) { + if (is_strict_mode(ctx)) { + JS_ThrowReferenceErrorNotDefined(ctx, atom); + goto exception; + } + val = JS_UNDEFINED; + } else { + val = JS_GetProperty(ctx, obj, atom); + if (unlikely(JS_IsException(val))) + goto exception; + } + set_value(ctx, &sp[-1], val); + break; + case OP_with_put_var: + /* XXX: check if strict mode */ + ret = JS_SetPropertyInternal(ctx, obj, atom, sp[-2], + JS_PROP_THROW_STRICT); + JS_FreeValue(ctx, sp[-1]); + sp -= 2; + if (unlikely(ret < 0)) + goto exception; + break; + case OP_with_delete_var: + ret = JS_DeleteProperty(ctx, obj, atom, 0); + if (unlikely(ret < 0)) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp[-1] = js_bool(ret); + break; + case OP_with_make_ref: + /* produce a pair object/propname on the stack */ + *sp++ = JS_AtomToValue(ctx, atom); + break; + case OP_with_get_ref: + /* produce a pair object/method on the stack */ + ret = JS_HasProperty(ctx, obj, atom); + if (unlikely(ret < 0)) + goto exception; + if (ret == 0) { + if (is_strict_mode(ctx)) { + JS_ThrowReferenceErrorNotDefined(ctx, atom); + goto exception; + } + val = JS_UNDEFINED; + } else { + val = JS_GetProperty(ctx, obj, atom); + if (unlikely(JS_IsException(val))) + goto exception; + } + *sp++ = val; + break; + case OP_with_get_ref_undef: + /* produce a pair undefined/function on the stack */ + val = JS_GetProperty(ctx, obj, atom); + if (unlikely(JS_IsException(val))) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp[-1] = JS_UNDEFINED; + *sp++ = val; + break; + } + pc += diff - 5; + } else { + no_with: + /* if not jumping, drop the object argument */ + JS_FreeValue(ctx, sp[-1]); + sp--; + } + } + BREAK; + + CASE(OP_await): + ret_val = js_int32(FUNC_RET_AWAIT); + goto done_generator; + CASE(OP_yield): + ret_val = js_int32(FUNC_RET_YIELD); + goto done_generator; + CASE(OP_yield_star): + CASE(OP_async_yield_star): + ret_val = js_int32(FUNC_RET_YIELD_STAR); + goto done_generator; + CASE(OP_return_async): + CASE(OP_initial_yield): + ret_val = JS_UNDEFINED; + goto done_generator; + + CASE(OP_nop): + BREAK; + CASE(OP_is_undefined_or_null): + if (JS_VALUE_GET_TAG(sp[-1]) == JS_TAG_UNDEFINED || + JS_VALUE_GET_TAG(sp[-1]) == JS_TAG_NULL) { + goto set_true; + } else { + goto free_and_set_false; + } + CASE(OP_is_undefined): + if (JS_VALUE_GET_TAG(sp[-1]) == JS_TAG_UNDEFINED) { + goto set_true; + } else { + goto free_and_set_false; + } + CASE(OP_is_null): + if (JS_VALUE_GET_TAG(sp[-1]) == JS_TAG_NULL) { + goto set_true; + } else { + goto free_and_set_false; + } + /* XXX: could merge to a single opcode */ + CASE(OP_typeof_is_undefined): + /* different from OP_is_undefined because of isHTMLDDA */ + if (js_operator_typeof(ctx, sp[-1]) == JS_ATOM_undefined) { + goto free_and_set_true; + } else { + goto free_and_set_false; + } + CASE(OP_typeof_is_function): + if (js_operator_typeof(ctx, sp[-1]) == JS_ATOM_function) { + goto free_and_set_true; + } else { + goto free_and_set_false; + } + free_and_set_true: + JS_FreeValue(ctx, sp[-1]); + set_true: + sp[-1] = JS_TRUE; + BREAK; + free_and_set_false: + JS_FreeValue(ctx, sp[-1]); + sp[-1] = JS_FALSE; + BREAK; + CASE(OP_invalid): + DEFAULT: + JS_ThrowInternalError(ctx, "invalid opcode: pc=%u opcode=0x%02x", + (int)(pc - b->byte_code_buf - 1), opcode); + goto exception; + } + } + exception: + if (needs_backtrace(rt->current_exception) + || JS_IsUndefined(ctx->error_back_trace)) { + sf->cur_pc = pc; + build_backtrace(ctx, rt->current_exception, JS_UNDEFINED, + NULL, 0, 0, 0); + } + if (!JS_IsUncatchableError(rt->current_exception)) { + while (sp > stack_buf) { + JSValue val = *--sp; + JS_FreeValue(ctx, val); + if (JS_VALUE_GET_TAG(val) == JS_TAG_CATCH_OFFSET) { + int pos = JS_VALUE_GET_INT(val); + if (pos == 0) { + /* enumerator: close it with a throw */ + JS_FreeValue(ctx, sp[-1]); /* drop the next method */ + sp--; + JS_IteratorClose(ctx, sp[-1], true); + } else { + *sp++ = rt->current_exception; + rt->current_exception = JS_UNINITIALIZED; + JS_FreeValueRT(rt, ctx->error_back_trace); + ctx->error_back_trace = JS_UNDEFINED; + pc = b->byte_code_buf + pos; + goto restart; + } + } + } + } + ret_val = JS_EXCEPTION; + /* the local variables are freed by the caller in the generator + case. Hence the label 'done' should never be reached in a + generator function. */ + if (b->func_kind != JS_FUNC_NORMAL) { + done_generator: + sf->cur_pc = pc; + sf->cur_sp = sp; + } else { + done: + if (unlikely(sf->var_ref_count != 0)) { + /* variable references reference the stack: must close them */ + close_var_refs(rt, sf); + } + /* free the local variables and stack */ + for(pval = local_buf; pval < sp; pval++) { + JS_FreeValue(ctx, *pval); + } + } + rt->current_stack_frame = sf->prev_frame; + return ret_val; +} + +JSValue JS_Call(JSContext *ctx, JSValueConst func_obj, JSValueConst this_obj, + int argc, JSValueConst *argv) +{ + return JS_CallInternal(ctx, func_obj, this_obj, JS_UNDEFINED, + argc, argv, JS_CALL_FLAG_COPY_ARGV); +} + +static JSValue JS_CallFree(JSContext *ctx, JSValue func_obj, JSValueConst this_obj, + int argc, JSValueConst *argv) +{ + JSValue res = JS_CallInternal(ctx, func_obj, this_obj, JS_UNDEFINED, + argc, argv, JS_CALL_FLAG_COPY_ARGV); + JS_FreeValue(ctx, func_obj); + return res; +} + +/* warning: the refcount of the context is not incremented. Return + NULL in case of exception (case of revoked proxy only) */ +static JSContext *JS_GetFunctionRealm(JSContext *ctx, JSValueConst func_obj) +{ + JSObject *p; + JSContext *realm; + + if (JS_VALUE_GET_TAG(func_obj) != JS_TAG_OBJECT) + return ctx; + p = JS_VALUE_GET_OBJ(func_obj); + switch(p->class_id) { + case JS_CLASS_C_FUNCTION: + realm = p->u.cfunc.realm; + break; + case JS_CLASS_BYTECODE_FUNCTION: + case JS_CLASS_GENERATOR_FUNCTION: + case JS_CLASS_ASYNC_FUNCTION: + case JS_CLASS_ASYNC_GENERATOR_FUNCTION: + { + JSFunctionBytecode *b; + b = p->u.func.function_bytecode; + realm = b->realm; + } + break; + case JS_CLASS_PROXY: + { + JSProxyData *s = p->u.opaque; + if (!s) + return ctx; + if (s->is_revoked) { + JS_ThrowTypeErrorRevokedProxy(ctx); + return NULL; + } else { + realm = JS_GetFunctionRealm(ctx, s->target); + } + } + break; + case JS_CLASS_BOUND_FUNCTION: + { + JSBoundFunction *bf = p->u.bound_function; + realm = JS_GetFunctionRealm(ctx, bf->func_obj); + } + break; + default: + realm = ctx; + break; + } + return realm; +} + +static JSValue js_create_from_ctor(JSContext *ctx, JSValueConst ctor, + int class_id) +{ + JSValue proto, obj; + JSContext *realm; + + if (JS_IsUndefined(ctor)) { + proto = js_dup(ctx->class_proto[class_id]); + } else { + proto = JS_GetProperty(ctx, ctor, JS_ATOM_prototype); + if (JS_IsException(proto)) + return proto; + if (!JS_IsObject(proto)) { + JS_FreeValue(ctx, proto); + realm = JS_GetFunctionRealm(ctx, ctor); + if (!realm) + return JS_EXCEPTION; + proto = js_dup(realm->class_proto[class_id]); + } + } + obj = JS_NewObjectProtoClass(ctx, proto, class_id); + JS_FreeValue(ctx, proto); + return obj; +} + +/* argv[] is modified if (flags & JS_CALL_FLAG_COPY_ARGV) = 0. */ +static JSValue JS_CallConstructorInternal(JSContext *ctx, + JSValueConst func_obj, + JSValueConst new_target, + int argc, JSValueConst *argv, + int flags) +{ + JSObject *p; + JSFunctionBytecode *b; + + if (js_poll_interrupts(ctx)) + return JS_EXCEPTION; + flags |= JS_CALL_FLAG_CONSTRUCTOR; + if (unlikely(JS_VALUE_GET_TAG(func_obj) != JS_TAG_OBJECT)) + goto not_a_function; + p = JS_VALUE_GET_OBJ(func_obj); + if (unlikely(!p->is_constructor)) + return JS_ThrowTypeErrorNotAConstructor(ctx, func_obj); + if (unlikely(p->class_id != JS_CLASS_BYTECODE_FUNCTION)) { + JSClassCall *call_func; + call_func = ctx->rt->class_array[p->class_id].call; + if (!call_func) { + not_a_function: + return JS_ThrowTypeErrorNotAFunction(ctx); + } + return call_func(ctx, func_obj, new_target, argc, + argv, flags); + } + + b = p->u.func.function_bytecode; + if (b->is_derived_class_constructor) { + return JS_CallInternal(ctx, func_obj, JS_UNDEFINED, new_target, argc, argv, flags); + } else { + JSValue obj, ret; + /* legacy constructor behavior */ + obj = js_create_from_ctor(ctx, new_target, JS_CLASS_OBJECT); + if (JS_IsException(obj)) + return JS_EXCEPTION; + ret = JS_CallInternal(ctx, func_obj, obj, new_target, argc, argv, flags); + if (JS_VALUE_GET_TAG(ret) == JS_TAG_OBJECT || + JS_IsException(ret)) { + JS_FreeValue(ctx, obj); + return ret; + } else { + JS_FreeValue(ctx, ret); + return obj; + } + } +} + +JSValue JS_CallConstructor2(JSContext *ctx, JSValueConst func_obj, + JSValueConst new_target, + int argc, JSValueConst *argv) +{ + return JS_CallConstructorInternal(ctx, func_obj, new_target, + argc, argv, + JS_CALL_FLAG_COPY_ARGV); +} + +JSValue JS_CallConstructor(JSContext *ctx, JSValueConst func_obj, + int argc, JSValueConst *argv) +{ + return JS_CallConstructorInternal(ctx, func_obj, func_obj, + argc, argv, + JS_CALL_FLAG_COPY_ARGV); +} + +JSValue JS_Invoke(JSContext *ctx, JSValueConst this_val, JSAtom atom, + int argc, JSValueConst *argv) +{ + JSValue func_obj; + func_obj = JS_GetProperty(ctx, this_val, atom); + if (JS_IsException(func_obj)) + return func_obj; + return JS_CallFree(ctx, func_obj, this_val, argc, argv); +} + +static JSValue JS_InvokeFree(JSContext *ctx, JSValue this_val, JSAtom atom, + int argc, JSValueConst *argv) +{ + JSValue res = JS_Invoke(ctx, this_val, atom, argc, argv); + JS_FreeValue(ctx, this_val); + return res; +} + +/* JSAsyncFunctionState (used by generator and async functions) */ +static __exception int async_func_init(JSContext *ctx, JSAsyncFunctionState *s, + JSValueConst func_obj, + JSValueConst this_obj, + int argc, JSValueConst *argv) +{ + JSObject *p; + JSFunctionBytecode *b; + JSStackFrame *sf; + int local_count, i, arg_buf_len, n; + size_t alloc_size; + + sf = &s->frame; + p = JS_VALUE_GET_OBJ(func_obj); + b = p->u.func.function_bytecode; + sf->is_strict_mode = b->is_strict_mode; + sf->is_constructor = false; + sf->cur_pc = b->byte_code_buf; + arg_buf_len = max_int(b->arg_count, argc); + local_count = arg_buf_len + b->var_count + b->stack_size; + alloc_size = sizeof(JSValue) * max_int(local_count, 1) + + sizeof(JSVarRef *) * b->var_ref_count; + sf->arg_buf = js_malloc(ctx, alloc_size); + if (!sf->arg_buf) + return -1; + sf->cur_func = js_dup(func_obj); + s->this_val = js_dup(this_obj); + s->argc = argc; + sf->arg_count = arg_buf_len; + sf->var_buf = sf->arg_buf + arg_buf_len; + sf->cur_sp = sf->var_buf + b->var_count; + /* set by the caller once the owning coroutine GC object exists */ + sf->cur_gc_obj = NULL; + sf->var_refs = (JSVarRef **)(sf->cur_sp + b->stack_size); + sf->var_ref_count = b->var_ref_count; + for(i = 0; i < b->var_ref_count; i++) + sf->var_refs[i] = NULL; + for(i = 0; i < argc; i++) + sf->arg_buf[i] = js_dup(argv[i]); + n = arg_buf_len + b->var_count; + for(i = argc; i < n; i++) + sf->arg_buf[i] = JS_UNDEFINED; + return 0; +} + +static void async_func_mark(JSRuntime *rt, JSAsyncFunctionState *s, + JS_MarkFunc *mark_func) +{ + JSStackFrame *sf; + JSValue *sp; + + sf = &s->frame; + JS_MarkValue(rt, sf->cur_func, mark_func); + JS_MarkValue(rt, s->this_val, mark_func); + if (sf->cur_sp) { + /* if the function is running, cur_sp is not known so we + cannot mark the stack. Marking the variables is not needed + because a running function cannot be part of a removable + cycle */ + for(sp = sf->arg_buf; sp < sf->cur_sp; sp++) + JS_MarkValue(rt, *sp, mark_func); + } +} + +static void async_func_free(JSRuntime *rt, JSAsyncFunctionState *s) +{ + JSStackFrame *sf; + JSValue *sp; + + sf = &s->frame; + + if (sf->arg_buf) { + /* close the closure variables. */ + if (sf->var_ref_count != 0) + close_var_refs(rt, sf); + + /* cannot free the function if it is running */ + assert(sf->cur_sp != NULL); + for(sp = sf->arg_buf; sp < sf->cur_sp; sp++) { + JS_FreeValueRT(rt, *sp); + } + js_free_rt(rt, sf->arg_buf); + } + JS_FreeValueRT(rt, sf->cur_func); + JS_FreeValueRT(rt, s->this_val); +} + +static JSValue async_func_resume(JSContext *ctx, JSAsyncFunctionState *s) +{ + JSValue func_obj; + + if (js_check_stack_overflow(ctx->rt, 0)) + return JS_ThrowStackOverflow(ctx); + + /* the tag does not matter provided it is not an object */ + func_obj = JS_MKPTR(JS_TAG_INT, s); + return JS_CallInternal(ctx, func_obj, s->this_val, JS_UNDEFINED, + s->argc, vc(s->frame.arg_buf), + JS_CALL_FLAG_GENERATOR); +} + + +/* Generators */ + +typedef enum JSGeneratorStateEnum { + JS_GENERATOR_STATE_SUSPENDED_START, + JS_GENERATOR_STATE_SUSPENDED_YIELD, + JS_GENERATOR_STATE_SUSPENDED_YIELD_STAR, + JS_GENERATOR_STATE_EXECUTING, + JS_GENERATOR_STATE_COMPLETED, +} JSGeneratorStateEnum; + +typedef struct JSGeneratorData { + JSGeneratorStateEnum state; + JSAsyncFunctionState func_state; +} JSGeneratorData; + +static void free_generator_stack_rt(JSRuntime *rt, JSGeneratorData *s) +{ + if (s->state == JS_GENERATOR_STATE_COMPLETED) + return; + async_func_free(rt, &s->func_state); + s->state = JS_GENERATOR_STATE_COMPLETED; +} + +static void js_generator_finalizer(JSRuntime *rt, JSValueConst obj) +{ + JSGeneratorData *s = JS_GetOpaque(obj, JS_CLASS_GENERATOR); + + if (s) { + free_generator_stack_rt(rt, s); + js_free_rt(rt, s); + } +} + +static void free_generator_stack(JSContext *ctx, JSGeneratorData *s) +{ + free_generator_stack_rt(ctx->rt, s); +} + +static void js_generator_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSGeneratorData *s = p->u.generator_data; + + if (!s || s->state == JS_GENERATOR_STATE_COMPLETED) + return; + async_func_mark(rt, &s->func_state, mark_func); +} + +/* XXX: use enum */ +#define GEN_MAGIC_NEXT 0 +#define GEN_MAGIC_RETURN 1 +#define GEN_MAGIC_THROW 2 + +static JSValue js_generator_next(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, + int *pdone, int magic) +{ + JSGeneratorData *s = JS_GetOpaque(this_val, JS_CLASS_GENERATOR); + JSStackFrame *sf; + JSValue ret, func_ret; + + *pdone = true; + if (!s) + return JS_ThrowTypeError(ctx, "not a generator"); + sf = &s->func_state.frame; + switch(s->state) { + default: + case JS_GENERATOR_STATE_SUSPENDED_START: + if (magic == GEN_MAGIC_NEXT) { + goto exec_no_arg; + } else { + free_generator_stack(ctx, s); + goto done; + } + break; + case JS_GENERATOR_STATE_SUSPENDED_YIELD_STAR: + case JS_GENERATOR_STATE_SUSPENDED_YIELD: + /* cur_sp[-1] was set to JS_UNDEFINED in the previous call */ + ret = js_dup(argv[0]); + if (magic == GEN_MAGIC_THROW && + s->state == JS_GENERATOR_STATE_SUSPENDED_YIELD) { + JS_Throw(ctx, ret); + s->func_state.throw_flag = true; + } else { + sf->cur_sp[-1] = ret; + sf->cur_sp[0] = js_int32(magic); + sf->cur_sp++; + exec_no_arg: + s->func_state.throw_flag = false; + } + s->state = JS_GENERATOR_STATE_EXECUTING; + func_ret = async_func_resume(ctx, &s->func_state); + s->state = JS_GENERATOR_STATE_SUSPENDED_YIELD; + if (JS_IsException(func_ret)) { + /* finalize the execution in case of exception */ + free_generator_stack(ctx, s); + return func_ret; + } + if (JS_VALUE_GET_TAG(func_ret) == JS_TAG_INT) { + /* get the returned yield value at the top of the stack */ + ret = sf->cur_sp[-1]; + sf->cur_sp[-1] = JS_UNDEFINED; + if (JS_VALUE_GET_INT(func_ret) == FUNC_RET_YIELD_STAR) { + s->state = JS_GENERATOR_STATE_SUSPENDED_YIELD_STAR; + /* return (value, done) object */ + *pdone = 2; + } else { + *pdone = false; + } + } else { + /* end of iterator */ + ret = sf->cur_sp[-1]; + sf->cur_sp[-1] = JS_UNDEFINED; + JS_FreeValue(ctx, func_ret); + free_generator_stack(ctx, s); + } + break; + case JS_GENERATOR_STATE_COMPLETED: + done: + /* execution is finished */ + switch(magic) { + default: + case GEN_MAGIC_NEXT: + ret = JS_UNDEFINED; + break; + case GEN_MAGIC_RETURN: + ret = js_dup(argv[0]); + break; + case GEN_MAGIC_THROW: + ret = JS_Throw(ctx, js_dup(argv[0])); + break; + } + break; + case JS_GENERATOR_STATE_EXECUTING: + ret = JS_ThrowTypeError(ctx, "cannot invoke a running generator"); + break; + } + return ret; +} + +static JSValue js_call_generator_function(JSContext *ctx, JSValueConst func_obj, + JSValueConst this_obj, + int argc, JSValueConst *argv, + int flags) +{ + JSValue obj, func_ret; + JSGeneratorData *s; + + s = js_mallocz(ctx, sizeof(*s)); + if (!s) + return JS_EXCEPTION; + s->state = JS_GENERATOR_STATE_SUSPENDED_START; + if (async_func_init(ctx, &s->func_state, func_obj, this_obj, argc, argv)) { + s->state = JS_GENERATOR_STATE_COMPLETED; + goto fail; + } + + /* execute the function up to 'OP_initial_yield' */ + func_ret = async_func_resume(ctx, &s->func_state); + if (JS_IsException(func_ret)) + goto fail; + JS_FreeValue(ctx, func_ret); + + obj = js_create_from_ctor(ctx, func_obj, JS_CLASS_GENERATOR); + if (JS_IsException(obj)) + goto fail; + JS_SetOpaqueInternal(obj, s); + /* the body only starts running on the first next(); root captured + locals against the generator object from now on (the initial resume + above only reaches OP_initial_yield, before any user code) */ + s->func_state.frame.cur_gc_obj = &JS_VALUE_GET_OBJ(obj)->header; + return obj; + fail: + free_generator_stack_rt(ctx->rt, s); + js_free(ctx, s); + return JS_EXCEPTION; +} + +/* AsyncFunction */ + +static void js_async_function_terminate(JSRuntime *rt, JSAsyncFunctionData *s) +{ + if (s->is_active) { + async_func_free(rt, &s->func_state); + s->is_active = false; + } +} + +static void js_async_function_free0(JSRuntime *rt, JSAsyncFunctionData *s) +{ + js_async_function_terminate(rt, s); + JS_FreeValueRT(rt, s->resolving_funcs[0]); + JS_FreeValueRT(rt, s->resolving_funcs[1]); + remove_gc_object(&s->header); + js_free_rt(rt, s); +} + +static void js_async_function_free(JSRuntime *rt, JSAsyncFunctionData *s) +{ + if (--JS_REF_COUNT(s) == 0) { + js_async_function_free0(rt, s); + } +} + +static void js_async_function_resolve_finalizer(JSRuntime *rt, + JSValueConst val) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSAsyncFunctionData *s = p->u.async_function_data; + if (s) { + js_async_function_free(rt, s); + } +} + +static void js_async_function_resolve_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSAsyncFunctionData *s = p->u.async_function_data; + if (s) { + mark_func(rt, &s->header); + } +} + +static int js_async_function_resolve_create(JSContext *ctx, + JSAsyncFunctionData *s, + JSValue *resolving_funcs) +{ + int i; + JSObject *p; + + for(i = 0; i < 2; i++) { + resolving_funcs[i] = + JS_NewObjectProtoClass(ctx, ctx->function_proto, + JS_CLASS_ASYNC_FUNCTION_RESOLVE + i); + if (JS_IsException(resolving_funcs[i])) { + if (i == 1) + JS_FreeValue(ctx, resolving_funcs[0]); + resolving_funcs[0] = JS_UNDEFINED; + resolving_funcs[1] = JS_UNDEFINED; + return -1; + } + p = JS_VALUE_GET_OBJ(resolving_funcs[i]); + JS_REF_COUNT(s)++; + p->u.async_function_data = s; + } + return 0; +} + +static bool js_async_function_resume(JSContext *ctx, JSAsyncFunctionData *s) +{ + bool is_success = true; + JSValue func_ret, ret2; + + func_ret = async_func_resume(ctx, &s->func_state); + if (JS_IsException(func_ret)) { + fail: + if (unlikely(JS_IsUncatchableError(ctx->rt->current_exception))) { + is_success = false; + } else { + JSValue error = JS_GetException(ctx); + ret2 = JS_Call(ctx, s->resolving_funcs[1], JS_UNDEFINED, + 1, vc(&error)); + JS_FreeValue(ctx, error); + resolved: + if (unlikely(JS_IsException(ret2))) { + if (JS_IsUncatchableError(ctx->rt->current_exception)) { + is_success = false; + } else { + abort(); /* BUG */ + } + } + JS_FreeValue(ctx, ret2); + } + js_async_function_terminate(ctx->rt, s); + } else { + JSValue value; + value = s->func_state.frame.cur_sp[-1]; + s->func_state.frame.cur_sp[-1] = JS_UNDEFINED; + if (JS_IsUndefined(func_ret)) { + /* function returned */ + ret2 = JS_Call(ctx, s->resolving_funcs[0], JS_UNDEFINED, + 1, vc(&value)); + JS_FreeValue(ctx, value); + goto resolved; + } else { + JSValue promise, resolving_funcs[2], resolving_funcs1[2]; + int i, res; + + /* await */ + JS_FreeValue(ctx, func_ret); /* not used */ + promise = js_promise_resolve(ctx, ctx->promise_ctor, + 1, vc(&value), 0); + JS_FreeValue(ctx, value); + if (JS_IsException(promise)) + goto fail; + if (js_async_function_resolve_create(ctx, s, resolving_funcs)) { + JS_FreeValue(ctx, promise); + goto fail; + } + + /* Note: no need to create 'thrownawayCapability' as in + the spec */ + for(i = 0; i < 2; i++) + resolving_funcs1[i] = JS_UNDEFINED; + res = perform_promise_then(ctx, promise, + vc(resolving_funcs), + vc(resolving_funcs1)); + JS_FreeValue(ctx, promise); + for(i = 0; i < 2; i++) + JS_FreeValue(ctx, resolving_funcs[i]); + if (res) + goto fail; + } + } + return is_success; +} + +static JSValue js_async_function_resolve_call(JSContext *ctx, + JSValueConst func_obj, + JSValueConst this_obj, + int argc, JSValueConst *argv, + int flags) +{ + JSObject *p = JS_VALUE_GET_OBJ(func_obj); + JSAsyncFunctionData *s = p->u.async_function_data; + bool is_reject = p->class_id - JS_CLASS_ASYNC_FUNCTION_RESOLVE; + JSValueConst arg; + + if (argc > 0) + arg = argv[0]; + else + arg = JS_UNDEFINED; + s->func_state.throw_flag = is_reject; + if (is_reject) { + JS_Throw(ctx, js_dup(arg)); + } else { + /* return value of await */ + s->func_state.frame.cur_sp[-1] = js_dup(arg); + } + if (!js_async_function_resume(ctx, s)) + return JS_EXCEPTION; + return JS_UNDEFINED; +} + +static JSValue js_async_function_call(JSContext *ctx, JSValueConst func_obj, + JSValueConst this_obj, + int argc, JSValueConst *argv, int flags) +{ + JSValue promise; + JSAsyncFunctionData *s; + + s = js_mallocz(ctx, sizeof(*s)); + if (!s) + return JS_EXCEPTION; + JS_REF_COUNT(s) = 1; + add_gc_object(ctx->rt, &s->header, JS_GC_OBJ_TYPE_ASYNC_FUNCTION); + s->is_active = false; + s->resolving_funcs[0] = JS_UNDEFINED; + s->resolving_funcs[1] = JS_UNDEFINED; + + promise = JS_NewPromiseCapability(ctx, s->resolving_funcs); + if (JS_IsException(promise)) + goto fail; + + if (async_func_init(ctx, &s->func_state, func_obj, this_obj, argc, argv)) { + fail: + JS_FreeValue(ctx, promise); + js_async_function_free(ctx->rt, s); + return JS_EXCEPTION; + } + s->is_active = true; + /* the body runs immediately (up to the first await), so the frame must + already know its owning coroutine to root captured locals */ + s->func_state.frame.cur_gc_obj = &s->header; + + if (!js_async_function_resume(ctx, s)) + goto fail; + + js_async_function_free(ctx->rt, s); + + return promise; +} + +/* AsyncGenerator */ + +typedef enum JSAsyncGeneratorStateEnum { + JS_ASYNC_GENERATOR_STATE_SUSPENDED_START, + JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD, + JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD_STAR, + JS_ASYNC_GENERATOR_STATE_EXECUTING, + JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN, + JS_ASYNC_GENERATOR_STATE_COMPLETED, +} JSAsyncGeneratorStateEnum; + +typedef struct JSAsyncGeneratorRequest { + struct list_head link; + /* completion */ + int completion_type; /* GEN_MAGIC_x */ + JSValue result; + /* promise capability */ + JSValue promise; + JSValue resolving_funcs[2]; +} JSAsyncGeneratorRequest; + +typedef struct JSAsyncGeneratorData { + JSObject *generator; /* back pointer to the object (const) */ + JSAsyncGeneratorStateEnum state; + JSAsyncFunctionState func_state; + struct list_head queue; /* list of JSAsyncGeneratorRequest.link */ +} JSAsyncGeneratorData; + +static void js_async_generator_free(JSRuntime *rt, + JSAsyncGeneratorData *s) +{ + struct list_head *el, *el1; + JSAsyncGeneratorRequest *req; + + list_for_each_safe(el, el1, &s->queue) { + req = list_entry(el, JSAsyncGeneratorRequest, link); + JS_FreeValueRT(rt, req->result); + JS_FreeValueRT(rt, req->promise); + JS_FreeValueRT(rt, req->resolving_funcs[0]); + JS_FreeValueRT(rt, req->resolving_funcs[1]); + js_free_rt(rt, req); + } + if (s->state != JS_ASYNC_GENERATOR_STATE_COMPLETED && + s->state != JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN) { + async_func_free(rt, &s->func_state); + } + js_free_rt(rt, s); +} + +static void js_async_generator_finalizer(JSRuntime *rt, JSValueConst obj) +{ + JSAsyncGeneratorData *s = JS_GetOpaque(obj, JS_CLASS_ASYNC_GENERATOR); + + if (s) { + js_async_generator_free(rt, s); + } +} + +static void js_async_generator_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func) +{ + JSAsyncGeneratorData *s = JS_GetOpaque(val, JS_CLASS_ASYNC_GENERATOR); + struct list_head *el; + JSAsyncGeneratorRequest *req; + if (s) { + list_for_each(el, &s->queue) { + req = list_entry(el, JSAsyncGeneratorRequest, link); + JS_MarkValue(rt, req->result, mark_func); + JS_MarkValue(rt, req->promise, mark_func); + JS_MarkValue(rt, req->resolving_funcs[0], mark_func); + JS_MarkValue(rt, req->resolving_funcs[1], mark_func); + } + if (s->state != JS_ASYNC_GENERATOR_STATE_COMPLETED && + s->state != JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN) { + async_func_mark(rt, &s->func_state, mark_func); + } + } +} + +static JSValue js_async_generator_resolve_function(JSContext *ctx, + JSValueConst this_obj, + int argc, JSValueConst *argv, + int magic, JSValueConst *func_data); + +static int js_async_generator_resolve_function_create(JSContext *ctx, + JSValue generator, + JSValue *resolving_funcs, + bool is_resume_next) +{ + int i; + JSValue func; + + for(i = 0; i < 2; i++) { + func = JS_NewCFunctionData(ctx, js_async_generator_resolve_function, 1, + i + is_resume_next * 2, 1, vc(&generator)); + if (JS_IsException(func)) { + if (i == 1) + JS_FreeValue(ctx, resolving_funcs[0]); + resolving_funcs[0] = JS_UNDEFINED; + resolving_funcs[1] = JS_UNDEFINED; + return -1; + } + resolving_funcs[i] = func; + } + return 0; +} + +static int js_async_generator_await(JSContext *ctx, + JSAsyncGeneratorData *s, + JSValue value) +{ + JSValue promise, resolving_funcs[2], resolving_funcs1[2]; + int i, res; + + promise = js_promise_resolve(ctx, ctx->promise_ctor, + 1, vc(&value), 0); + if (JS_IsException(promise)) + goto fail; + + if (js_async_generator_resolve_function_create(ctx, JS_MKPTR(JS_TAG_OBJECT, s->generator), + resolving_funcs, false)) { + JS_FreeValue(ctx, promise); + goto fail; + } + + /* Note: no need to create 'thrownawayCapability' as in + the spec */ + for(i = 0; i < 2; i++) + resolving_funcs1[i] = JS_UNDEFINED; + res = perform_promise_then(ctx, promise, + vc(resolving_funcs), + vc(resolving_funcs1)); + JS_FreeValue(ctx, promise); + for(i = 0; i < 2; i++) + JS_FreeValue(ctx, resolving_funcs[i]); + if (res) + goto fail; + return 0; + fail: + return -1; +} + +static void js_async_generator_resolve_or_reject(JSContext *ctx, + JSAsyncGeneratorData *s, + JSValueConst result, + int is_reject) +{ + JSAsyncGeneratorRequest *next; + JSValue ret; + + next = list_entry(s->queue.next, JSAsyncGeneratorRequest, link); + list_del(&next->link); + ret = JS_Call(ctx, next->resolving_funcs[is_reject], JS_UNDEFINED, 1, + &result); + JS_FreeValue(ctx, ret); + JS_FreeValue(ctx, next->result); + JS_FreeValue(ctx, next->promise); + JS_FreeValue(ctx, next->resolving_funcs[0]); + JS_FreeValue(ctx, next->resolving_funcs[1]); + js_free(ctx, next); +} + +static void js_async_generator_resolve(JSContext *ctx, + JSAsyncGeneratorData *s, + JSValueConst value, + bool done) +{ + JSValue result; + result = js_create_iterator_result(ctx, js_dup(value), done); + /* XXX: better exception handling ? */ + js_async_generator_resolve_or_reject(ctx, s, result, 0); + JS_FreeValue(ctx, result); + } + +static void js_async_generator_reject(JSContext *ctx, + JSAsyncGeneratorData *s, + JSValueConst exception) +{ + js_async_generator_resolve_or_reject(ctx, s, exception, 1); +} + +static void js_async_generator_complete(JSContext *ctx, + JSAsyncGeneratorData *s) +{ + if (s->state != JS_ASYNC_GENERATOR_STATE_COMPLETED) { + s->state = JS_ASYNC_GENERATOR_STATE_COMPLETED; + async_func_free(ctx->rt, &s->func_state); + } +} + +static int js_async_generator_completed_return(JSContext *ctx, + JSAsyncGeneratorData *s, + JSValue value) +{ + JSValue promise, resolving_funcs[2], resolving_funcs1[2]; + int res; + + // Can fail looking up JS_ATOM_constructor when is_reject==0. + promise = js_promise_resolve(ctx, ctx->promise_ctor, 1, vc(&value), + /*is_reject*/0); + // A poisoned .constructor property is observable and the resulting + // exception should be delivered to the catch handler. + if (JS_IsException(promise)) { + JSValue err = JS_GetException(ctx); + promise = js_promise_resolve(ctx, ctx->promise_ctor, 1, vc(&err), + /*is_reject*/1); + JS_FreeValue(ctx, err); + if (JS_IsException(promise)) + return -1; + } + if (js_async_generator_resolve_function_create(ctx, + JS_MKPTR(JS_TAG_OBJECT, s->generator), + resolving_funcs1, + true)) { + JS_FreeValue(ctx, promise); + return -1; + } + resolving_funcs[0] = JS_UNDEFINED; + resolving_funcs[1] = JS_UNDEFINED; + res = perform_promise_then(ctx, promise, + vc(resolving_funcs1), + vc(resolving_funcs)); + JS_FreeValue(ctx, resolving_funcs1[0]); + JS_FreeValue(ctx, resolving_funcs1[1]); + JS_FreeValue(ctx, promise); + return res; +} + +static void js_async_generator_resume_next(JSContext *ctx, + JSAsyncGeneratorData *s) +{ + JSAsyncGeneratorRequest *next; + JSValue func_ret, value; + + for(;;) { + if (list_empty(&s->queue)) + break; + next = list_entry(s->queue.next, JSAsyncGeneratorRequest, link); + switch(s->state) { + case JS_ASYNC_GENERATOR_STATE_EXECUTING: + /* only happens when restarting execution after await() */ + goto resume_exec; + case JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN: + goto done; + case JS_ASYNC_GENERATOR_STATE_SUSPENDED_START: + if (next->completion_type == GEN_MAGIC_NEXT) { + goto exec_no_arg; + } else { + js_async_generator_complete(ctx, s); + } + break; + case JS_ASYNC_GENERATOR_STATE_COMPLETED: + if (next->completion_type == GEN_MAGIC_NEXT) { + js_async_generator_resolve(ctx, s, JS_UNDEFINED, true); + } else if (next->completion_type == GEN_MAGIC_RETURN) { + s->state = JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN; + js_async_generator_completed_return(ctx, s, next->result); + } else { + js_async_generator_reject(ctx, s, next->result); + } + goto done; + case JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD: + case JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD_STAR: + value = js_dup(next->result); + if (next->completion_type == GEN_MAGIC_THROW && + s->state == JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD) { + JS_Throw(ctx, value); + s->func_state.throw_flag = true; + } else { + /* 'yield' returns a value. 'yield *' also returns a value + in case the 'throw' method is called */ + s->func_state.frame.cur_sp[-1] = value; + s->func_state.frame.cur_sp[0] = + js_int32(next->completion_type); + s->func_state.frame.cur_sp++; + exec_no_arg: + s->func_state.throw_flag = false; + } + s->state = JS_ASYNC_GENERATOR_STATE_EXECUTING; + resume_exec: + func_ret = async_func_resume(ctx, &s->func_state); + if (JS_IsException(func_ret)) { + value = JS_GetException(ctx); + js_async_generator_complete(ctx, s); + js_async_generator_reject(ctx, s, value); + JS_FreeValue(ctx, value); + } else if (JS_VALUE_GET_TAG(func_ret) == JS_TAG_INT) { + int func_ret_code, ret; + value = s->func_state.frame.cur_sp[-1]; + s->func_state.frame.cur_sp[-1] = JS_UNDEFINED; + func_ret_code = JS_VALUE_GET_INT(func_ret); + switch(func_ret_code) { + case FUNC_RET_YIELD: + case FUNC_RET_YIELD_STAR: + if (func_ret_code == FUNC_RET_YIELD_STAR) + s->state = JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD_STAR; + else + s->state = JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD; + js_async_generator_resolve(ctx, s, value, false); + JS_FreeValue(ctx, value); + break; + case FUNC_RET_AWAIT: + ret = js_async_generator_await(ctx, s, value); + JS_FreeValue(ctx, value); + if (ret < 0) { + /* exception: throw it */ + s->func_state.throw_flag = true; + goto resume_exec; + } + goto done; + default: + abort(); + } + } else { + assert(JS_IsUndefined(func_ret)); + /* end of function */ + value = s->func_state.frame.cur_sp[-1]; + s->func_state.frame.cur_sp[-1] = JS_UNDEFINED; + js_async_generator_complete(ctx, s); + js_async_generator_resolve(ctx, s, value, true); + JS_FreeValue(ctx, value); + } + break; + default: + abort(); + } + } + done: ; +} + +static JSValue js_async_generator_resolve_function(JSContext *ctx, + JSValueConst this_obj, + int argc, JSValueConst *argv, + int magic, JSValueConst *func_data) +{ + bool is_reject = magic & 1; + JSAsyncGeneratorData *s = JS_GetOpaque(func_data[0], JS_CLASS_ASYNC_GENERATOR); + JSValueConst arg = argv[0]; + + /* XXX: what if s == NULL */ + + if (magic >= 2) { + /* resume next case in AWAITING_RETURN state */ + assert(s->state == JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN || + s->state == JS_ASYNC_GENERATOR_STATE_COMPLETED); + s->state = JS_ASYNC_GENERATOR_STATE_COMPLETED; + if (is_reject) { + js_async_generator_reject(ctx, s, arg); + } else { + js_async_generator_resolve(ctx, s, arg, true); + } + } else if (s->state == JS_ASYNC_GENERATOR_STATE_EXECUTING) { + /* restart function execution after await() */ + s->func_state.throw_flag = is_reject; + if (is_reject) { + JS_Throw(ctx, js_dup(arg)); + } else { + /* return value of await */ + s->func_state.frame.cur_sp[-1] = js_dup(arg); + } + js_async_generator_resume_next(ctx, s); + } + return JS_UNDEFINED; +} + +/* magic = GEN_MAGIC_x */ +static JSValue js_async_generator_next(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, + int magic) +{ + JSAsyncGeneratorData *s = JS_GetOpaque(this_val, JS_CLASS_ASYNC_GENERATOR); + JSValue promise, resolving_funcs[2]; + JSAsyncGeneratorRequest *req; + + promise = JS_NewPromiseCapability(ctx, resolving_funcs); + if (JS_IsException(promise)) + return JS_EXCEPTION; + if (!s) { + JSValue err, res2; + JS_ThrowTypeError(ctx, "not an AsyncGenerator object"); + err = JS_GetException(ctx); + res2 = JS_Call(ctx, resolving_funcs[1], JS_UNDEFINED, + 1, vc(&err)); + JS_FreeValue(ctx, err); + JS_FreeValue(ctx, res2); + JS_FreeValue(ctx, resolving_funcs[0]); + JS_FreeValue(ctx, resolving_funcs[1]); + return promise; + } + req = js_mallocz(ctx, sizeof(*req)); + if (!req) + goto fail; + req->completion_type = magic; + req->result = js_dup(argv[0]); + req->promise = js_dup(promise); + req->resolving_funcs[0] = resolving_funcs[0]; + req->resolving_funcs[1] = resolving_funcs[1]; + list_add_tail(&req->link, &s->queue); + if (s->state != JS_ASYNC_GENERATOR_STATE_EXECUTING) { + js_async_generator_resume_next(ctx, s); + } + return promise; + fail: + JS_FreeValue(ctx, resolving_funcs[0]); + JS_FreeValue(ctx, resolving_funcs[1]); + JS_FreeValue(ctx, promise); + return JS_EXCEPTION; +} + +static JSValue js_async_generator_function_call(JSContext *ctx, + JSValueConst func_obj, + JSValueConst this_obj, + int argc, JSValueConst *argv, + int flags) +{ + JSValue obj, func_ret; + JSAsyncGeneratorData *s; + + s = js_mallocz(ctx, sizeof(*s)); + if (!s) + return JS_EXCEPTION; + s->state = JS_ASYNC_GENERATOR_STATE_SUSPENDED_START; + init_list_head(&s->queue); + if (async_func_init(ctx, &s->func_state, func_obj, this_obj, argc, argv)) { + s->state = JS_ASYNC_GENERATOR_STATE_COMPLETED; + goto fail; + } + + /* execute the function up to 'OP_initial_yield' (no yield nor + await are possible) */ + func_ret = async_func_resume(ctx, &s->func_state); + if (JS_IsException(func_ret)) + goto fail; + JS_FreeValue(ctx, func_ret); + + obj = js_create_from_ctor(ctx, func_obj, JS_CLASS_ASYNC_GENERATOR); + if (JS_IsException(obj)) + goto fail; + s->generator = JS_VALUE_GET_OBJ(obj); + /* root captured locals against the async generator object (the initial + resume above only reaches OP_initial_yield, before any user code) */ + s->func_state.frame.cur_gc_obj = &s->generator->header; + JS_SetOpaqueInternal(obj, s); + return obj; + fail: + js_async_generator_free(ctx->rt, s); + return JS_EXCEPTION; +} + +/* JS parser */ + +enum { + TOK_NUMBER = -128, + TOK_STRING, + TOK_TEMPLATE, + TOK_IDENT, + TOK_REGEXP, + /* warning: order matters (see js_parse_assign_expr) */ + TOK_MUL_ASSIGN, + TOK_DIV_ASSIGN, + TOK_MOD_ASSIGN, + TOK_PLUS_ASSIGN, + TOK_MINUS_ASSIGN, + TOK_SHL_ASSIGN, + TOK_SAR_ASSIGN, + TOK_SHR_ASSIGN, + TOK_AND_ASSIGN, + TOK_XOR_ASSIGN, + TOK_OR_ASSIGN, + TOK_POW_ASSIGN, + TOK_LAND_ASSIGN, + TOK_LOR_ASSIGN, + TOK_DOUBLE_QUESTION_MARK_ASSIGN, + TOK_DEC, + TOK_INC, + TOK_SHL, + TOK_SAR, + TOK_SHR, + TOK_LT, + TOK_LTE, + TOK_GT, + TOK_GTE, + TOK_EQ, + TOK_STRICT_EQ, + TOK_NEQ, + TOK_STRICT_NEQ, + TOK_LAND, + TOK_LOR, + TOK_POW, + TOK_ARROW, + TOK_ELLIPSIS, + TOK_DOUBLE_QUESTION_MARK, + TOK_QUESTION_MARK_DOT, + TOK_ERROR, + TOK_PRIVATE_NAME, + TOK_EOF, + /* keywords: WARNING: same order as atoms */ + TOK_NULL, /* must be first */ + TOK_FALSE, + TOK_TRUE, + TOK_IF, + TOK_ELSE, + TOK_RETURN, + TOK_VAR, + TOK_THIS, + TOK_DELETE, + TOK_VOID, + TOK_TYPEOF, + TOK_NEW, + TOK_IN, + TOK_INSTANCEOF, + TOK_DO, + TOK_WHILE, + TOK_FOR, + TOK_BREAK, + TOK_CONTINUE, + TOK_SWITCH, + TOK_CASE, + TOK_DEFAULT, + TOK_THROW, + TOK_TRY, + TOK_CATCH, + TOK_FINALLY, + TOK_FUNCTION, + TOK_DEBUGGER, + TOK_WITH, + /* FutureReservedWord */ + TOK_CLASS, + TOK_CONST, + TOK_ENUM, + TOK_EXPORT, + TOK_EXTENDS, + TOK_IMPORT, + TOK_SUPER, + TOK_USING, + /* FutureReservedWords when parsing strict mode code */ + TOK_IMPLEMENTS, + TOK_INTERFACE, + TOK_LET, + TOK_PACKAGE, + TOK_PRIVATE, + TOK_PROTECTED, + TOK_PUBLIC, + TOK_STATIC, + TOK_YIELD, + TOK_AWAIT, /* must be last */ + TOK_OF, /* only used for js_parse_skip_parens_token() */ +}; + +#define TOK_FIRST_KEYWORD TOK_NULL +#define TOK_LAST_KEYWORD TOK_AWAIT + +/* unicode code points */ +#define CP_NBSP 0x00a0 +#define CP_BOM 0xfeff + +#define CP_LS 0x2028 +#define CP_PS 0x2029 + +typedef struct BlockEnv { + struct BlockEnv *prev; + JSAtom label_name; /* JS_ATOM_NULL if none */ + int label_break; /* -1 if none */ + int label_cont; /* -1 if none */ + int drop_count; /* number of stack elements to drop */ + int label_finally; /* -1 if none */ + int scope_level; + uint8_t has_iterator : 1; + uint8_t is_async_iterator : 1; + uint8_t is_regular_stmt : 1; // i.e. not a loop statement + uint8_t has_using : 1; /* scope has using declarations needing disposal */ + int using_scope_level; /* scope level for OP_dispose_scope (-1 if none) */ +} BlockEnv; + +typedef struct JSGlobalVar { + int cpool_idx; /* if >= 0, index in the constant pool for hoisted + function defintion*/ + uint8_t force_init : 1; /* force initialization to undefined */ + uint8_t is_lexical : 1; /* global let/const definition */ + uint8_t is_const : 1; /* const definition */ + int scope_level; /* scope of definition */ + JSAtom var_name; /* variable name */ +} JSGlobalVar; + +typedef struct RelocEntry { + struct RelocEntry *next; + uint32_t addr; /* address to patch */ + int size; /* address size: 1, 2 or 4 bytes */ +} RelocEntry; + +typedef struct JumpSlot { + int op; + int size; + int pos; + int label; +} JumpSlot; + +typedef struct LabelSlot { + int ref_count; + int pos; /* phase 1 address, -1 means not resolved yet */ + int pos2; /* phase 2 address, -1 means not resolved yet */ + int addr; /* phase 3 address, -1 means not resolved yet */ + RelocEntry *first_reloc; +} LabelSlot; + +typedef struct SourceLocSlot { + uint32_t pc; + int line_num; + int col_num; +} SourceLocSlot; + +typedef enum JSParseFunctionEnum { + JS_PARSE_FUNC_STATEMENT, + JS_PARSE_FUNC_VAR, + JS_PARSE_FUNC_EXPR, + JS_PARSE_FUNC_ARROW, + JS_PARSE_FUNC_GETTER, + JS_PARSE_FUNC_SETTER, + JS_PARSE_FUNC_METHOD, + JS_PARSE_FUNC_CLASS_STATIC_INIT, + JS_PARSE_FUNC_CLASS_CONSTRUCTOR, + JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR, +} JSParseFunctionEnum; + +typedef enum JSParseExportEnum { + JS_PARSE_EXPORT_NONE, + JS_PARSE_EXPORT_NAMED, + JS_PARSE_EXPORT_DEFAULT, +} JSParseExportEnum; + +typedef struct JSFunctionDef { + JSContext *ctx; + struct JSFunctionDef *parent; + int parent_cpool_idx; /* index in the constant pool of the parent + or -1 if none */ + int parent_scope_level; /* scope level in parent at point of definition */ + struct list_head child_list; /* list of JSFunctionDef.link */ + struct list_head link; + + int eval_type; /* only valid if is_eval = true */ + + /* Pack all boolean flags together as 1-bit fields to reduce struct size + while avoiding padding and compiler deoptimization. */ + bool is_eval : 1; /* true if eval code */ + bool is_global_var : 1; /* true if variables are not defined locally: + eval global, eval module or non strict eval */ + bool is_func_expr : 1; /* true if function expression */ + bool has_home_object : 1; /* true if the home object is available */ + bool has_prototype : 1; /* true if a prototype field is necessary */ + bool has_simple_parameter_list : 1; + bool has_parameter_expressions : 1; /* if true, an argument scope is created */ + bool has_use_strict : 1; /* to reject directive in special cases */ + bool has_eval_call : 1; /* true if the function contains a call to eval() */ + bool has_arguments_binding : 1; /* true if the 'arguments' binding is + available in the function */ + bool has_this_binding : 1; /* true if the 'this' and new.target binding are + available in the function */ + bool new_target_allowed : 1; /* true if the 'new.target' does not + throw a syntax error */ + bool super_call_allowed : 1; /* true if super() is allowed */ + bool super_allowed : 1; /* true if super. or super[] is allowed */ + bool arguments_allowed : 1; /* true if the 'arguments' identifier is allowed */ + bool is_derived_class_constructor : 1; + bool in_function_body : 1; + bool backtrace_barrier : 1; + bool need_home_object : 1; + bool use_short_opcodes : 1; /* true if short opcodes are used in byte_code */ + bool has_await : 1; /* true if await is used (used in module eval) */ + + JSFunctionKindEnum func_kind : 8; + JSParseFunctionEnum func_type : 7; + uint8_t is_strict_mode : 1; + JSAtom func_name; /* JS_ATOM_NULL if no name */ + + JSVarDef *vars; + uint32_t *vars_htab; // indexes into vars[] + int var_size; /* allocated size for vars[] */ + int var_count; + JSVarDef *args; + int arg_size; /* allocated size for args[] */ + int arg_count; /* number of arguments */ + int defined_arg_count; + int var_ref_count; /* number of local/arg variable references */ + int var_object_idx; /* -1 if none */ + int arg_var_object_idx; /* -1 if none (var object for the argument scope) */ + int arguments_var_idx; /* -1 if none */ + int arguments_arg_idx; /* argument variable definition in argument scope, + -1 if none */ + int func_var_idx; /* variable containing the current function (-1 + if none, only used if is_func_expr is true) */ + int eval_ret_idx; /* variable containing the return value of the eval, -1 if none */ + int this_var_idx; /* variable containg the 'this' value, -1 if none */ + int new_target_var_idx; /* variable containg the 'new.target' value, -1 if none */ + int this_active_func_var_idx; /* variable containg the 'this.active_func' value, -1 if none */ + int home_object_var_idx; + + int scope_level; /* index into fd->scopes if the current lexical scope */ + int scope_first; /* index into vd->vars of first lexically scoped variable */ + int scope_size; /* allocated size of fd->scopes array */ + int scope_count; /* number of entries used in the fd->scopes array */ + JSVarScope *scopes; + JSVarScope def_scope_array[4]; + int body_scope; /* scope of the body of the function or eval */ + + int global_var_count; + int global_var_size; + JSGlobalVar *global_vars; + + DynBuf byte_code; + int last_opcode_pos; /* -1 if no last opcode */ + + LabelSlot *label_slots; + int label_size; /* allocated size for label_slots[] */ + int label_count; + BlockEnv *top_break; /* break/continue label stack */ + + /* constant pool (strings, functions, numbers) */ + JSValue *cpool; + int cpool_count; + int cpool_size; + + /* list of variables in the closure */ + int closure_var_count; + int closure_var_size; + JSClosureVar *closure_var; + + JumpSlot *jump_slots; + int jump_size; + int jump_count; + + SourceLocSlot *source_loc_slots; + int source_loc_size; + int source_loc_count; + int line_number_last; + int line_number_last_pc; + int col_number_last; + + /* pc2line table */ + JSAtom filename; + int line_num; + int col_num; + DynBuf pc2line; + + char *source; /* raw source, utf-8 encoded */ + int source_len; + + JSModuleDef *module; /* != NULL when parsing a module */ +} JSFunctionDef; + +typedef struct JSToken { + int val; + int line_num; /* line number of token start */ + int col_num; /* column number of token start */ + const uint8_t *ptr; + const uint8_t *line_start; /* first character of the line of token start */ + union { + struct { + JSValue str; + int sep; + } str; + struct { + JSValue val; + } num; + struct { + JSAtom atom; + bool has_escape; + bool is_reserved; + } ident; + struct { + JSValue body; + JSValue flags; + } regexp; + } u; +} JSToken; + +typedef struct JSParseState { + JSContext *ctx; + int last_line_num; /* line number of last token */ + int last_col_num; /* column number of last token */ + int line_num; /* line number of current offset */ + int col_num; /* column number of current offset */ + const char *filename; + JSToken token; + bool got_lf; /* true if got line feed before the current token */ + const uint8_t *last_ptr; + const uint8_t *buf_start; + const uint8_t *buf_ptr; + const uint8_t *buf_end; + const uint8_t *line_start; /* first character of the current line */ + const uint8_t *eol; // most recently seen end-of-line character + const uint8_t *mark; // first token character, invariant: eol < mark + + /* current function code */ + JSFunctionDef *cur_func; + bool is_module; /* parsing a module */ + bool allow_html_comments; +} JSParseState; + +typedef struct JSOpCode { +#ifdef ENABLE_DUMPS // JS_DUMP_BYTECODE_* + const char *name; +#endif + uint8_t size; /* in bytes */ + /* the opcodes remove n_pop items from the top of the stack, then + pushes n_push items */ + uint8_t n_pop; + uint8_t n_push; + uint8_t fmt; +} JSOpCode; + +static const JSOpCode opcode_info[OP_COUNT + (OP_TEMP_END - OP_TEMP_START)] = { +#define FMT(f) +#ifdef ENABLE_DUMPS // JS_DUMP_BYTECODE_* +#define DEF(id, size, n_pop, n_push, f) { #id, size, n_pop, n_push, OP_FMT_ ## f }, +#else +#define DEF(id, size, n_pop, n_push, f) { size, n_pop, n_push, OP_FMT_ ## f }, +#endif +#include "quickjs-opcode.h" +#undef DEF +#undef FMT +}; + +/* After the final compilation pass, short opcodes are used. Their + opcodes overlap with the temporary opcodes which cannot appear in + the final bytecode. Their description is after the temporary + opcodes in opcode_info[]. */ +#define short_opcode_info(op) \ + opcode_info[(op) >= OP_TEMP_START ? \ + (op) + (OP_TEMP_END - OP_TEMP_START) : (op)] + +static void json_free_token(JSParseState *s, JSToken *token) { + // Only free actual allocated values + switch(token->val) { + case TOK_NUMBER: + JS_FreeValue(s->ctx, token->u.num.val); + break; + case TOK_STRING: + JS_FreeValue(s->ctx, token->u.str.str); + break; + case TOK_IDENT: + JS_FreeAtom(s->ctx, token->u.ident.atom); + break; + } +} + +static void free_token(JSParseState *s, JSToken *token) +{ + switch(token->val) { + case TOK_NUMBER: + JS_FreeValue(s->ctx, token->u.num.val); + break; + case TOK_STRING: + case TOK_TEMPLATE: + JS_FreeValue(s->ctx, token->u.str.str); + break; + case TOK_REGEXP: + JS_FreeValue(s->ctx, token->u.regexp.body); + JS_FreeValue(s->ctx, token->u.regexp.flags); + break; + case TOK_IDENT: + case TOK_PRIVATE_NAME: + JS_FreeAtom(s->ctx, token->u.ident.atom); + break; + default: + if (token->val >= TOK_FIRST_KEYWORD && + token->val <= TOK_LAST_KEYWORD) { + JS_FreeAtom(s->ctx, token->u.ident.atom); + } + break; + } +} + +static void __attribute((unused)) dump_token(JSParseState *s, + const JSToken *token) +{ + printf("%d:%d ", token->line_num, token->col_num); + switch(token->val) { + case TOK_NUMBER: + { + double d; + JS_ToFloat64(s->ctx, &d, token->u.num.val); /* no exception possible */ + printf("number: %.14g\n", d); + } + break; + case TOK_IDENT: + dump_atom: + { + char buf[ATOM_GET_STR_BUF_SIZE]; + printf("ident: '%s'\n", + JS_AtomGetStr(s->ctx, buf, sizeof(buf), token->u.ident.atom)); + } + break; + case TOK_STRING: + { + const char *str; + /* XXX: quote the string */ + str = JS_ToCString(s->ctx, token->u.str.str); + printf("string: '%s'\n", str); + JS_FreeCString(s->ctx, str); + } + break; + case TOK_TEMPLATE: + { + const char *str; + str = JS_ToCString(s->ctx, token->u.str.str); + printf("template: `%s`\n", str); + JS_FreeCString(s->ctx, str); + } + break; + case TOK_REGEXP: + { + const char *str, *str2; + str = JS_ToCString(s->ctx, token->u.regexp.body); + str2 = JS_ToCString(s->ctx, token->u.regexp.flags); + printf("regexp: '%s' '%s'\n", str, str2); + JS_FreeCString(s->ctx, str); + JS_FreeCString(s->ctx, str2); + } + break; + case TOK_EOF: + printf("eof\n"); + break; + default: + if (s->token.val >= TOK_NULL && s->token.val <= TOK_LAST_KEYWORD) { + goto dump_atom; + } else if (s->token.val >= 256) { + printf("token: %d\n", token->val); + } else { + printf("token: '%c'\n", token->val); + } + break; + } +} + +int JS_PRINTF_FORMAT_ATTR(2, 3) js_parse_error(JSParseState *s, JS_PRINTF_FORMAT const char *fmt, ...) +{ + JSContext *ctx = s->ctx; + va_list ap; + int backtrace_flags; + + va_start(ap, fmt); + JS_ThrowError2(ctx, JS_SYNTAX_ERROR, false, fmt, ap); + va_end(ap); + backtrace_flags = 0; + if (s->cur_func && s->cur_func->backtrace_barrier) + backtrace_flags = JS_BACKTRACE_FLAG_SINGLE_LEVEL; + /* s->col_num is not advanced during token scanning, so derive the column + as the 1-based offset of the token from the start of its line. */ + int err_col_num = s->token.col_num; + if (s->token.ptr && s->token.ptr >= s->token.line_start) + err_col_num = (int)(s->token.ptr - s->token.line_start) + 1; + build_backtrace(ctx, ctx->rt->current_exception, JS_UNDEFINED, s->filename, + s->token.line_num, err_col_num, backtrace_flags); + return -1; +} + +#ifndef QJS_DISABLE_PARSER + +static __exception int next_token(JSParseState *s); + +static int js_parse_expect(JSParseState *s, int tok) +{ + char buf[ATOM_GET_STR_BUF_SIZE]; + + if (s->token.val == tok) + return next_token(s); + + switch(s->token.val) { + case TOK_EOF: + return js_parse_error(s, "Unexpected end of input"); + case TOK_NUMBER: + return js_parse_error(s, "Unexpected number"); + case TOK_STRING: + return js_parse_error(s, "Unexpected string"); + case TOK_TEMPLATE: + return js_parse_error(s, "Unexpected string template"); + case TOK_REGEXP: + return js_parse_error(s, "Unexpected regexp"); + case TOK_IDENT: + return js_parse_error(s, "Unexpected identifier '%s'", + JS_AtomGetStr(s->ctx, buf, sizeof(buf), + s->token.u.ident.atom)); + case TOK_ERROR: + return js_parse_error(s, "Invalid or unexpected token"); + default: + return js_parse_error(s, "Unexpected token '%.*s'", + (int)(s->buf_ptr - s->token.ptr), + (const char *)s->token.ptr); + } +} + +static int js_parse_expect_semi(JSParseState *s) +{ + if (s->token.val != ';') { + /* automatic insertion of ';' */ + if (s->token.val == TOK_EOF || s->token.val == '}' || s->got_lf) { + return 0; + } + return js_parse_error(s, "expecting '%c'", ';'); + } + return next_token(s); +} + +static int js_parse_error_reserved_identifier(JSParseState *s) +{ + char buf1[ATOM_GET_STR_BUF_SIZE]; + return js_parse_error(s, "'%s' is a reserved identifier", + JS_AtomGetStr(s->ctx, buf1, sizeof(buf1), + s->token.u.ident.atom)); +} + +static __exception int js_parse_template_part(JSParseState *s, + const uint8_t *p) +{ + const uint8_t *p_next; + uint32_t c; + StringBuffer b_s, *b = &b_s; + JSValue str; + + /* p points to the first byte of the template part */ + if (string_buffer_init(s->ctx, b, 32)) + goto fail; + for(;;) { + if (p >= s->buf_end) + goto unexpected_eof; + c = *p++; + if (c == '`') { + /* template end part */ + break; + } + if (c == '$' && *p == '{') { + /* template start or middle part */ + p++; + break; + } + if (c == '\\') { + if (string_buffer_putc8(b, c)) + goto fail; + if (p >= s->buf_end) + goto unexpected_eof; + c = *p++; + } + /* newline sequences are normalized as single '\n' bytes */ + if (c == '\r') { + if (*p == '\n') + p++; + c = '\n'; + } + if (c == '\n') { + s->line_num++; + s->line_start = p; + s->eol = &p[-1]; + s->mark = p; + } else if (c >= 0x80) { + c = utf8_decode(p - 1, &p_next); + if (p_next == p) { + js_parse_error(s, "invalid UTF-8 sequence"); + goto fail; + } + p = p_next; + } + if (string_buffer_putc(b, c)) + goto fail; + } + str = string_buffer_end(b); + if (JS_IsException(str)) + return -1; + s->token.val = TOK_TEMPLATE; + s->token.u.str.sep = c; + s->token.u.str.str = str; + s->buf_ptr = p; + return 0; + + unexpected_eof: + js_parse_error(s, "unexpected end of string"); + fail: + string_buffer_free(b); + return -1; +} + +static __exception int js_parse_string(JSParseState *s, int sep, + bool do_throw, const uint8_t *p, + JSToken *token, const uint8_t **pp) +{ + const uint8_t *p_next; + int ret; + uint32_t c; + StringBuffer b_s, *b = &b_s; + JSValue str; + + /* string */ + if (string_buffer_init(s->ctx, b, 32)) + goto fail; + for(;;) { + if (p >= s->buf_end) + goto invalid_char; + c = *p; + if (c < 0x20) { + if (sep == '`') { + if (c == '\r') { + if (p[1] == '\n') + p++; + c = '\n'; + } + /* do not update s->line_num */ + } else if (c == '\n' || c == '\r') + goto invalid_char; + } + p++; + if (c == sep) + break; + if (c == '$' && *p == '{' && sep == '`') { + /* template start or middle part */ + p++; + break; + } + if (c == '\\') { + c = *p; + switch(c) { + case '\0': + if (p >= s->buf_end) { + if (sep != '`') + goto invalid_char; + if (do_throw) + js_parse_error(s, "Unexpected end of input"); + goto fail; + } + p++; + break; + case '\'': + case '\"': + case '\\': + p++; + break; + case '\r': /* accept DOS and MAC newline sequences */ + if (p[1] == '\n') { + p++; + } + /* fall thru */ + case '\n': + /* ignore escaped newline sequence */ + p++; + if (sep != '`') { + s->line_num++; + s->line_start = p; + s->eol = &p[-1]; + s->mark = p; + } + continue; + default: + if (c == '0' && !(p[1] >= '0' && p[1] <= '9')) { + /* accept isolated \0 */ + p++; + c = '\0'; + } else + if ((c >= '0' && c <= '9') + && (s->cur_func->is_strict_mode || sep == '`')) { + if (do_throw) { + js_parse_error(s, "%s are not allowed in %s", + (c >= '8') ? "\\8 and \\9" : "Octal escape sequences", + (sep == '`') ? "template strings" : "strict mode"); + } + goto fail; + } else if (c >= 0x80) { + c = utf8_decode(p, &p_next); + if (p_next == p + 1) { + goto invalid_utf8; + } + p = p_next; + /* LS or PS are skipped */ + if (c == CP_LS || c == CP_PS) + continue; + } else { + ret = lre_parse_escape(&p, true); + if (ret == -1) { + if (do_throw) { + js_parse_error(s, "Invalid %s escape sequence", + c == 'u' ? "Unicode" : "hexadecimal"); + } + goto fail; + } else if (ret < 0) { + /* ignore the '\' (could output a warning) */ + p++; + } else { + c = ret; + } + } + break; + } + } else if (c >= 0x80) { + c = utf8_decode(p - 1, &p_next); + if (p_next == p) + goto invalid_utf8; + p = p_next; + } + if (string_buffer_putc(b, c)) + goto fail; + } + str = string_buffer_end(b); + if (JS_IsException(str)) + return -1; + token->val = TOK_STRING; + token->u.str.sep = c; + token->u.str.str = str; + *pp = p; + return 0; + + invalid_utf8: + if (do_throw) + js_parse_error(s, "invalid UTF-8 sequence"); + goto fail; + invalid_char: + if (do_throw) + js_parse_error(s, "unexpected end of string"); + fail: + string_buffer_free(b); + return -1; +} + +static inline bool token_is_pseudo_keyword(JSParseState *s, JSAtom atom) { + return s->token.val == TOK_IDENT && s->token.u.ident.atom == atom && + !s->token.u.ident.has_escape; +} + +static __exception int js_parse_regexp(JSParseState *s) +{ + const uint8_t *p, *p_next; + bool in_class; + StringBuffer b_s, *b = &b_s; + StringBuffer b2_s, *b2 = &b2_s; + uint32_t c; + JSValue body_str, flags_str; + + p = s->buf_ptr; + p++; + in_class = false; + if (string_buffer_init(s->ctx, b, 32)) + return -1; + if (string_buffer_init(s->ctx, b2, 1)) + goto fail; + for(;;) { + if (p >= s->buf_end) { + eof_error: + js_parse_error(s, "unexpected end of regexp"); + goto fail; + } + c = *p++; + if (c == '\n' || c == '\r') { + goto eol_error; + } else if (c == '/') { + if (!in_class) + break; + } else if (c == '[') { + in_class = true; + } else if (c == ']') { + /* XXX: incorrect as the first character in a class */ + in_class = false; + } else if (c == '\\') { + if (string_buffer_putc8(b, c)) + goto fail; + c = *p++; + if (c == '\n' || c == '\r') + goto eol_error; + else if (c == '\0' && p >= s->buf_end) + goto eof_error; + else if (c >= 0x80) { + c = utf8_decode(p - 1, &p_next); + if (p_next == p) { + goto invalid_utf8; + } + p = p_next; + if (c == CP_LS || c == CP_PS) + goto eol_error; + } + } else if (c >= 0x80) { + c = utf8_decode(p - 1, &p_next); + if (p_next == p) { + invalid_utf8: + js_parse_error(s, "invalid UTF-8 sequence"); + goto fail; + } + p = p_next; + /* LS or PS are considered as line terminator */ + if (c == CP_LS || c == CP_PS) { + eol_error: + js_parse_error(s, "unexpected line terminator in regexp"); + goto fail; + } + } + if (string_buffer_putc(b, c)) + goto fail; + } + + /* flags */ + for(;;) { + c = utf8_decode(p, &p_next); + /* no need to test for invalid UTF-8, 0xFFFD is not ident_next */ + if (!lre_js_is_ident_next(c)) + break; + if (string_buffer_putc(b2, c)) + goto fail; + p = p_next; + } + + body_str = string_buffer_end(b); + flags_str = string_buffer_end(b2); + if (JS_IsException(body_str) || + JS_IsException(flags_str)) { + JS_FreeValue(s->ctx, body_str); + JS_FreeValue(s->ctx, flags_str); + return -1; + } + s->token.val = TOK_REGEXP; + s->token.u.regexp.body = body_str; + s->token.u.regexp.flags = flags_str; + s->buf_ptr = p; + return 0; + fail: + string_buffer_free(b); + string_buffer_free(b2); + return -1; +} + +#endif // QJS_DISABLE_PARSER + +static __exception int ident_realloc(JSContext *ctx, char **pbuf, size_t *psize, + char *static_buf) +{ + char *buf, *new_buf; + size_t size, new_size; + + buf = *pbuf; + size = *psize; + if (size >= (SIZE_MAX / 3) * 2) + new_size = SIZE_MAX; + else + new_size = size + (size >> 1); + if (buf == static_buf) { + new_buf = js_malloc(ctx, new_size); + if (!new_buf) + return -1; + memcpy(new_buf, buf, size); + } else { + new_buf = js_realloc(ctx, buf, new_size); + if (!new_buf) + return -1; + } + *pbuf = new_buf; + *psize = new_size; + return 0; +} + +#ifndef QJS_DISABLE_PARSER + +/* convert a TOK_IDENT to a keyword when needed */ +static void update_token_ident(JSParseState *s) +{ + /* `using` is contextually reserved, not a true keyword. Leave it as + TOK_IDENT so it can be used as a regular identifier in expressions. + Using declarations are detected explicitly at statement and + for-loop head parsing via token_is_pseudo_keyword. */ + if (s->token.u.ident.atom == JS_ATOM_using) + return; + if (s->token.u.ident.atom <= JS_ATOM_LAST_KEYWORD || + (s->token.u.ident.atom <= JS_ATOM_LAST_STRICT_KEYWORD && + s->cur_func->is_strict_mode) || + (s->token.u.ident.atom == JS_ATOM_yield && + ((s->cur_func->func_kind & JS_FUNC_GENERATOR) || + (s->cur_func->func_type == JS_PARSE_FUNC_ARROW && + !s->cur_func->in_function_body && s->cur_func->parent && + (s->cur_func->parent->func_kind & JS_FUNC_GENERATOR)))) || + (s->token.u.ident.atom == JS_ATOM_await && + (s->is_module || + (s->cur_func->func_kind & JS_FUNC_ASYNC) || + s->cur_func->func_type == JS_PARSE_FUNC_CLASS_STATIC_INIT || + (s->cur_func->func_type == JS_PARSE_FUNC_ARROW && + !s->cur_func->in_function_body && s->cur_func->parent && + ((s->cur_func->parent->func_kind & JS_FUNC_ASYNC) || + s->cur_func->parent->func_type == JS_PARSE_FUNC_CLASS_STATIC_INIT))))) { + if (s->token.u.ident.has_escape) { + s->token.u.ident.is_reserved = true; + s->token.val = TOK_IDENT; + } else { + /* The keywords atoms are pre allocated */ + s->token.val = s->token.u.ident.atom - 1 + TOK_FIRST_KEYWORD; + } + } +} + +/* if the current token is an identifier or keyword, reparse it + according to the current function type */ +static void reparse_ident_token(JSParseState *s) +{ + if (s->token.val == TOK_IDENT || + (s->token.val >= TOK_FIRST_KEYWORD && + s->token.val <= TOK_LAST_KEYWORD)) { + s->token.val = TOK_IDENT; + s->token.u.ident.is_reserved = false; + update_token_ident(s); + } +} + +/* 'c' is the first character. Return JS_ATOM_NULL in case of error */ +static JSAtom parse_ident(JSParseState *s, const uint8_t **pp, + bool *pident_has_escape, int c, bool is_private) +{ + const uint8_t *p, *p_next; + char ident_buf[128], *buf; + size_t ident_size, ident_pos; + JSAtom atom = JS_ATOM_NULL; + + p = *pp; + buf = ident_buf; + ident_size = sizeof(ident_buf); + ident_pos = 0; + if (is_private) + buf[ident_pos++] = '#'; + for(;;) { + if (c < 0x80) { + buf[ident_pos++] = c; + } else { + ident_pos += utf8_encode((uint8_t*)buf + ident_pos, c); + } + c = *p; + p_next = p + 1; + if (c == '\\' && *p_next == 'u') { + c = lre_parse_escape(&p_next, true); + *pident_has_escape = true; + } else if (c >= 0x80) { + c = utf8_decode(p, &p_next); + /* no need to test for invalid UTF-8, 0xFFFD is not ident_next */ + } + if (!lre_js_is_ident_next(c)) + break; + p = p_next; + if (unlikely(ident_pos >= ident_size - UTF8_CHAR_LEN_MAX)) { + if (ident_realloc(s->ctx, &buf, &ident_size, ident_buf)) + goto done; + } + } + /* buf is pure ASCII or UTF-8 encoded */ + atom = JS_NewAtomLen(s->ctx, buf, ident_pos); + done: + if (unlikely(buf != ident_buf)) + js_free(s->ctx, buf); + *pp = p; + return atom; +} + + +static __exception int next_token(JSParseState *s) +{ + const uint8_t *p, *p_next; + int c; + bool ident_has_escape; + JSAtom atom; + + if (js_check_stack_overflow(s->ctx->rt, 1000)) { + JS_ThrowStackOverflow(s->ctx); + return -1; + } + + free_token(s, &s->token); + + p = s->last_ptr = s->buf_ptr; + s->got_lf = false; + s->last_line_num = s->token.line_num; + s->last_col_num = s->token.col_num; + redo: + s->token.line_num = s->line_num; + s->token.col_num = s->col_num; + s->token.ptr = p; + s->token.line_start = s->line_start; + c = *p; + switch(c) { + case 0: + if (p >= s->buf_end) { + s->token.val = TOK_EOF; + } else { + goto def_token; + } + break; + case '`': + if (js_parse_template_part(s, p + 1)) + goto fail; + p = s->buf_ptr; + break; + case '\'': + case '\"': + if (js_parse_string(s, c, true, p + 1, &s->token, &p)) + goto fail; + break; + case '\r': /* accept DOS and MAC newline sequences */ + if (p[1] == '\n') { + p++; + } + /* fall thru */ + case '\n': + p++; + line_terminator: + s->line_start = p; + s->eol = &p[-1]; + s->mark = p; + s->got_lf = true; + s->line_num++; + goto redo; + case '\f': + case '\v': + case ' ': + case '\t': + s->mark = ++p; + goto redo; + case '/': + if (p[1] == '*') { + /* comment */ + p += 2; + for(;;) { + if (*p == '\0' && p >= s->buf_end) { + js_parse_error(s, "unexpected end of comment"); + goto fail; + } + if (p[0] == '*' && p[1] == '/') { + p += 2; + break; + } + if (*p == '\n') { + s->line_num++; + s->got_lf = true; /* considered as LF for ASI */ + s->eol = p++; + s->line_start = p; + s->mark = p; + } else if (*p == '\r') { + s->got_lf = true; /* considered as LF for ASI */ + p++; + s->line_start = p; + } else if (*p >= 0x80) { + c = utf8_decode(p, &p); + /* ignore invalid UTF-8 in comments */ + if (c == CP_LS || c == CP_PS) { + s->got_lf = true; /* considered as LF for ASI */ + } + } else { + p++; + } + } + s->mark = p; + goto redo; + } else if (p[1] == '/') { + /* line comment */ + p += 2; + skip_line_comment: + for(;;) { + if (*p == '\0' && p >= s->buf_end) + break; + if (*p == '\r' || *p == '\n') + break; + if (*p >= 0x80) { + c = utf8_decode(p, &p); + /* ignore invalid UTF-8 in comments */ + /* LS or PS are considered as line terminator */ + if (c == CP_LS || c == CP_PS) { + break; + } + } else { + p++; + } + } + s->mark = p; + goto redo; + } else if (p[1] == '=') { + p += 2; + s->token.val = TOK_DIV_ASSIGN; + } else { + p++; + s->token.val = c; + } + break; + case '\\': + if (p[1] == 'u') { + const uint8_t *p1 = p + 1; + int c1 = lre_parse_escape(&p1, true); + if (c1 >= 0 && lre_js_is_ident_first(c1)) { + c = c1; + p = p1; + ident_has_escape = true; + goto has_ident; + } else { + /* XXX: syntax error? */ + } + } + goto def_token; + case 'a': case 'b': case 'c': case 'd': + case 'e': case 'f': case 'g': case 'h': + case 'i': case 'j': case 'k': case 'l': + case 'm': case 'n': case 'o': case 'p': + case 'q': case 'r': case 's': case 't': + case 'u': case 'v': case 'w': case 'x': + case 'y': case 'z': + case 'A': case 'B': case 'C': case 'D': + case 'E': case 'F': case 'G': case 'H': + case 'I': case 'J': case 'K': case 'L': + case 'M': case 'N': case 'O': case 'P': + case 'Q': case 'R': case 'S': case 'T': + case 'U': case 'V': case 'W': case 'X': + case 'Y': case 'Z': + case '_': + case '$': + /* identifier */ + s->mark = p; + p++; + ident_has_escape = false; + has_ident: + atom = parse_ident(s, &p, &ident_has_escape, c, false); + if (atom == JS_ATOM_NULL) + goto fail; + s->token.u.ident.atom = atom; + s->token.u.ident.has_escape = ident_has_escape; + s->token.u.ident.is_reserved = false; + s->token.val = TOK_IDENT; + update_token_ident(s); + break; + case '#': + /* private name */ + { + p++; + c = *p; + p_next = p + 1; + if (c == '\\' && *p_next == 'u') { + c = lre_parse_escape(&p_next, true); + } else if (c >= 0x80) { + c = utf8_decode(p, &p_next); + if (p_next == p + 1) + goto invalid_utf8; + } + if (!lre_js_is_ident_first(c)) { + js_parse_error(s, "invalid first character of private name"); + goto fail; + } + p = p_next; + ident_has_escape = false; /* not used */ + atom = parse_ident(s, &p, &ident_has_escape, c, true); + if (atom == JS_ATOM_NULL) + goto fail; + s->token.u.ident.atom = atom; + s->token.val = TOK_PRIVATE_NAME; + } + break; + case '.': + if (p[1] == '.' && p[2] == '.') { + p += 3; + s->token.val = TOK_ELLIPSIS; + break; + } + if (p[1] >= '0' && p[1] <= '9') { + goto parse_number; + } else { + goto def_token; + } + break; + case '0': + /* in strict mode, octal literals are not accepted */ + if (is_digit(p[1]) && (s->cur_func->is_strict_mode)) { + js_parse_error(s, "Octal literals are not allowed in strict mode"); + goto fail; + } + goto parse_number; + case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': + case '9': + /* number */ + parse_number: + { + JSValue ret; + const uint8_t *p1; + int flags; + flags = ATOD_ACCEPT_BIN_OCT | ATOD_ACCEPT_LEGACY_OCTAL | + ATOD_ACCEPT_UNDERSCORES | ATOD_ACCEPT_SUFFIX; + ret = js_atof(s->ctx, (const char *)p, (const char **)&p, 0, + flags); + if (JS_IsException(ret)) + goto fail; + /* reject `10instanceof Number` */ + if (JS_VALUE_IS_NAN(ret) || + lre_js_is_ident_next(utf8_decode(p, &p1))) { + JS_FreeValue(s->ctx, ret); + s->col_num = max_int(1, s->mark - s->eol); + js_parse_error(s, "invalid number literal"); + goto fail; + } + s->token.val = TOK_NUMBER; + s->token.u.num.val = ret; + } + break; + case '*': + if (p[1] == '=') { + p += 2; + s->token.val = TOK_MUL_ASSIGN; + } else if (p[1] == '*') { + if (p[2] == '=') { + p += 3; + s->token.val = TOK_POW_ASSIGN; + } else { + p += 2; + s->token.val = TOK_POW; + } + } else { + goto def_token; + } + break; + case '%': + if (p[1] == '=') { + p += 2; + s->token.val = TOK_MOD_ASSIGN; + } else { + goto def_token; + } + break; + case '+': + if (p[1] == '=') { + p += 2; + s->token.val = TOK_PLUS_ASSIGN; + } else if (p[1] == '+') { + p += 2; + s->token.val = TOK_INC; + } else { + goto def_token; + } + break; + case '-': + if (p[1] == '=') { + p += 2; + s->token.val = TOK_MINUS_ASSIGN; + } else if (p[1] == '-') { + if (s->allow_html_comments && p[2] == '>' && + (s->got_lf || s->last_ptr == s->buf_start)) { + /* Annex B: `-->` at beginning of line is an html comment end. + It extends to the end of the line. + */ + goto skip_line_comment; + } + p += 2; + s->token.val = TOK_DEC; + } else { + goto def_token; + } + break; + case '<': + if (p[1] == '=') { + p += 2; + s->token.val = TOK_LTE; + } else if (p[1] == '<') { + if (p[2] == '=') { + p += 3; + s->token.val = TOK_SHL_ASSIGN; + } else { + p += 2; + s->token.val = TOK_SHL; + } + } else if (s->allow_html_comments && + p[1] == '!' && p[2] == '-' && p[3] == '-') { + /* Annex B: handle `j!hd;M8V9BxvbLu4dOV-%4X`n!C*r{P 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 Exports, ov } } +/** + * Fills the guest's memory from the host's side, through the module's own allocator, to within + * `room` bytes of its soft limit, with the headroom closed, and returns the block that holds it. + * The largest block that fits is found by trying sizes; a try that's refused opens the headroom, + * and a byte allocated and freed far below the limit closes it again. + */ +function fillToSoftLimit(ex: Exports, room: number): number { + const fits = (n: number) => { + const p = ex.engine_alloc(n); + if (p) ex.engine_free(p); + else ex.engine_free(ex.engine_alloc(1)); + return p !== 0; + }; + let lo = 0, hi = MAX_MEMORY_BYTES; + while (hi - lo > 1) { + const mid = Math.floor((lo + hi) / 2); + if (fits(mid)) lo = mid; + else hi = mid; + } + const hold = ex.engine_alloc(Math.max(1, lo - room)); + assert.ok(hold, 'the block that fills memory fits'); + return hold; +} + test('a script\'s value is its last expression statement\'s, as JSON', async () => { const { e } = await engine(); assert.deepEqual(fulfilled(e.start(PRELUDE, '1 + 2; ({ a: [1, "two"], b: null })', 's.js')), { a: [1, 'two'], b: null }); @@ -198,12 +222,16 @@ test('a script\'s value is serialized once, however many calls settle after it r // Serializing runs the script's own code, here a toJSON that counts its runs, and every settle // after the script returned answers its ending again. const { e } = await engine(); - const s = e.start(PRELUDE, 'let n = 0; tool("a"); tool("b"); ({ toJSON() { n++; return { n }; } })', 's.js'); + const s = e.start(PRELUDE, 'let n = 0; tool("a"); tool("b"); tool("c"); ({ toJSON() { n++; return { n }; } })', 's.js'); assert.deepEqual(fulfilled(s), { n: 1 }); - // What a call answers is the caller's own: changing it doesn't change the next answer. + // What a call answers is the caller's own, read just now or kept: changing it doesn't change the + // next answer. (s as { json?: string }).json = 'changed by the caller'; - assert.deepEqual(fulfilled(e.settle(1, true, '1', UNFIT)), { n: 1 }); + const kept = e.settle(1, true, '1', UNFIT); + assert.deepEqual(fulfilled(kept), { n: 1 }); + (kept as { json?: string }).json = 'changed by the caller'; assert.deepEqual(fulfilled(e.settle(2, true, '2', UNFIT)), { n: 1 }); + assert.deepEqual(fulfilled(e.settle(3, true, '3', UNFIT)), { n: 1 }); e.dispose(); }); @@ -324,20 +352,26 @@ test('a slot that calls back into the engine ends the run with an EngineError', test('the script\'s code that runs while its ending is read, and the jobs it queues, can\'t reach the host', async () => { // A getter on what the script threw runs after its ending is fixed: the call it tries and its // output are refused, and so are a job's it queues, which runs before the read returns rather - // than at a later settle. + // than at a later settle. The script's continuation of its call in flight, which isn't part of + // the read, prints what the job met. const { e, rec } = await engine(); - const s = e.start(PRELUDE, `tool("in flight"); + const s = e.start(PRELUDE, `const queued = []; + const attempt = (into, what, fn) => { try { fn(); into.push(what + " went"); } catch { into.push(what + " refused"); } }; + tool("in flight").then(() => __output(1, queued.join(", "))); const tried = []; - const attempt = (what, fn) => { try { fn(); tried.push(what + " went"); } catch { tried.push(what + " refused"); } }; throw Object.defineProperty({}, "message", { get() { - attempt("call", () => __host("late", "{}")); - attempt("output", () => __output(1, "late")); - Promise.resolve().then(() => { __host("queued", "{}"); __output(1, "queued"); }); + attempt(tried, "call", () => __host("late", "{}")); + attempt(tried, "output", () => __output(1, "late")); + Promise.resolve().then(() => { + attempt(queued, "queued call", () => __host("queued", "{}")); + attempt(queued, "queued output", () => __output(1, "queued")); + }); return tried.join(", "); } })`, 's.js'); assert.deepEqual(s, { kind: 'rejected', name: '', message: 'call refused, output refused' }); e.settle(1, true, '1', UNFIT); - assert.deepEqual([rec.calls.map((c) => c.name), rec.output], [['in flight'], []]); + assert.deepEqual([rec.calls.map((c) => c.name), rec.output], + [['in flight'], [[1, 'queued call refused, queued output refused']]]); e.dispose(); }); @@ -358,12 +392,21 @@ test('what a thrown object holds is read as text, and a part that can\'t be read } }); -test('a slot that throws a value with no string form still ends the run with an EngineError', async () => { - const thrown = { toString() { throw new Error('no string form'); } }; - const { e } = await engine({ hostCall: () => { throw thrown; } }); - assert.throws(() => e.start(PRELUDE, '__host("a", "{}")', 's.js'), (err: unknown) => - err instanceof EngineError && err.cause === thrown && /a value with no string form/.test(err.message)); - e.dispose(); +test('a slot that throws a value with no string form, or an Error whose message has none, still ends the run with an EngineError', async () => { + const symbolic = Object.assign(new Error(), { message: Symbol('m') }); + for (const [thrown, text] of [ + [{ toString() { throw new Error('no string form'); } }, /a value with no string form/], + [symbolic, /Symbol\(m\)/], + ] as const) { + const { e } = await engine({ hostCall: () => { throw thrown; } }); + let failure: unknown; + assert.throws(() => e.start(PRELUDE, '__host("a", "{}")', 's.js'), (err: unknown) => { + failure = err; + return err instanceof EngineError && err.cause === thrown && text.test(err.message); + }); + assert.throws(() => e.settle(1, true, '1', UNFIT), (err: unknown) => err === failure); + e.dispose(); + } }); test('after a slot\'s failure, every later call throws that failure, and a dispose() refused from a slot can be made again', async () => { @@ -375,7 +418,8 @@ test('after a slot\'s failure, every later call throws that failure, and a dispo failure = err; return err instanceof EngineError && /dispose\(\) was called from inside one of the engine's slots/.test(err.message); }); - // Not "after dispose()", and not a bad id's own error, but the slot's failure. + // Not "after dispose()", "already called" or a bad id's own error, but the slot's failure. + assert.throws(() => e.start(PRELUDE, '1', 's.js'), (err: unknown) => err === failure); assert.throws(() => e.settle(1, true, '1', UNFIT), (err: unknown) => err === failure); assert.throws(() => e.settle(1.5, true, '1', UNFIT), (err: unknown) => err === failure); e.dispose(); @@ -397,8 +441,8 @@ test('an ending the module can\'t read out is answered as out of memory, and tha // happen on cue; the binding mustn't read it again, which would run the script's serializing // code again and could answer differently. for (const [script, message] of [ - ['tool("a"); tool("b"); ({ ok: true })', 'out of memory while serializing the script\'s return value; return less'], - ['tool("a"); tool("b"); throw new Error("x")', 'out of memory while reading what the script threw; throw something smaller, such as an Error with a shorter message'], + ['tool("a"); tool("b"); ({ ok: true })', 'out of memory while serializing the script\'s return value; return less, or let go of data the script no longer needs'], + ['tool("a"); tool("b"); throw new Error("x")', 'out of memory while reading what the script threw; throw something smaller, such as an Error with a shorter message, or let go of data the script no longer needs'], ]) { let reads = 0; const { e } = await engineAround((ex) => ({ engine_result: () => (++reads === 1 ? 0 : ex.engine_result()) })); @@ -413,21 +457,27 @@ test('an ending the module can\'t read out is answered as out of memory, and tha test('an ending longer than the binding can decode is answered with its length, not a crash', async () => { // The module's buffer here claims a text past V8's longest string; a real one needs a budget - // of gigabytes. - for (const [script, message] of [ - ['({ ok: true })', 'the script\'s return value is 600000000 bytes as JSON, more than the 536870888 the engine reads back; return less'], - ['throw new Error("x")', 'what the script threw is 600000000 bytes as JSON, more than the 536870888 the engine reads back; throw something smaller'], - ]) { + // of gigabytes. The buffer is freed all the same. + for (const [script, length, message] of [ + ['({ ok: true })', 600_000_000, 'the script\'s return value comes to 600000000 bytes as text, more than the 536870888 the engine reads back; return less'], + ['throw new Error("x")', 600_000_000, 'what the script threw comes to 600000000 bytes as text, more than the 536870888 the engine reads back; throw something smaller'], + // One byte past the bound is refused too. + ['({ ok: true })', 536_870_889, 'the script\'s return value comes to 536870889 bytes as text, more than the 536870888 the engine reads back; return less'], + ] as const) { + let ptr = 0; + const freed: number[] = []; const { e } = await engineAround((ex, imports) => ({ engine_result: () => { - const ptr = ex.engine_alloc(8); + ptr = ex.engine_alloc(8); const view = new DataView(imports.env.memory.buffer); view.setUint32(ptr, 1, true); - view.setUint32(ptr + 4, 600_000_000, true); + view.setUint32(ptr + 4, length, true); return ptr; }, + engine_free: (p) => { freed.push(p); return ex.engine_free(p); }, })); assert.deepEqual(e.start(PRELUDE, script, 's.js'), { kind: 'rejected', name: 'RangeError', message }, script); + assert.ok(ptr !== 0 && freed.includes(ptr), `${script}: the buffer is freed`); e.dispose(); } }); @@ -441,25 +491,57 @@ test('a stop while an ending is read answers interrupted, as every later call do e.dispose(); }); -test('a result too large to copy in still gets the child\'s own error when the error\'s first copy is refused', async () => { - // The refusal opens the allocator's headroom, which a second copy of the small error fits in, and - // without it the call would reject with an out of memory at its await. - let refuseNext = false; - const { e } = await engineAround((ex) => ({ - engine_alloc: (n) => { - if (!refuseNext) return ex.engine_alloc(n); - refuseNext = false; - return 0; - }, - }), {}, { memoryMaxBytes: 8 * MiB }); - e.start(PRELUDE, 'try { await tool("big"); "fit" } catch (err) { err.name }', 's.js'); - refuseNext = true; - const s = e.settle(1, true, JSON.stringify({ data: 'x'.repeat(12 * MiB) }), UNFIT); +test('a stop in the jobs a read queued comes after a whole ending, which that call answers; later calls answer interrupted', async () => { + let checks = 0; + const { e } = await engine({ interrupt: () => ++checks > 100 }); + const s = e.start(PRELUDE, 'tool("a"); ({ toJSON() { Promise.resolve().then(() => { for (;;) {} }); return 1 } })', 's.js'); + assert.deepEqual(fulfilled(s), 1); + assert.deepEqual(e.settle(1, true, '1', UNFIT), { kind: 'interrupted' }); + e.dispose(); +}); + +test('a result too large to copy in still gets the child\'s own error when the guest sits at the soft limit', async () => { + // The guest is filled to within 16 bytes of the soft limit, so the first copy of the small error + // is refused for real, which opens the allocator's headroom, and the second copy goes there. + // Without it, the call would reject at its await with an out-of-memory error, or a bare null. + let ex!: Exports; + const { e } = await engineAround((real) => { ex = real; return {}; }, {}, { memoryMaxBytes: 8 * MiB }); + e.start(PRELUDE, 'try { await tool("big"); "fit" } catch (err) { err && err.name }', 's.js'); + fillToSoftLimit(ex, 16); + const s = e.settle(1, true, JSON.stringify({ data: 'x'.repeat(MiB) }), UNFIT); assert.equal(s.unfit, true); assert.equal(fulfilled(s), 'ResultTooLarge'); e.dispose(); }); +test('a result\'s copy is freed once it\'s parsed, so the script\'s code after the call has its memory back', async () => { + // After a 2 MiB result, a script filling memory in 64 KiB steps gets as many steps as after a + // tiny one, less the 32 the parsed string holds; a copy still held would take 32 more. + async function steps(result: string) { + const { e } = await engine({}, { memoryMaxBytes: 16 * MiB }); + e.start(PRELUDE, 'const r = await tool("t"); const keep = []; try { for (;;) keep.push("y".repeat(65536)) } catch {} [keep.length, r.length]', 's.js'); + const [n] = fulfilled(e.settle(1, true, result, UNFIT)) as [number, number]; + e.dispose(); + return n; + } + const tiny = await steps(JSON.stringify('z')); + const big = await steps(JSON.stringify('z'.repeat(2 * MiB))); + assert.ok(tiny - big < 48, `${tiny} steps after a tiny result, ${big} after a 2 MiB one`); +}); + +test('dispose frees the runtime after a slot\'s failure, but leaves a module that trapped as it is', async () => { + let disposals = 0; + const counting = (ex: Exports): Exports => ({ engine_dispose: () => { disposals++; return ex.engine_dispose(); } }); + const a = await engineAround(counting, { hostCall: () => { throw new Error('gone'); } }); + assert.throws(() => a.e.start(PRELUDE, '__host("a", "{}")', 's.js'), EngineError); + a.e.dispose(); + assert.equal(disposals, 1); + const b = await engineAround((ex) => ({ ...counting(ex), engine_start: () => { throw new wasm.RuntimeError('unreachable'); } })); + assert.throws(() => b.e.start(PRELUDE, '1', 's.js'), /the engine crashed/); + b.e.dispose(); + assert.equal(disposals, 1); +}); + test('a script too large to copy into the guest\'s memory is rejected, not an engine failure', async () => { const { e } = await engine({}, { memoryMaxBytes: MIN_MEMORY_BYTES }); assert.deepEqual(e.start(PRELUDE, `/*${'x'.repeat(3 * MiB)}*/ 1`, 's.js'), From 115714e02c01de569edfa3e04b44d3491fd41fd4 Mon Sep 17 00:00:00 2001 From: Gladys-2592 Date: Mon, 19 Oct 2026 03:04:40 -0400 Subject: [PATCH 16/41] fix(engine): vendor.sh sees every entry in engine/patches, checks each changed path whole, and puts the committed copy back if the new one can't go in From the round review of the engine's last round: - The patches glob skipped dot-named entries, and a dangling link was passed over, so neither was applied or refused. Every entry is now a regular .patch file, applied, or refused. - The paths the patches change were word-split and globbed, a failed git left the list empty, and a rename showed only its new path. They're now read whole from git's NUL-separated output, with renames as both paths, each compared exactly with FILES, and a failed git stops the script. - A file missing after the patches, and a failure to write one into the new copy, each get their own message. - The committed copy is moved aside, the new one renamed into place, and only then is the old one removed. A failed rename puts the committed copy back, and each message names the path by its absolute path, so the git checkout it suggests works from any directory. - quickjs.wasm.LICENSES says the copy is the release's files with any patch applied; there are none today. Checked in a scratch clone with quickjs-ng v0.17.0: a dot-named .patch is applied; a .DS_Store, a dangling link, a new file named "foo bar.c", a rename of list.h, a removal of list.h and a new file named "*.c" are each refused with engine/quickjs-ng unchanged; a clean run matches the committed copy. The script before this change passed over the first three, reporting 0 patches, and named the spaced file "foo". The rename failures weren't made to happen. Co-Authored-By: Claude Opus 5.5 --- engine/vendor.sh | 51 +++++++++++++++++------- src/code-execution/quickjs.wasm.LICENSES | 6 +-- 2 files changed, 40 insertions(+), 17 deletions(-) diff --git a/engine/vendor.sh b/engine/vendor.sh index 66139cc54..3c4f93cd0 100755 --- a/engine/vendor.sh +++ b/engine/vendor.sh @@ -3,7 +3,8 @@ # files the module compiles and the headers they include, with upstream's LICENSE. Then it applies # the patches in engine/patches/, in the byte order of their names: fixes made here before a # release carries them, each a `git diff` made in a clone of quickjs-ng at COMMIT and saved with -# a .patch name, changing only files FILES lists. There are none today. +# a .patch name, changing only files FILES lists. Anything else there is refused. There are no +# patches today. # # CI runs this script and fails if what it copies differs from what's committed, so the copy is # always the pinned release with the committed patches: the engine changes through the pin or a @@ -38,8 +39,9 @@ if [ -n "$changes" ]; then fi tmp=$(mktemp -d) # The new copy is staged beside the old one, on the same filesystem: once it's whole, the old one -# is removed and the stage renamed into its place. +# is moved aside, the stage renamed into its place, and only then is the old one removed. stage=$(mktemp -d "$here/.quickjs-ng.XXXXXX") +old=$stage.old trap 'rm -rf "$tmp" "$stage"' EXIT git -c advice.detachedHead=false clone --quiet --depth 1 --branch "$TAG" https://github.com/quickjs-ng/quickjs.git "$tmp/quickjs-ng" got=$(git -C "$tmp/quickjs-ng" rev-parse HEAD) @@ -50,10 +52,11 @@ fi # Patches go into the clone's index, and files come from it, so each file's bytes are the # commit's with the patches, whatever the clone's checkout settings would make of them on disk. patched=0 +# Every entry, a dot-named one or a dangling link included, is a regular .patch file or refused. +shopt -s nullglob dotglob for patch in "$here"/patches/*; do - [ -e "$patch" ] || continue if [ "${patch%.patch}" = "$patch" ] || [ ! -f "$patch" ]; then - echo "vendor.sh: engine/patches/$(basename "$patch") isn't a .patch file; rename or remove it. engine/quickjs-ng is unchanged." >&2 + echo "vendor.sh: engine/patches/$(basename "$patch") isn't a regular .patch file; rename or remove it. engine/quickjs-ng is unchanged." >&2 exit 1 fi if ! git -C "$tmp/quickjs-ng" apply --cached "$patch"; then @@ -63,23 +66,43 @@ for patch in "$here"/patches/*; do patched=$((patched + 1)) done # Only FILES are copied, so a patch's change to any other file would be applied and then dropped. -for f in $(git -C "$tmp/quickjs-ng" diff --cached --name-only); do - case " ${FILES[*]} " in - *" $f "*) ;; - *) echo "vendor.sh: the patches change $f, which FILES doesn't list, so the copy would drop that change. engine/quickjs-ng is unchanged." >&2 - exit 1 ;; - esac -done +# Each path the patches leave changed is checked whole, a rename's old and new paths both. +git -C "$tmp/quickjs-ng" diff --cached --name-only --no-renames -z > "$tmp/changed" +while IFS= read -r -d '' f; do + listed=false + for want in "${FILES[@]}"; do + if [ "$want" = "$f" ]; then listed=true; fi + done + if [ "$listed" = false ]; then + echo "vendor.sh: the patches change $f, which FILES doesn't list, so the copy would drop that change. engine/quickjs-ng is unchanged." >&2 + exit 1 + fi +done < "$tmp/changed" for f in "${FILES[@]}"; do - if ! git -C "$tmp/quickjs-ng" show ":$f" > "$stage/$f"; then + if ! git -C "$tmp/quickjs-ng" cat-file -e ":$f" 2>/dev/null; then echo "vendor.sh: quickjs-ng $TAG has no $f, or a patch removes it; update FILES or the patch. engine/quickjs-ng is unchanged." >&2 exit 1 fi + if ! git -C "$tmp/quickjs-ng" show ":$f" > "$stage/$f"; then + echo "vendor.sh: couldn't write $f into the new copy; engine/quickjs-ng is unchanged." >&2 + exit 1 + fi done chmod 755 "$stage" -rm -rf "$dest" +if [ -e "$dest" ] && ! mv "$dest" "$old"; then + echo "vendor.sh: couldn't move $dest aside to replace it; it is unchanged." >&2 + exit 1 +fi if ! mv "$stage" "$dest"; then - echo "vendor.sh: couldn't move the new copy into engine/quickjs-ng; git checkout -- engine/quickjs-ng restores the committed one." >&2 + if [ -e "$old" ] && mv "$old" "$dest"; then + echo "vendor.sh: couldn't move the new copy into $dest; the committed copy is back in place." >&2 + else + echo "vendor.sh: couldn't move the new copy into $dest, nor the committed copy back from $old; git checkout -- $dest restores it." >&2 + fi + exit 1 +fi +if [ -e "$old" ] && ! rm -rf "$old"; then + echo "vendor.sh: the new copy is in place, but the committed copy moved aside to $old couldn't be removed; remove it." >&2 exit 1 fi echo "copied ${#FILES[@]} files from quickjs-ng $TAG ($COMMIT), with $patched patches" diff --git a/src/code-execution/quickjs.wasm.LICENSES b/src/code-execution/quickjs.wasm.LICENSES index 881473e21..eca295aeb 100644 --- a/src/code-execution/quickjs.wasm.LICENSES +++ b/src/code-execution/quickjs.wasm.LICENSES @@ -1,9 +1,9 @@ quickjs.wasm, code_execution's engine, is built by engine/build.sh from agent-framework's own engine/interface.c and the works below, whose notices it carries. -========================================================================================================== -QuickJS-ng v0.17.0 (engine/quickjs-ng, with engine/patches applied), https://github.com/quickjs-ng/quickjs -========================================================================================================== +============================================================================================================================================ +QuickJS-ng v0.17.0 (engine/quickjs-ng: the release's files, with any patch in engine/patches applied), https://github.com/quickjs-ng/quickjs +============================================================================================================================================ The MIT License (MIT) From 5c691cbda5f49c4aa647caa6211324cd5f75c696 Mon Sep 17 00:00:00 2001 From: Gladys-2592 Date: Mon, 19 Oct 2026 04:30:50 -0400 Subject: [PATCH 17/41] fix(code-execution): an exception that escapes the script's promises ends the run as failed, and a settle says when its result was dropped From the round review of the engine's last round: - An exception that escaped the script's promise chain once it was running, from a callback it gave queueMicrotask or a FinalizationRegistry (whose jobs pass a throw on) or from one of QuickJS's own jobs failing, was answered as rejected. Nothing told it from the script's own rejection, and after a rejection the binding had kept, the failure was never answered at all, since both had the same state code. It now has its own state, ENGINE_FAILED, and answer, `failed`, which every later call gives. `rejected` stays the script's own: its completion promise rejected, or the prelude or the script threw as it started. - A settle whose result never reached the script, because the run had already ended (a stop or a failure) or the engine failed before it could deliver it, now says so: ENGINE_DROPPED, and `dropped: true` in its answer. The early return after a failed read of the call's capability also keeps ENGINE_UNFIT now. - Comments and docs: the stack limit's account is scoped to recursion through calls, and names the recursion in C it doesn't hold (the parser, chains of iterator helpers, JSON); the interrupt slot's doc names the stop after a whole ending; engine_settle's comment gives the depths measured under default settings and says when the unfit error's copy is freed; run_jobs says which jobs pass a throw on; call() and dispose() say why any unexpected exception leaves the module as it is. Tests, under Bun 1.4.3: with the last round's binding and module, the two new behaviour tests fail, and so do the three whose settles after a stop now carry `dropped`; with only the module taken back, the same five fail. Taking out each new piece fails its test: a job's failure given the rejected state, or answered as rejected; the dropped flag in the module or in the binding; the final release of the copies (2,000 settles, half for ids no call has, would take far more room); the check of a slot's failure after a read; dispose()'s catch and its mark; the ending bound's comparison, now pinned at exactly MAX_ENDING_BYTES; and the error copy's retry, whose test now checks that the first copy was refused. Not pinned: freeing the unfit error's copy before the script's code runs rather than as engine_settle returns, which leaks nothing either way. At the clean head, on Linux x86_64: tsc is clean; all 12 code_execution test files pass, 287 of 287, under Node 24.22.0 and Node 20.20.2 file by file and under Bun 1.4.3; and a rebuild from a fresh toolchain fetch gives the committed module, 599,900 bytes, sha256 23cb4945. Co-Authored-By: Claude Opus 5.5 --- engine/interface.c | 71 +++++++++++------- src/code-execution/engine.ts | 98 ++++++++++++++++-------- src/code-execution/quickjs.wasm | Bin 599859 -> 599900 bytes test/code-execution-engine.test.ts | 116 ++++++++++++++++++++++++++--- 4 files changed, 216 insertions(+), 69 deletions(-) diff --git a/engine/interface.c b/engine/interface.c index 7b4f880d2..e881a8a97 100644 --- a/engine/interface.c +++ b/engine/interface.c @@ -49,6 +49,10 @@ enum { ENGINE_HOST_FAILED = 4, /* an import reported failure: the run is over, and the host refuses the guest's later calls and output and stops it at its next interrupt check */ + ENGINE_FAILED = 5, /* once the script was running, an exception escaped its promises: a + job failed, or a callback it gave queueMicrotask or a + FinalizationRegistry threw. The run is over, and engine_result has + the exception */ ENGINE_MISUSE = -1, /* called out of order, or with a malformed buffer */ ENGINE_NO_MEMORY = -2, /* the engine couldn't allocate its own state */ ENGINE_UNKNOWN_CALL = -3 /* engine_settle named no pending call */ @@ -56,6 +60,9 @@ enum { /* Or'd into engine_settle's state when the result didn't fit and the call took the host's rejection instead. */ #define ENGINE_UNFIT 16 +/* Or'd into engine_settle's state when the result never reached the script: the run had ended + before this settle, or the engine failed before it could deliver it. */ +#define ENGINE_DROPPED 32 static JSRuntime *rt; static JSContext *ctx; @@ -64,8 +71,10 @@ static JSValue pending; /* The script's completion promise from JS_EVAL_FLAG_ASYNC, which settles to { value }. */ static JSValue completion; /* What ended the run when it wasn't the completion promise: the prelude's or the script's - synchronous throw, or an exception that escaped a job. */ + synchronous throw, or an exception that escaped a job or settling a call; and the state it + gives, ENGINE_REJECTED or ENGINE_FAILED. */ static JSValue failure; +static int failure_state; static bool started; static bool failed; static bool interrupted; @@ -278,13 +287,16 @@ static JSValue js_output(JSContext *c, JSValueConst this_val, int argc, JSValueC return JS_UNDEFINED; } -/* Keeps `exc` as what ended the run, unless something already did. Takes `exc`. */ -static void fail_with(JSValue exc) { +/* Keeps `exc` as what ended the run, unless something already did, with the state it gives: + ENGINE_REJECTED for the prelude's or the script's own throw as it starts, ENGINE_FAILED for an + exception that escapes once it runs. Takes `exc`. */ +static void fail_with(JSValue exc, int st) { if (failed) { JS_FreeValue(ctx, exc); return; } failure = exc; + failure_state = st; failed = true; } @@ -298,13 +310,14 @@ static void run_jobs(void) { if (r == 0) return; if (r < 0) { - /* A promise job catches what its handler throws, so only an uncatchable error or a - failure in the job machinery itself gets here. */ + /* A promise job catches what its handler throws, but the job of a queueMicrotask or + FinalizationRegistry callback passes its throw on, and so does a failure of the + job machinery itself, or an uncatchable error. */ JSValue exc = JS_GetException(job_ctx); if (interrupted) JS_FreeValue(ctx, exc); else - fail_with(exc); + fail_with(exc, ENGINE_FAILED); return; } } @@ -316,7 +329,7 @@ static int state(void) { if (host_failed) return ENGINE_HOST_FAILED; if (failed) - return ENGINE_REJECTED; + return failure_state; switch (JS_PromiseState(ctx, completion)) { case JS_PROMISE_FULFILLED: return ENGINE_FULFILLED; @@ -376,7 +389,7 @@ int engine_start(const char *prelude, uint32_t prelude_len, const char *script, if (interrupted || host_failed) JS_FreeValue(ctx, exc); else - fail_with(exc); + fail_with(exc, ENGINE_REJECTED); return state(); } JS_FreeValue(ctx, r); @@ -387,7 +400,7 @@ int engine_start(const char *prelude, uint32_t prelude_len, const char *script, if (interrupted || host_failed) JS_FreeValue(ctx, exc); else - fail_with(exc); + fail_with(exc, ENGINE_REJECTED); return state(); } completion = r; @@ -401,7 +414,7 @@ static void release(char **buf) { *buf = NULL; } -/* engine_settle's work, which frees each buffer as soon as it's parsed. */ +/* engine_settle's work, which frees each buffer as soon as it's done with it. */ static int settle(uint32_t id, int ok, char **json, uint32_t json_len, char **unfit, uint32_t unfit_len) { if (!started || (*json && (*json)[json_len] != '\0') || (*unfit && (*unfit)[unfit_len] != '\0')) @@ -423,7 +436,7 @@ static int settle(uint32_t id, int ok, char **json, uint32_t json_len, char **un JS_FreeAtom(ctx, atom); if (interrupted || host_failed || failed) { JS_FreeValue(ctx, entry); - return state(); + return state() | ENGINE_DROPPED; } JSValue value = JS_UNDEFINED; @@ -453,14 +466,14 @@ static int settle(uint32_t id, int ok, char **json, uint32_t json_len, char **un JS_FreeValue(ctx, entry); if (JS_IsException(fn)) { JS_FreeValue(ctx, value); - fail_with(JS_GetException(ctx)); - return state(); + fail_with(JS_GetException(ctx), ENGINE_FAILED); + return state() | ENGINE_DROPPED | (too_large ? ENGINE_UNFIT : 0); } JSValue r = JS_Call(ctx, fn, JS_UNDEFINED, 1, (JSValueConst *)&value); JS_FreeValue(ctx, fn); JS_FreeValue(ctx, value); if (JS_IsException(r)) - fail_with(JS_GetException(ctx)); + fail_with(JS_GetException(ctx), ENGINE_FAILED); else JS_FreeValue(ctx, r); run_jobs(); @@ -474,15 +487,18 @@ static int settle(uint32_t id, int ok, char **json, uint32_t json_len, char **un the host couldn't copy in, or a parse that fails, for want of memory or stack (or because it isn't JSON, which a host's result never is). Any of these took effect without reaching the script, which an error at the call's await would hide. A result nested deeper than the - binding's MAX_RESULT_DEPTH never gets here: under Node, a parse nested 5,000 to 10,000 deep - overflows the host's stack before QuickJS's own check sees the recursion, and the binding - refuses results well short of that. + binding's MAX_RESULT_DEPTH never gets here: under Node, a parse nested about 10,000 deep (5,000 + with V8's optimizing tier alone) overflows the host's stack before QuickJS's own check sees the + recursion, and the binding refuses results well short of that. When the run had already ended, + or the engine fails before it can deliver the result, the state answered has ENGINE_DROPPED + set: the result never reached the script. json and unfit are the host's buffers from engine_alloc, each null or ending in a NUL at [len] - as JS_ParseJSON requires, and this frees both, whatever it returns: each as soon as it's - parsed, so the result's copy is gone before the unfit error is parsed and before the script's - code runs. A parse that fails frees what it built too. If even the unfit error can't be copied - in (a null unfit) or parsed, the call rejects with what that failure gives. */ + as JS_ParseJSON requires, and this frees both, whatever it returns: the result's copy as soon + as it's parsed, so it's gone before the unfit error is parsed, and the unfit error's once it's + parsed or isn't needed, before the script's code runs. A parse that fails frees what it built + too. If even the unfit error can't be copied in (a null unfit) or parsed, the call rejects with + what that failure gives. */ EXPORT("engine_settle") int engine_settle(uint32_t id, int ok, char *json, uint32_t json_len, char *unfit, uint32_t unfit_len) { @@ -584,7 +600,7 @@ static uint8_t *pack(uint32_t kind, JSValue text) { static uint8_t *read_ending(int st) { JSValue text; uint32_t kind = 2; - if (st == ENGINE_REJECTED) { + if (st != ENGINE_FULFILLED) { text = error_json(failed ? JS_DupValue(ctx, failure) : JS_PromiseResult(ctx, completion)); } else { JSValue settled = JS_PromiseResult(ctx, completion); @@ -608,10 +624,11 @@ static uint8_t *read_ending(int st) { return pack(kind, text); } -/* After ENGINE_FULFILLED or ENGINE_REJECTED: a buffer the host frees with engine_free, - [u32 kind][u32 length][UTF-8]. Kind 0: fulfilled with undefined (or a value JSON.stringify - leaves out), no text. Kind 1: fulfilled, the value's JSON. Kind 2: an error, as JSON - {name?, message?, stack?}: what the script threw, or why its value couldn't be stringified. +/* After ENGINE_FULFILLED, ENGINE_REJECTED or ENGINE_FAILED: a buffer the host frees with + engine_free, [u32 kind][u32 length][UTF-8]. Kind 0: fulfilled with undefined (or a value + JSON.stringify leaves out), no text. Kind 1: fulfilled, the value's JSON. Kind 2: an error, as + JSON {name?, message?, stack?}: what the script threw, why its value couldn't be stringified, + or the exception that ended the run. Returns 0 in any other state, when it can't allocate, or when a stop lands while the ending is read, which leaves none to give. The ending is fixed before it's read. The script's own code that serializing runs (a toJSON, a getter), and the jobs it queues, which run before this @@ -623,7 +640,7 @@ static uint8_t *read_ending(int st) { EXPORT("engine_result") uint8_t *engine_result(void) { int st = state(); - if (st != ENGINE_FULFILLED && st != ENGINE_REJECTED) + if (st != ENGINE_FULFILLED && st != ENGINE_REJECTED && st != ENGINE_FAILED) return NULL; reading = true; uint8_t *buf = read_ending(st); diff --git a/src/code-execution/engine.ts b/src/code-execution/engine.ts index be9444477..5ff94844f 100644 --- a/src/code-execution/engine.ts +++ b/src/code-execution/engine.ts @@ -38,7 +38,9 @@ export interface EngineSlots { hostCall(callId: number, name: string, argsJson: string): void; /** * Called while the guest runs, every few thousand of its operations. Return true to stop it for - * good: the call in progress answers `interrupted`, and so does every call after. + * good: the call in progress answers `interrupted`, and so does every call after. The one + * exception is a stop in the jobs that reading the script's ending queued, after the ending was + * read whole: that call answers the ending, and the calls after it answer `interrupted`. */ interrupt(): boolean; /** The guest called `__output(stream, text)`: stream 1 is out, 2 is err. */ @@ -61,15 +63,25 @@ export type RunState = * `String(value)`. */ | { kind: 'rejected'; name: string; message: string; stack?: string } + /** + * Once the script was running, an exception escaped its promises and ended the run: a callback + * it gave `queueMicrotask` or a `FinalizationRegistry` threw, or one of QuickJS's own jobs + * failed, such as for want of memory to settle a promise. The fields are the exception's, as for + * `rejected`. Every later call answers the same, and no later result reaches the script. + */ + | { kind: 'failed'; name: string; message: string; stack?: string } /** `interrupt()` returned true while it ran. */ | { kind: 'interrupted' }; /** - * What `settle` answers: the run's state, and `unfit` when the guest couldn't take the result, for + * What `settle` answers: the run's state; `unfit` when the guest couldn't take the result, for * want of memory to copy it in or of memory or stack to parse it, and the call took the child's - * own error instead. + * own error instead; and `dropped` when the result never reached the script, because the run had + * already ended (`interrupted` or `failed`) or the engine failed before it could deliver it. + * Without `dropped`, the script got the result or the child's error, even if the run ended in the + * jobs that followed. */ -export type SettleState = RunState & { unfit?: true }; +export type SettleState = RunState & { unfit?: true; dropped?: true }; export interface Engine { /** @@ -88,8 +100,8 @@ export interface Engine { * for calls still in flight when a script returned. Throws for an id that isn't pending. * * Once a call has answered the script's ending, later calls answer it again, unless something - * came after it: a stop answers `interrupted`, a slot's failure throws, and a failure of - * QuickJS's own job machinery answers `rejected` with that failure. + * came after it: a stop answers `interrupted`, a slot's failure throws, and an exception that + * escapes the script's promises answers `failed`. */ settle(callId: number, ok: boolean, json: string | Uint8Array, unfitJson: string): SettleState; /** Frees the engine's runtime, unless the module trapped, which leaves it as it is. Nothing may be called after it. */ @@ -115,15 +127,18 @@ export const MIN_MEMORY_BYTES = 4 * 1024 * 1024; export const MAX_MEMORY_BYTES = 2048 * 1024 * 1024; /** * The most QuickJS's stack limit may be, and its default. QuickJS measures its stack in the - * module's linear memory, but each level of a script's recursion also takes the host's own stack, - * about two to three times as much, and under Node that stack is about 984 KiB. Recursing through - * a getter, a proxy trap or a built-in that calls back (map, toString, for...of), the host's stack - * ran out first, crashing the engine, once QuickJS's limit passed about 320 KiB. At this limit, - * QuickJS's catchable RangeError comes first, with about two fifths of the host's stack to spare. - * Bun's stack is larger, and the same limit holds there, so a script ends the same way on either. + * module's linear memory, but each level of a script's recursion through calls also takes the + * host's own stack, two to three times as much in the 31 ways to recurse that were measured, and + * under Node that stack is about 984 KiB. Recursing through a getter, a proxy trap or a built-in + * that calls back (map, toString, for...of), the host's stack ran out first, crashing the engine, + * once QuickJS's limit passed about 320 KiB. At this limit, QuickJS's catchable RangeError comes + * first in each of the 31, with about two fifths of the host's stack to spare. Bun's stack is + * larger, and the same limit holds there, so recursion through calls ends the same way on either. * - * The parser isn't held to it: it recurses in C, which the check barely sees, so under Node a - * script whose text nests function expressions 300 deep, or brackets 700 deep, crashes its run. + * Recursion in C that the check barely sees isn't held to it, and under Node crashes the run: a + * script's text nesting function expressions 300 deep or brackets 700 deep (the parser), a chain + * of 3,000 iterator helpers (each one's next calls the one before), and the script's own JSON + * nested 5,000 to 10,000 deep (see MAX_RESULT_DEPTH). */ export const MAX_STACK_BYTES = 192 * 1024; export const DEFAULT_STACK_BYTES = MAX_STACK_BYTES; @@ -147,7 +162,8 @@ const DIAGNOSTICS_CHARS = 4096; const MAX_ENDING_BYTES = 2 ** 29 - 24; // interface.c's states and failures. -const PENDING = 0, FULFILLED = 1, REJECTED = 2, INTERRUPTED = 3, HOST_FAILED = 4, UNFIT = 16; +const PENDING = 0, FULFILLED = 1, REJECTED = 2, INTERRUPTED = 3, HOST_FAILED = 4, FAILED = 5; +const UNFIT = 16, DROPPED = 32; const MISUSE = -1, NO_MEMORY = -2, UNKNOWN_CALL = -3; interface Exports { @@ -336,6 +352,8 @@ export async function loadEngine(limits: EngineLimits, slots: EngineSlots): Prom out = fn(); } catch (err) { if (err instanceof EngineError) throw err; + // A trap, or an exception in reading what the module returned: either way the module may + // have stopped partway, so dispose() leaves it as it is. crashed = true; // A slot that failed first is why: the guest ran on, refused, until the module trapped. if (slotFailed) return slotFailure(what); @@ -381,12 +399,28 @@ export async function loadEngine(limits: EngineLimits, slots: EngineSlots): Prom ex.engine_free(ptr); return { kind, text: out }; }; - const errorState = (json: string): RunState => { + const errorState = (json: string, kind: 'rejected' | 'failed'): RunState => { const e = JSON.parse(json) as { name?: string; message?: string; stack?: string }; - const state: RunState = { kind: 'rejected', name: e.name ?? '', message: e.message ?? '' }; + const state: { kind: 'rejected' | 'failed'; name: string; message: string; stack?: string } = + { kind, name: e.name ?? '', message: e.message ?? '' }; if (typeof e.stack === 'string') state.stack = e.stack; return state; }; + /** What an ending that can't be read out is answered with, by what it is. */ + const unread = { + [FULFILLED]: { + memory: 'out of memory while serializing the script\'s return value; return less, or let go of data the script no longer needs', + long: (n: number) => `the script's return value comes to ${n} bytes as text, more than the ${MAX_ENDING_BYTES} the engine reads back; return less`, + }, + [REJECTED]: { + memory: 'out of memory while reading what the script threw; throw something smaller, such as an Error with a shorter message, or let go of data the script no longer needs', + long: (n: number) => `what the script threw comes to ${n} bytes as text, more than the ${MAX_ENDING_BYTES} the engine reads back; throw something smaller`, + }, + [FAILED]: { + memory: 'out of memory while reading the exception that ended the run', + long: (n: number) => `the exception that ended the run comes to ${n} bytes as text, more than the ${MAX_ENDING_BYTES} the engine reads back`, + }, + } as const; /** * Each ending as it was first read. Reading one serializes the script's value or error in the * guest, which can run the script's own code (a toJSON, a getter), and every settle after the @@ -408,7 +442,9 @@ export async function loadEngine(limits: EngineLimits, slots: EngineSlots): Prom if (slotFailed) return slotFailure(what); return { kind: 'interrupted' }; case FULFILLED: - case REJECTED: { + case REJECTED: + case FAILED: { + // Each kept under its own code, so a failure after the script's own ending is read anew. const known = endings.get(code); if (known) return { ...known }; const r = result(); @@ -418,17 +454,12 @@ export async function loadEngine(limits: EngineLimits, slots: EngineSlots): Prom // the read queued comes after a whole ending, which this call answers. if (slotFailed) return slotFailure(what); if (!r && stopped) return { kind: 'interrupted' }; - const state: RunState = !r - ? { kind: 'rejected', name: 'InternalError', message: code === FULFILLED - ? 'out of memory while serializing the script\'s return value; return less, or let go of data the script no longer needs' - : 'out of memory while reading what the script threw; throw something smaller, such as an Error with a shorter message, or let go of data the script no longer needs' } - : 'tooLong' in r - ? { kind: 'rejected', name: 'RangeError', message: code === FULFILLED - ? `the script's return value comes to ${r.tooLong} bytes as text, more than the ${MAX_ENDING_BYTES} the engine reads back; return less` - : `what the script threw comes to ${r.tooLong} bytes as text, more than the ${MAX_ENDING_BYTES} the engine reads back; throw something smaller` } + const kind = code === FAILED ? 'failed' : 'rejected'; + const state: RunState = !r ? { kind, name: 'InternalError', message: unread[code].memory } + : 'tooLong' in r ? { kind, name: 'RangeError', message: unread[code].long(r.tooLong) } : r.kind === 0 ? { kind: 'fulfilled', json: undefined } : r.kind === 1 ? { kind: 'fulfilled', json: r.text } - : errorState(r.text); + : errorState(r.text, kind); endings.set(code, state); return { ...state }; } @@ -485,17 +516,20 @@ export async function loadEngine(limits: EngineLimits, slots: EngineSlots): Prom if (!u.ptr) u = put(unfitJson); const j = nestsDeeper(json, MAX_RESULT_DEPTH) ? { ptr: 0, len: 0 } : put(json); const code = ex.engine_settle(callId, ok ? 1 : 0, j.ptr, j.len, u.ptr, u.len); - if (code >= 0 && (code & UNFIT) !== 0) return { ...toState(code & ~UNFIT, 'settle()'), unfit: true }; - return toState(code, 'settle()'); + if (code < 0) return toState(code, 'settle()'); + const state: SettleState = toState(code & ~(UNFIT | DROPPED), 'settle()'); + if (code & UNFIT) state.unfit = true; + if (code & DROPPED) state.dropped = true; + return state; }); }, dispose() { if (disposed) return; // Refused from inside a slot, it isn't marked done, so it can be made again. if (busy) throw new EngineError('dispose() was called from inside one of the engine\'s slots, which may not call back into it'); - // A module that trapped may have stopped anywhere, so it's left as it is. One that ended - // for a slot's failure, or for want of memory for its own state, is whole, and frees its - // runtime. + // A module whose call ended in an exception, a trap or otherwise, may have stopped anywhere, + // so it's left as it is. One that ended for a slot's failure, or for want of memory for its + // own state, is whole, and frees its runtime. if (!crashed) { busy = true; try { diff --git a/src/code-execution/quickjs.wasm b/src/code-execution/quickjs.wasm index 335699e3368bd04b1ae7b1a679df5c0acec9bc5c..e41f6d1662398d1554cd6073a91e51173ab6f00f 100644 GIT binary patch delta 1361 zcmY*XNpMtU5bo}OdGF1enfGSPHiY~$8IzgpiWL?MDlZriP(m;+QA809h$RK+!4nY? z0#&IJ>pu)!loRE0N)6?Bj2cPl0ln!#OD`TcSRS-Ip=D>Xp#RkWx~I4Q`?|Y!yY%Dj zQXG!F{u%-Reyv}bk7rQQ*%`1Kai2Yjf7YfchS*m-Ei#Dx_JZ6W4FGJ0ToV{loTZ?N z4=V}SB5>=YkR61+zOJs+wg8LGDN$(kyzcgNXI?vPT1uJ74%)2RjYIa3+Bj5hjm0qL zA)+TxBDP?6)d^PT@mfV zO^b935>h&u44<>#=yL9GB5>M+5l`iVz<;1S|9sFMykH&Y5$?kQr;tCAMTY1pmD!gjxqo4s=U$WcXO{w0rtI^exxPO9hXRkfymIsCe zk#LE6AJ*Cvp4LJNsReQfi44gYy!@boU=$#)VA_7~UdLnhO)rXN-7kW=xJmpS-&S5pDyZ8N>3HxXT7wIU#V8w<)eDm>3JqB#2h^F(tTRwghiy8HVAgzsQM z-?N3KHN61U)1xw54F|MiFD_#zYY}mzXwef`vx{(U6pzr@xJO24G6p=Yo z`}T`T3>#)%rD!NJ#>=0vmBmvS)vhj{#DiPlKbx~QFbGS5A1%MY{UB&;lwlStv5nR9 zn;YF)r8s1Skr9PA$^X^vzjccuve~5>7U%6!-7cZ}0iUd2`+z>YiHGW=lVVp4d+L`D si`g$(riyY7v8Vcer`#lam}+(YC!L+W(<%8XzM8MY*R9{_^!AE>0bBYCHvj+t delta 1367 zcmaJu4z$#RHa!*O{)MYEupkL3IR$XQBb9p zV{J-+1gR30=3q{Mgv3KgKyauz^pKV+IZ;(YLP#JaP8^L>h)z$`WHUQWZ7(gc! zlN_kcGEf8-;Q&)uoEJd?MEc&o3+Y{NlvPV+f zxVI-u0x9H6^jssHcpjI?WcsE7r2t3}+ z+wbp2*GeZWj=ayZPPnG)K5km6gk2EUPVS^qL4cg}q(}Fl<-@b;Wvdg1ht-mmhrFs;d*L~C%NnVf+irt>5TZ{J!a z-s|AQ9h(=lzI~L?ggmp*P}RTpGwUFRd{*aXFh`dRiB111xkvxGgC<*v#iqOHz0u&& zLIp37q`ExG66%%9?$G}h+Qcs&6+Xi;Nz@mYlND@L%Frz?tii=6lKqQ~q`Ln=w=Rv4 zj9>)E^sgdGNiLQC+}PM~&zInso-}Vvhxpq?b$Db}r@jdp^MD?-k;O<8NcgBJP&tKO zCW8kYqPwIcglWY_2$8gD(u|}j>FqY67tkosA}dXA5gJ%qV`!1Dex76PlT_-dZ2KC_ z8>A?Xg`enM4HKf7YCtcmD|kyF(WhebNQgd?MvvZXpgD+f^?N-JSj&7p!l7C_v6=wW zy97e~T03|3G}L~+Hr+Jnb5o`3XKE|g=iyL+gv;A7tu<*5u4vkuhZw%2Bcxt55gl4f z&0%x7dL%4FZw19K>g?K@+Phv^xUycqvJS=%@L2uYd-(ARJXN2YrW0+DtABZtE`Fo+ rXog(`$W_<-*&ZXOTVT~A-rYK?Lk8fEv6Pr3$M2a_5}S0fXEA( diff --git a/test/code-execution-engine.test.ts b/test/code-execution-engine.test.ts index b58dc4d33..8ee73f745 100644 --- a/test/code-execution-engine.test.ts +++ b/test/code-execution-engine.test.ts @@ -280,15 +280,15 @@ test('the interrupt stops a spinning script for good', async () => { e.dispose(); }); -test('the interrupt reaches a script spinning in jobs, and later settles answer interrupted', async () => { +test('the interrupt reaches a script spinning in jobs, and later settles answer interrupted, their results dropped', async () => { let stop = false; const { e } = await engine({ interrupt: () => stop }, { memoryMaxBytes: 8 * MiB }); e.start(PRELUDE, 'tool("u"); const v = await tool("t"); for (;;) { await 0 }', 's.js'); stop = true; assert.deepEqual(e.settle(2, true, '1', UNFIT), { kind: 'interrupted' }); - // A settle after that does nothing for the stopped run: a result too large for its memory isn't - // even read, so it isn't reported unfit. - assert.deepEqual(e.settle(1, true, JSON.stringify('x'.repeat(12 * MiB)), UNFIT), { kind: 'interrupted' }); + // A settle after that does nothing for the stopped run: its result is dropped, and one too large + // for its memory isn't even read, so it isn't reported unfit. + assert.deepEqual(e.settle(1, true, JSON.stringify('x'.repeat(12 * MiB)), UNFIT), { kind: 'interrupted', dropped: true }); e.dispose(); }); @@ -487,7 +487,46 @@ test('a stop while an ending is read answers interrupted, as every later call do let checks = 0; const { e } = await engine({ interrupt: () => ++checks > 100 }); assert.deepEqual(e.start(PRELUDE, 'tool("a"); ({ toJSON() { for (;;) {} } })', 's.js'), { kind: 'interrupted' }); - assert.deepEqual(e.settle(1, true, '1', UNFIT), { kind: 'interrupted' }); + assert.deepEqual(e.settle(1, true, '1', UNFIT), { kind: 'interrupted', dropped: true }); + e.dispose(); +}); + +test('a slot that fails in the jobs a read queued ends the run with its EngineError, not the ending', async () => { + let checks = 0; + const { e } = await engine({ interrupt: () => { if (++checks > 100) throw new Error('the stop check failed'); return false; } }); + assert.throws(() => e.start(PRELUDE, 'tool("a"); ({ toJSON() { Promise.resolve().then(() => { for (;;) {} }); return 1 } })', 's.js'), + (err: unknown) => err instanceof EngineError && /the stop check failed/.test(err.message)); + e.dispose(); +}); + +test('an exception that escapes the script\'s promises ends the run as failed, which a kept rejection doesn\'t hide, and drops later results', async () => { + // A callback given to queueMicrotask runs as a job that passes its throw on. + const a = await engine(); + const s = a.e.start(PRELUDE, 'const first = tool("a"); tool("b"); first.then(() => queueMicrotask(() => { throw new TypeError("late") })); throw new Error("own")', 's.js'); + assert.deepEqual([s.kind, (s as { message?: string }).message], ['rejected', 'own']); + // The result reached the script, whose code then queued the callback that threw. + const failed = a.e.settle(1, true, '1', UNFIT); + assert.deepEqual([failed.kind, (failed as { name?: string }).name, (failed as { message?: string }).message, failed.dropped], + ['failed', 'TypeError', 'late', undefined]); + // The run is over, so the next result never reaches the script. + const later = a.e.settle(2, true, '2', UNFIT); + assert.deepEqual([later.kind, (later as { message?: string }).message, later.dropped], ['failed', 'late', true]); + a.e.dispose(); + // Thrown in the same turn as the script's return, it ends the run before the value is read. + const b = await engine(); + const t = b.e.start(PRELUDE, 'queueMicrotask(() => { throw new RangeError("after") }); 1', 's.js'); + assert.deepEqual([t.kind, (t as { name?: string }).name, (t as { message?: string }).message], ['failed', 'RangeError', 'after']); + b.e.dispose(); +}); + +test('a result that reaches a stopped run is dropped', async () => { + let stop = false; + const { e } = await engine({ interrupt: () => stop }); + e.start(PRELUDE, 'tool("a"); await tool("b"); for (;;) {}', 's.js'); + stop = true; + // The second result reaches the script, which spins until the stop. + assert.deepEqual(e.settle(2, true, '2', UNFIT), { kind: 'interrupted' }); + assert.deepEqual(e.settle(1, true, '1', UNFIT), { kind: 'interrupted', dropped: true }); e.dispose(); }); @@ -496,7 +535,7 @@ test('a stop in the jobs a read queued comes after a whole ending, which that ca const { e } = await engine({ interrupt: () => ++checks > 100 }); const s = e.start(PRELUDE, 'tool("a"); ({ toJSON() { Promise.resolve().then(() => { for (;;) {} }); return 1 } })', 's.js'); assert.deepEqual(fulfilled(s), 1); - assert.deepEqual(e.settle(1, true, '1', UNFIT), { kind: 'interrupted' }); + assert.deepEqual(e.settle(1, true, '1', UNFIT), { kind: 'interrupted', dropped: true }); e.dispose(); }); @@ -505,10 +544,17 @@ test('a result too large to copy in still gets the child\'s own error when the g // is refused for real, which opens the allocator's headroom, and the second copy goes there. // Without it, the call would reject at its await with an out-of-memory error, or a bare null. let ex!: Exports; - const { e } = await engineAround((real) => { ex = real; return {}; }, {}, { memoryMaxBytes: 8 * MiB }); + const copies: number[] = []; + const { e } = await engineAround((real) => { + ex = real; + return { engine_alloc: (n) => { const p = real.engine_alloc(n); copies.push(p); return p; } }; + }, {}, { memoryMaxBytes: 8 * MiB }); e.start(PRELUDE, 'try { await tool("big"); "fit" } catch (err) { err && err.name }', 's.js'); fillToSoftLimit(ex, 16); + copies.length = 0; const s = e.settle(1, true, JSON.stringify({ data: 'x'.repeat(MiB) }), UNFIT); + // The error's first copy was refused, its second made, and the result's refused. + assert.deepEqual(copies.map((p) => p !== 0), [false, true, false]); assert.equal(s.unfit, true); assert.equal(fulfilled(s), 'ResultTooLarge'); e.dispose(); @@ -529,6 +575,55 @@ test('a result\'s copy is freed once it\'s parsed, so the script\'s code after t assert.ok(tiny - big < 48, `${tiny} steps after a tiny result, ${big} after a 2 MiB one`); }); +test('settling calls frees both copies, whether the result reaches the script or no call has its id', async () => { + // Measured through the module's allocator, as the largest block that fits: a copy kept on each + // of 2,000 settles of a 1 KiB result would take 2 MiB or more. + let ex!: Exports; + const { e } = await engineAround((real) => { ex = real; return {}; }, {}, { memoryMaxBytes: 16 * MiB }); + const largest = () => { + let lo = 0, hi = MAX_MEMORY_BYTES; + while (hi - lo > 1) { + const mid = Math.floor((lo + hi) / 2); + const p = ex.engine_alloc(mid); + if (p) { ex.engine_free(p); lo = mid; } else { ex.engine_free(ex.engine_alloc(1)); hi = mid; } + } + return lo; + }; + e.start(PRELUDE, 'for (;;) await tool("t")', 's.js'); + const result = JSON.stringify('r'.repeat(1024)); + let id = 1; + for (let i = 0; i < 200; i++) e.settle(id++, true, result, UNFIT); + const before = largest(); + for (let i = 0; i < 1000; i++) e.settle(id++, true, result, UNFIT); + for (let i = 0; i < 1000; i++) assert.throws(() => e.settle(1_000_000 + i, true, result, UNFIT), /no pending call/); + const after = largest(); + assert.ok(before - after < 64 * 1024, `${before - after} bytes less room after 2,000 settles`); + e.dispose(); +}); + +test('an ending of exactly MAX_ENDING_BYTES is read, not refused', async () => { + // The module's buffer here only claims that length, so reading it fails, which a refusal wouldn't. + const { e } = await engineAround((ex, imports) => ({ + engine_result: () => { + const ptr = ex.engine_alloc(8); + const view = new DataView(imports.env.memory.buffer); + view.setUint32(ptr, 1, true); + view.setUint32(ptr + 4, 536_870_888, true); + return ptr; + }, + })); + assert.throws(() => e.start(PRELUDE, '1', 's.js'), (err: unknown) => err instanceof EngineError && /the engine crashed/.test(err.message)); + e.dispose(); +}); + +test('a dispose() that traps throws an EngineError, and the engine is disposed all the same', async () => { + const { e } = await engineAround(() => ({ engine_dispose: () => { throw new wasm.RuntimeError('unreachable'); } })); + assert.equal(fulfilled(e.start(PRELUDE, '1', 's.js')), 1); + assert.throws(() => e.dispose(), (err: unknown) => err instanceof EngineError && /^the engine crashed in dispose\(\): unreachable$/.test(err.message)); + assert.throws(() => e.settle(1, true, '1', UNFIT), /after dispose/); + assert.doesNotThrow(() => e.dispose()); +}); + test('dispose frees the runtime after a slot\'s failure, but leaves a module that trapped as it is', async () => { let disposals = 0; const counting = (ex: Exports): Exports => ({ engine_dispose: () => { disposals++; return ex.engine_dispose(); } }); @@ -642,9 +737,10 @@ test('a result too large for the guest\'s memory rejects the call with the child }); test('a result counts against the limit while its call settles, so a script that fills memory then gets a catchable error', async () => { - // The result's copy stays in the guest's memory until settle returns, and the script runs on - // meanwhile. With the copy outside the limit, memory ran out before the limit refused anything, - // and the script caught a bare null. + // The result's copy is allocated within the guest's limit, beside the value parsed from it, + // until it's freed after the parse. A script that fills memory afterwards meets the limit's + // refusal and catches a real error; in the incident this test came from, with the copy outside + // the limit, it caught a bare null. const { e } = await engine({}, { memoryMaxBytes: 32 * MiB }); e.start(PRELUDE, 'const r = await tool("big"); let a = [], caught; try { for (;;) a.push({ n: a.length }); } catch (err) { caught = err === null ? "null" : err.message; } a = null; [r.length, caught]', 's.js'); assert.deepEqual(fulfilled(e.settle(1, true, JSON.stringify('x'.repeat(6 * MiB)), UNFIT)), [6 * MiB, 'out of memory']); From c49252134d5db61774dfa45a2a4e9193b7162a15 Mon Sep 17 00:00:00 2001 From: Gladys-2592 Date: Mon, 19 Oct 2026 04:30:50 -0400 Subject: [PATCH 18/41] fix(engine): vendor.sh refuses a listed file the patches change in kind, a link among the patches and an engine/patches that isn't a directory, and puts the committed copy back if stopped From the round review of the engine's last round: - A patch that left a listed file executable, a link or a submodule passed the name check, and the copy wrote it as a plain file. Each listed file must now have mode 100644 after the patches. - A link in engine/patches, which -f followed, was applied; and an engine/patches that was a file or a dangling link was passed over, so the release was copied unpatched. Both are refused now. - If the script stops between moving the committed copy aside and renaming the new one in, its exit trap puts the committed copy back and says so; once the new copy is in place, the trap removes the old one. - The restore command it suggests names the repository with git -C, and quotes both paths. Checked in a scratch clone with quickjs-ng v0.17.0: a patch making list.h executable, one turning it into a link, a link to a real patch, and engine/patches as a file or as a dangling link are each refused with engine/quickjs-ng unchanged; a copy of the script that signals itself between the two renames put the committed copy back; the restore command, run from /, restored the copy after it was removed; and clean runs match the committed copy. Co-Authored-By: Claude Opus 5.5 --- engine/vendor.sh | 38 +++++++++++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/engine/vendor.sh b/engine/vendor.sh index 3c4f93cd0..9dd7d19ff 100755 --- a/engine/vendor.sh +++ b/engine/vendor.sh @@ -31,6 +31,9 @@ FILES=( ) here=$(cd "$(dirname "$0")" && pwd) dest=$here/quickjs-ng +# The command that restores the committed copy, from any directory. +top=$(git -C "$here" rev-parse --show-toplevel) +restore="git -C $(printf '%q' "$top") checkout -- $(printf '%q' "${dest#"$top"/}")" changes=$(git -C "$here" status --porcelain --untracked-files=all --ignored -- quickjs-ng) if [ -n "$changes" ]; then echo "vendor.sh: engine/quickjs-ng holds changes or files not committed, which replacing it would erase; commit or remove them first:" >&2 @@ -42,7 +45,19 @@ tmp=$(mktemp -d) # is moved aside, the stage renamed into its place, and only then is the old one removed. stage=$(mktemp -d "$here/.quickjs-ng.XXXXXX") old=$stage.old -trap 'rm -rf "$tmp" "$stage"' EXIT +# However the script ends, a committed copy left moved aside goes back, unless the new one is in +# its place, and what was staged goes. +finish() { + if [ -e "$old" ]; then + if [ ! -e "$dest" ] && mv "$old" "$dest"; then + echo "vendor.sh: stopped while replacing the copy; the committed copy is back in place." >&2 + elif [ -e "$dest" ]; then + rm -rf "$old" + fi + fi + rm -rf "$tmp" "$stage" +} +trap finish EXIT git -c advice.detachedHead=false clone --quiet --depth 1 --branch "$TAG" https://github.com/quickjs-ng/quickjs.git "$tmp/quickjs-ng" got=$(git -C "$tmp/quickjs-ng" rev-parse HEAD) if [ "$got" != "$COMMIT" ]; then @@ -52,10 +67,15 @@ fi # Patches go into the clone's index, and files come from it, so each file's bytes are the # commit's with the patches, whatever the clone's checkout settings would make of them on disk. patched=0 -# Every entry, a dot-named one or a dangling link included, is a regular .patch file or refused. +# Every entry, a dot-named one or a link included, is a regular .patch file or refused, and so is +# an engine/patches that isn't a directory. +if { [ -e "$here/patches" ] || [ -L "$here/patches" ]; } && { [ ! -d "$here/patches" ] || [ -L "$here/patches" ]; }; then + echo "vendor.sh: engine/patches isn't a directory; remove it, or make it one. engine/quickjs-ng is unchanged." >&2 + exit 1 +fi shopt -s nullglob dotglob for patch in "$here"/patches/*; do - if [ "${patch%.patch}" = "$patch" ] || [ ! -f "$patch" ]; then + if [ "${patch%.patch}" = "$patch" ] || [ -L "$patch" ] || [ ! -f "$patch" ]; then echo "vendor.sh: engine/patches/$(basename "$patch") isn't a regular .patch file; rename or remove it. engine/quickjs-ng is unchanged." >&2 exit 1 fi @@ -66,7 +86,7 @@ for patch in "$here"/patches/*; do patched=$((patched + 1)) done # Only FILES are copied, so a patch's change to any other file would be applied and then dropped. -# Each path the patches leave changed is checked whole, a rename's old and new paths both. +# Each path the patches leave changed is checked by name, a rename's old and new paths both. git -C "$tmp/quickjs-ng" diff --cached --name-only --no-renames -z > "$tmp/changed" while IFS= read -r -d '' f; do listed=false @@ -78,11 +98,19 @@ while IFS= read -r -d '' f; do exit 1 fi done < "$tmp/changed" +# The copy writes each file as a plain one, so a listed file that the patches leave executable, a +# link or a submodule is refused rather than written as something else. for f in "${FILES[@]}"; do if ! git -C "$tmp/quickjs-ng" cat-file -e ":$f" 2>/dev/null; then echo "vendor.sh: quickjs-ng $TAG has no $f, or a patch removes it; update FILES or the patch. engine/quickjs-ng is unchanged." >&2 exit 1 fi + mode=$(git -C "$tmp/quickjs-ng" ls-files --stage -- ":(literal)$f") + mode=${mode%% *} + if [ "$mode" != 100644 ]; then + echo "vendor.sh: $f has mode $mode after the patches, which the copy can't keep, since it writes plain files; engine/quickjs-ng is unchanged." >&2 + exit 1 + fi if ! git -C "$tmp/quickjs-ng" show ":$f" > "$stage/$f"; then echo "vendor.sh: couldn't write $f into the new copy; engine/quickjs-ng is unchanged." >&2 exit 1 @@ -97,7 +125,7 @@ if ! mv "$stage" "$dest"; then if [ -e "$old" ] && mv "$old" "$dest"; then echo "vendor.sh: couldn't move the new copy into $dest; the committed copy is back in place." >&2 else - echo "vendor.sh: couldn't move the new copy into $dest, nor the committed copy back from $old; git checkout -- $dest restores it." >&2 + echo "vendor.sh: couldn't move the new copy into $dest, nor the committed copy back from $old; $restore restores it." >&2 fi exit 1 fi From 1e3e80677e59f583811b064768167ee5d0b90e5d Mon Sep 17 00:00:00 2001 From: Gladys-2592 Date: Mon, 19 Oct 2026 06:07:46 -0400 Subject: [PATCH 19/41] fix(code-execution): a settle whose resolve call was stopped on its way in is dropped, and a failure stays the run's answer, after its ending and through a stop From the round review of the engine's last round: - A settle whose call to the promise's resolve or reject function raised answered without ENGINE_DROPPED, so the result counted as delivered. Those functions don't throw once they run (what reading a result's `then` throws rejects the promise), so a raise means the call never ran: its interrupt check on the way in stopped the guest, or there was no stack for it. It now answers dropped, and a stop there is no longer recorded as a failure. A failed read of the call's resolve and reject functions answers dropped without ENGINE_UNFIT, since the call took neither: the two flags are never both set. - An exception escaping the jobs that reading the script's ending queued (a toJSON or getter queueing a callback that throws) ended the run, but the call that read the ending answered that ending, so with no call left to settle the failure was never answered. The module exports engine_state, and the binding reads it after reading an ending: a run that failed meanwhile is answered as failed, in the ending's place. - A stop while the exception that ended the run was read discarded what was read and answered interrupted, so a run that failed after its own ending was kept could report that ending. A failure now stays the answer: state() puts it ahead of a stop (nothing records a failure once the guest is stopped or an import has failed, so it came first), and what was read of the exception is kept, less the parts the stop cut off. Out of memory for that read still answers failed. - The messages for a failure that can't be read out say what to do. - Comments and docs: what `dropped` means (the call's promise never took the result; once it has, a stop or failure in the script's code leaves it taken, a `then` getter included); that a stop or failure "after the run had ended" is one after a stop or a failure; ENGINE_FAILED's sources, settle's own failures included; the RunState doc without a memory failure QuickJS drops silently; the stack limit's account of iterator helpers under Bun (each one's next calls the one before directly, past QuickJS's checks, so 30,000 run the module's 1 MiB stack out and it traps) and of JSON depth (about 10,000 deep under Node, 5,000 with V8's optimizing tier alone); MAX_ENDING_BYTES answering with the state the ending belongs to; why an exception reading what the module returned leaves the module alone; and build.sh's account of where QuickJS's stack limit sits. Tests, under Bun 1.4.3: with the last round's binding and module, four tests fail: a failure escaping the read's jobs, a stop while a failure is read, and the rows for a failure that can't be read out (out of memory, too long). Taking out each new piece fails its test: state()'s order; keeping a failure's read through a stop; the binding's read of engine_state, and its keeping a failure through a stop when nothing could be read. Pieces the last round left unpinned now have tests: freeing the child's error's copy before the script's code runs (a 2 MiB error, measured as the script's room); the error's copy freed on a settle for an id no call has (a 1 KiB error on each of 1,000); and dispose() leaving the module alone after a read that threw. Not pinned by a test: the dropped answer when the resolve call is stopped on its way in, which needs the interrupt check to fall due at that call. A probe made it do so: with k calls to an empty function before the script's await and the stop set before the settle, k = 9,993 put QuickJS's interrupt check on the resolve call's own entry; the module before this change answered interrupted without dropped, and this one answers dropped, of 1,101 values of k. At the clean head, on Linux x86_64: tsc is clean; all 12 code_execution test files pass, 303 of 303, under Node 24.22.0 and Node 20.20.2 file by file and under Bun 1.4.3; and a rebuild from a fresh toolchain fetch gives the committed module, 600,013 bytes, sha256 98e861c2. Co-Authored-By: Claude Opus 5.5 --- engine/build.sh | 6 +- engine/interface.c | 76 ++++++++++++++-------- src/code-execution/engine.ts | 68 ++++++++++++-------- src/code-execution/quickjs.wasm | Bin 599900 -> 600013 bytes test/code-execution-engine.test.ts | 97 +++++++++++++++++++++++------ 5 files changed, 175 insertions(+), 72 deletions(-) diff --git a/engine/build.sh b/engine/build.sh index 433b1f582..0204313ff 100755 --- a/engine/build.sh +++ b/engine/build.sh @@ -75,8 +75,10 @@ done "$CC" "${CFLAGS[@]}" -Werror -c interface.c -o "$build/interface.o" # A reactor whose memory the host makes, so the host sets its maximum, the child's budget. -# The stack sits first in memory, so QuickJS's limit beneath it is measured from address 0 -# and an overflow past it traps rather than writing over data. +# The stack sits first in memory, at [0, 1 MiB), growing down from its top. QuickJS's limit is +# set below the top as engine_init finds it, 192 KiB at most, and recursion its checks don't see +# that runs past the stack's bottom wraps below address 0, past the memory's end, and traps +# rather than writing over data. "$CC" --target=wasm32-wasip1 --sysroot="$SYSROOT" -Oz -flto -mexec-model=reactor \ -lwasi-emulated-process-clocks -lwasi-emulated-signal \ -Wl,--import-memory -Wl,--stack-first -Wl,-z,stack-size=1048576 -Wl,--strip-debug \ diff --git a/engine/interface.c b/engine/interface.c index e881a8a97..af5a9bb2c 100644 --- a/engine/interface.c +++ b/engine/interface.c @@ -49,10 +49,11 @@ enum { ENGINE_HOST_FAILED = 4, /* an import reported failure: the run is over, and the host refuses the guest's later calls and output and stops it at its next interrupt check */ - ENGINE_FAILED = 5, /* once the script was running, an exception escaped its promises: a - job failed, or a callback it gave queueMicrotask or a - FinalizationRegistry threw. The run is over, and engine_result has - the exception */ + ENGINE_FAILED = 5, /* once the script was running, an exception escaped its promises (a + callback it gave queueMicrotask or a FinalizationRegistry threw, + or a job failed), or the engine failed to settle a call. The run + is over, every later call answers this, and engine_result has the + exception */ ENGINE_MISUSE = -1, /* called out of order, or with a malformed buffer */ ENGINE_NO_MEMORY = -2, /* the engine couldn't allocate its own state */ ENGINE_UNKNOWN_CALL = -3 /* engine_settle named no pending call */ @@ -60,8 +61,9 @@ enum { /* Or'd into engine_settle's state when the result didn't fit and the call took the host's rejection instead. */ #define ENGINE_UNFIT 16 -/* Or'd into engine_settle's state when the result never reached the script: the run had ended - before this settle, or the engine failed before it could deliver it. */ +/* Or'd into engine_settle's state when the call's promise never took the result, nor the host's + rejection: the run had been stopped or had failed before this settle, or the call that would + have handed it over was stopped or failed before it ran. Never with ENGINE_UNFIT. */ #define ENGINE_DROPPED 32 static JSRuntime *rt; @@ -314,7 +316,7 @@ static void run_jobs(void) { FinalizationRegistry callback passes its throw on, and so does a failure of the job machinery itself, or an uncatchable error. */ JSValue exc = JS_GetException(job_ctx); - if (interrupted) + if (interrupted || host_failed) JS_FreeValue(ctx, exc); else fail_with(exc, ENGINE_FAILED); @@ -324,12 +326,14 @@ static void run_jobs(void) { } static int state(void) { + /* What ended the run comes first: nothing records a failure once the guest is stopped or an + import has failed, so a failure came before either, and stays the answer after one. */ + if (failed) + return failure_state; if (interrupted) return ENGINE_INTERRUPTED; if (host_failed) return ENGINE_HOST_FAILED; - if (failed) - return failure_state; switch (JS_PromiseState(ctx, completion)) { case JS_PROMISE_FULFILLED: return ENGINE_FULFILLED; @@ -467,15 +471,23 @@ static int settle(uint32_t id, int ok, char **json, uint32_t json_len, char **un if (JS_IsException(fn)) { JS_FreeValue(ctx, value); fail_with(JS_GetException(ctx), ENGINE_FAILED); - return state() | ENGINE_DROPPED | (too_large ? ENGINE_UNFIT : 0); + return state() | ENGINE_DROPPED; } JSValue r = JS_Call(ctx, fn, JS_UNDEFINED, 1, (JSValueConst *)&value); JS_FreeValue(ctx, fn); JS_FreeValue(ctx, value); - if (JS_IsException(r)) - fail_with(JS_GetException(ctx), ENGINE_FAILED); - else - JS_FreeValue(ctx, r); + if (JS_IsException(r)) { + /* A promise's resolve and reject functions don't throw once they run (what reading a + result's `then` throws rejects the promise), so the call never ran: the interrupt check + on its way in stopped the guest, or there was no stack left to make it. */ + JSValue exc = JS_GetException(ctx); + if (interrupted || host_failed) + JS_FreeValue(ctx, exc); + else + fail_with(exc, ENGINE_FAILED); + return state() | ENGINE_DROPPED; + } + JS_FreeValue(ctx, r); run_jobs(); int st = state(); return too_large ? st | ENGINE_UNFIT : st; @@ -489,9 +501,11 @@ static int settle(uint32_t id, int ok, char **json, uint32_t json_len, char **un script, which an error at the call's await would hide. A result nested deeper than the binding's MAX_RESULT_DEPTH never gets here: under Node, a parse nested about 10,000 deep (5,000 with V8's optimizing tier alone) overflows the host's stack before QuickJS's own check sees the - recursion, and the binding refuses results well short of that. When the run had already ended, - or the engine fails before it can deliver the result, the state answered has ENGINE_DROPPED - set: the result never reached the script. + recursion, and the binding refuses results well short of that. When the run had been stopped + or had failed before this settle, or the call that hands the result over is stopped or fails + before it runs, the state answered has ENGINE_DROPPED set: the call's promise never took the + result. Once it has, the script's own code runs with it, a `then` getter the script put on + Object.prototype included, and a stop or failure in that code leaves the result handed over. json and unfit are the host's buffers from engine_alloc, each null or ending in a NUL at [len] as JS_ParseJSON requires, and this frees both, whatever it returns: the result's copy as soon @@ -629,14 +643,16 @@ static uint8_t *read_ending(int st) { JSON.stringify leaves out), no text. Kind 1: fulfilled, the value's JSON. Kind 2: an error, as JSON {name?, message?, stack?}: what the script threw, why its value couldn't be stringified, or the exception that ended the run. - Returns 0 in any other state, when it can't allocate, or when a stop lands while the ending is - read, which leaves none to give. The ending is fixed before it's read. The script's own code - that serializing runs (a toJSON, a getter), and the jobs it queues, which run before this - returns, can't reach the host; a reaction it attaches to a promise still pending runs when that - settles, as the script's other continuations do after its ending. A stop or a failure in those - jobs comes after the ending was read, so this returns the ending, and later states say what - came after. Data nested deeper than the host's stack allows can overflow it in - JS_JSONStringify, which throws out of the module: the binding reports that as a crash. */ + Returns 0 in any other state, when it can't allocate, or when a stop lands while the script's + own ending is read, which leaves none to give. The ending is fixed before it's read. The + script's own code that serializing runs (a toJSON, a getter), and the jobs it queues, which run + before this returns, can't reach the host; a reaction it attaches to a promise still pending + runs when that settles, as the script's other continuations do after its ending. A stop in + those jobs comes after the ending was read, so this returns the ending; a failure there ends + the run after its ending, which engine_state then answers. An exception that ended the run is + what ended it however its read goes, so a stop while it's read leaves out the parts the stop + cut off, and the rest is returned. Data nested deeper than the host's stack allows can overflow + it in JS_JSONStringify, which throws out of the module: the binding reports that as a crash. */ EXPORT("engine_result") uint8_t *engine_result(void) { int st = state(); @@ -644,7 +660,7 @@ uint8_t *engine_result(void) { return NULL; reading = true; uint8_t *buf = read_ending(st); - if (interrupted) { + if (interrupted && st != ENGINE_FAILED) { /* What was read is the stop's error, not the ending. */ limited_free(NULL, buf); buf = NULL; @@ -655,6 +671,14 @@ uint8_t *engine_result(void) { return buf; } +/* The run's state as engine_start and engine_settle answer it, for the host to read again after + engine_result: the jobs reading an ending queues run before that returns, and an exception + escaping them ends the run after its ending. */ +EXPORT("engine_state") +int engine_state(void) { + return started ? state() : ENGINE_MISUSE; +} + /* Buffers the host fills for engine_start and engine_settle. The host frees engine_start's after that call, and engine_result's once it has read it; engine_settle frees its own. They're held to the same limit as QuickJS's allocations: a result's copy and the value parsed from it share diff --git a/src/code-execution/engine.ts b/src/code-execution/engine.ts index 5ff94844f..112d2ccf4 100644 --- a/src/code-execution/engine.ts +++ b/src/code-execution/engine.ts @@ -64,10 +64,11 @@ export type RunState = */ | { kind: 'rejected'; name: string; message: string; stack?: string } /** - * Once the script was running, an exception escaped its promises and ended the run: a callback - * it gave `queueMicrotask` or a `FinalizationRegistry` threw, or one of QuickJS's own jobs - * failed, such as for want of memory to settle a promise. The fields are the exception's, as for - * `rejected`. Every later call answers the same, and no later result reaches the script. + * Once the script was running, an exception escaped its promises and ended the run, even after + * its own ending: a callback it gave `queueMicrotask` or a `FinalizationRegistry` threw, or a job + * failed in the engine itself; or the engine couldn't settle a call. The fields are the + * exception's, as for `rejected`, less any part a stop cut off while it was read. Every later + * call answers the same, a stop after it included, and no later result reaches the script. */ | { kind: 'failed'; name: string; message: string; stack?: string } /** `interrupt()` returned true while it ran. */ @@ -76,10 +77,12 @@ export type RunState = /** * What `settle` answers: the run's state; `unfit` when the guest couldn't take the result, for * want of memory to copy it in or of memory or stack to parse it, and the call took the child's - * own error instead; and `dropped` when the result never reached the script, because the run had - * already ended (`interrupted` or `failed`) or the engine failed before it could deliver it. - * Without `dropped`, the script got the result or the child's error, even if the run ended in the - * jobs that followed. + * own error instead; and `dropped` when the call never took either, because the run had been + * stopped or had failed before this settle (`interrupted` or `failed`), or the step that hands it + * over was stopped or failed before it ran. Without `dropped`, the call's promise took the result + * or the child's error, and the script's own code runs with it, a `then` getter it put on + * Object.prototype included: a stop or failure in that code, or in the jobs after, leaves it + * taken. The two are never both set. */ export type SettleState = RunState & { unfit?: true; dropped?: true }; @@ -101,7 +104,8 @@ export interface Engine { * * Once a call has answered the script's ending, later calls answer it again, unless something * came after it: a stop answers `interrupted`, a slot's failure throws, and an exception that - * escapes the script's promises answers `failed`. + * escapes the script's promises answers `failed`. One that escapes the jobs reading the ending + * queued is answered by the call that read it, in the ending's place. */ settle(callId: number, ok: boolean, json: string | Uint8Array, unfitJson: string): SettleState; /** Frees the engine's runtime, unless the module trapped, which leaves it as it is. Nothing may be called after it. */ @@ -135,19 +139,23 @@ export const MAX_MEMORY_BYTES = 2048 * 1024 * 1024; * first in each of the 31, with about two fifths of the host's stack to spare. Bun's stack is * larger, and the same limit holds there, so recursion through calls ends the same way on either. * - * Recursion in C that the check barely sees isn't held to it, and under Node crashes the run: a - * script's text nesting function expressions 300 deep or brackets 700 deep (the parser), a chain - * of 3,000 iterator helpers (each one's next calls the one before), and the script's own JSON - * nested 5,000 to 10,000 deep (see MAX_RESULT_DEPTH). + * Recursion in C that the check barely sees isn't held to it, and under Node crashes the run, the + * host's stack running out: a script's text nesting function expressions 300 deep or brackets 700 + * deep (the parser), a chain of 3,000 iterator helpers, and the script's own JSON nested about + * 10,000 deep (5,000 with V8's optimizing tier alone; see MAX_RESULT_DEPTH). Each iterator helper's + * next calls the one before directly, past QuickJS's checks, so under Bun, whose stack is larger, + * a chain of 30,000 runs the module's own 1 MiB stack out instead, and the module traps: the stack + * sits first in its memory, so an overflow wraps below address 0 to past the memory's end. */ export const MAX_STACK_BYTES = 192 * 1024; export const DEFAULT_STACK_BYTES = MAX_STACK_BYTES; /** * The deepest nesting of arrays and objects a result may have. QuickJS's stack check measures the * module's own stack, which the C recursion of JSON's parse and stringify barely uses, so under - * Node nesting 5,000 to 10,000 deep overflows the host's stack first, and the engine crashes - * (under Bun, QuickJS's check comes first). A deeper result is unfit, since the call took effect. - * A script's own data isn't held to this, and nested that deep, it crashes its own run under Node. + * Node nesting about 10,000 deep (5,000 with V8's optimizing tier alone) overflows the host's + * stack first, and the engine crashes (under Bun, QuickJS's check comes first). A deeper result + * is unfit, since the call took effect. A script's own data isn't held to this, and nested that + * deep, it crashes its own run under Node. */ export const MAX_RESULT_DEPTH = 2000; /** The most of the engine's own stdio a crash's message keeps. */ @@ -156,8 +164,9 @@ const DIAGNOSTICS_CHARS = 4096; * The longest ending text the binding decodes, in UTF-8 bytes: V8's longest string, 2^29 - 24 * characters (Node's `buffer.constants.MAX_STRING_LENGTH`), which a text of this many bytes can't * exceed. Node can't decode a longer one. Bun's longest string is 2^31 - 1, and the same bound - * holds there, so a script ends the same way on either. A longer text is answered as a rejection - * that gives its length. + * holds there, so a script ends the same way on either. A longer text is answered, as the state it + * belongs to (`rejected` for the script's own ending, `failed` for an exception that ended the + * run), with a RangeError that gives its length. */ const MAX_ENDING_BYTES = 2 ** 29 - 24; @@ -172,6 +181,7 @@ interface Exports { engine_start(prelude: number, preludeLen: number, script: number, scriptLen: number, filename: number): number; engine_settle(id: number, ok: number, json: number, jsonLen: number, unfit: number, unfitLen: number): number; engine_result(): number; + engine_state(): number; engine_alloc(size: number): number; engine_free(ptr: number): void; engine_dispose(): void; @@ -352,8 +362,9 @@ export async function loadEngine(limits: EngineLimits, slots: EngineSlots): Prom out = fn(); } catch (err) { if (err instanceof EngineError) throw err; - // A trap, or an exception in reading what the module returned: either way the module may - // have stopped partway, so dispose() leaves it as it is. + // A trap, or an exception in reading what the module returned, which it only throws when the + // module returned something it couldn't have made whole: either way the module can't be + // taken as whole, so dispose() leaves it as it is. crashed = true; // A slot that failed first is why: the guest ran on, refused, until the module trapped. if (slotFailed) return slotFailure(what); @@ -417,8 +428,8 @@ export async function loadEngine(limits: EngineLimits, slots: EngineSlots): Prom long: (n: number) => `what the script threw comes to ${n} bytes as text, more than the ${MAX_ENDING_BYTES} the engine reads back; throw something smaller`, }, [FAILED]: { - memory: 'out of memory while reading the exception that ended the run', - long: (n: number) => `the exception that ended the run comes to ${n} bytes as text, more than the ${MAX_ENDING_BYTES} the engine reads back`, + memory: 'out of memory while reading the exception that ended the run; throw something smaller, catch it in the callback that threw it, or let go of data the script no longer needs', + long: (n: number) => `the exception that ended the run comes to ${n} bytes as text, more than the ${MAX_ENDING_BYTES} the engine reads back; throw something smaller, or catch it in the callback that threw it`, }, } as const; /** @@ -449,11 +460,16 @@ export async function loadEngine(limits: EngineLimits, slots: EngineSlots): Prom if (known) return { ...known }; const r = result(); // Reading runs the interrupt slot, which can throw, or stop the guest. A stop that lands - // while the ending itself is read leaves none to give (the module returns nothing), so - // this call answers as any call does in which that happens. One that lands in the jobs - // the read queued comes after a whole ending, which this call answers. + // while the script's own ending is read leaves none to give (the module returns nothing), + // so this call answers as any call does in which that happens. One that lands in the jobs + // the read queued comes after a whole ending, which this call answers. An exception + // escaping those jobs ends the run after its ending, and is what this call answers. An + // exception that ended the run is read with the parts a stop cut off left out. if (slotFailed) return slotFailure(what); - if (!r && stopped) return { kind: 'interrupted' }; + if (code !== FAILED) { + if (ex.engine_state() === FAILED) return toState(FAILED, what); + if (!r && stopped) return { kind: 'interrupted' }; + } const kind = code === FAILED ? 'failed' : 'rejected'; const state: RunState = !r ? { kind, name: 'InternalError', message: unread[code].memory } : 'tooLong' in r ? { kind, name: 'RangeError', message: unread[code].long(r.tooLong) } diff --git a/src/code-execution/quickjs.wasm b/src/code-execution/quickjs.wasm index e41f6d1662398d1554cd6073a91e51173ab6f00f..c706a22842a1cff798dea6f834a93eba506656f0 100644 GIT binary patch delta 79637 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 delta 79749 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�Q9%a~^|j<2c0vv9jipdrtyI&r6cm`NX)8LGm`ch9O+ zNSH&<;`yFck9Gn{Y6n;*XO)xOjCjXCEi$#%-Sm+CL9nyEQXa2mls^3Ux%W`FR9dvP_d6v&lgp_0t5-qeo;N<#dwP3 zsI2(>sC2|$@rpC(0(uutT$xAhbJT6VQ$8d~M&jOgbAU)&=*Jv2urx~h*c168coX=A zi>Tot!(m1M<{1+(EyF1^62tqJnvPUiB`|kJH;J%B8afh!?I&DzE!7i4vFMlqRyjL4A8g7Xz29& zXxV58yQ8#yG@yQz{um8Fe@>w>uxU=vYhzTMH1z8Rxg^X38IWCZ_v0AV6kx747UuU= zdS$F?Sz?Vt4cOMjk*g?7-;D*RXNLph)XQG%F`D%fjGH4f#;f%(t2T_sr-h-CjLhKQ z(-VLa!-X?}!s#1naPgNsk-f_ zL(;Kf3bDY9*ZkZAb5Y1RveTj~!~1{d_@JgQs~S8YQG$yU3~1QP@G?!MyqDoux=8-X z!Zw>vR^7cv!m}nTq{>GoiYe-h_vi46scO16_DeW4f}dNV*azY?y**8(gdd%*^1a?) z!fjqv#e9gIzmwD_u_%Fv2oFjIuTsU=)KkT;Mg@wYpH(f42Y>K*1NT)S?Zp6BqS+!sk--dv!zd3T19ZmY33%{8WqMC=Wz2)C|HvwIQneJb+E zfzMB+Frofz!zWqaRo`2MvV=p6ZG%L@49;aOd&N^3X~yaAfD zLe-|nR;u{2juUh20xk^8eF-w?RM0Xvy0U z^owch+iD2N=%zLBGLEHrYw++GeY8fE^)2z!wZ^&>{fZm^EPuKmd2#5%0T4#DcOa^^ z&~5Lik9l*y@g}jL^&`Q5#dlSCR;2M1`N|%U=^6Q>gFE-V3k&dPTKBH1Qxv{bpM7yx zE@FRqZQ;*%)h%ec%~~<@9$u?%#TK}C)~Xte`y2v<0g5c<$QqEfkB@`ooi^|m`Q8J| zM-sL7uoY;QpB8LZsr1Twu-mp!?tAKia;QPXty2TwBwtEJ_`Lc1IHBe!ef7S&Gc=YJ zI-GETWDXA7kqf64;3b1&)FrGsN67KNyR;>&wnZO>Zpqn%X#YJ}3|_QOwMZN*#DMR3 zqO}wtYr=!FgHX?ptuRhmJr805t_s?wDGOO>J^)H?A?rgdUXP_NA3}v1 zNBuukGybhVm<}I%v=~?zWBK*)!5yXZ>($-Gfmy;eB>ez%*Ew|e2GF!L{xXOyuo2+g zLTxsxTT%cIAa`P#vwa&oQNPC;&I8hTU zYx;sQ@pN&sy1T@UF1hp~)U1_4fom7u<*HlNB(@D=AU!s1g`$K_pIhM) zTSb9w;7A8>4yDRO02qviYd>H7wLzcXzxS1X%~oY89lH|mCr&_ zS|DRP7-aC8I0;tS@y*$4MMjH#DYK7o`*(Ih-P=H?cBz|+F5@){`x|UB7ow}W8y?7I z)MqzX-h5iUTUEt*gXWZEMo~8PY_@JLa$&SufudsHXh z&dQrc5COBDV)m+su!=NbFQmZ&8nYL0yG+aWg72)R6MNxlS?f+qW_;Xavzx3SX#MHHI0!(4SeX4H56Tb`g3^YXXarEYXdF+T;O%~ zb5*VxrU2mQ{kx*iVleDDhWCT5oYfH73_frH<}1)gVoic%d;`BLD}ycf!y`3|9@&pk z&!-vtK@b~g|9(|F`5i3QL44+f2BrthB-`8IB7`IT9ZJQ1dAOe&eTKE|i}dAZkdRx* zdqAbuGcpTSHG4L>4ri)KEcSpnF}mtuGs zo>UlIly(sFynyV3(CTJU_k*fFs+xLGRgF98&F4d>Huhg6ri}+rE(Xy}4hg3MJ7w zVOtP}uz3WCbm$9MeWU5GFH~k6w_$%i$HAuT!|En?k2P$PW%SfxRjx!~XL)U28m~ur zZz9{wr-vb{f2NEtVbpG-sb6BCAJJQ1Vghq%|CcJI;%2{iMSQ_rC>J~u(?{85Qq6wl zeevynL?w?v{mP@(M}V}mX}}TeqMJ!|zf#`^_Oc!buU&ATpNc;TTc*iTm5`FhwJB7} zAoK%1l3`MVbc5Joe-vmpk={BA=;zXoqfke4DfSptmoSYwhWVUGJC3PC82|iyRV!(t zKRr&++Sfc3C#Wfh5EO+y_Cr1-$7PB-t~!+FU1tF&nP*X67KKsI2VM3(u13Ia`ti7` z5&NePo>JBmc@8p9AR^>Xdf|ks#fI2|8c}Jq|Ab1bfYVMs7P$%sEaJ-e^b>Or0D*0Y zez}oKpG5aJQ2mp@jX&x6lj=q0tE`n@JE`iGo2o&FoIw!V)_l#;b>s^XWOU;nct}KC6PLuxdgj3KvAqoT%@hvsDr?R+26t(3)64kswc|9`twPvg{(g( z6ETBUQM zkk<~6km?MSZi{Gg-?8(|gMZd=cY^Unq!{PlY4f!5Qx%(0148D=z{Y?89 z*O~V{CVLBIoKu-a(OePJ9Mb0H(B0=?Q!J-f&OuQ*MS16>)(htl_k`*U{_cR9$Vr}? z$ALh2;~P5W^hX$=%eo&>=P5_L~ z{(yy`9n|edb$f~P@KlTVuzw!yuz^iKs;O}E5Bwy`tiw{DpH!KcYpi+s>~z#L@h9+} zU9|Hjb!$;kOa@#CJ$0P|O$S1n!7FgL zKf~U6mD>G+HM1GC`WM6?gu}&tg}%g2mBGJZt#2P4`VEeW{S^1RdJdOyzpIw~0!a}BIY?#j@fYfu@t((l)x^K5;!nr?=_{qg(0@uokw!tAC3sH&=&B zzLA~~Dj6!-$|L_SG_&i5z)R=q;F<8+XpJOMb14|3tKrioF}kI9Yj}8!ws?G_19|`! z*HBY@xJaxX23%uf5FEg3ul!i%=(VD{6ZqKKqWWI%UTRxRzgP<%T__SU!fFwS#8L?s zW}@AwtxPX|XV{DW;IB<(h*kQdm@Zp%lb`(@QoRqfksj2oQ01dRUA_iNa(oO|hMhLZ z;T|xyQ%F2E{M6yvxaZ8t9|ZMKOjrHly1KQ`pH3GKfHVS>G!y)Q-s~u@Uq;K_O6Ui1*-%0cEP*DR&9yE@QL~b|D%$8*QYRJN5Bx&6IINr3 zQm2;$7)R3=B{d&lQ9oWUMZx3oXl^#8CFoRWu6BZc09>$OI(zmPS zoQCRwQo2?7DWdHA0B%26XDAw(PoXZetEYJI5PMp%h_IuSe$JIM64wF-0I_zF?kugF zC%U#XABlxYgCv%>U(l-3`abZaVu`vE9H#XXb$O6$k3`)SKDAYex&u0Tg)iSy`!c#k z)wj?SVSIc6S$+GdkQG9xB}*hf8fR8!XHvKfc)@WxTt@dsEq0QA+q)_JbCT}jEqh%G zHC_VpwgA&wY0|dReJMH~{_!Cxy0(8aV?wNN#wry*z2Y-LI*_6(V($J-(HTI<)Uvu^ z;Ue^8S-|!ydata03{96Wr`O`h!o%fseJGQ`RGp1InRcqa6|?zrs=lWL%LS+<_Ms1R zJ$-TTIGFU+RNXxNXRPMn@o`TZVcre;+DGw@MUM|jDi=jAbtFJQ+??L%IU8 z5QK4RSee2vgeN{#7!^^aV{1mStrjk#p15Du}>0VyfDvk3r91Ymdt`lB2 zMi%5@Jq$byVDO{y-#qByNx!kEEc@g2A`5Z@# zqV5QOu&T1|nR-~b98@pS85JKH>x|SBO2D}Vm8*c0PN8vCFz>TzZ54e_=vz+q;bra# zGUQkmSZG2$Zv8c@>U2KnnOiL�RVDdWm^B_7J-<&B(YzA%I~e`n0AhC~_BJXG}WI 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 { e.dispose(); }); +test('the script\'s code that QuickJS runs after a stop can\'t reach the host', async () => { + // A stop throws an error, and building its backtrace converts Error.stackTraceLimit and calls + // Error.prepareStackTrace: here a valueOf and a prepareStackTrace that try a call and output. + // The slot says stop once only, as the script spins. + let checks = 0; + const { e, rec } = await engine({ interrupt: () => ++checks === 3 }); + const hooks = `const attempt = (fn) => { try { fn() } catch {} }; + Error.stackTraceLimit = { valueOf() { attempt(() => __host("from the limit", "{}")); return 10; } }; + Error.prepareStackTrace = () => { attempt(() => __host("from the hook", "{}")); attempt(() => __output(1, "from the hook")); return ""; }; + for (;;) {}`; + assert.deepEqual(e.start(PRELUDE, hooks, 's.js'), { kind: 'interrupted' }); + assert.deepEqual([rec.calls, rec.output], [[], []]); + e.dispose(); +}); + +test('a stop that lands in the script\'s stack hook holds, though QuickJS puts back the error it was building', async () => { + // The slot's one stop lands in the hook, which counts to 3,000,000. QuickJS then throws away the + // hook's error and puts back the TypeError whose backtrace it was building, which the script + // catches and goes on from: its next check stops it, without asking the slot again, and its call + // in between is refused. (The refusal's error runs the hook again, which a stop that didn't hold + // would let count to the end.) + let checks = 0; + const { e, rec } = await engine({ interrupt: () => ++checks === 101 }); + const s = e.start(PRELUDE, `Error.prepareStackTrace = () => { for (let i = 0; i < 3e6; i++) {} }; + try { null.x } catch {} + try { __host("after the stop", "{}") } catch {} + for (let i = 0; i < 1e6; i++) {} + "went on"`, 's.js'); + assert.deepEqual(s, { kind: 'interrupted' }); + assert.equal(checks, 101, 'the slot was asked again after it said stop'); + assert.deepEqual(rec.calls, []); + e.dispose(); +}); + test('the interrupt reaches a script spinning in jobs, and later settles answer interrupted, their results dropped', async () => { let stop = false; const { e } = await engine({ interrupt: () => stop }, { memoryMaxBytes: 8 * MiB }); @@ -382,6 +416,8 @@ test('what a thrown object holds is read as text, and a part that can\'t be read // A message that can't be read is told by the object's JSON, and with no JSON, its string form. ['throw Object.defineProperty({ why: 1 }, "message", { get() { throw 1 } })', '', '{"why":1}', false], ['throw { big: 1n, toString() { return "told by its string form" } }', '', 'told by its string form', false], + // A message that's there but has no string form is left out, with no stand-in for it. + ['throw Object.assign(new Error(), { message: Symbol("m") })', 'Error', '', true], // A toJSON the script puts on Object.prototype doesn't reach the engine's own record of it. ['Object.prototype.toJSON = () => { throw new Error("not this") }; throw new RangeError("this")', 'RangeError', 'this', true], ] as const) { @@ -558,8 +594,8 @@ test('a stop while the exception that ended the run is read leaves the run faile test('a stop ends a failure\'s read: nothing after it is read, a part or a part\'s string form', async () => { // The interrupt slot says stop once only. The thrown object's message, read first, spins until // then; its name getter would give "made up" at once, and isn't run. In the second, the message - // is read, the name is an object whose toString would give "made up", and the stack getter - // spins until the stop, after which the name's string form isn't made. + // is read, and the name is an object whose toString spins until the stop: the name's string form + // isn't made, and the stack getter, which would give "late", isn't run. let checks = 0; const a = await engine({ interrupt: () => ++checks === 101 }); const s = a.e.start(PRELUDE, 'queueMicrotask(() => { throw { get message() { for (;;) {} }, get name() { return "made up" } } }); 1', 's.js'); @@ -568,12 +604,33 @@ test('a stop ends a failure\'s read: nothing after it is read, a part or a part\ a.e.dispose(); checks = 0; const b = await engine({ interrupt: () => ++checks > 100 }); - const t = b.e.start(PRELUDE, 'queueMicrotask(() => { throw { message: "m", name: { toString() { return "made up" } }, get stack() { for (;;) {} } } }); 1', 's.js'); + const t = b.e.start(PRELUDE, 'queueMicrotask(() => { throw { message: "m", name: { toString() { for (;;) {} } }, get stack() { return "late" } } }); 1', 's.js'); assert.deepEqual(t, { kind: 'failed', name: '', message: 'm' }); - assert.ok(checks > 100, 'the stop came while the stack was read'); + assert.ok(checks > 100, 'the stop came while the name was made a string'); b.e.dispose(); }); +test('each part read before a stop keeps its string form, a number\'s or an object\'s', async () => { + // The slot says stop once only, as the name getter spins. The message, read first, isn't a + // string: its string form is made before the name is read. + for (const [message, made] of [['42', '42'], ['{ toString() { return "made" } }', 'made']] as const) { + let checks = 0; + const { e } = await engine({ interrupt: () => ++checks === 101 }); + const s = e.start(PRELUDE, `queueMicrotask(() => { throw { message: ${message}, get name() { for (;;) {} } } }); 1`, 's.js'); + assert.deepEqual(s, { kind: 'failed', name: '', message: made }, message); + assert.equal(checks, 101); + e.dispose(); + } +}); + +test('a stack is left out once a stop has come, though it\'s plain data', async () => { + let checks = 0; + const { e } = await engine({ interrupt: () => ++checks === 101 }); + const s = e.start(PRELUDE, 'queueMicrotask(() => { throw { get message() { for (;;) {} }, stack: "late" } }); 1', 's.js'); + assert.deepEqual(s, { kind: 'failed', name: '', message: '' }); + e.dispose(); +}); + test('once a stop has come, a failure with no message gets no stand-in for one', async () => { // Each thrown object has no message. Without the stop, the engine would tell it by its JSON or // its string form; here the first a non-enumerable name getter ends at the stop (its JSON would From d8e8971d8a96d85f5287a54fd3281949e797b20c Mon Sep 17 00:00:00 2001 From: Imogen-2490 Date: Sun, 18 Oct 2026 20:30:15 -0400 Subject: [PATCH 28/41] feat(tools): ToolResult.refused says the dispatcher refused a call before it opened `refused: true` means the dispatcher refused the call before it opened, as RFC-007's lifecycle refuses one, so nothing ran and the call certainly had no effect. It is set at the five places the dispatcher refuses: - the agent's allowedTools; - a refused skip_reply wake; - an MCPL server that isn't there or is still connecting; - the host's MCPL tool policy for that server; - a fork reaching outside its channel. Each sets the field and RFC-007's mark from one helper, refusedResult(), so the two can't disagree. A check made once the call has opened is a failure whose text says nothing was done, not a refusal, since observers have seen the call open: the module registry's malformed name, module not found and schema check, `utils run`'s, each module's answer for a tool it doesn't have, and an MCPL request that couldn't be written. So is a check off the dispatcher, where nothing opens the call: executeMcplToolCall's, the allowedTools check of a programmatic call (executeToolCall, ModuleContext.callTool), which traces its failure, and that check when `utils run` makes it. The texts that didn't say nothing was done, or that the call didn't run, now do. A module's own result can't claim `refused` on any route: the registry drops the field from the copy it stores, and an in-process caller gets the module's own result unless it claims one. Then it gets a copy less the claim: every field ToolResult declares, read by name, so a result class's getters count, and every other own enumerable field, string or symbol, as a spread takes them. A result that can't be read, its claim included, is a failure saying so, as on the stored route, keeping the turn's end and the module's error when those could be read, each field read once, so a getter that threw isn't asked again: the call completed. Tests: mcpl-tool-resolution tells a refusal from a failure on the model's, executeToolCall's and a module's path, and module-tool-args, agent-allowed-tools, utils-surface and history-module check that the checks made after the call opened answer with failures, with no `refused`. Co-Authored-By: Claude Opus 5.5 --- changelog.d/tool-result-refused.added.md | 1 + src/framework.ts | 66 +++++++++++------ src/module-registry.ts | 94 ++++++++++++++++++++++-- src/modules/unknown-tool.ts | 5 +- src/modules/workspace/index.ts | 2 +- src/types/events.ts | 14 ++++ test/agent-allowed-tools.test.ts | 2 + test/history-module.test.ts | 1 + test/mcpl-tool-resolution.test.ts | 26 ++++--- test/module-tool-args.test.ts | 2 + test/utils-surface.test.ts | 5 +- 11 files changed, 178 insertions(+), 40 deletions(-) create mode 100644 changelog.d/tool-result-refused.added.md diff --git a/changelog.d/tool-result-refused.added.md b/changelog.d/tool-result-refused.added.md new file mode 100644 index 000000000..dba5427b3 --- /dev/null +++ b/changelog.d/tool-result-refused.added.md @@ -0,0 +1 @@ +- **`ToolResult.refused`:** `true` when the dispatcher refused the call before it opened, as RFC-007's lifecycle refuses one, so nothing ran and the call certainly had no effect: the agent may not use the tool (its `allowedTools`), a `skip_reply` wake was refused, the call's MCPL server is absent or still connecting, the host's policy for that server denies the tool, or a fork reached outside its channel. The lifecycle refuses the call from the same place, so observers see no events for it. A check made after the call opened is a failure whose text says nothing was done, and a module's result can't claim `refused`. diff --git a/src/framework.ts b/src/framework.ts index 72c570fe5..010d92a59 100644 --- a/src/framework.ts +++ b/src/framework.ts @@ -19117,8 +19117,13 @@ export class AgentFramework { private async executeToolCallFrom(call: ToolCall, origin: ChannelToolOrigin): Promise { if (origin.kind === 'agent') { - const refusal = this.agentToolPermissionFailure(origin.agentName, call); - if (refusal) return refusal; + // A programmatic call, off the dispatcher: no RFC-007 call opened, so a + // denial here is a failure. It's this call's end, so it's traced here. + const failure = this.agentToolPermissionFailure(origin.agentName, call, false); + if (failure) { + this.emitTrace({ type: 'tool:failed', module: 'framework', tool: call.name, callId: call.id, error: failure.error ?? '' }); + return failure; + } } if (isPresentationTool(call.name)) return this.editToolPresentation(call.callerAgentName ?? '__ephemeral__', call); @@ -19166,14 +19171,15 @@ export class AgentFramework { } // Module tools. The caller uses the result in process, so it gets the - // module's own result, as the module returned it. + // module's own result, as the module returned it, less a refusal it + // claims, which is the dispatcher's to say. return this.moduleRegistry.handleToolCall(call); } /** A programmatic call of an MCPL tool: `serverId` and `prefix` as resolveMcplTool matched them. */ private async executeMcplToolCall(call: ToolCall, serverId: string, prefix: string): Promise { if (!this.mcplServerRegistry) { - return { success: false, error: 'MCPL not initialized', isError: true }; + return { success: false, error: 'MCPL not initialized, so the call didn\'t run', isError: true }; } const modern = this.modernServers().get(serverId) ?? null; const server = modern ? null : this.mcplServerRegistry.getServer(serverId); @@ -20685,16 +20691,29 @@ export class AgentFramework { private agentToolPermissionFailure(agentName: string, call: ToolCall, reportRefusal = true): ToolResult | undefined { if (this.canAgentUseTool(agentName, call.name)) return undefined; - const error = !this.agents.has(agentName) && this.usedEphemeralAgentNames.has(agentName) - ? `Caller '${agentName}' is not a running agent; framework-created agents have tool authority only while registered.` - : `Tool '${call.name}' is not permitted by this agent's allowedTools.`; - if (reportRefusal) { - this.toolLifecycleEmitter?.refuse(agentName, call.id); - this.emitTrace({ - type: 'tool:failed', module: 'framework', tool: call.name, callId: call.id, error, - }); - } - return { success: false, error, isError: true }; + const why = !this.agents.has(agentName) && this.usedEphemeralAgentNames.has(agentName) + ? `Caller '${agentName}' is not a running agent; framework-created agents have tool authority only while registered` + : `Tool '${call.name}' is not permitted by this agent's allowedTools`; + // Off the dispatcher, nothing opened the call to refuse it in RFC-007's + // lifecycle, so it's a failure, not a refusal (ToolResult.refused), whose + // text says the call didn't run. + if (!reportRefusal) return { success: false, error: `${why}, so the call didn't run.`, isError: true }; + const error = `${why}.`; + const refusal = this.refusedResult(agentName, call.id, error); + this.emitTrace({ + type: 'tool:failed', module: 'framework', tool: call.name, callId: call.id, error, + }); + return refusal; + } + + /** + * A dispatched call answered without running its tool. RFC-007's lifecycle + * forgets it, and its result says it was refused (`ToolResult.refused`), both + * from here, so the two can't disagree. + */ + private refusedResult(agentName: string, callId: string, error: string): ToolResult { + this.toolLifecycleEmitter?.refuse(agentName, callId); + return { success: false, error, isError: true, refused: true }; } /** @@ -20873,9 +20892,9 @@ export class AgentFramework { if (error !== undefined) { const wakeValue = (enrichedCall.input as { wake_in_seconds?: unknown } | null | undefined)?.wake_in_seconds; console.error(`[self-wake] agent=${agentName} wake_in_seconds=${logValue(wakeValue)} skip_reply refused: ${logDetail(this.eventGate ? "wake_in_seconds isn't a number of seconds" : 'No EventGate configured (FrameworkConfig.gate is unset)')}`); - this.toolLifecycleEmitter?.refuse(agentName, enrichedCall.id); + const result = this.refusedResult(agentName, enrichedCall.id, error); this.emitTrace({ type: 'tool:failed', module: 'channels', tool: enrichedCall.name, callId: enrichedCall.id, error }); - this.pushEvent({ type: 'tool-result', callId: enrichedCall.id, toolName: enrichedCall.name, agentName, moduleName: 'channels', result: { success: false, error, isError: true } }); + this.pushEvent({ type: 'tool-result', callId: enrichedCall.id, toolName: enrichedCall.name, agentName, moduleName: 'channels', result }); return; } const wake = readSkipReplyWake(enrichedCall.input); @@ -25724,21 +25743,20 @@ export class AgentFramework { if ((!modern && !server) || this.modernStillConnecting(modern)) { // RFC-007: refused before executing — no lifecycle events. - this.toolLifecycleEmitter?.refuse(agentName, call.id); this.pushEvent({ type: 'tool-result', callId: call.id, toolName: call.name, agentName, moduleName: `mcpl:${serverId}`, - result: { success: false, error: this.mcplServerAbsentText(serverId, call.name), isError: true }, + result: this.refusedResult(agentName, call.id, this.mcplServerAbsentText(serverId, call.name)), }); return; } const config = this.mcplServerConfigs.get(serverId); if (!isToolAllowed(toolName, config)) { - this.toolLifecycleEmitter?.refuse(agentName, call.id); + const result = this.refusedResult(agentName, call.id, mcplToolDeniedText(serverId, call.name)); this.emitTrace({ type: 'tool:failed', module: `mcpl:${serverId}`, tool: toolName, callId: call.id, error: 'denied by tool policy' }); this.pushEvent({ type: 'tool-result', @@ -25746,7 +25764,7 @@ export class AgentFramework { toolName: call.name, agentName, moduleName: `mcpl:${serverId}`, - result: { success: false, error: mcplToolDeniedText(serverId, call.name), isError: true }, + result, }); return; } @@ -25768,6 +25786,8 @@ export class AgentFramework { this.emitTrace({ type: 'tool:failed', module: `mcpl:${serverId}`, tool: toolName, callId: call.id, error: err.message, stack: err.stack }); // RFC-007: transport error, closed connection or timeout — no result. this.toolLifecycleEmitter?.markDispatchFailure(agentName, call.id); + // A request that couldn't be written is a failure, not a refusal: the + // call had opened (RFC-007), and its error says it wasn't sent. this.pushEvent({ type: 'tool-result', callId: call.id, @@ -26085,7 +26105,7 @@ export class AgentFramework { if (home) { const reject = (error: string): void => { // RFC-007: the guard refuses before the tool runs — no lifecycle events. - this.toolLifecycleEmitter?.refuse(agentName, call.id); + const result = this.refusedResult(agentName, call.id, error); this.emitTrace({ type: 'tool:failed', module: 'channels', tool: call.name, callId: call.id, error: `conversation agent ${agentName}: ${error}`, @@ -26096,7 +26116,7 @@ export class AgentFramework { toolName: call.name, agentName, moduleName: 'channels', - result: { success: false, error, isError: true }, + result, }); }; @@ -27072,7 +27092,7 @@ export class AgentFramework { case 'run': { const def = allUtilities.find((u) => u.name === input.name); if (!def) { - return { success: false, isError: true, error: `No utility "${String(input.name)}". ${available()}` }; + return { success: false, isError: true, error: `No utility "${String(input.name)}", so nothing was done. ${available()}` }; } if (!options.trustedModule) { // The allowed outer utils call owns its lifecycle and terminal trace; diff --git a/src/module-registry.ts b/src/module-registry.ts index 6218a7ec1..daba429ca 100644 --- a/src/module-registry.ts +++ b/src/module-registry.ts @@ -310,7 +310,8 @@ export class ModuleRegistry { * Handle a tool call by routing to the appropriate module: the module's own * result, unchecked, as it returned it, for a caller that uses the result * in process (`ModuleContext.callTool`, `executeToolCall`, or a host - * calling the registry). A module's throw is answered as an error result. + * calling the registry), less a refusal it claims (withoutRefusal). A + * module's throw is answered as an error result. * A call whose input isn't an object, or lacks a key the tool's schema * requires, is answered before the module runs, with an error result that * opens "Nothing was done:" and names what to send (toolArgsProblems). @@ -324,7 +325,7 @@ export class ModuleRegistry { if (sepIndex === -1) { return { success: false, - error: `Invalid tool name format: ${call.name}`, + error: `Invalid tool name format: ${call.name}, so nothing was done`, isError: true, }; } @@ -336,7 +337,7 @@ export class ModuleRegistry { if (!module) { return { success: false, - error: `Module not found: ${moduleName}`, + error: `Module not found: ${moduleName}, so nothing was done`, isError: true, }; } @@ -367,7 +368,9 @@ export class ModuleRegistry { isError: true, }; } - return await module.handleToolCall(moduleCall); + // A refusal is the dispatcher's to say (RFC-007), so a module's result + // can't claim one on this route either. + return withoutRefusal(call, await module.handleToolCall(moduleCall)); } catch (error) { return thrownResult(error); } @@ -895,6 +898,83 @@ class ProcessStateImpl implements ProcessState { } } +/** The fields ToolResult declares, read by name, so a result class's getters count: endTurn first and the error next, so a failure keeps them. */ +const TOOL_RESULT_FIELDS = ['endTurn', 'error', 'success', 'isError', 'data', 'structured', 'keepWhenWithheld'] as const; + +/** + * A module's result for an in-process caller: the module's own object, unless + * it claims a refusal, which is the dispatcher's to say (RFC-007). Then it's a + * copy, less the claim: a plain object of every field ToolResult declares, + * read by name, so a result class's getters count, each read once, and every + * other own enumerable field, string or symbol, as a spread takes them, so + * non-enumerable own fields and inherited undeclared ones are left out. A + * result that can't be read, its claim included, is a failure saying so, as + * on the stored route (checkedToolResult), + * keeping the turn's end and the module's error if those could be read: the + * module's call completed, and the claim never reaches the caller. + */ +function withoutRefusal(call: ToolCall, result: ToolResult): ToolResult { + // Not an object: nothing to claim, and the caller gets it as it came. + if (!result || (typeof result !== 'object' && typeof result !== 'function')) return result; + const source = result as unknown as Record; + const copy: Record = {}; + // Each field is read once: one whose getter threw isn't asked again. + const read = new Set(); + // Defined, not assigned, so a field named __proto__ is a field. + const keep = (key: PropertyKey): void => { + read.add(key); + Object.defineProperty(copy, key, { value: source[key], enumerable: true, writable: true, configurable: true }); + }; + const once = (key: string): unknown => { + if (key in copy) return copy[key]; + if (read.has(key)) return undefined; + return source[key]; + }; + // A failure keeps what could be read of the turn's end and the module's + // error, each read on its own, whatever else couldn't be. + const unreadable = (error: unknown): ToolResult => { + let endTurn: unknown; + let own: string | undefined; + try { + endTurn = once('endTurn'); + } catch { + // Not readable: the failure doesn't end the turn. + } + try { + own = errorText(once('error')); + } catch { + // An error whose own text can't be read: the failure says only why. + } + return { + success: false, + isError: true, + error: `Tool '${call.name}' returned a result that couldn't be read (${thrownText(error).split('\n')[0] || 'no reason given'}). ` + + `The call itself completed${own ? `, with this error: ${own}` : '.'}`, + ...(endTurn ? { endTurn: true } : {}), + }; + }; + let claim: unknown; + try { + claim = source.refused; + } catch (error) { + return unreadable(error); + } + if (!claim) return result; + try { + const copied = new Set(['refused']); + for (const key of TOOL_RESULT_FIELDS) { + copied.add(key); + if (key in source) keep(key); + } + for (const key of Reflect.ownKeys(source)) { + if (!copied.has(key) && Object.prototype.propertyIsEnumerable.call(source, key)) keep(key); + } + return copy as unknown as ToolResult; + } catch (error) { + return unreadable(error); + } +} + /** A result's flags, which consumers read for their truth: booleans here. */ const RESULT_FLAGS = ['success', 'isError', 'endTurn'] as const; @@ -916,7 +996,8 @@ const RESULT_FLAGS = ['success', 'isError', 'endTurn'] as const; * runs again. It's taken where the framework stores or sends a result * ({@link ModuleRegistry.storableToolCall}). A caller that uses the result * in process (`ModuleContext.callTool`, `executeToolCall`) gets the module's - * own result, as the module returned it ({@link ModuleRegistry.handleToolCall}). + * own result, as the module returned it, less a refusal it claims + * ({@link ModuleRegistry.handleToolCall}). * The flags are booleans, read for their truth as consumers read them. * `error` is its text form, which is all a consumer reads of it, so an error * value JSON can't hold costs nothing. An error that says nothing (null, @@ -972,6 +1053,9 @@ export function checkedToolResult(call: ToolCall, result: unknown): { result: To } // The error is kept as its text, which consumers read; its value isn't. delete fields.error; + // A refusal is the dispatcher's to say, before the call opens (RFC-007): + // a module answers after that, so its result can't claim one. + delete fields.refused; // The flags are kept as their truth, which is all consumers read of them, // before anything is serialized, so a flag's own value (a BigInt, a // function, an object with a toJSON) neither breaks nor runs anything. diff --git a/src/modules/unknown-tool.ts b/src/modules/unknown-tool.ts index 2e8f5dd32..e9b18ce48 100644 --- a/src/modules/unknown-tool.ts +++ b/src/modules/unknown-tool.ts @@ -5,7 +5,10 @@ import type { ToolDefinition } from '../types/events.js'; * tool, so nothing ran, and which tools the module has, read from its own * getTools() so the list can't drift. The registry hands every `module--name` * to its module, even one the module doesn't declare, since some modules route - * undeclared names on purpose, so this answer is the module's to give. + * undeclared names on purpose, so this answer is the module's to give. On the + * dispatcher's path it comes after the call has opened (RFC-007), and an + * in-process route opens none, so either way its result is a failure whose + * text says nothing ran, not a refusal (ToolResult.refused). */ export function unknownToolMessage(module: string, name: string, tools: readonly Pick[]): string { const missing = `There's no tool "${module}--${name}", so nothing ran.`; diff --git a/src/modules/workspace/index.ts b/src/modules/workspace/index.ts index 99d8448d1..0dfd65b35 100644 --- a/src/modules/workspace/index.ts +++ b/src/modules/workspace/index.ts @@ -1800,7 +1800,7 @@ export class WorkspaceModule implements Module { case 'materialize': return await this.handleMaterialize(input as unknown as MaterializeInput); case 'sync': return await this.handleSync(input as unknown as SyncInput); default: - return { success: false, error: `Unknown tool: ${call.name}`, isError: true }; + return { success: false, error: `Unknown tool: ${call.name}, so nothing was done`, isError: true }; } } catch (err) { return { success: false, error: err instanceof Error ? err.message : String(err), isError: true }; diff --git a/src/types/events.ts b/src/types/events.ts index 1c32e9fd0..74ae99174 100644 --- a/src/types/events.ts +++ b/src/types/events.ts @@ -192,6 +192,20 @@ export interface ToolResult { error?: string; /** Whether this was an error (for LLM) */ isError?: boolean; + /** + * The dispatcher refused the call before it opened, RFC-007's refusal, so + * nothing ran and the call certainly had no effect. Only the dispatcher's + * path, the model's calls and a script's, sets it: the caller may not use + * the tool (allowedTools), a `skip_reply` wake was refused, the call's MCPL + * server is absent or still connecting, the host's policy for that server + * denies the tool, or a fork reached outside its channel. RFC-007's + * lifecycle refuses the call from the same place, so observers see no + * events for it. A check made after the call opened, such as a module's + * answer for a name it has no tool for or arguments that don't fit a + * schema, is a failure whose text says nothing was done, as is an MCPL + * request that couldn't be written. + */ + refused?: boolean; /** * The result's machine-readable form, beside `data`. Programs read it: a * script gets it in its result object, and a host in the tool result diff --git a/test/agent-allowed-tools.test.ts b/test/agent-allowed-tools.test.ts index f352dd11a..af24fed80 100644 --- a/test/agent-allowed-tools.test.ts +++ b/test/agent-allowed-tools.test.ts @@ -486,12 +486,14 @@ for (const route of ['model', 'public', 'script'] as const) { } else if (route === 'public') { const result = await h.framework.executeToolCall({ id: 'utility-denied', name: 'utils', input, callerAgentName: 'assistant' }); assert.equal(result.isError, true); assert.match(result.error!, /not permitted.*allowedTools/); + assert.equal(result.refused, undefined, 'its utils call has opened, so the utility\'s refusal is a failure'); } else { const pending = h.internal.dispatchScriptToolCall('assistant', 'utils', input); const processing = setInterval(() => { void h.framework.runUntilIdle(); }, 5); try { const result = await pending; assert.equal(result.isError, true); assert.match(result.error!, /not permitted.*allowedTools/); + assert.equal(result.refused, undefined, 'its utils call has opened, so the utility\'s refusal is a failure'); } finally { clearInterval(processing); } } assert.deepEqual(h.effects, []); diff --git a/test/history-module.test.ts b/test/history-module.test.ts index 0481c8887..2d242a897 100644 --- a/test/history-module.test.ts +++ b/test/history-module.test.ts @@ -726,5 +726,6 @@ describe('HistoryModule', () => { assert.equal(result.success, false); assert.equal(result.isError, true); assert.match(result.error ?? '', /There's no tool "history--bogus"/); + assert.equal(result.refused, undefined); }); }); diff --git a/test/mcpl-tool-resolution.test.ts b/test/mcpl-tool-resolution.test.ts index c61ad02f8..93ebad8b7 100644 --- a/test/mcpl-tool-resolution.test.ts +++ b/test/mcpl-tool-resolution.test.ts @@ -113,7 +113,13 @@ async function standIns(servers: Server[], agent: Record = {}, push(event); }; - /** Call `name` by one path: the stand-in it reached, as `:`, or how it was answered without one. */ + /** + * Call `name` by one path: the stand-in it reached, as `:`, or how + * it was answered without one: `refused` when the dispatcher refused it + * before it opened (ToolResult.refused), as the model's path does and a + * script's call too, and `failed` for any other failure, a programmatic + * path's answer for the same reason included. + */ const call = async (path: Path, name: string): Promise => { const count = reached.length; const c: ToolCall = { id: `call-${path}-${name}`, name, input: {} }; @@ -124,13 +130,13 @@ async function standIns(servers: Server[], agent: Record = {}, if (reached.length > count) return reached.at(-1)!; await waitFor(() => answers.has(c.id), `the model's answer to ${name}`); const answer = answers.get(c.id)!; - return answer.success ? `answered: ${String(answer.data)}` : `refused: ${answer.error}`; + return answer.success ? `answered: ${String(answer.data)}` : `${answer.refused ? 'refused' : 'failed'}: ${answer.error}`; } const result = path === 'executeToolCall' ? await h.fw.executeToolCall({ ...c, callerAgentName: 'assistant' }) : await h.module.ctx!.callTool(c); if (reached.length > count) return reached.at(-1)!; - return result.success ? `answered: ${String(result.data)}` : `refused: ${result.error}`; + return result.success ? `answered: ${String(result.data)}` : `${result.refused ? 'refused' : 'failed'}: ${result.error}`; }; return { @@ -207,12 +213,12 @@ test("a tool its own server's policy denies isn't in that server's listing, so e } }); -test("a call of a tool its server's policy denies is refused on every path, not sent", async () => { +test("a call of a tool its server's policy denies is sent on no path: the model's is refused, the others fail", async () => { // Left out of the listing, `foo--secret` resolves to the longest prefix, `foo`'s, whose policy refuses it. const h = await standIns([{ id: 'short', prefix: 'foo', lists: ['secret', 'echo'], disabledTools: ['secret'] }]); try { for (const path of PATHS) { - assert.equal(await h.call(path, 'foo--secret'), `refused: foo--secret isn't allowed by this host's policy for the "short" server, ` + + assert.equal(await h.call(path, 'foo--secret'), `${path === 'model' ? 'refused' : 'failed'}: foo--secret isn't allowed by this host's policy for the "short" server, ` + "so the call didn't run, and calling it again won't change that. If you need it, ask whoever runs this host.", `by ${path}`); } assert.equal(await h.call('executeToolCall', 'foo--echo'), 'short:echo', 'an allowed tool is sent'); @@ -238,11 +244,13 @@ test('a programmatic call sends the tool name the model\'s call sends, though th test('a name an MCPL prefix fits goes to MCPL on every path, even with no config for its server', async () => { const h = await standIns([{ id: 'short', prefix: 'foo', lists: ['echo'] }]); try { - // A prefix with neither a config nor a server: refused as MCPL, never handed to a module. + // A prefix with neither a config nor a server: answered as MCPL, never handed to a module. h.internals.mcplPrefixMap.set('ghost', 'ghost'); - // Every path says the same: the server isn't connected, and won't be on its own. + // Every path's text says the same: the server isn't connected, and won't be + // on its own. The model's call is refused; the others, which open no + // RFC-007 call, fail. for (const path of ['executeToolCall', 'ctx.callTool', 'model'] as const) { - assert.equal(await h.call(path, 'ghost--echo'), `refused: The "ghost" server that provides ghost--echo isn't connected, ` + + assert.equal(await h.call(path, 'ghost--echo'), `${path === 'model' ? 'refused' : 'failed'}: The "ghost" server that provides ghost--echo isn't connected, ` + "and this host won't reconnect to it on its own, so the call didn't run. If you need it, ask whoever runs this host.", `by ${path}`); } // A server with no config is still sent the call. @@ -262,7 +270,7 @@ test('a name no MCPL prefix fits falls through to the module registry on the pro for (const path of ['executeToolCall', 'ctx.callTool'] as const) { assert.equal(await h.call(path, 'ctx-capture--ping'), 'answered: module:ping', `a module's tool, by ${path}`); // `foo` is the start of `foobar`, but a prefix takes the separator too. - assert.equal(await h.call(path, 'foobar--echo'), 'refused: Module not found: foobar', `foobar--echo, by ${path}`); + assert.equal(await h.call(path, 'foobar--echo'), 'failed: Module not found: foobar, so nothing was done', `foobar--echo, by ${path}`); } assert.deepEqual(h.reached, [], 'no server was reached'); } finally { diff --git a/test/module-tool-args.test.ts b/test/module-tool-args.test.ts index adb825b16..f88191720 100644 --- a/test/module-tool-args.test.ts +++ b/test/module-tool-args.test.ts @@ -162,6 +162,8 @@ describe('a module tool\'s arguments are checked before the module runs', () => await withFramework(async (framework, probe) => { const result = await call(framework, {}); assert.equal(result.isError, true); + // Checked once the call has opened: a failure that says nothing was done, not a refusal. + assert.equal(result.refused, undefined); assert.equal(result.error, 'Nothing was done: probe--echo needs "path" (a string), and this call gave none.'); assert.equal(probe.calls.length, 0, 'the handler never saw the call'); }); diff --git a/test/utils-surface.test.ts b/test/utils-surface.test.ts index 7141592d9..2cab3e396 100644 --- a/test/utils-surface.test.ts +++ b/test/utils-surface.test.ts @@ -163,10 +163,13 @@ describe('utils surface', () => { // The utility's input is the run's `args`, so the refusal names them. const notObject = await call({ action: 'run', name: 'stub--rare_op', args: 'soft' }); assert.match(notObject.error!, /^Nothing was done: stub--rare_op its args must be an object, and this call gave a string\./); + // Checks made after the call opened are failures, not the dispatcher's refusals. + assert.equal(notObject.refused, undefined); assert.equal(stub.calls.length, 0, 'handler never reached on a validation miss'); const unknown = await call({ action: 'run', name: 'stub--nope', args: {} }); - assert.match(unknown.error!, /No utility "stub--nope"\. Available: stub--rare_op, stub--other_op/); + assert.match(unknown.error!, /No utility "stub--nope", so nothing was done\. Available: stub--rare_op, stub--other_op/); + assert.equal(unknown.refused, undefined); const badAction = await call({ action: 'zap' }); assert.match(badAction.error!, /action must be "list", "describe", or "run"/); From 987f63b4b5d76eaed023691a9db434e13bdc3b21 Mon Sep 17 00:00:00 2001 From: Imogen-2490 Date: Sun, 18 Oct 2026 20:31:08 -0400 Subject: [PATCH 29/41] feat(code-execution): the isolate, a script's own process on the engine and the parent's side of it `src/code-execution/isolate/` runs one script in a child process of its own, on the engine, and keeps the parent's side of it. Nothing in the framework uses it yet. - The child (child-main.ts, child.ts, child-entry.ts) is spawned on the parent's own runtime with an empty environment, in a directory of its own, and from an entry that exists (CHILD_ENTRY), so that Bun loads no .env into it and runs the file as it is. The parent's frames come in on stdin, and the child's go out on fd 3 with writeSync. It fills the engine's three imports: a tool call goes to the parent, the interrupt looks for the stop file at most every 3 ms, and output is flushed in bounded batches and before each call. It refuses a wake's payload past its cap, then a wake past the script's cap, and sends a wake at once, even while a result settles. Other calls made while a result settles wait for its seen, at most 16 of them: a guest that makes more has the rest sent at once, which the parent crashes it for. An answer in a result's place that the guest can't take keeps its name and its message's start. A script that had returned or thrown when a stop came ends as it did, but an exception that escapes the script's promises once it runs (the engine's `failed`) ends the run as failed at once, past an ending kept. Every failed or rejected ending's stack frames go apart from its name and message, the script's own, numbered as written (scriptFrames): a failure's stack holds frames only, QuickJS's or one the script set, and the lines kept are those shaped as QuickJS writes the script's frames. A result the engine never delivered (`dropped`) gets no seen: the script never had it, and what script code QuickJS still runs as a stop lands can't reach the parent, since the engine refuses its calls once stopped; the run's end names it as unread. The child ends its process once its parent, whose pid its start names, has gone, rather than stop its script, since a script can keep QuickJS from taking a stop (one QuickJS takes inside a stack hook or an iterator's return is dropped) and only the parent kills a child that runs on, and encodes its frames against the parent's cap, which protocol.ts holds for both. - The prelude (prelude.ts), adapted from @earendil-works/pi-codemode under its MIT notice: tools by exact name with a close-match error, ALL_TOOLS with the input schemas parsed from JSON, the output helpers, error formatting for what the script logs, which keeps only its own frames by the child's rule and patterns, wake_agent(), and the stall check. The script runs as an async function body, and its value goes out as JSON text, serialized once; a value JSON can't hold comes back saying so, and a throw that isn't an Error fails the script with the thrown value's JSON or string form as its message. At most 16 of its calls are out at once, and one past them waits in the script. - frames.ts and protocol.ts: length-prefixed frames, binary for a result, a strict check of every frame from the child, which is untrusted, with at most 32 values counted before it's parsed, and the caps on a call's arguments (4 MiB of JSON holding 100,000 values) and a wake's payload (64 KiB), which the child checks before it sends. A result frame is written into one buffer, and the decoder copies each frame's body into one buffer of the length its header names, keeping no chunk. A failed ending's error carries the script's stack frames apart from its name and message. - runner.ts: one run per script, with its calls and their timeouts, results held as frames and written one at a time after the child's stdin drains, a result too large for the script refused before it's sent, the stop and its grace, and an ending decided once the process has exited and fd 3 and stderr have ended. A child that breaks the protocol crashes its run, saying how: a call to a tool it wasn't given, more than 16 out at once, an id used again before its answer was seen, a seen for an answer not yet written, more wakes than the cap, arguments or a wake past their caps, output past the child's batch, a frame cut off (unless a stop's kill cut it), a frame longer than a call frame needs, or one holding more values than the protocol's (a byte-order mark is no JSON). The host holds at most 16 of a script's calls, timed-out ones included, counted by call rather than by the id the child names; one past them waits as its text, and one whose timeout passes there is told it was never sent. A call the bridge answered for, with no result from the host (`held`), keeps its place until the run ends. Once a stop is asked, calls and wakes are refused, and wakes still count against the cap, though a listing counts only the wakes the host delivered. Once the process has exited, nothing goes out, and a call read then counts as never sent. A refusal too long for the script's memory reaches it as a refusal, its start and a note. A wake waits for its spacing on a monotonic clock, from the last delivery's end, and one still waiting when its script ends, once a stop is asked or once its process has exited is never sent, and the record counts it so; the end lets every waiting wake and call go. A child that has ended its script but doesn't exit is killed after the grace, and an ended run holds no child. A process that never started counts as exited, and a stop asked once the process has exited isn't its ending's cause. - calls.ts and render.ts: the call record, which names each call whose answer the script didn't see (still running, timed out, too large, or answered as the script was stopped), counts a refusal as one though the script couldn't receive it, counts a call never sent apart, one still waiting for room at the end included, and counts the calls waiting for room, and the wakes waiting for their turn, among those made; and the text the model reads for each return, its durations said exactly, and a failure's name and message given once, its frames after them as they came. - output.ts: the child's bounded batches, and each return's first and last 8,000 characters in the parent. - scripts.ts: an agent's scripts, with run, wait, stop and list, one call watching a script at a time, the limits per agent and per host, turn ends by each script's own turn, the one its run call was made in (a non-primary agent's script lives while that turn is live, and an abandoned turn stops, for any agent, a script its calls watch that never returned), wakes, ids tagged with the host process, and the ended scripts kept: the 8 whose endings came last, by sequence, and none of a gone agent's, whose scripts a later agent of its name doesn't see. A tool list that throws refuses a run with nothing taken, a script that ends as it starts is trimmed with the rest, a left script's ending follows any wake of its still being delivered unless a wait has returned it meanwhile, and a stop watches with the call that asked it. Tests: the isolate's own, with real child processes on a stand-in engine (test/helpers/fake-engine.ts, on node:vm) and on the real one, a child that breaks the protocol in each of those ways (test/helpers/bad-child-entry.ts), and the protocol and the rendering on their own. Co-Authored-By: Claude Opus 5.5 --- src/code-execution/isolate/calls.ts | 199 +++ src/code-execution/isolate/child-entry.ts | 5 + src/code-execution/isolate/child-main.ts | 132 ++ src/code-execution/isolate/child.ts | 318 +++++ src/code-execution/isolate/frames.ts | 209 ++++ src/code-execution/isolate/output.ts | 121 ++ src/code-execution/isolate/prelude.ts | 409 ++++++ src/code-execution/isolate/protocol.ts | 240 ++++ src/code-execution/isolate/render.ts | 213 ++++ src/code-execution/isolate/runner.ts | 906 ++++++++++++++ src/code-execution/isolate/scripts.ts | 465 +++++++ test/code-execution-isolate-calls.test.ts | 85 ++ test/code-execution-isolate-child.test.ts | 606 +++++++++ test/code-execution-isolate-engine.test.ts | 423 +++++++ test/code-execution-isolate-output.test.ts | 58 + test/code-execution-isolate-protocol.test.ts | 154 +++ test/code-execution-isolate-render.test.ts | 168 +++ test/code-execution-isolate-runner.test.ts | 1162 ++++++++++++++++++ test/code-execution-isolate-scripts.test.ts | 690 +++++++++++ test/helpers/bad-child-entry.ts | 263 ++++ test/helpers/bad-child-finished-then-hold.ts | 3 + test/helpers/fake-child-entry.ts | 5 + test/helpers/fake-engine.ts | 94 ++ test/helpers/orphan-parent-entry.ts | 24 + 24 files changed, 6952 insertions(+) create mode 100644 src/code-execution/isolate/calls.ts create mode 100644 src/code-execution/isolate/child-entry.ts create mode 100644 src/code-execution/isolate/child-main.ts create mode 100644 src/code-execution/isolate/child.ts create mode 100644 src/code-execution/isolate/frames.ts create mode 100644 src/code-execution/isolate/output.ts create mode 100644 src/code-execution/isolate/prelude.ts create mode 100644 src/code-execution/isolate/protocol.ts create mode 100644 src/code-execution/isolate/render.ts create mode 100644 src/code-execution/isolate/runner.ts create mode 100644 src/code-execution/isolate/scripts.ts create mode 100644 test/code-execution-isolate-calls.test.ts create mode 100644 test/code-execution-isolate-child.test.ts create mode 100644 test/code-execution-isolate-engine.test.ts create mode 100644 test/code-execution-isolate-output.test.ts create mode 100644 test/code-execution-isolate-protocol.test.ts create mode 100644 test/code-execution-isolate-render.test.ts create mode 100644 test/code-execution-isolate-runner.test.ts create mode 100644 test/code-execution-isolate-scripts.test.ts create mode 100644 test/helpers/bad-child-entry.ts create mode 100644 test/helpers/bad-child-finished-then-hold.ts create mode 100644 test/helpers/fake-child-entry.ts create mode 100644 test/helpers/fake-engine.ts create mode 100644 test/helpers/orphan-parent-entry.ts diff --git a/src/code-execution/isolate/calls.ts b/src/code-execution/isolate/calls.ts new file mode 100644 index 000000000..6bfa72dbd --- /dev/null +++ b/src/code-execution/isolate/calls.ts @@ -0,0 +1,199 @@ +/** + * A script's call record, kept in the parent, outside the child, so losing + * the child loses none of it. A call goes in at + * dispatch, as running until it's answered. Answered calls are counted; those + * whose answer the script didn't see are named, with their arguments, so an + * agent can tell which effects may have taken place. A script can live a day, + * so the record is bounded: it names at most CALLS_NAMED and counts the rest + * by tool, and it holds a counted call only while it runs. A named call stays + * until the script ends, and its state can still change after the call ends, + * as when a timed-out call's answer comes. + */ + +/** A call's arguments, or a later answer's start, as a return names it: at most this many characters. */ +export const CALL_BRIEF_CHARS = 120; +/** How many calls whose answer the script didn't see a return names; it counts the rest by tool. */ +export const CALLS_NAMED = 10; + +/** How a return names a tool, as the agent writes it in a script. */ +export const toolRef = (tool: string): string => (tool === 'wake_agent' ? 'wake_agent()' : `tools[${JSON.stringify(tool)}]`); + +/** + * A copy of `text` that shares no storage with it. A slice of a string can + * keep the whole string it was cut from alive, in V8 and in JavaScriptCore, so + * a short piece kept from a long text is copied first. + */ +export function fresh(text: string): string { + return JSON.parse(JSON.stringify(text)) as string; +} + +/** `text` cut to CALL_BRIEF_CHARS with "…", never between a surrogate pair's halves, as a copy of its own. */ +export function callBrief(text: string): string { + if (text.length <= CALL_BRIEF_CHARS) return fresh(text); + let cut = CALL_BRIEF_CHARS - 1; + const before = text.charCodeAt(cut - 1); + if (before >= 0xd800 && before <= 0xdbff) cut -= 1; + return fresh(`${text.slice(0, cut)}…`); +} + +/** A call whose answer the script didn't see, as a return names it. */ +export interface NamedCall { + tool: string; + /** Its arguments as the script sent them, cut to CALL_BRIEF_CHARS. */ + args: string; + /** + * - `uncertain`: still running when the script ended, so it may or may not take effect. + * - `timed-out`: no answer came within the call's timeout, so it may still take effect. The script was given a TimeoutError, unless it ended before reading it. + * - `timed-out-answered`: no answer came within the call's timeout, and one came later; `answer` is its start. + * - `too-large`: answered, with a result the script couldn't receive (too large for its memory, or nested too deep), so it got ResultTooLarge in its place. + * - `unseen`: answered, and the script was stopped or crashed before it read the answer. + */ + state: 'uncertain' | 'timed-out' | 'timed-out-answered' | 'too-large' | 'unseen'; + /** For `timed-out-answered`: the answer's start, cut to CALL_BRIEF_CHARS. */ + answer?: string; +} + +/** The record as a return reports it. */ +export interface CallSummary { + /** Calls the script made. */ + made: number; + /** Answered while the script waited, with a result that wasn't an error. */ + answered: number; + /** Answered while the script waited, with a result marked as an error. */ + failed: number; + /** Refused before they ran: nothing was done. */ + refused: number; + /** Never sent, so nothing was done: they timed out while they waited for room in the host, the run ended while they waited, or the host read them once the script's process had exited. */ + unsent: number; + /** Still in flight, the script waiting on them. */ + running: number; + /** Waiting their turn in the host, not sent yet: calls waiting for room, and wakes waiting for theirs. */ + waiting: number; + /** Those whose answer the script didn't see, the first CALLS_NAMED of them, in the order each became so. */ + named: NamedCall[]; + /** The rest of those, counted by tool. */ + more: Record; +} + +/** How a call that the script waited on was answered. */ +export type CallOutcome = 'answered' | 'failed' | 'refused' | 'too-large'; + +/** One call the record still follows, held by the caller between its start and its end. */ +export interface CallEntry { + readonly tool: string; + readonly args: string; + /** Set once the call has a place among the named, so a later answer can say what it was. */ + named: NamedCall | null; + /** True once the call's timeout passed with no answer: the script is given a TimeoutError, unless it ended before reading it. */ + timedOut: boolean; + /** True once the record holds the call's last word. */ + done: boolean; +} + +export class CallLog { + private made = 0; + private readonly counts = { answered: 0, failed: 0, refused: 0, unsent: 0 }; + private readonly running = new Set(); + /** Timed-out calls among the named whose answer hasn't come: it can still be named, until the script ends. */ + private readonly awaiting = new Set(); + private readonly named: NamedCall[] = []; + private readonly more = new Map(); + + /** A call went out: it's running until it's answered. */ + start(tool: string, argsJson: string): CallEntry { + this.made += 1; + const entry: CallEntry = { tool, args: callBrief(argsJson), named: null, timedOut: false, done: false }; + this.running.add(entry); + return entry; + } + + /** + * A call or wake the script made was never sent, so nothing was done: a + * call that timed out while it waited for room in the host, or that the run + * ended, or its process exited, before it went out, and one read after the + * process exited; a wake still waiting for its turn when the run ended, or + * once a stop was asked or the process exited. + */ + unsent(): void { + this.made += 1; + this.counts.unsent += 1; + } + + /** The call's timeout passed with no answer, and the script is given a TimeoutError. Its answer may still come. */ + timedOut(entry: CallEntry): void { + if (entry.done || entry.timedOut) return; + entry.timedOut = true; + this.running.delete(entry); + entry.named = this.name(entry, 'timed-out'); + if (entry.named) this.awaiting.add(entry); + else entry.done = true; + } + + /** The call's answer came. `brief` is the start of what the tool answered, for a call the script stopped waiting for. */ + answered(entry: CallEntry, outcome: CallOutcome, brief: string): void { + if (entry.done) return; + entry.done = true; + this.running.delete(entry); + this.awaiting.delete(entry); + if (entry.timedOut) { + if (entry.named) { + entry.named.state = 'timed-out-answered'; + entry.named.answer = callBrief(brief); + } + return; + } + if (outcome === 'too-large') this.name(entry, 'too-large'); + else this.counts[outcome] += 1; + } + + /** + * The call was answered, and the script ended before it read the answer: a + * refusal did nothing, and is counted; any other answer is named. + */ + unseen(entry: CallEntry, outcome: CallOutcome): void { + if (entry.done) return; + entry.done = true; + this.running.delete(entry); + this.awaiting.delete(entry); + if (outcome === 'refused') this.counts.refused += 1; + else this.name(entry, 'unseen'); + } + + /** + * The script ended: every call still running may or may not take effect, + * and the record stays as it stands, so it names the calls as the host saw + * them when the script ended. + */ + ended(): void { + for (const entry of this.running) { + entry.done = true; + this.name(entry, 'uncertain'); + } + this.running.clear(); + for (const entry of this.awaiting) entry.done = true; + this.awaiting.clear(); + } + + /** The record, with `waiting` calls the host holds back, for room or a wake's turn, which the script has made too. */ + summary(waiting = 0): CallSummary { + return { + made: this.made + waiting, + ...this.counts, + running: this.running.size, + waiting, + named: this.named.map((c) => ({ ...c })), + more: Object.fromEntries(this.more), + }; + } + + /** A place among the named, or a count by tool past CALLS_NAMED. */ + private name(entry: CallEntry, state: NamedCall['state']): NamedCall | null { + if (this.named.length < CALLS_NAMED) { + const named: NamedCall = { tool: entry.tool, args: entry.args, state }; + this.named.push(named); + return named; + } + this.more.set(entry.tool, (this.more.get(entry.tool) ?? 0) + 1); + return null; + } +} diff --git a/src/code-execution/isolate/child-entry.ts b/src/code-execution/isolate/child-entry.ts new file mode 100644 index 000000000..8c7ba27d8 --- /dev/null +++ b/src/code-execution/isolate/child-entry.ts @@ -0,0 +1,5 @@ +/** The script the parent spawns for each script: the child, on the engine. */ +import { loadEngine } from '../engine.js'; +import { childMain } from './child-main.js'; + +childMain(loadEngine); diff --git a/src/code-execution/isolate/child-main.ts b/src/code-execution/isolate/child-main.ts new file mode 100644 index 000000000..258b6e769 --- /dev/null +++ b/src/code-execution/isolate/child-main.ts @@ -0,0 +1,132 @@ +/** + * The child's process: reads the parent's frames on stdin, writes its own on + * fd 3, runs the script with the engine, and exits. Spawned by the parent with + * the parent's own runtime (`process.execPath`), Node's or Bun's. + * + * The channel is the one way that works the same under both runtimes, as + * probes under Bun 1.4.3, Node 24.19.0 and Node 20.20.2 showed: a socket on + * another fd reads nothing under Bun, and a file stream on one leaves Node's + * exit waiting on a blocked read. The child writes with `writeSync`, since its slots run while + * the engine is mid-call, and output has to reach the parent while a guest + * computes without queuing in memory. + * + * `runChildProcess` takes the engine loader so a test can spawn a child whose + * engine is a stand-in; the entry the parent spawns in production passes the + * engine's binding. + */ + +import { writeSync } from 'node:fs'; +import { performance } from 'node:perf_hooks'; +import { EngineError, MAX_MEMORY_BYTES } from '../engine.js'; +import { runChild, type ChildIo, type LoadEngine } from './child.js'; +import { encodeFrame, FrameDecoder, RESULT_FRAME_OVERHEAD } from './frames.js'; +import { MAX_CHILD_FRAME_BYTES, type ParentMessage, type StartMessage } from './protocol.js'; + +const TO_PARENT_FD = 3; + +/** + * The largest frame the child takes from its parent: a result as large as the + * largest memory can be given (a third of it, as the parent bounds a result), + * with its header. The parent is trusted; this only bounds a broken one. + */ +const MAX_PARENT_FRAME_BYTES = Math.floor(MAX_MEMORY_BYTES / 3) + RESULT_FRAME_OVERHEAD; + +/** A shared buffer to wait on, so a full pipe is retried after a short sleep without the event loop. */ +const sleeper = new Int32Array(new SharedArrayBuffer(4)); + +/** Write all of `buf` to `fd`, synchronously, waiting a moment when the pipe is full. */ +function writeAll(fd: number, buf: Buffer): void { + let offset = 0; + while (offset < buf.length) { + try { + offset += writeSync(fd, buf, offset, buf.length - offset); + } catch (err) { + const code = (err as NodeJS.ErrnoException).code; + if (code !== 'EAGAIN' && code !== 'EWOULDBLOCK') throw err; + Atomics.wait(sleeper, 0, 0, 1); + } + } +} + +/** The parent's frames, as messages, from the child's stdin. The parent's frames are trusted: a bad one ends the child. */ +function parentFrames(): () => Promise { + const decoder = new FrameDecoder(MAX_PARENT_FRAME_BYTES, true); + const queue: ParentMessage[] = []; + let ended = false; + let failure: Error | null = null; + let wake: (() => void) | null = null; + const stream = process.stdin; + stream.on('data', (chunk) => { + try { + for (const value of decoder.push(chunk as Buffer)) queue.push(value as ParentMessage); + } catch (err) { + failure = err as Error; + } + wake?.(); + }); + const finish = () => { + ended = true; + wake?.(); + }; + stream.on('end', finish); + stream.on('error', (err) => { + failure = err; + finish(); + }); + return async () => { + for (;;) { + if (failure) throw failure; + const next = queue.shift(); + if (next) return next; + if (ended) return null; + await new Promise((resolve) => { + wake = () => { + wake = null; + resolve(); + }; + }); + } + }; +} + +export async function runChildProcess(loadEngine: LoadEngine): Promise { + const next = parentFrames(); + const first = await next(); + if (first === null) return 0; + if (first.t !== 'start') throw new Error('the first frame from the parent was not a start'); + const start = first as StartMessage; + const io: ChildIo = { + next, + // Against the parent's cap, so the two can't drift apart. + send: (message) => writeAll(TO_PARENT_FD, encodeFrame(message, MAX_CHILD_FRAME_BYTES)), + now: () => performance.now(), + // On Linux and macOS an orphan is reparented, so its parent's pid isn't + // the one the start named once its parent has gone, even if that went + // before this ran. Windows keeps the pid, so an orphan isn't seen there. + parentGone: () => process.ppid !== start.parentPid, + orphaned: () => process.exit(0), + }; + await runChild(io, start, loadEngine); + return 0; +} + +/** + * The child's process as the parent spawns it: run the script, and exit. An + * engine that fails, rather than the script, ends the child with its reason + * on one line of stderr, which the parent puts in the crash it reports; any + * other throw is a fault here, and its stack goes there instead. + */ +export function childMain(loadEngine: LoadEngine): void { + runChildProcess(loadEngine).then( + (code) => process.exit(code), + (err: unknown) => { + // Written at once: a write to a pipe is asynchronous on macOS, and the exit would drop it. + try { + writeSync(2, err instanceof EngineError ? `${err.message}\n` : `${err instanceof Error ? err.stack : String(err)}\n`); + } catch { + // The parent learns of the crash from the exit either way. + } + process.exit(1); + }, + ); +} diff --git a/src/code-execution/isolate/child.ts b/src/code-execution/isolate/child.ts new file mode 100644 index 000000000..fe82f4a47 --- /dev/null +++ b/src/code-execution/isolate/child.ts @@ -0,0 +1,318 @@ +/** + * A script's child: one process per script, which runs it in the engine and + * answers the engine's three imports. It takes its start from the parent's + * first frame, sends each tool call to the parent and settles it with the + * parent's answer, flushes the output in bounded batches, and reports how the + * script ended. A crash it can't report: the parent sees that the child ended + * without saying how. + * + * Stopping: the parent creates the stop file and sends a stop frame. A + * computing guest's interrupt import sees the file, checked at most every few + * milliseconds, so a stop reaches a spinning guest without its event loop; an + * idle guest's child reads the frame. Why the stop was asked is the parent's + * to say, so the child reports only that the script stopped. + */ + +import { existsSync } from 'node:fs'; +import { MAX_RESULT_DEPTH, type Engine, type EngineLimits, type EngineSlots, type RunState, type SettleState } from '../engine.js'; +import { argumentsRefusal, MAX_CALLS_IN_FLIGHT, RESULT_TOO_LARGE, wakeLimitRefusal, wakeRefusal } from './protocol.js'; +import { OutputBatch } from './output.js'; +import { bounded, glimpse, NAME_CHARS, VALUE_CHARS } from './render.js'; +import { preludeSource, scriptCall, scriptFrames, WAKE_CALL_NAME } from './prelude.js'; +import type { ParentMessage, StartMessage } from './protocol.js'; + +/** How often, at most, the interrupt import looks for the stop file. */ +export const STOP_CHECK_MS = 3; + +/** + * The filename of the one line that starts the script, `await __run(...)`. + * The script's own frames carry ``, the source name QuickJS gives the + * async function it's compiled into (SCRIPT_LINE_OFFSET). + */ +const SCRIPT_FILENAME = 'script.js'; + +export type LoadEngine = (limits: EngineLimits, slots: EngineSlots) => Promise; + +export interface ChildIo { + /** The parent's frames, in order, each already decoded; done when the parent closes its end. */ + next(): Promise; + /** + * Send one message now, synchronously: the engine may be mid-call. A message + * too long for a frame throws its FrameError, and nothing is sent. + */ + send(message: unknown): void; + /** Milliseconds from some fixed point. */ + now(): number; + /** Whether the parent has gone, so no one is left to stop the script or read its ending. */ + parentGone?(): boolean; + /** + * End the child's process at once, its parent having gone: nothing it does + * reaches anyone, its unsent output included, and nothing is left to kill + * it if its guest keeps a stop from ending it. The child looks for its + * parent only until it has a stop, since the engine asks it nothing after + * one, so a stop taken before the parent went is ended by the parent's kill + * or not at all. Without this, the child stops its guest instead. + */ + orphaned?(): never; +} + +/** The value's JSON from the engine's JSON of the prelude's text: a string, so one parse gives it, and anything else is given as it came. */ +function valueJson(json: string): string { + try { + const text: unknown = JSON.parse(json); + return typeof text === 'string' ? text : json; + } catch { + return json; + } +} + +/** Run the script `start` names to its end. Resolves once its ending is sent, or when the parent closes the channel. */ +export async function runChild(io: ChildIo, start: StartMessage, loadEngine: LoadEngine): Promise { + const batch = new OutputBatch(); + /** The calls the guest waits on, by id: their tools' names. */ + const outstanding = new Map(); + /** Calls answered here, without going out: their arguments were too large to send. */ + const local: { id: number; json: string }[] = []; + /** + * While a result settles, the calls the guest's jobs make are held, and go + * out after that result's `seen`, which says whether the result fit: the + * parent then frees the answered call's place before it counts the next. + * A result the engine drops reaches no script code, and what code QuickJS + * still runs as the stop lands, a stack hook building the stop's error, + * can't make a call, since the engine refuses `__host` once stopped: its + * settle holds none. + */ + let held: unknown[] | null = null; + /** Wakes sent, which the script's cap counts. */ + let wakesSent = 0; + let stopped = false; + let lastFlush = io.now(); + let lastStopCheck = -Infinity; + + const flush = (): void => { + lastFlush = io.now(); + for (const text of batch.flush()) io.send({ t: 'out', text }); + }; + + const slots: EngineSlots = { + hostCall(callId, name, argsJson) { + outstanding.set(callId, name); + // What the host won't take is refused here, before a frame is built, + // and by the parent again, since the child is untrusted. A call that + // passes always fits a frame, which carries its arguments escaped, at + // most six bytes for each of theirs. + const wake = name === WAKE_CALL_NAME; + // A wake past the script's cap is refused too, after its size, so one + // refused for its size uses none of the script's wakes. + const refusal = wake + ? (wakeRefusal(argsJson) ?? (wakesSent >= start.maxWakes ? wakeLimitRefusal(start.maxWakes) : null)) + : argumentsRefusal(name, argsJson); + if (refusal) { + // The slot can't settle the call from inside the engine: it's answered + // as soon as the engine returns. + local.push({ id: callId, json: JSON.stringify(refusal) }); + return; + } + if (wake) wakesSent += 1; + // What the script wrote before the call reaches the host before it does. + if (batch.unflushed > 0) flush(); + const call = { t: 'call', id: callId, name, args: argsJson }; + // A wake takes no place among the calls in flight, so it needn't wait for a seen. + if (held === null || wake) io.send(call); + else if (held.length < MAX_CALLS_IN_FLIGHT) held.push(call); + else { + // A guest held to the prelude has at most that many calls out. One + // that makes more, its promises replaced, would have them held + // without bound: the rest go out at once, which the parent takes as + // the protocol break it is, and the guest is stopped. + stopped = true; + io.send(call); + } + }, + interrupt() { + const t = io.now(); + if (!stopped && t - lastStopCheck >= STOP_CHECK_MS) { + lastStopCheck = t; + // A guest that computes doesn't let the child see its parent go, so + // that's checked here too. An orphan ends its process at once rather + // than stop its guest: a guest can keep a stop from ending it, since + // QuickJS drops a stop it takes inside a stack hook or an iterator's + // return, and only the parent kills a child that runs on. The engine + // asks no more once it has a stop, so a stop the guest keeps from + // landing, taken before the parent went, isn't checked again. Output + // waiting to be sent goes with the process: the parent that would + // read it is gone, and a write to it could fail or block. + if (io.parentGone?.()) { + io.orphaned?.(); + stopped = true; + } else stopped = existsSync(start.stopFile); + } + if (batch.unflushed > 0 && t - lastFlush >= start.flushMs) flush(); + return stopped; + }, + output(_stream, text) { + if (batch.write(text) || io.now() - lastFlush >= start.flushMs) flush(); + }, + }; + + const engine: Engine = await loadEngine( + { memoryMaxBytes: start.memoryMaxBytes, ...(start.maxStackBytes === undefined ? {} : { maxStackBytes: start.maxStackBytes }) }, + slots, + ); + // The loop's own failure, if one is leaving it: dispose()'s, after it, is the lesser news. + let failing = false; + try { + // The script's own ending: the first terminal state. Calls still in flight + // when it ends are waited for, so each outcome reaches the record, and their + // later states don't change how the script ended: a stop then ends the + // wait, and the script's own ending is what's reported. A failure once the + // script ran (`failed`: an exception escaped its promises) ends the run at + // once, past an ending it kept: the engine takes nothing more. + const terminal = (s: RunState) => s.kind === 'fulfilled' || s.kind === 'rejected'; + let ending = { kind: 'pending' } as RunState; + let interrupted = false; + const after = (next: RunState): void => { + if (next.kind === 'interrupted') interrupted = true; + if (next.kind === 'failed' || !terminal(ending)) ending = next; + }; + const over = (): boolean => interrupted || ending.kind === 'failed'; + const settleLocal = (): void => { + while (local.length > 0 && !over()) { + const { id, json } = local.shift()!; + outstanding.delete(id); + after(engine.settle(id, false, json, json)); + } + }; + after(engine.start(preludeSource(start.tools), scriptCall(start.script), SCRIPT_FILENAME)); + settleLocal(); + for (;;) { + if (over() || stopped || outstanding.size === 0) { + flush(); + // The value, or a failure's message and stack, cut as a return shows + // them, so the ending's frame always fits whatever the script made. + if (ending.kind === 'fulfilled') { + // The prelude's run resolves to its value's JSON text, which the engine + // gives as a JSON string: one layer off is the value's JSON. + io.send(ending.json === undefined ? { t: 'end', how: 'finished' } : { t: 'end', how: 'finished', value: bounded(valueJson(ending.json), VALUE_CHARS) }); + } else if (ending.kind === 'rejected' || ending.kind === 'failed') { + // The script's frames go apart from the name and message, each cut + // on its own, so a cut never breaks which is which. Every failure's + // stack is QuickJS's own, or one the script set, and is put as the + // agent reads it here (scriptFrames), whether the script threw it or + // it escaped the script's promises, and cut as it's read: the frames + // go as scriptFrames gives them, cut once. + const frames = ending.stack ? scriptFrames(ending.stack, VALUE_CHARS) : ''; + io.send({ + t: 'end', + how: 'failed', + error: { + name: bounded(ending.name, NAME_CHARS), + message: bounded(ending.message, VALUE_CHARS), + ...(frames ? { stack: frames } : {}), + }, + }); + } else if (interrupted || stopped) { + io.send({ t: 'end', how: 'stopped' }); + } else { + io.send({ + t: 'end', + how: 'failed', + stalled: true, + error: { + name: 'Error', + message: 'The script is waiting on a promise that nothing can settle: no tool call is pending, and there are no timers here.', + }, + }); + } + return; + } + // The guest is idle until a call is answered: send its output so far. + flush(); + const message = await io.next(); + if (message === null) return; // the parent is gone, and with it anyone to tell + if (message.t === 'stop') { + stopped = true; + continue; + } + if (message.t !== 'result' || !outstanding.has(message.id)) continue; + const name = outstanding.get(message.id)!; + outstanding.delete(message.id); + // A result the guest can't take, too large for its memory, nested too + // deep or not parsed, reaches the script as ResultTooLarge, made here, + // and the parent hears it wasn't seen. An answer that isn't ok is an + // error the host gave in the result's place, such as a refusal. + // The call as the script wrote it: a wake is wake_agent. + const called = name === WAKE_CALL_NAME ? 'wake_agent' : name; + // An error the host gave in the result's place keeps its name, such as + // a refusal's, and its message's start: whether the call ran is in + // them, read here, outside the guest. + const unfit = message.ok + ? JSON.stringify({ + name: RESULT_TOO_LARGE, + message: `${called}'s result, ${message.json.length} bytes, couldn't be received by this script: it was ${unfitWhy(true)}; ` + + 'the call took effect, and none of its result reached the script. Calling it again would do the call again, its effect included, and likely bring the same result, so if the call only reads, ask the tool for less if it can', + bytes: message.json.length, + }) + : unfitAnswer(called, message.json); + held = []; + let state: SettleState; + let calls: unknown[]; + try { + state = engine.settle(message.id, message.ok, message.json, unfit); + } finally { + calls = held; + held = null; + } + // A result that reached the script freed its call's place, as the + // prelude has it, and its seen says so. One that never reached it + // (`dropped`) has no seen: the script never had it, so the prelude + // never freed its place, and what code QuickJS still runs as the stop + // lands can't reach the host, since the engine refuses `__host` once + // stopped. The run's end counts the answer as one the script didn't + // read (Gladys-2592, room-203 #159239, #162129). + if (!state.dropped) io.send(state.unfit ? { t: 'seen', id: message.id, unfit: true } : { t: 'seen', id: message.id }); + for (const call of calls) io.send(call); + after(state); + settleLocal(); + } + } catch (err) { + failing = true; + throw err; + } finally { + try { + engine.dispose(); + } catch (err) { + if (!failing) throw err; + } + } +} + +/** An error the host gave in a result's place, which the guest couldn't take: its name and its message's start, read in the child. */ +function unfitAnswer(tool: string, json: Uint8Array): string { + let name = RESULT_TOO_LARGE; + let message = ''; + let parsed = false; + try { + const answer = JSON.parse(new TextDecoder().decode(json)) as { name?: unknown; message?: unknown }; + parsed = true; + if (typeof answer.name === 'string' && answer.name) name = bounded(answer.name, NAME_CHARS); + if (typeof answer.message === 'string') message = glimpse(answer.message, UNFIT_GLIMPSE_CHARS); + } catch { + // Not readable here either: only its size and the tool are known. + } + return JSON.stringify({ + name, + message: `${message ? `${message} ` : `${tool}'s answer `}(the host's answer, ${json.length} bytes, couldn't be received by this script whole: it was ${unfitWhy(!parsed)})`, + bytes: json.length, + }); +} + +/** Why the guest couldn't take an answer: what the engine refuses, less what the child has ruled out. */ +function unfitWhy(couldBeJson: boolean): string { + return couldBeJson + ? `too large for its memory, nested deeper than ${MAX_RESULT_DEPTH} levels, or not valid JSON` + : `too large for its memory, or nested deeper than ${MAX_RESULT_DEPTH} levels`; +} + +/** How much of an unfit answer's message reaches the script. */ +const UNFIT_GLIMPSE_CHARS = 2000; diff --git a/src/code-execution/isolate/frames.ts b/src/code-execution/isolate/frames.ts new file mode 100644 index 000000000..27f6a6900 --- /dev/null +++ b/src/code-execution/isolate/frames.ts @@ -0,0 +1,209 @@ +/** + * Length-prefixed frames between a script's parent and its child: four bytes + * of big-endian length, then that many bytes of body. The parent writes to the + * child's stdin and reads the child's fd 3. + * + * A body's first byte says what it holds: + * - `JSON_KIND`: a message as UTF-8 JSON. Every frame from a child is one. + * - `RESULT_KIND`: a call's result for the guest, from the parent: whether it + * resolves or rejects, the call's id, and the result's JSON as UTF-8 bytes, + * which the child hands the engine as they are, with no second parse and no + * string copy (the path the memory budget was measured on). + * + * The decoder is the only way bytes from a child become values in the parent, + * so it bounds what a child can make the parent hold: a frame longer than its + * limit, of a kind it doesn't take, not JSON, or holding more values than its + * limit (counted before it's parsed), is a protocol error, and the parent ends + * the script as crashed rather than reading on. + */ + +/** The largest frame from a child: a call's arguments or a batch of output. */ +export const MAX_FRAME_BYTES = 64 * 1024 * 1024; + +const HEADER_BYTES = 4; +export const JSON_KIND = 0; +export const RESULT_KIND = 1; +/** A result body's own header: its kind, `ok`, and the call's id. */ +const RESULT_HEADER_BYTES = 1 + 1 + 4; + +/** A frame that breaks the protocol: too long, of a kind not taken, not UTF-8, not JSON, holding too many values, or cut off when its stream ended. */ +export class FrameError extends Error { + /** For a frame too long to encode: how long its body came to. */ + readonly bytes: number | undefined; + + constructor(message: string, bytes?: number) { + super(message); + this.name = 'FrameError'; + this.bytes = bytes; + } +} + +/** + * The values in a JSON text, counted outside its strings: the top value, and + * each value inside an object or array. Counting stops once past `stopAfter`. + */ +export function countJsonValues(text: string, stopAfter = Infinity): number { + let values = 1; + let inString = false; + for (let i = 0; i < text.length; i++) { + const c = text.charCodeAt(i); + if (inString) { + if (c === 0x5c) i++; + else if (c === 0x22) inString = false; + } else if (c === 0x22) { + inString = true; + } else if (c === 0x7b || c === 0x5b) { + // An object or array holds a first value unless it closes at once. + let j = i + 1; + while (j < text.length && (text.charCodeAt(j) === 0x20 || text.charCodeAt(j) === 0x0a || text.charCodeAt(j) === 0x0d || text.charCodeAt(j) === 0x09)) j++; + const next = text.charCodeAt(j); + if (next !== 0x7d && next !== 0x5d && ++values > stopAfter) return values; + } else if (c === 0x2c) { + if (++values > stopAfter) return values; + } + } + return values; +} + +/** A call's result as the child decodes it: the JSON's bytes, untouched. */ +export interface ResultFrame { + t: 'result'; + id: number; + ok: boolean; + json: Uint8Array; +} + +function frame(body: Buffer, maxFrameBytes: number): Buffer { + if (body.length > maxFrameBytes) { + throw new FrameError(`a frame of ${body.length} bytes is longer than the ${maxFrameBytes} the protocol allows`, body.length); + } + const out = Buffer.allocUnsafe(HEADER_BYTES + body.length); + out.writeUInt32BE(body.length, 0); + body.copy(out, HEADER_BYTES); + return out; +} + +/** One message as a JSON frame. One longer than `maxFrameBytes` throws a FrameError, and nothing is written. */ +export function encodeFrame(message: unknown, maxFrameBytes: number = MAX_FRAME_BYTES): Buffer { + const json = JSON.stringify(message); + if (json === undefined) throw new FrameError('a frame must hold a JSON value'); + const body = Buffer.allocUnsafe(1 + Buffer.byteLength(json, 'utf8')); + body[0] = JSON_KIND; + body.write(json, 1, 'utf8'); + return frame(body, maxFrameBytes); +} + +/** + * A call's result as a result frame: the JSON's bytes follow a header of `ok` + * and the id. Written straight into the frame, so a result's bytes are held + * once, which the host's memory figure counts. + */ +export function encodeResultFrame(id: number, ok: boolean, json: string, maxFrameBytes: number): Buffer { + const bodyLength = RESULT_HEADER_BYTES + Buffer.byteLength(json, 'utf8'); + if (bodyLength > maxFrameBytes) { + throw new FrameError(`a frame of ${bodyLength} bytes is longer than the ${maxFrameBytes} the protocol allows`, bodyLength); + } + const out = Buffer.allocUnsafe(HEADER_BYTES + bodyLength); + out.writeUInt32BE(bodyLength, 0); + out[HEADER_BYTES] = RESULT_KIND; + out[HEADER_BYTES + 1] = ok ? 1 : 0; + out.writeUInt32BE(id >>> 0, HEADER_BYTES + 2); + out.write(json, HEADER_BYTES + RESULT_HEADER_BYTES, 'utf8'); + return out; +} + +/** How much a result frame's body holds beyond its JSON. */ +export const RESULT_FRAME_OVERHEAD = RESULT_HEADER_BYTES; + +/** + * Bytes in, whole frames' values out. Holds at most one frame's bytes plus its + * header: a header naming more than `maxFrameBytes` throws before any of that + * frame's body is kept, and once a header names its body's length, the body + * is one buffer of that length, which each chunk is copied into. So however + * the bytes arrive, one at a time included, the decoder keeps no chunk of + * them. Takes result frames only if asked to: a parent's decoder takes JSON + * alone. + */ +export class FrameDecoder { + private readonly header = Buffer.alloc(HEADER_BYTES); + private headerFilled = 0; + private body: Buffer | null = null; + private bodyFilled = 0; + private broken: FrameError | null = null; + + constructor( + private readonly maxFrameBytes: number = MAX_FRAME_BYTES, + private readonly takesResults = false, + /** The most values a JSON frame may hold, counted before it's parsed. */ + private readonly maxValues = Infinity, + ) {} + + /** The values of every frame `chunk` completes, in order. Throws the same FrameError from then on, once one frame was bad. */ + push(chunk: Buffer): unknown[] { + if (this.broken) throw this.broken; + const values: unknown[] = []; + let at = 0; + while (at < chunk.length) { + if (this.body === null) { + const n = Math.min(HEADER_BYTES - this.headerFilled, chunk.length - at); + chunk.copy(this.header, this.headerFilled, at, at + n); + this.headerFilled += n; + at += n; + if (this.headerFilled < HEADER_BYTES) break; + const length = this.header.readUInt32BE(0); + if (length > this.maxFrameBytes) { + return this.fail(`a frame announced ${length} bytes, more than the ${this.maxFrameBytes} the protocol allows`); + } + if (length === 0) return this.fail('a frame was empty'); + this.body = Buffer.allocUnsafe(length); + this.bodyFilled = 0; + } + const n = Math.min(this.body.length - this.bodyFilled, chunk.length - at); + chunk.copy(this.body, this.bodyFilled, at, at + n); + this.bodyFilled += n; + at += n; + if (this.bodyFilled < this.body.length) break; + const body = this.body; + this.body = null; + this.headerFilled = 0; + values.push(this.decode(body)); + } + return values; + } + + /** At the stream's end: throws if a frame was begun and not finished. */ + end(): void { + if (this.broken) throw this.broken; + if (this.headerFilled > 0 || this.body !== null) { + this.fail(`the stream ended inside a frame, with ${this.body === null ? this.headerFilled : this.bodyFilled} of its bytes read`); + } + } + + private decode(body: Buffer): unknown { + if (body[0] === RESULT_KIND && this.takesResults) { + if (body.length < RESULT_HEADER_BYTES) return this.fail('a result frame was shorter than its header'); + const result: ResultFrame = { t: 'result', id: body.readUInt32BE(2), ok: body[1] === 1, json: body.subarray(RESULT_HEADER_BYTES) }; + return result; + } + if (body[0] !== JSON_KIND) return this.fail(`a frame was of kind ${body[0]}, which this side doesn't take`); + let text: string; + try { + // A byte-order mark is kept, so it fails JSON.parse as the text it is. + text = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(body.subarray(1)); + } catch { + return this.fail('a frame was not UTF-8'); + } + if (countJsonValues(text, this.maxValues) > this.maxValues) return this.fail(`a frame held more than ${this.maxValues} values`); + try { + return JSON.parse(text); + } catch { + return this.fail('a frame was not JSON'); + } + } + + private fail(message: string): never { + this.broken = new FrameError(message); + this.body = null; + throw this.broken; + } +} diff --git a/src/code-execution/isolate/output.ts b/src/code-execution/isolate/output.ts new file mode 100644 index 000000000..1b211bc0a --- /dev/null +++ b/src/code-execution/isolate/output.ts @@ -0,0 +1,121 @@ +/** + * A script's output, bounded once, in the parent: every return, whether a + * yield, a wait or the ending's, gives what the script wrote since the + * previous one, its first and last characters within the bound and a count of + * what was dropped between. A long stretch keeps its start, since that's + * often where its trouble began, such as the first error before a cascade. + * + * The child can't know when the parent returns, since its frames reach it only + * while its guest is idle, so the bound sits in the parent: the child holds + * one bounded batch of what the guest writes and flushes it, and the parent + * keeps the head and tail of each return's stretch, starting both afresh at + * each return. Memory is bounded by construction on both sides; the price is + * that a printing loop's whole output crosses the pipe, in batches. + */ + +export interface OutputBounds { + /** Characters kept from the start of each return's stretch. */ + headChars: number; + /** Characters kept from its end. */ + tailChars: number; +} + +export const DEFAULT_OUTPUT_BOUNDS: OutputBounds = { headChars: 8_000, tailChars: 8_000 }; + +/** The most one output frame carries, so a single large write is sent in pieces. */ +export const MAX_BATCH_CHARS = 64 * 1024; + +/** Where to cut `text` near `at` without parting a surrogate pair: one earlier if `at` falls inside one. */ +function safeCut(text: string, at: number): number { + if (at <= 0 || at >= text.length) return Math.max(0, Math.min(at, text.length)); + const before = text.charCodeAt(at - 1); + return before >= 0xd800 && before <= 0xdbff ? at - 1 : at; +} + +/** The child's side: what the guest wrote since the last flush, at most a batch of it at a time. */ +export class OutputBatch { + private pending: string[] = []; + private size = 0; + + /** Characters waiting to be flushed. */ + get unflushed(): number { + return this.size; + } + + /** True once the batch is full, so the caller flushes now. */ + write(text: string): boolean { + if (text.length === 0) return this.size >= MAX_BATCH_CHARS; + this.pending.push(text); + this.size += text.length; + return this.size >= MAX_BATCH_CHARS; + } + + /** The waiting text in pieces of at most a batch each, emptying it; empty when nothing waits. */ + flush(): string[] { + if (this.size === 0) return []; + let text = this.pending.join(''); + this.pending = []; + this.size = 0; + const pieces: string[] = []; + while (text.length > MAX_BATCH_CHARS) { + const cut = safeCut(text, MAX_BATCH_CHARS); + pieces.push(text.slice(0, cut)); + text = text.slice(cut); + } + if (text.length > 0) pieces.push(text); + return pieces; + } +} + +/** The parent's side: the head and tail of what arrived since the last return, and the count dropped between. */ +export class WindowOutput { + private head = ''; + private tail = ''; + private dropped = 0; + + constructor(private readonly bounds: OutputBounds) {} + + /** Add text the child flushed. */ + apply(text: string): void { + let rest = text; + if (this.tail.length === 0 && this.dropped === 0) { + const room = this.bounds.headChars - this.head.length; + if (room > 0) { + const cut = safeCut(rest, room); + this.head += rest.slice(0, cut); + rest = rest.slice(cut); + if (rest.length === 0) return; + } + } + this.tail += rest; + const over = this.tail.length - this.bounds.tailChars; + if (over > 0) { + // Drop from the front, never splitting a pair: the cut moves later by one if it would. + let cut = over; + const at = this.tail.charCodeAt(cut - 1); + if (at >= 0xd800 && at <= 0xdbff) cut += 1; + this.dropped += cut; + this.tail = this.tail.slice(cut); + } + } + + /** Characters dropped from the stretch so far. */ + get droppedChars(): number { + return this.dropped; + } + + /** The stretch as the model sees it: the head, a line naming what was dropped if anything was, and the tail. */ + render(): string { + if (this.dropped === 0) return this.head + this.tail; + return `${this.head}\n[… ${this.dropped} characters of output dropped here …]\n${this.tail}`; + } + + /** The stretch as the model sees it, and a fresh one from here. */ + take(): string { + const text = this.render(); + this.head = ''; + this.tail = ''; + this.dropped = 0; + return text; + } +} diff --git a/src/code-execution/isolate/prelude.ts b/src/code-execution/isolate/prelude.ts new file mode 100644 index 000000000..6c59f4575 --- /dev/null +++ b/src/code-execution/isolate/prelude.ts @@ -0,0 +1,409 @@ +/** + * The guest's prelude: JavaScript the engine evaluates before a script, which + * builds what a script meets from the binding's two globals, `__host(name, + * argsJson)` and `__output(stream, text)`: + * + * - `tools[name](args)`, each tool by its exact name, answering one result + * shape, `{ content, structuredContent, isError }`. A refusal before + * dispatch rejects with an Error carrying the refusal's fields. Reading a + * name that isn't a tool throws at once, naming the close matches. + * At most MAX_CALLS_IN_FLIGHT are out at once, and one past them waits + * here, in the script's own memory, until one is answered. + * - `ALL_TOOLS`, each tool's name, description and input schema. + * - `text(value)` and `console.log/info/warn/error/debug(...)`, which write + * the script's output: a string as it is, anything else as JSON. + * - `wake_agent(payload)`, which asks the host to wake the agent. + * - `__run(src)`, which runs a script as the body of an async function, so it + * may `await` at its top level and `return` a value. + * + * Nothing here guards a boundary: the guest's whole world is this instance, + * and every call it makes is checked by the host. Built-ins aren't frozen, so a + * script that patches one breaks only itself: its calls and its ending are + * recorded outside the guest. + * + * Adapted from pi-codemode's guest prelude (`@earendil-works/pi-codemode` + * 1.3.0, `dist/runtime/prelude-source.js`): the tools proxy and its + * close-match error, the output helpers and the error formatting. Its licence, + * the LICENSE at the root of github.com/earendil-works/pi, which the package's + * `license: "MIT"` refers to: + * + * MIT License. Copyright (c) 2025 Mario Zechner. 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. + */ + +import { MAX_CALLS_IN_FLIGHT } from './protocol.js'; +import { cutOf } from './render.js'; + +/** The host call name `wake_agent()` uses. Parentheses can't be in a tool's name, so it never names a tool. */ +export const WAKE_CALL_NAME = '(wake_agent)'; + +/** + * How many lines QuickJS's async function constructor puts before a script's + * first line, and the filename it gives the script's code: a script's frames + * read `:N:C`, with its own line N less this. + */ +export const SCRIPT_LINE_OFFSET = 2; + +/** + * A frame of the script's own code, as QuickJS writes one in a stack: " at + * NAME (PLACE)", or " at PLACE" for a syntax error, its PLACE at the end + * and the script's, `:LINE:COLUMN`, its LINE of at most ten digits, as + * QuickJS writes a line (a 32-bit integer). A NAME may hold any character but + * a line feed, which the stack's lines are split at, so `[^]` rather than + * `.`, which stops at a carriage return and U+2028 and U+2029 too. The + * guest's copy of the rule (errorText) is given these two patterns as they + * are. + */ +export const SCRIPT_FRAME = /^\s+at\s[^]*:\d{1,10}:\d+\)?$/; +/** A script frame's place, at its end: its line is renumbered as the agent wrote it. */ +export const SCRIPT_PLACE = /:(\d{1,10})(:\d+\)?)$/; +/** + * SCRIPT_FRAME as it reads a line within a whole stack, from the line's + * start (sticky) to the line feed or the stack's end that closes it, so that + * no string is made for the line: a line holds no line feed, so there `\s` + * is `[^\S\n]` and `[^]` is `[^\n]`. A test holds the two to one reading. + */ +const SCRIPT_FRAME_IN_STACK = /[^\S\n]+at[^\S\n][^\n]*:\d{1,10}:\d+\)?(?=\n|$)/y; + +/** + * A failure's frames as the agent reads them, one per line: the script's own, + * numbered as the agent wrote its lines, leaving out the prelude's frames and + * the engine's native ones. QuickJS gives an Error's stack as its frames + * alone, and nothing here adds the failure's name and message to it, so the + * script's frames are the lines shaped as its frames are (SCRIPT_FRAME): a + * line of a stack the script or its Error.prepareStackTrace set is one if it + * is shaped so, and isn't otherwise. A function's name holding a line feed + * splits its frame in two, and each half is read on its own, so the frame is + * lost unless a half is itself shaped as a frame. Code the script compiles at + * run time is named `` as the script is, so the rule takes its frames + * for the script's: an eval's read at its string's lines less two, and a + * Function constructor's at its body's own lines, plus one for each line + * break in its parameters, since the constructor puts its parameters and two + * lines before the body, as the prelude's AsyncFunction does. The prelude's + * errorText applies this rule in the guest, to an Error the script logs, + * leading with the name and message, while a string the script logs, an + * Error's `stack` among them, shows as QuickJS or the script wrote it; the + * child applies the rule here to every failure's stack. The two are one rule: + * the guest is given the pair of patterns as they are, and the walk here + * reads SCRIPT_FRAME as SCRIPT_FRAME_IN_STACK and renumbers a place itself, + * as SCRIPT_PLACE says, so a change to one has to be made to all three, and + * tests hold them together. + */ +export function scriptFrames(stack: string, max = Infinity): string { + // Two walks of the stack, a line at a time, making no string for a line + // that isn't a frame, or for a frame the cut doesn't keep: a stack the + // script set can be any length, as many lines as characters or one line as + // long as the stack, and beside it the child holds only what the cut keeps. + // The first walk measures the frames' text; the second takes what the cut + // keeps of it, all of it if it fits `max`, or else its first and last + // halves (bounded's cut, cutOf). + let length = -1; + eachFrame(stack, (start, place, line, rest, end) => { + length += 1 + (place - start) + LABEL_CHARS + digits(line) + (end - rest); + }); + if (length < 0) return ''; + const fits = length <= max; + const half = fits ? length : Math.floor(max / 2); + const tailFrom = length - (fits ? 0 : half); + const head: string[] = []; + const tail: string[] = []; + let at = 0; + // The part of the frames' text from `at` that `text` gives from `from` to + // `to`: what of it falls in the head or the tail goes there. + const put = (text: string, from: number, to: number): void => { + const n = to - from; + if (at < half) head.push(text.slice(from, from + Math.min(n, half - at))); + if (at + n > tailFrom) tail.push(text.slice(from + Math.max(0, tailFrom - at), to)); + at += n; + }; + eachFrame(stack, (start, place, line, rest, end) => { + if (at > 0) put('\n', 0, 1); + put(stack, start, place); + const n = LABEL_CHARS + digits(line); + if (at < half || at + n > tailFrom) put(`script:${line}`, 0, n); + else at += n; + put(stack, rest, end); + }); + return fits ? head.join('') : cutOf(head.join(''), tail.join(''), length); +} + +/** `script:`, which a frame's place is renumbered with. */ +const LABEL_CHARS = 7; + +/** How many digits a whole number from 1 to 9,999,999,999 is written with. */ +function digits(n: number): number { + let count = 1; + for (let rest = n; rest >= 10; rest = Math.floor(rest / 10)) count += 1; + return count; +} + +/** + * Each of the script's frames in `stack`, in order, as scriptFrames reads + * them, with no string made for a line: where its line starts, where its + * place starts, its place's line as the agent wrote it, where the rest of its + * place starts (`:COLUMN`, and a closing parenthesis), and where its line + * ends. + */ +function eachFrame(stack: string, frame: (start: number, place: number, line: number, rest: number, end: number) => void): void { + for (let start = 0; start <= stack.length; ) { + let end = stack.indexOf('\n', start); + if (end === -1) end = stack.length; + SCRIPT_FRAME_IN_STACK.lastIndex = start; + if (SCRIPT_FRAME_IN_STACK.test(stack)) { + // A frame ends in its place, and no later `:` fits in what + // follows it there. Its line has at most ten digits, so the number is + // exact and digits() ends, as it wouldn't on the Infinity that a few + // hundred digits parse to. + const place = stack.lastIndexOf(':', end); + let line = 0; + let rest = place + 8; + for (let c = stack.charCodeAt(rest); c >= 48 && c <= 57; c = stack.charCodeAt(++rest)) line = line * 10 + (c - 48); + frame(start, place, Math.max(1, line - SCRIPT_LINE_OFFSET), rest, end); + } + start = end + 1; + } +} + +/** One tool as the prelude lists it in ALL_TOOLS. */ +export interface PreludeTool { + name: string; + description: string; + /** The tool's input schema, as the agent's own tool list gives it. */ + inputSchema?: unknown; +} + +/** + * The prelude, for these tools. The child, not the prelude, refuses a wake + * past the script's cap (child.ts). The tool list goes in as + * a JSON string the prelude parses, so a schema's `__proto__` key stays a key, + * as it would not in an object literal, and the prelude is one string the + * engine evaluates as global code. + */ +export function preludeSource(tools: readonly PreludeTool[]): string { + const toolsJson = JSON.stringify(tools.map((t) => ({ name: t.name, description: t.description, inputSchema: t.inputSchema ?? null }))); + return `(function (host, out, toolList) { + "use strict"; + const stringify = JSON.stringify; + const ErrorCtor = Error; + const TypeErrorCtor = TypeError; + const scriptOffset = ${SCRIPT_LINE_OFFSET}; + + // QuickJS stacks list frames only: lead with "Name: message", as V8 does, and + // keep only the script's own frames, leaving out this prelude's, the + // engine's native ones and the line that started the script, each numbered + // as the agent wrote its lines. The child applies the same rule to every + // failure's stack (scriptFrames), reading each line in place. + const scriptFrame = ${SCRIPT_FRAME}; + const scriptPlace = ${SCRIPT_PLACE}; + function errorText(error) { + const head = error.message ? String(error.name) + ": " + String(error.message) : String(error.name); + const stack = typeof error.stack === "string" ? error.stack : ""; + const frames = stack.split("\\n") + .filter((line) => scriptFrame.test(line)) + .map((line) => line.replace(scriptPlace, (m, n, rest) => "script:" + Math.max(1, Number(n) - scriptOffset) + rest)); + return [head, ...frames].join("\\n"); + } + + function format(value) { + if (typeof value === "string") return value; + if (value instanceof ErrorCtor) return errorText(value); + try { + const json = stringify(value); + return json === undefined ? String(value) : json; + } catch { + return String(value); + } + } + + function write(stream, value) { + let rendered; + try { + rendered = format(value); + } catch (error) { + throw new TypeErrorCtor(error instanceof ErrorCtor ? error.message : String(error)); + } + out(stream, rendered + "\\n"); + } + + // A refusal before dispatch arrives as its JSON, { name, message, ...fields }. + function asError(reason) { + if (reason instanceof ErrorCtor) return reason; + const fields = reason !== null && typeof reason === "object" ? reason : { message: String(reason) }; + const error = new ErrorCtor(typeof fields.message === "string" ? fields.message : "the call was refused"); + for (const key of Object.keys(fields)) { + if (key === "name" || key === "message" || key === "stack") continue; + Object.defineProperty(error, key, { value: fields[key], writable: true, enumerable: true, configurable: true }); + } + if (typeof fields.name === "string" && fields.name) error.name = fields.name; + return error; + } + + function toJson(name, args) { + let json; + try { + json = stringify(args); + } catch (error) { + throw new TypeErrorCtor(name + "'s arguments aren't JSON (" + (error instanceof ErrorCtor ? error.message : String(error)) + "); nothing was sent"); + } + if (json === undefined) throw new TypeErrorCtor(name + "'s arguments aren't JSON; nothing was sent"); + return json; + } + + // At most maxInFlight of the script's tool calls are out at once. One past + // them waits here, in the script's own memory, until one is answered, and + // its place passes straight to it: never refused, only paced. + const maxInFlight = ${MAX_CALLS_IN_FLIGHT}; + let inFlight = 0; + const waiting = []; + function release() { + const next = waiting.shift(); + if (next) next(); + else inFlight -= 1; + } + function call(name, args) { + let json; + try { + json = args === undefined ? "{}" : toJson(name, args); + } catch (error) { + return Promise.reject(error); + } + return new Promise((resolve, reject) => { + const go = () => { + let pending; + try { + pending = host(name, json); + } catch (error) { + release(); + reject(asError(error)); + return; + } + pending.then( + (value) => { release(); resolve(value); }, + (reason) => { release(); reject(asError(reason)); }, + ); + }; + if (inFlight < maxInFlight) { + inFlight += 1; + go(); + } else { + waiting.push(go); + } + }); + } + + // wake_agent() takes no place among the calls in flight. The child refuses + // a payload too large, and a wake past the script's cap, before any goes out. + function wake(payload) { + let json; + try { + json = toJson("wake_agent()", payload === undefined ? null : payload); + } catch (error) { + return Promise.reject(error); + } + return host(${JSON.stringify(WAKE_CALL_NAME)}, json).then((value) => value, (reason) => { throw asError(reason); }); + } + + const tools = Object.create(null); + const allTools = []; + for (const { name, description, inputSchema } of toolList) { + if (name in tools) continue; + tools[name] = (args) => call(name, args); + allTools.push(Object.freeze({ name, description, inputSchema })); + } + Object.freeze(tools); + Object.freeze(allTools); + + // A name that isn't a tool throws at once, naming the close matches, rather + // than failing later as "not a function". \`in\` still answers false. + const comparable = (name) => name.toLowerCase().replace(/[^a-z0-9]/g, ""); + const names = allTools.map((tool) => tool.name); + const toolsProxy = new Proxy(tools, { + get(object, property, receiver) { + if (typeof property !== "string" || property in object || property in Object.prototype || property === "then" || property === "toJSON") { + return Reflect.get(object, property, receiver); + } + const wanted = comparable(property); + const exact = names.filter((name) => comparable(name) === wanted); + const close = exact.length > 0 ? exact : names.filter((name) => wanted && (comparable(name).includes(wanted) || wanted.includes(comparable(name)))); + let message = "tools[" + stringify(property) + "] is not one of your tools."; + if (close.length > 0) message += " Did you mean " + close.slice(0, 5).map((name) => "tools[" + stringify(name) + "]").join(", ") + "?"; + else if (names.length === 0) message += " You have no tools."; + else if (names.length <= 20) message += " Your tools: " + names.join(", ") + "."; + else message += " ALL_TOOLS lists them."; + message += " Nothing was sent."; + throw new TypeErrorCtor(message); + }, + }); + + const consoleObject = {}; + for (const level of ["log", "info", "debug"]) consoleObject[level] = (...args) => out(1, args.map(format).join(" ") + "\\n"); + for (const level of ["warn", "error"]) consoleObject[level] = (...args) => out(2, args.map(format).join(" ") + "\\n"); + Object.freeze(consoleObject); + + const AsyncFunction = (async function () {}).constructor; + // The script's value, as the JSON text of what it returned, serialized here + // once: its toJSON and getters run once, and the engine serializes this text + // as a string, which runs no script code however often it's read. A value + // JSON can't hold, such as a function, a cycle or a BigInt, is its string form. + async function run(src) { + let value; + try { + const fn = new AsyncFunction(src); + value = await fn.call(undefined); + } catch (error) { + // An Error goes as it is, a syntax error's included: the child puts its + // frames as the agent reads them (scriptFrames). Anything else thrown, + // such as a plain object or a string, fails the script with its JSON or + // its string form as the message, so the agent reads what was thrown; + // its stack, made here, holds no frame of the script's. + if (error instanceof ErrorCtor) throw error; + const failure = new ErrorCtor(format(error)); + failure.name = ""; + throw failure; + } + if (value === undefined) return undefined; + let json; + try { + json = stringify(value); + } catch { + json = undefined; + } + if (json !== undefined) return json; + // Said as a string, so the agent reads that the value itself didn't come back. + let text; + try { + text = "JSON can't hold the returned value, so this is its string form: " + String(value); + } catch { + text = "JSON can't hold the returned value, and it has no string form"; + } + return stringify(text); + } + + Object.defineProperty(globalThis, "tools", { value: toolsProxy, enumerable: true }); + Object.defineProperty(globalThis, "ALL_TOOLS", { value: allTools, enumerable: true }); + Object.defineProperty(globalThis, "console", { value: consoleObject, enumerable: true, configurable: true }); + Object.defineProperty(globalThis, "text", { value: (value) => write(1, value), enumerable: true }); + Object.defineProperty(globalThis, "wake_agent", { value: wake, enumerable: true }); + Object.defineProperty(globalThis, "__run", { value: run }); + try { delete globalThis.__host; delete globalThis.__output; } catch {} +})(globalThis.__host, globalThis.__output, JSON.parse(${JSON.stringify(toolsJson)})); +`; +} + +/** The one line `start` evaluates after the prelude: the script, as a string literal, handed to `__run`. */ +export function scriptCall(script: string): string { + return `await __run(${JSON.stringify(script)});`; +} diff --git a/src/code-execution/isolate/protocol.ts b/src/code-execution/isolate/protocol.ts new file mode 100644 index 000000000..6d3526995 --- /dev/null +++ b/src/code-execution/isolate/protocol.ts @@ -0,0 +1,240 @@ +/** + * The messages between a script's parent and its child, and the checks that + * turn a decoded frame from the child into one of them. Every frame from a + * child is untrusted: the parent acts only on a + * message `decodeChildMessage` accepts, and any other ends the script as + * crashed. + */ + +import { countJsonValues } from './frames.js'; +import type { PreludeTool } from './prelude.js'; + +export { countJsonValues }; + +/** + * The most tool calls a script has out at once, from the call until the + * script has its answer. One past them waits in the script, in its own memory, + * until one is answered, and is never refused, so a `Promise.all` over many + * calls still works, only paced, and what waits is the script's to hold. A + * child that sends more crashes its run. `wake_agent()` takes no place: its + * answer holds nothing, and its own cap bounds it. + */ +export const MAX_CALLS_IN_FLIGHT = 16; + +/** + * The most JSON a tool call's arguments may be, in UTF-8 bytes: room for a + * generated file of a few MB of ordinary text as one string (escaping takes + * more room for quotes, backslashes and control characters). With MAX_ARGUMENT_VALUES it bounds + * what the host holds for a call while it runs: its arguments' text and their + * parsed value, at most about twice these bytes each (a string the runtime + * keeps in two bytes a character) and about 92 bytes a value, the most a + * value cost in a parse under Node 24 and Bun 1.4. + */ +export const MAX_ARGUMENT_BYTES = 4 * 1024 * 1024; + +/** The most values a tool call's arguments may hold: each object, array and element counts. */ +export const MAX_ARGUMENT_VALUES = 100_000; + +/** The most JSON a `wake_agent()` payload may be, in UTF-8 bytes: a wake says what happened, and a longer result is written with a tool. */ +export const MAX_WAKE_PAYLOAD_BYTES = 64 * 1024; + +/** A cap as the tool's text says it, such as 4 MiB. */ +export const capText = (bytes: number): string => (bytes % (1024 * 1024) === 0 ? `${bytes / (1024 * 1024)} MiB` : `${bytes / 1024} KiB`); + +/** A `wake_agent()` payload the host won't take, said to the script: nothing was sent. Null when it may go out. */ +export function wakeRefusal(payloadJson: string): { name: 'WakeRefused'; message: string; bytes: number } | null { + const bytes = Buffer.byteLength(payloadJson, 'utf8'); + if (bytes <= MAX_WAKE_PAYLOAD_BYTES) return null; + return { + name: 'WakeRefused', + message: `wake_agent()'s payload is ${bytes} bytes of JSON, past the ${capText(MAX_WAKE_PAYLOAD_BYTES)} a wake can carry; nothing was sent. Write a longer result with a tool, and wake with where to find it.`, + bytes, + }; +} + +/** + * The longest frame a child sends: a call frame carries its arguments escaped, + * at most six bytes for each of the 4 MiB the script may send, with room for + * its name and the rest. The child encodes its frames against it, and the + * parent refuses a longer one before its body is kept, so one frame being + * read costs the host at most this much as bytes, and its text and its parse + * up to twice as much again each. + */ +export const MAX_CHILD_FRAME_BYTES = 6 * MAX_ARGUMENT_BYTES + 1024 * 1024; + +/** A wake past the script's cap, said to the script: nothing was sent. */ +export function wakeLimitRefusal(maxWakes: number): { name: 'WakeRefused'; message: string } { + // A cap of 0 turns wakes off for every script, so a new one can't either. + if (maxWakes === 0) { + return { + name: 'WakeRefused', + message: "wake_agent() is off on this host: its operator set maxWakesPerScript to 0, so nothing was sent, and no script here can wake its agent.", + }; + } + return { + name: 'WakeRefused', + message: + `wake limit reached: ${maxWakes} per script, set by the host's maxWakesPerScript; nothing was sent, and every later ` + + 'wake_agent() in this script is refused the same way. A new script can wake its agent again.', + }; +} + +/** A call's arguments the host won't take, said to the script: nothing was sent. Null when they may go out. */ +export function argumentsRefusal(tool: string, argsJson: string): { name: 'ArgumentsTooLarge'; message: string; bytes: number } | null { + const bytes = Buffer.byteLength(argsJson, 'utf8'); + if (bytes > MAX_ARGUMENT_BYTES) { + return { + name: 'ArgumentsTooLarge', + message: `${tool}'s arguments are ${bytes} bytes of JSON, past the ${capText(MAX_ARGUMENT_BYTES)} a call can take; nothing was sent. Send the data in parts, over several calls.`, + bytes, + }; + } + if (countJsonValues(argsJson, MAX_ARGUMENT_VALUES) > MAX_ARGUMENT_VALUES) { + return { + name: 'ArgumentsTooLarge', + message: `${tool}'s arguments hold more than ${MAX_ARGUMENT_VALUES.toLocaleString('en-US')} values, the most a call can take; nothing was sent. Send the data in parts, or as one string.`, + bytes, + }; + } + return null; +} + +/** The error a script gets for a call whose result was too large for its memory: the call took effect. */ +export const RESULT_TOO_LARGE = 'ResultTooLarge'; + +/** Why a stop was asked for: by the agent, or at the script's limit. Only the parent knows it. */ +export type StopReason = 'agent' | 'limit'; + +/** The first frame to the child: the script and everything it runs with. */ +export interface StartMessage { + t: 'start'; + script: string; + tools: PreludeTool[]; + /** The WebAssembly memory's maximum: the child's budget. */ + memoryMaxBytes: number; + maxStackBytes?: number; + /** The file whose appearance stops the script: its content says nothing. */ + stopFile: string; + /** How often, at most, waiting output is flushed while the guest runs. */ + flushMs: number; + /** The most wakes the script may send: the child refuses one past them itself. */ + maxWakes: number; + /** The parent's pid: the child ends its process once its parent is another, its parent having gone. */ + parentPid: number; +} + +/** + * A tool call's answer, as the child decodes a result frame: `ok` resolves the + * guest's promise with the parsed `json`, otherwise rejects with it. The JSON + * stays bytes, for the engine to parse in the guest. + */ +export type { ResultFrame as ResultMessage } from './frames.js'; +import type { ResultFrame } from './frames.js'; + +/** A stop for a child whose guest is idle, waiting on calls; a computing guest sees the stop file instead. */ +export interface StopMessage { + t: 'stop'; +} + +export type ParentMessage = StartMessage | ResultFrame | StopMessage; + +/** The guest called a tool, or `wake_agent()`, by the prelude's call name. */ +export interface CallMessage { + t: 'call'; + id: number; + name: string; + /** The arguments as the guest's JSON text. */ + args: string; +} + +/** Output the guest wrote since the last such frame. */ +export interface OutMessage { + t: 'out'; + text: string; +} + +/** An error as the guest describes it. */ +export interface GuestError { + name: string; + message: string; + /** The script's frames, one per line, as the agent reads them (scriptFrames): the name and message aren't in it. */ + stack?: string; +} + +/** + * The engine settled a call's result, so its place is free. `unfit` when the + * result didn't fit the guest's memory, so the script got ResultTooLarge in + * its place. The parent's record counts a call as the script saw it. + */ +export interface SeenMessage { + t: 'seen'; + id: number; + unfit?: true; +} + +/** + * How the script ended, as the child saw it. A crash is the parent's to see: + * the child can't report it. A stop's reason is the parent's too, since it + * asked for the stop. + */ +export type EndMessage = + | { t: 'end'; how: 'finished'; value?: string } + | { t: 'end'; how: 'failed'; error: GuestError; stalled?: boolean } + | { t: 'end'; how: 'stopped' }; + +export type ChildMessage = CallMessage | OutMessage | SeenMessage | EndMessage; + +const isRecord = (v: unknown): v is Record => typeof v === 'object' && v !== null && !Array.isArray(v); +/** A call's id: a result frame carries it as an unsigned 32-bit number, so a larger one couldn't name its call. */ +const isCallId = (v: unknown): v is number => typeof v === 'number' && Number.isInteger(v) && v >= 0 && v <= 0xffffffff; + +/** The longest tool name the parent takes from a child, in UTF-16 code units. Providers allow 64; this leaves room and bounds the error text. */ +const MAX_NAME_CHARS = 256; + +/** + * A frame from a child, as a message the parent may act on, or the reason it + * can't be one. Fields beyond those a message has are ignored. + */ +export function decodeChildMessage(value: unknown): ChildMessage | { invalid: string } { + if (!isRecord(value)) return { invalid: 'a frame from the child was not an object' }; + switch (value.t) { + case 'call': { + if (!isCallId(value.id)) return { invalid: "a call's id was not a whole number from 0 to 4294967295" }; + if (typeof value.name !== 'string' || value.name.length === 0 || value.name.length > MAX_NAME_CHARS) { + return { invalid: "a call's name was not a string of 1 to 256 UTF-16 code units" }; + } + if (typeof value.args !== 'string') return { invalid: "a call's arguments were not text" }; + return { t: 'call', id: value.id, name: value.name, args: value.args }; + } + case 'seen': { + if (!isCallId(value.id)) return { invalid: "a seen frame's id was not a whole number from 0 to 4294967295" }; + return value.unfit === true ? { t: 'seen', id: value.id, unfit: true } : { t: 'seen', id: value.id }; + } + case 'out': { + if (typeof value.text !== 'string') return { invalid: 'an output frame did not hold text' }; + return { t: 'out', text: value.text }; + } + case 'end': { + if (value.how === 'finished') { + if (value.value !== undefined && typeof value.value !== 'string') return { invalid: "a finished script's value was not JSON text" }; + return value.value === undefined ? { t: 'end', how: 'finished' } : { t: 'end', how: 'finished', value: value.value }; + } + if (value.how === 'failed') { + const error = value.error; + if (!isRecord(error) || typeof error.name !== 'string' || typeof error.message !== 'string' || (error.stack !== undefined && typeof error.stack !== 'string')) { + return { invalid: "a failed script's error did not hold a name and a message" }; + } + return { + t: 'end', + how: 'failed', + error: { name: error.name, message: error.message, ...(error.stack === undefined ? {} : { stack: error.stack }) }, + ...(value.stalled === true ? { stalled: true } : {}), + }; + } + if (value.how === 'stopped') return { t: 'end', how: 'stopped' }; + return { invalid: 'an end frame named no ending' }; + } + default: + return { invalid: 'a frame from the child named no message' }; + } +} diff --git a/src/code-execution/isolate/render.ts b/src/code-execution/isolate/render.ts new file mode 100644 index 000000000..b2be3a4b4 --- /dev/null +++ b/src/code-execution/isolate/render.ts @@ -0,0 +1,213 @@ +/** + * What the model reads for a return of `code_execution`: how the script ended + * or that it's still running, first, so no cut can drop it; then what became + * of its calls, naming each whose effect is uncertain with a count of the + * rest; then what it returned and what it wrote since the last return. + */ + +import { fresh, toolRef, type CallSummary, type NamedCall } from './calls.js'; +import type { Ending } from './runner.js'; +import type { ScriptListing, ScriptReturn } from './scripts.js'; + +/** + * The most of a script's return value, or of a failure's message or stack, + * that a return shows, as a head and a tail of half this each. The run keeps + * them cut to this, so an ended run kept for a later wait holds no more. + */ +export const VALUE_CHARS = 8_000; + +/** The most of a failure's name a return shows: a name is a word, so the message and the stack keep the room. */ +export const NAME_CHARS = 200; + +/** + * The most a cut's marker adds to its text (bounded's, with a count of up to + * 16 digits). A text bounded again with this much room past its first bound + * keeps its first cut, and the count that cut gives, as they are. + */ +export const CUT_MARK_CHARS = 64; + +/** + * `text`, or its head and tail of about `max / 2` each with the count dropped + * between, as copies of their own. Neither cut parts a surrogate pair. + */ +export function bounded(text: string, max: number): string { + if (text.length <= max) return text; + const half = Math.floor(max / 2); + return cutOf(text.slice(0, half), text.slice(text.length - half), text.length); +} + +/** + * bounded's cut of a text of `length` characters, past its bound, from its + * first and its last `max / 2` characters, rounded down: `head` and `tail`, + * of that one length. So a text that's never held whole, such as a stack's + * frames as the agent reads them (scriptFrames), is cut as bounded cuts it. + */ +export function cutOf(head: string, tail: string, length: number): string { + const half = head.length; + let kept = half; + const last = head.charCodeAt(kept - 1); + if (last >= 0xd800 && last <= 0xdbff) kept -= 1; + const first = tail.charCodeAt(0); + const skipped = first >= 0xdc00 && first <= 0xdfff ? 1 : 0; + return `${fresh(head.slice(0, kept))}\n[… ${length - half + skipped - kept} characters dropped here …]\n${fresh(tail.slice(skipped))}`; +} + +/** A duration as the settings give it: exactly, a fraction of a second included. */ +export function formatMs(ms: number): string { + if (ms < 1000) return `${ms} ms`; + if (ms % 3_600_000 === 0) return `${ms / 3_600_000} h`; + if (ms % 60_000 === 0) return `${ms / 60_000} min`; + return `${ms / 1000} s`; +} + +function endingLine(id: string, ending: Ending, lifetimeMs: number): string { + switch (ending.how) { + case 'finished': + return `Script ${id} finished.`; + case 'failed': + // The run keeps the message cut to VALUE_CHARS, with room for the cut's marker. + return `Script ${id} failed: ${ending.error.name ? `${ending.error.name}: ` : ''}${ending.error.message}`; + case 'stopped': { + const why = ending.reason === 'agent' + ? `Script ${id} was stopped, as you asked.` + : ending.why + ? `Script ${id} was stopped: ${ending.why}.` + : `Script ${id} was stopped at its limit of ${formatMs(lifetimeMs)}.`; + return ending.short ? `${why} Its process didn't stop by itself and was ended, so its output may be short.` : why; + } + case 'crashed': + // The detail is the host's account; what it means for the agent follows. + return `Script ${id} crashed: ${ending.detail}. A crash is the script's process failing, not an error its code threw: a fault in the host or its runtime, or something the script's own data asked of the runtime that it couldn't do, such as reading values nested thousands deep, or code nested hundreds deep. Running it again may work; if the same script crashes again, change what it does with its data or its code.`; + } +} + +/** + * The start of `text`, up to `max` characters and "…" when it goes on, never + * parting a surrogate pair: a glimpse of a text that may already have been cut, + * whose count a second cut would misstate. + */ +export function glimpse(text: string, max: number): string { + if (text.length <= max) return text; + let cut = max; + const last = text.charCodeAt(cut - 1); + if (last >= 0xd800 && last <= 0xdbff) cut -= 1; + return `${fresh(text.slice(0, cut))}…`; +} + +/** A named call as the agent wrote it, with its arguments. */ +function namedRef(call: NamedCall): string { + return call.tool === 'wake_agent' ? `wake_agent(${call.args})` : `${toolRef(call.tool)}(${call.args})`; +} + +function namedLine(call: NamedCall): string { + switch (call.state) { + case 'uncertain': + return `- still running when the script ended, so it may or may not take effect: ${namedRef(call)}`; + case 'timed-out': + return `- timed out in the script, and still running when the script ended, so it may still take effect: ${namedRef(call)}`; + case 'timed-out-answered': + return `- timed out in the script, and its answer came later: ${namedRef(call)}, answer ${JSON.stringify(call.answer ?? '')}`; + case 'too-large': + return `- answered with a result the script couldn't receive, too large for its memory or nested too deep, so it got an error instead: ${namedRef(call)}`; + case 'unseen': + return `- answered, but the script ended before it read the answer: ${namedRef(call)}`; + } +} + +/** + * What became of the calls. At a yield, counts. At the end, counts, and each + * call whose answer the script didn't see, named with its arguments, past + * CALLS_NAMED counted by tool. + */ +function callsLines(calls: CallSummary, running: boolean): string { + if (calls.made === 0) return running ? 'It has made no tool calls yet.' : 'It made no tool calls.'; + const unseen = calls.named.length + Object.values(calls.more).reduce((sum, n) => sum + n, 0); + const parts: string[] = []; + const add = (n: number, words: string) => { + if (n > 0) parts.push(`${n} ${words}`); + }; + add(calls.answered, 'answered'); + add(calls.failed, 'answered with an error'); + add(calls.refused, calls.refused === 1 ? 'refused before it ran, so nothing was done' : 'refused before they ran, so nothing was done'); + add(calls.unsent, 'never sent, so nothing was done'); + add(calls.running, 'still running'); + add(calls.waiting, 'waiting their turn in the host'); + // Timed out, answered with a result too large for the script's memory, + // still running when the script ended (uncertain), or answered after the + // script stopped reading (unseen): either way the script went on without + // the answer. + add(unseen, unseen === 1 ? "whose answer it didn't see" : "whose answers it didn't see"); + const head = `${running ? 'Its calls so far' : 'Its calls'}: ${calls.made}${parts.length ? ` (${parts.join('; ')})` : ''}`; + if (running || unseen === 0) return `${head}.`; + const lines = [`${head}:`, ...calls.named.map(namedLine)]; + const more = Object.entries(calls.more); + if (more.length > 0) { + lines.push(`- and ${unseen - calls.named.length} more: ${more.map(([tool, n]) => `${toolRef(tool)} ×${n}`).join(', ')}`); + } + return lines.join('\n'); +} + +export interface RenderOptions { + /** The bound the return was made at, for a yield's line. */ + boundMs: number; + lifetimeMs: number; +} + +/** The text a return gives the model. */ +export function renderReturn(r: ScriptReturn, options: RenderOptions): string { + const lines: string[] = []; + if (r.ending === null) { + lines.push(`Script ${r.scriptId} is still running after ${formatMs(options.boundMs)}.`); + lines.push( + r.stopsAtTurnEnd + ? `It will be stopped when your turn ends if it's still running, since only the primary agent's scripts outlive a turn. ` + + `Wait for it with {"action": "wait", "script_id": "${r.scriptId}"} to see it finish, or stop it with {"action": "stop", "script_id": "${r.scriptId}"}.` + : `Wait for it with {"action": "wait", "script_id": "${r.scriptId}"}, stop it with {"action": "stop", "script_id": "${r.scriptId}"}, ` + + 'or leave it running: it can wake you with wake_agent(), and you are woken if it fails, crashes or reaches its limit.', + ); + } else { + lines.push(endingLine(r.scriptId, r.ending, options.lifetimeMs)); + } + // The ending's line gave the name and message; the script's frames follow (GuestError.stack). + if (r.ending?.how === 'failed' && r.ending.error.stack) lines.push(r.ending.error.stack); + lines.push(callsLines(r.calls, r.ending === null)); + // The run keeps the value cut to VALUE_CHARS, with room for the cut's marker. + if (r.ending?.how === 'finished' && r.ending.value !== undefined) lines.push(`It returned: ${r.ending.value}`); + lines.push(r.output.length > 0 ? `${r.first ? 'Its output' : 'Its output since the last return'}:\n${r.output}` : r.first ? 'No output.' : 'No output since the last return.'); + return lines.join('\n'); +} + +function minutesAgo(ms: number): string { + const minutes = Math.round(ms / 60_000); + return minutes < 1 ? 'under a minute' : `${minutes} min`; +} + +/** How a listed script stands, in a few words. */ +function listingState(s: ScriptListing, now: number): string { + if (s.running) return `running for ${minutesAgo(now - s.startedAt)}`; + const when = s.endedAt === null ? '' : ` ${minutesAgo(now - s.endedAt)} ago`; + const e = s.ending; + switch (e?.how) { + case 'finished': + return `finished${when}`; + case 'failed': + return `failed${when}: ${e.error.name ? `${e.error.name}: ` : ''}${glimpse(e.error.message, 300)}`; + case 'crashed': + return `crashed${when}`; + case 'stopped': + return e.reason === 'agent' ? `stopped by you${when}` : e.why ? `stopped${when}: ${e.why}` : `stopped at its limit${when}`; + default: + return 'ended'; + } +} + +/** A `list` of an agent's scripts: each one's id and how it stands, with how to see more. */ +export function renderList(scripts: ScriptListing[], now: number): string { + if (scripts.length === 0) return 'You have no scripts.'; + const lines = scripts.map((s) => `- ${s.scriptId}: ${listingState(s, now)}${s.wakes > 0 ? ` (${s.wakes} wake${s.wakes === 1 ? '' : 's'} delivered)` : ''}`); + return [ + 'Your scripts:', + ...lines, + '{"action": "wait", "script_id": ...} gives a script\'s output since its last return, or its result once it has ended.', + ].join('\n'); +} diff --git a/src/code-execution/isolate/runner.ts b/src/code-execution/isolate/runner.ts new file mode 100644 index 000000000..b46a09805 --- /dev/null +++ b/src/code-execution/isolate/runner.ts @@ -0,0 +1,906 @@ +/** + * The parent's side of one script: its child process, the calls it makes, its + * output, and how it ends. One `ScriptRun` per script, and one child per run, + * which ends with it, so nothing a script held stays after it. + * + * Every frame from the child is decoded and checked before the parent acts on + * it, since the child is untrusted. A frame that breaks the protocol, a child + * that exits without saying how the script ended, or one that doesn't stop + * when asked, ends the run as crashed. A tool call goes to `onToolCall`, the + * bridge to the framework's dispatcher, and its outcome goes into the run's + * call record at dispatch, as running until it's answered. + * + * What the host holds for a script is bounded: at most MAX_CALLS_IN_FLIGHT of + * its calls wait for answers at once, and at most MAX_CALLS_IN_FLIGHT are out + * in the host, timed-out ones whose tools still run included, with any past + * those waiting here as their text. Each call's arguments are within + * MAX_ARGUMENT_BYTES and MAX_ARGUMENT_VALUES, and each answer is at most + * `maxResultBytes`. Answers wait for the child as their frames, written while + * its stdin takes them and after its 'drain' once it hasn't, so each is held + * once, in bytes. + */ + +import { spawn, type ChildProcess } from 'node:child_process'; +import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { extname, join, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import type { Readable, Writable } from 'node:stream'; +import { encodeFrame, encodeResultFrame, FrameDecoder, FrameError, RESULT_FRAME_OVERHEAD } from './frames.js'; +import { MAX_BATCH_CHARS, WindowOutput, type OutputBounds } from './output.js'; +import { WAKE_CALL_NAME, type PreludeTool } from './prelude.js'; +import { CallLog, callBrief, type CallEntry, type CallOutcome, type CallSummary } from './calls.js'; +import { + argumentsRefusal, + capText, + MAX_CHILD_FRAME_BYTES, + decodeChildMessage, + MAX_ARGUMENT_BYTES, + MAX_ARGUMENT_VALUES, + MAX_CALLS_IN_FLIGHT, + MAX_WAKE_PAYLOAD_BYTES, + RESULT_TOO_LARGE, + wakeRefusal, + type ChildMessage, + type GuestError, + type StopReason, +} from './protocol.js'; +import { bounded, CUT_MARK_CHARS, formatMs, glimpse, NAME_CHARS, VALUE_CHARS } from './render.js'; + +/** A call's answer for the guest: `ok` resolves its promise with the parsed JSON, otherwise rejects with it. */ +export interface CallAnswer { + ok: boolean; + json: string; +} + +/** A tool call's answer from the bridge. */ +export interface ToolAnswer extends CallAnswer { + /** The result was marked as an error. */ + isError?: boolean; + /** The start of what the tool answered, as an agent would read it, for naming a call the script stopped waiting for. */ + brief?: string; + /** + * No result came from the host in time, so the bridge answered for it: the + * tool may still be running, holding its work, or its result was lost, and + * the host can't tell which. Its place stays taken until the run ends. + */ + held?: boolean; +} + +/** How a script ended. */ +export type Ending = + | { how: 'finished'; value?: string } + | { how: 'failed'; error: GuestError; stalled?: boolean } + /** `short` when the child had to be ended, so its output so far may be short. */ + | { how: 'stopped'; reason: StopReason; short?: boolean; why?: string } + | { how: 'crashed'; detail: string }; + +/** A run as it stands: its output since the last return, its calls, and its ending once it has one. */ +export interface RunSnapshot { + output: string; + calls: CallSummary; + ending: Ending | null; +} + +/** How many script children a host runs at once, counted until each child's process has exited. */ +export class ChildCapacity { + private live = 0; + constructor(readonly max: number) {} + get running(): number { + return this.live; + } + /** Take a place, or false when the host is full. */ + take(): boolean { + if (this.live >= this.max) return false; + this.live += 1; + return true; + } + release(): void { + this.live = Math.max(0, this.live - 1); + } +} + +/** + * The child's entry, beside this module and with its extension: `.ts` where + * the host runs this package's source, as a Bun host does through the + * package's `bun` export, and `.js` where it runs the build. So the runtime + * is given a file that exists: given a `.js` path that it maps to the `.ts` + * beside it, Bun runs it as `bun run` would, with npm_* variables and a PATH + * of its own in the child's environment. + */ +export const CHILD_ENTRY = fileURLToPath(new URL(`./child-entry${extname(fileURLToPath(import.meta.url))}`, import.meta.url)); + +export interface ScriptRunOptions { + /** The child's entry: a script the runtime runs as the child process (CHILD_ENTRY, but for tests). */ + childEntry: string; + /** Arguments after the entry, if any. */ + childArgs?: readonly string[]; + /** The runtime to run it with: by default the parent's own. */ + execPath?: string; + memoryMaxBytes: number; + maxStackBytes?: number; + output: OutputBounds; + /** How often, at most, the child flushes output while its guest computes. */ + flushMs: number; + /** The script's limit: at this age it's stopped, as "stopped at its limit". */ + lifetimeMs: number; + /** How long a call may go unanswered before the script is told it timed out. */ + toolCallTimeoutMs: number; + /** How long a stopped child has to report its ending before it's ended. */ + stopGraceMs: number; + /** The least time between two of the script's wakes: an early one waits for its turn. */ + wakeMinIntervalMs: number; + /** The most wakes one script may send; past it, a wake is refused. */ + maxWakes: number; + /** The bridge: dispatch one tool call. Never rejects; a refusal is `ok: false`. */ + onToolCall(name: string, argsJson: string): Promise; + /** Send `wake_agent(payload)`, the script's `n`th wake, once its turn has come: answers the guest's promise. */ + onWake(payloadJson: string, n: number): Promise; + /** Called once, when the run has an ending. */ + onEnd?(ending: Ending): void; +} + +/** The most of a child's stderr kept, for a crash's detail. */ +const STDERR_KEEP_CHARS = 4_000; +/** + * The most values a frame from the child may hold. Its largest, a failure's + * ending, holds eight, since arguments, output and values travel as strings. + */ +const MAX_CHILD_FRAME_VALUES = 32; +/** How much of a refusal too long for the script's memory reaches it. */ +const REFUSAL_GLIMPSE_CHARS = 2000; + +/** Remove a run's directory. A failure leaves only an empty directory behind, which mustn't stop the run's ending. */ +function removeDir(dir: string): void { + try { + rmSync(dir, { recursive: true, force: true }); + } catch { + // Nothing else to do: the directory is left. + } +} + +export { MAX_CALLS_IN_FLIGHT }; + +/** + * The largest result the parent sends a script: a third of its linear memory, + * since parsing one takes about three times its size there (a heap run under + * Bun 1.4.3 and Node 24.19.0). A larger one would fail in the guest anyway, + * after growing the child past its budget, so it's refused before it's sent, + * and the script's ResultTooLarge says the call took effect. + */ +export function maxResultBytes(memoryMaxBytes: number): number { + return Math.max(0, Math.floor(memoryMaxBytes / 3)); +} + +/** A refusal's own text, from its JSON, for a refusal that came with no brief. */ +function refusalText(json: string): string { + try { + const message = (JSON.parse(json) as { message?: unknown }).message; + if (typeof message === 'string') return message; + } catch { + // Not a refusal's JSON: its text is what there is. + } + return json; +} + +export class ScriptRun { + private child: ChildProcess | null = null; + private dir: string | null = null; + private readonly output: WindowOutput; + private readonly log = new CallLog(); + /** The ids of the guest's calls the child named, reserved until it says the script has their answer. */ + private readonly pendingIds = new Set(); + /** The calls answered and not yet seen: only these may the child say the script has. */ + private readonly answeredIds = new Set(); + /** The script's tools, the only names its calls may have. */ + private toolNames = new Set(); + /** Results sent to the guest and not yet seen: each is counted once the child says the script has it, and whether it fit. */ + private readonly delivered = new Map(); + private ending: Ending | null = null; + private readonly waiters = new Set<() => void>(); + private stderr = ''; + private lifetimeTimer: ReturnType | null = null; + private killTimer: ReturnType | null = null; + private processExited = false; + private stopAsked: { reason: StopReason; why?: string } | null = null; + /** Each unanswered call's timeout, cleared when it's answered or the run ends. */ + private readonly callTimers = new Set>(); + /** The calls holding a place among those in flight, by id, until the child says the script has their answer. */ + private readonly inFlight = new Set(); + /** + * How many calls the bridge holds, until it answers each: a call that timed + * out in the script, and so left its place there, may still hold its + * arguments in the host. Counted, not keyed by id, since the child may name + * a timed-out call's id again once the script has seen its TimeoutError. + * At most MAX_CALLS_IN_FLIGHT; a call past them waits in `dispatchWaiting`, + * as its text, until one is answered. One the bridge answered for (`held`) + * keeps its place until the run ends. + */ + private hostCalls = 0; + /** Calls waiting for the bridge to hold fewer: each resolved with true when its turn comes, false when it won't be sent. */ + private readonly dispatchWaiting = new Map void>(); + /** Wakes the child has sent, which the cap counts: those refused once a stop was asked included, so a child that ignores the stop is still bounded. */ + private wakeFrames = 0; + /** Wakes handed to the host to deliver, whatever it answered: a wake's number, which the host is given. */ + private wakes = 0; + /** Wakes the host delivered, its answer neither a refusal nor a failure. */ + private delivered_wakes = 0; + /** Wakes waiting for their turn, which the run's end counts as never sent. */ + private wakesWaiting = 0; + /** When the next wake may go out, on the monotonic clock: the spacing counts from when the last wake's delivery ended. */ + private nextWakeAt = 0; + /** Settles once the latest wake has gone out, or won't: wakes go out in the order they were made. */ + private wakeChain: Promise = Promise.resolve(); + /** Wakes waiting out the spacing, each released at its time or when the run ends. */ + private readonly wakeWaiters = new Set<() => void>(); + /** + * Results waiting for the child's stdin to drain, as their frames: each holds + * its JSON's bytes, which the result bound counts. A result's id becomes one + * the child may say it has seen once its frame is written. + */ + private readonly resultQueue: Array<{ id: number; frame: Buffer }> = []; + private draining = false; + private exitResolve!: () => void; + private endedResolve!: () => void; + /** Resolves when the run ends: a wake waiting behind another's delivery stops waiting then. */ + private readonly endedSignal: Promise = new Promise((resolve) => (this.endedResolve = resolve)); + /** Resolves when the child's process has exited, or at once if it never started. */ + readonly exited: Promise = new Promise((resolve) => (this.exitResolve = resolve)); + + constructor(private readonly options: ScriptRunOptions) { + this.output = new WindowOutput(options.output); + } + + /** + * Spawn the child and send it the script. A start frame too long for a frame + * throws before anything is spawned, with `exited` resolved, so nothing is + * left running and the place it took frees at once. A child that can't be + * spawned crashes the run. + */ + start(script: string, tools: readonly PreludeTool[]): void { + let dir: string | null = null; + let startFrame: Buffer; + try { + this.toolNames = new Set(tools.map((t) => t.name)); + // Absolute, since the child starts in it: a relative TMPDIR would name + // the stop file from two different places. + dir = resolve(mkdtempSync(join(tmpdir(), 'af-script-'))); + startFrame = encodeFrame({ + t: 'start', + script, + tools: tools.map((t) => ({ name: t.name, description: t.description, ...(t.inputSchema === undefined ? {} : { inputSchema: t.inputSchema }) })), + memoryMaxBytes: this.options.memoryMaxBytes, + ...(this.options.maxStackBytes === undefined ? {} : { maxStackBytes: this.options.maxStackBytes }), + stopFile: join(dir, 'stop'), + parentPid: process.pid, + flushMs: this.options.flushMs, + maxWakes: this.options.maxWakes, + }); + } catch (err) { + if (dir) removeDir(dir); + this.exitResolve(); + throw err; + } + this.dir = dir; + const runDir = dir; + // A process that never started never exits: the run crashes, and its place + // is free now. It counts as exited, so the ending arms no kill and holds no child. + const neverStarted = (why: unknown): void => { + this.processExited = true; + this.crash(`its process could not run: ${why instanceof Error ? why.message : String(why)}`); + removeDir(runDir); + this.exitResolve(); + }; + let child: ChildProcess; + try { + child = spawn(this.options.execPath ?? process.execPath, [this.options.childEntry, ...(this.options.childArgs ?? [])], { + // The parent's frames go in on stdin, and the child's come back on fd 3 (child-main.ts says why). + stdio: ['pipe', 'ignore', 'pipe', 'pipe'], + // The child is ours and reads nothing from its environment; the guest has none at all. + env: {}, + // Its own directory, which holds nothing but the stop file: Bun reads a + // .env file, and its bunfig.toml, from the directory it starts in, so + // in the host's it would load the host's .env into the child. + cwd: runDir, + }); + } catch (err) { + // Node throws here for some arguments it refuses at once. + neverStarted(err); + return; + } + // Node emits a spawn's 'error' on a later tick, which this takes. Bun's can + // come while spawn runs, before this is attached: spawn then throws it into + // the catch above, or returns with its pipes null, which the check below + // takes. + child.on('error', (err) => { + if (child.pid === undefined) neverStarted(err); + else this.crash(`its process could not run: ${err.message}`); + }); + this.child = child; + const toChild = child.stdin as Writable | null; + const fromChild = child.stdio[3] as Readable | null; + if (!toChild || !fromChild) { + // Bun can return with no pipes and emit the spawn's 'error', which names + // the cause, just after: a turn of the loop lets it come first. + setImmediate(() => neverStarted(new Error('its pipes were never opened'))); + return; + } + // A child's frames are small flat objects, its arguments and output + // carried as strings, so one holding more values isn't following the + // protocol, and is refused before it's parsed. + const decoder = new FrameDecoder(MAX_CHILD_FRAME_BYTES, false, MAX_CHILD_FRAME_VALUES); + fromChild.on('data', (chunk: Buffer) => { + let values: unknown[]; + try { + values = decoder.push(chunk); + } catch (err) { + this.crash(`its process sent the host a message it couldn't read (${(err as FrameError).message})`); + return; + } + for (const value of values) { + const message = decodeChildMessage(value); + if ('invalid' in message) { + this.crash(`its process sent the host a message it couldn't read (${message.invalid})`); + return; + } + this.handle(message); + } + }); + // Whether the child said how the script ended is known once its process has + // exited and fd 3 has ended, so every frame it wrote has been read: Node can + // emit 'exit' before it has read a child's last frames (1 in 2,700 runs of a + // probe under Node 20.20.2), and Bun 1.4.3 emits 'close' before an extra + // pipe like fd 3 has ended. A crash's detail waits for stderr's end too, for + // the same reason. + let exit: { code: number | null; signal: NodeJS.Signals | null } | null = null; + let channelEnded = false; + let stderrEnded = child.stderr === null; + const decide = (): void => { + if (!exit || !channelEnded || !stderrEnded || this.ending !== null) return; + const how = exit.signal ? `was ended by ${exit.signal}` : `exited with code ${exit.code}`; + if (this.stopAsked) this.end({ how: 'stopped', reason: this.stopAsked.reason, short: true, ...(this.stopAsked.why ? { why: this.stopAsked.why } : {}) }); + else this.crash(`its process ${how} before the script ended${this.stderr ? `: ${this.stderr.trim()}` : ''}`); + }; + const channelEnd = (): void => { + if (channelEnded) return; + channelEnded = true; + // A frame cut off at the channel's end can't be read: the child broke off + // mid-message. Once a stop was asked that's the stop's doing, since the + // grace's kill can cut a frame, and the run ends as stopped. + try { + decoder.end(); + } catch (err) { + if (!this.stopAsked) this.crash(`its process sent the host a message it couldn't read (${(err as FrameError).message})`); + } + decide(); + }; + fromChild.on('end', channelEnd); + fromChild.on('close', channelEnd); + fromChild.on('error', () => {}); + toChild.on('error', () => {}); + const stderrEnd = (): void => { + stderrEnded = true; + decide(); + }; + child.stderr?.on('data', (chunk: Buffer) => { + this.stderr = (this.stderr + chunk.toString('utf8')).slice(-STDERR_KEEP_CHARS); + }); + child.stderr?.on('end', stderrEnd); + child.stderr?.on('close', stderrEnd); + child.stderr?.on('error', () => {}); + // The host's place for the child frees when its process exits. fd 3 and + // stderr have the grace to end after it, which only a process the child + // left holding them could stretch. + child.on('exit', (code, signal) => { + exit = { code, signal }; + this.processExited = true; + if (this.killTimer) clearTimeout(this.killTimer); + removeDir(runDir); + this.exitResolve(); + if (!channelEnded || !stderrEnded) { + setTimeout(() => { + // After the next poll, so what the child wrote before it exited is + // read first, even when this loop was too busy to read it in the grace. + setImmediate(() => { + fromChild.destroy(); + child.stderr?.destroy(); + channelEnd(); + stderrEnd(); + }); + }, this.options.stopGraceMs).unref(); + } + decide(); + if (this.ending !== null) this.release(); + }); + this.write(startFrame); + this.lifetimeTimer = setTimeout(() => this.stop('limit'), this.options.lifetimeMs); + } + + /** The run as it stands, its output since the last return included. */ + snapshot(): RunSnapshot { + // Calls waiting for room in the host, and wakes waiting for their turn, are the script's too. + return { output: this.output.render(), calls: this.log.summary(this.dispatchWaiting.size + this.wakesWaiting), ending: this.ending }; + } + + /** The output since the last return, as the model sees it, starting a fresh stretch from here. */ + takeOutput(): string { + return this.output.take(); + } + + get ended(): boolean { + return this.ending !== null; + } + + /** End the child's process now if it's still alive, whatever the run's state. Resolves once it has exited. */ + kill(): Promise { + if (!this.processExited) this.child?.kill('SIGKILL'); + return this.exited; + } + + /** The wakes the host delivered to the script's agent: those it answered ok and not as an error. A refused wake isn't one, nor a failed one, though a failure may have taken effect. */ + get wakesDelivered(): number { + return this.delivered_wakes; + } + + /** Resolves when the script has ended, or after `ms`, whichever comes first. */ + wait(ms: number): Promise { + if (this.ending) return Promise.resolve(this.snapshot()); + return new Promise((resolve) => { + const done = () => { + clearTimeout(timer); + this.waiters.delete(done); + resolve(this.snapshot()); + }; + const timer = setTimeout(done, ms); + this.waiters.add(done); + }); + } + + /** + * Ask the script to stop: the stop file for a computing guest, a frame for an + * idle one. If the child hasn't said how it ended within the grace, it's + * ended, and the ending says its output so far may be short. The reason is + * the one asked here, whatever the child says. + */ + stop(reason: StopReason, why?: string): Promise { + if (this.ending || !this.child) return Promise.resolve(this.snapshot()); + // A process that has exited has no stop to take: its run ends as its last + // frames say, or as a crash, which a stop asked now mustn't take for its own. + if (!this.stopAsked && !this.processExited) { + this.stopAsked = { reason, ...(why ? { why } : {}) }; + try { + if (this.dir) writeFileSync(join(this.dir, 'stop'), ''); + } catch { + // The frame below still reaches an idle child, and the grace ends a computing one. + } + this.send({ t: 'stop' }); + this.killTimer = setTimeout(() => { + this.child?.kill('SIGKILL'); + }, this.options.stopGraceMs); + this.killTimer.unref?.(); + } + return new Promise((resolve) => { + const done = () => { + this.waiters.delete(done); + resolve(this.snapshot()); + }; + if (this.ending) done(); + else this.waiters.add(done); + }); + } + + private send(message: unknown): void { + this.write(encodeFrame(message)); + } + + /** A call's result, as a result frame once its turn comes: the JSON's bytes go to the engine as they are. */ + private sendResult(id: number, ok: boolean, json: string): void { + this.resultQueue.push({ id, frame: encodeResultFrame(id, ok, json, maxResultBytes(this.options.memoryMaxBytes) + RESULT_FRAME_OVERHEAD) }); + this.pump(); + } + + /** Write waiting results, one frame at a time: after a write the pipe didn't take whole, the next waits for 'drain'. */ + private pump(): void { + const pipe = this.child?.stdin ?? undefined; + if (!pipe || pipe.destroyed) { + this.resultQueue.length = 0; + return; + } + while (!this.draining && this.resultQueue.length > 0) { + const { id, frame } = this.resultQueue.shift()!; + this.answeredIds.add(id); + if (!pipe.write(frame)) { + this.draining = true; + pipe.once('drain', () => { + this.draining = false; + this.pump(); + }); + } + } + } + + private write(frame: Buffer): void { + const pipe = this.child?.stdin ?? undefined; + if (!pipe || pipe.destroyed) return; + pipe.write(frame); + } + + + private handle(message: ChildMessage): void { + if (this.ending && message.t !== 'out') return; + switch (message.t) { + case 'out': + // The child flushes output in pieces of at most a batch. + if (message.text.length > MAX_BATCH_CHARS) { + this.crash(`its process sent the host ${message.text.length.toLocaleString('en-US')} characters of output in one frame, past the ${MAX_BATCH_CHARS.toLocaleString('en-US')} it sends at most`); + return; + } + this.output.apply(message.text); + return; + case 'call': + void this.call(message.id, message.name, message.args); + return; + case 'seen': { + // The script has the answer the parent sent, or got ResultTooLarge in + // its place: the call's id and its place are free. A seen for a call + // whose answer the parent hasn't written isn't following the protocol. + if (!this.answeredIds.delete(message.id)) { + this.crash(`its process said the script had the answer to call ${message.id}, which the host never sent`); + return; + } + this.pendingIds.delete(message.id); + this.inFlight.delete(message.id); + const sent = this.delivered.get(message.id); + if (!sent) return; + this.delivered.delete(message.id); + // A refusal stays one though the script couldn't receive it: nothing was done either way. + this.log.answered(sent.entry, message.unfit && sent.outcome !== 'refused' ? 'too-large' : sent.outcome, sent.brief); + return; + } + case 'end': { + const { t: _t, ...said } = message; + // The child cuts the value and a failure's parts as a return shows them. + // The parent bounds them too, since an ended run is kept for a later + // wait, with room for the child's cut and its marker, so an honest + // child's cut, and the count it gives, pass as they are. + if (said.how === 'finished') { + this.end(said.value === undefined ? { how: 'finished' } : { how: 'finished', value: bounded(said.value, VALUE_CHARS + CUT_MARK_CHARS) }); + return; + } + if (said.how === 'failed') { + const error = { + name: bounded(said.error.name, NAME_CHARS + CUT_MARK_CHARS), + message: bounded(said.error.message, VALUE_CHARS + CUT_MARK_CHARS), + ...(said.error.stack === undefined ? {} : { stack: bounded(said.error.stack, VALUE_CHARS + CUT_MARK_CHARS) }), + }; + this.end({ how: 'failed', error, ...(said.stalled ? { stalled: true } : {}) }); + return; + } + if (!this.stopAsked) { + this.crash("its process sent the host a message it couldn't read (the script said it was stopped, and no stop was asked)"); + return; + } + this.end({ how: 'stopped', reason: this.stopAsked.reason, ...(this.stopAsked.why ? { why: this.stopAsked.why } : {}) }); + return; + } + } + } + + private async call(id: number, name: string, args: string): Promise { + const wake = name === WAKE_CALL_NAME; + // The prelude offers only the script's tools, so a call to another name + // isn't following it. The name is the child's, bounded by the protocol. + if (!wake && !this.toolNames.has(name)) { + this.crash(`its process sent the host a call to ${JSON.stringify(name)}, which isn't one of the script's tools`); + return; + } + if (this.pendingIds.has(id)) { + this.crash(`its process sent the host a message it couldn't read (call ${id} was named twice)`); + return; + } + this.pendingIds.add(id); + const tool = wake ? 'wake_agent' : name; + // Refused here, before anything is sent: the record counts it, and nothing was done. + const refuse = (refusal: { name: string; message: string }): void => { + this.delivered.set(id, { entry: this.log.start(tool, args), outcome: 'refused', brief: callBrief(refusal.message) }); + this.answer(id, false, JSON.stringify(refusal)); + }; + let n = 0; + let wakeSent = (): void => {}; + if (wake) { + // The child refuses a wake past the cap itself, and counts every wake it + // sends, one refused here included, so a child that sends more isn't + // following it. Counted before a stop's refusal, so wakes sent after a + // stop are bounded too. + if (this.wakeFrames >= this.options.maxWakes) { + this.crash(`its process sent the host more than ${this.options.maxWakes} wakes, the most a script may send`); + return; + } + // The child refuses a payload past its cap before a frame is built. + const tooLarge = wakeRefusal(args); + if (tooLarge) { + this.crash(`its process sent the host a wake of ${tooLarge.bytes.toLocaleString('en-US')} bytes, past the ${capText(MAX_WAKE_PAYLOAD_BYTES)} the script refuses itself`); + return; + } + this.wakeFrames += 1; + const stopping = this.stopRefusal('WakeRefused', tool); + if (stopping) { + refuse(stopping); + return; + } + // An early wake waits for its turn rather than being dropped: once the + // one before it has gone out, and the spacing since that one's delivery + // has passed. The run's end stops both waits. + const previous = this.wakeChain; + this.wakeChain = new Promise((resolve) => (wakeSent = resolve)); + this.wakesWaiting += 1; + await Promise.race([previous, this.endedSignal]); + await this.wakeTurn(); + // A wake still waiting for its turn when the script ended, or once a + // stop was asked or its process exited, is never sent: the ending is the + // script's last word, and the endings that should wake its agent already + // do. The record counts it as never sent: the run's end counts those + // still waiting when it comes, and one dropped before it is counted here. + // (A call waiting in the script for a place is likewise never sent when a + // stop or a crash ends it; one whose script returns goes out, since the + // child waits for every call it made.) + if (this.ending) { + wakeSent(); + return; + } + this.wakesWaiting -= 1; + if (this.stopAsked || this.processExited) { + wakeSent(); + this.log.unsent(); + return; + } + this.wakes += 1; + n = this.wakes; + } else { + // The prelude sends at most MAX_CALLS_IN_FLIGHT at once and keeps the + // rest waiting in the script, so a child that sends more isn't following it. + if (this.inFlight.size >= MAX_CALLS_IN_FLIGHT) { + this.crash(`its process sent the host more than ${MAX_CALLS_IN_FLIGHT} calls at once, the most a script may have out`); + return; + } + this.inFlight.add(id); + // The child refuses arguments past their caps before a frame is built. + const tooLarge = argumentsRefusal(name, args); + if (tooLarge) { + this.crash( + `its process sent the host a call to ${name} whose arguments pass their caps of ${capText(MAX_ARGUMENT_BYTES)} and ` + + `${MAX_ARGUMENT_VALUES.toLocaleString('en-US')} values, which the script refuses itself`, + ); + return; + } + const refusal = this.stopRefusal('ToolRefused', tool); + if (refusal) { + refuse(refusal); + return; + } + } + // The guest has its answer: a result, or a TimeoutError in its place. + let settled = false; + const settle = (ok: boolean, json: string): void => { + settled = true; + this.answer(id, ok, json); + }; + let entry: CallEntry | null = null; + // A call's time runs from when the script made it, a wait for room in the host included. + const timer = wake ? null : setTimeout(() => { + if (timer) this.callTimers.delete(timer); + if (settled || this.ending) return; + const waiting = this.dispatchWaiting.get(id); + if (waiting) { + // It never went out: no place in the host came free for it in time. + this.dispatchWaiting.delete(id); + waiting(false); + this.log.unsent(); + settle(false, JSON.stringify({ + name: 'TimeoutError', + message: `${name} was never sent: ${MAX_CALLS_IN_FLIGHT} of this script's earlier calls were still running in the host, timed-out ones ` + + `included, and its turn didn't come within ${formatMs(this.options.toolCallTimeoutMs)}, so nothing was done; calling it again may go through once one of them gets its result, ` + + 'though a call whose tool the host stopped waiting for stays counted until this script ends, and a new script starts with none', + })); + return; + } + if (!entry) return; + this.log.timedOut(entry); + settle(false, JSON.stringify({ + name: 'TimeoutError', + message: `${name} was sent, and no answer came within ${formatMs(this.options.toolCallTimeoutMs)}; it may still take effect`, + })); + }, this.options.toolCallTimeoutMs); + if (timer) this.callTimers.add(timer); + const clearTimer = (): void => { + if (!timer) return; + clearTimeout(timer); + this.callTimers.delete(timer); + }; + if (!wake) { + // Past the calls the host holds, it waits as its text, and its place is + // taken for it when its turn comes (releasePlace). + if (this.hostCalls < MAX_CALLS_IN_FLIGHT) this.hostCalls += 1; + else if (!(await new Promise((resolve) => this.dispatchWaiting.set(id, resolve)))) return; + // Once the run has ended or its process has exited, nothing it asks goes + // out: a frame the child wrote before it exited can be read after the + // exit, and a call waiting for room can be given its place after it. The + // record counts such a call as never sent: the script made it, and + // nothing was done. A stop asked while it waited refuses it, as it would + // have refused it then. + if (this.ending || this.processExited) { + clearTimer(); + this.releasePlace(); + this.log.unsent(); + return; + } + const stopping = this.stopRefusal('ToolRefused', tool); + if (stopping) { + clearTimer(); + this.releasePlace(); + refuse(stopping); + return; + } + } + entry = this.log.start(tool, args); + let answer: ToolAnswer; + try { + answer = wake ? await this.options.onWake(args, n) : await this.options.onToolCall(name, args); + } catch (err) { + // The bridge never rejects. One that does is answered as a failure, + // which may have taken effect, never as an unhandled rejection. + const text = `${tool} failed in the host: ${err instanceof Error ? err.message : String(err)}; it may have taken effect`; + answer = { ok: true, isError: true, brief: text, json: JSON.stringify({ content: [{ type: 'text', text }], structuredContent: null, isError: true }) }; + } + if (wake) { + this.nextWakeAt = performance.now() + this.options.wakeMinIntervalMs; + if (answer.ok && !answer.isError) this.delivered_wakes += 1; + wakeSent(); + } else if (!answer.held) this.releasePlace(); + // No result came, and the bridge answered for it, after the script's own + // timeout (the bridge waits longer): the call stays as the record had it + // (timed out, or uncertain at an ending), and its place stays taken. Had + // the script's timeout not come yet, its timer would still give it one. + if (answer.held) return; + clearTimer(); + const bytes = Buffer.byteLength(answer.json, 'utf8'); + const limit = maxResultBytes(this.options.memoryMaxBytes); + const tooLarge = bytes > limit; + // A refusal stays one however long its text: nothing was done. + const outcome: CallOutcome = !answer.ok ? 'refused' : tooLarge ? 'too-large' : answer.isError ? 'failed' : 'answered'; + // Only its start is kept, as a copy: the record names a call by that much. + const brief = callBrief(answer.brief ?? answer.json); + if (settled || this.ending) { + // The script stopped waiting for it, or ended: the record takes the answer as it comes. + this.log.answered(entry, outcome, brief); + return; + } + this.delivered.set(id, { entry, outcome, brief }); + if (tooLarge) { + settle(false, JSON.stringify(outcome === 'refused' + ? { + name: wake ? 'WakeRefused' : 'ToolRefused', + message: `${glimpse(answer.brief ?? refusalText(answer.json), REFUSAL_GLIMPSE_CHARS)} (the refusal's text, ${bytes} bytes, is too large for this script's memory, so only its start is here; nothing was done)`, + } + : { + name: RESULT_TOO_LARGE, + message: `${name}'s result, ${bytes} bytes, is too large for this script's memory; the call took effect, and none of its result reached the script`, + bytes, + })); + return; + } + settle(answer.ok, answer.json); + } + + /** + * A call no longer holds a place in the host: the bridge answered it, or it + * won't be sent. The first call waiting for room takes the place, in the + * same step, so no later call can take it first. Whatever lets a waiting + * call go takes it off the queue. One given its place once the run has + * ended or its process has exited isn't sent (the call's own check), and + * the end lets the calls still waiting go. + */ + private releasePlace(): void { + this.hostCalls -= 1; + const next = this.dispatchWaiting.entries().next(); + if (next.done) return; + const [nextId, go] = next.value; + this.dispatchWaiting.delete(nextId); + this.hostCalls += 1; + go(true); + } + + /** An answer to the call the child named `id`, sent once its turn comes. The id stays reserved until the child says the script has it. */ + private answer(id: number, ok: boolean, json: string): void { + this.sendResult(id, ok, json); + } + + /** A call or wake that comes once a stop has been asked isn't sent: the script is ending. */ + private stopRefusal(name: 'ToolRefused' | 'WakeRefused', tool: string): { name: string; message: string } | null { + return this.stopAsked ? { name, message: `${tool}: the script is being stopped, so nothing was sent` } : null; + } + + /** Resolves once the spacing since the last wake has passed, or at once when the run has ended. */ + private wakeTurn(): Promise { + const wait = this.nextWakeAt - performance.now(); + if (wait <= 0 || this.ending) return Promise.resolve(); + return new Promise((resolve) => { + const release = (): void => { + clearTimeout(timer); + this.wakeWaiters.delete(release); + resolve(); + }; + const timer = setTimeout(release, wait); + this.wakeWaiters.add(release); + }); + } + + /** + * Once the process has exited and the run has ended, nothing more goes to + * the child: an ended run kept for a later wait holds none of it. Whichever + * of the two comes second calls this. + */ + private release(): void { + this.child?.stdin?.destroy(); + this.child = null; + } + + private crash(detail: string): void { + if (this.ending) return; + this.end({ how: 'crashed', detail }); + this.child?.kill('SIGKILL'); + } + + private end(ending: Ending): void { + if (this.ending) return; + // Results sent and not yet seen were answered, and the script ended + // before it read them: each is named, a refusal counted. Calls still + // running may or may not take effect. A stop or a crash can leave either, + // whatever the ending: a script that had returned or thrown when a stop + // came ends as it did, with the calls its stop cut the wait for. So can a + // failure that escaped the script's promises, which ends the run at once, + // and a result the engine dropped as a stop came, which gets no seen. A + // TimeoutError the engine dropped so leaves its call named timed-out, as + // the host's timer had it, with no answer to name. Otherwise the child + // reports a finish or a failure only once every call it made is settled + // and seen. The host's count of the calls the bridge holds stays as it + // is, since those tools may still run: each frees its place as it + // answers, and one the bridge answered for with no result (held) never + // does. + for (const sent of this.delivered.values()) this.log.unseen(sent.entry, sent.outcome); + this.delivered.clear(); + this.pendingIds.clear(); + this.answeredIds.clear(); + this.inFlight.clear(); + this.log.ended(); + this.ending = ending; + if (this.lifetimeTimer) clearTimeout(this.lifetimeTimer); + // A child exits on its own once it has said how its script ended. One that + // doesn't, such as one stuck in its cleanup, is ended after the stop's + // grace, so it can't hold its place or outlive its run. + if (this.child && !this.processExited && !this.killTimer) { + this.killTimer = setTimeout(() => this.child?.kill('SIGKILL'), this.options.stopGraceMs); + this.killTimer.unref?.(); + } + for (const timer of this.callTimers) clearTimeout(timer); + this.callTimers.clear(); + // Nothing more goes to the child: waiting results are dropped, and calls + // waiting for a place go nowhere, so an ended run that's kept for a later + // wait holds neither. + this.resultQueue.length = 0; + for (const release of [...this.wakeWaiters]) release(); + this.endedResolve(); + // Calls waiting for room in the host are never sent, and the record counts + // them so: the script made them, and nothing was done. + for (const [waitingId, go] of [...this.dispatchWaiting]) { + this.dispatchWaiting.delete(waitingId); + this.log.unsent(); + go(false); + } + // So are wakes waiting for their turn: each sees the ending and goes. + for (; this.wakesWaiting > 0; this.wakesWaiting -= 1) this.log.unsent(); + for (const waiter of [...this.waiters]) waiter(); + if (this.processExited) this.release(); + this.options.onEnd?.(ending); + } +} diff --git a/src/code-execution/isolate/scripts.ts b/src/code-execution/isolate/scripts.ts new file mode 100644 index 000000000..a602f2360 --- /dev/null +++ b/src/code-execution/isolate/scripts.ts @@ -0,0 +1,465 @@ +/** + * Every agent's scripts: one kind of script, which runs in its agent's turn + * and, past its bound, yields with its output so far and keeps running. The + * agent then waits on it, stops it, or leaves it. + * + * - A left script that finishes cleanly, or that its agent stopped, says + * nothing; its result waits for a `wait` or a `list`, kept for the agent's + * newest ended scripts. One that fails, crashes or is stopped at its limit + * wakes its agent with its ending. + * - Only the primary agent's scripts outlive a turn (any agent's, when no + * primary agent is named). Another agent's yield says so, and at its turn's + * end its running scripts are stopped, as "stopped at its limit". That stop + * wakes no one, since the agent was told at the yield. + * - A turn that ends abnormally (a stream cancelled, an error, a quiesce) + * while a call still watches its script stops the script quietly if it + * hasn't returned yet; a wait it abandons leaves its script running, so a + * later failure still wakes the agent. A return made just before such an + * end counts as made, though its result is lost with the turn's. + * - An agent has at most `maxRunning` scripts running, and the host at most + * its capacity of children. + * - `wake_agent()` keeps today's spacing and cap: an early wake waits for its + * turn rather than being dropped, and past the cap it's refused. A wake + * still waiting for its turn when its script ends is never sent. + * + * The framework supplies what this can't know: the agent's tools now, the + * dispatcher a script's calls go through, and how to wake an agent. + */ + +import { ChildCapacity, ScriptRun, type CallAnswer, type Ending, type ScriptRunOptions, type ToolAnswer } from './runner.js'; +import type { CallSummary } from './calls.js'; +import type { OutputBounds } from './output.js'; +import type { PreludeTool } from './prelude.js'; +import { glimpse } from './render.js'; + +/** The run or wait call watching a script: its turn, which the script's calls carry, and whether one of them ended that turn. */ +export interface ScriptWatch { + turnId?: number; + endTurn: boolean; +} + +/** What the framework keeps with a script, handed back with each of its calls. */ +export interface ScriptCallContext { + /** RFC-007: what the script's calls are attributed to, its run call's origin. */ + origin?: unknown; + /** The call watching the script when this call reached the host (`host.dispatch`), if one was: the call carries its turn. One the script made while watched that waited, with 16 of its calls already out, takes the watch as it stood when it went out, not when the script made it. */ + watch?: ScriptWatch; + /** The call watching the script now, if one is: a result that ends the turn ends it at that call's return. Without it, `watch`. */ + watching?(): ScriptWatch | undefined; +} + +export interface ScriptsHost { + /** The tools a script of this agent may call now, without code_execution itself. */ + tools(agentName: string): PreludeTool[]; + /** The bridge: dispatch one of the script's calls as its agent's, through the ordinary dispatcher. Never rejects. */ + dispatch(agentName: string, toolName: string, argsJson: string, context: ScriptCallContext): Promise; + /** Wake the agent with this text: a script's wake_agent() payload, or a left script's ending. `label` names this wake, for a spilled copy. */ + wake(agentName: string, scriptId: string, text: string, label: string): Promise; + /** Whether this agent's scripts may outlive its turn. */ + outlivesTurn(agentName: string): boolean; +} + +export interface ScriptsConfig { + childEntry: string; + execPath?: string; + memoryMaxBytes: number; + maxStackBytes?: number; + output: OutputBounds; + flushMs: number; + /** The bound a run or wait yields at by default, and the most an agent may ask. */ + yieldMs: number; + maxYieldMs: number; + /** A script's limit. */ + lifetimeMs: number; + toolCallTimeoutMs: number; + stopGraceMs: number; + /** Scripts one agent may have running at once, yielded ones included. */ + maxRunning: number; + /** Ended scripts kept per agent for a later wait or list. */ + keepEnded: number; + wakeMinIntervalMs: number; + maxWakesPerScript: number; +} + +/** What one run, wait or stop returns. */ +export interface ScriptReturn { + scriptId: string; + /** Still running past the bound, or ended. */ + running: boolean; + /** The output since this script's previous return. */ + output: string; + /** True for the script's first return. */ + first: boolean; + calls: CallSummary; + ending: Ending | null; + /** True when this agent's script will be stopped at the end of its turn unless it waits for it. */ + stopsAtTurnEnd: boolean; +} + +/** One script in a listing. */ +export interface ScriptListing { + scriptId: string; + running: boolean; + ending: Ending | null; + startedAt: number; + endedAt: number | null; + /** The wakes the host delivered for it. */ + wakes: number; +} + +interface Script { + id: string; + agentName: string; + run: ScriptRun; + context: { origin?: unknown }; + /** The turn its run call was made in, when one was: its agent's turns end it by that. */ + turnId: number | undefined; + startedAt: number; + endedAt: number | null; + /** The order its ending came in among this host's: the newest ended are those kept. */ + endedSeq: number; + /** The delivery of a wake it sent that may not have reached its agent yet: its ending's notice follows it. */ + delivering: Promise | null; + /** The call watching it now, if one is: its ending goes there, and its calls carry its turn. */ + watch: ScriptWatch | null; + /** Its ending wakes no one: it was stopped where its agent was told it would be, or never saw it. */ + quietEnd: boolean; + /** Its agent has gone: nothing can wait on it or list it, so it leaves the map once it has ended. */ + gone: boolean; + /** Returns made so far. */ + returns: number; + /** A call watching it has returned its ending, so a notice of the ending would only repeat it. */ + endingReturned: boolean; +} + +/** Why a run, wait or stop couldn't be done: nothing was run or changed. */ +export class ScriptRefusal extends Error { + constructor(message: string) { + super(message); + this.name = 'ScriptRefusal'; + } +} + +/** The endings that wake a left script's agent: it didn't do its work, and not by its agent's choice. */ +export function endingWakes(ending: Ending): boolean { + return ending.how === 'failed' || ending.how === 'crashed' || (ending.how === 'stopped' && ending.reason === 'limit'); +} + +export class AgentScripts { + private readonly scripts = new Map(); + /** + * Marks this host's script ids, so an id from before a restart, still in an + * agent's history, almost surely names no script of this one: four base-36 + * characters, so about one restart in 1.7 million draws the tag of the host + * before it, and an old id could then name a new script of the same agent. + */ + private readonly idTag = Math.random().toString(36).slice(2, 6).padEnd(4, '0'); + /** Every run whose child process hasn't exited, kept or not: dispose() ends any still alive. */ + private readonly live = new Set(); + private counter = 0; + private endedCounter = 0; + + constructor( + private readonly host: ScriptsHost, + private readonly config: ScriptsConfig, + private readonly capacity: ChildCapacity, + ) {} + + /** The bound for this call: the agent's ask, capped, or the default. */ + bound(askedMs: number | undefined): number { + return askedMs === undefined ? this.config.yieldMs : Math.min(askedMs, this.config.maxYieldMs); + } + + async run( + agentName: string, + code: string, + askedMs?: number, + options: { origin?: unknown; watch?: ScriptWatch } = {}, + ): Promise { + // Listed first, so a module whose tool list throws refuses the run with nothing taken. + let tools: readonly PreludeTool[]; + try { + tools = this.host.tools(agentName); + } catch (err) { + throw new ScriptRefusal(`The script's tools couldn't be listed: ${err instanceof Error ? err.message : String(err)}. Nothing was run.`); + } + const running = this.running(agentName); + if (running >= this.config.maxRunning) { + throw new ScriptRefusal( + `You have ${running} scripts running, your most. Wait for one, stop one, or let one end before you start another. Nothing was run.`, + ); + } + if (!this.capacity.take()) { + throw new ScriptRefusal( + `This host is running ${this.capacity.running} scripts, its most. Try again once one has ended. Nothing was run.`, + ); + } + const id = `s${++this.counter}-${this.idTag}`; + const script: Script = { + id, + agentName, + run: null as unknown as ScriptRun, + context: options.origin === undefined ? {} : { origin: options.origin }, + turnId: options.watch?.turnId, + startedAt: Date.now(), + endedAt: null, + endedSeq: 0, + delivering: null, + watch: options.watch ?? { endTurn: false }, + quietEnd: false, + gone: false, + returns: 0, + endingReturned: false, + }; + const runOptions: ScriptRunOptions = { + childEntry: this.config.childEntry, + ...(this.config.execPath ? { execPath: this.config.execPath } : {}), + memoryMaxBytes: this.config.memoryMaxBytes, + ...(this.config.maxStackBytes === undefined ? {} : { maxStackBytes: this.config.maxStackBytes }), + output: this.config.output, + flushMs: this.config.flushMs, + lifetimeMs: this.config.lifetimeMs, + toolCallTimeoutMs: this.config.toolCallTimeoutMs, + stopGraceMs: this.config.stopGraceMs, + wakeMinIntervalMs: this.config.wakeMinIntervalMs, + maxWakes: this.config.maxWakesPerScript, + onToolCall: (name, args) => + this.host.dispatch(agentName, name, args, { + ...script.context, + ...(script.watch ? { watch: script.watch } : {}), + watching: () => script.watch ?? undefined, + }), + onWake: (payload, n) => this.wake(script, payload, n), + onEnd: (ending) => this.ended(script, ending), + }; + script.run = new ScriptRun(runOptions); + const run = script.run; + this.live.add(run); + void run.exited.then(() => { + this.live.delete(run); + this.capacity.release(); + }); + try { + script.run.start(code, tools); + } catch (err) { + // Nothing was spawned, and `exited` has resolved, which frees the place. + throw new ScriptRefusal(`The script's process could not start: ${err instanceof Error ? err.message : String(err)}. Nothing was run.`); + } + this.scripts.set(id, script); + // A script that ended at its start, before it was in the map, trimmed + // without itself: the trim is taken again with it there. + this.trimEnded(agentName); + return this.observe(script, this.bound(askedMs), script.watch!); + } + + async wait(agentName: string, scriptId: string, askedMs?: number, watch: ScriptWatch = { endTurn: false }): Promise { + const script = this.unwatched(this.own(agentName, scriptId)); + script.watch = watch; + return this.observe(script, this.bound(askedMs), watch); + } + + async stop(agentName: string, scriptId: string, watch: ScriptWatch = { endTurn: false }): Promise { + const script = this.unwatched(this.own(agentName, scriptId)); + script.watch = watch; + await script.run.stop('agent'); + return this.observe(script, 0, watch); + } + + list(agentName: string): ScriptListing[] { + return [...this.scripts.values()] + .filter((s) => s.agentName === agentName && !s.gone) + .map((s) => ({ + scriptId: s.id, + running: !s.run.ended, + ending: s.run.snapshot().ending, + startedAt: s.startedAt, + endedAt: s.endedAt, + wakes: s.run.wakesDelivered, + })); + } + + /** + * An agent's turn ended: `turn.id`, with `turn.live` the agent's turns still + * live (its open turn, and the turn of the stream that holds the agent), or + * `'every'` when all its turns end at once (the host stopping, or a quiesce + * that names no turn of its own). + * Turns can overlap, a silent one beside an open one, so each script goes by + * its own turn, the one its run call was made in. A turn that ended without + * its calls' answers (`abandoned`) leaves its calls watching scripts with no + * one to answer, whatever the agent: a script that hasn't returned yet is + * stopped, and one that has is let go. An agent whose scripts don't outlive + * a turn has stopped every script whose turn isn't live, and every one + * started in no turn: a script lives while the turn that started it does. + */ + turnEnded(agentName: string, turn: { id: number; live: readonly number[] } | 'every', abandoned: boolean, reason?: string): void { + const outlives = this.host.outlivesTurn(agentName); + for (const script of this.scripts.values()) { + if (script.agentName !== agentName || script.gone || script.run.ended) continue; + if (abandoned && script.watch && (turn === 'every' || script.watch.turnId === turn.id)) { + if (script.returns === 0) { + this.stopQuietly(script, `its turn ended before the script returned${reason ? ` (${reason})` : ''}`); + continue; + } + script.watch = null; + } + if (outlives) continue; + if (turn !== 'every' && script.turnId !== undefined && turn.live.includes(script.turnId)) continue; + this.stopQuietly(script, "its agent's turn ended, and only the primary agent's scripts outlive a turn"); + } + } + + /** + * An agent that has gone, such as an ephemeral one whose run ended: its + * scripts are stopped, and each leaves the map once it has ended, since no + * later run of the agent will trim them. + */ + agentGone(agentName: string): void { + for (const script of [...this.scripts.values()]) { + if (script.agentName !== agentName || script.gone) continue; + if (script.run.ended) { + this.scripts.delete(script.id); + continue; + } + script.gone = true; + this.stopQuietly(script, 'its agent ended'); + } + } + + /** Stop every running script, as the framework stops, and end any child process still alive after its script's ending. */ + async dispose(): Promise { + const running = [...this.scripts.values()].filter((s) => !s.run.ended); + for (const script of running) script.quietEnd = true; + await Promise.all(running.map((s) => s.run.stop('limit', 'the host stopped'))); + await Promise.all([...this.live].map((run) => run.kill())); + } + + /** Running scripts of this agent, yielded ones included; not those of an earlier agent of its name that has gone, which the host's count still holds. */ + running(agentName: string): number { + return [...this.scripts.values()].filter((s) => s.agentName === agentName && !s.gone && !s.run.ended).length; + } + + /** Running scripts that no call is watching: those acting outside any turn. */ + leftRunning(): number { + return [...this.scripts.values()].filter((s) => !s.run.ended && s.watch === null).length; + } + + private stopQuietly(script: Script, why: string): void { + script.quietEnd = true; + script.watch = null; + void script.run.stop('limit', why); + } + + private own(agentName: string, scriptId: string): Script { + const script = this.scripts.get(scriptId); + if (!script || script.agentName !== agentName || script.gone) { + throw new ScriptRefusal( + `You have no script ${JSON.stringify(scriptId)}: it isn't one of yours, it ran before this host restarted, ` + + `or it ended and was dropped: only your ${this.config.keepEnded} newest ended scripts are kept, and none of an earlier agent of your name that has gone. Nothing was changed.`, + ); + } + return script; + } + + /** A script no other call is watching now: one watching it gets its output and its ending. */ + private unwatched(script: Script): Script { + if (script.watch !== null) { + throw new ScriptRefusal(`Another of your calls is waiting on ${script.id} now, and its result will show how the script stands. Nothing was changed.`); + } + return script; + } + + private async observe(script: Script, ms: number, watch: ScriptWatch): Promise { + // A run that has ended returns its ending at once: marked now, so a notice + // whose turn comes before this return does doesn't repeat it. One that ends + // while this call watches it gives no notice at all (ended), so this is the + // only mark a returned ending needs. + if (script.run.ended && script.watch === watch) script.endingReturned = true; + const snap = await script.run.wait(ms); + // A call whose turn was abandoned no longer watches the script: its + // return reaches no one, so it takes none of the output from the next. + const current = script.watch === watch; + if (current) script.watch = null; + if (current) script.returns += 1; + return { + scriptId: script.id, + running: snap.ending === null, + output: current ? script.run.takeOutput() : snap.output, + first: script.returns <= 1, + calls: snap.calls, + ending: snap.ending, + stopsAtTurnEnd: snap.ending === null && !this.host.outlivesTurn(script.agentName), + }; + } + + private ended(script: Script, ending: Ending): void { + script.endedAt = Date.now(); + script.endedSeq = ++this.endedCounter; + if (script.gone) this.scripts.delete(script.id); + else this.trimEnded(script.agentName); + // A watched script's ending goes to the call watching it. A left one says + // nothing when it finished or its agent stopped it, and wakes its agent + // otherwise, after any wake it sent that is still being delivered: the + // ending is the script's last word. + if (script.watch || script.quietEnd || !endingWakes(ending)) return; + const send = (): void => { + // A wait or stop that took the ending while the notice waited has already given it. + if (script.endingReturned) return; + void this.host.wake(script.agentName, script.id, leftEndingText(script.id, ending), `${script.id}-ended`).catch(() => {}); + }; + if (script.delivering) void script.delivering.then(send); + else send(); + } + + /** Send the script's `n`th wake, whose turn the run has waited for. */ + private async wake(script: Script, payloadJson: string, n: number): Promise { + let delivery: Promise; + try { + delivery = Promise.resolve(this.host.wake( + script.agentName, + script.id, + wakeText(script.id, payloadJson, n, this.config.maxWakesPerScript, Date.now() - script.startedAt), + `${script.id}-wake${n}`, + )); + } catch (err) { + delivery = Promise.reject(err); + } + const done = delivery.then(() => {}, () => {}); + script.delivering = done; + void done.then(() => { + if (script.delivering === done) script.delivering = null; + }); + try { + await delivery; + return { ok: true, json: 'null' }; + } catch (err) { + return { + ok: false, + json: JSON.stringify({ name: 'WakeRefused', message: `the wake couldn't be delivered: ${err instanceof Error ? err.message : String(err)}` }), + }; + } + } + + /** Keep only the agent's newest ended scripts, newest by the order their endings came in. */ + private trimEnded(agentName: string): void { + const ended = [...this.scripts.values()] + .filter((s) => s.agentName === agentName && s.run.ended) + .sort((a, b) => a.endedSeq - b.endedSeq); + for (const s of ended.slice(0, Math.max(0, ended.length - this.config.keepEnded))) this.scripts.delete(s.id); + } +} + +function leftEndingText(scriptId: string, ending: Ending): string { + const how = ending.how === 'failed' + ? `failed: ${ending.error.name ? `${ending.error.name}: ` : ''}${glimpse(ending.error.message, 300)}` + : ending.how === 'crashed' + ? `crashed: ${ending.detail}` + : ending.how === 'stopped' + ? ending.why ? `was stopped: ${ending.why}` : 'was stopped at its limit' + : 'ended'; + return `[script ${scriptId}] Your script ${how}. {"action": "wait", "script_id": "${scriptId}"} gives its result.`; +} + +function wakeText(scriptId: string, payloadJson: string, n: number, max: number, ageMs: number): string { + const minutes = Math.round(ageMs / 60_000); + return `[script ${scriptId}] Woke you with wake_agent(), ${minutes} min after it started (wake ${n} of ${max}): ${payloadJson}`; +} diff --git a/test/code-execution-isolate-calls.test.ts b/test/code-execution-isolate-calls.test.ts new file mode 100644 index 000000000..0a4da0874 --- /dev/null +++ b/test/code-execution-isolate-calls.test.ts @@ -0,0 +1,85 @@ +/** The isolate's call record: what it counts, what it names, and its bounds. */ + +import { test } from 'node:test'; +import { strict as assert } from 'node:assert'; +import { CALL_BRIEF_CHARS, CALLS_NAMED, CallLog, callBrief } from '../src/code-execution/isolate/calls.js'; + +test('answered calls are counted by how they were answered, and none is kept', () => { + const log = new CallLog(); + for (const outcome of ['answered', 'answered', 'failed', 'refused'] as const) log.answered(log.start('echo', '{}'), outcome, ''); + assert.deepEqual(log.summary(), { made: 4, answered: 2, failed: 1, refused: 1, unsent: 0, running: 0, waiting: 0, named: [], more: {} }); +}); + +test('a call never sent, for want of room in the host, counts among those made, and apart from the refused', () => { + const log = new CallLog(); + log.unsent(); + log.answered(log.start('echo', '{}'), 'refused', ''); + assert.deepEqual(log.summary(), { made: 2, answered: 0, failed: 0, refused: 1, unsent: 1, running: 0, waiting: 0, named: [], more: {} }); +}); + +test("a refusal the script never saw is counted as a refusal, and nothing is named", () => { + const log = new CallLog(); + log.unseen(log.start('echo', '{}'), 'refused'); + assert.deepEqual(log.summary(), { made: 1, answered: 0, failed: 0, refused: 1, unsent: 0, running: 0, waiting: 0, named: [], more: {} }); +}); + +test("a result too large for the script's memory is named, since the script didn't see it", () => { + const log = new CallLog(); + log.answered(log.start('workspace--read', '{"path":"big.json"}'), 'too-large', ''); + assert.deepEqual(log.summary().named, [{ tool: 'workspace--read', args: '{"path":"big.json"}', state: 'too-large' }]); +}); + +test('calls still running when the script ends are named as uncertain, with their arguments', () => { + const log = new CallLog(); + log.start('workspace--write', '{"path":"a.md"}'); + log.start('workspace--write', '{"path":"b.md"}'); + assert.equal(log.summary().running, 2); + log.ended(); + assert.deepEqual(log.summary().named, [ + { tool: 'workspace--write', args: '{"path":"a.md"}', state: 'uncertain' }, + { tool: 'workspace--write', args: '{"path":"b.md"}', state: 'uncertain' }, + ]); + assert.equal(log.summary().running, 0); +}); + +test("a timed-out call is named, and an answer before the end names the answer's start", () => { + const log = new CallLog(); + const late = log.start('slow', '{"n":1}'); + const never = log.start('slow', '{"n":2}'); + log.timedOut(late); + log.timedOut(never); + log.answered(late, 'answered', 'Wrote 3 lines to notes.md'); + log.ended(); + assert.deepEqual(log.summary().named, [ + { tool: 'slow', args: '{"n":1}', state: 'timed-out-answered', answer: 'Wrote 3 lines to notes.md' }, + { tool: 'slow', args: '{"n":2}', state: 'timed-out' }, + ]); +}); + +test('the record stands as it was at the end: a later answer changes nothing', () => { + const log = new CallLog(); + const call = log.start('slow', '{}'); + log.timedOut(call); + log.ended(); + log.answered(call, 'answered', 'too late'); + assert.deepEqual(log.summary().named, [{ tool: 'slow', args: '{}', state: 'timed-out' }]); +}); + +test(`past ${CALLS_NAMED} named calls, the rest are counted by tool`, () => { + const log = new CallLog(); + for (let i = 0; i < CALLS_NAMED + 3; i++) log.start(i % 2 ? 'a' : 'b', `{"i":${i}}`); + log.ended(); + const s = log.summary(); + assert.equal(s.named.length, CALLS_NAMED); + // i = 10, 11 and 12 are past the named: b, a, b. + assert.deepEqual(s.more, { a: 1, b: 2 }); +}); + +test('arguments are cut to their brief, never between the halves of a surrogate pair', () => { + assert.equal(callBrief('x'.repeat(CALL_BRIEF_CHARS)), 'x'.repeat(CALL_BRIEF_CHARS)); + // An ordinary cut: the first CALL_BRIEF_CHARS - 1 characters, then the ellipsis. + assert.equal(callBrief('y'.repeat(CALL_BRIEF_CHARS + 50)), `${'y'.repeat(CALL_BRIEF_CHARS - 1)}…`); + const cut = callBrief('x'.repeat(CALL_BRIEF_CHARS - 2) + '\u{1F600}'.repeat(5)); + assert.ok(cut.length <= CALL_BRIEF_CHARS && cut.endsWith('…')); + assert.equal(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])/.test(cut), false); +}); diff --git a/test/code-execution-isolate-child.test.ts b/test/code-execution-isolate-child.test.ts new file mode 100644 index 000000000..106110205 --- /dev/null +++ b/test/code-execution-isolate-child.test.ts @@ -0,0 +1,606 @@ +/** + * The isolate's child, in process, on the stand-in engine: what it sends for each + * way a script can end, how it settles calls, how it bounds and flushes output, + * and how a stop reaches it idle or computing. + */ + +import { test } from 'node:test'; +import { strict as assert } from 'node:assert'; +import { mkdtempSync, writeFileSync, rmSync } from 'node:fs'; +import { execFileSync, spawn } from 'node:child_process'; +import { fileURLToPath } from 'node:url'; +import { extname } from 'node:path'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { runChild, type ChildIo } from '../src/code-execution/isolate/child.js'; +import type { ParentMessage, StartMessage } from '../src/code-execution/isolate/protocol.js'; +import { loadFakeEngine } from './helpers/fake-engine.js'; +import type { Engine, EngineSlots, RunState, SettleState } from '../src/code-execution/engine.js'; +import { MAX_BATCH_CHARS } from '../src/code-execution/isolate/output.js'; +import { encodeFrame, FrameDecoder } from '../src/code-execution/isolate/frames.js'; +import { bounded, NAME_CHARS, VALUE_CHARS } from '../src/code-execution/isolate/render.js'; +import { SCRIPT_FRAME, SCRIPT_LINE_OFFSET, SCRIPT_PLACE, scriptFrames, WAKE_CALL_NAME } from '../src/code-execution/isolate/prelude.js'; + +type Sent = Record; + +/** A value's JSON as the bytes a result frame carries. */ +const bytes = (value: unknown): Uint8Array => Buffer.from(JSON.stringify(value)); + +/** A child's channel in memory: its sends, a queue of parent frames, and a clock that moves 1 ms a read. */ +function channel(answer?: (call: Sent, push: (m: ParentMessage) => void) => void) { + const sent: Sent[] = []; + const queue: ParentMessage[] = []; + let waiter: (() => void) | null = null; + let t = 0; + const push = (m: ParentMessage) => { + queue.push(m); + waiter?.(); + }; + const io: ChildIo = { + async next() { + while (queue.length === 0) await new Promise((r) => (waiter = r)); + return queue.shift()!; + }, + send(message) { + // As the child's process sends it: a message too long for a frame throws, and nothing is sent. + const frame = encodeFrame(message); + const m = JSON.parse(frame.subarray(5).toString('utf8')) as Sent; + sent.push(m); + if (m.t === 'call' && answer) queueMicrotask(() => answer(m, push)); + }, + now: () => (t += 1), + }; + return { io, sent, push }; +} + +function start(script: string, over: Partial = {}): StartMessage { + return { + t: 'start', + script, + tools: [ + { name: 'echo', description: 'Echoes its arguments.' }, + { name: 'workspace--read', description: 'Reads a file.' }, + ], + memoryMaxBytes: 64 * 1024 * 1024, + stopFile: join(tmpdir(), `no-stop-${process.pid}-${Math.random()}`), + flushMs: 1_000_000, + maxWakes: 100, + parentPid: process.pid, + ...over, + } as StartMessage; +} + +const ends = (sent: Sent[]) => sent.filter((m) => m.t === 'end'); +const output = (sent: Sent[]) => ({ head: sent.filter((m) => m.t === 'out').map((m) => m.text as string).join('') }); + +const echo = (call: Sent, push: (m: ParentMessage) => void) => + push({ t: 'result', id: call.id as number, ok: true, json: bytes({ content: [{ type: 'text', text: call.args }], structuredContent: null, isError: false }) }); + +test('a script that calls a tool, prints and returns finishes with its value and its output', async () => { + const { io, sent } = channel(echo); + await runChild(io, start(`const r = await tools.echo({ n: 1 }); text(r.content[0].text); return { got: r.isError };`), loadFakeEngine); + assert.deepEqual(sent.filter((m) => m.t === 'call').map((m) => [m.name, m.args]), [['echo', '{"n":1}']]); + assert.equal(output(sent).head, '{"n":1}\n'); + assert.deepEqual(ends(sent), [{ t: 'end', how: 'finished', value: '{"got":false}' }]); +}); + +test('a script that throws fails with the error, and its output so far is sent first', async () => { + const { io, sent } = channel(echo); + await runChild(io, start(`text('before'); throw new RangeError('no such page');`), loadFakeEngine); + const last = sent.at(-1)!; + assert.equal(last.how, 'failed'); + assert.equal((last.error as Sent).name, 'RangeError'); + assert.equal((last.error as Sent).message, 'no such page'); + assert.equal(output(sent).head, 'before\n'); + assert.ok(sent.findIndex((m) => m.t === 'out') < sent.findIndex((m) => m.t === 'end')); +}); + +test('a refusal before dispatch reaches the script as an Error with its fields', async () => { + const { io, sent } = channel((call, push) => + push({ t: 'result', id: call.id as number, ok: false, json: bytes({ name: 'ToolRefused', message: 'not allowed now; nothing was sent', code: 'not_allowed' }) })); + await runChild(io, start(`try { await tools.echo({}); } catch (e) { return [e instanceof Error, e.name, e.message, e.code]; }`), loadFakeEngine); + assert.deepEqual(ends(sent), [{ t: 'end', how: 'finished', value: '[true,"ToolRefused","not allowed now; nothing was sent","not_allowed"]' }]); +}); + +test('a name that is not a tool throws at once, naming the close match, and sends nothing', async () => { + const { io, sent } = channel(echo); + await runChild(io, start(`try { tools['workspace-read']; } catch (e) { return e.message; }`), loadFakeEngine); + assert.equal(sent.filter((m) => m.t === 'call').length, 0); + const value = JSON.parse(ends(sent)[0]!.value as string) as string; + assert.match(value, /is not one of your tools\. Did you mean tools\["workspace--read"\]\? Nothing was sent\./); +}); + +test('a script waiting on a promise nothing can settle fails at once, as stalled', async () => { + const { io, sent } = channel(echo); + await runChild(io, start(`await new Promise(() => {});`), loadFakeEngine); + const end = ends(sent)[0]!; + assert.equal(end.how, 'failed'); + assert.equal(end.stalled, true); +}); + +test('a script that returns with a call in flight ends only once that call is answered', async () => { + let held: Sent | null = null; + const { io, sent, push } = channel((call) => { held = call; }); + const done = runChild(io, start(`tools.echo({ late: true }).then((r) => text('answered')); return 'returned';`), loadFakeEngine); + // Once the call is out, the guest has returned and waits on it. + for (let i = 0; i < 500 && held === null; i++) await new Promise((r) => setTimeout(r, 2)); + assert.notEqual(held, null, 'the call went out'); + await new Promise((r) => setImmediate(r)); + assert.equal(ends(sent).length, 0, 'not ended while the call is in flight'); + push({ t: 'result', id: held!.id as number, ok: true, json: Buffer.from('{"content":[],"structuredContent":null,"isError":false}') }); + await done; + assert.deepEqual(ends(sent), [{ t: 'end', how: 'finished', value: '"returned"' }]); + assert.equal(output(sent).head, 'answered\n'); +}); + +test('a stop frame ends an idle script as stopped', async () => { + const { io, sent, push } = channel(() => {}); + const done = runChild(io, start(`await tools.echo({});`), loadFakeEngine); + await new Promise((r) => setTimeout(r, 5)); + push({ t: 'stop' }); + await done; + assert.deepEqual(ends(sent), [{ t: 'end', how: 'stopped' }]); +}); + +test('the stop file stops a spinning guest as soon as it exists, empty as the parent makes it', async () => { + const dir = mkdtempSync(join(tmpdir(), 'isolate-stop-')); + try { + const stopFile = join(dir, 'stop'); + const { io, sent } = channel(echo); + let ticks = 0; + // Bounded, so a stop the guest never sees fails the test rather than hanging it. + await runChild(io, start(`text('spinning'); for (let i = 0; i < 1e6; i++) __tick(); return 'never stopped';`, { stopFile }), async (limits, slots) => + loadFakeEngine(limits, { + ...slots, + interrupt() { + if (++ticks === 20) writeFileSync(stopFile, ''); + return slots.interrupt(); + }, + })); + assert.deepEqual(ends(sent), [{ t: 'end', how: 'stopped' }]); + assert.equal(output(sent).head, 'spinning\n'); + } finally { + rmSync(dir, { recursive: true, force: true }); + } +}); + +test("a call whose arguments pass a call's bytes or values is answered in the child, and nothing is sent", async () => { + const { io, sent } = channel(echo); + await runChild( + io, + start( + `const out = []; + try { await tools.echo({ big: 'x'.repeat(4 * 1024 * 1024) }); } catch (e) { out.push(e.name, e.bytes > 4 * 1024 * 1024, /nothing was sent/.test(e.message)); } + try { await tools.echo({ many: Array.from({ length: 100001 }, () => 1) }); } catch (e) { out.push(e.name); } + try { await wake_agent({ long: 'w'.repeat(65 * 1024) }); } catch (e) { out.push(e.name, /64 KiB|65536/.test(e.message)); } + return out;`, + ), + loadFakeEngine, + ); + assert.equal(sent.filter((m) => m.t === 'call').length, 0); + assert.deepEqual(ends(sent), [{ t: 'end', how: 'finished', value: '["ArgumentsTooLarge",true,true,"ArgumentsTooLarge","WakeRefused",true]' }]); +}); + +test("while a result settles, the calls the guest's jobs make go out after its seen", async () => { + const { io, sent } = channel(echo); + // Each answer's continuation makes the next call, inside the settle that delivers the answer. + await runChild(io, start(`await tools.echo({ n: 1 }); await tools.echo({ n: 2 }); return 'done';`), loadFakeEngine); + const order = sent.filter((m) => m.t === 'call' || m.t === 'seen').map((m) => `${m.t} ${m.id}`); + assert.deepEqual(order, ['call 1', 'seen 1', 'call 2', 'seen 2']); +}); + +test("the ending's value, or a failure's name, message and stack, goes out cut to what a return shows", async () => { + const value = channel(echo); + await runChild(value.io, start(`return 'v'.repeat(20000);`), loadFakeEngine); + const finished = ends(value.sent)[0]!; + assert.ok((finished.value as string).length < 8200, `the value went out at ${(finished.value as string).length} characters`); + assert.match(finished.value as string, /characters dropped here/); + const failure = channel(echo); + await runChild(failure.io, start(`const e = new Error('m'.repeat(20000)); e.name = 'N'.repeat(20000); throw e;`), loadFakeEngine); + const failed = ends(failure.sent)[0]! as { error: { name: string; message: string; stack?: string } }; + assert.ok(failed.error.message.length < 8200); + assert.match(failed.error.message, /characters dropped here/); + // A name is a word: it keeps far less, so the message and the frames keep the room. + assert.ok(failed.error.name.length < 300, `the name went out at ${failed.error.name.length} characters`); + // The frames go apart from the name and message, put as the agent reads them and cut on their own, as many as a deep recursion leaves. + const deep = channel(echo); + const frames = Array.from({ length: 2_000 }, (_, i) => ` at f (:${i + 3}:5)`).join('\n'); + await runChild(deep.io, start('ignored'), scriptedEngine([], { kind: 'failed', name: 'N'.repeat(500), message: 'Maximum call stack size exceeded', stack: frames }, () => ({ kind: 'pending' }))); + const recursed = ends(deep.sent)[0]! as { error: { name: string; message: string; stack?: string } }; + // Cut once, so the count dropped is the frames' own, not a first cut's marker. + assert.equal(recursed.error.stack, bounded(scriptFrames(frames), VALUE_CHARS)); + assert.ok(recursed.error.stack !== undefined && recursed.error.stack.length < 8200, `the frames went out at ${recursed.error.stack?.length} characters`); + assert.match(recursed.error.stack!, /^ {4}at f \(script:1:5\)\n/); + assert.match(recursed.error.stack!, /characters dropped here/); + assert.match(recursed.error.stack!, / {4}at f \(script:2000:5\)$/); +}); + +test('a spinning guest whose io has no orphaned hook stops once its parent has gone', async () => { + // child-main's io ends the process instead (orphaned), which the tests of a child process below run. + const { io, sent } = channel(echo); + let checks = 0; + io.parentGone = () => ++checks > 3; + await runChild(io, start(`text('spinning'); for (let i = 0; i < 1e6; i++) __tick(); return 'never stopped';`), loadFakeEngine); + assert.deepEqual(ends(sent), [{ t: 'end', how: 'stopped' }]); +}); + +test('an orphan ends before it sends the output waiting to go, when the two fall due at one check', async () => { + const { io, sent } = channel(echo); + // The clock stands still until the first look for the parent, then jumps + // past flushMs: at the next check, the parent is gone and the output written + // before it is due, and the parent is looked for first. + let clock = 0; + io.now = () => clock; + let looks = 0; + let sentBeforeOrphaned: string[] | undefined; + io.parentGone = () => { + if (++looks === 1) { + clock = 100; + return false; + } + return true; + }; + io.orphaned = (() => { + sentBeforeOrphaned = sent.map((m) => m.t as string); + }) as () => never; + await runChild(io, start(`text('pending'); for (let i = 0; i < 1e6; i++) __tick(); return 'never stopped';`, { flushMs: 10 }), loadFakeEngine); + assert.deepEqual(sentBeforeOrphaned, [], 'nothing went out before the orphan ended'); + // This io's orphaned returns, so the guest is stopped, and the output then goes with the ending. + assert.deepEqual(sent.map((m) => m.t), ['out', 'end']); +}); + +test('an orphan whose stop file is there too ends its process rather than stop its guest', async () => { + const dir = mkdtempSync(join(tmpdir(), 'isolate-stop-')); + try { + const stopFile = join(dir, 'stop'); + writeFileSync(stopFile, ''); + const { io, sent } = channel(echo); + io.parentGone = () => true; + let orphaned = 0; + io.orphaned = (() => { orphaned += 1; }) as () => never; + await runChild(io, start(`for (let i = 0; i < 1e6; i++) __tick(); return 'never stopped';`, { stopFile }), loadFakeEngine); + // A guest can keep a stop from ending it, and with its parent gone nothing would kill it. + assert.equal(orphaned, 1); + assert.deepEqual(ends(sent), [{ t: 'end', how: 'stopped' }]); + } finally { + rmSync(dir, { recursive: true, force: true }); + } +}); + +test('a printing loop is flushed in bounded batches, and every character arrives', async () => { + const { io, sent } = channel(echo); + await runChild(io, start(`for (let i = 0; i < 20000; i++) text('line ' + i);`), loadFakeEngine); + const outs = sent.filter((m) => m.t === 'out').map((m) => m.text as string); + assert.ok(outs.length > 1, 'more than one batch'); + assert.ok(outs.every((t) => t.length <= MAX_BATCH_CHARS), 'no batch past the bound'); + assert.equal(outs.join(''), Array.from({ length: 20000 }, (_, i) => `line ${i}\n`).join('')); +}); + +test('a stop after the script has returned ends the wait for its calls, and the script ends as it did', async () => { + const { io, sent, push } = channel(() => {}); + const done = runChild(io, start(`tools.echo({ late: true }); return 'returned';`), loadFakeEngine); + for (let i = 0; i < 500 && !sent.some((m) => m.t === 'call'); i++) await new Promise((r) => setTimeout(r, 2)); + push({ t: 'stop' }); + await done; + // Its own ending came first: the stop cut only the wait for the call in flight. + assert.deepEqual(ends(sent), [{ t: 'end', how: 'finished', value: '"returned"' }]); +}); + +test('output written before a call reaches the parent before the call does', async () => { + const { io, sent } = channel(echo); + await runChild(io, start(`text('before'); await tools.echo({}); return 1;`), loadFakeEngine); + const order = sent.filter((m) => m.t === 'out' || m.t === 'call').map((m) => m.t); + assert.deepEqual(order, ['out', 'call']); +}); + +test("a wake made while a result settles goes out at once, ahead of that result's seen", async () => { + const { io, sent } = channel(echo); + await runChild(io, start(`await tools.echo({}); wake_agent('working'); for (let i = 0; i < 1e5; i++) __tick(); return 1;`), loadFakeEngine); + const order = sent.filter((m) => m.t === 'call' || m.t === 'seen').map((m) => `${m.t} ${m.name ?? m.id}`); + // The wake takes no place among the calls in flight, so nothing holds it for the seen. + assert.deepEqual(order, ['call echo', 'call (wake_agent)', 'seen 1', 'seen 2']); +}); + +test('a wake refused for its size uses none of the script\'s wakes', async () => { + const { io, sent } = channel((call, push) => push({ t: 'result', id: call.id as number, ok: true, json: bytes(null) })); + await runChild(io, start( + `let first; try { await wake_agent('x'.repeat(70000)); } catch (e) { first = e.name; } await wake_agent('small'); try { await wake_agent('one more'); } catch (e) { return [first, e.name, /wake limit reached: 1 per script/.test(e.message)]; }`, + { maxWakes: 1 }, + ), loadFakeEngine); + assert.deepEqual(sent.filter((m) => m.t === 'call').map((m) => m.args), ['"small"']); + assert.deepEqual(ends(sent), [{ t: 'end', how: 'finished', value: '["WakeRefused","WakeRefused",true]' }]); +}); + +test("a child whose start names a parent that isn't its parent ends its process, as an orphan does", async () => { + const entry = fileURLToPath(new URL(`./helpers/fake-child-entry${extname(fileURLToPath(import.meta.url))}`, import.meta.url)); + const dir = mkdtempSync(join(tmpdir(), 'af-orphan-')); + const child = spawn(process.execPath, [entry], { stdio: ['pipe', 'ignore', 'pipe', 'pipe'], cwd: dir, env: {} }); + try { + const frames: Sent[] = []; + const decoder = new FrameDecoder(); + (child.stdio[3] as NodeJS.ReadableStream).on('data', (chunk: Buffer) => { for (const v of decoder.push(chunk)) frames.push(v as Sent); }); + // Its parent is this process, but the start names another: what a child whose parent died before it read its start sees. + child.stdin!.write(encodeFrame(start(`for (;;) __tick();`, { stopFile: join(dir, 'stop'), parentPid: process.pid + 100_000 }))); + // fd 3's last data can come after the exit, so both are waited for. + const exited = new Promise((resolve) => child.on('exit', (code) => resolve(code))); + const drained = new Promise((resolve) => (child.stdio[3] as NodeJS.ReadableStream).on('end', () => resolve())); + let bound: NodeJS.Timeout | undefined; + const code = await Promise.race([ + Promise.all([exited, drained]).then(([c]) => c), + new Promise<'spinning'>((resolve) => (bound = setTimeout(() => resolve('spinning'), 10_000))), + ]); + clearTimeout(bound); + // An orphan has no one to report to: it ends its process at once. + assert.equal(code, 0, 'the child ended'); + assert.deepEqual(frames.filter((m) => m.t === 'end'), []); + } finally { + child.kill('SIGKILL'); + rmSync(dir, { recursive: true, force: true }); + } +}); + +test("a stop that interrupts a call's continuation after the script returned leaves the script's ending as it was", async () => { + const { io, sent } = channel(echo); + let checks = 0; + // The parent goes once the continuation spins: the interrupt stops the guest. + io.parentGone = () => ++checks > 3; + // Its rejection is caught, so the stand-in under Bun, which reports a guest's unhandled rejection, has none to report. + await runChild(io, start(`tools.echo({}).then(() => { for (;;) __tick(); }).catch(() => {}); return 'returned';`), loadFakeEngine); + assert.deepEqual(ends(sent), [{ t: 'end', how: 'finished', value: '"returned"' }]); +}); + +test('a spinning child whose parent is killed stops, orphaned for real', async () => { + const ext = extname(fileURLToPath(import.meta.url)); + const childEntry = fileURLToPath(new URL(`./helpers/fake-child-entry${ext}`, import.meta.url)); + const parentEntry = fileURLToPath(new URL(`./helpers/orphan-parent-entry${ext}`, import.meta.url)); + const dir = mkdtempSync(join(tmpdir(), 'af-orphan-')); + // It says it's running, from inside its run, and spins: its parent goes only after that. + const { parentPid: _unused, ...message } = start(`text('spinning'); for (;;) __tick();`, { stopFile: join(dir, 'stop'), flushMs: 0 }) as unknown as Record; + const parent = spawn(process.execPath, [parentEntry, childEntry], { stdio: ['ignore', 'pipe', 'inherit'], env: { ...process.env, CHILD_DIR: dir, START: JSON.stringify(message) } }); + let childPid = 0; + try { + childPid = await new Promise((resolve, reject) => { + let out = ''; + parent.stdout!.on('data', (chunk: Buffer) => { + out += chunk.toString(); + if (out.includes('\n')) resolve(Number(out.trim())); + }); + parent.on('exit', () => reject(new Error('the parent exited before it started its child'))); + }); + // Gone, or a zombie its new parent hasn't reaped yet, which ps marks Z on Linux and macOS alike. + const alive = () => { + try { process.kill(childPid, 0); } catch { return false; } + // Where ps can't say (no ps, as on Windows, or the process reaped since + // the signal's answer), the signal is asked again. + try { return !execFileSync('ps', ['-o', 'stat=', '-p', String(childPid)], { encoding: 'utf8' }).trim().startsWith('Z'); } catch { + try { process.kill(childPid, 0); return true; } catch { return false; } + } + }; + assert.equal(alive(), true, 'the child is running'); + // Its parent goes; nothing tells the child but its parent pid changing. + parent.kill('SIGKILL'); + const deadline = Date.now() + 10_000; + while (alive() && Date.now() < deadline) await new Promise((r) => setTimeout(r, 20)); + assert.equal(alive(), false, 'the orphaned child stopped'); + } finally { + parent.kill('SIGKILL'); + if (childPid) try { process.kill(childPid, 'SIGKILL'); } catch { /* gone */ } + rmSync(dir, { recursive: true, force: true }); + } +}); + +/** An engine standing in for QuickJS's answers: start makes the calls named and answers `started`; each settle answers from `settled`. */ +function scriptedEngine(calls: string[], started: RunState, settled: (id: number) => SettleState) { + return async (_limits: unknown, slots: EngineSlots): Promise => ({ + start() { + calls.forEach((name, i) => slots.hostCall(i + 1, name, '{}')); + return started; + }, + settle: (id: number) => settled(id), + dispose() {}, + }) as unknown as Engine; +} + +test("a failure after the script's ending was kept ends the run as failed, at once", async () => { + const { io, sent } = channel(echo); + // The script returned with a call out; that call's result runs a callback that throws. + await runChild(io, start('ignored'), scriptedEngine(['echo'], { kind: 'fulfilled', json: '1' }, () => ({ kind: 'failed', name: 'Error', message: 'late', stack: ' at cb (:4:3)\n at ' }))); + assert.deepEqual(sent.filter((m) => m.t === 'seen').map((m) => m.id), [1], 'the result reached the script'); + assert.deepEqual(ends(sent), [{ t: 'end', how: 'failed', error: { name: 'Error', message: 'late', stack: ' at cb (script:2:3)' } }]); +}); + +test("a result that never reached the script has no seen, and a failure ends the run without waiting for the calls still out", async () => { + // Only the first call is answered: a child that waited for the second would never end. + const { io, sent } = channel((call, push) => { if (call.id === 1) echo(call, push); }); + const running = runChild(io, start('ignored'), scriptedEngine(['echo', 'echo'], { kind: 'pending' }, () => ({ kind: 'failed', name: 'InternalError', message: 'out of memory', dropped: true }))); + await Promise.race([running, new Promise((_, reject) => setTimeout(() => reject(new Error('the child kept waiting for the call still out')), 5_000))]); + assert.deepEqual(sent.filter((m) => m.t === 'seen'), [], 'the script never read it, and the run is over'); + assert.deepEqual(ends(sent), [{ t: 'end', how: 'failed', error: { name: 'InternalError', message: 'out of memory' } }]); +}); + +/** An engine standing in for QuickJS, each of whose parts a test sets. */ +function stubEngine(parts: { start?: (slots: EngineSlots) => RunState; settle?: (id: number, ok: boolean, json: unknown, unfit: string, slots: EngineSlots) => SettleState; dispose?: () => void }) { + return async (_limits: unknown, slots: EngineSlots): Promise => ({ + start: () => (parts.start ? parts.start(slots) : { kind: 'pending' }), + settle: (id: number, ok: boolean, json: unknown, unfit: string) => (parts.settle ? parts.settle(id, ok, json, unfit, slots) : { kind: 'pending' }), + dispose: () => parts.dispose?.(), + }) as unknown as Engine; +} + +test("a failure in the run stands over dispose()'s, and dispose()'s alone is thrown", async () => { + const loop = channel(); + await assert.rejects( + runChild(loop.io, start('ignored'), stubEngine({ start: () => { throw new Error('the loop failed'); }, dispose: () => { throw new Error('dispose failed'); } })), + /the loop failed/, + ); + const clean = channel(); + await assert.rejects( + runChild(clean.io, start('ignored'), stubEngine({ start: () => ({ kind: 'fulfilled', json: '1' }), dispose: () => { throw new Error('dispose failed'); } })), + /dispose failed/, + ); +}); + +test('a run that fails at its start ends as failed', async () => { + const { io, sent } = channel(); + await runChild(io, start('ignored'), stubEngine({ start: () => ({ kind: 'failed', name: 'Error', message: 'at once' }) })); + assert.deepEqual(ends(sent), [{ t: 'end', how: 'failed', error: { name: 'Error', message: 'at once' } }]); +}); + +test("a failure in a call answered in the child ends the run, and settles no more of them", async () => { + const { io, sent } = channel(); + let settles = 0; + // Two wakes past a cap of 0, answered in the child, the first's settle failing. + await runChild(io, start('ignored', { maxWakes: 0 }), stubEngine({ + start: (slots) => { slots.hostCall(1, '(wake_agent)', '1'); slots.hostCall(2, '(wake_agent)', '2'); return { kind: 'pending' }; }, + settle: () => { settles += 1; return { kind: 'failed', name: 'Error', message: 'in a job' }; }, + })); + assert.equal(settles, 1); + assert.deepEqual(ends(sent), [{ t: 'end', how: 'failed', error: { name: 'Error', message: 'in a job' } }]); +}); + +test("an answer in a result's place the script can't take keeps its name and its message's start", async () => { + let unfitGiven = ''; + const refusal = JSON.stringify({ name: 'ToolRefused', message: `not allowed: ${'no '.repeat(5_000)}`, tool: 'echo' }); + const { io } = channel((call, push) => push({ t: 'result', id: call.id as number, ok: false, json: Buffer.from(refusal) })); + await runChild(io, start('ignored'), stubEngine({ + start: (slots) => { slots.hostCall(1, 'echo', '{}'); return { kind: 'pending' }; }, + settle: (_id, _ok, _json, unfit) => { unfitGiven = unfit; return { kind: 'fulfilled', json: '1', unfit: true }; }, + })); + const given = JSON.parse(unfitGiven) as { name: string; message: string; bytes: number }; + assert.equal(given.name, 'ToolRefused'); + assert.match(given.message, /^not allowed: no no/); + assert.ok(given.message.length < 2_400, `${given.message.length} characters`); + assert.match(given.message, /couldn't be received by this script whole: it was too large for its memory, or nested deeper than 2000 levels\)$/); + assert.equal(given.bytes, Buffer.byteLength(refusal)); +}); + +test("an unfit answer's name is held to a name's bound, and a wake's is named as the script wrote it", async () => { + const given: string[] = []; + const answer = JSON.stringify({ name: 'N'.repeat(5_000), message: 'refused' }); + const { io } = channel((call, push) => push({ t: 'result', id: call.id as number, ok: call.name === WAKE_CALL_NAME, json: Buffer.from(call.name === WAKE_CALL_NAME ? '[' : answer) })); + await runChild(io, start('ignored'), stubEngine({ + start: (slots) => { slots.hostCall(1, 'echo', '{}'); slots.hostCall(2, WAKE_CALL_NAME, '1'); return { kind: 'pending' }; }, + settle: (_id, _ok, _json, unfit) => { given.push(unfit); return given.length === 2 ? { kind: 'fulfilled', json: '1', unfit: true } : { kind: 'pending', unfit: true }; }, + })); + const [refused, wake] = given.map((g) => JSON.parse(g) as { name: string; message: string; bytes: number }); + assert.equal(refused!.name, bounded('N'.repeat(5_000), NAME_CHARS)); + assert.equal(refused!.bytes, Buffer.byteLength(answer)); + assert.match(wake!.message, /^wake_agent's result, 1 bytes, couldn't be received by this script: .*Calling it again would do the call again, its effect included, and likely bring the same result, so if the call only reads, ask the tool for less if it can$/); +}); + +test('a guest that makes more calls than it may have out while a result settles has the rest sent at once, and is stopped', async () => { + const { io, sent } = channel((call, push) => { if (call.id === 1) echo(call, push); }); + await runChild(io, start('ignored'), stubEngine({ + start: (slots) => { slots.hostCall(1, 'echo', '{}'); return { kind: 'pending' }; }, + // While the result settles, the guest's jobs call 20 times more. + settle: (_id, _ok, _json, _unfit, slots) => { + for (let i = 2; i <= 21; i++) slots.hostCall(i, 'echo', '{}'); + return { kind: 'pending' }; + }, + })); + const order = sent.filter((m) => m.t === 'call' || m.t === 'seen').map((m) => (m.t === 'seen' ? 'seen' : m.id)); + // The four past the 16 held went out at once; the seen, then the 16 held. + assert.deepEqual(order.slice(0, 6), [1, 18, 19, 20, 21, 'seen']); + assert.equal(order.length, 1 + 4 + 1 + 16); + assert.deepEqual(ends(sent), [{ t: 'end', how: 'stopped' }]); +}); + +test("a failure's frames are the script's own, numbered as written: the lines of its stack shaped as QuickJS writes them", () => { + const raw = [ + ' at f (:5:11)', + ' at (:7:1)', + ' at run (:250:20)', + ' at ', + ' at (script.js:1:7)', + '', + ].join('\n'); + // QuickJS's own stack: frames only, ending in a newline. + assert.equal(scriptFrames(raw), ' at f (script:3:11)\n at (script:5:1)'); + // A line before the script's first is its first, and a syntax error's place has no parentheses. + assert.equal(scriptFrames(' at f (:1:2)\n at :4:9'), ' at f (script:1:2)\n at script:2:9'); + // No frame of the script's: none. + assert.equal(scriptFrames(' at '), ''); + assert.equal(scriptFrames(''), ''); + // A stack the script set, opening as V8's do: its head, a line of its message shaped as a frame, and a line naming the + // script's source are no frames. Only a line shaped as QuickJS writes one of the script's frames is. + assert.equal(scriptFrames('Error: see :4\n at note (script:9:1)\nsee :3\n at f (:5:5)'), ' at f (script:3:5)'); + // A function's name with a line break splits its frame, which is left out, and no other is; one with leading spaces is kept. + assert.equal( + scriptFrames(' at fail (:3:20)\n at outer\nlabel (:4:30)\n at run (:5:1)\n at anonymous (:6:1)'), + ' at fail (script:1:20)\n at run (script:3:1)\n at anonymous (script:4:1)', + ); + // A name that holds a place of the script's: only the place at the line's end is renumbered. + assert.equal(scriptFrames(' at x :9:9 (:5:5)'), ' at x :9:9 (script:3:5)'); + // A line whose place of the script's isn't at its end isn't a frame. + assert.equal(scriptFrames(' at f (:5:5) and more'), ''); + // Nor is one that doesn't open with the spaces and `at` a frame opens with, though it holds one after its start. + assert.equal(scriptFrames('Error: boom at f (:5:5)\nboom at :6:1'), ''); + // A name may hold a carriage return, U+2028 or U+2029: only a line feed splits a frame. + assert.equal( + scriptFrames(' at a\rb (:5:1)\n at c\u2028d (:6:2)\n at e\u2029f (:7:3)'), + ' at a\rb (script:3:1)\n at c\u2028d (script:4:2)\n at e\u2029f (script:5:3)', + ); + // Cut as they're read, as bounded cuts them: a stack the script set can be any length, as many lines as characters. + const long = Array.from({ length: 3_000 }, (_, i) => ` at f${i} (:${i + 3}:5)`).join('\nnot a frame\n'); + const expected = bounded(scriptFrames(long), 8_000); + assert.equal(scriptFrames(long, 8_000), expected); + assert.equal(scriptFrames(long, 1_000_000), scriptFrames(long)); + assert.equal(scriptFrames('\n'.repeat(2_000_000), 8_000), ''); + // A line at a time, the stack never split whole: it can hold more lines than an array can hold elements (Alfred-2570's + // run crashed V8 at 140 million). + const split = String.prototype.split; + (String.prototype as { split: unknown }).split = () => { throw new Error('scriptFrames split the whole stack'); }; + try { + assert.equal(scriptFrames(long, 8_000), expected); + } finally { + String.prototype.split = split; + } + // One frame longer than the cut: its head and its end, the place renumbered. + const wide = ` at ${'n'.repeat(50_000)} (:7:3)`; + assert.equal(scriptFrames(wide, 8_000), bounded(` at ${'n'.repeat(50_000)} (script:5:3)`, 8_000)); +}); + +test("the frames read from a whole stack are the rule's, line by line, and their cut is bounded's", () => { + // The rule as the guest's copy applies it, a line at a time (errorText). + const rule = (stack: string): string => stack.split('\n') + .filter((line) => SCRIPT_FRAME.test(line)) + .map((line) => line.replace(SCRIPT_PLACE, (_m, n: string, rest: string) => `script:${Math.max(1, Number(n) - SCRIPT_LINE_OFFSET)}${rest}`)) + .join('\n'); + // mulberry32, whose 32-bit steps lose nothing in a JavaScript number. + let seed = 11; + const next = (n: number) => { + seed = (seed + 0x6d2b79f5) | 0; + let t = Math.imul(seed ^ (seed >>> 15), 1 | seed); + t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; + return Math.floor((((t ^ (t >>> 14)) >>> 0) / 4294967296) * n); + }; + const pick = (items: readonly T[]): T => items[next(items.length)]!; + // Lines near a frame's shape and in it: its spaces, `at`, a name of any characters but a line feed (surrogates, lone + // halves, carriage returns and line separators among them, and a place of its own), a place whose line has up to + // eleven digits, and what may close it or follow it. + const space = [' ', ' ', '\t', '\u00a0', '\u2028', '', ' \r']; + const name = ['f', '', '', 'a\rb', 'c\u2028d', '\u{1F600}', '\ud800', 'x :9:9', 'at', ' ']; + const digitsOf = (n: number) => Array.from({ length: n }, () => String(next(10))).join(''); + const line = (): string => { + const lead = pick(space) + pick(space); + const head = pick(['at ', 'at\t', 'at\u00a0', 'at\u2028', 'at', 'At ', '']); + const place = `:${digitsOf(1 + next(11))}:${digitsOf(next(3))}`; + const open = pick(['(', '']); + const close = open ? pick([')', '', ') and more']) : pick(['', ')']); + return lead + head + pick(name) + (open ? ` ${open}` : ' ') + place + close; + }; + for (let round = 0; round < 400; round++) { + const lines = Array.from({ length: next(25) }, () => (next(6) === 0 ? pick(['', 'not a frame', '\r']) : line())); + const stack = lines.join('\n') + pick(['', '\n']); + const max = next(5) === 0 ? Infinity : next(200); + const expected = rule(stack); + assert.equal(scriptFrames(stack), expected, JSON.stringify(stack)); + assert.equal(scriptFrames(stack, max), max === Infinity ? expected : bounded(expected, max), JSON.stringify({ stack, max })); + } + // A text exactly the cut's length is kept whole, and one character more is cut. + const one = scriptFrames(' at f (:3:1)'); + assert.equal(scriptFrames(' at f (:3:1)', one.length), one); + assert.match(scriptFrames(' at f (:3:1)', one.length - 1), /characters dropped here/); + // A line of eleven digits is no line of QuickJS's, so its frame isn't one. + assert.equal(scriptFrames(' at f (:12345678901:1)\n at g (:1234567890:1)'), ' at g (script:1234567888:1)'); +}); diff --git a/test/code-execution-isolate-engine.test.ts b/test/code-execution-isolate-engine.test.ts new file mode 100644 index 000000000..4a2cbecd8 --- /dev/null +++ b/test/code-execution-isolate-engine.test.ts @@ -0,0 +1,423 @@ +/** + * The isolate end to end on the real engine: a child spawned on this test's + * own runtime, Node's or Bun's, running QuickJS in WebAssembly. Each runtime + * runs these: a spawn and a script that calls a tool, a stop through the stop + * file reaching a spinning guest, and the memory limit. + */ + +import { test } from 'node:test'; +import { strict as assert } from 'node:assert'; +import { execFileSync, spawn } from 'node:child_process'; +import { existsSync, mkdtempSync, rmSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { extname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { CHILD_ENTRY, maxResultBytes, ScriptRun, type ScriptRunOptions, type ToolAnswer } from '../src/code-execution/isolate/runner.js'; + +// The entry the framework runs, which is the file beside the runner with its own extension. +const CHILD = CHILD_ENTRY; + +test('the entry the runner names is a file that exists, so the runtime runs it and not a file it maps the name to', () => { + // Under Bun, a .js path mapped to the .ts beside it runs as `bun run` would, with npm_* variables and a PATH in the child's environment. + assert.ok(existsSync(CHILD_ENTRY), CHILD_ENTRY); +}); +const TOOLS = [{ name: 'echo', description: 'Echoes its arguments.', inputSchema: { type: 'object' } }]; + +const answer = (value: unknown): ToolAnswer => ({ + ok: true, + json: JSON.stringify({ content: [{ type: 'text', text: JSON.stringify(value) }], structuredContent: value, isError: false }), + isError: false, +}); + +function run(over: Partial = {}): ScriptRun { + return new ScriptRun({ + childEntry: CHILD, + memoryMaxBytes: 16 * 1024 * 1024, + output: { headChars: 8000, tailChars: 8000 }, + flushMs: 50, + lifetimeMs: 60_000, + toolCallTimeoutMs: 60_000, + stopGraceMs: 5_000, + wakeMinIntervalMs: 0, + maxWakes: 100, + onToolCall: async (_name, args) => answer(JSON.parse(args)), + onWake: async () => ({ ok: true, json: 'null' }), + ...over, + }); +} + +test('a script on the engine calls tools together, prints, and returns its value', async () => { + const r = run(); + r.start( + `const [a, b] = await Promise.all([tools.echo({ n: 1 }), tools.echo({ n: 2 })]); + console.log('sum', a.structuredContent.n + b.structuredContent.n); + text(ALL_TOOLS.map((t) => t.name + ':' + t.inputSchema.type).join()); + return { isError: a.isError, n: b.structuredContent.n };`, + TOOLS, + ); + const snap = await r.wait(20_000); + assert.deepEqual(snap.ending, { how: 'finished', value: '{"isError":false,"n":2}' }); + assert.equal(snap.output, 'sum 3\necho:object\n'); + assert.equal(snap.calls.answered, 2); + await r.exited; +}); + +test("a script's error on the engine names its own line, not the prelude's", async () => { + const r = run(); + r.start(`const x = 1;\nnull.boom;`, TOOLS); + const snap = await r.wait(20_000); + assert.equal(snap.ending?.how, 'failed'); + const ending = snap.ending as { error: { name: string; message: string; stack?: string } }; + assert.equal(ending.error.name, 'TypeError'); + // Only the script's own frame: not the prelude's, the engine's native one, or the line that started it. + assert.deepEqual((ending.error.stack ?? '').split('\n'), [' at anonymous (script:2:1)']); + await r.exited; +}); + +test('the stop file reaches a spinning guest on the engine, and its output so far comes back', async () => { + const r = run(); + r.start(`text('spinning'); for (;;) {}`, TOOLS); + // The guest's output is flushed from its interrupt checks while it spins. + const deadline = Date.now() + 20_000; + while (r.snapshot().output !== 'spinning\n') { + assert.ok(Date.now() < deadline, 'the output while it spins'); + await new Promise((resolve) => setTimeout(resolve, 10)); + } + const snap = await r.stop('limit'); + assert.deepEqual(snap.ending, { how: 'stopped', reason: 'limit' }); + assert.equal(snap.output, 'spinning\n'); + await r.exited; +}); + +// A script that keeps QuickJS from taking a stop: each TypeError's stack runs +// the hook, which spins until a stop lands in it, and QuickJS drops that stop, +// putting back the TypeError, which the loop catches. +const DROPS_EVERY_STOP = `text('spinning');\nError.prepareStackTrace = () => { for (;;) {} };\nfor (;;) { try { null.x; } catch {} }`; + +test("a stop that a script's stack hook keeps QuickJS from taking ends the run at the stop's grace, its process killed", async () => { + const r = run({ stopGraceMs: 1_000 }); + r.start(DROPS_EVERY_STOP, TOOLS); + const deadline = performance.now() + 20_000; + while (r.snapshot().output !== 'spinning\n') { + assert.ok(performance.now() < deadline, 'the output while it spins'); + await new Promise((resolve) => setTimeout(resolve, 10)); + } + const asked = performance.now(); + const snap = await r.stop('limit'); + const took = performance.now() - asked; + assert.deepEqual(snap.ending, { how: 'stopped', reason: 'limit', short: true }); + // Killed at the grace: not before it, since the stop never landed, and not + // long after it, with room for a loaded host. + assert.ok(took >= 950, `ended ${took} ms after the stop, before its 1 s grace`); + assert.ok(took < 6_000, `ended ${took} ms after the stop`); + await r.exited; +}); + +test("an orphan whose script's stack hook keeps QuickJS from taking a stop ends its process", async () => { + const parentEntry = fileURLToPath(new URL(`./helpers/orphan-parent-entry${extname(fileURLToPath(import.meta.url))}`, import.meta.url)); + const dir = mkdtempSync(join(tmpdir(), 'af-orphan-')); + // The child the runner starts, under a parent that names itself: it says it's running, from inside its run, and its parent goes only after that. + const message = { t: 'start', script: DROPS_EVERY_STOP, tools: TOOLS, memoryMaxBytes: 16 * 1024 * 1024, stopFile: join(dir, 'stop'), flushMs: 0, maxWakes: 100 }; + const parent = spawn(process.execPath, [parentEntry, CHILD], { stdio: ['ignore', 'pipe', 'inherit'], env: { ...process.env, CHILD_DIR: dir, START: JSON.stringify(message) } }); + let childPid = 0; + try { + childPid = await new Promise((resolve, reject) => { + let out = ''; + parent.stdout!.on('data', (chunk: Buffer) => { + out += chunk.toString(); + if (out.includes('\n')) resolve(Number(out.trim())); + }); + parent.on('exit', () => reject(new Error('the parent exited before it started its child'))); + }); + // Gone, or a zombie its new parent hasn't reaped yet, which ps marks Z. + const alive = () => { + try { process.kill(childPid, 0); } catch { return false; } + try { return !execFileSync('ps', ['-o', 'stat=', '-p', String(childPid)], { encoding: 'utf8' }).trim().startsWith('Z'); } catch { + try { process.kill(childPid, 0); return true; } catch { return false; } + } + }; + assert.equal(alive(), true, 'the child is running'); + // Its parent goes; nothing tells the child but its parent pid changing, and no one is left to kill it. + parent.kill('SIGKILL'); + const deadline = performance.now() + 10_000; + while (alive() && performance.now() < deadline) await new Promise((r) => setTimeout(r, 20)); + assert.equal(alive(), false, 'the orphaned child ended'); + } finally { + parent.kill('SIGKILL'); + if (childPid) try { process.kill(childPid, 'SIGKILL'); } catch { /* gone */ } + rmSync(dir, { recursive: true, force: true }); + } +}); + +test('a script that fills its memory gets a catchable out-of-memory error, and the child lives on', async () => { + const r = run(); + r.start( + `const kept = []; + try { for (;;) kept.push({ i: kept.length, s: 'x'.repeat(64) }); } + catch (e) { kept.length = 0; return [e.name, e.message]; }`, + TOOLS, + ); + const snap = await r.wait(30_000); + assert.equal(snap.ending?.how, 'finished', JSON.stringify(snap.ending)); + const [name, message] = JSON.parse((snap.ending as { value: string }).value) as [string, string]; + assert.equal(name, 'InternalError'); + assert.match(message, /out of memory/); + await r.exited; +}); + +test("an answer the script couldn't receive says whether the call took effect, and a refusal stays one in the record", async () => { + const r = run({ + onToolCall: async (_name, args) => (JSON.parse(args).refuse + // A refusal whose text isn't valid JSON, which no host sends, can't reach the script. + ? { ok: false, json: '{"name": "ToolRefused", "message": ' } + : { ok: true, json: '{"content": [', isError: false }), + }); + r.start(`const out = []; for (const refuse of [false, true]) { try { await tools.echo({ refuse }); } catch (e) { out.push([e.name, /not valid JSON/.test(e.message), /the call took effect/.test(e.message), /the host's answer, \\d+ bytes, couldn't be received by this script whole/.test(e.message)]); } } return out;`, TOOLS); + const snap = await r.wait(30_000); + assert.deepEqual(snap.ending, { how: 'finished', value: '[["ResultTooLarge",true,true,false],["ResultTooLarge",true,false,true]]' }); + assert.equal(snap.calls.refused, 1, 'the refusal is still counted as one'); + assert.deepEqual(snap.calls.named, [{ tool: 'echo', args: '{"refuse":false}', state: 'too-large' }]); + await r.exited; +}); + +test("a result within the bound that doesn't fit the guest's memory reaches the script as ResultTooLarge, and is named", async () => { + // 16 MiB of memory bounds a result at about 5.3 MiB, but small objects need + // more room to parse than their JSON takes: + // about 4.5 MiB of them doesn't fit. + const rows = Array.from({ length: 100_000 }, (_, i) => ({ id: 100_000 + i, name: `item-${i}`, ok: true })); + const json = JSON.stringify({ content: [], structuredContent: rows, isError: false }); + assert.ok(Buffer.byteLength(json) < maxResultBytes(16 * 1024 * 1024), 'within the bound, so the parent sends it'); + const r = run({ onToolCall: async () => ({ ok: true, json, isError: false }) }); + r.start(`try { await tools.echo({ big: true }); return 'fit'; } catch (e) { return [e.name, /couldn't be received by this script: it was too large for its memory, nested deeper than 2000 levels, or not valid JSON; the call took effect/.test(e.message), e.bytes > 4000000]; }`, TOOLS); + const snap = await r.wait(30_000); + assert.deepEqual(snap.ending, { how: 'finished', value: '["ResultTooLarge",true,true]' }); + assert.deepEqual(snap.calls.named, [{ tool: 'echo', args: '{"big":true}', state: 'too-large' }]); + assert.equal(snap.calls.answered, 0); + await r.exited; +}); + +test('a result over the bound is refused before it is sent, as ResultTooLarge, and is named', async () => { + const json = JSON.stringify({ content: [], structuredContent: 'x'.repeat(6 * 1024 * 1024), isError: false }); + const r = run({ onToolCall: async () => ({ ok: true, json, isError: false }) }); + r.start(`try { await tools.echo({}); return 'fit'; } catch (e) { return [e.name, /is too large for this script's memory/.test(e.message)]; }`, TOOLS); + const snap = await r.wait(30_000); + assert.deepEqual(snap.ending, { how: 'finished', value: '["ResultTooLarge",true]' }); + assert.deepEqual(snap.calls.named, [{ tool: 'echo', args: '{}', state: 'too-large' }]); + await r.exited; +}); + +test("on the engine, arguments past a call's bytes are refused before a frame is built, and the script goes on", async () => { + const r = run({ memoryMaxBytes: 64 * 1024 * 1024 }); + // 3 MiB of quotes: 6,291,464 bytes of arguments once stringified, past the 4 MiB a call takes. + r.start(`try { await tools.echo({ s: '"'.repeat(3 * 1024 * 1024) }); } catch (e) { return [e.name, e.bytes]; }`, TOOLS); + const snap = await r.wait(60_000); + assert.equal(snap.ending?.how, 'finished', JSON.stringify(snap.ending)); + const [name, bytes] = JSON.parse((snap.ending as { value: string }).value) as [string, number]; + assert.equal(name, 'ArgumentsTooLarge'); + assert.equal(bytes, 6 * 1024 * 1024 + 8); + assert.equal(snap.calls.made, 0); + await r.exited; +}); + +test("a script's syntax error on the engine names its own line", async () => { + const r = run(); + r.start(`const x = 1;\nlet y = ;`, TOOLS); + const snap = await r.wait(20_000); + assert.equal(snap.ending?.how, 'failed'); + const ending = snap.ending as { error: { name: string; message: string; stack?: string } }; + assert.equal(ending.error.name, 'SyntaxError'); + assert.match(ending.error.stack ?? '', /script:2:/); + assert.doesNotMatch(ending.error.stack ?? '', /|/); + await r.exited; +}); + +test('a thrown value that is no Error fails the script with its JSON as the message', async () => { + const r = run(); + r.start(`throw { why: 'this failure should stay visible' };`, TOOLS); + const snap = await r.wait(20_000); + assert.deepEqual(snap.ending, { how: 'failed', error: { name: '', message: '{"why":"this failure should stay visible"}' } }); + await r.exited; +}); + +test("a returned value's toJSON runs once, however many answers come after the return", async () => { + const r = run(); + r.start( + `let n = 0; + const value = { toJSON() { n += 1; text('serialized ' + n); return { n }; } }; + tools.echo({ a: 1 }); + tools.echo({ b: 2 }); + return value;`, + TOOLS, + ); + const snap = await r.wait(20_000); + assert.deepEqual(snap.ending, { how: 'finished', value: '{"n":1}' }); + assert.equal(snap.output, 'serialized 1\n'); + await r.exited; +}); + +test("a tool's schema keeps a __proto__ key as a key in ALL_TOOLS", async () => { + const r = run(); + const schema = JSON.parse('{"type":"object","properties":{"__proto__":{"type":"string"},"path":{"type":"string"}}}') as unknown; + r.start(`return Object.keys(ALL_TOOLS[0].inputSchema.properties);`, [{ name: 'echo', description: 'Echoes.', inputSchema: schema }]); + const snap = await r.wait(20_000); + assert.deepEqual(snap.ending, { how: 'finished', value: '["__proto__","path"]' }); + await r.exited; +}); + +test('past its cap a wake is refused in the script, and never reaches the host', async () => { + let woke = 0; + const r = run({ maxWakes: 2, onWake: async () => { woke += 1; return { ok: true, json: 'null' }; } }); + r.start(`await wake_agent(1); await wake_agent(2); try { await wake_agent(3); } catch (e) { return [e.name, e.message]; }`, TOOLS); + const snap = await r.wait(20_000); + assert.deepEqual(snap.ending, { + how: 'finished', + value: JSON.stringify(['WakeRefused', "wake limit reached: 2 per script, set by the host's maxWakesPerScript; nothing was sent, and every later wake_agent() in this script is refused the same way. A new script can wake its agent again."]), + }); + assert.equal(woke, 2); + await r.exited; +}); + +test("a script's mistakes with its tools read plainly: no tools at all, or arguments that aren't JSON", async () => { + const none = run(); + none.start(`try { tools.anything; } catch (e) { return e.message; }`, []); + const a = await none.wait(20_000); + assert.match((a.ending as { value: string }).value, /is not one of your tools\. You have no tools\. Nothing was sent\./); + await none.exited; + const cyclic = run(); + cyclic.start(`const a = {}; a.self = a; try { await tools.echo(a); } catch (e) { return e.message; }`, TOOLS); + const b = await cyclic.wait(20_000); + const message = JSON.parse((b.ending as { value: string }).value) as string; + assert.match(message, /^echo's arguments aren't JSON \(.+\); nothing was sent$/); + assert.doesNotMatch(message, /\n|TypeError: /); + await cyclic.exited; +}); + +test('a result nested deeper than the engine takes reaches the script as ResultTooLarge, and the call took effect', async () => { + let deep = '1'; + for (let i = 0; i < 2_100; i++) deep = `[${deep}]`; + const json = JSON.stringify({ content: [{ type: 'text', text: 'deep' }], structuredContent: JSON.parse(deep), isError: false }); + const r = run({ onToolCall: async () => ({ ok: true, json, isError: false }) }); + r.start(`try { await tools.echo({}); return 'received'; } catch (e) { return [e.name, /nested deeper than 2000 levels/.test(e.message)]; }`, TOOLS); + const snap = await r.wait(30_000); + assert.deepEqual(snap.ending, { how: 'finished', value: '["ResultTooLarge",true]' }); + assert.deepEqual(snap.calls.named, [{ tool: 'echo', args: '{}', state: 'too-large' }]); + await r.exited; +}); + +test("a return value JSON can't hold comes back saying so, with its string form if it has one", async () => { + for (const [script, said] of [ + ['return 10n;', "JSON can't hold the returned value, so this is its string form: 10"], + ['return () => 1;', "JSON can't hold the returned value, so this is its string form: () => 1"], + ['const a = {}; a.self = a; return a;', "JSON can't hold the returned value, so this is its string form: [object Object]"], + ['return { toJSON() { throw new Error("no"); }, toString() { throw new Error("no"); } };', "JSON can't hold the returned value, and it has no string form"], + ] as const) { + const r = run(); + r.start(script, TOOLS); + const snap = await r.wait(20_000); + assert.deepEqual(snap.ending, { how: 'finished', value: JSON.stringify(said) }, script); + await r.exited; + } +}); + +test("a failure that escaped the script's promises reads as a thrown one does: the script's own frames, its lines as written", async () => { + const stackOf = async (code: string) => { + const r = run(); + r.start(code, TOOLS); + const snap = await r.wait(20_000); + await r.exited; + assert.equal(snap.ending?.how, 'failed', JSON.stringify(snap.ending)); + return snap.ending?.how === 'failed' ? snap.ending.error : undefined; + }; + // Thrown by the script itself, through the prelude, at its line 3. + const thrown = await stackOf(`const a = 1;\nfunction f() {\n throw new Error('here');\n}\nf();`); + // Thrown by a microtask's callback, which escapes the script's promises (the engine's failed), at its line 3. + const escaped = await stackOf(`const a = 1;\nqueueMicrotask(function f() {\n throw new Error('here');\n});\nawait tools.echo({});`); + // The ending's stack is the script's frames alone (GuestError.stack). + const framesOf = (error: { stack?: string } | undefined): string[] => { + const frames = (error?.stack ?? '').split('\n'); + assert.ok(frames.length > 0 && frames.every((line) => /^ {4}at .*\(script:\d+:\d+\)$/.test(line)), error?.stack); + return frames; + }; + for (const error of [thrown, escaped]) { + assert.equal(error?.name, 'Error'); + assert.equal(error?.message, 'here'); + // Only the script's own frames, each numbered as written, f's first. + assert.match(framesOf(error)[0]!, /^ {4}at f \(script:3:\d+\)$/, error?.stack); + } + // The escaped one was called by the engine's job queue, which has no frame of the script's. + assert.equal(framesOf(escaped).length, 1, escaped?.stack); + // One Error, thrown by the script and again by a job that escapes its + // promises: its stack is QuickJS's own both times, frames only, so the + // escaped failure's frames are the script's, and a line of its message that + // reads like a frame stays in the message, given once. + const twice = await stackOf(`const e = new Error('boom\\n at note (script:9:1)');\nqueueMicrotask(() => queueMicrotask(() => { throw e; }));\nthrow e;`); + assert.equal(twice?.message, 'boom\n at note (script:9:1)'); + assert.match(framesOf(twice)[0]!, /^ {4}at anonymous \(script:1:\d+\)$/, twice?.stack); + assert.doesNotMatch(twice?.stack ?? '', /note/); + // A frozen Error: its frames are put the same way. + const frozen = await stackOf(`const a = 1;\nfunction f() {\n throw Object.freeze(new Error('cold'));\n}\nf();`); + assert.equal(frozen?.message, 'cold'); + assert.match(framesOf(frozen)[0]!, /^ {4}at f \(script:3:\d+\)$/, frozen?.stack); + // A message cut for its length, ending in a line that reads like a frame, and a name cut likewise: the frames are the script's own. + const long = await stackOf(`const e = new Error('x'.repeat(9000) + '\\n at evil (script:9:1)');\ne.name = 'N'.repeat(300);\nfunction f() { throw e; }\nf();`); + assert.match(long?.message ?? '', /characters dropped here/); + assert.match(framesOf(long)[0]!, /^ {4}at anonymous \(script:1:\d+\)$/, long?.stack); + assert.doesNotMatch(long?.stack ?? '', /evil/); + // A name whose getter answers differently at each read, its message's last line shaped as a frame: the frames are still the script's own. + const unstable = await stackOf(`const e = new Error('first\\n at note (script:9:1)');\nlet n = 0;\nObject.defineProperty(e, 'name', { get() { return ++n === 1 ? 'Old' : 'New'; } });\nthrow e;`); + assert.deepEqual(framesOf(unstable).length, 1, unstable?.stack); + assert.match(framesOf(unstable)[0]!, /^ {4}at anonymous \(script:1:\d+\)$/, unstable?.stack); + // A name set to undefined, which the engine leaves out. + const unnamed = await stackOf(`const e = new Error('bad :4');\ne.name = undefined;\nthrow e;`); + assert.equal(unnamed?.name, ''); + assert.deepEqual(framesOf(unnamed).length, 1, unnamed?.stack); + assert.match(framesOf(unnamed)[0]!, /^ {4}at anonymous \(script:1:\d+\)$/, unnamed?.stack); + // A function whose name holds a line break, called between two others: its frame, split, is left out, and the throw's and + // the caller's stay. One whose name starts with a space is kept. + const split = await stackOf(`function fail() { throw new Error('boom'); }\nconst o = { ['outer\\nlabel']() { fail(); }, [' run']() { o['outer\\nlabel'](); } };\no[' run']();`); + assert.deepEqual(framesOf(split).map((line) => line.replace(/:\d+\)$/, ')')), [' at fail (script:1)', ' at run (script:2)', ' at anonymous (script:3)'], split?.stack); + // Code the script compiles at run time is named as the script is: a frame in an eval'd string reads at the + // string's line less two, one in a Function constructor's body at the body's own line, plus one for each line break in + // its parameters, and the script's own frame below. + const lineOf = (line: string) => Number(/script:(\d+):\d+\)$/.exec(line)![1]); + const evald = await stackOf(`eval('1;\\n2;\\n3;\\nthrow new Error("in eval");');`); + assert.deepEqual(framesOf(evald).map(lineOf), [2, 1], evald?.stack); + const built = await stackOf(`new Function('1;\\n2;\\nthrow new Error("in fn");')();`); + assert.deepEqual(framesOf(built).map(lineOf), [3, 1], built?.stack); + const params = await stackOf(`new Function('a,\\nb', 'throw new Error("in fn");')();`); + assert.deepEqual(framesOf(params).map(lineOf), [2, 1], params?.stack); +}); + +test("inside the script, a logged Error leads with its head and keeps only the script's frames, its message's lines given once", async () => { + const r = run(); + // A stack the script set, opening with the error's head, whose message has a line that reads like a frame, then a line shaped as a frame of no place of + // the script's and one of QuickJS's: only the last is a frame. + r.start([ + `const e = new Error('first\\n at note (script:8:1)');`, + `e.stack = 'Error: first\\n at note (script:8:1)\\n at g (script:9:1)\\n at h (:12:3)';`, + 'console.log(e);', + // One whose stack is its head alone, a frame-like line of its message included. + `const only = new Error('only\\n at note (script:7:1)');`, + `only.stack = 'Error: only\\n at note (script:7:1)';`, + 'console.log(only);', + // One the script made, whose stack is QuickJS's own: the script's frame alone, not the prelude's that called it. + `console.log(new Error('logged'));`, + // One whose stack opens with another head: only the line shaped as a frame of the script's is one, not its old message's frame-shaped line. + `const other = new Error('mine');`, + `other.stack = 'Other: theirs\\n at note (script:9:1)\\nlast\\n at h (:12:3)';`, + 'console.log(other);', + // One whose stack, after its head, has a line that names the script's source but isn't shaped as a frame: not a frame. + `const plain = new Error('x');`, + `plain.stack = 'Error: x\\nsee :3\\n at h (:12:3)';`, + 'console.log(plain);', + // One QuickJS made, thrown through a function whose name holds a line break: that frame, split, is left out, and the + // frames below and above it stay. + `function fail() { throw new Error('split'); }`, + `const o = { ['outer\\nlabel']() { try { fail(); } catch (e) { console.log(e); } }, [' run']() { o['outer\\nlabel'](); } };`, + `o[' run']();`, + ].join('\n'), TOOLS); + const snap = await r.wait(20_000); + await r.exited; + assert.equal(snap.ending?.how, 'finished', JSON.stringify(snap.ending)); + assert.match(snap.output, /^Error: first\n {4}at note \(script:8:1\)\n {4}at h \(script:10:3\)\nError: only\n {4}at note \(script:7:1\)\nError: logged\n {4}at anonymous \(script:7:\d+\)\nError: mine\n {4}at h \(script:10:3\)\nError: x\n {4}at h \(script:10:3\)\nError: split\n {4}at fail \(script:14:\d+\)\n {4}at run \(script:15:\d+\)\n {4}at anonymous \(script:16:\d+\)\n?$/); +}); diff --git a/test/code-execution-isolate-output.test.ts b/test/code-execution-isolate-output.test.ts new file mode 100644 index 000000000..ff250c876 --- /dev/null +++ b/test/code-execution-isolate-output.test.ts @@ -0,0 +1,58 @@ +/** The isolate's output bound: the child's batches, and the parent's head and tail of each return's stretch. */ + +import { test } from 'node:test'; +import { strict as assert } from 'node:assert'; +import { MAX_BATCH_CHARS, OutputBatch, WindowOutput } from '../src/code-execution/isolate/output.js'; + +const bounds = { headChars: 10, tailChars: 10 }; + +test('a batch says when it is full, and flushes in pieces of at most a batch', () => { + const batch = new OutputBatch(); + assert.equal(batch.write('x'.repeat(MAX_BATCH_CHARS - 1)), false); + assert.equal(batch.write('yy'), true); + const pieces = batch.flush(); + assert.deepEqual(pieces.map((p) => p.length), [MAX_BATCH_CHARS, 1]); + assert.equal(batch.unflushed, 0); + assert.deepEqual(batch.flush(), []); +}); + +test("a batch's pieces never split a surrogate pair", () => { + const batch = new OutputBatch(); + // The pair straddles where the first piece would end. + batch.write('x'.repeat(MAX_BATCH_CHARS - 1) + '\u{1F600}' + 'z'); + const pieces = batch.flush(); + assert.equal(pieces.join(''), 'x'.repeat(MAX_BATCH_CHARS - 1) + '\u{1F600}' + 'z'); + for (const piece of pieces) assert.equal(/[\uD800-\uDBFF]$|^[\uDC00-\uDFFF]/.test(piece), false, 'no piece ends or starts inside a pair'); +}); + +test('a stretch within the head comes through whole', () => { + const w = new WindowOutput(bounds); + w.apply('abc'); + w.apply('def'); + assert.equal(w.render(), 'abcdef'); +}); + +test('past the head and tail, the middle is dropped and counted', () => { + const w = new WindowOutput(bounds); + for (const piece of ['0123456789', 'abcdefghij', 'ABCDEFGHIJ']) w.apply(piece); + assert.equal(w.render(), '0123456789\n[… 10 characters of output dropped here …]\nABCDEFGHIJ'); + assert.equal(w.droppedChars, 10); +}); + +test('each return starts a fresh stretch, with its own head and tail', () => { + const w = new WindowOutput(bounds); + w.apply('first stretch, long enough to drop some'); + assert.match(w.take(), /^first stre\n\[… \d+ characters of output dropped here …\]\n drop some$/); + w.apply('0123456789abcdefghijABCDEFGHIJ'); + assert.equal(w.take(), '0123456789\n[… 10 characters of output dropped here …]\nABCDEFGHIJ'); + assert.equal(w.take(), ''); +}); + +test('a surrogate pair is never split where the head ends or where the tail begins', () => { + const lone = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(? { + const message = decodeChildMessage(frame); + assert.ok('invalid' in message, JSON.stringify(message)); + assert.match((message as { invalid: string }).invalid, reason); + }); +} + +test('the frames a child may send decode as themselves', () => { + assert.deepEqual(decodeChildMessage({ t: 'call', id: 4294967295, name: 'x', args: '{}' }), { t: 'call', id: 4294967295, name: 'x', args: '{}' }); + assert.deepEqual(decodeChildMessage({ t: 'seen', id: 3, unfit: true }), { t: 'seen', id: 3, unfit: true }); + assert.deepEqual(decodeChildMessage({ t: 'out', text: 'hi' }), { t: 'out', text: 'hi' }); +}); + +test('the frame decoder takes frames across chunks, and refuses one too long, an empty one, and one cut off at the end', () => { + const decoder = new FrameDecoder(); + const frame = encodeFrame({ t: 'out', text: 'hello' }); + assert.deepEqual(decoder.push(frame.subarray(0, 3)), []); + assert.deepEqual(decoder.push(frame.subarray(3)), [{ t: 'out', text: 'hello' }]); + decoder.end(); + const header = (n: number) => { const b = Buffer.alloc(4); b.writeUInt32BE(n, 0); return b; }; + assert.throws(() => new FrameDecoder().push(header(MAX_FRAME_BYTES + 1)), /more than the 67108864 the protocol allows/); + assert.throws(() => new FrameDecoder().push(header(0)), /a frame was empty/); + const cut = new FrameDecoder(); + cut.push(Buffer.concat([header(100), Buffer.alloc(10, 0x7b)])); + assert.throws(() => cut.end(), /the stream ended inside a frame, with 10 of its bytes read/); + // Cut inside the header itself: one, two or three of its four bytes. + for (const n of [1, 2, 3]) { + const inHeader = new FrameDecoder(); + inHeader.push(frame.subarray(0, n)); + assert.throws(() => inHeader.end(), new RegExp(`the stream ended inside a frame, with ${n} of its bytes read`), `${n} header bytes`); + } +}); + +test('a frame whose JSON starts with a byte-order mark is refused as not JSON', () => { + const json = Buffer.from(JSON.stringify({ t: 'out', text: 'x' }), 'utf8'); + const body = Buffer.concat([Buffer.from([0]), Buffer.from([0xef, 0xbb, 0xbf]), json]); + const header = Buffer.alloc(4); + header.writeUInt32BE(body.length, 0); + assert.throws(() => new FrameDecoder().push(Buffer.concat([header, body])), /not JSON/); +}); + +test("a call's frame always fits, since it carries its arguments escaped, at most six bytes for each of theirs", () => { + // A control character escapes to six bytes in a frame, the most any one byte becomes. + assert.equal(JSON.stringify('\u0001'), '"\\u0001"'); + assert.ok(MAX_ARGUMENT_BYTES * 6 + 4096 <= MAX_FRAME_BYTES); + // The largest arguments of the costliest characters still make a frame. + const frame = encodeFrame({ t: 'call', id: 1, name: 'x', args: '\u0001'.repeat(MAX_ARGUMENT_BYTES) }); + assert.ok(frame.length <= MAX_FRAME_BYTES); +}); + +test('values are counted outside strings, and counting stops once past its limit', () => { + assert.equal(countJsonValues('1'), 1); + assert.equal(countJsonValues('[1,2,3]'), 4); + // The object, the array, its two empty objects, and the string: an empty object or array holds nothing. + assert.equal(countJsonValues('{"a":[{},{}],"b":"x,[{"}'), 5); + assert.equal(countJsonValues('[{},[ ],{"a":[]}]'), 5); + // 50,000 empty objects in an array in an object: 50,002 values. + assert.equal(countJsonValues(JSON.stringify({ items: Array.from({ length: 50_000 }, () => ({})) })), 50_002); + assert.equal(countJsonValues('"a \\" , [ {"'), 1); + assert.equal(countJsonValues(JSON.stringify(Array.from({ length: 1000 }, () => 0)), 10), 11); +}); + +test("arguments past a call's bytes or values, and a wake past its payload, are refused with what to do", () => { + assert.equal(argumentsRefusal('echo', JSON.stringify({ s: 'x'.repeat(MAX_ARGUMENT_BYTES - 20) })), null); + const big = argumentsRefusal('echo', JSON.stringify({ s: 'x'.repeat(MAX_ARGUMENT_BYTES) })); + assert.equal(big?.name, 'ArgumentsTooLarge'); + assert.match(big!.message, /^echo's arguments are \d+ bytes of JSON, past the 4 MiB a call can take; nothing was sent\. Send the data in parts, over several calls\.$/); + const many = argumentsRefusal('echo', JSON.stringify(Array.from({ length: MAX_ARGUMENT_VALUES }, () => 0))); + assert.equal(many?.name, 'ArgumentsTooLarge'); + assert.match(many!.message, /more than 100,000 values/); + assert.equal(wakeRefusal(JSON.stringify('w'.repeat(MAX_WAKE_PAYLOAD_BYTES - 2))), null); + const wake = wakeRefusal(JSON.stringify('w'.repeat(MAX_WAKE_PAYLOAD_BYTES))); + assert.equal(wake?.name, 'WakeRefused'); + assert.match(wake!.message, /past the 64 KiB a wake can carry; nothing was sent/); +}); + +test('a decoder given a cap on values refuses a frame holding more, before parsing it', () => { + const capped = new FrameDecoder(undefined, false, 4); + assert.deepEqual(capped.push(encodeFrame({ t: 'out', text: 'x' })), [{ t: 'out', text: 'x' }]); + assert.throws(() => new FrameDecoder(undefined, false, 4).push(encodeFrame({ t: 'out', text: 'x', more: [1, 2, 3] })), /a frame held more than 4 values/); +}); + +test('a frame pushed a byte at a time decodes, and the decoder keeps one buffer of its body, not its chunks', () => { + const frame = encodeFrame({ t: 'out', text: 'x'.repeat(5000) }); + const decoder = new FrameDecoder(); + const internals = decoder as unknown as Record; + for (let i = 0; i < frame.length - 1; i++) assert.deepEqual(decoder.push(frame.subarray(i, i + 1)), []); + // Short of its last byte: the body is one buffer of the length its header named. + assert.equal((internals.body as Buffer).length, frame.length - 4); + assert.equal('chunks' in internals, false); + assert.deepEqual(decoder.push(frame.subarray(frame.length - 1)), [{ t: 'out', text: 'x'.repeat(5000) }]); + assert.equal(internals.body, null); + // Two frames and part of a third in one chunk. + const two = Buffer.concat([encodeFrame({ t: 'out', text: 'a' }), encodeFrame({ t: 'out', text: 'b' }), frame.subarray(0, 7)]); + const d2 = new FrameDecoder(); + assert.deepEqual(d2.push(two), [{ t: 'out', text: 'a' }, { t: 'out', text: 'b' }]); + assert.deepEqual(d2.push(frame.subarray(7)), [{ t: 'out', text: 'x'.repeat(5000) }]); +}); + +test("a frame's values are counted before it's parsed, its UTF-8 checked before that, and each failure says which", () => { + const raw = (text: string | Buffer) => { + const body = Buffer.concat([Buffer.from([0]), Buffer.isBuffer(text) ? text : Buffer.from(text, 'utf8')]); + const header = Buffer.alloc(4); + header.writeUInt32BE(body.length, 0); + return Buffer.concat([header, body]); + }; + // Not JSON at all, and past the cap: the count comes first, so no parse is tried. + assert.throws(() => new FrameDecoder(undefined, false, 4).push(raw('[1,2,3,4,5,6,')), /a frame held more than 4 values/); + // Within the cap, not JSON. + assert.throws(() => new FrameDecoder(undefined, false, 4).push(raw('[1,2,')), /a frame was not JSON$/); + // A byte that isn't UTF-8 inside a string. + assert.throws(() => new FrameDecoder().push(raw(Buffer.concat([Buffer.from('{"t":"out","text":"'), Buffer.from([0xff]), Buffer.from('"}')]))), /a frame was not UTF-8$/); +}); + +test('with wakes off, the refusal says no script here can wake its agent', () => { + assert.match(wakeLimitRefusal(0).message, /wake_agent\(\) is off on this host: its operator set maxWakesPerScript to 0, so nothing was sent, and no script here can wake its agent\./); + assert.match(wakeLimitRefusal(3).message, /^wake limit reached: 3 per script.*A new script can wake its agent again\.$/); +}); diff --git a/test/code-execution-isolate-render.test.ts b/test/code-execution-isolate-render.test.ts new file mode 100644 index 000000000..568735d19 --- /dev/null +++ b/test/code-execution-isolate-render.test.ts @@ -0,0 +1,168 @@ +/** What a return of `code_execution` says to the model: the ending, the calls' record, the value and the output, each within its bound. */ + +import { test } from 'node:test'; +import { strict as assert } from 'node:assert'; +import { bounded, formatMs, glimpse, renderList, renderReturn, VALUE_CHARS } from '../src/code-execution/isolate/render.js'; +import type { CallSummary } from '../src/code-execution/isolate/calls.js'; +import type { ScriptReturn } from '../src/code-execution/isolate/scripts.js'; + +const noCalls: CallSummary = { made: 0, answered: 0, failed: 0, refused: 0, unsent: 0, running: 0, waiting: 0, named: [], more: {} }; +const ret = (over: Partial): ScriptReturn => ({ + scriptId: 's1-test', + running: false, + output: '', + first: true, + calls: noCalls, + ending: { how: 'finished' }, + stopsAtTurnEnd: false, + ...over, +}); +const options = { boundMs: 30_000, lifetimeMs: 86_400_000 }; + +test("a call that was never sent reads as nothing done, not as refused", () => { + const text = renderReturn(ret({ calls: { ...noCalls, made: 3, answered: 1, refused: 1, unsent: 1 } }), options); + assert.match(text, /Its calls: 3 \(1 answered; 1 refused before it ran, so nothing was done; 1 never sent, so nothing was done\)\./); +}); + +test('each call whose answer the script did not see is named, as it was left', () => { + const calls: CallSummary = { + made: 6, + answered: 1, + failed: 0, + refused: 0, + unsent: 0, + running: 0, + waiting: 0, + named: [ + { tool: 'write', args: '{"p":1}', state: 'uncertain' }, + { tool: 'slow', args: '{}', state: 'timed-out' }, + { tool: 'slow', args: '{"k":2}', state: 'timed-out-answered', answer: 'ok' }, + { tool: 'big', args: '{}', state: 'too-large' }, + { tool: 'echo', args: '{}', state: 'unseen' }, + ], + more: {}, + }; + const text = renderReturn(ret({ calls, ending: { how: 'stopped', reason: 'agent' } }), options); + assert.match(text, /Its calls: 6 \(1 answered; 5 whose answers it didn't see\):/); + assert.match(text, /- still running when the script ended, so it may or may not take effect: tools\["write"\]\(\{"p":1\}\)/); + assert.match(text, /- timed out in the script, and still running when the script ended, so it may still take effect: tools\["slow"\]\(\{\}\)/); + assert.match(text, /- timed out in the script, and its answer came later: tools\["slow"\]\(\{"k":2\}\), answer "ok"/); + assert.match(text, /- answered with a result the script couldn't receive, too large for its memory or nested too deep, so it got an error instead: tools\["big"\]\(\{\}\)/); + assert.match(text, /- answered, but the script ended before it read the answer: tools\["echo"\]\(\{\}\)/); +}); + +test("at a yield, a too-large answer isn't called a timeout", () => { + const calls: CallSummary = { ...noCalls, made: 2, running: 1, named: [{ tool: 'big', args: '{}', state: 'too-large' }] }; + const text = renderReturn(ret({ running: true, ending: null, calls }), options); + assert.match(text, /Its calls so far: 2 \(1 still running; 1 whose answer it didn't see\)\./); + assert.doesNotMatch(text, /timed out/); +}); + +test('a crash says what it means for the agent, and a failure gives its stack after its line', () => { + const crashed = renderReturn(ret({ ending: { how: 'crashed', detail: 'its process exited with code 7 before the script ended' } }), options); + assert.match(crashed, /^Script s1-test crashed: its process exited with code 7 before the script ended\. A crash is the script's process failing, not an error its code threw: a fault in the host or its runtime, or something the script's own data asked of the runtime that it couldn't do, .*Running it again may work; if the same script crashes again, change what it does with its data or its code\.\n/); + // The ending's stack is the script's frames alone (GuestError.stack): they follow the ending's line. + const failed = renderReturn(ret({ ending: { how: 'failed', error: { name: 'TypeError', message: 'boom', stack: ' at anonymous (script:2:1)' } } }), options); + assert.match(failed, /^Script s1-test failed: TypeError: boom\n at anonymous \(script:2:1\)\nIt made no tool calls\./); + // A message of several lines is given once, by the ending's line. + const multi = renderReturn(ret({ ending: { how: 'failed', error: { name: 'Error', message: 'first\nsecond', stack: ' at f (script:3:5)' } } }), options); + assert.equal(multi.split('second').length - 1, 1, multi); + assert.match(multi, /^Script s1-test failed: Error: first\nsecond\n at f \(script:3:5\)\n/); + // A message line that reads like a frame is the message's, given once. + const framed = renderReturn(ret({ ending: { how: 'failed', error: { name: 'Error', message: 'first\n at note', stack: ' at f (script:3:5)' } } }), options); + assert.equal(framed.split('at note').length - 1, 1, framed); + assert.match(framed, /^Script s1-test failed: Error: first\n at note\n at f \(script:3:5\)\n/); + // No frames, no stack: the ending's line alone. + const bare = renderReturn(ret({ ending: { how: 'failed', error: { name: 'InternalError', message: 'out of memory' } } }), options); + assert.match(bare, /^Script s1-test failed: InternalError: out of memory\nIt made no tool calls\./); + // A cut message and cut frames each keep their own marker. + const cut = renderReturn(ret({ ending: { how: 'failed', error: { name: 'Error', message: bounded('m'.repeat(VALUE_CHARS * 2), VALUE_CHARS), stack: bounded(' at f (script:3:5)\n'.repeat(1_000), VALUE_CHARS) } } }), options); + assert.equal(cut.split('characters dropped here').length - 1, 2, cut.slice(0, 200)); + assert.match(cut, /\n {4}at f \(script:3:5\)\n/); +}); + +test("a return shows the value as the run kept it, and a listing cuts a failure's message to 300 characters", () => { + const kept = bounded('v'.repeat(VALUE_CHARS * 2), VALUE_CHARS); + const text = renderReturn(ret({ ending: { how: 'finished', value: kept } }), options); + assert.ok(text.includes(`It returned: ${kept}`)); + assert.match(text, /\[… 8000 characters dropped here …\]/); + const listed = renderList( + [{ scriptId: 's2-test', running: false, ending: { how: 'failed', error: { name: 'Error', message: 'm'.repeat(1000) } }, startedAt: Date.now(), endedAt: Date.now(), wakes: 0 }], + Date.now(), + ); + const line = listed.split('\n').find((l) => l.startsWith('- s2-test'))!; + assert.ok(line.length < 400, `the listed line is ${line.length} characters`); + // A glimpse of its start, with no count: the message may be one the run cut already, whose count a second cut would misstate. + assert.match(line, /m{300}…/); + assert.doesNotMatch(line, /dropped here/); +}); + +test('a cut never parts a surrogate pair, at the head or the tail', () => { + const pair = '\u{1F600}'; + // The head's last unit would be a pair's first half, and the tail's first a pair's second half. + const text = 'a'.repeat(3) + pair + 'b'.repeat(20) + pair + 'c'.repeat(3); + const cut = bounded(text, 8); + const [head, tail] = cut.split(/\n\[… \d+ characters dropped here …\]\n/); + assert.equal(/[\uD800-\uDBFF]$/.test(head!), false, `head ${JSON.stringify(head)}`); + assert.equal(/^[\uDC00-\uDFFF]/.test(tail!), false, `tail ${JSON.stringify(tail)}`); + const dropped = Number(/\[… (\d+) characters dropped here …\]/.exec(cut)![1]); + assert.equal(head!.length + dropped + tail!.length, text.length); +}); + +test("a glimpse is a text's start and an ellipsis, never parting a surrogate pair, and never a count", () => { + assert.equal(glimpse('short', 300), 'short'); + assert.equal(glimpse('x'.repeat(400), 300), `${'x'.repeat(300)}…`); + // A pair across the cut is left out whole. + const pair = `${'a'.repeat(299)}😀b`; + assert.equal(glimpse(pair, 300), `${'a'.repeat(299)}…`); + assert.doesNotMatch(glimpse('m'.repeat(20_000), 300), /dropped here/); +}); + +test('durations are said exactly, a fraction of a second included', () => { + assert.equal(formatMs(1_500), '1.5 s'); + assert.equal(formatMs(300), '300 ms'); + assert.equal(formatMs(120_000), '2 min'); + assert.equal(formatMs(86_400_000), '24 h'); +}); + +test('durations at their sites are said exactly: a yield, a lifetime', () => { + const running = renderReturn(ret({ running: true, ending: null }), { boundMs: 1_500, lifetimeMs: 86_400_000 }); + assert.match(running, /is still running after 1\.5 s\./); + const limit = renderReturn(ret({ ending: { how: 'stopped', reason: 'limit' } }), { boundMs: 30_000, lifetimeMs: 1_500 }); + assert.match(limit, /was stopped at its limit of 1\.5 s\./); +}); + +test('calls waiting their turn in the host are among those a yield counts', () => { + const text = renderReturn(ret({ running: true, ending: null, calls: { ...noCalls, made: 19, running: 16, waiting: 3 } }), options); + assert.match(text, /Its calls so far: 19 \(16 still running; 3 waiting their turn in the host\)\./); +}); + +test("bounded's cut, from the text's first and last halves (cutOf), is its cut as first written, over the whole text", () => { + // The cut as it was first written, over the whole text. + const whole = (text: string, max: number): string => { + if (text.length <= max) return text; + const half = Math.floor(max / 2); + let head = half; + const last = text.charCodeAt(head - 1); + if (last >= 0xd800 && last <= 0xdbff) head -= 1; + let tail = text.length - half; + const first = text.charCodeAt(tail); + if (first >= 0xdc00 && first <= 0xdfff) tail += 1; + return `${text.slice(0, head)}\n[… ${tail - head} characters dropped here …]\n${text.slice(tail)}`; + }; + // mulberry32, whose 32-bit steps lose nothing in a JavaScript number. + let seed = 7; + const next = (n: number) => { + seed = (seed + 0x6d2b79f5) | 0; + let t = Math.imul(seed ^ (seed >>> 15), 1 | seed); + t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; + return Math.floor((((t ^ (t >>> 14)) >>> 0) / 4294967296) * n); + }; + // Surrogate pairs, so a cut falls inside one, and lone halves too, at bounds from none up. + const alphabet = ['a', 'b', '\n', '\u{1F600}', '\u00e9', '\ud800', '\udc00']; + for (let round = 0; round < 500; round++) { + const max = next(41); + const text = Array.from({ length: next(120) }, () => alphabet[next(alphabet.length)]).join(''); + assert.equal(bounded(text, max), whole(text, max), JSON.stringify({ max, text })); + } +}); diff --git a/test/code-execution-isolate-runner.test.ts b/test/code-execution-isolate-runner.test.ts new file mode 100644 index 000000000..42d392da6 --- /dev/null +++ b/test/code-execution-isolate-runner.test.ts @@ -0,0 +1,1162 @@ +/** + * The isolate's runner with real child processes, on the stand-in engine: the call + * record, the yield, each ending, a child that breaks the protocol, and the + * host's child capacity. The child runs on this test's own runtime. + */ + +import { test } from 'node:test'; +import { strict as assert } from 'node:assert'; +import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { extname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { ChildCapacity, MAX_CALLS_IN_FLIGHT, ScriptRun, type CallAnswer, type ScriptRunOptions } from '../src/code-execution/isolate/runner.js'; +import { MAX_CHILD_FRAME_BYTES } from '../src/code-execution/isolate/protocol.js'; +import { CUT_MARK_CHARS, NAME_CHARS, VALUE_CHARS } from '../src/code-execution/isolate/render.js'; + +// With this file's own extension, as the runner names its entry, so the runtime runs a file that exists. +const EXT = extname(fileURLToPath(import.meta.url)); +const FAKE_CHILD = fileURLToPath(new URL(`./helpers/fake-child-entry${EXT}`, import.meta.url)); +const BAD_CHILD = fileURLToPath(new URL(`./helpers/bad-child-entry${EXT}`, import.meta.url)); + +const TOOLS = [{ name: 'echo', description: 'Echoes.' }, { name: 'slow', description: 'Answers when told.' }]; + +const result = (text: string, isError = false): CallAnswer & { isError: boolean } => ({ + ok: true, + json: JSON.stringify({ content: [{ type: 'text', text }], structuredContent: null, isError }), + isError, +}); + +function run(over: Partial = {}): ScriptRun { + return new ScriptRun({ + childEntry: FAKE_CHILD, + memoryMaxBytes: 64 * 1024 * 1024, + output: { headChars: 1000, tailChars: 1000 }, + flushMs: 20, + lifetimeMs: 60_000, + toolCallTimeoutMs: 60_000, + stopGraceMs: 2_000, + wakeMinIntervalMs: 0, + maxWakes: 100, + onToolCall: async (name, args) => result(`${name} ${args}`), + onWake: async () => ({ ok: true, json: 'null' }), + ...over, + }); +} + +test('a script that calls tools finishes with its value, its output, and each call answered', async () => { + const r = run(); + r.start(`const a = await tools.echo({ n: 1 }); text(a.content[0].text); return 'done';`, TOOLS); + const snap = await r.wait(10_000); + assert.deepEqual(snap.ending, { how: 'finished', value: '"done"' }); + assert.equal(snap.output, 'echo {"n":1}\n'); + assert.deepEqual(snap.calls, { made: 1, answered: 1, failed: 0, refused: 0, unsent: 0, running: 0, waiting: 0, named: [], more: {} }); + await r.exited; +}); + +/** Wait for a condition, failing after 10 s rather than hanging the run. */ +const until = async (cond: () => boolean, what: string) => { + const deadline = Date.now() + 10_000; + while (!cond()) { + if (Date.now() > deadline) throw new Error(`timed out waiting for ${what}`); + await new Promise((r) => setTimeout(r, 10)); + } +}; + +test('past its bound a run yields its output so far and keeps running, and a later wait gets its end', async () => { + let release!: () => void; + const gate = new Promise((resolve) => (release = resolve)); + let called = false; + const r = run({ onToolCall: async (name, args) => { if (name === 'slow') { called = true; await gate; } return result(`${name} ${args}`); } }); + r.start(`text('before'); await tools.slow({}); text('after'); return 1;`, TOOLS); + // The child goes idle on its call, flushing what the guest wrote first. + await until(() => called && r.snapshot().output === 'before\n', 'the call and the output before it'); + const first = await r.wait(0); + assert.equal(first.ending, null); + assert.equal(first.output, 'before\n'); + assert.deepEqual(first.calls, { made: 1, answered: 0, failed: 0, refused: 0, unsent: 0, running: 1, waiting: 0, named: [], more: {} }); + release(); + const second = await r.wait(10_000); + assert.deepEqual(second.ending, { how: 'finished', value: '1' }); + assert.equal(second.output, 'before\nafter\n'); + await r.exited; +}); + +test("the agent's stop ends an idle run as stopped, and the call it was waiting on is uncertain", async () => { + let called = false; + const r = run({ onToolCall: () => { called = true; return new Promise(() => {}); } }); + r.start(`await tools.slow({});`, TOOLS); + // The stop comes once the call is out, however slow the child was to start: one asked earlier refuses it. + await until(() => called, 'the call'); + const snap = await r.stop('agent'); + assert.deepEqual(snap.ending, { how: 'stopped', reason: 'agent' }); + assert.deepEqual(snap.calls, { made: 1, answered: 0, failed: 0, refused: 0, unsent: 0, running: 0, waiting: 0, named: [{ tool: 'slow', args: '{}', state: 'uncertain' }], more: {} }); + await r.exited; +}); + +test('a computing run is stopped through the stop file, with its output so far', async () => { + const r = run(); + r.start(`text('spinning'); for (;;) __tick();`, TOOLS); + // The guest's output is flushed from its interrupt checks while it computes. + await until(() => r.snapshot().output === 'spinning\n', 'the output while it computes'); + const snap = await r.stop('limit'); + assert.deepEqual(snap.ending, { how: 'stopped', reason: 'limit' }); + assert.equal(snap.output, 'spinning\n'); + await r.exited; +}); + +test('a run still going at its limit is stopped there', async () => { + const r = run({ lifetimeMs: 300 }); + r.start(`for (;;) __tick();`, TOOLS); + const snap = await r.wait(10_000); + assert.deepEqual(snap.ending, { how: 'stopped', reason: 'limit' }); + await r.exited; +}); + +test("a guest that never reaches the interrupt is ended after the stop's grace, and its ending says so", async () => { + const r = run({ lifetimeMs: 200, stopGraceMs: 300 }); + r.start(`text('stuck'); for (;;) {}`, TOOLS); + const snap = await r.wait(10_000); + assert.equal(snap.ending?.how, 'stopped'); + assert.equal((snap.ending as { short?: boolean }).short, true); + await r.exited; +}); + +test('a refusal and a failed result are recorded as such, and the script sees each', async () => { + const r = run({ + onToolCall: async (name) => name === 'echo' + ? { ok: false, json: JSON.stringify({ name: 'ToolRefused', message: 'echo is not yours now; nothing was sent' }) } + : result('it broke', true), + }); + r.start(`let refused; try { await tools.echo({}); } catch (e) { refused = e.message; } const f = await tools.slow({}); return [refused, f.isError];`, TOOLS); + const snap = await r.wait(10_000); + assert.deepEqual(snap.ending, { how: 'finished', value: '["echo is not yours now; nothing was sent",true]' }); + assert.deepEqual([snap.calls.refused, snap.calls.failed], [1, 1]); + await r.exited; +}); + +test('a call with no answer within its timeout rejects in the script, and is recorded as timed out', async () => { + const r = run({ toolCallTimeoutMs: 200, onToolCall: () => new Promise(() => {}) }); + r.start(`try { await tools.slow({}); } catch (e) { return [e.name, /may still take effect/.test(e.message)]; }`, TOOLS); + const snap = await r.wait(10_000); + assert.deepEqual(snap.ending, { how: 'finished', value: '["TimeoutError",true]' }); + assert.deepEqual(snap.calls, { made: 1, answered: 0, failed: 0, refused: 0, unsent: 0, running: 0, waiting: 0, named: [{ tool: 'slow', args: '{}', state: 'timed-out' }], more: {} }); + await r.exited; +}); + +for (const how of ['garbage', 'not-a-message', 'result-kind', 'empty'] as const) { + test(`a child that breaks the protocol (${how}) ends its run as crashed`, async () => { + const r = run({ childEntry: BAD_CHILD, childArgs: [how] }); + r.start('return 1;', TOOLS); + const snap = await r.wait(10_000); + assert.equal(snap.ending?.how, 'crashed'); + assert.match((snap.ending as { detail: string }).detail, /sent the host a message it couldn't read/); + await r.exited; + }); +} + +test("a stop's reason is the one the parent asked, whatever the child says", async () => { + const r = run({ childEntry: BAD_CHILD, childArgs: ['stopped-as-agent'] }); + r.start('return 1;', TOOLS); + const snap = await r.stop('limit', 'it reached its limit'); + assert.deepEqual(snap.ending, { how: 'stopped', reason: 'limit', why: 'it reached its limit' }); + await r.exited; +}); + +test('a child that says the script stopped when no stop was asked crashes its run', async () => { + const r = run({ childEntry: BAD_CHILD, childArgs: ['stopped-unasked'] }); + r.start('return 1;', TOOLS); + const snap = await r.wait(10_000); + assert.equal(snap.ending?.how, 'crashed'); + assert.match((snap.ending as { detail: string }).detail, /no stop was asked/); + await r.exited; +}); + +test("a run whose process exited before its ending came holds no child once it ends", async () => { + const r = run({ childEntry: BAD_CHILD, childArgs: ['late-end'] }); + r.start('return 1;', TOOLS); + await r.exited; + const snap = await r.wait(10_000); + assert.deepEqual(snap.ending, { how: 'finished', value: '"late"' }); + assert.equal((r as unknown as { child: unknown }).child, null); +}); + +test("a child's last frames count though its process exits before the parent reads them", async () => { + // A grace far past the fixture's 300 ms, however slow its writer is to start on a loaded host. + const r = run({ childEntry: BAD_CHILD, childArgs: ['late-end'], stopGraceMs: 30_000 }); + r.start('return 1;', TOOLS); + const snap = await r.wait(10_000); + assert.deepEqual(snap.ending, { how: 'finished', value: '"late"' }); + await r.exited; +}); + +test("a child whose fd 3 stays open after its process exits crashes its run once the stop's grace has passed", async () => { + const r = run({ childEntry: BAD_CHILD, childArgs: ['held-fd3'], stopGraceMs: 300 }); + r.start('return 1;', TOOLS); + const snap = await r.wait(5_000); + assert.equal(snap.ending?.how, 'crashed'); + assert.match((snap.ending as { detail: string }).detail, /exited with code 0 before the script ended/); + await r.exited; +}); + +test("arguments past a call's bytes or values are refused in the child, nothing sent, and a large file's text still fits", async () => { + const calls: number[] = []; + const r = run({ onToolCall: async (_name, args) => { calls.push(args.length); return result('ok'); } }); + r.start( + `const out = []; + // 3 MiB of quotes: 6 MiB of JSON once escaped, past the 4 MiB a call takes. + try { await tools.echo({ s: '"'.repeat(3 * 1024 * 1024) }); } catch (e) { out.push(e.name, /nothing was sent/.test(e.message)); } + try { await tools.echo({ a: Array.from({ length: 100001 }, () => 0) }); } catch (e) { out.push(e.name, /100,000 values/.test(e.message)); } + await tools.echo({ content: 'x'.repeat(4 * 1024 * 1024 - 100) }); + return out;`, + TOOLS, + ); + const snap = await r.wait(60_000); + assert.deepEqual(snap.ending, { how: 'finished', value: '["ArgumentsTooLarge",true,"ArgumentsTooLarge",true]' }); + assert.equal(calls.length, 1, 'only the file-sized call went out'); + assert.equal(snap.calls.made, 1); + await r.exited; +}); + +test('results reach a computing child one frame at a time, the rest held as the results they are', async () => { + const big = 'x'.repeat(4 * 1024 * 1024); + let answered = 0; + const r = run({ onToolCall: async () => { answered += 1; return result(big); } }); + const internals = r as unknown as { child: { stdin: { writableLength: number } } | null; resultQueue: unknown[] }; + const gateDir = mkdtempSync(join(tmpdir(), 'af-gate-')); + const gate = join(gateDir, 'go'); + // Four calls go out, and the guest computes, reading no answer, until the test makes the gate. + r.start(`const ps = [1, 2, 3, 4].map(() => tools.echo({})); while (!__exists(${JSON.stringify(gate)})) __tick(); return (await Promise.all(ps)).map((x) => x.content[0].text.length);`, TOOLS); + // All four answered: a frame went to the pipe, and the others wait as results until it drains. + for (let i = 0; i < 3_000 && (answered < 4 || internals.resultQueue.length < 2); i++) await new Promise((res) => setTimeout(res, 10)); + assert.equal(answered, 4); + assert.ok(internals.resultQueue.length >= 2, `${internals.resultQueue.length} results waited while a frame was in the pipe`); + // At most one frame in the pipe's buffer (Node's writableLength; Bun reads 0). + assert.ok((internals.child?.stdin.writableLength ?? 0) <= big.length + 1024, `the pipe held ${internals.child?.stdin.writableLength} bytes`); + writeFileSync(gate, ''); + const snap = await r.wait(30_000); + assert.deepEqual(snap.ending, { how: 'finished', value: JSON.stringify([big.length, big.length, big.length, big.length]) }); + await r.exited; + rmSync(gateDir, { recursive: true, force: true }); +}); + +test('a run that ends with results waiting for the child drops them', async () => { + const big = 'x'.repeat(4 * 1024 * 1024); + const r = run({ onToolCall: async () => result(big) }); + const internals = r as unknown as { resultQueue: unknown[] }; + r.start(`const ps = [1, 2, 3, 4].map(() => tools.echo({})); for (;;) __tick();`, TOOLS); + await until(() => internals.resultQueue.length >= 2, 'results waiting for the child'); + const snap = await r.stop('agent'); + assert.equal(snap.ending?.how, 'stopped'); + assert.equal(internals.resultQueue.length, 0); + await r.exited; +}); + +test(`at most ${MAX_CALLS_IN_FLIGHT} calls are in flight at once, and one past it waits for a place, never refused`, async () => { + let inFlight = 0; + let most = 0; + // The first calls are held until as many as may be out at once have come, + // so the most at once is the bound itself, however the child is scheduled. + const first: Array<() => void> = []; + let filled = false; + const r = run({ + onToolCall: async () => { + most = Math.max(most, ++inFlight); + if (!filled) { + await new Promise((resolve) => { + first.push(resolve); + if (first.length === MAX_CALLS_IN_FLIGHT) { + filled = true; + for (const go of first.splice(0)) go(); + } + }); + } + inFlight -= 1; + return result('ok'); + }, + }); + r.start(`return (await Promise.all(Array.from({ length: 40 }, () => tools.echo({})))).length;`, TOOLS); + const snap = await r.wait(30_000); + assert.deepEqual(snap.ending, { how: 'finished', value: '40' }); + assert.equal(most, MAX_CALLS_IN_FLIGHT); + assert.equal(snap.calls.answered, 40); + await r.exited; +}); + +/** A bridge that holds each call until the test lets it answer, counting how many it holds at once. */ +function holdingBridge() { + const held: Array<() => void> = []; + const bridge = { held, inBridge: 0, most: 0, calls: 0 }; + const onToolCall = async (name: string, args: string): Promise => { + bridge.calls += 1; + bridge.most = Math.max(bridge.most, ++bridge.inBridge); + if (JSON.parse(args).hold) await new Promise((resolve) => held.push(resolve)); + bridge.inBridge -= 1; + return result(`${name} ${args}`); + }; + return { bridge, onToolCall }; +} + +// Each of the first 16 times out in the script while the bridge still holds it. +const SIXTEEN_TIME_OUT = `const names = await Promise.all(Array.from({ length: ${MAX_CALLS_IN_FLIGHT} }, (_, i) => tools.slow({ hold: true, i }).then(() => 'answered', (e) => e.name)));`; + +test(`the host holds at most ${MAX_CALLS_IN_FLIGHT} calls, timed-out ones included: one past them waits, and goes out once one ends`, async () => { + const { bridge, onToolCall } = holdingBridge(); + const r = run({ toolCallTimeoutMs: 1_500, onToolCall }); + const internals = r as unknown as { dispatchWaiting: Map }; + r.start(`${SIXTEEN_TIME_OUT} const late = await tools.echo({ late: true }); return [names.every((n) => n === 'TimeoutError'), late.content[0].text];`, TOOLS); + // All sixteen timed out in the script, so its places are free, and the next call waits for the host. + await until(() => internals.dispatchWaiting.size === 1, 'the next call to wait for room in the host'); + assert.equal(bridge.inBridge, MAX_CALLS_IN_FLIGHT); + bridge.held.shift()!(); + const snap = await r.wait(30_000); + assert.deepEqual(snap.ending, { how: 'finished', value: '[true,"echo {\\"late\\":true}"]' }); + assert.equal(bridge.most, MAX_CALLS_IN_FLIGHT); + assert.equal(bridge.calls, MAX_CALLS_IN_FLIGHT + 1); + while (bridge.held.length) bridge.held.shift()!(); + await r.exited; +}); + +test('a call that waits for room in the host past its timeout is told it was never sent, and the record counts it so', async () => { + const { bridge, onToolCall } = holdingBridge(); + const r = run({ toolCallTimeoutMs: 1_000, onToolCall }); + r.start(`${SIXTEEN_TIME_OUT} try { await tools.echo({ late: true }); return 'sent'; } catch (e) { return [e.name, e.message]; }`, TOOLS); + const snap = await r.wait(30_000); + assert.equal(snap.ending?.how, 'finished'); + const [name, message] = JSON.parse((snap.ending as { value: string }).value) as [string, string]; + assert.equal(name, 'TimeoutError'); + assert.match(message, /^echo was never sent: 16 of this script's earlier calls were still running in the host, timed-out ones included, and its turn didn't come within 1 s, so nothing was done; calling it again may go through once one of them gets its result, though a call whose tool the host stopped waiting for stays counted until this script ends, and a new script starts with none$/); + assert.equal(bridge.calls, MAX_CALLS_IN_FLIGHT, 'the late call never reached the bridge'); + assert.equal(snap.calls.made, MAX_CALLS_IN_FLIGHT + 1); + assert.equal(snap.calls.unsent, 1); + assert.equal(snap.calls.refused, 0, 'a call that waited for room was not refused'); + while (bridge.held.length) bridge.held.shift()!(); + await r.exited; +}); + +test('a call waiting for room in the host when its script is stopped is never sent', async () => { + const { bridge, onToolCall } = holdingBridge(); + const r = run({ toolCallTimeoutMs: 1_000, onToolCall }); + const internals = r as unknown as { dispatchWaiting: Map }; + r.start(`${SIXTEEN_TIME_OUT} await tools.echo({ late: true });`, TOOLS); + await until(() => internals.dispatchWaiting.size === 1, 'the next call to wait for room in the host'); + const snap = await r.stop('agent'); + assert.equal(internals.dispatchWaiting.size, 0); + // The script made it, and nothing was done: the record says so. + assert.equal(snap.calls.made, MAX_CALLS_IN_FLIGHT + 1); + assert.equal(snap.calls.unsent, 1); + while (bridge.held.length) bridge.held.shift()!(); + await new Promise((res) => setImmediate(res)); + assert.equal(bridge.calls, MAX_CALLS_IN_FLIGHT, 'the waiting call never reached the bridge'); + await r.exited; +}); + +test('a call waiting for room in the host when a stop is asked is refused once a place frees, and never sent', async () => { + const { bridge, onToolCall } = holdingBridge(); + const r = run({ toolCallTimeoutMs: 1_000, onToolCall }); + const internals = r as unknown as { dispatchWaiting: Map }; + r.start(`${SIXTEEN_TIME_OUT} await tools.echo({ late: true });`, TOOLS); + await until(() => internals.dispatchWaiting.size === 1, 'the next call to wait for room in the host'); + const stopped = r.stop('agent'); + // A place frees after the stop was asked, before the script has ended. + bridge.held.shift()!(); + const snap = await stopped; + assert.equal(bridge.calls, MAX_CALLS_IN_FLIGHT, 'the waiting call never reached the bridge'); + assert.equal(snap.calls.refused, 1); + while (bridge.held.length) bridge.held.shift()!(); + await r.exited; +}); + +test('wake_agent() takes no place among the calls in flight', async () => { + let held = 0; + let woke = false; + const r = run({ + onToolCall: async () => { held += 1; return new Promise(() => {}); }, + onWake: async () => { woke = true; return { ok: true, json: 'null' }; }, + }); + // Every place is held by a call that never answers, and the wake still goes through. + r.start(`const ps = Array.from({ length: ${MAX_CALLS_IN_FLIGHT} }, () => tools.slow({})); await wake_agent({ n: 1 }); await Promise.all(ps);`, TOOLS); + await until(() => woke, 'the wake'); + assert.equal(held, MAX_CALLS_IN_FLIGHT); + await r.stop('agent'); + await r.exited; +}); + +test('a call waiting in the script for a place when its script is stopped is never sent', async () => { + let dispatched = 0; + const r = run({ onToolCall: async () => { dispatched += 1; return new Promise(() => {}); } }); + r.start(`await Promise.all(Array.from({ length: ${MAX_CALLS_IN_FLIGHT + 4} }, () => tools.slow({})));`, TOOLS); + const deadline = Date.now() + 10_000; + while (dispatched < MAX_CALLS_IN_FLIGHT && Date.now() < deadline) await new Promise((resolve) => setTimeout(resolve, 10)); + const snap = await r.stop('agent'); + await new Promise((resolve) => setTimeout(resolve, 50)); + assert.equal(dispatched, MAX_CALLS_IN_FLIGHT); + assert.equal(snap.calls.made, MAX_CALLS_IN_FLIGHT); + assert.equal(snap.calls.named.length + Object.values(snap.calls.more).reduce((a, b) => a + b, 0), MAX_CALLS_IN_FLIGHT, 'each sent call is uncertain'); + await r.exited; +}); + +test('a child that exits without saying how the script ended crashes the run, with its stderr', async () => { + const r = run({ childEntry: BAD_CHILD, childArgs: ['silent-exit'] }); + r.start('return 1;', TOOLS); + const snap = await r.wait(10_000); + assert.equal(snap.ending?.how, 'crashed'); + assert.match((snap.ending as { detail: string }).detail, /exited with code 3 .*something to say on its way out/s); + await r.exited; +}); + +test("the host's capacity counts children until each exits", () => { + const capacity = new ChildCapacity(2); + assert.equal(capacity.take(), true); + assert.equal(capacity.take(), true); + assert.equal(capacity.take(), false); + capacity.release(); + assert.equal(capacity.running, 1); + assert.equal(capacity.take(), true); +}); + +test('a result too large for the script is refused before it is sent, naming its size, and recorded as too large', async () => { + // A third of 3 MiB is 1 MiB: a result of 2 MiB can't be held. + const r = run({ memoryMaxBytes: 3 * 1024 * 1024, onToolCall: async () => result('x'.repeat(2 * 1024 * 1024)) }); + r.start(`try { await tools.echo({}); } catch (e) { return [e.name, e.bytes > 2000000, /took effect/.test(e.message)]; }`, TOOLS); + const snap = await r.wait(10_000); + assert.deepEqual(snap.ending, { how: 'finished', value: '["ResultTooLarge",true,true]' }); + assert.deepEqual(snap.calls, { made: 1, answered: 0, failed: 0, refused: 0, unsent: 0, running: 0, waiting: 0, named: [{ tool: 'echo', args: '{}', state: 'too-large' }], more: {} }); + await r.exited; +}); + +test('a child whose runtime cannot start crashes its run, and its place is freed', async () => { + const r = run({ execPath: '/nonexistent/runtime-for-a-test' }); + r.start('return 1;', TOOLS); + const snap = await r.wait(10_000); + assert.equal(snap.ending?.how, 'crashed'); + assert.match((snap.ending as { detail: string }).detail, /^its process could not run: /); + await r.exited; +}); + +test(`a child that sends more than ${MAX_CALLS_IN_FLIGHT} calls at once crashes its run, saying why`, async () => { + const r = run({ childEntry: BAD_CHILD, childArgs: ['seventeen-calls'], onToolCall: () => new Promise(() => {}) }); + r.start('return 1;', TOOLS); + const snap = await r.wait(10_000); + assert.equal(snap.ending?.how, 'crashed'); + assert.match((snap.ending as { detail: string }).detail, new RegExp(`more than ${MAX_CALLS_IN_FLIGHT} calls at once`)); + await r.exited; +}); + +for (const [how, detail] of [ + ['undeclared-call', /a call to "nope", which isn't one of the script's tools/], + ['reused-id', /call 1 was named twice/], + ['unanswered-seen', /had the answer to call 5, which the host never sent/], + ['seen-pending', /had the answer to call 1, which the host never sent/], + ['wakes-past-cap', /more than 2 wakes/], + ['cut-frame', /the stream ended inside a frame/], + ['huge-out', new RegExp(`characters of output in one frame, past the 65,536 it sends at most`)], + ['many-values-frame', /a frame held more than 32 values/], + ['args-past-caps', /a call to echo whose arguments pass their caps of 4 MiB and 100,000 values, which the script refuses itself/], + ['wake-past-payload', /a wake of 65,539 bytes, past the 64 KiB the script refuses itself/], +] as const) { + test(`a child that breaks the protocol (${how}) crashes its run, saying how`, async () => { + const r = run({ childEntry: BAD_CHILD, childArgs: [how], maxWakes: 2, onToolCall: () => new Promise(() => {}) }); + r.start('return 1;', TOOLS); + const snap = await r.wait(10_000); + assert.equal(snap.ending?.how, 'crashed', JSON.stringify(snap.ending)); + assert.match((snap.ending as { detail: string }).detail, detail); + await r.exited; + }); +} + +test("a failure's name, message and stack from a child are cut in the parent, whatever length the child sends", async () => { + const r = run({ childEntry: BAD_CHILD, childArgs: ['long-failure'] }); + r.start('return 1;', TOOLS); + const snap = await r.wait(10_000); + assert.equal(snap.ending?.how, 'failed', JSON.stringify(snap.ending).slice(0, 200)); + const { error } = snap.ending as { error: { name: string; message: string; stack?: string } }; + for (const [part, text, max] of [['name', error.name, NAME_CHARS], ['message', error.message, VALUE_CHARS], ['stack', error.stack ?? '', VALUE_CHARS]] as const) { + assert.match(text, /characters dropped here/, `the ${part} was cut`); + assert.ok(text.length < max + 2 * CUT_MARK_CHARS, `the ${part} is ${text.length} characters`); + } + await r.exited; +}); + +test('a wake still waiting for its spacing when its run crashes is never sent, and the ending lets it go', async () => { + let wakes = 0; + const r = run({ wakeMinIntervalMs: 60_000, onWake: async () => (wakes += 1, { ok: true, json: 'null' }) }); + const internals = r as unknown as { child: { kill(signal: string): boolean }; wakeWaiters: Set }; + r.start(`await wake_agent(1); await wake_agent(2);`, TOOLS); + await until(() => wakes === 1 && internals.wakeWaiters.size === 1, 'the second wake to wait for its spacing'); + // Its process dies, as one killed from outside would. + internals.child.kill('SIGKILL'); + const snap = await r.wait(10_000); + assert.equal(snap.ending?.how, 'crashed'); + assert.equal(internals.wakeWaiters.size, 0, 'the ending let the waiting wake go'); + await new Promise((resolve) => setImmediate(resolve)); + assert.equal(wakes, 1, 'the waiting wake was never sent'); + await r.exited; +}); + +test("a child that says how its script ended and doesn't exit is ended after the stop's grace", async () => { + const r = run({ childEntry: BAD_CHILD, childArgs: ['finished-then-hold'], stopGraceMs: 300 }); + r.start('return 1;', TOOLS); + const snap = await r.wait(10_000); + assert.deepEqual(snap.ending, { how: 'finished', value: '1' }); + const exited = await Promise.race([r.exited.then(() => true), new Promise((resolve) => setTimeout(() => resolve(false), 10_000))]); + assert.equal(exited, true, 'its process was ended'); +}); + +test("a crash's detail has the stderr that comes after its process exits", async () => { + const r = run({ childEntry: BAD_CHILD, childArgs: ['late-stderr'], stopGraceMs: 10_000 }); + r.start('return 1;', TOOLS); + const snap = await r.wait(15_000); + assert.equal(snap.ending?.how, 'crashed'); + assert.match((snap.ending as { detail: string }).detail, /words that came late/); + await r.exited; +}); + +test("the child's environment is empty, with a .env file where the host runs", async () => { + // Bun reads a .env file from the directory a process starts in: the child starts in its own. + const hostDir = mkdtempSync(join(tmpdir(), 'af-host-')); + writeFileSync(join(hostDir, '.env'), 'FROM_THE_HOSTS_DOTENV=1\n'); + const was = process.cwd(); + process.chdir(hostDir); + try { + const r = run({ childEntry: BAD_CHILD, childArgs: ['env'] }); + r.start('return 1;', TOOLS); + const snap = await r.wait(10_000); + assert.equal(snap.ending?.how, 'crashed'); + assert.match((snap.ending as { detail: string }).detail, /exited with code 3 before the script ended: \[\]$/); + await r.exited; + } finally { + process.chdir(was); + rmSync(hostDir, { recursive: true, force: true }); + } +}); + +test('a call that comes after a stop was asked is not dispatched', async () => { + let dispatched = 0; + const r = run({ childEntry: BAD_CHILD, childArgs: ['call-after-stop'], onToolCall: async () => { dispatched += 1; return result('ok'); } }); + r.start('return 1;', TOOLS); + const snap = await r.stop('agent'); + assert.equal(snap.ending?.how, 'stopped'); + assert.equal(dispatched, 0); + // It reached the parent, which refused it, and the record counts the refusal, unseen at the ending. + assert.equal(snap.calls.refused, 1); + assert.deepEqual(snap.calls.named, []); + await r.exited; +}); + +test('at a stop, an answer the script never read is named in the record', async () => { + const r = run({ childEntry: BAD_CHILD, childArgs: ['no-seen-then-stop'] }); + const internals = r as unknown as { delivered: Map }; + r.start('return 1;', TOOLS); + await until(() => internals.delivered.size === 1, 'the answer sent'); + const snap = await r.stop('agent'); + assert.deepEqual(snap.calls.named, [{ tool: 'echo', args: '{}', state: 'unseen' }]); + await r.exited; +}); + +test('a bridge that rejects answers the call as a failure that may have taken effect', async () => { + const r = run({ onToolCall: async () => { throw new Error('the dispatcher fell over'); } }); + r.start(`const r = await tools.echo({}); return [r.isError, r.content[0].text];`, TOOLS); + const snap = await r.wait(10_000); + assert.equal(snap.ending?.how, 'finished'); + const [isError, text] = JSON.parse((snap.ending as { value: string }).value) as [boolean, string]; + assert.equal(isError, true); + assert.match(text, /echo failed in the host: the dispatcher fell over; it may have taken effect/); + await r.exited; +}); + +test('a too-large answer waiting to be seen keeps only its brief', async () => { + let release!: () => void; + const hold = new Promise((resolve) => (release = resolve)); + const r = run({ memoryMaxBytes: 3 * 1024 * 1024, onToolCall: async () => { await hold; return result('y'.repeat(2 * 1024 * 1024)); } }); + const internals = r as unknown as { delivered: Map }; + const gateDir = mkdtempSync(join(tmpdir(), 'af-gate-')); + const gate = join(gateDir, 'go'); + // The guest computes until the gate opens, so the answer stays sent and unseen meanwhile. + r.start(`const p = tools.echo({}); while (!__exists(${JSON.stringify(gate)})) __tick(); try { await p; } catch (e) { return e.name; }`, TOOLS); + release(); + await until(() => internals.delivered.size === 1, 'the answer sent and not yet seen'); + assert.ok([...internals.delivered.values()][0]!.brief.length <= 120); + writeFileSync(gate, ''); + const snap = await r.wait(10_000); + rmSync(gateDir, { recursive: true, force: true }); + assert.deepEqual(snap.ending, { how: 'finished', value: '"ResultTooLarge"' }); + await r.exited; +}); + +test('an ended run holds no call ids, places or answers', async () => { + const r = run({ onToolCall: () => new Promise(() => {}) }); + const internals = r as unknown as { pendingIds: Set; answeredIds: Set; inFlight: Set }; + r.start(`await Promise.all([tools.slow({}), tools.slow({})]);`, TOOLS); + await until(() => internals.inFlight.size === 2, 'two calls in flight'); + await r.stop('agent'); + assert.deepEqual([internals.pendingIds.size, internals.answeredIds.size, internals.inFlight.size], [0, 0, 0]); + await r.exited; +}); + +test('an answered call the script never saw is cleared when its run ends', async () => { + const r = run({ childEntry: BAD_CHILD, childArgs: ['no-seen-then-stop'] }); + const internals = r as unknown as { answeredIds: Set }; + r.start('return 1;', TOOLS); + await until(() => internals.answeredIds.size === 1, 'the answer written'); + await r.stop('agent'); + assert.equal(internals.answeredIds.size, 0); + await r.exited; +}); + +test("a child's cut of a long value, and the count it gives, pass the parent's bound as they are", async () => { + const r = run(); + r.start(`return 'v'.repeat(20000);`, TOOLS); + const snap = await r.wait(10_000); + assert.equal(snap.ending?.how, 'finished'); + // Its JSON is 20,002 characters, of which the child keeps 8,000. + assert.match((snap.ending as { value: string }).value, /\[… 12002 characters dropped here …\]/); +}); + +test("a seen for an answer the parent hasn't written yet crashes the run", async () => { + const gateDir = mkdtempSync(join(tmpdir(), 'af-gate-')); + const gate = join(gateDir, 'go'); + const r = run({ childEntry: BAD_CHILD, childArgs: ['seen-queued', gate], onToolCall: async () => result('z'.repeat(2 * 1024 * 1024)) }); + const internals = r as unknown as { resultQueue: unknown[] }; + r.start('return 1;', TOOLS); + // The child reads none of its answers, so the second waits in the parent's queue. + await until(() => internals.resultQueue.length === 1, 'the second answer queued'); + writeFileSync(gate, ''); + const snap = await r.wait(10_000); + assert.equal(snap.ending?.how, 'crashed'); + assert.match((snap.ending as { detail: string }).detail, /had the answer to call 2, which the host never sent/); + rmSync(gateDir, { recursive: true, force: true }); + await r.exited; +}); + +test('an id named again after its answer came and before it was seen crashes the run', async () => { + const r = run({ childEntry: BAD_CHILD, childArgs: ['reuse-after-answer'] }); + r.start('return 1;', TOOLS); + const snap = await r.wait(10_000); + assert.equal(snap.ending?.how, 'crashed'); + assert.match((snap.ending as { detail: string }).detail, /call 1 was named twice/); + await r.exited; +}); + +test("a frame cut off by the stop's own kill ends the run as stopped, not as a broken protocol", async () => { + const r = run({ childEntry: BAD_CHILD, childArgs: ['half-frame-on-stop'], stopGraceMs: 2_000 }); + r.start('return 1;', TOOLS); + // Its listener is on once it has said so, so the stop reaches it and it writes half a frame. + await until(() => r.snapshot().output.includes('ready'), 'the child to be ready'); + const snap = await r.stop('agent'); + // It took the stop and wrote its half frame before the grace's kill. + assert.match(snap.output, /cutting/); + assert.deepEqual(snap.ending, { how: 'stopped', reason: 'agent', short: true }); + await r.exited; +}); + +test("the run's end lets go a wake waiting behind another's delivery", async () => { + const r = run({ onWake: () => new Promise(() => {}) }); + const internals = r as unknown as { wakeFrames: number; wakeChain: Promise }; + r.start(`wake_agent(1); wake_agent(2); await new Promise(() => {});`, TOOLS); + await until(() => internals.wakeFrames === 2, 'two wakes, the second waiting behind the first'); + // Waiting behind the first's delivery, the second is among the calls waiting. + assert.equal(r.snapshot().calls.waiting, 1); + const snap = await r.stop('agent'); + // The chain settles once the second wake has gone or won't, so it settles only if the end let it go. + const settled = await Promise.race([internals.wakeChain.then(() => true), new Promise((resolve) => setTimeout(() => resolve(false), 2_000))]); + assert.equal(settled, true); + // The second never went out, and the record counts it so. + assert.equal(snap.calls.made, 2); + assert.equal(snap.calls.unsent, 1); + await r.exited; +}); + +test('only the wakes the host delivered count as delivered: one it refused does not', async () => { + const r = run({ + onWake: async (payload) => (JSON.parse(payload) === 1 + ? { ok: true, json: 'null' } + : { ok: false, json: JSON.stringify({ name: 'WakeRefused', message: "the wake couldn't be delivered" }) }), + }); + r.start(`await wake_agent(1); try { await wake_agent(2); } catch (e) { return e.name; }`, TOOLS); + const snap = await r.wait(20_000); + assert.deepEqual(snap.ending, { how: 'finished', value: '"WakeRefused"' }); + assert.equal(r.wakesDelivered, 1); + await r.exited; + // Nor does one whose delivery failed in the host, answered as a failure that may have taken effect. + const failing = run({ onWake: async () => { throw new Error('the wake handler broke'); } }); + failing.start(`try { await wake_agent(1); } catch (e) { return e.name; }`, TOOLS); + const failed = await failing.wait(20_000); + assert.equal(failed.ending?.how, 'finished', JSON.stringify(failed.ending)); + assert.equal(failing.wakesDelivered, 0); + await failing.exited; +}); + +test("a wake's spacing counts from when the wake before it was delivered", async () => { + const delivered: Array<{ n: number; start: number; end: number }> = []; + const r = run({ + wakeMinIntervalMs: 200, + onWake: async (_payload, n) => { + const start = performance.now(); + // The first delivery takes longer than the spacing. + if (n === 1) await new Promise((resolve) => setTimeout(resolve, 400)); + delivered.push({ n, start, end: performance.now() }); + return { ok: true, json: 'null' }; + }, + }); + r.start(`await Promise.all([wake_agent(1), wake_agent(2)]); return 'done';`, TOOLS); + const snap = await r.wait(10_000); + assert.deepEqual(snap.ending, { how: 'finished', value: '"done"' }); + const [first, second] = delivered; + assert.ok(second!.start - first!.end >= 190, `the second came ${Math.round(second!.start - first!.end)} ms after the first's delivery`); + await r.exited; +}); + +test('with a relative TMPDIR, a computing script still sees its stop file', async () => { + const relative = `af-reltmp-${process.pid}-${Date.now()}`; + mkdirSync(relative); + const before = process.env.TMPDIR; + process.env.TMPDIR = relative; + try { + const r = run(); + r.start(`for (;;) __tick();`, TOOLS); + await new Promise((resolve) => setTimeout(resolve, 300)); + const snap = await r.stop('agent'); + // Stopped by the stop file, not ended by the grace's kill. + assert.deepEqual(snap.ending, { how: 'stopped', reason: 'agent' }); + await r.exited; + } finally { + if (before === undefined) delete process.env.TMPDIR; + else process.env.TMPDIR = before; + rmSync(relative, { recursive: true, force: true }); + } +}); + +test('a stop asked after the process exited arms no kill', async () => { + const r = run({ childEntry: BAD_CHILD, childArgs: ['exit-holding-fd3'], stopGraceMs: 300 }); + const internals = r as unknown as { killTimer: unknown }; + r.start('return 1;', TOOLS); + await r.exited; + assert.equal(r.snapshot().ending, null, 'fd 3 is still held open'); + const stopped = r.stop('agent'); + assert.equal(internals.killTimer, null); + // The process had gone before the stop was asked: the stop isn't its ending's cause. + const ending = (await stopped).ending; + assert.equal(ending?.how, 'crashed'); + assert.match((ending as { detail: string }).detail, /exited with code 0 before the script ended/); +}); + +for (const [how, why] of [['wakes-then-ignore-stop', 'once a stop was asked'], ['wake-then-exit-on-gate', 'once its process exited']] as const) { + test(`a wake whose turn comes ${why} is never sent, and the record counts it so`, async () => { + const sent: number[] = []; + const gateDir = mkdtempSync(join(tmpdir(), 'af-gate-')); + const gate = join(gateDir, 'go'); + const r = run({ + childEntry: BAD_CHILD, + childArgs: how === 'wake-then-exit-on-gate' ? [how, gate] : [how], + // The turn comes when the test lets it, once the stop is asked or the + // exit has landed: the child holds fd 3 for 4 s after its exit, and the + // run waits up to 10 s for fd 3 to end. + wakeMinIntervalMs: 60_000, + stopGraceMs: how === 'wake-then-exit-on-gate' ? 10_000 : 2_000, + onWake: async (_payload, n) => { sent.push(n); return { ok: true, json: 'null' }; }, + }); + const internals = r as unknown as { wakeWaiters: Set<() => void>; processExited: boolean }; + r.start('return 1;', TOOLS); + // The second wake is waiting out its spacing before the stop, or the exit, comes. + await until(() => sent.length === 1 && internals.wakeWaiters.size === 1, 'the second wake waiting for its turn'); + if (how === 'wakes-then-ignore-stop') void r.stop('agent'); + else { + writeFileSync(gate, ''); + await until(() => internals.processExited, 'the child to exit'); + } + // Its turn comes now, while the run hasn't yet ended. + assert.equal(r.snapshot().ending, null, 'the run had not ended when the turn came'); + for (const release of [...internals.wakeWaiters]) release(); + await new Promise((res) => setImmediate(res)); + assert.equal(r.snapshot().ending, null, 'the run had not ended when the turn passed'); + const snap = await r.wait(20_000); + assert.deepEqual(sent, [1]); + assert.equal(snap.calls.made, 2); + assert.equal(snap.calls.unsent, 1); + // Only the wake that went out is among the script's wakes delivered. + assert.equal(r.wakesDelivered, 1); + assert.equal(snap.ending?.how, how === 'wakes-then-ignore-stop' ? 'stopped' : 'crashed'); + rmSync(gateDir, { recursive: true, force: true }); + await r.exited; + }); +} + +test('a wake waiting for its turn when its run crashes is never sent, and the record counts it so', async () => { + const sent: number[] = []; + const gateDir = mkdtempSync(join(tmpdir(), 'af-gate-')); + const gate = join(gateDir, 'go'); + const r = run({ + childEntry: BAD_CHILD, + childArgs: ['wakes-then-garbage-on-gate', gate], + // Its turn is a minute off: only the run's end lets it go. + wakeMinIntervalMs: 60_000, + onWake: async (_payload, n) => { sent.push(n); return { ok: true, json: 'null' }; }, + }); + const internals = r as unknown as { wakeWaiters: Set; processExited: boolean }; + r.start('return 1;', TOOLS); + await until(() => sent.length === 1 && internals.wakeWaiters.size === 1, 'the second wake waiting for its turn'); + // The crash ends the run while the wake waits, the child still alive: the end lets the wake go at once. + writeFileSync(gate, ''); + const snap = await r.wait(20_000); + assert.equal(snap.ending?.how, 'crashed'); + assert.equal(internals.wakeWaiters.size, 0, 'the end let the waiting wake go'); + await r.exited; + assert.deepEqual(sent, [1], 'the waiting wake never went out'); + assert.equal(snap.calls.made, 2); + assert.equal(snap.calls.unsent, 1); + assert.equal(r.wakesDelivered, 1); + rmSync(gateDir, { recursive: true, force: true }); +}); + +test("a timed-out call's late answer is recorded with its start, and not sent", async () => { + let release!: () => void; + const hold = new Promise((resolve) => (release = resolve)); + const r = run({ + toolCallTimeoutMs: 100, + onToolCall: async (_name, args) => { + if (!args.includes('first')) return new Promise(() => {}); + await hold; + return result('the late answer'); + }, + }); + // The script keeps going after the timeout, so it's running when the late answer comes. + r.start(`try { await tools.slow({ first: true }); } catch (e) { text(e.name); } for (;;) { try { await tools.slow({}); } catch {} }`, TOOLS); + await until(() => r.snapshot().output === 'TimeoutError\n', 'the timeout in the script'); + release(); + await until(() => r.snapshot().calls.named[0]?.state === 'timed-out-answered', 'the late answer recorded'); + const snap = await r.stop('agent'); + // The first call named is the one whose answer came late; the loop's calls after it are named too. + assert.deepEqual([snap.calls.named[0]!.tool, snap.calls.named[0]!.args, snap.calls.named[0]!.state], ['slow', '{"first":true}', 'timed-out-answered']); + assert.match(snap.calls.named[0]!.answer ?? '', /the late answer/); + await r.exited; +}); + +for (const [kind, over, script] of [ + ['refused', { onToolCall: async () => ({ ok: false, json: JSON.stringify({ name: 'ToolRefused', message: 'no' }) }) }, `tools.echo({})`], + ['too large', { memoryMaxBytes: 3 * 1024 * 1024, onToolCall: async () => result('z'.repeat(2 * 1024 * 1024)) }, `tools.echo({})`], + ['timed out', { toolCallTimeoutMs: 50, onToolCall: () => new Promise(() => {}) }, `tools.slow({})`], +] as const) { + test(`a place frees once the script has a ${kind} answer, so ${MAX_CALLS_IN_FLIGHT * 2} such calls all get one`, async () => { + const r = run(over as Partial); + r.start(`const settled = await Promise.allSettled(Array.from({ length: ${MAX_CALLS_IN_FLIGHT * 2} }, () => ${script})); return settled.length;`, TOOLS); + const snap = await r.wait(30_000); + assert.deepEqual(snap.ending, { how: 'finished', value: String(MAX_CALLS_IN_FLIGHT * 2) }); + // Timed out, the first 16 are still held in the host, whose tools never + // answer, so the other 16 wait there past their timeouts and are told + // they were never sent. + assert.equal(snap.calls.unsent, kind === 'timed out' ? MAX_CALLS_IN_FLIGHT : 0); + await r.exited; + }); +} + +test('a wake waiting for its turn when its script is stopped is never sent, and the record counts it so', async () => { + const sent: number[] = []; + const r = run({ wakeMinIntervalMs: 60_000, onWake: async (_payload, n) => { sent.push(n); return { ok: true, json: 'null' }; } }); + const internals = r as unknown as { wakeWaiters: Set }; + r.start(`await wake_agent({ n: 1 }); await wake_agent({ n: 2 });`, TOOLS); + await until(() => sent.length === 1 && internals.wakeWaiters.size === 1, 'the second wake waiting out the spacing'); + const snap = await r.stop('agent'); + assert.deepEqual(sent, [1]); + assert.equal(snap.calls.made, 2); + assert.equal(snap.calls.unsent, 1, 'the waiting wake is counted never sent'); + assert.deepEqual(snap.calls.named, []); + assert.equal(r.wakesDelivered, 1); + await r.exited; +}); + +test("a start frame too long to send starts nothing, and the run's exit resolves at once", async () => { + const r = run(); + // The script alone passes the 64 MiB a frame can take. + assert.throws(() => r.start('x'.repeat(65 * 1024 * 1024), TOOLS), /64|frame/); + await r.exited; + assert.equal((r as unknown as { child: unknown }).child, null); +}); + +test("a call id named again once its TimeoutError was seen takes a place of its own: the host still holds at most 16", async () => { + const { bridge, onToolCall } = holdingBridge(); + const r = run({ childEntry: BAD_CHILD, childArgs: ['reuse-timed-out-id'], toolCallTimeoutMs: 150, onToolCall }); + const internals = r as unknown as { dispatchWaiting: Map }; + r.start('return 1;', TOOLS); + // Sixteen held under one id, and the next waits, never sent. + await until(() => bridge.inBridge === MAX_CALLS_IN_FLIGHT && internals.dispatchWaiting.size === 1, 'sixteen held and one waiting'); + await until(() => r.snapshot().calls.unsent >= 2, 'later ones told they were never sent'); + assert.equal(bridge.most, MAX_CALLS_IN_FLIGHT); + await r.stop('agent'); + while (bridge.held.length) bridge.held.shift()!(); + await r.exited; +}); + +test("a call the bridge answered for, with no result from the host, keeps its place until the run ends", async () => { + let calls = 0; + const r = run({ + toolCallTimeoutMs: 200, + // The bridge's own answer when no result came, which says the tool may + // still run. It comes before the script's timeout here, which still + // gives the script its TimeoutError. + onToolCall: async () => { + calls += 1; + await new Promise((res) => setTimeout(res, 50)); + return { ...result('no result'), held: true }; + }, + }); + // Had the answers freed the places, the seventeenth would go out at once. + r.start(`const all = await Promise.all(Array.from({ length: ${MAX_CALLS_IN_FLIGHT} }, () => tools.slow({}).then(() => 'answered', (e) => e.name))); + try { await tools.echo({}); return 'sent'; } catch (e) { return [all.every((n) => n === 'TimeoutError'), e.name, /was never sent/.test(e.message)]; }`, TOOLS); + const snap = await r.wait(20_000); + assert.deepEqual(snap.ending, { how: 'finished', value: '[true,"TimeoutError",true]' }); + assert.equal(calls, MAX_CALLS_IN_FLIGHT, 'the seventeenth never reached the bridge'); + // The sixteen stay timed out in the record: no answer came for them. + assert.equal(snap.calls.answered + snap.calls.failed, 0); + await r.exited; +}); + +test("a held answer that comes after the script's TimeoutError changes nothing: the call stays timed out, and keeps its place", async () => { + let heldCame = false; + let releaseWake!: () => void; + const wakeGate = new Promise((res) => (releaseWake = res)); + const r = run({ + toolCallTimeoutMs: 100, + // As the bridge's safety timer does: its answer comes well after the script's own timeout. + onToolCall: async () => { + await new Promise((res) => setTimeout(res, 400)); + heldCame = true; + return { ...result('no result'), held: true }; + }, + // A wake, which has no timeout, keeps the run going while the record is read. + onWake: async () => { + await wakeGate; + return { ok: true, json: 'null' }; + }, + }); + const internals = r as unknown as { hostCalls: number }; + r.start(`const t = await tools.slow({}).then(() => 'answered', (e) => e.name); await wake_agent('hold'); return t;`, TOOLS); + await until(() => heldCame, 'the held answer to come'); + await new Promise((res) => setImmediate(res)); + // Still timed out in the record, and its place kept. + const calls = r.snapshot().calls; + assert.equal(calls.answered + calls.failed, 0); + assert.deepEqual(calls.named.map((c) => [c.tool, c.state]), [['slow', 'timed-out']]); + assert.equal(internals.hostCalls, 1); + releaseWake(); + const snap = await r.wait(10_000); + assert.deepEqual(snap.ending, { how: 'finished', value: '"TimeoutError"' }); + assert.deepEqual(snap.calls.named.map((c) => [c.tool, c.state]), [['slow', 'timed-out']]); + await r.exited; +}); + +test("wakes sent after a stop count against the cap, so a child that keeps sending them crashes its run", async () => { + let woke = 0; + const r = run({ childEntry: BAD_CHILD, childArgs: ['wakes-after-stop', '5'], maxWakes: 3, onWake: async () => { woke += 1; return { ok: true, json: 'null' }; } }); + r.start('return 1;', TOOLS); + await until(() => r.snapshot().output.includes('ready'), 'the child to be ready'); + const snap = await r.stop('agent'); + assert.equal(snap.ending?.how, 'crashed'); + assert.match((snap.ending as { detail: string }).detail, /more than 3 wakes, the most a script may send/); + assert.equal(woke, 0); + await r.exited; +}); + +test('a wake first received after a stop is refused, and the record counts it', async () => { + let woke = 0; + const r = run({ childEntry: BAD_CHILD, childArgs: ['wakes-after-stop', '1'], stopGraceMs: 500, onWake: async () => { woke += 1; return { ok: true, json: 'null' }; } }); + r.start('return 1;', TOOLS); + await until(() => r.snapshot().output.includes('ready'), 'the child to be ready'); + const snap = await r.stop('agent'); + assert.equal(snap.ending?.how, 'stopped'); + assert.equal(snap.calls.refused, 1); + assert.equal(woke, 0); + // Refused, it never went out: the script's wakes delivered count none. + assert.equal(r.wakesDelivered, 0); + await r.exited; +}); + +test("once its process has exited, nothing a script asks goes out: a call waiting for room, or one read after the exit", async () => { + const { bridge, onToolCall } = holdingBridge(); + const gateDir = mkdtempSync(join(tmpdir(), 'af-gate-')); + const gate = join(gateDir, 'go'); + const r = run({ childEntry: BAD_CHILD, childArgs: ['calls-then-exit-on-gate', gate], toolCallTimeoutMs: 3_000, onToolCall }); + const internals = r as unknown as { dispatchWaiting: Map; processExited: boolean }; + r.start('return 1;', TOOLS); + // Its own timeout is a second away: the exit and the freed place both come well before it. + await until(() => internals.dispatchWaiting.size === 1, 'the seventeenth to wait for room'); + writeFileSync(gate, ''); + await until(() => internals.processExited, 'the child to exit'); + // Its own timeout is seconds away: it still waits as the exit lands. + assert.equal(internals.dispatchWaiting.size, 1, 'the seventeenth still waiting when the exit landed'); + // A place frees after the exit, while fd 3 is still open. + bridge.held.shift()!(); + await new Promise((res) => setTimeout(res, 100)); + assert.equal(bridge.calls, MAX_CALLS_IN_FLIGHT, 'the waiting call never reached the bridge'); + const snap = await r.wait(10_000); + assert.equal(snap.ending?.how, 'crashed'); + assert.equal(snap.calls.unsent, 1); + while (bridge.held.length) bridge.held.shift()!(); + rmSync(gateDir, { recursive: true, force: true }); + + // A call frame written to fd 3 after the child has gone, by a process + // holding it, once the runner has seen the exit: the record counts it as + // never sent, which shows the runner read it. + let late = 0; + const lateDir = mkdtempSync(join(tmpdir(), 'af-gate-')); + const lateGate = join(lateDir, 'go'); + const r2 = run({ childEntry: BAD_CHILD, childArgs: ['late-call', lateGate], onToolCall: async () => { late += 1; return result('x'); } }); + const internals2 = r2 as unknown as { processExited: boolean }; + r2.start('return 1;', TOOLS); + await until(() => internals2.processExited, 'the child to exit'); + writeFileSync(lateGate, ''); + const snap2 = await r2.wait(10_000); + assert.equal(snap2.ending?.how, 'crashed'); + assert.equal(late, 0); + assert.equal(snap2.calls.made, 1); + assert.equal(snap2.calls.unsent, 1); + rmSync(lateDir, { recursive: true, force: true }); +}); + +for (const withBrief of [true, false]) { + test(`a refusal too long for the script's memory is still a refusal: its start reaches the script, ${withBrief ? 'from its brief' : 'from its JSON when it has no brief'}, and the record counts it so`, async () => { + const text = `Policy denied: ${'no '.repeat(800_000)}`; + const r = run({ + memoryMaxBytes: 4 * 1024 * 1024, + onToolCall: async () => ({ ok: false, json: JSON.stringify({ name: 'ToolRefused', message: text, tool: 'echo' }), ...(withBrief ? { brief: text } : {}) }), + }); + r.start(`try { await tools.echo({}); return 'ran'; } catch (e) { return [e.name, e.message.length < 3000, e.message.startsWith('Policy denied: no no'), /so only its start is here; nothing was done\\)$/.test(e.message)]; }`, TOOLS); + const snap = await r.wait(20_000); + assert.deepEqual(snap.ending, { how: 'finished', value: '["ToolRefused",true,true,true]' }); + assert.equal(snap.calls.refused, 1); + assert.deepEqual(snap.calls.named, []); + await r.exited; + }); +} + +test('a process that never started holds no kill timer and no child once its run has crashed', async () => { + const r = run({ execPath: '/nonexistent/af-node' }); + const internals = r as unknown as { killTimer: unknown; child: unknown }; + r.start('return 1;', TOOLS); + const snap = await r.wait(10_000); + assert.equal(snap.ending?.how, 'crashed'); + // The cause is named, under Bun too, where spawn can return before its error comes. + assert.match((snap.ending as { detail: string }).detail, /could not run: .*ENOENT/); + assert.equal(internals.killTimer, null); + assert.equal(internals.child, null); + await r.exited; +}); + +test('a call handed a place keeps it while it runs, and the first call waiting is the one handed it', async () => { + const held: Array<{ i: unknown; go: () => void }> = []; + let inBridge = 0; + let most = 0; + const r = run({ + toolCallTimeoutMs: 1_000, + onToolCall: async (_name, args) => { + most = Math.max(most, ++inBridge); + await new Promise((go) => held.push({ i: JSON.parse(args).i, go })); + inBridge -= 1; + return result('ok'); + }, + }); + const internals = r as unknown as { dispatchWaiting: Map; hostCalls: number }; + r.start(`${SIXTEEN_TIME_OUT} const a = tools.slow({ i: 'a' }).catch(() => {}); const b = tools.slow({ i: 'b' }).catch(() => {}); await Promise.all([a, b]);`, TOOLS); + await until(() => internals.dispatchWaiting.size === 2, 'a and b waiting for room'); + // The record counts the two waiting among the calls the script made. + const waiting = r.snapshot().calls; + assert.equal(waiting.waiting, 2); + assert.equal(waiting.made, MAX_CALLS_IN_FLIGHT + 2); + held.shift()!.go(); + // The first waiting, a, went out and holds the place; b still waits. + await until(() => held.length === MAX_CALLS_IN_FLIGHT && held[held.length - 1]!.i === 'a', 'a in the bridge'); + assert.equal(internals.dispatchWaiting.size, 1); + assert.equal(most, MAX_CALLS_IN_FLIGHT); + // a's place was taken for it: the host still counts 16, so no fresh call could go out beside it. + assert.equal(internals.hostCalls, MAX_CALLS_IN_FLIGHT); + assert.equal(r.snapshot().calls.waiting, 1); + await r.stop('agent'); + while (held.length) held.shift()!.go(); + await r.exited; +}); + +test("a wake's spacing runs on a monotonic clock: the wall clock stepped back an hour doesn't hold it", async (t) => { + const realNow = Date.now.bind(Date); + let offset = 0; + t.mock.method(Date, 'now', () => realNow() + offset); + let wakes = 0; + const r = run({ + wakeMinIntervalMs: 200, + onWake: async () => { + wakes += 1; + return { ok: true, json: 'null' }; + }, + // Made between the two wakes, after the first's delivery set when the next + // may go and before the second asks: the wall clock goes back an hour, so a + // turn timed by it would hold the second wake an hour. + onToolCall: async (name, args) => { + offset = -3_600_000; + return result(`${name} ${args}`); + }, + }); + r.start(`await wake_agent(1); await tools.echo({}); await wake_agent(2); return 'done';`, TOOLS); + // 200 ms of spacing against an hour: the run's own wait tells them apart. + const snap = await r.wait(60_000); + assert.deepEqual(snap.ending, { how: 'finished', value: '"done"' }); + assert.equal(wakes, 2); + await r.exited; +}); + +test("a child's finished value far past what a return shows is bounded by the parent", async () => { + const r = run({ childEntry: BAD_CHILD, childArgs: ['long-value'] }); + r.start('return 1;', TOOLS); + const snap = await r.wait(10_000); + assert.equal(snap.ending?.how, 'finished'); + const value = (snap.ending as { value: string }).value; + assert.ok(value.length < VALUE_CHARS + 2 * CUT_MARK_CHARS, `the value is ${value.length} characters`); + assert.match(value, /characters dropped here/); + await r.exited; +}); + +test("an honest child's cut failure passes the parent as it is, with the child's own count", async () => { + const r = run(); + r.start(`throw new Error('m'.repeat(20000));`, TOOLS); + const snap = await r.wait(10_000); + assert.equal(snap.ending?.how, 'failed'); + // The child keeps 8,000 of the message's 20,000: its marker counts the 12,000 dropped. + assert.match((snap.ending as { error: { message: string } }).error.message, /\[… 12000 characters dropped here …\]/); + await r.exited; +}); + +test("a child's call with one string past the argument bytes, and few values, crashes the run", async () => { + let calls = 0; + const r = run({ childEntry: BAD_CHILD, childArgs: ['big-arg-string'], onToolCall: async () => { calls += 1; return result('x'); } }); + r.start('return 1;', TOOLS); + const snap = await r.wait(10_000); + assert.equal(snap.ending?.how, 'crashed'); + assert.match((snap.ending as { detail: string }).detail, /whose arguments pass their caps of 4 MiB and 100,000 values/); + assert.equal(calls, 0); + await r.exited; +}); + +test("a child's frame longer than the longest a child sends, 25 MiB, crashes the run before its body is kept", async () => { + assert.equal(MAX_CHILD_FRAME_BYTES, 25 * 1024 * 1024, 'six bytes for each of 4 MiB of arguments, and 1 MiB more'); + const r = run({ childEntry: BAD_CHILD, childArgs: ['oversized-frame', String(MAX_CHILD_FRAME_BYTES)] }); + r.start('return 1;', TOOLS); + const snap = await r.wait(10_000); + assert.equal(snap.ending?.how, 'crashed'); + assert.match((snap.ending as { detail: string }).detail, new RegExp(`announced ${MAX_CHILD_FRAME_BYTES + 1} bytes, more than the ${MAX_CHILD_FRAME_BYTES} the protocol allows`)); + await r.exited; +}); + +test("a sent call's timeout is said exactly, a fraction of a second included", async () => { + const r = run({ toolCallTimeoutMs: 1_500, onToolCall: () => new Promise(() => {}) }); + r.start(`try { await tools.slow({}); return 'answered'; } catch (e) { return e.message; }`, TOOLS); + const snap = await r.wait(20_000); + assert.deepEqual(snap.ending, { how: 'finished', value: JSON.stringify('slow was sent, and no answer came within 1.5 s; it may still take effect') }); + await r.exited; +}); + +test('a frame of exactly the longest a child sends is read', async () => { + const r = run({ childEntry: BAD_CHILD, childArgs: ['frame-at-cap', String(MAX_CHILD_FRAME_BYTES)] }); + r.start('return 1;', TOOLS); + const snap = await r.wait(20_000); + // Read and taken as output, cut to what a return shows; then the child says it finished. + assert.equal(snap.ending?.how, 'finished', JSON.stringify(snap.ending)); + await r.exited; +}); diff --git a/test/code-execution-isolate-scripts.test.ts b/test/code-execution-isolate-scripts.test.ts new file mode 100644 index 000000000..d6001abf8 --- /dev/null +++ b/test/code-execution-isolate-scripts.test.ts @@ -0,0 +1,690 @@ +/** + * The isolate's scripts for one agent, with real children on the stand-in engine: + * the yield and later waits, each return's own output, stopping, what wakes + * an agent and what doesn't, the limits on running scripts and children, and + * wake_agent()'s spacing and cap. + */ + +import { after, test } from 'node:test'; +import { strict as assert } from 'node:assert'; +import { extname } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { ChildCapacity, type CallAnswer } from '../src/code-execution/isolate/runner.js'; +import { AgentScripts, ScriptRefusal, type ScriptCallContext, type ScriptsConfig, type ScriptsHost } from '../src/code-execution/isolate/scripts.js'; + +// With this file's own extension, as the runner names its entry, so the runtime runs a file that exists. +const FAKE_CHILD = fileURLToPath(new URL(`./helpers/fake-child-entry${extname(fileURLToPath(import.meta.url))}`, import.meta.url)); +const LINGERING_CHILD = fileURLToPath(new URL(`./helpers/bad-child-finished-then-hold${extname(fileURLToPath(import.meta.url))}`, import.meta.url)); + +const result = (text: string): CallAnswer & { isError: boolean } => ({ + ok: true, + json: JSON.stringify({ content: [{ type: 'text', text }], structuredContent: null, isError: false }), + isError: false, +}); + +/** Every setup's scripts, stopped once the file's tests are done, so no child outlives them. */ +const made: AgentScripts[] = []; +after(async () => { + for (const scripts of made) await scripts.dispose(); +}); + +function setup(over: { host?: Partial; config?: Partial; capacity?: number } = {}) { + const wakes: [string, string][] = []; + const gates = new Map void>(); + const host: ScriptsHost = { + tools: () => [{ name: 'echo', description: 'Echoes.' }, { name: 'slow', description: 'Answers when released.' }], + dispatch: async (_agent, name, args) => { + if (name === 'slow') await new Promise((resolve) => gates.set(JSON.parse(args).key ?? 'slow', resolve)); + return result(`${name} ${args}`); + }, + wake: async (agent, _scriptId, text) => { + wakes.push([agent, text]); + }, + outlivesTurn: () => true, + ...over.host, + }; + const config: ScriptsConfig = { + childEntry: FAKE_CHILD, + memoryMaxBytes: 64 * 1024 * 1024, + output: { headChars: 1000, tailChars: 1000 }, + flushMs: 20, + yieldMs: 300, + maxYieldMs: 5_000, + lifetimeMs: 60_000, + toolCallTimeoutMs: 60_000, + stopGraceMs: 2_000, + maxRunning: 3, + keepEnded: 8, + wakeMinIntervalMs: 0, + maxWakesPerScript: 100, + ...over.config, + }; + const scripts = new AgentScripts(host, config, new ChildCapacity(over.capacity ?? 16)); + made.push(scripts); + /** Wait until the script's call `key` is waiting in the host. */ + const called = async (key = 'slow') => { + for (let i = 0; i < 1000 && !gates.has(key); i++) await new Promise((r) => setTimeout(r, 10)); + assert.ok(gates.has(key), `the call ${key} arrived`); + }; + const release = async (key = 'slow') => { + await called(key); + gates.get(key)!(); + gates.delete(key); + }; + return { scripts, wakes, release, called }; +} + +test('a script that ends within its bound returns whole', async () => { + // A bound no child's start reaches, however loaded the host. + const { scripts } = setup({ config: { yieldMs: 30_000 } }); + const r = await scripts.run('alice', `const a = await tools.echo({ x: 1 }); text(a.content[0].text); return 7;`); + assert.equal(r.running, false); + assert.deepEqual(r.ending, { how: 'finished', value: '7' }); + assert.equal(r.output, 'echo {"x":1}\n'); + assert.equal(r.stopsAtTurnEnd, false); +}); + +test('past its bound a script yields what it wrote, and each wait returns only what came since', async () => { + const { scripts, release } = setup(); + // Yield at once; its child may not have started yet. + const first = await scripts.run('alice', `text('one'); await tools.slow({}); text('two'); return 'end';`, 0); + assert.equal(first.running, true); + // Once its call waits, what it wrote first has been flushed. + const waiting = scripts.wait('alice', first.scriptId, 10_000); + await release(); + const second = await waiting; + assert.equal(second.running, false); + assert.equal(first.output + second.output, 'one\ntwo\n'); + assert.deepEqual(second.ending, { how: 'finished', value: '"end"' }); + // A return after the end has nothing new. + const third = await scripts.wait('alice', first.scriptId); + assert.equal(third.output, ''); +}); + +test("a non-primary agent's yield says its turn's end will stop it, and that stop wakes no one", async () => { + const { scripts, wakes } = setup({ host: { outlivesTurn: (agent) => agent === 'primary' } }); + const r = await scripts.run('helper', `await tools.slow({});`); + assert.equal(r.running, true); + assert.equal(r.stopsAtTurnEnd, true); + scripts.turnEnded('helper', { id: 1, live: [] }, false); + for (let i = 0; i < 200 && scripts.running('helper') > 0; i++) await new Promise((res) => setTimeout(res, 10)); + const [listed] = scripts.list('helper'); + assert.deepEqual(listed!.ending, { how: 'stopped', reason: 'limit', why: "its agent's turn ended, and only the primary agent's scripts outlive a turn" }); + assert.deepEqual(wakes, []); +}); + +test('a left script that fails wakes its agent; one that finishes says nothing', async () => { + const { scripts, wakes, release } = setup(); + const failing = await scripts.run('alice', `await tools.slow({ key: 'a' }); throw new Error('broke');`); + const finishing = await scripts.run('alice', `await tools.slow({ key: 'b' }); return 1;`); + assert.equal(failing.running && finishing.running, true); + await release('a'); + await release('b'); + for (let i = 0; i < 300 && scripts.running('alice') > 0; i++) await new Promise((res) => setTimeout(res, 10)); + assert.equal(wakes.length, 1); + assert.match(wakes[0]![1], new RegExp(`\\[script ${failing.scriptId}\\] Your script failed: Error: broke`)); + const later = await scripts.wait('alice', finishing.scriptId); + assert.deepEqual(later.ending, { how: 'finished', value: '1' }); +}); + +test("the agent's stop ends its script and leaves the call it waited on uncertain", async () => { + const { scripts, called } = setup(); + const r = await scripts.run('alice', `await tools.slow({});`); + await called(); + const stopped = await scripts.stop('alice', r.scriptId); + assert.deepEqual(stopped.ending, { how: 'stopped', reason: 'agent' }); + assert.deepEqual(stopped.calls.named.map((c) => c.state), ['uncertain']); +}); + +test('an agent at its most running scripts, or a host at its most children, is refused, with nothing run', async () => { + const { scripts } = setup({ config: { maxRunning: 1 } }); + await scripts.run('alice', `await tools.slow({});`); + await assert.rejects(scripts.run('alice', `return 1;`), (e: unknown) => e instanceof ScriptRefusal && /your most\..*Nothing was run\./s.test(e.message)); + const full = setup({ capacity: 1 }); + await full.scripts.run('bob', `await tools.slow({});`); + await assert.rejects(full.scripts.run('carol', `return 1;`), (e: unknown) => e instanceof ScriptRefusal && /its most\..*Nothing was run\./s.test(e.message)); +}); + +test("another agent's script, or one that isn't there, is refused by name", async () => { + const { scripts } = setup(); + const r = await scripts.run('alice', `return 1;`); + await assert.rejects(scripts.wait('bob', r.scriptId), (e: unknown) => e instanceof ScriptRefusal && /no script "s\d+-[0-9a-z]{4}"/.test(e.message)); + // An id carries its host's tag, so one from before a restart names no script here. + assert.match(r.scriptId, /^s\d+-[0-9a-z]{4}$/); + await assert.rejects(scripts.wait('alice', 's1'), /no script "s1": it isn't one of yours, it ran before this host restarted, or it ended and was dropped/); +}); + +test("another host's id names no script here, as one from before a restart doesn't", async () => { + const first = setup(); + const theirs = await first.scripts.run('alice', `return 1;`); + // Each host tags its ids, so this host's first script isn't the other's, + // though both count from one. Two hosts' random tags are alike about one + // time in 1.7 million, so it tries up to three hosts. + let here = setup(); + let mine = await here.scripts.run('alice', `return 1;`); + for (let tries = 0; tries < 2 && mine.scriptId === theirs.scriptId; tries++) { + here = setup(); + mine = await here.scripts.run('alice', `return 1;`); + } + assert.notEqual(mine.scriptId, theirs.scriptId); + await assert.rejects(here.scripts.wait('alice', theirs.scriptId), (e: unknown) => e instanceof ScriptRefusal && /no script "s1-[0-9a-z]{4}"/.test(e.message)); +}); + +test('wake_agent() wakes its agent with the payload, and past the cap it is refused', async () => { + const { scripts, wakes } = setup({ config: { maxWakesPerScript: 2, yieldMs: 30_000 } }); + const r = await scripts.run('alice', `await wake_agent({ n: 1 }); await wake_agent({ n: 2 }); try { await wake_agent({ n: 3 }); } catch (e) { return e.message; }`); + assert.equal(r.running, false); + assert.match(r.ending?.how === 'finished' ? r.ending.value ?? '' : '', /wake limit reached: 2 per script/); + assert.equal(wakes.length, 2); + assert.match(wakes[0]![1], /\(wake 1 of 2\): \{"n":1\}$/); +}); + +test('a listing counts the wakes the host delivered, not one whose delivery failed', async () => { + const { scripts } = setup({ host: { wake: async (_agent, _scriptId, text) => { if (text.includes('"n":2')) throw new Error('no route'); } } }); + // A yield past the script's end, which comes first: the listing is read once it has ended. + const r = await scripts.run('alice', `await wake_agent({ n: 1 }); try { await wake_agent({ n: 2 }); } catch {} await wake_agent({ n: 3 }); return 1;`, 5_000); + assert.equal(r.running, false, JSON.stringify(r.ending)); + const listed = scripts.list('alice'); + assert.equal(listed.find((s) => s.scriptId === r.scriptId)?.wakes, 2); + const { renderList } = await import('../src/code-execution/isolate/render.js'); + assert.match(renderList(listed, Date.now()), new RegExp(`- ${r.scriptId}: .*\\(2 wakes delivered\\)`)); + await scripts.dispose(); +}); + +test('an early wake waits for its spacing rather than being dropped', async () => { + const at: number[] = []; + const { scripts, wakes } = setup({ + config: { wakeMinIntervalMs: 250, yieldMs: 3_000 }, + host: { wake: async (agent, _id, text) => { at.push(Date.now()); wakes.push([agent, text]); } }, + }); + await scripts.run('alice', `await wake_agent('a'); await wake_agent('b');`); + assert.equal(wakes.length, 2); + // Measured between the deliveries, not from the run's start, so a slow child start can't stand in for the spacing. + assert.ok(at[1]! - at[0]! >= 240, `the second wake came ${at[1]! - at[0]!} ms after the first`); +}); + +test('wakes go out in the order they were made, and a failed delivery refuses its wake and not the next', async () => { + let n = 0; + const { scripts, wakes } = setup({ + config: { wakeMinIntervalMs: 50, yieldMs: 10_000 }, + host: { + wake: async (agent, _id, text) => { + n += 1; + if (n === 2) throw new Error('no context'); + wakes.push([agent, text]); + }, + }, + }); + const r = await scripts.run('alice', `const ps = [1, 2, 3].map((n) => wake_agent({ n })); const s = await Promise.allSettled(ps); return s.map((x) => x.status === 'fulfilled' ? 'ok' : x.reason.name);`); + assert.deepEqual(r.ending, { how: 'finished', value: '["ok","WakeRefused","ok"]' }); + assert.deepEqual(wakes.map(([, text]) => /\{"n":(\d)\}$/.exec(text)?.[1]), ['1', '3']); +}); + +test("an abandoned turn stops a script that hadn't returned, quietly, and its pending return takes no output", async () => { + const { scripts, wakes, called } = setup({ config: { yieldMs: 10_000 } }); + const pending = scripts.run('alice', `text('hi'); await tools.slow({});`, undefined, { watch: { turnId: 1, endTurn: false } }); + // Once its call waits, what it wrote first has been flushed. + await called(); + scripts.turnEnded('alice', { id: 1, live: [] }, true); + const r = await pending; + assert.deepEqual(r.ending, { how: 'stopped', reason: 'limit', why: 'its turn ended before the script returned' }); + assert.deepEqual(wakes, []); + // The return no one will read left the output for the next one. + assert.equal((await scripts.wait('alice', r.scriptId)).output, 'hi\n'); +}); + +test('only the newest ended scripts are kept, newest by when each ended, from each ending on', async () => { + // Each run returns at its end: a bound no child's start reaches, however loaded the host. + const { scripts, release } = setup({ config: { keepEnded: 2, yieldMs: 30_000 } }); + // The first started ends last, so it's among the newest ended. + const first = await scripts.run('alice', `await tools.slow({}); return 'first';`, 0); + const ids: string[] = []; + for (let i = 0; i < 3; i++) ids.push((await scripts.run('alice', `return ${i};`)).scriptId); + // The bound holds as each ends, before any later start. + assert.deepEqual(scripts.list('alice').map((s) => s.scriptId), [first.scriptId, ids[1], ids[2]]); + await release(); + for (let i = 0; i < 300 && scripts.running('alice') > 0; i++) await new Promise((r) => setTimeout(r, 10)); + assert.deepEqual(scripts.list('alice').map((s) => s.scriptId).sort(), [first.scriptId, ids[2]].sort()); + await assert.rejects(scripts.wait('alice', ids[0]!), /You have no script/); +}); + +test('an abandoned wait leaves its yielded script running, and a later failure still wakes the agent', async () => { + const { scripts, wakes, release } = setup({ config: { yieldMs: 200 } }); + const first = await scripts.run('alice', `await tools.slow({}); throw new Error('later');`); + assert.equal(first.running, true); + const waiting = scripts.wait('alice', first.scriptId, 10_000, { turnId: 1, endTurn: false }); + await new Promise((r) => setTimeout(r, 50)); + scripts.turnEnded('alice', { id: 1, live: [] }, true); + assert.equal(scripts.running('alice'), 1, 'still running'); + await release(); + for (let i = 0; i < 300 && scripts.running('alice') > 0; i++) await new Promise((r) => setTimeout(r, 10)); + assert.equal(wakes.length, 1); + assert.match(wakes[0]![1], /Your script failed: Error: later/); + await waiting; +}); + +test("each call carries the script's origin, and the watching call's turn only while one watches", async () => { + const contexts: unknown[] = []; + const gates: Array<() => void> = []; + const { scripts } = setup({ + // A bound no child's start reaches, however loaded the host: both calls come while the run watches. + config: { yieldMs: 30_000 }, + host: { + dispatch: async (_agent, name, args, context) => { + contexts.push({ name, ...context }); + if (name === 'slow') await new Promise((resolve) => gates.push(resolve)); + return result(`${name} ${args}`); + }, + }, + }); + const watch = { turnId: 7, endTurn: false }; + const running = scripts.run('alice', `await tools.echo({}); await tools.slow({});`, undefined, { origin: 'origin-1', watch }); + for (let i = 0; i < 2_000 && gates.length === 0; i++) await new Promise((r) => setTimeout(r, 10)); + assert.deepEqual( + contexts.map((c) => { const { watching: _, ...rest } = c as { watching?: unknown }; return rest; }), + [ + { name: 'echo', origin: 'origin-1', watch }, + { name: 'slow', origin: 'origin-1', watch }, + ], + ); + assert.equal((contexts[1] as { watching(): unknown }).watching(), watch); + // Once the run has returned, no call watches: the call's turn is the one it was made under. + gates[0]!(); + const r = await running; + assert.deepEqual(r.ending, { how: 'finished' }); + assert.equal((contexts[1] as { watching(): unknown }).watching(), undefined); + await scripts.dispose(); +}); + +test('an agent gone, or the host stopping, stops its scripts quietly, and a gone agent keeps none', async () => { + const { scripts, wakes } = setup({ config: { yieldMs: 100 } }); + const ended = await scripts.run('eph', `return 1;`, 30_000); + assert.equal(ended.running, false); + const left = await scripts.run('eph', `await tools.slow({});`); + await scripts.run('alice', `await tools.slow({ key: 'x' });`); + scripts.agentGone('eph'); + // Its ended script leaves at once, its running one as it ends: no later run of + // the agent would trim them. Neither is a later agent's of its name. + const internals = scripts as unknown as { scripts: Map }; + assert.deepEqual(scripts.list('eph'), []); + assert.equal(scripts.running('eph'), 0); + assert.equal([...internals.scripts.values()].filter((s) => s.agentName === 'eph').length, 1, 'the stopping one is still held'); + // While it's held, a later agent of the name can't wait on it or stop it. + await assert.rejects(scripts.wait('eph', left.scriptId, 0), (e: unknown) => e instanceof ScriptRefusal); + await assert.rejects(scripts.stop('eph', left.scriptId), (e: unknown) => e instanceof ScriptRefusal); + for (let i = 0; i < 200 && [...internals.scripts.values()].some((s) => s.agentName === 'eph'); i++) await new Promise((r) => setTimeout(r, 10)); + assert.equal([...internals.scripts.values()].filter((s) => s.agentName === 'eph').length, 0); + await scripts.dispose(); + assert.equal(scripts.running('alice'), 0); + assert.deepEqual(wakes, []); +}); + +test('a second call on a script another call is waiting on is refused, with nothing changed', async () => { + const { scripts, release } = setup({ config: { yieldMs: 100 } }); + const r = await scripts.run('alice', `await tools.slow({}); return 2;`); + const waiting = scripts.wait('alice', r.scriptId, 10_000); + await assert.rejects(scripts.wait('alice', r.scriptId, 0), (e: unknown) => e instanceof ScriptRefusal && /Another of your calls is waiting on .* Nothing was changed\./.test(e.message)); + await assert.rejects(scripts.stop('alice', r.scriptId), /Another of your calls is waiting on/); + await release(); + assert.deepEqual((await waiting).ending, { how: 'finished', value: '2' }); +}); + +test('a left script that crashes or reaches its limit wakes its agent', async () => { + const { scripts, wakes } = setup({ config: { yieldMs: 100, lifetimeMs: 1_500 } }); + // Each yields at once, before its child has started, so it's left running when it ends. + const crash = await scripts.run('alice', `await tools.echo({}); __crash();`, 0); + const limit = await scripts.run('alice', `await tools.slow({ key: 'l' });`, 0); + for (let i = 0; i < 400 && scripts.running('alice') > 0; i++) await new Promise((r) => setTimeout(r, 10)); + const texts = wakes.map(([, text]) => text); + assert.ok(texts.some((t) => t.startsWith(`[script ${crash.scriptId}] Your script crashed`)), texts.join('\n')); + assert.ok(texts.some((t) => t.startsWith(`[script ${limit.scriptId}] Your script was stopped at its limit`)), texts.join('\n')); + await scripts.dispose(); +}); + +test("a turn's end stop and an agent's end say why, not that a limit was reached", async () => { + const { scripts } = setup({ host: { outlivesTurn: () => false }, config: { yieldMs: 100 } }); + const r = await scripts.run('helper', `await tools.slow({});`); + scripts.turnEnded('helper', { id: 1, live: [] }, false); + for (let i = 0; i < 200 && scripts.running('helper') > 0; i++) await new Promise((res) => setTimeout(res, 10)); + const { renderList } = await import('../src/code-execution/isolate/render.js'); + const listed = renderList(scripts.list('helper'), Date.now()); + assert.match(listed, new RegExp(`${r.scriptId}: stopped under a minute ago: its agent's turn ended`)); + assert.doesNotMatch(listed, /at its limit/); +}); + +test("a run or wait waits no longer than maxYieldMs, whatever it asks", () => { + const { scripts } = setup({ config: { maxYieldMs: 5_000, yieldMs: 300 } }); + assert.equal(scripts.bound(60_000), 5_000); + assert.equal(scripts.bound(undefined), 300); + assert.equal(scripts.bound(100), 100); +}); + +test("a script's calls carry the turn of the call watching when each reaches the host, and a turn-ending result goes to the call watching when it comes", async () => { + const contexts: { name: string; watch?: { turnId?: number; endTurn: boolean }; watching?: () => { turnId?: number; endTurn: boolean } | undefined }[] = []; + let answer!: () => void; + const held = new Promise((resolve) => (answer = resolve)); + const { scripts } = setup({ + config: { yieldMs: 100 }, + host: { + dispatch: async (_agent, name, args, context) => { + contexts.push({ name, ...context }); + if (name === 'slow') await held; + if (name === 'slow') { + // The result ends the turn: it goes to whichever call watches the script now. + const watching = context.watching?.(); + if (watching) watching.endTurn = true; + } + return result(`${name} ${args}`); + }, + }, + }); + // The run yields at once, before its child has started, so every call comes under a wait. + const r = await scripts.run('alice', `const p = tools.slow({}); await tools.echo({ a: 1 }); await p; await tools.echo({ b: 2 }); return 3;`, 0); + assert.equal(r.running, true); + const first = { turnId: 1, endTurn: false }; + const firstWait = scripts.wait('alice', r.scriptId, 30_000, first); + for (let i = 0; i < 2_000 && contexts.length < 2; i++) await new Promise((res) => setTimeout(res, 10)); + // Its turn is abandoned, and the script is left running with its slow call held. + scripts.turnEnded('alice', { id: 1, live: [] }, true); + // A later wait, in a later turn: the call made under it carries its turn, and the held result ends it. + const second = { turnId: 2, endTurn: false }; + const waiting = scripts.wait('alice', r.scriptId, 30_000, second); + answer(); + const done = await waiting; + assert.deepEqual(done.ending, { how: 'finished', value: '3' }); + assert.equal(second.endTurn, true, 'the wait watching when the result came ends its turn'); + assert.equal(first.endTurn, false); + assert.deepEqual(contexts.filter((c) => c.name === 'echo').map((c) => c.watch?.turnId), [1, 2]); + await firstWait; +}); + +test("the host's places come back as children exit, a launch that fails included", async () => { + const capacity = new ChildCapacity(2); + const { scripts } = setup({ config: { yieldMs: 30_000 } }); + const scriptsWith = new AgentScripts( + { tools: () => [], dispatch: async () => result(''), wake: async () => {}, outlivesTurn: () => true }, + { ...(scripts as unknown as { config: ScriptsConfig }).config }, + capacity, + ); + made.push(scriptsWith); + await scriptsWith.run('alice', 'return 1;'); + await scriptsWith.run('alice', 'return 2;'); + for (let i = 0; i < 300 && capacity.running > 0; i++) await new Promise((r) => setTimeout(r, 10)); + assert.equal(capacity.running, 0, 'both places back once the children exited'); + const failing = new AgentScripts( + { tools: () => [], dispatch: async () => result(''), wake: async () => {}, outlivesTurn: () => true }, + { ...(scripts as unknown as { config: ScriptsConfig }).config, execPath: '/nonexistent/runtime-for-a-test' }, + capacity, + ); + made.push(failing); + const r = await failing.run('alice', 'return 1;'); + assert.equal(r.ending?.how, 'crashed'); + for (let i = 0; i < 300 && capacity.running > 0; i++) await new Promise((r2) => setTimeout(r2, 10)); + assert.equal(capacity.running, 0, 'a launch that failed gives its place back'); +}); + +test('a script that ends at its start, before it is in the map, still leaves the ended scripts at their bound', async () => { + // A runtime path Node's spawn refuses at once: the run crashes inside start(). + const { scripts } = setup({ config: { keepEnded: 1, execPath: 'node\0not-a-path' } }); + const first = await scripts.run('alice', 'return 1;'); + const second = await scripts.run('alice', 'return 2;'); + assert.deepEqual([first.ending?.how, second.ending?.how], ['crashed', 'crashed']); + assert.deepEqual(scripts.list('alice').map((s) => s.scriptId), [second.scriptId]); +}); + +test('a tool list that throws refuses the run with nothing taken, and the host still stops', async () => { + let fail = true; + const { scripts } = setup({ + capacity: 1, + // Long enough that run() returns the ending, however slowly the child starts. + config: { yieldMs: 30_000 }, + host: { tools: () => { if (fail) throw new Error('a module listed nothing'); return [{ name: 'echo', description: 'Echoes.' }]; } }, + }); + await assert.rejects(scripts.run('alice', `return 1;`), (e: unknown) => e instanceof ScriptRefusal && /The script's tools couldn't be listed: a module listed nothing\. Nothing was run\./.test(e.message)); + // The host's one place is still free. + fail = false; + const r = await scripts.run('alice', `return 2;`); + assert.deepEqual(r.ending, { how: 'finished', value: '2' }); + await scripts.dispose(); +}); + +/** A host whose delivery of the script's 'working' wake waits until `deliver` is called. */ +function heldWake() { + const wakes: string[] = []; + const state = { entered: false }; + let deliver!: () => void; + const delivered = new Promise((resolve) => (deliver = resolve)); + const wake = async (_agent: string, _id: string, text: string) => { + if (text.includes('working')) { + state.entered = true; + await delivered; + } + wakes.push(text); + }; + return { wakes, state, deliver: () => deliver(), wake }; +} + +// It wakes its agent and spins. A wake's call is answered once the wake is +// delivered, and a script that returns waits for its calls, so only an ending +// that doesn't wait, such as its limit, can come while the wake is delivered. +const WAKE_THEN_SPIN = `wake_agent('working'); for (;;) __tick();`; +type Internals = { scripts: Map } }> }; + +test("a left script's ending reaches its agent after a wake it sent that was still being delivered", async () => { + const held = heldWake(); + const { scripts } = setup({ config: { yieldMs: 0 }, host: { wake: held.wake } }); + const r = await scripts.run('alice', WAKE_THEN_SPIN); + for (let i = 0; i < 500 && !held.state.entered; i++) await new Promise((res) => setTimeout(res, 10)); + assert.equal(held.state.entered, true, 'the wake is being delivered'); + // Its limit stops it while the wake is still being delivered. + await (scripts as unknown as Internals).scripts.get(r.scriptId)!.run.stop('limit'); + await new Promise((res) => setTimeout(res, 100)); + assert.deepEqual(held.wakes, [], 'the ending waits for the wake'); + held.deliver(); + for (let i = 0; i < 500 && held.wakes.length < 2; i++) await new Promise((res) => setTimeout(res, 10)); + assert.match(held.wakes[0]!, /working/); + assert.match(held.wakes[1]!, /was stopped at its limit/); +}); + +test("a left script's ending that a wait has returned isn't sent again as a notice", async () => { + const held = heldWake(); + const { scripts } = setup({ config: { yieldMs: 0 }, host: { wake: held.wake } }); + const r = await scripts.run('alice', WAKE_THEN_SPIN); + for (let i = 0; i < 500 && !held.state.entered; i++) await new Promise((res) => setTimeout(res, 10)); + await (scripts as unknown as Internals).scripts.get(r.scriptId)!.run.stop('limit'); + // The agent waits on it while its notice waits for the wake's delivery, and gets the ending there. + const waited = await scripts.wait('alice', r.scriptId, 1_000); + assert.equal(waited.ending?.how, 'stopped'); + held.deliver(); + await new Promise((res) => setTimeout(res, 200)); + assert.equal(held.wakes.length, 1); + assert.match(held.wakes[0]!, /working/); +}); + +test('the ended scripts kept are those whose endings came last, though the clock says otherwise', async (t) => { + // A clock that doesn't move: endings in the same millisecond, or a clock stepped back. + t.mock.method(Date, 'now', () => 1_000_000); + const { scripts, release } = setup({ config: { keepEnded: 1, yieldMs: 30_000 } }); + // The first started ends last. + const first = await scripts.run('alice', `await tools.slow({}); return 'first';`, 0); + const second = await scripts.run('alice', `return 'second';`); + assert.deepEqual(scripts.list('alice').map((s) => s.scriptId), [first.scriptId, second.scriptId]); + await release(); + for (let i = 0; i < 300 && scripts.running('alice') > 0; i++) await new Promise((r) => setTimeout(r, 10)); + assert.deepEqual(scripts.list('alice').map((s) => s.scriptId), [first.scriptId]); +}); + +test("dispose ends a child that said how its script ended and stayed", async () => { + const { scripts } = setup({ config: { childEntry: LINGERING_CHILD, stopGraceMs: 60_000, yieldMs: 30_000 } }); + const r = await scripts.run('alice', `return 1;`); + assert.deepEqual(r.ending, { how: 'finished', value: '1' }); + // Its child hasn't exited, and nothing has ended it: dispose must. + const disposed = await Promise.race([scripts.dispose().then(() => true), new Promise((resolve) => setTimeout(() => resolve(false), 10_000))]); + assert.equal(disposed, true); +}); + +test("stop watches its script with the caller's watch, so a call's turn-ending result reaches the stop's result", async () => { + const { scripts, called } = setup(); + const r = await scripts.run('alice', `await tools.slow({});`, 0); + await called(); + const internals = scripts as unknown as { scripts: Map }; + const watch = { endTurn: false }; + const stopping = scripts.stop('alice', r.scriptId, watch); + assert.equal(internals.scripts.get(r.scriptId)!.watch, watch); + await stopping; +}); + +test("a left script's failure wakes its agent with a glimpse of a long message, and no count a second cut would misstate", async () => { + const { scripts, wakes, release } = setup(); + // It yields on the call first, so it's left running when it fails. + const r = await scripts.run('alice', `await tools.slow({ key: 'g' }); throw new Error('w'.repeat(20000));`); + assert.equal(r.running, true); + await release('g'); + for (let i = 0; i < 300 && wakes.length === 0; i++) await new Promise((res) => setTimeout(res, 10)); + assert.equal(wakes.length, 1, `${r.scriptId} woke its agent`); + assert.match(wakes[0]![1], /Your script failed: Error: w{300}…/); + assert.doesNotMatch(wakes[0]![1], /dropped here/); +}); + +test("turns that overlap: a turn's end stops a non-primary agent's scripts whose turn isn't live, and those started in none", async () => { + const { scripts } = setup({ host: { outlivesTurn: () => false }, config: { yieldMs: 0, maxRunning: 6 } }); + const start = async (turnId: number | undefined) => + (await scripts.run('helper', `await tools.slow({});`, 0, { watch: { ...(turnId === undefined ? {} : { turnId }), endTurn: false } })).scriptId; + const running = (id: string) => scripts.list('helper').find((x) => x.scriptId === id)!.running; + // Those a turn's end stops have ended once their stops land; the rest were never stopped. + const ended = async (...ids: string[]) => { + for (let i = 0; i < 1_000 && ids.some(running); i++) await new Promise((res) => setTimeout(res, 10)); + }; + const older = await start(3); + const open = await start(5); + const silent = await start(7); + const newer = await start(9); + // The silent turn 7 ends while turn 5 is open and turn 9's start is in progress: 3's and 7's scripts end. + scripts.turnEnded('helper', { id: 7, live: [5, 9] }, false); + await ended(older, silent); + assert.deepEqual([older, open, silent, newer].map(running), [false, true, false, true]); + // A stale settle of turn 4 leaves the live turns' scripts alone. + scripts.turnEnded('helper', { id: 4, live: [5, 9] }, false); + assert.deepEqual([open, newer].map(running), [true, true]); + // Turn 5 closes: its script ends; 9's lives on while its turn does. + scripts.turnEnded('helper', { id: 5, live: [9] }, false); + await ended(open); + assert.deepEqual([open, newer].map(running), [false, true]); + // A script started in no turn ends at any turn's end, and every turn's end takes the rest. + const none = await start(undefined); + scripts.turnEnded('helper', { id: 1, live: [9] }, false); + await ended(none); + assert.deepEqual([none, newer].map(running), [false, true]); + scripts.turnEnded('helper', 'every', false); + await ended(newer); + assert.equal(running(newer), false); + // A newer turn's script isn't kept for being newer: an older turn's end with + // no turn live stops it. + const later = await start(12); + scripts.turnEnded('helper', { id: 4, live: [] }, false); + await ended(later); + assert.equal(running(later), false); +}); + +test("an abandoned turn stops a non-primary agent's script its calls watch that never returned, though the turn is live, and lets go of one that did", async () => { + const { scripts, release } = setup({ host: { outlivesTurn: () => false }, config: { maxRunning: 6 } }); + const internals = scripts as unknown as { scripts: Map }; + // Its run call is still waiting on it: the script hasn't returned. + const pending = scripts.run('helper', `await tools.slow({ key: 'u' });`, 30_000, { watch: { turnId: 5, endTurn: false } }); + // This one returned at once, and a wait of the same turn watches it now. + const returned = await scripts.run('helper', `await tools.slow({ key: 'r' });`, 0, { watch: { turnId: 5, endTurn: false } }); + const waiting = scripts.wait('helper', returned.scriptId, 30_000, { turnId: 5, endTurn: false }); + await new Promise((res) => setTimeout(res, 50)); + // Turn 5's stream is abandoned and will run again: the turn stays live. + scripts.turnEnded('helper', { id: 5, live: [5] }, true, 'stream error'); + assert.deepEqual((await pending).ending, { how: 'stopped', reason: 'limit', why: 'its turn ended before the script returned (stream error)' }); + assert.equal(internals.scripts.get(returned.scriptId)!.watch, null, 'the returned one is let go'); + assert.equal(scripts.list('helper').find((x) => x.scriptId === returned.scriptId)!.running, true, 'and runs on while its turn is live'); + await release('r'); + await waiting; +}); + +test("every turn of a primary agent's abandoned at once: a watched script that never returned stops, and one that did is let go", async () => { + const { scripts, release } = setup({ config: { maxRunning: 6 } }); + const internals = scripts as unknown as { scripts: Map }; + const pending = scripts.run('alice', `await tools.slow({ key: 'u' });`, 30_000, { watch: { turnId: 2, endTurn: false } }); + const returned = await scripts.run('alice', `await tools.slow({ key: 'r' });`, 0, { watch: { turnId: 3, endTurn: false } }); + const waiting = scripts.wait('alice', returned.scriptId, 30_000, { turnId: 3, endTurn: false }); + await new Promise((res) => setTimeout(res, 50)); + scripts.turnEnded('alice', 'every', true, 'turn abandoned by quiesce'); + assert.equal((await pending).ending?.how, 'stopped'); + assert.equal(internals.scripts.get(returned.scriptId)!.watch, null); + assert.equal(scripts.list('alice').find((x) => x.scriptId === returned.scriptId)!.running, true, "a primary agent's script outlives its turns"); + await release('r'); + await waiting; +}); + +test("an abandoned turn acts only on the scripts its own calls watch", async () => { + const { scripts, release } = setup({ config: { yieldMs: 0 } }); + const watched = async (turnId: number) => + (await scripts.run('alice', `await tools.slow({ key: 'k${turnId}' });`, 0, { watch: { turnId, endTurn: false } })).scriptId; + const byOpen = await watched(5); + const bySilent = await watched(7); + const running = (id: string) => scripts.list('alice').find((x) => x.scriptId === id)!.running; + // The waits that watch them: each script's run returned once, so an abandoned watch is let go, not stopped. + const waits = [scripts.wait('alice', byOpen, 30_000, { turnId: 5, endTurn: false }), scripts.wait('alice', bySilent, 30_000, { turnId: 7, endTurn: false })]; + await new Promise((res) => setTimeout(res, 50)); + scripts.turnEnded('alice', { id: 7, live: [5] }, true); + const internals = scripts as unknown as { scripts: Map }; + assert.equal(internals.scripts.get(bySilent)!.watch, null, "turn 7's watch is let go"); + assert.equal(internals.scripts.get(byOpen)!.watch?.turnId, 5, "turn 5's watch stands"); + assert.deepEqual([byOpen, bySilent].map(running), [true, true]); + await release('k5'); + await release('k7'); + await Promise.all(waits); +}); + +test("a wait whose turn was abandoned takes the ending to no one, so the ending's notice still goes out", async () => { + const held = heldWake(); + const { scripts } = setup({ config: { yieldMs: 0 }, host: { wake: held.wake } }); + const r = await scripts.run('alice', WAKE_THEN_SPIN, 0); + for (let i = 0; i < 500 && !held.state.entered; i++) await new Promise((res) => setTimeout(res, 10)); + const waiting = scripts.wait('alice', r.scriptId, 30_000, { turnId: 1, endTurn: false }); + await new Promise((res) => setTimeout(res, 50)); + // The wait's turn is abandoned: the script had returned once, so its watch is let go. + scripts.turnEnded('alice', { id: 1, live: [] }, true); + await (scripts as unknown as Internals).scripts.get(r.scriptId)!.run.stop('limit'); + assert.equal((await waiting).ending?.how, 'stopped'); + held.deliver(); + for (let i = 0; i < 500 && held.wakes.length < 2; i++) await new Promise((res) => setTimeout(res, 10)); + assert.match(held.wakes[1]!, /was stopped at its limit/); +}); + +test("a stop watches the script with its own call's watch: a call in flight sees it as the one watching", async () => { + let context: ScriptCallContext | undefined; + const { scripts } = setup({ + config: { yieldMs: 0 }, + host: { dispatch: async (_agent, _name, _args, ctx) => { context = ctx; return new Promise(() => {}); } }, + }); + const r = await scripts.run('alice', `await tools.slow({});`, 0); + for (let i = 0; i < 500 && !context; i++) await new Promise((res) => setTimeout(res, 10)); + const watch = { turnId: 9, endTurn: false }; + const stopping = scripts.stop('alice', r.scriptId, watch); + // A turn-ending result arriving now would mark the stop's watch, and so end the stop's turn. + assert.equal(context!.watching?.(), watch); + await stopping; +}); + +test("a wait started on an ended script takes its ending at once, so a notice whose turn comes before the wait returns doesn't repeat it", async () => { + const held = heldWake(); + const { scripts } = setup({ config: { yieldMs: 0 }, host: { wake: held.wake } }); + const r = await scripts.run('alice', WAKE_THEN_SPIN, 0); + for (let i = 0; i < 500 && !held.state.entered; i++) await new Promise((res) => setTimeout(res, 10)); + const internals = scripts as unknown as { scripts: Map }; delivering: Promise | null }> }; + // A reaction to the delivery, ordered before the notice's: it starts the + // wait, and the notice runs before the wait's own continuation does. + let waiting: Promise | undefined; + void internals.scripts.get(r.scriptId)!.delivering!.then(() => { waiting = scripts.wait('alice', r.scriptId, 1_000); }); + await internals.scripts.get(r.scriptId)!.run.stop('limit'); + held.deliver(); + for (let i = 0; i < 500 && !waiting; i++) await new Promise((res) => setTimeout(res, 10)); + assert.ok(waiting, 'the wait started, before the notice ran'); + await waiting; + await new Promise((res) => setTimeout(res, 100)); + assert.equal(held.wakes.length, 1, 'only the script\'s own wake, and no notice'); +}); diff --git a/test/helpers/bad-child-entry.ts b/test/helpers/bad-child-entry.ts new file mode 100644 index 000000000..57a8ff497 --- /dev/null +++ b/test/helpers/bad-child-entry.ts @@ -0,0 +1,263 @@ +/** + * A child that breaks the protocol, or tests the parent's reading of it, chosen + * by argv: garbage on fd 3, a frame that isn't a message, a result frame (which + * only a parent sends), an empty frame, or an exit with no ending; a script + * stopped with no stop asked, or a stop answered with a reason of the child's + * own; an ending written after the child's process has exited, by a process + * of its own that holds fd 3; more calls at once than a script may have out, a + * call to a name that isn't a tool, an id named again before its answer was + * seen, a seen for a call never answered, or more wakes than the cap; a frame + * cut off at the channel's end; an ending, and then a process that stays; a + * crash whose stderr comes late; a call sent after a stop was asked; an answer + * never seen before a stop; a call whose arguments, or a wake whose payload, + * passes its cap, which the prelude would have refused; a failure whose name, + * message and stack are far past what a return shows; and its environment on + * stderr. + */ +import { spawn } from 'node:child_process'; +import { existsSync, writeSync } from 'node:fs'; +import { encodeFrame, encodeResultFrame, FrameDecoder, MAX_FRAME_BYTES } from '../../src/code-execution/isolate/frames.js'; +import { MAX_ARGUMENT_VALUES, MAX_WAKE_PAYLOAD_BYTES } from '../../src/code-execution/isolate/protocol.js'; +import { MAX_BATCH_CHARS } from '../../src/code-execution/isolate/output.js'; + +const how = process.argv[2]; +const call = (id: number, name = 'echo', args = '{}') => writeSync(3, encodeFrame({ t: 'call', id, name, args })); +/** The parent's frames, each handed to `on`, for the modes that answer them. */ +function parentFrames(on: (frame: { t: string; id?: number }) => void): void { + const decoder = new FrameDecoder(MAX_FRAME_BYTES, true); + process.stdin.on('data', (chunk: Buffer) => { + for (const value of decoder.push(chunk)) on(value as { t: string; id?: number }); + }); +} +const SELF_TIMED = new Set([ + 'stopped-as-agent', 'finished-then-hold', 'call-after-stop', 'no-seen-then-stop', 'late-stderr', + 'seen-queued', 'half-frame-on-stop', 'reuse-after-answer', 'wakes-then-ignore-stop', 'exit-holding-fd3', 'wakes-then-exit-holding-fd3', + 'reuse-timed-out-id', 'wakes-after-stop', 'calls-then-exit-on-gate', 'late-call', 'wake-then-exit-on-gate', 'wakes-then-garbage-on-gate', +]); +/** A process of its own that holds this one's fd 3 for `ms` after this one has gone, writing nothing. */ +const holdFd3 = (ms: number) => spawn(process.execPath, ['-e', `setTimeout(() => {}, ${ms})`], { stdio: ['ignore', 'ignore', 'ignore', 3] }).unref(); +if (how === 'stopped-as-agent') { + const decoder = new FrameDecoder(MAX_FRAME_BYTES, true); + process.stdin.on('data', (chunk: Buffer) => { + for (const value of decoder.push(chunk)) { + if ((value as { t?: string }).t !== 'stop') continue; + writeSync(3, encodeFrame({ t: 'end', how: 'stopped', reason: 'agent' })); + process.exit(0); + } + }); +} else if (how === 'held-fd3') { + // fd 3 stays open for 10 s after this process has gone, held by a process of its own, which writes nothing. + const held = spawn(process.execPath, ['-e', 'setTimeout(() => {}, 10_000)'], { stdio: ['ignore', 'ignore', 'ignore', 3] }); + held.unref(); +} else if (how === 'late-end') { + // The ending comes 300 ms after this process has gone, through the fd 3 it shares. + const late = spawn(process.execPath, ['-e', `setTimeout(() => require('node:fs').writeSync(3, Buffer.from(process.env.FRAME, 'base64')), 300)`], { + stdio: ['ignore', 'ignore', 'ignore', 3], + env: { FRAME: encodeFrame({ t: 'end', how: 'finished', value: '"late"' }).toString('base64') }, + }); + late.unref(); +} +if (how === 'seventeen-calls') for (let id = 1; id <= 17; id++) call(id); +else if (how === 'undeclared-call') call(1, 'nope'); +else if (how === 'reused-id') { + call(1); + call(1); +} else if (how === 'unanswered-seen') writeSync(3, encodeFrame({ t: 'seen', id: 5 })); +else if (how === 'wakes-past-cap') for (let id = 1; id <= 3; id++) call(id, '(wake_agent)', 'null'); +else if (how === 'args-past-caps') call(1, 'echo', JSON.stringify({ values: new Array(MAX_ARGUMENT_VALUES + 1).fill(0) })); +else if (how === 'wake-past-payload') call(1, '(wake_agent)', JSON.stringify('x'.repeat(MAX_WAKE_PAYLOAD_BYTES + 1))); +else if (how === 'long-failure') { + writeSync(3, encodeFrame({ t: 'end', how: 'failed', error: { name: 'N'.repeat(100_000), message: 'm'.repeat(100_000), stack: 's'.repeat(100_000) } })); +} +else if (how === 'cut-frame') { + // A header that announces 100 bytes, and 10 of them. + const header = Buffer.alloc(4); + header.writeUInt32BE(100, 0); + writeSync(3, Buffer.concat([header, Buffer.alloc(10, 0x7b)])); +} else if (how === 'finished-then-hold') { + writeSync(3, encodeFrame({ t: 'end', how: 'finished', value: '1' })); + setInterval(() => {}, 60_000); +} else if (how === 'late-stderr') { + // The words come 300 ms after this process has gone, through the stderr a process of its own holds. + const late = spawn(process.execPath, ['-e', `setTimeout(() => process.stderr.write('words that came late\\n'), 300)`], { stdio: ['ignore', 'ignore', 2] }); + late.unref(); + process.exit(3); +} else if (how === 'call-after-stop') { + parentFrames((frame) => { + if (frame.t !== 'stop') return; + call(1); + writeSync(3, encodeFrame({ t: 'end', how: 'stopped' })); + process.exit(0); + }); +} else if (how === 'no-seen-then-stop') { + call(1); + // Its answer comes and is never seen; then the stop. + parentFrames((frame) => { + if (frame.t !== 'stop') return; + writeSync(3, encodeFrame({ t: 'end', how: 'stopped' })); + process.exit(0); + }); +} else if (how === 'env') process.stderr.write(`${JSON.stringify(Object.keys(process.env))}\n`); +else if (how === 'garbage') writeSync(3, Buffer.from([0xff, 0xff, 0xff, 0xff, 1, 2, 3])); +else if (how === 'not-a-message') writeSync(3, encodeFrame({ t: 'call', id: -1, name: 'x', args: '{}' })); +else if (how === 'result-kind') writeSync(3, encodeResultFrame(1, true, '{}', 1024)); +else if (how === 'empty') writeSync(3, Buffer.from([0, 0, 0, 0])); +else if (how === 'silent-exit') process.stderr.write('the child had something to say on its way out\n'); +// With a reason of its own, which an earlier version of the protocol took from the child. +else if (how === 'stopped-unasked') writeSync(3, encodeFrame({ t: 'end', how: 'stopped', reason: 'agent' })); +else if (how === 'seen-queued') { + // Calls with large answers it never reads, so the parent's queue holds the + // second; once the gate file (argv[3]) appears, a seen for that second answer. + call(1, 'echo', '{"n":1}'); + call(2, 'echo', '{"n":2}'); + const gate = process.argv[3]!; + const poll = setInterval(() => { + if (!existsSync(gate)) return; + clearInterval(poll); + writeSync(3, encodeFrame({ t: 'seen', id: 2 })); + setTimeout(() => process.exit(0), 50); + }, 10); +} else if (how === 'half-frame-on-stop') { + // Ready once its listener is on; at a stop, half a frame, and then nothing until the grace's kill. + writeSync(3, encodeFrame({ t: 'out', text: 'ready' })); + parentFrames((frame) => { + if (frame.t !== 'stop') return; + // It says so first, then writes the half frame at once. + writeSync(3, encodeFrame({ t: 'out', text: 'cutting' })); + const header = Buffer.alloc(4); + header.writeUInt32BE(100, 0); + writeSync(3, Buffer.concat([header, Buffer.alloc(10, 0x7b)])); + }); + setInterval(() => {}, 60_000); +} else if (how === 'reuse-after-answer') { + // Call 1, and once its answer has come, call 1 again before saying it was seen. + call(1); + parentFrames((frame) => { + if (frame.t === 'result' && frame.id === 1) call(1); + }); + setTimeout(() => process.exit(0), 5_000); +} else if (how === 'seen-pending') { + // A seen for a call whose answer hasn't come. + call(1, 'slow'); + writeSync(3, encodeFrame({ t: 'seen', id: 1 })); +} else if (how === 'huge-out') writeSync(3, encodeFrame({ t: 'out', text: 'x'.repeat(MAX_BATCH_CHARS + 1) })); +else if (how === 'many-values-frame') writeSync(3, encodeFrame({ t: 'out', text: 'x', more: Array.from({ length: 40 }, () => 0) })); +else if (how === 'wakes-then-ignore-stop') { + // Two wakes, the second waiting out the spacing; a stop is ignored for 1.5 s. + call(1, '(wake_agent)', '1'); + call(2, '(wake_agent)', '2'); + parentFrames((frame) => { + if (frame.t !== 'stop') return; + setTimeout(() => { + writeSync(3, encodeFrame({ t: 'end', how: 'stopped' })); + process.exit(0); + }, 1_500); + }); +} else if (how === 'exit-holding-fd3') { + // Gone without an ending, with fd 3 held open for 1.5 s. + holdFd3(1_500); + setTimeout(() => process.exit(0), 50); +} else if (how === 'wakes-then-exit-holding-fd3') { + // Two wakes, the second waiting out the spacing; then gone, with fd 3 held open for 1.5 s. + call(1, '(wake_agent)', '1'); + call(2, '(wake_agent)', '2'); + holdFd3(1_500); + setTimeout(() => process.exit(0), 100); +} +if (!SELF_TIMED.has(how)) setTimeout(() => process.exit(how === 'silent-exit' || how === 'env' ? 3 : 0), 50); + +if (how === 'reuse-timed-out-id') { + // Call 1, and each time its TimeoutError comes, say the script saw it and call 1 again, 20 times. + let calls = 1; + call(1, 'slow', '{"hold":true}'); + parentFrames((frame) => { + if (frame.t !== 'result' || frame.id !== 1 || calls >= 20) return; + writeSync(3, encodeFrame({ t: 'seen', id: 1 })); + calls += 1; + call(1, 'slow', '{"hold":true}'); + }); + setInterval(() => {}, 60_000); +} else if (how === 'wakes-after-stop') { + // Ready once its listener is on; at a stop, as many wakes as argv[3] says, each with an id of its own, none seen. + writeSync(3, encodeFrame({ t: 'out', text: 'ready' })); + parentFrames((frame) => { + if (frame.t !== 'stop') return; + for (let id = 1; id <= Number(process.argv[3]); id++) call(id, '(wake_agent)', '"after"'); + }); + setInterval(() => {}, 60_000); +} else if (how === 'calls-then-exit-on-gate') { + // 16 calls the bridge holds, each seen once its TimeoutError comes, which + // frees its place in the script, and then a 17th, which waits for room in + // the host; once the gate file (argv[3]) appears, gone, with fd 3 held open for 1.5 s. + for (let id = 1; id <= 16; id++) call(id, 'slow', `{"hold":true,"i":${id}}`); + let seen = 0; + parentFrames((frame) => { + if (frame.t !== 'result') return; + writeSync(3, encodeFrame({ t: 'seen', id: frame.id })); + if (++seen === 16) call(17, 'slow', '{"hold":true,"i":17}'); + }); + const gate = process.argv[3]!; + const poll = setInterval(() => { + if (!existsSync(gate)) return; + clearInterval(poll); + holdFd3(1_500); + process.exit(0); + }, 10); +} else if (how === 'late-call') { + // Gone at once; a process of its own, holding the fd 3 it shares, writes a + // call frame once the gate file (argv[3]) appears, which the test makes + // once it has seen this one's exit, and holds fd 3 a little after. + const late = spawn(process.execPath, ['-e', [ + "const fs = require('node:fs');", + 'const poll = setInterval(() => {', + ' if (!fs.existsSync(process.env.GATE)) return;', + ' clearInterval(poll);', + " fs.writeSync(3, Buffer.from(process.env.FRAME, 'base64'));", + ' setTimeout(() => process.exit(0), 300);', + '}, 10);', + 'setTimeout(() => process.exit(0), 20000);', + ].join('\n')], { + stdio: ['ignore', 'ignore', 'ignore', 3], + env: { FRAME: encodeFrame({ t: 'call', id: 1, name: 'echo', args: '{"late":true}' }).toString('base64'), GATE: process.argv[3]! }, + }); + late.unref(); + process.exit(0); +} else if (how === 'long-value') writeSync(3, encodeFrame({ t: 'end', how: 'finished', value: JSON.stringify('v'.repeat(100_000)) })); +else if (how === 'frame-at-cap') { + // An ending whose frame's body is exactly the longest a child sends (argv[3] bytes). + const frame = (n: number) => encodeFrame({ t: 'end', how: 'finished', value: JSON.stringify('v'.repeat(n)) }); + const bodyOf = (n: number) => frame(n).length - 4; + writeSync(3, frame(Number(process.argv[3]) - bodyOf(0))); +} +else if (how === 'big-arg-string') call(1, 'echo', JSON.stringify({ s: 'x'.repeat(5 * 1024 * 1024) })); +else if (how === 'oversized-frame') { + // A header naming one byte more than the longest frame a child sends. + const header = Buffer.alloc(4); + header.writeUInt32BE(Number(process.argv[3]) + 1, 0); + writeSync(3, header); +} +if (how === 'wakes-then-garbage-on-gate') { + // Two wakes, the second waiting out the spacing; once the gate file (argv[3]) appears, a frame the host can't read. + call(1, '(wake_agent)', '1'); + call(2, '(wake_agent)', '2'); + const gate = process.argv[3]!; + const poll = setInterval(() => { + if (!existsSync(gate)) return; + clearInterval(poll); + writeSync(3, encodeFrame({ t: 'bogus' })); + // It stays until the host kills it, so its exit can't come before the frame is read. + setInterval(() => {}, 60_000); + }, 10); +} +if (how === 'wake-then-exit-on-gate') { + // Two wakes, the second waiting out the spacing; once the gate file (argv[3]) appears, gone, with fd 3 held open for 4 s. + call(1, '(wake_agent)', '1'); + call(2, '(wake_agent)', '2'); + const gate = process.argv[3]!; + const poll = setInterval(() => { + if (!existsSync(gate)) return; + clearInterval(poll); + holdFd3(4_000); + process.exit(0); + }, 10); +} diff --git a/test/helpers/bad-child-finished-then-hold.ts b/test/helpers/bad-child-finished-then-hold.ts new file mode 100644 index 000000000..ab93eb99b --- /dev/null +++ b/test/helpers/bad-child-finished-then-hold.ts @@ -0,0 +1,3 @@ +/** The bad child's `finished-then-hold`, for a caller that can name only an entry: it ends its script and stays. */ +process.argv[2] = 'finished-then-hold'; +await import('./bad-child-entry.js'); diff --git a/test/helpers/fake-child-entry.ts b/test/helpers/fake-child-entry.ts new file mode 100644 index 000000000..149a24976 --- /dev/null +++ b/test/helpers/fake-child-entry.ts @@ -0,0 +1,5 @@ +/** A script's child on the stand-in engine, for the isolate's runner tests. */ +import { childMain } from '../../src/code-execution/isolate/child-main.js'; +import { loadFakeEngine } from './fake-engine.js'; + +childMain(loadFakeEngine); diff --git a/test/helpers/fake-engine.ts b/test/helpers/fake-engine.ts new file mode 100644 index 000000000..33d03b2a3 --- /dev/null +++ b/test/helpers/fake-engine.ts @@ -0,0 +1,94 @@ +/** + * A stand-in for the engine binding, on `node:vm`, for testing the host's side + * of the isolate without the engine. It keeps the binding's contract: the + * three slots, `start` and `settle` answering a RunState once their jobs have + * run, and a stop through `interrupt()`. It's no boundary, and its interrupt + * is cooperative: a guest that spins calls `__tick()` in its loop, which asks + * `interrupt()` as the real engine's handler would. `__crash()` ends the + * child's process, as a crash would, and `__exists(path)` lets a test hold a + * computing guest until it makes a file. + */ + +import { existsSync } from 'node:fs'; +import vm from 'node:vm'; +import type { Engine, EngineSlots, RunState } from '../../src/code-execution/engine.js'; +import type { LoadEngine } from '../../src/code-execution/isolate/child.js'; + +const HOST_SIDE = ` +globalThis.__state = { kind: 'pending' }; +const __pending = new Map(); +globalThis.__host = (name, json) => { + const id = __hostCall(name, json); + return new Promise((resolve, reject) => { __pending.set(id, { resolve, reject }); }); +}; +globalThis.__output = (stream, text) => __outputCall(stream, text); +globalThis.__tick = () => { if (__interruptCall()) throw new Error('interrupted'); }; +globalThis.__crash = () => __crashCall(); +globalThis.__exists = (path) => __existsCall(path); +globalThis.__settle = (id, ok, json) => { + const entry = __pending.get(id); + if (!entry) throw new Error('no pending call ' + id); + __pending.delete(id); + const value = JSON.parse(json); + if (ok) entry.resolve(value); else entry.reject(value); +}; +`; + +export const loadFakeEngine: LoadEngine = async (_limits, slots: EngineSlots): Promise => { + let nextId = 1; + let interrupted = false; + const context = vm.createContext( + { + __hostCall: (name: string, json: string) => { + const id = nextId++; + slots.hostCall(id, name, json); + return id; + }, + __outputCall: (stream: 1 | 2, text: string) => slots.output(stream === 2 ? 2 : 1, String(text)), + __interruptCall: () => { + if (slots.interrupt()) interrupted = true; + return interrupted; + }, + // The child's process ending mid-script, as a crash of the real engine would. + __crashCall: () => process.exit(7), + // Whether a file exists, so a test can hold a computing guest until it says so. + __existsCall: (path: string) => existsSync(String(path)), + }, + { microtaskMode: 'afterEvaluate' }, + ); + vm.runInContext(HOST_SIDE, context); + + const state = (): RunState => { + if (interrupted) return { kind: 'interrupted' }; + const s = (context as { __state: RunState }).__state; + return { ...s }; + }; + + return { + start(prelude, script) { + try { + vm.runInContext(prelude, context, { filename: '' }); + // vm scripts have no top-level await: the line runs inside an async arrow, whose settling sets __state. + vm.runInContext( + `(async () => { ${script.replace(/^await /, 'return await ')} })().then( + (v) => { globalThis.__state = { kind: 'fulfilled', json: v === undefined ? undefined : JSON.stringify(v) }; }, + (e) => { globalThis.__state = { kind: 'rejected', name: e instanceof Error ? String(e.name) : '', message: e instanceof Error ? String(e.message) : String(e), stack: e instanceof Error ? e.stack : undefined }; });`, + context, + { filename: 'script.js' }, + ); + } catch (e) { + if (!interrupted) { + const err = e as Error; + (context as { __state: RunState }).__state = { kind: 'rejected', name: err.name ?? '', message: err.message ?? String(e) }; + } + } + return state(); + }, + settle(callId, ok, json) { + const text = typeof json === 'string' ? json : Buffer.from(json.buffer, json.byteOffset, json.byteLength).toString('utf8'); + vm.runInContext(`__settle(${callId}, ${ok}, ${JSON.stringify(text)})`, context); + return state(); + }, + dispose() {}, + }; +}; diff --git a/test/helpers/orphan-parent-entry.ts b/test/helpers/orphan-parent-entry.ts new file mode 100644 index 000000000..798bf3c10 --- /dev/null +++ b/test/helpers/orphan-parent-entry.ts @@ -0,0 +1,24 @@ +/** + * A script's child started as the runner starts one, under this process, which + * names itself the child's parent. Once the guest says it's running, by an + * output frame on fd 3, this prints the child's pid; the orphan test then + * kills this process, so the child is orphaned for real, mid-run. + */ +import { spawn } from 'node:child_process'; +import type { Readable } from 'node:stream'; +import { encodeFrame, FrameDecoder } from '../../src/code-execution/isolate/frames.js'; + +const [entry] = process.argv.slice(2); +const child = spawn(process.execPath, [entry!], { stdio: ['pipe', 'ignore', 'ignore', 'pipe'], cwd: process.env.CHILD_DIR, env: {} }); +child.stdin!.write(encodeFrame({ ...JSON.parse(process.env.START!), parentPid: process.pid })); +const decoder = new FrameDecoder(); +let told = false; +(child.stdio[3] as Readable).on('data', (chunk: Buffer) => { + for (const frame of decoder.push(chunk)) { + if (!told && (frame as { t?: string }).t === 'out') { + told = true; + process.stdout.write(`${child.pid}\n`); + } + } +}); +setInterval(() => {}, 60_000); From 9a385b46d833c95573e26c711a5a50c43a0899de Mon Sep 17 00:00:00 2001 From: Imogen-2490 Date: Sun, 18 Oct 2026 20:31:34 -0400 Subject: [PATCH 30/41] feat(code-execution)!: code_execution runs JavaScript in the isolate, and the Python runner goes code_execution now runs each script in the isolate: JavaScript in QuickJS-ng compiled to WebAssembly, in a process of its own, on the host's own runtime. A script reaches only its agent's tools. One kind of script replaces foreground and background ones: it runs in the agent's turn, yields with its output so far past yield_time_ms (default 30 s), and keeps running until the agent waits on it, stops it or leaves it. - code_execution takes a run ({code, yield_time_ms?}), wait, stop and list. Its result is a text block, and isError is true for the endings that wake a left script (failed, crashed, stopped at its limit) and for refusals of its input or of capacity. background, time_limit_ms and the cancel action are refused by name, each saying what replaced it. Once the host has begun to stop, a run starts no script. - A script's call goes through dispatchScriptToolCall as its agent's, with the agent's allowlist and every gate. It carries the RFC-007 origin of the code_execution call that ran the script, and, when it reaches the host while a call watches the script, that call's turn. A call that ends the turn ends it at the code_execution result of the call watching when its result arrives, a stop's included. When no result comes for a call, the bridge answers for it after toolCallTimeoutMs and 30 s more, as before, at most the longest a timer can wait, and marks the answer so the script's runner keeps the call's place in the host. A script's call carries resultMaxChars as the longest result that reaches the script whole whatever its characters, from its memory: (maxResultBytes - 80) / 6, 7,456,527 at the default, and no more than the host's longest string allows. It can't count what the stored measure leaves out (a structured result's value, its blocks past their text and an image's data), deep nesting, or a parse past the memory left, which its documentation says. main gave it the Python runner's cut, 5,000,000. - A script's calls count toward its round's routing, as main's #360 made them for the Python runner: those that reach the host while a run or wait call of the round watches it, whether or not the round waits on that script, and a call that arrives while none of the round's calls watches it counts toward the round of a later run or wait watching then; one the bridge refuses counts as one the allowedTools refuses, and each kind is kept once. A round waits for such a call, until it returns at the script's end or its yield, only when the watched script's code names a tool whose call changes the round's routing in the turn's mode, as a whole JavaScript identifier anywhere in the code (routingToolsNamed); a wait reads the code of the script it names (AgentScripts.codeOf). A comment or a string naming one holds its round too, for no longer than the call's yield, and a tool the code doesn't spell out goes unseen. #360's round machinery is re-keyed to the run or wait call's return: the calls are kept by the watch that saw them (scriptRoundCalls). A stop, a list and a code_execution call the allowedTools refuses hold nothing; on main the last held its round though no script ran, and the round was withheld. - Every result reaches a script as { content, structuredContent, isError }. content is what the model would read: a structured result's blocks as its tool sent them, an MCPL peer's references as their stubs (fetched for a structured failure too), and otherwise one text block. A refused call (ToolResult.refused) throws ToolRefused in the script. - Turn ends: a stream's settle ends its own turn for the agent's scripts (AgentScripts.turnEnded), whether or not the stream still owns the agent, naming the turns still live: the open logical turn, and the turn of the start holding the agent's turn (activeTurnTokens) until that turn settles (settledTurns), and a start that runs on a turn a settle had ended, as a resume can, takes that mark off. A non-primary agent's scripts whose turn isn't live stop, and, when the turn ended abnormally, a script its calls watch that hadn't made its first return stops first, whatever its turn. Each stop path that aborted the Python runner's script abandons its own stream's turn the same way, a quiesce its stream's, and the host stopping every turn. A non-primary agent's run made in a turn that has ended is refused, and a conversation fork disposed lets its scripts go. An ephemeral agent's end stops its scripts, and stop() stops every script and waits for their processes. - A left script's wake enters its agent's context with metadata.source code-execution-script and its scriptId, and asks for a turn. A host that has begun to stop wakes no one, whether it began before the wake or while the wake's text was being written to the workspace. - Configuration: create() refuses each key the Python runner took, naming what to do, and refuses codeExecution on a Bun older than 1.4.0, a prerelease of 1.4.0 included, which loses a child's fd 3 at some starts. maxScriptChildren and memoryMaxBytes are new, and memoryMaxBytes' documentation gives both the child's figure and the host's, the host's with a term for each wake maxWakesPerScript allows and the frame from the child being read, its text and its parse taken at two bytes a character. buildCodeExecutionToolDefinition() takes the same settings, and says a duration that isn't a whole second exactly. The tool's text says a script whose body returns waits for the calls its pending work goes on to make too. - CI: a job of its own runs code_execution's files from source under Bun 1.4.0, the oldest release create() admits, and under Bun 1.4.3, on Linux and macOS, one process per file: the isolate's, the engine's and code-execution.test.ts. The pack job runs the settings file under Bun 1.3.14, which create() refuses. - Removed: py-runner.ts, runtime-py.ts, their exports, test/code-execution-env.test.ts, test/helpers/pytc-stop-rows.ts, #495's tests of the runner's output, F55's result-text.ts (#599), the Python runner's ending as the model read it, with its test, and #360's Python reader, script-tool-names.ts, with its test. ToolResult.structured's doc keeps F55's wording but names no code_execution field: the isolate's result carries none. - Changelog: 0.23.0 published the Python runner's last entries, and 0.23.1 changed nothing of code execution, so code-execution-isolate.breaking.md says what changes from 0.23.1, as from 0.23.0. main's unreleased hold-round-speech.fixed.md (#360), tool-call-result-max-chars.added.md (#463) and mount-include.added.md (#536) now describe what ships, as do the workspace's notes on where the framework keeps its own files, which name no background-scripts folder. #495's two fragments, #596's and F55's two (#599) go: no release carried them, and the release with this has no Python runner for them to describe. Tests: code-execution.test.ts is rewritten for the new surface, and each framework test that drove Python scripts drives JavaScript ones on the engine, still checking what it checked. #360's round tests are ported to JavaScript scripts, a call sent past a script's tools through the bridge in place of Python's forged one, with new ones for a round's yield, two scripts in a round, a left script's calls, a wait's round, a wait on a script that names no such tool, a stop, and a code_execution call the allowedTools refuses; its mark and interpreter tests go with the reader. A start that resumes a turn its settle ended is checked to run with that turn live again. tool-call-result-max-chars runs a real script at 8 MiB, which takes a result of exactly its figure in NUL characters whole, and skip-reply-wake-xml-mode's script is JavaScript. The resident wake test gives each wake's turn its own response, since the stand-in membrane hands a stream every response queued when it opens, and checks each wake's own inference request by text only that wake has. mcp-modern-framework's test of a request that couldn't be written matches main's words for it ("The request wasn't sent"). Co-Authored-By: Claude Opus 5.5 --- .github/workflows/ci.yml | 48 + .../code-execution-isolate.breaking.md | 13 + .../f55-code-execution-text.breaking.md | 1 - .../f55-code-execution-text.changed.md | 1 - ...-code-execution-output-to-host.breaking.md | 1 - ...fix-code-execution-output-to-host.fixed.md | 8 - .../fix-code-execution-signal-advice.fixed.md | 1 - changelog.d/hold-round-speech.fixed.md | 117 +- changelog.d/mount-include.added.md | 2 +- .../tool-call-result-max-chars.added.md | 2 +- changelog.d/tool-result-refused.added.md | 2 +- src/code-execution/index.ts | 22 +- src/code-execution/isolate/scripts.ts | 9 + src/code-execution/py-runner.ts | 1392 --- src/code-execution/result-text.ts | 111 - src/code-execution/runtime-py.ts | 1070 --- src/code-execution/script-tool-names.ts | 164 - src/code-execution/tool-definition.ts | 401 +- src/framework.ts | 1351 ++- src/index.ts | 20 +- src/modules/workspace/types.ts | 8 +- src/tool-result-history.ts | 2 +- src/types/events.ts | 57 +- src/types/framework.ts | 108 +- src/types/module.ts | 5 +- test/agent-allowed-tools.test.ts | 111 +- test/code-execution-env.test.ts | 294 - test/code-execution-isolate-scripts.test.ts | 14 +- test/code-execution-isolate-settings.test.ts | 98 + test/code-execution-result-text.test.ts | 411 - .../code-execution-structured-results.test.ts | 90 +- test/code-execution.test.ts | 7452 +++++------------ test/helpers/pytc-stop-rows.ts | 173 - test/helpers/script-call.ts | 25 + test/host-quiesce.test.ts | 25 +- test/mcp-modern-framework.test.ts | 187 +- test/mcpl-tool-lifecycle-script.test.ts | 30 +- test/refusals-are-error-results.test.ts | 32 +- test/route-failure-targeting.test.ts | 82 +- test/script-structured-references.test.ts | 37 +- test/script-tool-names.test.ts | 157 - test/skip-reply-wake-xml-mode.test.ts | 5 +- test/stop-step-throws.test.ts | 16 +- test/stop-with-host-operations.test.ts | 8 +- test/tool-call-result-max-chars.test.ts | 92 +- test/tool-result-reference-uri.test.ts | 26 +- test/turn-id.test.ts | 25 +- test/unconvertible-tool-result.test.ts | 64 +- test/workspace-include.test.ts | 2 +- 49 files changed, 3773 insertions(+), 10599 deletions(-) create mode 100644 changelog.d/code-execution-isolate.breaking.md delete mode 100644 changelog.d/f55-code-execution-text.breaking.md delete mode 100644 changelog.d/f55-code-execution-text.changed.md delete mode 100644 changelog.d/fix-code-execution-output-to-host.breaking.md delete mode 100644 changelog.d/fix-code-execution-output-to-host.fixed.md delete mode 100644 changelog.d/fix-code-execution-signal-advice.fixed.md delete mode 100644 src/code-execution/py-runner.ts delete mode 100644 src/code-execution/result-text.ts delete mode 100644 src/code-execution/runtime-py.ts delete mode 100644 src/code-execution/script-tool-names.ts delete mode 100644 test/code-execution-env.test.ts create mode 100644 test/code-execution-isolate-settings.test.ts delete mode 100644 test/code-execution-result-text.test.ts delete mode 100644 test/helpers/pytc-stop-rows.ts create mode 100644 test/helpers/script-call.ts delete mode 100644 test/script-tool-names.test.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 382eb9835..c97568f85 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -118,3 +118,51 @@ jobs: - name: Packed release installs on its own run: node scripts/pack-installs-alone.mjs + + # create() refuses codeExecution on a Bun older than 1.4.0, reading the + # Bun it runs under: the settings file checks that here, under the Bun + # connectome-host's CI runs, which create() refuses. + - name: code_execution's settings under Bun 1.3.14, which create() refuses + run: bun test --timeout 60000 ./test/code-execution-isolate-settings.test.ts + + # A Bun host runs a script's process on Bun too, from the package's source, + # so code_execution's files run there as well as under Node, each file in a + # process of its own, on both platforms, since the child's stdio and fd 3 + # are where runtimes and platforms differ. code_execution needs Bun 1.4.0 or + # later (create() refuses it on an older one), so they run under 1.4.0, the + # oldest release it admits, and under 1.4.3. + code-execution-bun: + name: code_execution under Bun (${{ matrix.os }}, bun ${{ matrix.bun }}) + runs-on: ${{ matrix.os }} + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest] + bun: [1.4.0, 1.4.3] + + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 + with: + persist-credentials: false + + - name: Setup Node.js + uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0 + with: + node-version: 24 + + - name: Install dependencies + run: npm install --ignore-scripts + + - name: Setup Bun + uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 + with: + bun-version: ${{ matrix.bun }} + + - name: code_execution's tests under Bun, from source + run: | + for f in test/code-execution-isolate-*.test.ts test/code-execution-engine.test.ts test/code-execution.test.ts; do + echo "::group::$f" + bun test --timeout 60000 "./$f" || exit 1 + echo "::endgroup::" + done diff --git a/changelog.d/code-execution-isolate.breaking.md b/changelog.d/code-execution-isolate.breaking.md new file mode 100644 index 000000000..de7faf194 --- /dev/null +++ b/changelog.d/code-execution-isolate.breaking.md @@ -0,0 +1,13 @@ +- **Agents with `code_execution`, and whoever writes their prompts:** scripts are JavaScript now, not Python, and each runs in a process of its own, in QuickJS compiled to WebAssembly, on the host's own runtime (Node or Bun). A script reaches nothing but its agent's tools: no network, files, timers, packages or environment. + - **Calling tools:** by exact name, `await tools["workspace--read"]({ path: "work/notes.md" })`, one argument, an object. The mangled Python names (`workspace__read`) are gone. `ALL_TOOLS` lists each tool's name, description and `inputSchema`. A name that isn't one of the agent's tools throws at once in the script, naming close matches, and nothing is sent. At most 16 of a script's calls are out at once: one past them waits until an earlier one's answer has reached the script, and is never refused, so a `Promise.all` over many calls is paced rather than refused. Calls that timed out count against the 16 the host holds until their tools end, so a call made while 16 of those still run waits, and one that waits past its timeout gets a `TimeoutError` saying it was never sent. A call whose tool gives no result within `toolCallTimeoutMs` and 30 s more, which the host stops waiting for then, keeps its place until the script ends, since its tool may still be running. A call's arguments may be up to 4 MiB of JSON holding at most 100,000 values: past either, the call throws `ArgumentsTooLarge`, and nothing is sent. + - **Results:** every tool answers one shape, `{ content, structuredContent, isError }`: `content` is what the model would read (a structured result's blocks, otherwise one text block), `structuredContent` the tool's own structured content or `null`, and `isError` true when the tool failed. A structured result's blocks are as its tool sent them, so images survive. A call refused before its tool ran (`ToolResult.refused`) throws an `Error` instead, `ToolRefused`, and nothing was done. A result that can't be converted to JSON, such as a cyclic object from an in-process module, comes as a failure in the same shape, its text naming the tool and saying the call completed. Before, a script got a string, the tool's text or `Error: …` for a failure, carrying `.is_error` and `.structured`, and a result with structured content got `{ content, structuredContent, isError, error? }`, with a failure's text in `error`. + - **`code_execution`'s own result** is an error (`isError`) when its script failed, crashed or was stopped at its limit, with the text the model reads as its `error`; otherwise that text is its `data`, as a list of one text block. It carries no `structured`. Before, a script's result was a success whatever its `return_code`, and its `data` an object of `stdout`, `stderr`, `return_code` and, when the call's limit was capped, `time_limit_note`. + - **One kind of script:** a script runs in its agent's turn; past `yield_time_ms` (default 30 s, at most `maxYieldMs`, 10 min by default) it yields with its output so far and keeps running. Then `{"action": "wait", "script_id": …}` waits again, `{"action": "stop", …}` stops it and `{"action": "list"}` lists the agent's scripts, each with the wakes the host delivered for it. Each return shows the output since the previous one, its first and last 8,000 characters. The primary agent may leave a script running past its turn: `wake_agent()` is open to any running script, and a left script that fails, crashes or reaches its limit (24 h) wakes its agent; any other agent's scripts stop when its turn ends. The background log file is gone, its place taken by each return's output. + - **Refused inputs, each naming its replacement:** `background` (every script can yield and keep running), `time_limit_ms` (`yield_time_ms`) and the `cancel` action (`stop`). + - **Nothing is kept between scripts:** each starts fresh, so globals no longer persist until an idle reclaim. A script writes what must last with a tool. + - **Large results:** a result too large for the script's memory (past a third of it, or one that doesn't fit when it's parsed), or nested more than 2,000 deep, isn't passed to the script: the call's `ResultTooLarge` error says the tool ran, and a refusal the script can't take stays a refusal, with its start. Any other result reaches the script whole. Before, a text result was cut at 5,000,000 characters, and a structured one past that length was saved to the workspace, the script getting `{ isError, oversized: { chars, savedTo } }` in its place, or, when it couldn't be saved, a failure saying the tool may have completed. + - **Unchanged:** a script's calls go through the ordinary dispatcher as its agent's, with its allowlist and every gate; intermediate results stay out of the model's context; a call's `endTurn` ends the turn at the `code_execution` result watching the script; `wake_agent()` keeps its spacing and cap; RFC-007 observers see a script's calls as before. + - **Wakes:** a `wake_agent()` still waiting for its spacing when its script ends is dropped, however the script ended. Before, only a cancel dropped it, and a script that finished or died still woke its agent after the wait, saying it was still running. New: a wake's payload may be up to 64 KiB of JSON, and past it `wake_agent()` throws `WakeRefused`, with nothing sent. +- **Operators with `codeExecution` enabled:** the host no longer needs a Python interpreter. On Bun it needs Bun 1.4.0 or later: `create()` refuses `codeExecution` on an older Bun, since Bun 1.3.14 loses, at some starts, the pipe a script's process answers on, which crashes the script. `create()` refuses each setting the Python runner took, naming what to do: `pythonPath`, `idleReclaimMs`, `env` and `inheritEnv` are gone, since a script has no interpreter, nothing to reclaim and no environment, and `scriptTimeoutMs`, `maxScriptTimeoutMs`, `backgroundMaxLifetimeMs` and `maxBackgroundScripts` become `yieldMs`, `maxYieldMs`, `scriptLifetimeMs` and `maxRunningScripts`. New: `maxScriptChildren` (default 16), the script processes a host runs at once, and `memoryMaxBytes` (default 128 MiB, 4 MiB to 2 GiB in 64 KiB pages), each script's budget. A script's process stays within about its runtime's base plus 2.7 times `memoryMaxBytes`, plus what its calls take as they go out, up to about 200 MiB under Node and 410 MiB under Bun, and the host holds at most about 6 times `memoryMaxBytes`, plus 653 MiB, plus 128 KiB for each wake `maxWakesPerScript` allows, for a script, about 1.4 GiB at the defaults, for its 16 calls out at once, timed-out ones whose tools still run included, up to 16 more waiting for those, their answers, the frame from its process being read and its waiting wakes: size a host by both. `toolCallTimeoutMs`, `wakeMinIntervalMs` and `maxWakesPerScript` are unchanged. A connectome-host recipe that sets `codeExecution.pythonPath`, `env`, `scriptTimeoutMs` or `idleReclaimMs` is refused the same way. + - **What it promises:** `code_execution` adds nothing to what an agent's tools allow, provided the host runtime's WebAssembly engine, Bun's or Node's, and our glue are right. It claims no containment beyond that. +- **Library users:** `PyRunner`, `buildInjectedTools`, `PYTHON_RUNTIME_SOURCE` and the types `InjectedTool`, `ExecResult`, `PyRunnerOptions`, `ScriptToolCallHandler` and `ScriptToolResult` are no longer exported. `CodeExecutionConfig` has the new keys, and `buildCodeExecutionToolDefinition()` takes them, as `create()` does, where it took the Python runner's options. `HostModeStatus.backgroundScripts` counts scripts left running after a yield. A script's wake enters the agent's context with `metadata.source` `code-execution-script` (was `background-script`), tagged `script:wake` as before. diff --git a/changelog.d/f55-code-execution-text.breaking.md b/changelog.d/f55-code-execution-text.breaking.md deleted file mode 100644 index db183989a..000000000 --- a/changelog.d/f55-code-execution-text.breaking.md +++ /dev/null @@ -1 +0,0 @@ -- **Hosts:** a foreground `code_execution` result's `data` is now the text the model reads, as a list of one text block, and its fields, `stdout`, `stderr`, `return_code` and `time_limit_note`, are in `structured`. A host that reads those fields from `data` reads them from `structured`. We found no host that reads them. A background script's results are unchanged. diff --git a/changelog.d/f55-code-execution-text.changed.md b/changelog.d/f55-code-execution-text.changed.md deleted file mode 100644 index 7a4f1d7c5..000000000 --- a/changelog.d/f55-code-execution-text.changed.md +++ /dev/null @@ -1 +0,0 @@ -- `code_execution`'s result now reaches the model as text, where it was one line of JSON with the script's output escaped inside it. How the script ended comes first, as `exit code: N` and, when the call's `time_limit_ms` was capped, the time-limit note; then the script's stderr, then its stdout, each under a label. A result past the agent's inline cap (`tool_result_inline_max_chars`, 24,000 characters by default) is cut a stream at a time to each one's head and tail, around a count of what was cut, at a line's end where one is near: stderr keeps up to a third of the room, or more when stdout needs less, and its tail keeps its last line whole when that line fits. So at any cap that holds a cut of each stream, however long stdout runs, it no longer pushes the exit code and the end of stderr, which holds a traceback's last line and the reason a stopped script gives, past the cut. With a writable workspace mount that has no include list, the whole output goes to a file of its own in the folder a long result spills to, and the result's last line says where; without one, or when writing it fails, that line says what was cut wasn't kept. A cap too small to hold a cut of each stream beside that line, a few hundred characters, leaves the whole text to the inline cap's usual spill, which keeps its head. diff --git a/changelog.d/fix-code-execution-output-to-host.breaking.md b/changelog.d/fix-code-execution-output-to-host.breaking.md deleted file mode 100644 index 9939a5827..000000000 --- a/changelog.d/fix-code-execution-output-to-host.breaking.md +++ /dev/null @@ -1 +0,0 @@ -- **Hosts:** the `code_execution` interpreter now takes its channel to the host on its file descriptors 3 and 4, and `PYTHON_RUNTIME_SOURCE`, which the package exports, speaks a new protocol there. **Who needs to act:** a host whose `pythonPath` starts Python through a wrapper that doesn't pass fds 3 and 4 on, such as one that runs it in a container, under sudo with its default settings, or from a Python script through `subprocess`, whose `close_fds` is on by default, and a host that runs `PYTHON_RUNTIME_SOURCE` itself rather than through `PyRunner`. Until it acts, every script fails to start, and the result says the host's channel didn't reach the interpreter: the runtime says so on stderr and exits with 78 (EX_CONFIG). **Migration:** have the wrapper `exec` Python, or pass fds 3 and 4 on. A host that runs the runtime itself opens fd 3 for its messages to the runtime and fd 4 for the runtime's, gives it `/dev/null` as fd 0, reads its fds 1 and 2 itself, puts a `mark` in each `exec`, and takes the script's output on each stream up to that mark, which the runtime writes once the script returns: `exec_result` now carries only the return code. It stops the runtime with the `exit` op, since closing fd 3 while a script runs now ends the runtime with exit code 70 and a line saying a script closed, shut down or replaced fd 3. `init` no longer takes `log_path` or `tail_chars`, so the runtime writes no journal for a background script and keeps no tail: the host writes both from fds 1 and 2, where a background script's stderr joins its stdout. **Unchanged by this change:** `PyRunner`'s interface, `ExecResult` included, and a `pythonPath` that names an interpreter or a wrapper that `exec`s one, of which it needs nothing more. The interpreter's new working directory, where a wrapper's own relative paths now resolve, comes from #193, whose entry says what it changes. No sibling package needs a new version. diff --git a/changelog.d/fix-code-execution-output-to-host.fixed.md b/changelog.d/fix-code-execution-output-to-host.fixed.md deleted file mode 100644 index 7e7ce60ff..000000000 --- a/changelog.d/fix-code-execution-output-to-host.fixed.md +++ /dev/null @@ -1,8 +0,0 @@ -- What the commands a `code_execution` script runs write to stdout and stderr now comes back in the script's result, beside the script's own prints, line by line in the order written on each stream: `subprocess` without `capture_output`, `os.system`, and C code writing to file descriptors 1 and 2. It used to reach only the host's log, and a line a command wrote without a newline ran into the runtime's own reply, so the script waited out its time limit. A command with a lot of output now fills the result as the script's own prints do: each stream keeps up to 2,000,000 characters, counted as UTF-16 code units, so a character outside the Basic Multilingual Plane, such as an emoji, counts as two, and a result past the agent's inline cap is cut to fit it, with a note naming the workspace file that holds the whole of it or saying that it wasn't kept. Output belongs to the script running when it arrives: a command a script left running writes into the result of a later script running then, and into the host's log between scripts or once its interpreter has been replaced. The tool's description now says that what the script and the commands it runs print comes back, and that a background script's output streams to a log file when the agent has a writable workspace. That changes the tool definitions every request carries, and by [Anthropic's prompt-caching documentation](https://platform.claude.com/docs/en/build-with-claude/prompt-caching#what-invalidates-the-cache), a prompt cache written before the upgrade isn't reused. -- A command a `code_execution` script runs now reads the end of its input at once: its standard input is `/dev/null`. It used to read the runtime's channel from the host, so a command that reads its input, such as `cat`, or `ssh` without `-n`, took the host's messages, and the script failed or waited out its time limit. -- A script that closes or redirects its file descriptors 0, 1 or 2 leaves the next script its output: the runtime's channel to the host has file descriptors of its own, 3 and 4, which no command a script runs is given, and each script starts with 0, 1 and 2 as the host set them, blocking even after a command set one non-blocking. A script that points 1 or 2 at the runtime's channel gets it put back at its end, so a partial line it left there reaches its result; what a script or its commands write onto that channel, or read from it, can still garble the runtime's messages: the script's own, and, from a command started holding the channel, a later script's for as long as that command runs. A script whose result is garbled that way waits out its time limit and the grace, and the interpreter restarts, so the next script's variables are gone. A script's `os.close(1)` used to leave it waiting out its time limit, and its `os.close(0)` sometimes left the next script doing so. A script that closes or replaces one of the runtime's own file descriptors, 3, 4, or its copies of 0 to 2, now ends the interpreter by the end of that script at the latest, and the result says which and what to check, as does one that shuts down the socket under file descriptor 1, 3 or 4. That line is lost when the script also closed or replaced both the runtime's copy of file descriptor 2 and file descriptor 2 itself, or shut down the socket under them, and the result leaves it out once the script's stderr has passed its 2,000,000-character cap. -- What a script and its commands wrote before it was stopped, as far as it had reached the host, now comes back ahead of the message saying why, when it's killed at its time limit, aborted, or its interpreter crashes; it used to be lost. A partial line still in the script's writer when the interpreter is killed or crashes is lost with it. A crash's own text comes back too, and goes to the host's log: a fatal error's, which goes to the script's file descriptor 2 wherever the script pointed it, and now a traceback for a segfault or an abort, which faulthandler writes where CPython alone wrote nothing. -- A background script's journal now holds its commands' output too, each line as it arrives, and ends saying why the host stopped the script, when it did, a time limit's note included, unless the journal reached its 50 MB cap, now counted in bytes, as its cap line says, or a write to it failed. A background script that crashes leaves the last of what it wrote, its crash's text included and after it, in the message that wakes its agent, where that held only the message about the exit. A journal that can't be opened or written says so once in the host's log. A background script started where no workspace is writable is told that `wake_agent()` reaches its agent and, if the script dies, the last of its output reaches it too, where it was told its output wasn't retrievable. -- A script that prints a lone surrogate, such as `"\udcff"`, now gets its output back with it written as `\udcff`, rather than as U+FFFD. -- A script's prints now reach the host as each line is complete, so a partial line waits for its newline, a flush or the end of the script, as Python's stdout does on a terminal, and what a wrapper of the script's own in `sys.stdout` or `sys.stderr` holds is flushed at the script's end, as Python's exit would. Printing costs more than it did: 100,000 short `print` calls took about 0.36 s, against about 0.15 s before, on Linux with Node 24.22.0 and Python 3.13.5. -- An interpreter that exits as it starts now says in the result what it wrote to stderr, up to its last 2,000 characters, with "…" where they're cut, and one ended by a signal as it starts names the signal. Output that arrives while its interpreter runs no script, such as from a command a script left running, even once that interpreter has ended, reaches the host's log as `stdout while its interpreter runs no script: …` or `stderr while its interpreter runs no script: …`, each piece cut at 2,000 characters, where stdout's used to read `non-protocol stdout line: …`: up to 65,536 characters of such lines, counted as UTF-16 code units, on each pipe from the start of its interpreter's last script, then a line saying the host drops the rest while the interpreter runs no script. While the interpreter lives, the host reads such output as it's written; once the host has torn the interpreter down, as at an idle reclaim, it reads it at 1 MiB a second at most, so a command left running that writes without end slows to that rather than costing the host a core, and keeps running. A host that turns the idle reclaim off reads such a command at full speed for as long as its interpreter lives. When the host's messages stop reaching an interpreter, the host now gives it a moment to exit on its own before killing it, so one that died, as by running out of memory, reads as its own exit rather than as one that stopped reading; and the stopped-reading message no longer says a script closed its standard input: it says the interpreter stopped reading the host's messages, and to check whether the script closes, shuts down or replaces file descriptor 3. diff --git a/changelog.d/fix-code-execution-signal-advice.fixed.md b/changelog.d/fix-code-execution-signal-advice.fixed.md deleted file mode 100644 index 779fa9ac0..000000000 --- a/changelog.d/fix-code-execution-signal-advice.fixed.md +++ /dev/null @@ -1 +0,0 @@ -- When a crash's signal (SIGSEGV, SIGABRT, SIGBUS, SIGFPE or SIGILL) ends `code_execution`'s Python interpreter while a script runs, the result now says to check the traceback above it for the call that crashed in native code, such as an extension module the script uses, or, when no traceback reached the result, to check whether native code the script calls crashes. It used to say to check whether the script uses too much memory, which now comes only after SIGKILL, the signal the out-of-memory killer sends. Any other signal gets `os.kill()` as its example. diff --git a/changelog.d/hold-round-speech.fixed.md b/changelog.d/hold-round-speech.fixed.md index 8e6d1cfc7..5bc872cf2 100644 --- a/changelog.d/hold-round-speech.fixed.md +++ b/changelog.d/hold-round-speech.fixed.md @@ -25,67 +25,66 @@ out are never streamed, and neither is text held as a draft. A `{{unsent}}` resend streams the draft's words it sends, rather than the token. -- With native tool calls (`roundContent`), a call made inside a foreground - `code_execution` script now counts toward its round's routing, as the - same call made directly does (#306). In explicit mode an inner - `skip_reply` keeps the round's envelopes in the context. In locus and - hybrid mode an inner `skip_reply` keeps the round's prose private, and an - inner silencing send, a `drafts` resend among them, holds it and the - turn's later prose as drafts, as a direct one does. An inner - `think` under the private same-round policy holds the round back in - every mode. An inner `skip_reply` that's refused, because the agent's - allowedTools doesn't name it or because this host can't set the - `wake_in_seconds` it asks for, counts as a refused direct one: it keeps - only its own round private, and the turn goes on. An inner send the - allowedTools refuses, like a refused direct one, silences nothing. On - the fallback without `roundContent`, as in XML tool mode, an inner - call counts only as a direct one does there. Nothing is routed until the - turn ends, so a silencing send holds the whole turn's prose as drafts, - the rounds before it included. The private `think` hold and an explicit - round's `skip_reply` don't apply there (#307). -- A round whose script can change its routing waits for the script, and its - prose goes out when the script finishes. A script can change it when its - code names a tool whose call changes the round's routing in the turn's - mode: in locus and hybrid mode `skip_reply`, a silencing send, `drafts` - (whose resend is one), `say` or `whisper`; in explicit mode only - `skip_reply`; and in either, `think` under the private same-round - policy. Disabled mode holds nothing. A name - counts in code wherever it stands as a whole word of ASCII letters, digits - and underscores, or after `__` as in `mcpl__world__say`, as a function is - called or a `tools[...]` key is written: `say_hello` doesn't name `say`. A - name in a comment doesn't count, since Python never runs one, while a name - anywhere in a string does, since a string can be a key or code to run. A - script can also change its round's routing when the interpreter it runs in - has run a script that left such a tool reachable, by naming the tool - inside a definition (a function or lambda, a class's methods among them) - or other than in a direct call, or by keeping a string that is the tool's - whole name, optionally namespaced, as a key kept for later is. That holds - until the interpreter is reclaimed or restarted. A direct call outside a - definition, a class's own body included, or a string that only mentions a - tool, as a printed sentence does, leaves nothing reachable. Without code - execution, a `code_execution` call holds nothing. Any other round is - routed at its tool-calls event, as before, so narration beside a script - isn't held for the script's runtime. What goes unseen is routed as it was - before scripts counted, such as a tool name the code doesn't spell out - (built at runtime, written with escapes, or in non-ASCII spellings), code - loaded from a file or module, an enumeration of the tools, a kept key - whose prefix, as the script writes it, contains whitespace, a call written - in a string's text (code run with `exec` or `eval`, or an f-string's - field), inside a definition too, a definition or alias made inside such a - string, a call that outlives its script (one it awaits only in a later - script, schedules as a task, or leaves in a generator expression), and - code a later script runs from a string an earlier one kept. The turn's +- With native tool calls (`roundContent`), a call of a `code_execution` + script that reaches the host while a run or wait call of the round + watches the script now counts toward that round's routing, as the same + call made directly does (#306), whether or not the round waits on that + call's script. In explicit mode an inner `skip_reply` keeps the round's + envelopes in the context. In locus and hybrid mode an inner `skip_reply` + keeps the round's prose private, and an inner silencing send, a `drafts` + resend among them, holds it and the turn's later prose as drafts, as a + direct one does. An inner `think` under the private same-round policy + holds the round back in every mode but disabled. An inner `skip_reply` + that's refused at dispatch, because this host can't set the + `wake_in_seconds` it asks for or because the agent's allowedTools doesn't + name it, or that the host refuses before dispatch, as for arguments that + aren't an object, counts as a refused direct one: it keeps only its own + round private, and the turn goes on. An inner send refused so, like a + refused direct one, silences nothing. A script has only the tools its + agent's allowedTools names, so a call of + any other tool fails in the script and reaches no one. On the fallback + without `roundContent`, as in XML tool mode, an inner call counts only as + a direct one does there. Nothing is routed until the turn ends, so a + silencing send holds the whole turn's prose as drafts, the rounds before + it included. The private `think` hold and an explicit round's + `skip_reply` don't apply there (#307). +- A round whose run or wait call watches a script that can change its + routing waits for that call to return, at the script's end or at the + call's yield, and its prose goes out then. A script can change it when + its code names a tool whose call changes the round's routing in the + turn's mode: in locus and hybrid mode `skip_reply`, a silencing send, + `drafts` (whose resend is one, so its name holds whatever action its + call asks), `say` or `whisper`; in explicit mode only + `skip_reply`; and in either, `think` under the private same-round policy. + Disabled mode holds nothing. A wait reads the code of the script it + names. A name counts wherever it stands in the code as a whole + JavaScript identifier, comments and strings included: `tools.say(...)`, + `tools["say"]` and `tools["world--say"]` name `say`, and `say_hello` + doesn't. So a comment or a string that mentions such a tool holds its + round too, for no longer than the call's yield: 30 seconds by default, + and up to `maxYieldMs`, 600 seconds by default, when the call asks. A + tool name the code doesn't spell out, built at runtime or written with + escapes, goes unseen, and its round is routed as it was before scripts + counted. A stop or a list holds nothing, and neither does a + `code_execution` call without code execution or one the agent's + allowedTools refuses. Any other round is routed at its tool-calls event, + as before, so narration beside a script isn't held for it. The turn's locus and an explicit round's `>>` targets are taken at the round's tool-calls event, when the model has finished writing it, not when its - scripts return; hybrid's `>>>` targets apply as its delivery runs, as for - any queued hybrid delivery. A background script's call returns as soon as - the script has started, and its later calls aren't the round's. A script's - result settles only the round that dispatched it, even when a newer round - reuses its call id. A round whose turn ends or stops waiting on it, or - that a newer round of the same agent replaces, before its scripts return, - as when the turn is stopped, is withheld: it is neither sent nor held as - a draft, a log line names it, and in locus and hybrid mode the turn's - delivery receipt counts it as suppressed. + calls return; hybrid's `>>>` targets apply as its delivery runs, as for + any queued hybrid delivery. What counts is the host's receipt of a call, + not the script's making it: a call that reaches the host while no call of + the round watches the script isn't the round's, as one made once its run + has yielded, one made while it was watched that waited, with 16 of the + script's calls already out, until the watching call had returned, and one + still on its way when the call yielded. Such a call's effect can come + after the round's prose. A call's result settles only the round that + dispatched it, even when a + newer round reuses its call id. A round whose turn ends or stops waiting + on it, or that a newer round of the same agent replaces, before its calls + return, as when the turn is stopped, is withheld: it is neither sent nor + held as a draft, a log line names it, and in locus and hybrid mode the + turn's delivery receipt counts it as suppressed. - Every stream now sends `channels/outgoing/complete` for each channel it streamed to, while its server registers it and takes the stream, on every exit path, as MCPL Spec 14.3 asks: once its deliveries have run, or, when diff --git a/changelog.d/mount-include.added.md b/changelog.d/mount-include.added.md index 910507d7e..4df4dfffb 100644 --- a/changelog.d/mount-include.added.md +++ b/changelog.d/mount-include.added.md @@ -7,4 +7,4 @@ - **Where a nested mount is found:** its root is placed where disk has it at each start, and its directory is matched by spelling, so four cases fall outside the guarantee, as for any nested mount: on a disk that folds names, a mount configured through a link that spells a directory otherwise than disk does isn't found nested, and the outer mount holds its files as its own; on such a disk, a path through a link of the outer mount that spells a nested mount's directory otherwise than that mount's configured path does, whether the link or the configured path is the one spelled otherwise than disk, isn't found to lead there, and the outer mount reads, lists, searches and writes there as its own, creating files too, though a walk from its root passes over the directory; on such a disk, a place in a nested mount's directory that a link does lead to is judged by that mount's include, and named, in the spelling the link's target gives, so a refusal can name a file disk spells outside that include, or leave a covered one unnamed; and a nested mount whose root moves while running, as `acceptRoot` takes, stays placed where it was until the next start. - **Registered text:** text a host registers for a path with `registerGeneratedTextFile` is served there whatever the include says: every spelling that resolves to its path reads it, and none writes it. On a disk that folds names, a spelling in another case isn't its path, and reaches whatever file disk has there. - History from before the include stays as it was: no pass decides an entry outside it, a `materialize` of the whole mount leaves a deletion or conflict held outside it alone (one that names a directory inside the mount counts those beneath it under `passedOver`), the mount's `status` counts only what's inside, and a listing shows no directory that only such history holds. - - The framework keeps its own files (`tool-results/`, `refs/`, `background-scripts/`) in the first read-write mount without an include, and a setup whose only writable mount has one is treated as having none. The over-cap notice, the background-script log's text and the host's log line for a fetched reference it can't store say there is no writable mount without an include. What it kept in a mount before the mount had an include is outside the mount once it has one, unless the include covers it, so a `read` of a path an earlier spill note or reference gave is refused. + - The framework keeps its own files (`tool-results/`, `refs/`) in the first read-write mount without an include, and a setup whose only writable mount has one is treated as having none. The over-cap notice and the host's log line for a fetched reference it can't store say there is no writable mount without an include. What it kept in a mount before the mount had an include is outside the mount once it has one, unless the include covers it, so a `read` of a path an earlier spill note or reference gave is refused. diff --git a/changelog.d/tool-call-result-max-chars.added.md b/changelog.d/tool-call-result-max-chars.added.md index 7b3016fe7..3911b192d 100644 --- a/changelog.d/tool-call-result-max-chars.added.md +++ b/changelog.d/tool-call-result-max-chars.added.md @@ -1,5 +1,5 @@ - **Module authors:** a tool call now carries `resultMaxChars`: how long its result may be and still reach its caller whole, measured as the framework stores it (an error's text, or a success's `data` as JSON, with a content array taken as its text). A tool that pages or budgets its answer can stay under it. - A model's call carries the agent's effective inline cap: its `tool_result_inline_max_chars` setting, else the residence's `toolResultInlineMaxChars`, else the default 24,000, lowered to the strategy's per-message bound. Past it, the framework writes the result to a workspace file and stores its start, or only its start where no file can be written. A utility run through `utils` carries the same, and so does an operator's puppet call, a utility run through it included. - - A call from a running `code_execution` script carries the script's limit, 5,000,000 characters, past which a successful result the script receives is cut. A result that carries `structured` reaches the script instead as one JSON object of its content, structured content and error, measured whole; one past the limit is saved to a workspace file in its place, or, where it can't be, isn't delivered. A utility the script runs through `utils` carries the same. + - A call from a running `code_execution` script carries the longest result that reaches the script whole whatever its characters, from the script's memory. The script takes an answer of up to a third of its `memoryMaxBytes`, and a character of a result takes at most six bytes there, a control character escaped as `\u0000`. So the figure is that third, less the 80 bytes of a text answer's envelope, over six: 7,456,527 characters at the default 128 MiB, moving with the setting. On Node, past about 1.5 GiB, the host's longest string bounds it instead. A result with a `structured` value reaches the script as its content's blocks as they are, beside that value as JSON, and the figure counts neither the value nor what the blocks carry past their text, such as each further block's keys or an image's data, so a tool that returns one leaves room for them. A result nested deeper than 2,000 levels, or whose parse needs more of the script's memory than is free, doesn't reach the script whatever its length. A result that doesn't reach it is answered `ResultTooLarge`, saying the call took effect. Nothing is cut or saved to a file. A utility the script runs through `utils` carries the same figure. - A call made in process (`ModuleContext.callTool`, `executeToolCall`) carries what its caller put on it, and a utility it runs through `utils` carries what that call carried. Without it, a module holds its answer to a bound of its own, if any. - The module registry now passes every field of a call on to the module, `resultMaxChars` included. It used to pass `id`, `name`, `input`, `callerAgentName` and `callerTurnId` alone. diff --git a/changelog.d/tool-result-refused.added.md b/changelog.d/tool-result-refused.added.md index dba5427b3..dc6da6eac 100644 --- a/changelog.d/tool-result-refused.added.md +++ b/changelog.d/tool-result-refused.added.md @@ -1 +1 @@ -- **`ToolResult.refused`:** `true` when the dispatcher refused the call before it opened, as RFC-007's lifecycle refuses one, so nothing ran and the call certainly had no effect: the agent may not use the tool (its `allowedTools`), a `skip_reply` wake was refused, the call's MCPL server is absent or still connecting, the host's policy for that server denies the tool, or a fork reached outside its channel. The lifecycle refuses the call from the same place, so observers see no events for it. A check made after the call opened is a failure whose text says nothing was done, and a module's result can't claim `refused`. +- **`ToolResult.refused`:** `true` when the dispatcher refused the call before it opened, as RFC-007's lifecycle refuses one, so nothing ran and the call certainly had no effect: the agent may not use the tool (its `allowedTools`), a `skip_reply` wake was refused, the call's MCPL server is absent or still connecting, the host's policy for that server denies the tool, or a fork reached outside its channel. The lifecycle refuses the call from the same place, so observers see no events for it. A check made after the call opened is a failure whose text says nothing was done, and a module's result can't claim `refused`. A script's call refused so reaches the script as an `Error`, any other failure as a result with `isError`. diff --git a/src/code-execution/index.ts b/src/code-execution/index.ts index 062fd1f38..d213c54f2 100644 --- a/src/code-execution/index.ts +++ b/src/code-execution/index.ts @@ -1,21 +1,9 @@ /** - * Client-side programmatic tool calling (PTC) for the connectome native stack. - * - * Mirrors the surface Anthropic's server-side PTC was trained on - * (code_execution tool + tools exposed as async python functions), but runs - * the interpreter on the host and dispatches inner tool calls in-process - * through the framework's own tool routing — intermediate results never - * enter the model's context; the full trace stays on-box. + * Client-side programmatic tool calling (`code_execution`): an agent's + * scripts call its other tools in-process through the framework's own tool + * routing, so intermediate results never enter the model's context. Each + * script runs in a process of its own, in QuickJS compiled to WebAssembly + * (engine.ts), and reaches nothing but the agent's tools. */ -export { PyRunner, buildInjectedTools } from './py-runner.js'; -export type { - InjectedTool, - ExecResult, - PyRunnerOptions, - ScriptToolCallHandler, - ScriptToolResult, - BackgroundExecOptions, -} from './py-runner.js'; -export { PYTHON_RUNTIME_SOURCE } from './runtime-py.js'; export { buildCodeExecutionToolDefinition, CODE_EXECUTION_TOOL_NAME } from './tool-definition.js'; diff --git a/src/code-execution/isolate/scripts.ts b/src/code-execution/isolate/scripts.ts index a602f2360..61c9eb303 100644 --- a/src/code-execution/isolate/scripts.ts +++ b/src/code-execution/isolate/scripts.ts @@ -110,6 +110,8 @@ export interface ScriptListing { interface Script { id: string; agentName: string; + /** The code it runs. */ + code: string; run: ScriptRun; context: { origin?: unknown }; /** The turn its run call was made in, when one was: its agent's turns end it by that. */ @@ -198,6 +200,7 @@ export class AgentScripts { const script: Script = { id, agentName, + code, run: null as unknown as ScriptRun, context: options.origin === undefined ? {} : { origin: options.origin }, turnId: options.watch?.turnId, @@ -265,6 +268,12 @@ export class AgentScripts { return this.observe(script, 0, watch); } + /** The code of this agent's script by that id, as a wait or a stop would find the script; undefined when the agent has none by it. */ + codeOf(agentName: string, scriptId: string): string | undefined { + const script = this.scripts.get(scriptId); + return script && script.agentName === agentName && !script.gone ? script.code : undefined; + } + list(agentName: string): ScriptListing[] { return [...this.scripts.values()] .filter((s) => s.agentName === agentName && !s.gone) diff --git a/src/code-execution/py-runner.ts b/src/code-execution/py-runner.ts deleted file mode 100644 index 3f112ec3e..000000000 --- a/src/code-execution/py-runner.ts +++ /dev/null @@ -1,1392 +0,0 @@ -/** - * PyRunner — subprocess lifecycle + protocol driver for the client-side - * programmatic tool calling (`code_execution`) runtime. - * - * One runner per agent. The interpreter is spawned lazily on first exec, - * persists between execs (script globals survive — container-reuse - * semantics), and is reclaimed after an idle period, mirroring the managed - * runtime's ~5-minute container reclaim. - * - * This is a ROBUSTNESS boundary, not a security sandbox — same doctrine as - * GateScript: the agent already has broader host access through its tools. - * What this class guarantees is liveness: a wedged or runaway script cannot - * hang the agent turn (cancel + SIGINT -> grace -> SIGKILL -> respawn) and a crashed - * interpreter surfaces as a tool result, never as an unhandled rejection. - * - * The protocol runs on the interpreter's fds 3 (host -> py) and 4 (py -> host); - * its fd 0 is /dev/null, and the host reads its fds 1 and 2 itself (see - * OutputStream), so what a script and the commands it runs write to them - * reaches the host whatever then happens to the interpreter. - */ - -import { spawn, type ChildProcess } from 'node:child_process'; -import { buildChildEnv } from '../mcpl/transport.js'; -import { randomBytes } from 'node:crypto'; -import { closeSync, mkdirSync, mkdtempSync, openSync, rmSync, writeFileSync, writeSync } from 'node:fs'; -import { tmpdir } from 'node:os'; -import { dirname, join, resolve } from 'node:path'; -import { createInterface, type Interface } from 'node:readline'; -import type { Readable, Writable } from 'node:stream'; -import { StringDecoder } from 'node:string_decoder'; -import { boundedErrorMessage } from '../error-message.js'; -import { PYTHON_RUNTIME_SOURCE } from './runtime-py.js'; - -export interface InjectedTool { - /** Python identifier the tool is bound to (sanitized, `--` -> `__`). */ - pyName: string; - /** Exact framework tool name, sent back on tool_call ops. */ - toolName: string; -} - -export interface ExecResult { - stdout: string; - stderr: string; - returnCode: number; - /** Set when the host killed the script (deadline / abort / crash). */ - aborted?: boolean; - /** Background execs: rolling tail of interleaved stdout+stderr (for the - * crash/finish envelope; the full journal is in the log file). */ - tail?: string; -} - -/** Per-exec options for background (daemon) scripts. */ -export interface BackgroundExecOptions { - /** Absolute path for the stdout+stderr journal, which the runner appends to as the output arrives (null: tail only). */ - logPath: string | null; - /** - * Called on each wake_agent() from the script. Resolve with null to ack - * (deliver), or an error string to refuse (raises RuntimeError in-script). - * May resolve late — rate limiting backpressures inside wake_agent(). - */ - onWake: (line: number, payload: unknown) => Promise; - /** Overrides the runner's scriptTimeoutMs (background lifetime). */ - lifetimeMs: number; -} - -/** - * An inner tool call's result as the script gets it. `text` is the string - * its await returns, and the runner sets `.is_error` on that string from - * `isError`. With `structuredJson`, `text` is the JSON object a structured - * result reaches a script as, in any of its forms, and `.structured` reads - * its `structuredContent`, None for a form that holds none, such as one past - * the size cap; without it, `.structured` is None. - */ -export interface ScriptToolResult { - text: string; - isError: boolean; - structuredJson?: boolean; -} - -/** - * Resolves inner tool calls. Must never reject — map errors to results; a - * rejection reaches the script as an `Error: …` result. A plain string is - * the result's text with nothing said about it, so the script's - * `.is_error` and `.structured` are None. - */ -export type ScriptToolCallHandler = ( - toolName: string, - args: Record, -) => Promise; - -export interface PyRunnerOptions { - /** Python to spawn (default 'python3'). A wrapper must pass fds 3 and 4 on, where the protocol runs (see CodeExecutionConfig). */ - pythonPath?: string; - /** Per-tool-call timeout surfaced as TimeoutError inside the script. */ - toolCallTimeoutMs?: number; - /** Whole-script deadline; exceeded -> cancel, grace, kill. */ - scriptTimeoutMs?: number; - /** How long a script gets to stop after its deadline before the interpreter is killed (default 10s). */ - cancelGraceMs?: number; - /** Idle interpreter reclaim (state lost), mirroring container reclaim. */ - idleReclaimMs?: number; - onToolCall: ScriptToolCallHandler; - /** Log prefix, typically the agent name. */ - label?: string; - /** Variables for the interpreter beyond what it inherits, such as - * PYTHONPATH or a venv's. It inherits only what stdio MCPL children do - * (CHILD_ENV_ALLOWLIST and LC_*, CA bundles and proxy variables among - * them, and a proxy URL can carry credentials), then these, then - * PYTHONUNBUFFERED and PYTHONIOENCODING, which the runner sets last. - * Like the allowlist, this limits what the model's code inherits, not - * what a process running as the same user can read. */ - env?: Record; - /** Pass the host's whole environment instead of the allowlist, as before - * `env` existed. That hands every host variable, provider keys included, - * to the model's code and to every subprocess a script starts; listing - * what scripts need in `env` is the safer route. */ - inheritEnv?: boolean; -} - -const DEFAULT_TOOL_CALL_TIMEOUT_MS = 270_000; -const DEFAULT_SCRIPT_TIMEOUT_MS = 600_000; -const DEFAULT_IDLE_RECLAIM_MS = 300_000; -const CANCEL_GRACE_MS = 10_000; -/** After the deadline, SIGINT is repeated at this interval until the script reports or is killed. */ -const INTERRUPT_REPEAT_MS = 200; -/** Each of a script's stdout and stderr is kept up to this many characters in its result. */ -const OUTPUT_CAP_CHARS = 2_000_000; -/** A background script's journal stops growing past this many bytes. */ -const LOG_FILE_CAP_BYTES = 50_000_000; -/** How much of a background script's output its result keeps, for the envelope that tells the agent how it ended. */ -const TAIL_CHARS = 4000; -/** - * How long an interpreter that exited unexpectedly gets for the last of what it - * wrote, its output and its messages, to arrive when something still holds its - * pipes open, such as a command its script left running; otherwise their end - * says it has all arrived. - */ -const EXIT_OUTPUT_GRACE_MS = 200; -/** How long a message failing to reach an interpreter waits for the interpreter's own exit before the host ends it as one that stopped reading. */ -const UNREACHABLE_GRACE_MS = 200; -/** How much of what an interpreter wrote before it failed to start, or crashed, goes into the message and the host's log, and how much of each piece of output with no script running goes into the log. */ -const LAST_WORDS_CHARS = 2000; -/** - * How much of the host's log a pipe's output takes while no script of its - * interpreter runs, from the start of its last exec, or its own start: lines - * of up to 65,536 characters in all, counted as UTF-16 code units, each piece - * cut at LAST_WORDS_CHARS, enough to see what a command a script left running - * is doing. The rest is dropped. - */ -const IDLE_LOG_CHARS = 65_536; -/** - * How fast the host reads the pipes of an interpreter it has torn down, which - * a command a script left running may still hold. It goes on reading, so such - * a command isn't ended by a broken pipe, and one writing far under this, as a - * dev server's log does, never waits; one that writes without end slows to - * this, rather than costing the host a core for as long as it runs. A live - * interpreter's pipes are read at full speed, so what such a command writes - * between scripts is read as it's written, and the idle reclaim bounds how - * long that lasts, unless a host turns the reclaim off. - */ -const IDLE_READ_BYTES_PER_SECOND = 1_048_576; -/** The runtime's exit when the host's channel, fds 3 and 4, didn't reach it: the host's configuration, which calling again doesn't change. 78 is sysexits' EX_CONFIG, which Python itself never exits with. */ -const HOST_CHANNEL_MISSING_EXIT = 78; -/** - * The runtime's exit when it ends itself (runtime-py.ts _fatal), after a line - * that, when a script took one of its fds, says which. Read with that line, so - * a script that prints such a line and ends any other way isn't taken for it. - */ -const RUNTIME_FATAL_EXIT = 70; - -/** The longest delay Node's timers honour (~24.8 days); a longer one fires after ~1 ms. */ -export const MAX_TIMER_MS = 2_147_483_647; - -/** A time limit for a message: "45s", "10 min", "2.5 h". */ -export function formatLimit(ms: number): string { - if (ms < 120_000) return `${Math.round(ms / 1000)}s`; - if (ms < 7_200_000) return `${Math.round(ms / 6_000) / 10} min`; - return `${Math.round(ms / 360_000) / 10} h`; -} - -/** `line` after `text`, on a line of its own: a text that doesn't end with a newline gets one first. */ -function onItsOwnLine(text: string, line: string): string { - return (text === '' || text.endsWith('\n') ? text : `${text}\n`) + line; -} - -/** Add a line of the runner's own to a background script's journal, on a line of its own. */ -function writeLine(journal: Journal, line: string): void { - journal.write(`${journal.tail === '' || journal.tail.endsWith('\n') ? '' : '\n'}${line}\n`); -} - -/** Whether the child is still running: not exited, not ended by a signal, and not killed by us. */ -function alive(child: ChildProcess): boolean { - return child.exitCode === null && child.signalCode === null && !child.killed; -} - -/** - * All of `text` at the end of the file: a write the kernel cuts short, as at a - * file-size limit, goes on from where it stopped, and one that fails throws. - */ -function writeAll(fd: number, text: string): void { - const data = Buffer.from(text); - for (let at = 0; at < data.length; ) { - const n = writeSync(fd, data, at); - // POSIX has a regular file take at least a byte of a write or fail it; a - // write that took none would otherwise hold the host's event loop here. - if (n === 0) throw new Error('the file took none of a write'); - at += n; - } -} - -/** - * A background script's journal: the log file the agent can read with its own - * tools while the script runs, and the tail its result carries. It's written - * synchronously, from the host's event loop, so a workspace whose writes block - * holds the host while they do. - */ -class Journal { - private fd: number | null = null; - private written = 0; - private capped = false; - tail = ''; - - constructor( - path: string | null, - private readonly log: (text: string) => void, - ) { - if (!path) return; - try { - mkdirSync(dirname(path), { recursive: true }); - this.fd = openSync(path, 'a'); - } catch (err) { - log(`cannot open log file: ${err instanceof Error ? err.message : String(err)}`); - } - } - - write(text: string): void { - if (text === '') return; - this.tail = (this.tail + text).slice(-TAIL_CHARS); - if (this.fd === null || this.capped) return; - const bytes = Buffer.byteLength(text); - try { - if (this.written + bytes > LOG_FILE_CAP_BYTES) { - this.capped = true; - writeAll(this.fd, `\n[log capped at ${LOG_FILE_CAP_BYTES} bytes]\n`); - } else { - writeAll(this.fd, text); - this.written += bytes; - } - } catch (err) { - // A journal that can't be written to still keeps its tail. - this.log(`cannot write the log file any more: ${err instanceof Error ? err.message : String(err)}`); - this.close(); - } - } - - close(): void { - if (this.fd === null) return; - try { - closeSync(this.fd); - } catch { - // already gone - } - this.fd = null; - } -} - -const NOTHING = Buffer.alloc(0); - -/** The first n UTF-16 code units of s, short of a surrogate pair the cut would split. */ -function cutWhole(s: string, n: number): string { - if (s.length <= n) return s; - const cut = s.slice(0, n); - return /[\uD800-\uDBFF]$/.test(cut) ? cut.slice(0, -1) : cut; -} - -/** - * One of the interpreter's output pipes, its fd 1 or fd 2, as the host reads it. - * While an exec runs, what arrives before that exec's end mark is the exec's: - * kept for its result, up to OUTPUT_CAP_CHARS, or written to a background - * script's journal. The mark itself is cut out. What arrives while no exec of - * its interpreter runs, such as what a command a script left running writes, - * goes to the host's log as it arrives, both while the interpreter lives and - * after it has ended, up to IDLE_LOG_CHARS between scripts; once the runner - * has torn the interpreter down, it's read at IDLE_READ_BYTES_PER_SECOND at - * most (retire). - */ -export class OutputStream { - /** Every process holding the pipe has closed it, so nothing more will come. */ - ended = false; - /** The running exec's mark has arrived, so all of its output on this pipe has. */ - marked = false; - /** The end of what arrived while no exec ran: an interpreter that failed to start says why here. */ - lastIdle = ''; - /** Whether lastIdle lost its beginning to the LAST_WORDS_CHARS cut. */ - lastIdleCut = false; - private mark: Buffer | null = null; - /** The end of a chunk that could be the start of a mark the next chunk completes. */ - private carry: Buffer = NOTHING; - private readonly decoder = new StringDecoder('utf8'); - private text = ''; - private truncated = false; - private journal: Journal | null = null; - private onEnd: (() => void)[] = []; - /** How many characters of log lines output with no script running has taken since this pipe's last exec began: IDLE_LOG_CHARS at most. */ - private idleLogged = 0; - /** Set once the runner has torn its interpreter down: nothing reads this pipe for an exec again. */ - private retired = false; - /** The wait that holds a retired pipe back (slow). */ - private resumer: ReturnType | null = null; - /** Bytes read since the last wait was set, which the next one charges (slow). */ - private owed = 0; - - constructor( - private readonly name: 'stdout' | 'stderr', - private readonly pipe: Readable, - private readonly log: (text: string) => void, - private readonly onMarked: () => void, - ) { - pipe.on('data', (chunk: Buffer) => this.push(chunk)); - const ended = () => { - if (this.ended) return; - this.ended = true; - for (const fn of this.onEnd.splice(0)) fn(); - }; - pipe.on('end', ended); - pipe.on('close', ended); - pipe.on('error', (err) => { - log(`reading the python runtime's ${name} failed: ${err.message}`); - ended(); - }); - } - - /** Begin an exec's share of the pipe, which its mark ends. */ - begin(mark: Buffer, journal: Journal | null): void { - // A character that output with no script running left half-written is - // that output's, not this exec's. - this.logIdle(this.decoder.end()); - // The stretch with no script running that follows this exec logs a share - // of its own. - this.idleLogged = 0; - this.mark = mark; - this.marked = false; - this.journal = journal; - this.text = ''; - this.truncated = false; - } - - /** The exec's share as it stands, with a note if it was cut at the cap; what arrives next goes to the log. */ - take(): string { - if (this.mark !== null) { - // Settled before its mark came (a kill, an abort or a crash): what arrived counts. - this.keep(this.carry); - this.carry = NOTHING; - this.keep(this.decoder.end()); - } - const text = this.text + (this.truncated ? `\n[${this.name} truncated at ${OUTPUT_CAP_CHARS} chars]` : ''); - this.mark = null; - this.journal = null; - this.text = ''; - this.truncated = false; - return text; - } - - /** Run fn once the pipe has ended (at once if it has). */ - whenEnded(fn: () => void): void { - if (this.ended) fn(); - else this.onEnd.push(fn); - } - - private push(chunk: Buffer): void { - let bytes = this.carry.length > 0 ? Buffer.concat([this.carry, chunk]) : chunk; - this.carry = NOTHING; - if (this.mark !== null) { - const at = bytes.indexOf(this.mark); - if (at < 0) { - // Hold back only an end that could be the mark's start, so a - // background script's journal keeps up with its output. - const hold = markStartAtEnd(bytes, this.mark); - this.keep(bytes.subarray(0, bytes.length - hold)); - this.carry = bytes.subarray(bytes.length - hold); - return; - } - this.keep(bytes.subarray(0, at)); - this.keep(this.decoder.end()); - bytes = bytes.subarray(at + this.mark.length); - this.mark = null; - this.marked = true; - this.onMarked(); - } - if (bytes.length > 0) { - const text = this.decoder.write(bytes); - const idle = this.lastIdle + text; - if (idle.length > LAST_WORDS_CHARS) { - this.lastIdleCut = true; - // A cut between a surrogate pair's halves would leave its low half, which reads as U+FFFD. - const kept = idle.slice(-LAST_WORDS_CHARS); - this.lastIdle = /^[\uDC00-\uDFFF]/.test(kept) ? kept.slice(1) : kept; - } else { - this.lastIdle = idle; - } - this.logIdle(text); - if (this.retired) this.slow(bytes.length); - } - } - - /** The runner has torn its interpreter down: read what still comes at IDLE_READ_BYTES_PER_SECOND at most. */ - retire(): void { - this.retired = true; - } - - /** - * Log output that arrived while no exec of this pipe's interpreter ran, each - * piece up to LAST_WORDS_CHARS, in lines of IDLE_LOG_CHARS in all since the - * pipe's last exec began, the last cut to fit. Each line is charged its - * whole length, so many small writes can't take more of the log. - */ - private logIdle(text: string): void { - const shown = text.trimEnd(); - if (this.idleLogged >= IDLE_LOG_CHARS || shown.trim() === '') return; - const head = `${this.name} while its interpreter runs no script: `; - const kept = cutWhole(shown, LAST_WORDS_CHARS); - const piece = kept.length < shown.length ? `${kept} [… ${shown.length - kept.length} more characters]` : shown; - const fits = IDLE_LOG_CHARS - this.idleLogged - head.length; - if (fits > 0) { - const line = head + cutWhole(piece, fits); - this.idleLogged += line.length; - this.log(line); - } - if (fits <= piece.length) { - this.idleLogged = IDLE_LOG_CHARS; - this.log(`${head}past ${IDLE_LOG_CHARS} characters of the log, so the host drops what follows while its interpreter runs no script`); - } - } - - /** - * Hold the pipe back for as long as these bytes take at - * IDLE_READ_BYTES_PER_SECOND, then read on. Something else can resume the - * pipe during a wait, as Node does with a child's stdio when the child - * exits, so each piece holds it back again, and what came during a wait - * waits its own time once that wait ends. - */ - private slow(bytes: number): void { - this.pipe.pause(); - this.owed += bytes; - if (this.resumer === null) this.wait(); - } - - private wait(): void { - const ms = Math.ceil((this.owed * 1000) / IDLE_READ_BYTES_PER_SECOND); - this.owed = 0; - this.resumer = setTimeout(() => { - this.resumer = null; - if (this.owed > 0) this.wait(); - else this.pipe.resume(); - }, ms); - // A command left running doesn't keep the host's process alive. - this.resumer.unref(); - } - - /** Keep what arrived for the running exec. */ - private keep(part: Buffer | string): void { - const text = typeof part === 'string' ? part : this.decoder.write(part); - if (text === '') return; - if (this.journal) { - this.journal.write(text); - return; - } - if (this.truncated) return; - const room = OUTPUT_CAP_CHARS - this.text.length; - if (text.length <= room) { - this.text += text; - return; - } - this.text += text.slice(0, room); - this.truncated = true; - } -} - -/** How many bytes at the end of `bytes` could be the start of `mark`. */ -function markStartAtEnd(bytes: Buffer, mark: Buffer): number { - for (let n = Math.min(bytes.length, mark.length - 1); n > 0; n--) { - if (bytes.subarray(bytes.length - n).equals(mark.subarray(0, n))) return n; - } - return 0; -} - -/** Run fn once the readable has ended (at once if it has). */ -function whenReadableEnded(stream: Readable, fn: () => void): void { - if (stream.readableEnded || stream.destroyed) { - fn(); - return; - } - let called = false; - const once = () => { - if (called) return; - called = true; - fn(); - }; - stream.once('end', once); - stream.once('close', once); -} - -/** - * Run fn once an exited interpreter's output pipes and its messages' pipe have - * all ended, which says the last of what it wrote has arrived, or else after - * EXIT_OUTPUT_GRACE_MS, if something, such as a command its script left - * running, still holds them, and one more round of the event loop's reads, so - * what had already arrived is read first even when the host's loop was busy - * past the grace. - */ -function afterOutputEnds(stdout: OutputStream, stderr: OutputStream, messages: Readable | null, fn: () => void): void { - let done = false; - const finish = () => { - if (done) return; - done = true; - clearTimeout(timer); - fn(); - }; - const timer = setTimeout(() => setImmediate(finish), EXIT_OUTPUT_GRACE_MS); - stdout.whenEnded(() => stderr.whenEnded(() => (messages ? whenReadableEnded(messages, finish) : finish()))); -} - -/** - * What a script stopped at its time limit is told after why: that the tool - * calls it made keep their effects, and whether a longer limit can help, - * which only the deployment's ceiling for its kind of script can say. A - * foreground script at its ceiling is offered background mode too, when its - * caller passes `backgroundCeilingMs` (the framework does for an agent that - * could start a background script as the script starts) and a background - * script may run longer. - */ -function limitRemedy(limitMs: number, ceilingMs: number | undefined, background: boolean, backgroundCeilingMs?: number): string { - const effects = 'Tool calls it had already made keep their effects, so check them before running it again.'; - if (ceilingMs === undefined) return effects; - if (limitMs < ceilingMs) return `${effects} To give it longer, ${background ? 'start it with' : 'pass'} time_limit_ms up to ${ceilingMs} (${formatLimit(ceilingMs)}).`; - if (background) return `${effects} ${formatLimit(limitMs)} is the longest a background script runs here; starting it again runs it from the top.`; - const inBackground = backgroundCeilingMs !== undefined && backgroundCeilingMs > limitMs - ? `, or run it with background: true, where a script can run up to ${formatLimit(backgroundCeilingMs)}` : ''; - return `${effects} ${formatLimit(limitMs)} is the longest a script can run here, so split the work across shorter scripts${inBackground}.`; -} - -/** - * Why a script didn't run when its interpreter didn't start. A spawn that - * found nothing it could run (ENOENT, EACCES) fails the same way until the - * host changes, so that one says code_execution can't run here rather than - * to try again. A failure before the spawn, while creating or writing the - * runtime's files, is about those files rather than the interpreter, and - * keeps Node's own text, which names the path for an open or a mkdtemp. An - * interpreter that exits as it starts passes on up to the last LAST_WORDS_CHARS - * of what it wrote to stderr, from the end, where a traceback says what failed, - * and with "…" where they're cut; when the runtime itself exits so because - * the host's channel didn't reach it (HOST_CHANNEL_MISSING_EXIT), its own - * words say who must act and that calling again fails the same way, so they - * end the text. - */ -function startFailure(pythonPath: string, err: unknown): string { - const { code, syscall, exitCode, said } = (err ?? {}) as { code?: unknown; syscall?: unknown; exitCode?: unknown; said?: unknown }; - const spawning = typeof syscall === 'string' && syscall.startsWith('spawn'); - if (spawning && (code === 'ENOENT' || code === 'EACCES')) { - const until = code === 'ENOENT' ? 'installs it or points this host at one' : 'makes it executable or points this host at another'; - return `script didn't run: this host can't start its Python interpreter, ${pythonPath} (${code}), so code_execution can't run scripts here until whoever runs this host ${until}. Calling it again before then fails the same way.`; - } - if (typeof syscall === 'string' && !spawning) { - return `script didn't run: this host couldn't create or write the files its Python runtime starts from (${boundedErrorMessage(err)}), so none of the script ran. Run it again; if it fails the same way, ask whoever runs this host.`; - } - const wrote = typeof said === 'string' ? said : ''; - // The runtime's own words say who must act, and that calling again fails the same way. - if (exitCode === HOST_CHANNEL_MISSING_EXIT && wrote !== '') { - return `script didn't run: the Python interpreter, ${pythonPath}, exited as it started, saying: ${wrote}`; - } - const why = typeof code === 'string' ? `couldn't be started (${code})` : boundedErrorMessage(err); - return `script didn't run: the Python interpreter, ${pythonPath}, ${why}, so none of the script ran. Run it again; if it fails the same way, ask whoever runs this host.` - + (wrote === '' ? '' : ` It wrote to stderr: ${wrote}`); -} - -/** Why a script stopped when its interpreter stopped reading the host's messages, so that the host ended it. */ -function stoppedReading(background: boolean): string { - return "script stopped: the Python interpreter stopped reading the host's messages, so the host ended it and the rest of the script didn't run" - + `${background ? '' : ', and variables from earlier scripts are gone'}. Tool calls it had already made keep their effects, ` - + 'so check them before you run it again in a new interpreter. If it stops the same way, check whether the script closes, ' - + "shuts down or replaces file descriptor 3, where the host's messages arrive; if not, ask whoever runs this host."; -} - -/** The signals faulthandler writes a traceback for (runtime-py.ts), which a crash ends a process with. */ -const CRASH_SIGNALS: ReadonlySet = new Set(['SIGSEGV', 'SIGABRT', 'SIGBUS', 'SIGFPE', 'SIGILL']); - -/** How faulthandler's traceback for a crash's signal begins. */ -const CRASH_TRACEBACK = 'Fatal Python error: '; - -/** - * Why a script stopped when its interpreter exited under it, and what running it - * again does; or, when the script's result came before the exit (ended), that - * the script ran to its end and what of its output the exit may have cost. - */ -function interpreterExit(exitCode: number | null, signal: NodeJS.Signals | null, background: boolean, said = '', ended = false): string { - const how = signal ? `was ended by ${signal}` : `exited with code ${exitCode}`; - // The runtime ends itself (_fatal) with code 70 after a line saying which of its fds a script took, - // or shut down the socket under. The line reaches the host unless the script took both the runtime's - // copy of fd 2 and fd 2 itself, or shut down the socket under them, and it's read from what the result - // keeps, which past a stream's cap or a journal's tail lacks it. - const taken = exitCode === RUNTIME_FATAL_EXIT ? fdTakenFromRuntime(said) : null; - // After a crash's signal, faulthandler's traceback shows the call that crashed, in native code or the - // script's own os.abort(), when it reached what the result keeps: a script can disable faulthandler, - // and a stream's cap or a journal's tail can lose it. Its first words are looked for anywhere, not - // only at a line's start: faulthandler doesn't start a line, so after output that didn't end one, - // such as a progress bar's, they follow on that line. SIGKILL is the out-of-memory killer's; any - // other signal, the script can send itself. - const check = taken - ? `check whether the script closes, shuts down or replaces ${taken}, as the runtime's line above says` - : signal && CRASH_SIGNALS.has(signal) - ? said.includes(CRASH_TRACEBACK) - ? 'check the traceback above for the call that crashed in native code, such as an extension module the script uses' - : 'check whether native code the script calls crashes, such as an extension module it uses' - : `check whether the script itself ends the process, for example ${ - signal === 'SIGKILL' ? 'by using too much memory' : signal ? 'with os.kill()' : 'with os._exit()'}`; - const gone = background ? '' : ', and variables from earlier scripts are gone'; - if (ended) { - return `script ended, but the Python interpreter ${how} before all of its output reached the host, so some of it may be missing${gone}. ` - + `If it happens again, ${check}; if not, ask whoever runs this host.`; - } - return `script stopped: the Python interpreter ${how} while it ran, so the rest of the script didn't run${gone}. ` - + 'Tool calls it had already made keep their effects, so check them before you run it again in a new interpreter. ' - + `If it stops the same way, ${check}; if not, ask whoever runs this host.`; -} - -/** The file descriptor the runtime's last line of its own says a script took from it (runtime-py.ts _lost), if it says so. */ -function fdTakenFromRuntime(said: string): string | null { - let taken: string | null = null; - for (const m of said.matchAll(/\[pytc-runtime\] [^\n]*: a script closed, shut down or replaced ((?:the runtime's copy of )?fd \d)[^\n]*; exiting/g)) { - taken = m[1]; - } - return taken; -} - -interface PendingExec { - id: string; - resolve: (result: ExecResult) => void; - deadlineTimer: ReturnType | null; - killTimer: ReturnType | null; - /** Repeats the deadline SIGINT (see interruptChild). */ - interruptTimer?: ReturnType; - settled: boolean; - /** Set once the deadline fired: the result then says the script ran out of time. */ - deadlineMs: number | null; - /** The longest limit this exec could have had (exec's opts): whether a longer one would help. */ - ceilingMs: number | undefined; - /** For a foreground exec, the longest a background script may run (exec's opts): whether background mode would help. */ - backgroundCeilingMs: number | undefined; - /** A background exec has its interpreter to itself, so no earlier script's state is lost with it. */ - background: boolean; - /** Set once the script went to the interpreter. Before that, the exec is - * waiting for the interpreter to start, and an interpreter that exits then - * failed to start (see exec). */ - sent: boolean; - /** A background exec's journal, which its output goes to instead of its result. */ - journal: Journal | null; - /** The script's return code, once the interpreter has reported it; the exec - * settles when its output's end marks have arrived too. */ - returnCode: number | null; - /** Set when the host settled the exec itself (stopPending), rather than its result and marks. */ - stopped?: true; -} - -/** Interpreters started in this process, by every runner: each one's number. */ -let interpretersStarted = 0; - -/** - * The interpreter to spawn. A bare name is looked up on PATH. A path with a - * separator is resolved against the host's working directory, where it - * resolved before the interpreter got a working directory of its own: spawn - * would resolve it against the runtime directory instead. - */ -function spawnablePath(pythonPath: string): string { - const hasSeparator = pythonPath.includes('/') || (process.platform === 'win32' && pythonPath.includes('\\')); - return hasSeparator ? resolve(pythonPath) : pythonPath; -} - -export class PyRunner { - private readonly pythonPath: string; - private readonly toolCallTimeoutMs: number; - private readonly scriptTimeoutMs: number; - private readonly cancelGraceMs: number; - private readonly idleReclaimMs: number; - private readonly onToolCall: ScriptToolCallHandler; - private readonly label: string; - private readonly envOptions: { env?: Record; inheritEnv?: boolean }; - - private onWake: ((line: number, payload: unknown) => Promise) | null = null; - private child: ChildProcess | null = null; - private childReady: Promise | null = null; - /** The interpreter's fd 3, where the host's messages go. */ - private protoIn: Writable | null = null; - /** Ends the interpreter whose fd 3 a message couldn't reach (see ensureChild). */ - private unreachable: ((why: string) => void) | null = null; - /** Reads the interpreter's messages from its fd 4. */ - private reader: Interface | null = null; - /** The interpreter's stdout and stderr, as the host reads them. */ - private out: OutputStream | null = null; - private err: OutputStream | null = null; - private runtimeDir: string | null = null; - private pending: PendingExec | null = null; - private idleTimer: ReturnType | null = null; - private execCounter = 0; - /** The number of this runner's latest interpreter (interpretersStarted). */ - private interpreterNumber = 0; - private disposed = false; - - constructor(options: PyRunnerOptions) { - this.pythonPath = options.pythonPath ?? 'python3'; - this.toolCallTimeoutMs = options.toolCallTimeoutMs ?? DEFAULT_TOOL_CALL_TIMEOUT_MS; - this.scriptTimeoutMs = options.scriptTimeoutMs ?? DEFAULT_SCRIPT_TIMEOUT_MS; - this.cancelGraceMs = options.cancelGraceMs ?? CANCEL_GRACE_MS; - this.idleReclaimMs = options.idleReclaimMs ?? DEFAULT_IDLE_RECLAIM_MS; - this.onToolCall = options.onToolCall; - this.label = options.label ?? 'pytc'; - // The interpreter runs model-authored code: the same env discipline as an - // MCPL connector child (#175), for a child that deserves it more. Stored - // as options, not a snapshot — the env is rebuilt at every spawn so a - // respawn after idle reclaim sees current host values, as the old - // per-spawn `{ ...process.env }` did. - this.envOptions = { env: options.env, inheritEnv: options.inheritEnv }; - } - - get busy(): boolean { - return this.pending !== null; - } - - /** - * The live interpreter, whose state (variables, imports, functions) the - * scripts run in it share: a number no other interpreter in this process - * has had, from any runner, or null while none is running, before the - * first script or once it was reclaimed, killed or has exited. - */ - get interpreter(): number | null { - return this.child && this.child.exitCode === null && !this.child.killed ? this.interpreterNumber : null; - } - - /** - * Run one script. Tools are (re-)injected before every exec so the - * interpreter always reflects the current tool surface (list_changed etc.). - * Never rejects: every failure mode resolves to an ExecResult. - * - * With `background` options the exec is a daemon: output journals to the - * log file, wake_agent() is available in-script, and the deadline is the - * background lifetime. A background runner should be DEDICATED to that one - * script (the framework creates one per background script). - * - * `opts.deadlineMs` replaces the runner's scriptTimeoutMs for this exec (a - * per-call time limit); a background exec uses its `lifetimeMs` instead. - * `opts.ceilingMs` is the longest limit the deployment allows this kind of - * script: a script stopped at its limit is offered a longer one only below - * it. Without it, the stop doesn't say whether a longer limit would help. - * `opts.backgroundCeilingMs`, for a foreground exec, is the longest a - * background script may run: one stopped at its ceiling is offered - * background mode when that is longer. - */ - async exec( - code: string, - tools: InjectedTool[], - background?: BackgroundExecOptions, - opts?: { deadlineMs?: number; ceilingMs?: number; backgroundCeilingMs?: number }, - ): Promise { - if (this.disposed) { - return { stdout: '', stderr: 'code_execution runner disposed', returnCode: 1, aborted: true }; - } - if (this.busy) { - return { - stdout: '', - stderr: "script didn't run: another code_execution script of yours is already running, and they run one at a time. " - + 'Run this one again once that one has finished, or combine the two into one script.', - returnCode: 1, - aborted: true, - }; - } - this.clearIdleTimer(); - - // The exec claims the runner before the await: a same-tick second exec - // sees it busy, and abort() or dispose() while the interpreter starts - // settles this exec, whose script then never runs. - let resolveResult!: (result: ExecResult) => void; - const settled = new Promise((resolve) => { - resolveResult = resolve; - }); - const pending: PendingExec = { - id: `e${++this.execCounter}`, - resolve: resolveResult, - deadlineTimer: null, - killTimer: null, - settled: false, - deadlineMs: null, - ceilingMs: opts?.ceilingMs, - backgroundCeilingMs: opts?.backgroundCeilingMs, - background: background !== undefined, - sent: false, - journal: null, - returnCode: null, - }; - this.pending = pending; - try { - await this.ensureChild(); - } catch (err) { - // Settled already when an abort or dispose reclaimed the interpreter - // as it started; another exec may have claimed the runner since. - if (!pending.settled) { - this.settlePending({ - stdout: '', - stderr: startFailure(this.pythonPath, err), - returnCode: 1, - aborted: true, - }); - this.reclaim('spawn-failed'); - } - return settled; - } - if (pending.settled) return settled; - - const execId = pending.id; - // Clamped: a deadline past Node's timer range would fire at once instead of never. - const deadlineMs = Math.min(MAX_TIMER_MS, background?.lifetimeMs ?? opts?.deadlineMs ?? this.scriptTimeoutMs); - this.onWake = background?.onWake ?? null; - pending.deadlineTimer = setTimeout(() => { - pending.deadlineMs = deadlineMs; - // Deadline: ask politely first (script sees CancelledError and its - // exec_result still flows back), then kill on unresponsiveness. - this.send({ op: 'cancel', id: execId, reason: 'deadline' }); - // The cancel lands only when the script awaits. Blocking code (time.sleep, a - // busy loop, a blocking read) never does, so interrupt it as well: the runtime - // raises KeyboardInterrupt in the script's own code, or schedules the - // cancellation of a script that is waiting. Repeated, because a waiting script - // can resume into blocking code before that cancellation runs; the runtime - // ignores the repeats once the script was interrupted or cancelled. - this.interruptChild(); - pending.interruptTimer = setInterval(() => this.interruptChild(), INTERRUPT_REPEAT_MS); - pending.killTimer = setTimeout(() => { - pending.deadlineMs = null; // this message already says why - this.stopPending( - `script stopped: it reached its ${formatLimit(deadlineMs)} time limit and did not respond, so it was killed` + - (pending.background ? '. ' : ' and the interpreter restarted (variables from earlier scripts are gone). ') + - limitRemedy(deadlineMs, pending.ceilingMs, pending.background, pending.backgroundCeilingMs), - ); - this.reclaim('deadline-kill'); - }, this.cancelGraceMs); - }, deadlineMs); - // A day-scale background deadline must not hold the process open. - if (background) pending.deadlineTimer.unref?.(); - - // What the script writes, up to its mark, is this exec's: a background - // script's goes to its journal. - const mark = Buffer.from(`\x1epytc-end-${randomBytes(16).toString('hex')}\x1e`); - pending.journal = background ? new Journal(background.logPath, (text) => this.log(text)) : null; - this.out?.begin(mark, pending.journal); - this.err?.begin(mark, pending.journal); - pending.sent = true; - this.send({ - op: 'init', - tools: tools.map((t) => ({ py_name: t.pyName, tool_name: t.toolName })), - call_timeout_s: Math.round(this.toolCallTimeoutMs / 1000), - ...(background ? { background: true } : {}), - }); - this.send({ op: 'exec', id: execId, code, mark: mark.toString('utf8') }); - - const result = await settled; - - this.onWake = null; - if (!background) this.armIdleTimer(); - return result; - } - - /** - * Abort the runner's script (its turn ended or is reset to run again, or the - * agent was reset or stopped), whether it's running or still waiting for its - * interpreter to start; a waiting script then never runs. The exec settles - * at once, and the interpreter is reclaimed, a starting one too: after an - * abort its state is suspect (a script died midway), and the next exec gets - * a fresh one. - */ - abort(reason: string): void { - if (!this.pending) return; - this.send({ op: 'cancel', id: this.pending.id, reason }); - this.stopPending(`script aborted by host: ${reason}`); - this.reclaim(`abort: ${reason}`); - } - - dispose(): void { - if (this.disposed) return; - this.disposed = true; - this.stopPending('code_execution runner disposed'); - this.reclaim('dispose'); - } - - // ------------------------------------------------------------------------- - - private async ensureChild(): Promise { - if (this.child && alive(this.child)) { - return this.childReady ?? Promise.resolve(); - } - this.teardownChild(); - - // Absolute, since the interpreter runs in it: from a relative TMPDIR, - // runtime.py's path would otherwise resolve against the directory itself. - this.runtimeDir = resolve(mkdtempSync(join(tmpdir(), 'af-pytc-'))); - const runtimePath = join(this.runtimeDir, 'runtime.py'); - writeFileSync(runtimePath, PYTHON_RUNTIME_SOURCE, 'utf8'); - - const child = spawn(spawnablePath(this.pythonPath), [runtimePath], { - // fd 0 is /dev/null, the host reads fds 1 and 2, and the protocol has 3 and 4. - stdio: ['ignore', 'pipe', 'pipe', 'pipe', 'pipe'], - // The runtime directory, not the host's working directory: a script's - // relative paths land here, and go at teardown, rather than beside the - // host's .env and data. An absolute path still reaches the host's files. - cwd: this.runtimeDir, - // Set last, over a declared env. The host reads fds 1 and 2 as UTF-8. - // The runtime's channel and a script's writers say UTF-8 themselves - // (runtime-py.ts), but other Python writing there takes its encoding and - // buffering from these: the interpreter's sys.stdout and sys.stderr - // between scripts, which a thread a script left running prints through, - // and any Python a script starts. So what it writes reaches the host as - // it's written, and in UTF-8, where Python before 3.15 on Windows writes - // a pipe in the ANSI code page unless told otherwise; a PYTHONUTF8 in - // the host process's own environment reaches the interpreter only with - // inheritEnv: true or declared in env. - env: { ...buildChildEnv(this.envOptions), PYTHONUNBUFFERED: '1', PYTHONIOENCODING: 'utf-8' }, - }); - this.child = child; - this.interpreterNumber = ++interpretersStarted; - const protoIn = child.stdio[3] as Writable; - const protoOut = child.stdio[4] as Readable; - this.protoIn = protoIn; - const stdout = new OutputStream('stdout', child.stdout as Readable, (text) => this.log(text), () => this.settleIfComplete(child)); - const stderr = new OutputStream('stderr', child.stderr as Readable, (text) => this.log(text), () => this.settleIfComplete(child)); - this.out = stdout; - this.err = stderr; - protoOut.on('error', (e) => this.log(`reading the python runtime's messages failed: ${e.message}`)); - - // An interpreter that no longer reads the host's messages, as after a - // script closes file descriptor 3, could go on running, and every later - // message to it is lost: its exec would wait out its time limit. So when - // a message can't reach it, it's ended, and its exit settles the exec. Such - // a write fails after write() returns: the pipe refuses it, or has already - // ended, its far end closed. Its error, which with no listener would be - // thrown as an uncaught exception, is heard here too. An interpreter that - // died on its own breaks the pipe the same way, as by dying with a message - // unread, and its exit can be seen after the break: so the kill waits - // UNREACHABLE_GRACE_MS and one more round of reads, and an interpreter that - // exited by then is left to its own exit, which says what happened. - let inputFailed = false; - let killing = false; - const unreachable = (why: string) => { - // One the host has torn down already ended its pipe on purpose. - if (child !== this.child || killing) return; - this.log(`messages to the python runtime failed: ${why}`); - killing = true; - setTimeout( - () => - setImmediate(() => { - if (child === this.child && alive(child) && child.pid !== undefined) { - inputFailed = true; - child.kill('SIGKILL'); - } - }), - UNREACHABLE_GRACE_MS, - ); - }; - this.unreachable = unreachable; - protoIn.on('error', (e) => unreachable(e.message)); - - child.on('error', (err) => { - // Spawn failure lands here (e.g. python3 missing); readiness rejects. - console.error(`[pytc:${this.label}] python process error: ${err.message}`); - }); - - child.on('exit', (exitCode, signal) => { - // An exec still waiting for the interpreter is settled by exec, as a - // runtime that failed to start. - const pending = this.pending; - if (!pending?.sent) { - // One still starting is reported by its exec, as a failed start. - if (!pending && this.child === child) { - this.log(`python runtime exited while no script ran (code=${exitCode}, signal=${signal ?? 'none'}); the next script starts a new one`); - } - if (this.child === child) this.teardownChild(); - return; - } - const killedDeaf = inputFailed && signal === 'SIGKILL'; - // Why, from what arrived: the runtime's own exit names the fd it lost in its last line. - const why = (said: string) => - killedDeaf - ? stoppedReading(pending.background) - : interpreterExit(exitCode, signal, pending.background, said, pending.returnCode !== null); - // The host's log keeps the short form, with the exit's own code and signal. - const logged = killedDeaf - ? "python runtime stopped reading the host's messages, so it was killed" - : `python runtime exited unexpectedly (code=${exitCode}, signal=${signal ?? 'none'})`; - // What it wrote last, such as a fatal error's own text or an exec's - // result, is already on its way through its pipes: settle once they've - // ended, or after a short grace while a command it started still holds - // them. - afterOutputEnds(stdout, stderr, protoOut, () => { - if (this.pending === pending) { - const last = this.stopPending(why).trim(); - // A background script's last output is its journal's, both streams. - const what = pending.journal ? 'the last it wrote' : 'the last it wrote to stderr'; - this.log(last ? `${logged}; ${what}: ${last.slice(-LAST_WORDS_CHARS)}` : logged); - } else if (!pending.stopped) { - // Its script's result and marks, already on their way, settled the - // exec while the host waited: it exited as its script ended, whether - // or not a next exec has replaced it since. - this.log(`python runtime exited as its script ended (code=${exitCode}, signal=${signal ?? 'none'}); the next script starts a new one`); - } - if (this.child === child) this.teardownChild(); - }); - }); - - this.reader = createInterface({ input: protoOut }); - this.reader.on('line', (line) => this.handleLine(line, child)); - // readline passes its input's error on to the interface, where with no - // listener it would be thrown as an uncaught exception and end the host. - // protoOut's own listener above logs it. - this.reader.on('error', () => {}); - - this.childReady = new Promise((resolve, reject) => { - let over = false; - const finish = (outcome: () => void) => { - if (over) return; - over = true; - clearTimeout(timeout); - // Another interpreter may have started since this one exited. - if (this.readyResolver === onReady) this.readyResolver = null; - if (this.readyFailer === onError) this.readyFailer = null; - child.off('exit', onExit); - child.off('error', onError); - outcome(); - }; - const onReady = () => finish(resolve); - const onExit = (exitCode: number | null, signal: NodeJS.Signals | null) => { - // Why it ended, such as a traceback, is on its way through its stderr. - // Its messages' pipe doesn't matter here, and the exit's teardown has - // stopped reading it. A tail that lost its beginning says so. - afterOutputEnds(stdout, stderr, null, () => { - const tail = stderr.lastIdle.trim(); - const said = tail !== '' && stderr.lastIdleCut ? `…${tail}` : tail; - const how = signal ? `was ended by ${signal} as it started` : 'exited as it started'; - finish(() => reject(Object.assign(new Error(how), { exitCode, said }))); - }); - }; - const onError = (e: Error) => finish(() => reject(e)); - const timeout = setTimeout(() => finish(() => reject(new Error("didn't start within 15 seconds"))), 15_000); - this.readyResolver = onReady; - this.readyFailer = onError; - child.on('exit', onExit); - child.on('error', onError); - }); - return this.childReady; - } - - private log(text: string): void { - console.error(`[pytc:${this.label}] ${text}`); - } - - private readyResolver: (() => void) | null = null; - /** Fails the readiness of an interpreter that is still starting (see reclaim). */ - private readyFailer: ((err: Error) => void) | null = null; - - private handleLine(line: string, child: ChildProcess): void { - let msg: { op?: string; id?: string; name?: string; args?: unknown; return_code?: number }; - try { - msg = JSON.parse(line); - } catch { - this.log(`non-protocol line from the python runtime: ${line.slice(0, 200)}`); - return; - } - - switch (msg.op) { - case 'ready': - this.readyResolver?.(); - return; - - case 'tool_call': { - const callId = msg.id; - const toolName = msg.name; - if (!callId || !toolName) return; - const args = - msg.args && typeof msg.args === 'object' && !Array.isArray(msg.args) - ? (msg.args as Record) - : {}; - this.onToolCall(toolName, args) - .catch((err): ScriptToolResult => ({ - text: `Error: ${err instanceof Error ? err.message : String(err)}`, - isError: true, - })) - .then((result) => { - // Only an object is a ScriptToolResult. Anything else a handler - // resolves with, a string or a value its type rules out, is sent - // as it is, so the call is still answered. - const reply = typeof result === 'object' && result !== null - ? { - op: 'tool_result', - id: callId, - result: result.text, - is_error: result.isError, - ...(result.structuredJson ? { structured_json: true } : {}), - } - : { op: 'tool_result', id: callId, result }; - this.reply(child, reply, `result of ${toolName} call ${callId}`); - }); - return; - } - - case 'wake': { - const wakeId = msg.id; - if (!wakeId) return; - const line = typeof (msg as { line?: unknown }).line === 'number' - ? (msg as { line: number }).line - : -1; - const payload = (msg as { payload?: unknown }).payload; - const handler = this.onWake; - const refuse = handler - ? handler(line, payload).catch((err: unknown) => - `wake handler failed: ${err instanceof Error ? err.message : String(err)}`) - : Promise.resolve('this script is not allowed to wake the agent'); - void refuse.then((error) => { - this.reply(child, { op: 'wake_ack', id: wakeId, ...(error ? { error } : {}) }, `ack of wake ${wakeId}`); - }); - return; - } - - case 'exec_result': { - const pending = this.pending; - if (pending && msg.id === pending.id && pending.returnCode === null) { - pending.returnCode = typeof msg.return_code === 'number' ? msg.return_code : 1; - this.settleIfComplete(child); - } - return; - } - - default: - this.log(`unknown protocol op: ${String(msg.op)}`); - } - } - - /** - * Settle the running exec once its script has reported and the end marks - * of both its output pipes have arrived, which says all of its output has. - */ - private settleIfComplete(child: ChildProcess): void { - const pending = this.pending; - if (child !== this.child || !pending || pending.returnCode === null) return; - if (!this.out?.marked || !this.err?.marked) return; - const stdout = this.out.take(); - const stderr = this.err.take(); - const journal = pending.journal; - if (journal) { - this.settlePending({ stdout: '', stderr: '', returnCode: pending.returnCode, tail: journal.tail }); - } else { - this.settlePending({ stdout, stderr, returnCode: pending.returnCode }); - } - } - - /** - * Settle the running exec now: what its script wrote so far, as far as it - * has reached the host, then `why` it stopped, on a line of its own. Output - * still in the pipes goes to the host's log. A script that reported before - * its interpreter ended keeps its own return code. Returns what the script - * wrote to stderr (a background script's tail), for the host's log. - */ - private stopPending(reason: string | ((said: string) => string)): string { - const pending = this.pending; - if (!pending || pending.settled) return ''; - pending.stopped = true; - const stdout = this.out?.take() ?? ''; - const stderr = this.err?.take() ?? ''; - const returnCode = pending.returnCode ?? 1; - const journal = pending.journal; - // The runtime's own lines are in a background script's journal. A foreground - // script's are in its stderr, unless it pointed fd 2 at stdout's pipe and took - // the runtime's copy of fd 2 (runtime-py.ts _diag): stderr goes last, as the - // one the runtime writes to first, since the last line that names a fd counts. - const why = typeof reason === 'string' ? reason : reason(journal ? journal.tail : `${stdout}\n${stderr}`); - if (journal) { - const tail = journal.tail; - // The journal says why too: in its file, which the agent reads with its - // own tools, when it has one that hasn't reached its cap or failed a - // write, and in its tail, which the result carries. - writeLine(journal, why); - this.settlePending({ stdout: '', stderr: why, returnCode, aborted: true, tail: journal.tail }); - return tail; - } - this.settlePending({ stdout, stderr: onItsOwnLine(stderr, why), returnCode, aborted: true }); - return stderr; - } - - private settlePending(result: ExecResult): void { - const pending = this.pending; - if (!pending || pending.settled) return; - pending.settled = true; - // Say why the script stopped: the in-script cancellation only reads "cancelled by host". - // Only a script that ended unsuccessfully was stopped: one that caught the cancellation - // and finished its work returns 0, and must not read as stopped. - if (pending.deadlineMs !== null && result.returnCode !== 0) { - const note = `script stopped: it reached its ${formatLimit(pending.deadlineMs)} time limit. ` - + `${limitRemedy(pending.deadlineMs, pending.ceilingMs, pending.background, pending.backgroundCeilingMs)}\n`; - // On a line of its own: a script's output, and the runner's own texts, such as an - // interpreter's exit, can end without a newline. - result = { ...result, stderr: onItsOwnLine(result.stderr, note) }; - // A background script's journal says so too: in its file, when it has one, - // and in its tail, which is the result's. - if (pending.journal) { - writeLine(pending.journal, note.trimEnd()); - result = { ...result, tail: pending.journal.tail }; - } - } - // Only now: what the stop wrote above is the journal's last. - pending.journal?.close(); - if (pending.deadlineTimer) clearTimeout(pending.deadlineTimer); - if (pending.killTimer) clearTimeout(pending.killTimer); - if (pending.interruptTimer) clearInterval(pending.interruptTimer); - this.pending = null; - pending.resolve(result); - } - - /** - * Answer the interpreter that asked. Call and wake ids restart in every - * interpreter, so an answer that outlives its interpreter (the script was - * aborted, killed or crashed, or the interpreter reclaimed) would resolve - * the same id in the one that replaced it, inside another script: drop it. - */ - private reply(child: ChildProcess, msg: unknown, what: string): void { - if (child !== this.child) { - this.log(`dropped late ${what}: the interpreter that asked for it is gone`); - return; - } - this.send(msg); - } - - private send(obj: unknown): void { - const child = this.child; - const input = this.protoIn; - const unreachable = this.unreachable; - if (!child || !input || !alive(child)) return; - try { - input.write(JSON.stringify(obj) + '\n', (err) => { - if (err) unreachable?.(err.message); - }); - } catch (err) { - this.log(`protocol write failed: ${err instanceof Error ? err.message : String(err)}`); - } - } - - /** - * SIGINT the interpreter: blocking code in the running script raises - * KeyboardInterrupt. Not on Windows, where Node's kill() ends the process - * outright; there the cancel op and the kill grace remain. - * process.kill rather than child.kill, which would mark the child `killed`: - * a killed child gets no more protocol messages and is respawned on the - * next exec, losing the interpreter state this keeps. - */ - private interruptChild(): void { - const child = this.child; - if (process.platform === 'win32' || !child || !alive(child)) return; - if (child.pid === undefined) return; - try { - process.kill(child.pid, 'SIGINT'); - } catch { - // already gone: the exit handler settles the exec - } - } - - /** Kill interpreter + clean temp dir. State is lost (by design). */ - private reclaim(reason: string): void { - if (this.child) { - this.log(`reclaiming python interpreter (${reason})`); - } - // An interpreter killed as it starts never says it's ready: fail its - // readiness now, so an exec waiting on it returns at once rather than at - // the 15 s timeout. Here rather than in teardownChild, which an - // interpreter's own exit also runs, so that one still reads "exited as - // it started". - this.readyFailer?.(new Error(`python runtime reclaimed (${reason}) before it was ready`)); - this.teardownChild(); - this.clearIdleTimer(); - } - - private teardownChild(): void { - if (this.reader) { - this.reader.close(); - this.reader = null; - } - const child = this.child; - if (child) { - const input = this.protoIn; - this.child = null; - this.childReady = null; - this.readyResolver = null; - this.readyFailer = null; - this.protoIn = null; - this.unreachable = null; - // Its output pipes stay read, into the host's log, until whatever still - // holds them, such as a command its script left running, closes them: - // breaking them under such a command could end it. They're read slower - // from now on, since nothing reads them for an exec again. - this.out?.retire(); - this.err?.retire(); - this.out = null; - this.err = null; - child.removeAllListeners('exit'); - try { - input?.end(); - } catch { - // stream may already be destroyed - } - // A child whose spawn failed has no pid, and its exitCode stays null - // until Node's 'error' for it, a tick later. A kill() in that tick, as - // from an abort or dispose on the tick exec was called, signals no - // child of ours but whatever pid Node's handle held: the host's own - // process group on Node 20.20.2, another process's pid on 24.19. - if (alive(child) && child.pid !== undefined) { - child.kill('SIGKILL'); - } - } - const dir = this.runtimeDir; - this.runtimeDir = null; - if (dir) { - const remove = (): void => { - try { - rmSync(dir, { recursive: true, force: true }); - } catch { - // temp cleanup is best-effort - } - }; - // The directory is the interpreter's working directory, which Windows - // won't remove while the process lives, so there it goes once the - // process has. Elsewhere it goes now, so a host that exits right after - // this doesn't leave it behind. A child that never started has no exit - // to wait for. - const running = child !== null && child.pid !== undefined && child.exitCode === null && child.signalCode === null; - if (running && process.platform === 'win32') child.once('exit', remove); - else remove(); - } - } - - private armIdleTimer(): void { - this.clearIdleTimer(); - if (this.idleReclaimMs <= 0 || !this.child) return; - this.idleTimer = setTimeout(() => { - if (!this.pending) this.reclaim('idle'); - }, this.idleReclaimMs); - // Do not hold the process open just to reclaim an idle interpreter. - this.idleTimer.unref?.(); - } - - private clearIdleTimer(): void { - if (this.idleTimer) { - clearTimeout(this.idleTimer); - this.idleTimer = null; - } - } -} - -/** - * Map framework tool names to python identifiers: `--` (the framework's - * prefix separator) becomes `__`, and every remaining character that is not - * valid in a python identifier becomes `_` (fleet reality: module and server - * ids contain single hyphens — `mcpl-admin`, `dog-events` — found live on - * the first canary run, 2026-07-26). A leading digit gets a `_` prefix. - * - * Names that sanitize to nothing, or that collide after sanitization, are - * skipped loudly — a skipped tool is unreachable from scripts (the - * exact-name tools[...] dict is built from this same list). - */ -export function buildInjectedTools( - toolNames: string[], - log: (message: string) => void = (m) => console.error(m), -): InjectedTool[] { - const seen = new Map(); - const injected: InjectedTool[] = []; - for (const toolName of toolNames) { - let pyName = toolName.replace(/--/g, '__').replace(/[^A-Za-z0-9_]/g, '_'); - if (/^[0-9]/.test(pyName)) pyName = '_' + pyName; - if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(pyName)) { - log(`[pytc] tool '${toolName}' skipped: cannot derive a python identifier`); - continue; - } - const existing = seen.get(pyName); - if (existing) { - log(`[pytc] tool '${toolName}' skipped: python name '${pyName}' collides with '${existing}'`); - continue; - } - seen.set(pyName, toolName); - injected.push({ pyName, toolName }); - } - return injected; -} diff --git a/src/code-execution/result-text.ts b/src/code-execution/result-text.ts deleted file mode 100644 index 231899070..000000000 --- a/src/code-execution/result-text.ts +++ /dev/null @@ -1,111 +0,0 @@ -/** - * What the model reads when a foreground `code_execution` script ends. - * - * How the script ended comes first: its exit code, and the time-limit note - * when the call's limit was capped. stderr comes next, ahead of stdout: its - * end holds a traceback, and for a script the runner stopped, why it stopped, - * which the runner writes on a line of its own after the script's own stderr. - * Within a budget, each stream is cut to its head and its tail around a count - * of what was cut, and stderr keeps a share of the room of its own, so in any - * budget that holds a cut of each stream, however long stdout runs, it can't - * push the exit code or stderr's end out of view. - */ - -export interface ScriptEnd { - stdout: string; - stderr: string; - returnCode: number; - /** Set when the call's time_limit_ms was capped at the deployment's maximum. */ - timeLimitNote?: string; -} - -/** The whole text, nothing cut. */ -export function scriptEndText(end: ScriptEnd): string { - return heading(end) + section('stderr', end.stderr) + section('stdout', end.stdout); -} - -/** - * The text within `budget` characters. When the whole text is longer, stderr - * keeps up to a third of the room the streams have, or more when stdout needs - * less, and stdout takes the rest, each cut to its head and tail. stderr's - * tail keeps its last line whole when the line fits its room. Returns null - * when the streams' room is three cut markers or less, so that stderr's share, - * when stderr is cut, keeps some of its own text beside its count. - */ -export function scriptEndTextWithin(end: ScriptEnd, budget: number): string | null { - const whole = scriptEndText(end); - if (whole.length <= budget) return whole; - const { stderr, stdout } = end; - const marker = cutMarker(Math.max(stderr.length, stdout.length)).length; - const room = budget - heading(end).length - labelLength('stderr', stderr) - labelLength('stdout', stdout); - if (room <= 3 * marker) return null; - const stderrRoom = Math.min(stderr.length, Math.max(Math.ceil(room / 3), room - stdout.length)); - return heading(end) - + section('stderr', cutMiddle(stderr, stderrRoom, true)) - + section('stdout', cutMiddle(stdout, room - stderrRoom)); -} - -function heading(end: ScriptEnd): string { - return `exit code: ${end.returnCode}\n` + (end.timeLimitNote ? `time limit: ${end.timeLimitNote}\n` : ''); -} - -/** A stream under its label, ending in a newline; nothing for an empty stream. */ -function section(name: string, body: string): string { - if (body === '') return ''; - return `--- ${name} ---\n${body}${body.endsWith('\n') ? '' : '\n'}`; -} - -/** What a stream's section adds besides the stream itself, at most: its label and a closing newline. */ -function labelLength(name: string, body: string): number { - return body === '' ? 0 : `--- ${name} ---\n`.length + 1; -} - -/** The count of what was cut, on a line of its own. */ -function cutMarker(cut: number, afterLineEnd = false): string { - return `${afterLineEnd ? '' : '\n'}[… ${cut} characters cut …]\n`; -} - -/** - * `text` in at most `keep` characters: its head and its tail around a count of - * what was cut. Each cut falls at a line's end when one is near, within a - * quarter of the part it ends, so a part line doesn't read as a whole one; - * otherwise it falls mid-line, never inside a surrogate pair. With - * `keepLastLine`, the tail is at least the last line when that line fits. - * `keep` must hold the marker for a cut of `text.length`. - */ -function cutMiddle(text: string, keep: number, keepLastLine = false): string { - if (text.length <= keep) return text; - const left = keep - cutMarker(text.length).length; - let headLength = Math.floor(left / 2); - let tailLength = left - headLength; - if (keepLastLine) { - // The tail keeps the last line whole, taking from the head, when the - // line fits; a longer one keeps its end. Blank lines at the end, LF or - // CRLF, go with the line before them. - let contentEnd = text.length; - while (contentEnd > 0 && (text[contentEnd - 1] === '\n' || text[contentEnd - 1] === '\r')) contentEnd--; - const lastLine = text.length - (text.lastIndexOf('\n', contentEnd - 1) + 1); - if (lastLine > tailLength && lastLine <= left) { - tailLength = lastLine; - headLength = left - lastLine; - } - } - let headEnd = headLength; - const lineEnd = headEnd > 0 ? text.lastIndexOf('\n', headEnd - 1) : -1; - if (lineEnd >= 0 && headEnd - (lineEnd + 1) <= headLength / 4) headEnd = lineEnd + 1; - else if (headEnd > 0 && isHighSurrogate(text.charCodeAt(headEnd - 1))) headEnd--; - let tailStart = text.length - tailLength; - const lineStart = text.indexOf('\n', tailStart - 1) + 1; - if (lineStart > 0 && lineStart - tailStart <= tailLength / 4) tailStart = lineStart; - else if (tailStart < text.length && isLowSurrogate(text.charCodeAt(tailStart))) tailStart++; - const head = text.slice(0, headEnd); - return head + cutMarker(tailStart - headEnd, head === '' || head.endsWith('\n')) + text.slice(tailStart); -} - -function isHighSurrogate(code: number): boolean { - return code >= 0xd800 && code <= 0xdbff; -} - -function isLowSurrogate(code: number): boolean { - return code >= 0xdc00 && code <= 0xdfff; -} diff --git a/src/code-execution/runtime-py.ts b/src/code-execution/runtime-py.ts deleted file mode 100644 index 967fc619a..000000000 --- a/src/code-execution/runtime-py.ts +++ /dev/null @@ -1,1070 +0,0 @@ -/** - * Python runtime for client-side programmatic tool calling (PTC). - * - * This is the interpreter-side half of the `code_execution` tool: a small - * asyncio REPL that mimics the environment Anthropic's server-side PTC was - * trained on (see docs/en/agents-and-tools/tool-use/programmatic-tool-calling): - * - * - scripts run with top-level `await` (PyCF_ALLOW_TOP_LEVEL_AWAIT) - * - host tools are injected as async functions taking a single dict and - * returning the tool_result text as a str (ToolResult), which also - * carries .is_error and .structured - * - a pending tool call that gets no response raises TimeoutError with the - * same message shape the managed runtime produces - * - interpreter globals persist across execs (container-reuse semantics) - * - * BACKGROUND MODE (model-authored daemons): when init carries - * `background: true`, the exec additionally gets: - * - `wake_agent(payload)` — async; reports the caller's line number (which - * indexes into the agent-authored script — self-correlation) and delivers - * the payload to the host, which injects it into the agent's context and - * triggers inference. Awaits a host ack so rate limiting backpressures - * inside the script instead of dropping wakes. - * - its stderr joined to its stdout (fd 2 onto fd 1's pipe), so the host's - * journal of its output (a log file the agent can read, and a tail for the - * crash envelope) holds their lines in the order they were written. The - * runtime's own lines and faulthandler's traceback go to that pipe too, so - * a crash's text comes after the lines the script wrote before it; a - * partial line still in a script's writer is lost with the process. - * - * OUTPUT. The protocol has file descriptors of its own: the host opens the - * runtime's fds 3 (host -> py) and 4 (py -> host), which the runtime makes - * non-inheritable as it starts, so no command a script runs is given them. fd 0 - * is /dev/null, and fds 1 and 2 are pipes the host reads itself: a script's - * prints, line by line, and the output of every command it runs go there in - * the order written, and what reaches them reaches the host whatever then - * happens to the interpreter. A partial line waits in the script's writer for - * its newline, a flush or the exec's end, as Python's stdout does on a - * terminal, and a command writing at the same moment as a long write can land - * between its parts. The host gives each exec what arrived on each stream - * before that exec's end mark (the exec op's `mark`), which the runtime writes - * into both streams once the script has returned; each stream is first in, - * first out, so what the script and the commands it waited for wrote is ahead - * of it. Nothing a script or a command does with fds 0, 1 or 2 reaches the - * protocol, short of pointing them at the runtime's channel, fds 3 and 4, - * which can garble its messages while the script runs and, through a command - * started holding the channel, for as long as that command runs. A garbled - * exec_result leaves its script waiting out its time limit and the grace, and - * the interpreter is restarted, so the next script's variables are gone. The - * runtime puts fds 0, 1 and 2 back as the host set them, blocking, at each - * exec's start, and puts back fd 1 or 2 pointed at the channel at the script's - * end too, before anything is flushed (_off_channel). Its own fds (3 and 4, and its copies of 0, 1 and 2) it - * checks at each exec's start and end, and fd 4 before each message: one a - * script closed or replaced ends the interpreter, saying which - * (_check_own_fds). So does a message or an end mark that can't be written, as - * after a script shuts down the socket under fd 4 or an output pipe, which - * leaves the fd as it was (send, _write_marks). - * - * Embedded as a TS string because tsc does not copy non-TS assets to dist/; - * PyRunner materializes it to a temp file at spawn. - * - * Protocol (JSON lines, on fds 3 and 4): - * host -> py : {op:"init", tools:[{py_name,tool_name}], call_timeout_s, - * background?} - * {op:"exec", id, code, mark} - * {op:"tool_result", id, result, is_error?, structured_json?} - * {op:"wake_ack", id, error?} - * {op:"cancel", id, reason?} - * {op:"exit"} stops the runtime: a running script is - * cancelled at its next await, one blocked in - * synchronous code runs on until then (a host - * that can't wait sends SIGINT, as at a time - * limit), and it exits 0. It goes alone on a - * line under 100 characters, as {"op":"exit"} - * does: the reader thread looks for it only in - * such lines. A host that closes fd 3 instead - * while a script runs gets exit code 70 and the - * line "cannot read the host's messages any - * more: a script closed, shut down or replaced - * fd 3, where they arrive", since the runtime - * can't tell that from a script's own shutdown - * of the socket under fd 3. - * py -> host : {op:"ready"} - * {op:"tool_call", id, exec_id, name, args} - * {op:"wake", id, exec_id, line, payload} - * {op:"exec_result", id, return_code} - * - * At a deadline the host sends cancel and then SIGINT, repeated until the - * script reports (not on Windows): the cancel stops a script waiting on an - * await, the signal also one stuck in blocking code (see _on_sigint). - * - * IMPORTANT: the python source below must not contain backticks or the - * sequence dollar+brace (TS template literal syntax). String.raw preserves - * backslashes, so \n inside python string literals is fine. - */ - -export const PYTHON_RUNTIME_SOURCE: string = String.raw` -import ast -import asyncio -import faulthandler -import functools -import inspect -import io -import json -import os -import re -import select -import signal -import stat -import sys -import threading -import traceback -import weakref - -CALL_TIMEOUT_S = 270.0 -BACKGROUND = False - -# A process a script forks has the runtime's state too; at its script's end -# it leaves, since the exec and its result are the runtime's (_run_script). -_RUNTIME_PID = os.getpid() - -# The protocol's own channel, fds 3 and 4 as the host opened them (_start). -PROTO_IN = None -PROTO_OUT = None - -# Private copies of fds 0, 1 and 2 as the host set them up: /dev/null, and the -# two output pipes the host reads (_take_std_fds). Each exec puts fds 0, 1 and 2 -# back from them, whatever an earlier script did with its own, and writes its -# end marks through them. os.dup's copies are non-inheritable, so no command a -# script runs holds them. -_STD_IN = None -_STD_OUT = None -_STD_ERR = None - -# The runtime's own fds, as it found them: fd -> (what losing it stops, which -# fd it is, what it was). A script can close or replace any of them (os.close, -# os.dup2), so each exec's start and end checks them (_check_own_fds). -_OWN_FDS = {} - -# Every writer the runtime gave a script, which a script can keep past its -# exec (a logging handler keeps sys.stderr): each exec's end flushes them all, -# so a partial line goes out in the exec that wrote it. -_script_writers = weakref.WeakSet() - -_send_lock = threading.Lock() - -# A str can hold a lone surrogate, which UTF-8 can't encode: on POSIX, -# os.fsdecode makes one of each byte in a filename that doesn't decode as -# UTF-8, and json.loads makes one of a "\ud800" escape. The protocol's channel -# is strict UTF-8 (_start), so writing one would fail the whole message, and a -# tool call or a wake carrying one would never reach the host. Each goes as -# U+FFFD. What a script prints shows it escaped, as backslashreplace writes -# it (_script_writer). -_LONE_SURROGATE = re.compile(r"[\ud800-\udfff]") - - -def send(obj): - line = _LONE_SURROGATE.sub("\ufffd", json.dumps(obj, ensure_ascii=False, default=str)) - with _send_lock: - # A script can close or replace fd 4 before a tool call, as well as - # before its end, so each message checks it first. - _check_own_fd(4) - try: - PROTO_OUT.write(line + "\n") - PROTO_OUT.flush() - except OSError as exc: - # fd 4 is still the host's, so the socket under it broke: a script - # shut it down, or the host's end is gone. Nothing more reaches it. - _fatal(_lost(4) + " (" + str(exc) + ")") - - -def _start(): - global PROTO_IN, PROTO_OUT - # A wrapper between the host and this interpreter can close fds 3 and 4 - # (a container, or sudo with its default settings, does): say so where the - # host reads, for a failed start that names what to fix, and stop with - # EX_CONFIG (78), which tells the host that calling again won't help - # (HOST_CHANNEL_MISSING_EXIT in py-runner.ts). - missing = [] - for fd in (3, 4): - try: - os.fstat(fd) - except OSError: - missing.append(fd) - if missing: - which = ("its file descriptors 3 and 4 aren't open" if len(missing) == 2 - else "its file descriptor " + str(missing[0]) + " isn't open") - try: - os.write(2, ( - "The host's channel didn't reach this Python interpreter: " + which + ", as when the " - "host's pythonPath starts Python through a wrapper that doesn't pass both file descriptors " - "3 and 4 on, such as a container, or sudo with its default settings. code_execution can't " - "run scripts here until whoever runs this host changes that, so calling it again fails " - "the same way.\n" - ).encode()) - except OSError: - pass - sys.exit(78) - # fds 3 and 4 came from the host inheritable; no command a script runs may - # hold them, or read the host's messages. - os.set_inheritable(3, False) - os.set_inheritable(4, False) - _own(3, "cannot read the host's messages any more", "fd 3, where they arrive") - _own(4, "cannot send the host a message any more", "fd 4, where they go") - PROTO_IN = open(3, "r", encoding="utf-8", newline="\n") - PROTO_OUT = open(4, "w", encoding="utf-8", newline="\n") - # Between scripts, a thread a script left running prints through these: - # a lone surrogate is escaped there, as in a script's own writers, rather - # than raising in the thread (PYTHONIOENCODING names no handler). - for stream in (sys.stdout, sys.stderr): - try: - stream.reconfigure(errors="backslashreplace") - except Exception: - pass - _take_std_fds() - - -def _take_std_fds(): - global _STD_IN, _STD_OUT, _STD_ERR - _STD_IN, _STD_OUT, _STD_ERR = os.dup(0), os.dup(1), os.dup(2) - _own(_STD_IN, "cannot give scripts their stdin any more", "the runtime's copy of fd 0") - _own(_STD_OUT, "cannot reach the output pipes any more", "the runtime's copy of fd 1") - _own(_STD_ERR, "cannot reach the output pipes any more", "the runtime's copy of fd 2") - _enable_faulthandler(_STD_ERR) - - -def _enable_faulthandler(fd): - # A crash's own text goes where the host reads it, a pipe that outlives the - # process: faulthandler adds a traceback for a segfault or an abort, where - # CPython alone writes nothing. Python 3.14 adds the C stack after it, - # native frames a script can't act on, which would push the error's own - # line out of the end of it that the host logs, so it's left out. - faulthandler.enable(file=fd, all_threads=False, - **({"c_stack": False} if sys.version_info >= (3, 14) else {})) - - -def _identity(fd): - # Which file the fd is: its type, device and inode. Not its whole mode: - # macOS's fstat sets a socket's permission bits from its state, so they - # change when its write side is shut down or its peer ends it, as when a - # host ends fd 3 after the exit op, while the fd is still the runtime's own. - st = os.fstat(fd) - return (stat.S_IFMT(st.st_mode), st.st_dev, st.st_ino) - - -def _own(fd, stops, which): - _OWN_FDS[fd] = (stops, which, _identity(fd)) - - -def _lost(fd): - # The runtime's line for one of its own fds that it can't use any more, in - # the words the host's advice reads the fd from (fdTakenFromRuntime in - # py-runner.ts): a script closed or replaced it, or shut down the socket - # under it, which only a failed write shows. - stops, which, _ = _OWN_FDS[fd] - return stops + ": a script closed, shut down or replaced " + which - - -def _check_own_fd(fd): - # A script closed or replaced one of the runtime's own fds: say which and - # end, before the runtime writes to a file that isn't its own any more. - own = _OWN_FDS.get(fd) - if own is None: - return - try: - same = _identity(fd) == own[2] - except OSError: - same = False - if not same: - _fatal(_lost(fd)) - - -def _check_own_fds(): - for fd in list(_OWN_FDS): - _check_own_fd(fd) - - -def _diag(text): - # A note of the runtime's own for the host's log, through fd 2's copy or, - # if a script closed or replaced that, fd 2 itself, and only while it's - # one of the two outputs the host gave the runtime, as the runtime found - # them at its start: a note never goes into a file a script put in its - # place. A background script's journal is one stream, stdout's pipe, so - # there the note goes through fd 1's copy first, after the lines the - # script wrote before it. It waits for room on a pipe a command left non-blocking, - # as the marks do (_write_some). - data = ("[pytc-runtime] " + text + "\n").encode("utf-8", "backslashreplace") - pipes = [_OWN_FDS[fd][2] for fd in (_STD_OUT, _STD_ERR) if fd in _OWN_FDS] - for fd in ((_STD_OUT, 2, _STD_ERR) if BACKGROUND else (_STD_ERR, 2)): - try: - if fd is not None and _identity(fd) in pipes: - _write_all(fd, data) - return - except OSError: - pass - - -def _write_some(fd, data): - # A command can leave the output pipes non-blocking: they're one open file - # with the runtime's fds 1 and 2 and its copies. A full pipe then waits for - # room, where a bare write would raise BlockingIOError. - while True: - try: - return os.write(fd, data) - except BlockingIOError: - select.select([], [fd], []) - - -def _write_all(fd, data): - while data: - data = data[_write_some(fd, data):] - - -class _FdWriter(io.RawIOBase): - # A script's fd 1 or 2 by number (_write_some), which closing never closes. - def __init__(self, fd): - super().__init__() - self._fd = fd - # Set while _drop_held empties the writer above it. - self.dropping = False - - def writable(self): - return True - - def fileno(self): - return self._fd - - def write(self, data): - if self.dropping: - return len(data) - return _write_some(self._fd, data) - - -def _script_writer(fd): - # Line-buffered, as Python's own stdout is on a terminal: each line reaches - # the host as it's printed, in order with what the commands a script runs - # write, for one write per line. A partial line waits for its newline, a - # flush or the end of the exec. It writes to the fd by number, so one an - # earlier script kept, such as a logging handler's, writes into the exec - # that's running. - writer = io.TextIOWrapper(io.BufferedWriter(_FdWriter(fd)), encoding="utf-8", - errors="backslashreplace", newline="\n", line_buffering=True) - _script_writers.add(writer) - return writer - - -def _open_streams(): - # fds 0, 1 and 2 as the host set them, whatever the last script did. - os.dup2(_STD_IN, 0) - os.dup2(_STD_OUT, 1) - # A background script's journal is one stream: its stderr joins its stdout's - # pipe, so their lines keep the order they were written in. - os.dup2(_STD_OUT if BACKGROUND else _STD_ERR, 2) - # And blocking, as the host set them: O_NONBLOCK belongs to a pipe's open - # file, which dup2 doesn't touch, so one a command set would reach every - # later script and command. - os.set_blocking(_STD_OUT, True) - os.set_blocking(_STD_ERR, True) - return _script_writer(1), _script_writer(2) - - -def _off_channel(): - # A script can point its fd 1 or 2 at the runtime's own channel, as with - # os.dup2(4, 1). What its writers still hold would then go into the channel - # ahead of the script's result, which the host couldn't read, and the exec - # would wait out its time limit. So at the script's end, before anything is - # flushed, one that is fd 3 or fd 4 goes back to its pipe, as each exec's - # start puts it (_open_streams). What a script or its commands write onto - # the channel, or read from it, can still garble the runtime's messages: the - # script's own while it runs, and, from a command it started holding the - # channel, a later script's for as long as that command runs. A script - # whose exec_result is garbled waits out its time limit and the grace, and - # the interpreter is restarted. Only a channel per script would end that. - # A copy a script closed or replaced is left alone, so nothing goes into - # a file of the script's: the check at the exec's end ends the runtime. - channel = [_OWN_FDS[fd][2] for fd in (3, 4) if fd in _OWN_FDS] - for fd, pipe in ((1, _STD_OUT), (2, _STD_OUT if BACKGROUND else _STD_ERR)): - try: - if _identity(fd) in channel and _identity(pipe) == _OWN_FDS[pipe][2]: - os.dup2(pipe, fd) - except OSError: - pass - - -def _note(stream, text): - # The runtime's own line in a script's stream, which may have broken under - # it, or whose write the script may have replaced with its own, which may - # raise anything, SystemExit included: the line then goes through the - # runtime's own note (_diag), and the exec's end goes on. - try: - stream.write(text) - except BaseException: - _diag(text.strip("\n")) - - -def _flush_writers(): - # Send what the script left in its writers, the ones it kept from earlier - # execs too, at the exec's end and before a fork. A script can put its own - # code in one, as by replacing its flush, which may raise anything, - # SystemExit included: what it raises is said, but for the ValueError - # _flush takes as a closed writer, and the exec's end or the fork goes on, - # as for a script's own object (_flush_script_streams). - for writer in list(_script_writers): - try: - _flush(writer) - except BaseException as exc: - _diag("some of what the script wrote couldn't be sent (" + _said(exc) + ")") - - -def _flush_script_streams(): - # What a script left in a wrapper of its own that it put in sys.stdout or - # sys.stderr goes out in its exec, as Python flushes them at exit. The - # runtime's own writers are _flush_writers', and a wrapper the script - # keeps anywhere else is its own. Compared by identity: the weak set's - # membership test would hash the object, which a script's may refuse. - writers = list(_script_writers) - for stream in (sys.stdout, sys.stderr): - # A script's own object runs its own code here, which may raise - # anything, SystemExit included: it's said, and the exec's end goes on. - # A closed one's ValueError is passed over, as _flush passes over the - # runtime's own: closing it flushed what it held. - try: - if any(stream is w for w in writers) or not callable(getattr(stream, "flush", None)): - continue - _flush(stream) - except BaseException as exc: - _diag("some of what the script wrote couldn't be sent (" + _said(exc) + ")") - - -def _said(exc): - # An exception's text, or its type's name when it has none, as SystemExit() - # hasn't, or when a script's own exception class refuses to give it. None - # of the script's code runs past the try: the text is taken as an exact str, - # since a str subclass's own methods run on truth and concatenation, and the - # name comes through type's own descriptor, as _brief's does, so a - # metaclass's doesn't run, and as an exact str too, since a script can set - # a class's __name__ to a str subclass. - try: - text = str.__str__(str(exc)) - except BaseException: - text = "" - return text or str.__str__(type.__dict__["__name__"].__get__(type(exc))) - - -def _flush(writer): - try: - writer.flush() - except ValueError: - # A script closed or detached it, or the stream under it: nothing more - # goes through it. A script that closed or detached the writer itself - # flushed it then, and saw any failure of that flush; one that closed - # the stream under it dropped what the writer held, which can't be told - # from nothing here. A ValueError from a flush the script put in its - # place is taken the same way. - return - except BaseException as exc: - # Its fd closed or broke under it, or the script's own flush in its place - # raised, so that much can't be sent. Dropped, - # it can't go out later, once the fd is back, in another exec's output, - # where a writer the script kept would flush it: a writer over the - # runtime's raw writer is (_drop_held), and what a file of the script's - # own holds stays its own, as with any file a script keeps. - _drop_held(writer) - _diag("some of what the script wrote couldn't be sent (" + _said(exc) + ")") - - -def _drop_held(writer): - # Empty a writer whose flush failed, and leave it usable: one of the - # runtime's, or a script's own wrapper over the runtime's raw writer, - # through the runtime's buffer, as TextIOWrapper(sys.stdout.detach()) - # makes, or through a buffer of its own. Emptying one writer leaves - # another wrapper's own buffer as it is; one that shares this writer's - # buffer, as TextIOWrapper(sys.stdout.buffer) does, loses with it what it - # put there, under the same note. - raw = getattr(getattr(writer, "buffer", None), "raw", None) - if not isinstance(raw, _FdWriter): - return - # Through the type's own flush, since a script may have put a flush of - # its own on the writer, which is what failed. - raw.dropping = True - try: - type(writer).flush(writer) - finally: - raw.dropping = False - - -def _before_fork(): - _flush_script_streams() - _flush_writers() - - -if hasattr(os, "register_at_fork"): - # A fork copies what the script's writers hold, and what its own wrappers - # in sys.stdout and sys.stderr hold. Sent first, it reaches the host once, - # and a forked child's writers hold only what the child writes. A wrapper - # the script keeps anywhere else is its own, at a fork as at its end, as - # any buffered file a Python program forks with is. - os.register_at_fork(before=_before_fork) - - -def _write_marks(mark): - # End this exec's share of each stream with its mark: everything the - # script wrote, and all of every command it waited for, is ahead of it. - # A mark that can't be written, on a fd the check before it passed, as - # after a script shut down the socket under fd 1 or 2, ends the - # interpreter, which settles the exec rather than leave it waiting out - # its time limit for the mark. - # The stream the runtime's notes go to gets its mark last, so the line - # _fatal writes when the other's can't be written is still in this exec's - # share: stderr's, or a background script's journal, stdout's (_diag). - data = mark.encode("utf-8") - for fd in ((_STD_ERR, _STD_OUT) if BACKGROUND else (_STD_OUT, _STD_ERR)): - try: - _write_all(fd, data) - except OSError as exc: - _fatal(_lost(fd) + " (" + str(exc) + ")") - - -def _fatal(why): - # The runtime can't go on, as when a script closed one of the runtime's own - # fds under it: say why through its copy of fd 2, which the host reads - # unless a script replaced that too, or shut down the socket under it - # (_diag), and end, so the host settles - # the exec on the exit with the output that arrived, rather than waiting - # out its time limit for a result that can't come. - _diag(why + "; exiting") - os._exit(70) - - -# Persistent interpreter state (mirrors container reuse: variables survive -# between exec calls until the host reclaims the interpreter). -SCRIPT_GLOBALS = {"__name__": "__main__", "__builtins__": __builtins__} -_injected_py_names = set() - -_pending_tool_futures = {} -_pending_wake_futures = {} -_next_tool_call_id = 0 -_next_wake_id = 0 -_current_exec_id = None -_current_exec_task = None - -# How the host stops a script (its time limit, or an abort): ONE per-script -# state, so the script is stopped exactly once, by whichever path gets there -# first. Every other path then does nothing to that script. -# PENDING -> nothing asked yet. -# REQUESTED -> asked while the script was outside its own code (waiting on -# an await, writing a protocol line, or not started yet). A -# cancellation is queued on the loop, or delivered as the -# script starts. -# DELIVERED -> KeyboardInterrupt raised in its code, or its task cancelled. -# Final: whatever the script does next (except/finally, awaits -# in its cleanup) runs to completion or to the host's kill. -# Transitions happen only in the main thread: in the SIGINT handler (between -# bytecodes, so it never cancels anything itself -- see _on_sigint) and in -# loop callbacks. Neither can interleave a transition of the other halfway -# in a way that matters: the handler only moves PENDING to REQUESTED or, with -# the script's own frame on the stack, to DELIVERED; a loop callback never -# runs while the script's frame is on the stack. -# -# Where the signal (or the cancel op) can land, and what happens: -# 1. script's own code, running or blocking -> KeyboardInterrupt there -# 2. script waiting on an await (loop idle) -> cancellation queued; it wakes -# the loop and lands at that await -# 3. main() mid-dispatch, between the done() check of a future the script -# awaits and its set_result (reply behind the cancellation) -> queued; -# set_result completes; the script gets CancelledError instead of it -# 4. the script writing a protocol line (send) -> queued; the line stays -# whole; the cancellation lands at the script's next await -# 5. reply ahead of the queued cancellation, script then awaits -> the -# cancellation lands at that await -# 6. reply ahead, script then blocks -> the next signal (the host repeats -# it) raises KeyboardInterrupt there; the queued cancellation then does -# nothing, so cleanup that awaits is not cut short -# 7. reply ahead, script finishes before the queued cancellation runs -> -# it does nothing, and never reaches the next script -# 8. script not started yet (signal, or the cancel op) -> delivered as -# CancelledError when it starts, so it still reports -# 9. cancel op before the signal -> delivered; the signal and its repeats -# do nothing, so cleanup after a caught CancelledError runs on -# 10. cancel op or repeated signal after delivery -> nothing -# 11. script finished, reporting its result -> nothing -# 12. between scripts -> nothing -# 13. a previous script installed an eager task factory -> no effect: the -# runtime builds its script task directly, and the state is in place -# before the script's first step -# 14. the flush before a fork the script makes -> queued, as in 4; the fork -# goes on, and the cancellation lands at the script's next await, or the -# next signal stops a script that blocks, as in 6 -_PENDING = "pending" -_REQUESTED = "requested" -_DELIVERED = "delivered" - - -class _ScriptStop: - __slots__ = ("task", "running", "state", "interrupt") - - def __init__(self): - self.task = None - self.running = False # the script's own code may be on the stack - self.state = _PENDING - self.interrupt = None # the KeyboardInterrupt raised in its code - - -_stop = None # the running script's _ScriptStop; None between scripts - - -def _deliver_cancel(st): - # Loop context only: the cancel op in main()'s dispatch, or queued by the - # SIGINT handler. Never from inside the handler, where a cancellation - # could land between main()'s done() check of a future the script awaits - # and its set_result. - if st is None or st is not _stop or st.state == _DELIVERED: - return - if not st.running: - st.state = _REQUESTED # not started yet: delivered as it starts - return - st.state = _DELIVERED - st.task.cancel() - - -class ToolResult(str): - """A tool's result: the string the tool gave, with two attributes. - - is_error: True if the result is an error, False if not, None if the - host didn't say. - structured: the parsed structuredContent of a structured result's JSON - object, or None. - """ - - # Defaults, since pickle and copy rebuild a str subclass from its text - # alone and then restore the attributes. - def __new__(cls, text="", is_error=None, structured_json=False): - self = str.__new__(cls, text) - self.is_error = is_error - self._structured_json = structured_json - return self - - # Read from the string itself, the one copy sent, on first use only. - @functools.cached_property - def structured(self): - if not self._structured_json: - return None - return json.loads(self).get("structuredContent") - - -def _resolve(fut, value): - # Defense in depth: the script may have been cancelled since the lookup. - if fut is not None and not fut.done(): - try: - fut.set_result(value) - except asyncio.InvalidStateError: - pass - - -def _brief(value): - # A value as a refusal shows it: by its type, such as , which fails - # loudly if copied rather than sending something else. The agent has its - # values in the call it just wrote. No value's own code runs here: its - # __repr__ isn't called, and its type is named through type's own - # descriptor, so a metaclass's __name__ doesn't run either. - return "<" + type.__dict__["__name__"].__get__(type(value)) + ">" - - -def _make_tool_fn(tool_name, py_name): - async def tool_fn(*positional, **kwargs): - global _next_tool_call_id - if len(positional) > 1: - raise TypeError( - py_name + " wasn't called: a tool function takes one dict of " - "arguments, such as await " + py_name + "({...}), and this call " - "gave it " + str(len(positional)) + " positional arguments" - ) - # args= on its own is the dict, as it has always been. Beside other - # keywords it's one of them, since a tool such as utils has a field - # of that name, so the suggested dict keeps it. - if positional: - args = positional[0] - elif list(kwargs) == ["args"]: - args = kwargs.pop("args") - else: - args = None - if kwargs: - if args is not None and not isinstance(args, dict): - raise TypeError( - py_name + " wasn't called: a tool function takes one dict of " - "arguments, not keyword arguments, and this call also gave it " - + _brief(args) + ", so put every argument in one dict: await " - + py_name + "({...})" - ) - # The likely intent is plain: the same arguments in one dict. - meant = dict(args) if isinstance(args, dict) else {} - meant.update(kwargs) - # A key is what the agent has to write, so a str key is shown - # whole up to 60 characters, as names are elsewhere; a longer one, - # or any other key, is shown as a value is. A tool's result counts - # as a str: ToolResult keeps str's repr, so no script code runs. - shown = ", ".join((repr(k) if (type(k) is str or type(k) is ToolResult) and len(k) <= 60 else _brief(k)) + ": " + _brief(v) - for k, v in meant.items()) - raise TypeError( - py_name + " wasn't called: a tool function takes one dict of " - "arguments, not keyword arguments, so write await " - + py_name + "({" + shown + "})" - ) - if args is None: - args = {} - if not isinstance(args, dict): - raise TypeError( - py_name + " wasn't called: a tool function takes one dict of " - "arguments, such as await " + py_name + "({...}), and this call " - "gave it " + _brief(args) - ) - _next_tool_call_id += 1 - call_id = "t" + str(_next_tool_call_id) - fut = asyncio.get_running_loop().create_future() - _pending_tool_futures[call_id] = fut - send({ - "op": "tool_call", - "id": call_id, - "exec_id": _current_exec_id, - "name": tool_name, - "args": args, - }) - try: - return await asyncio.wait_for(fut, timeout=CALL_TIMEOUT_S) - except asyncio.TimeoutError: - raise TimeoutError( - "Calling tool ['" + tool_name + "'] timed out (no response after " - + str(int(CALL_TIMEOUT_S)) + "s)." - ) - finally: - _pending_tool_futures.pop(call_id, None) - tool_fn.__name__ = py_name - tool_fn.__qualname__ = py_name - tool_fn.__doc__ = ( - "Host tool '" + tool_name + "'. Takes a single dict of arguments and " - "returns the tool result as a string, with .is_error and .structured " - "beside it." - ) - return tool_fn - - -async def wake_agent(payload=None): - """Wake the agent: deliver payload into their context and trigger a turn. - - Reports the caller's line number (indexes into the script the agent - wrote). Awaits the host ack — rate limiting backpressures here rather - than dropping wakes. Raises RuntimeError when the host refuses (wake cap - exceeded, script cancelled). - """ - global _next_wake_id - frame = inspect.currentframe() - line = frame.f_back.f_lineno if frame is not None and frame.f_back is not None else -1 - _next_wake_id += 1 - wake_id = "w" + str(_next_wake_id) - fut = asyncio.get_running_loop().create_future() - _pending_wake_futures[wake_id] = fut - send({ - "op": "wake", - "id": wake_id, - "exec_id": _current_exec_id, - "line": line, - "payload": payload, - }) - try: - error = await fut - if error: - raise RuntimeError("wake_agent refused by host: " + str(error)) - return None - finally: - _pending_wake_futures.pop(wake_id, None) - - -def handle_init(msg): - global CALL_TIMEOUT_S, BACKGROUND - timeout = msg.get("call_timeout_s") - if isinstance(timeout, (int, float)) and timeout > 0: - CALL_TIMEOUT_S = float(timeout) - BACKGROUND = bool(msg.get("background")) - # A background script's stderr joins its stdout's pipe (_open_streams), so - # a crash's traceback goes there too, after the lines the script wrote first. - _enable_faulthandler(_STD_OUT if BACKGROUND else _STD_ERR) - - # Remove functions injected by a previous init that are no longer present - # (tool list changed) without touching user-defined globals. - new_tools = msg.get("tools") or [] - new_py_names = set() - tools_dict = {} - for entry in new_tools: - py_name = entry.get("py_name") - tool_name = entry.get("tool_name") - if not py_name or not tool_name: - continue - fn = _make_tool_fn(tool_name, py_name) - SCRIPT_GLOBALS[py_name] = fn - tools_dict[tool_name] = fn - new_py_names.add(py_name) - for stale in _injected_py_names - new_py_names: - SCRIPT_GLOBALS.pop(stale, None) - _injected_py_names.clear() - _injected_py_names.update(new_py_names) - # Exact-name escape hatch: tools["mcpl--server--tool"]({...}) - SCRIPT_GLOBALS["tools"] = tools_dict - # The doorbell exists only in background mode — a foreground script's - # agent is already awake (mid-turn). - if BACKGROUND: - SCRIPT_GLOBALS["wake_agent"] = wake_agent - else: - SCRIPT_GLOBALS.pop("wake_agent", None) - - -def _leave_forked(code, err): - # A process the script forked ends on sys.exit() as Python ends: with its - # code, 0 for none, or 1 after writing a code that isn't a number, and with - # what it wrote sent. The atexit handlers are the parent's, so it skips - # them, as multiprocessing's children do. - if code is None: - status = 0 - elif isinstance(code, int): - status = code & 0xFF - else: - status = 1 - try: - text = str(code) + "\n" - except BaseException: - text = "" - _note(err, text) - _flush_script_streams() - _flush_writers() - os._exit(status) - - -async def _run_script(exec_id, code, mark, st): - global _current_exec_id, _current_exec_task, _stop - _check_own_fds() - out, err = _open_streams() - old_out, old_err, old_in = sys.stdout, sys.stderr, sys.stdin - sys.stdout, sys.stderr = out, err - sys.stdin = io.StringIO("") # input() ends at once, as fd 0's /dev/null does for a command - return_code = 0 - try: - compiled = compile(code, "

+sFavvG(jg=}Kbn1u%}Ayz~Nn2yaJ=A0QsC z^41?9XLI@AKVXA;EI)RU{)U(HE`mAS6YDjS_g^F<@m<*ovMkd^reP{K5A4;v379Z; zz3yJ5lva~SohVV(gm<)TEm@|M+Rdq8*jkg2Kqe{7T`s{e-ozs=Vb^2}AG!pqqm=c_ zAl-G`_%gj9CcwZfii%Sn2ec-RNCBdp3c)U?)3=Jhx{U2$F%_`AsSXsO<#cX*1sth} z`&`bejwj%IDc>l$bE)CxdY-Y-~)FsB83tWwY4-Y&4ifSXQ3>9!cm~60CsC`s0Z5I zr4BB~SAq0WtfXUeAmb;hra^oC{1>If?l<@OWq^B0VI@S`etmubbL~QxYNrd41-` z81o~cw{lkFW{cOeqzFo` zjZ;1RrwWva)S?(fyLjUnl$;qa6#|>%jbRSwLM@1UCm0d@L{(#xS0*4jYQJPBJK~IR zLpDE9HBh&jvEBjA_FQ$N86J^|#@Be{CmMgnqd}6<)v-G;1{t}%@a8%i0^EDKc0=Q( zn7zR&dpl4{N5H633=;KzzP*9D4UOQ&aLg_1Y5c{j1d&)hzKQSO=xGFQ^mL!w`@hR& B^J4%2 diff --git a/test/code-execution-engine.test.ts b/test/code-execution-engine.test.ts index 8ee73f745..1e030de8d 100644 --- a/test/code-execution-engine.test.ts +++ b/test/code-execution-engine.test.ts @@ -439,17 +439,20 @@ test('a module trap after a slot\'s failure the script caught reports the slot\' test('an ending the module can\'t read out is answered as out of memory, and that answer is kept', async () => { // The module can't allocate the ending's text the first time it's read, which can't be made to // happen on cue; the binding mustn't read it again, which would run the script's serializing - // code again and could answer differently. - for (const [script, message] of [ - ['tool("a"); tool("b"); ({ ok: true })', 'out of memory while serializing the script\'s return value; return less, or let go of data the script no longer needs'], - ['tool("a"); tool("b"); throw new Error("x")', 'out of memory while reading what the script threw; throw something smaller, such as an Error with a shorter message, or let go of data the script no longer needs'], - ]) { + // code again and could answer differently. A run that failed stays failed, and its later + // results are dropped. + for (const [script, kind, message] of [ + ['tool("a"); tool("b"); ({ ok: true })', 'rejected', 'out of memory while serializing the script\'s return value; return less, or let go of data the script no longer needs'], + ['tool("a"); tool("b"); throw new Error("x")', 'rejected', 'out of memory while reading what the script threw; throw something smaller, such as an Error with a shorter message, or let go of data the script no longer needs'], + ['tool("a"); tool("b"); queueMicrotask(() => { throw new TypeError("x") }); 1', 'failed', 'out of memory while reading the exception that ended the run; throw something smaller, catch it in the callback that threw it, or let go of data the script no longer needs'], + ] as const) { let reads = 0; const { e } = await engineAround((ex) => ({ engine_result: () => (++reads === 1 ? 0 : ex.engine_result()) })); - const expected = { kind: 'rejected', name: 'InternalError', message }; + const expected = { kind, name: 'InternalError', message }; + const later = kind === 'failed' ? { ...expected, dropped: true } : expected; assert.deepEqual(e.start(PRELUDE, script, 's.js'), expected, script); - assert.deepEqual(e.settle(1, true, '1', UNFIT), expected, script); - assert.deepEqual(e.settle(2, true, '2', UNFIT), expected, script); + assert.deepEqual(e.settle(1, true, '1', UNFIT), later, script); + assert.deepEqual(e.settle(2, true, '2', UNFIT), later, script); assert.equal(reads, 1, script); e.dispose(); } @@ -458,11 +461,12 @@ test('an ending the module can\'t read out is answered as out of memory, and tha test('an ending longer than the binding can decode is answered with its length, not a crash', async () => { // The module's buffer here claims a text past V8's longest string; a real one needs a budget // of gigabytes. The buffer is freed all the same. - for (const [script, length, message] of [ - ['({ ok: true })', 600_000_000, 'the script\'s return value comes to 600000000 bytes as text, more than the 536870888 the engine reads back; return less'], - ['throw new Error("x")', 600_000_000, 'what the script threw comes to 600000000 bytes as text, more than the 536870888 the engine reads back; throw something smaller'], + for (const [script, length, kind, message] of [ + ['({ ok: true })', 600_000_000, 'rejected', 'the script\'s return value comes to 600000000 bytes as text, more than the 536870888 the engine reads back; return less'], + ['throw new Error("x")', 600_000_000, 'rejected', 'what the script threw comes to 600000000 bytes as text, more than the 536870888 the engine reads back; throw something smaller'], + ['queueMicrotask(() => { throw new TypeError("x") }); 1', 600_000_000, 'failed', 'the exception that ended the run comes to 600000000 bytes as text, more than the 536870888 the engine reads back; throw something smaller, or catch it in the callback that threw it'], // One byte past the bound is refused too. - ['({ ok: true })', 536_870_889, 'the script\'s return value comes to 536870889 bytes as text, more than the 536870888 the engine reads back; return less'], + ['({ ok: true })', 536_870_889, 'rejected', 'the script\'s return value comes to 536870889 bytes as text, more than the 536870888 the engine reads back; return less'], ] as const) { let ptr = 0; const freed: number[] = []; @@ -476,7 +480,7 @@ test('an ending longer than the binding can decode is answered with its length, }, engine_free: (p) => { freed.push(p); return ex.engine_free(p); }, })); - assert.deepEqual(e.start(PRELUDE, script, 's.js'), { kind: 'rejected', name: 'RangeError', message }, script); + assert.deepEqual(e.start(PRELUDE, script, 's.js'), { kind, name: 'RangeError', message }, script); assert.ok(ptr !== 0 && freed.includes(ptr), `${script}: the buffer is freed`); e.dispose(); } @@ -519,6 +523,38 @@ test('an exception that escapes the script\'s promises ends the run as failed, w b.e.dispose(); }); +test('an exception that escapes the jobs reading the ending queued ends the run as failed, in the ending\'s place', async () => { + // With no call left to settle, the call that read the ending is the only answer the run gets. + const a = await engine(); + const s = a.e.start(PRELUDE, '({ toJSON() { queueMicrotask(() => { throw new TypeError("in the read") }); return 1 } })', 's.js'); + assert.deepEqual([s.kind, (s as { name?: string }).name, (s as { message?: string }).message], ['failed', 'TypeError', 'in the read']); + a.e.dispose(); + // The same for what the script threw, read through a getter. + const b = await engine(); + const t = b.e.start(PRELUDE, 'throw { get message() { queueMicrotask(() => { throw new RangeError("read") }); return "own" } }', 's.js'); + assert.deepEqual([t.kind, (t as { name?: string }).name, (t as { message?: string }).message], ['failed', 'RangeError', 'read']); + b.e.dispose(); +}); + +test('a stop while the exception that ended the run is read leaves the run failed, with the parts it cut off left out', async () => { + // The thrown object's name getter spins until the stop; its message is plain data. The run's + // own ending, kept first, doesn't stand: the failure came after it. + const thrower = 'queueMicrotask(() => { throw { get name() { for (;;) {} }, message: "m" } })'; + let checks = 0; + const a = await engine({ interrupt: () => ++checks > 100 }); + assert.equal(fulfilled(a.e.start(PRELUDE, `tool("a"); tool("b").then(() => ${thrower}); 1`, 's.js')), 1); + assert.deepEqual(a.e.settle(2, true, '2', UNFIT), { kind: 'failed', name: '', message: 'm' }); + // Every later call answers it too, though the guest is stopped. + assert.deepEqual(a.e.settle(1, true, '1', UNFIT), { kind: 'failed', name: '', message: 'm', dropped: true }); + a.e.dispose(); + // Read out not at all (the module here gives nothing back), it's still the failure. + checks = 0; + const b = await engineAround((ex) => ({ engine_result: () => { ex.engine_free(ex.engine_result()); return 0; } }), { interrupt: () => ++checks > 100 }); + const t = b.e.start(PRELUDE, `${thrower}; 1`, 's.js'); + assert.deepEqual([t.kind, (t as { name?: string }).name], ['failed', 'InternalError']); + b.e.dispose(); +}); + test('a result that reaches a stopped run is dropped', async () => { let stop = false; const { e } = await engine({ interrupt: () => stop }); @@ -575,9 +611,29 @@ test('a result\'s copy is freed once it\'s parsed, so the script\'s code after t assert.ok(tiny - big < 48, `${tiny} steps after a tiny result, ${big} after a 2 MiB one`); }); +test('the child\'s error\'s copy is freed once it\'s parsed, so the script\'s code after the call has its memory back', async () => { + // A result nested too deeply is unfit, and the call gets the child's error, here with a 2 MiB + // message. A script filling memory in 64 KiB steps after it gets as many steps as after a small + // one, less the 32 the message holds; a copy still held would take 32 more. + const deep = '['.repeat(MAX_RESULT_DEPTH + 1) + ']'.repeat(MAX_RESULT_DEPTH + 1); + async function steps(message: string) { + const { e } = await engine({}, { memoryMaxBytes: 16 * MiB }); + e.start(PRELUDE, 'let m; try { await tool("t") } catch (err) { m = err.message } const keep = []; try { for (;;) keep.push("y".repeat(65536)) } catch {} [keep.length, m.length]', 's.js'); + const s = e.settle(1, true, deep, JSON.stringify({ name: 'ResultTooLarge', message })); + assert.equal(s.unfit, true); + const [n, length] = fulfilled(s) as [number, number]; + assert.equal(length, message.length); + e.dispose(); + return n; + } + const small = await steps('z'); + const big = await steps('z'.repeat(2 * MiB)); + assert.ok(small - big < 48, `${small} steps after a small error, ${big} after a 2 MiB one`); +}); + test('settling calls frees both copies, whether the result reaches the script or no call has its id', async () => { - // Measured through the module's allocator, as the largest block that fits: a copy kept on each - // of 2,000 settles of a 1 KiB result would take 2 MiB or more. + // Measured through the module's allocator, as the largest block that fits: either copy kept on + // each of 1,000 settles, with a 1 KiB result and a 1 KiB error, would take 1 MiB or more. let ex!: Exports; const { e } = await engineAround((real) => { ex = real; return {}; }, {}, { memoryMaxBytes: 16 * MiB }); const largest = () => { @@ -591,11 +647,12 @@ test('settling calls frees both copies, whether the result reaches the script or }; e.start(PRELUDE, 'for (;;) await tool("t")', 's.js'); const result = JSON.stringify('r'.repeat(1024)); + const unfit = JSON.stringify({ name: 'ResultTooLarge', message: 'u'.repeat(1024) }); let id = 1; - for (let i = 0; i < 200; i++) e.settle(id++, true, result, UNFIT); + for (let i = 0; i < 200; i++) e.settle(id++, true, result, unfit); const before = largest(); - for (let i = 0; i < 1000; i++) e.settle(id++, true, result, UNFIT); - for (let i = 0; i < 1000; i++) assert.throws(() => e.settle(1_000_000 + i, true, result, UNFIT), /no pending call/); + for (let i = 0; i < 1000; i++) e.settle(id++, true, result, unfit); + for (let i = 0; i < 1000; i++) assert.throws(() => e.settle(1_000_000 + i, true, result, unfit), /no pending call/); const after = largest(); assert.ok(before - after < 64 * 1024, `${before - after} bytes less room after 2,000 settles`); e.dispose(); @@ -603,6 +660,8 @@ test('settling calls frees both copies, whether the result reaches the script or test('an ending of exactly MAX_ENDING_BYTES is read, not refused', async () => { // The module's buffer here only claims that length, so reading it fails, which a refusal wouldn't. + // What the module returned can't be read, so dispose() leaves the module as it is. + let disposals = 0; const { e } = await engineAround((ex, imports) => ({ engine_result: () => { const ptr = ex.engine_alloc(8); @@ -611,9 +670,11 @@ test('an ending of exactly MAX_ENDING_BYTES is read, not refused', async () => { view.setUint32(ptr + 4, 536_870_888, true); return ptr; }, + engine_dispose: () => { disposals++; ex.engine_dispose(); return 0; }, })); assert.throws(() => e.start(PRELUDE, '1', 's.js'), (err: unknown) => err instanceof EngineError && /the engine crashed/.test(err.message)); e.dispose(); + assert.equal(disposals, 0); }); test('a dispose() that traps throws an EngineError, and the engine is disposed all the same', async () => { From efc9c49be9de463fbfd30368c6d86e91293e3afa Mon Sep 17 00:00:00 2001 From: Gladys-2592 Date: Mon, 19 Oct 2026 06:07:46 -0400 Subject: [PATCH 20/41] fix(engine): vendor.sh cleans up whenever it exits, puts the copy back or says how, and names what it refuses From the round review of the engine's last round: - The exit trap is installed before the temporary directories are made, so a failure or signal right after leaves nothing behind. Its cleanup runs with errexit off, so one failed removal doesn't stop the rest, ignores further signals, and when it can't move the committed copy back it says so and gives the command that restores it. - INT, TERM and HUP exit through the trap, after the command in progress, with 130, 143 and 129. - The comment no longer promises what an exit trap can't: killed outright (SIGKILL) between the two renames, the script leaves the committed copy at engine/.quickjs-ng.XXXXXX.old. The next run's refusal over the missing copy now gives the command that puts back what's committed, rather than only "commit or remove them". - The restore command is built from physical paths, so a repository reached through a symbolic link gets a path relative to it. - The refusals of a link among the patches, and of an engine/patches that's a link to a directory, say that a link isn't taken. Checked in a scratch clone with bash 5.2.21, git 2.55.0 and quickjs-ng v0.17.0: a patch that doesn't apply is refused with nothing left behind; SIGINT, SIGTERM and SIGHUP while the clone runs exit 130, 143 and 129 with the copy unchanged and nothing left behind; SIGTERM between the two renames puts the committed copy back; with engine/ made read-only there, the script says it couldn't, and the command it prints, run from /, restored the copy; run through a symbolic link to the clone with a file of the copy removed, it refuses and prints a command with the physical repository and a relative path, which, run from /, restored the file; a link to a real patch and an engine/patches that's a link to a directory are refused with their messages; clean runs match the committed copy. Co-Authored-By: Claude Opus 5.5 --- engine/vendor.sh | 45 +++++++++++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/engine/vendor.sh b/engine/vendor.sh index 9dd7d19ff..8753676e6 100755 --- a/engine/vendor.sh +++ b/engine/vendor.sh @@ -29,35 +29,48 @@ FILES=( libregexp.h libregexp-opcode.h libunicode.h libunicode-table.h builtin-array-fromasync.h builtin-iterator-zip.h builtin-iterator-zip-keyed.h ) -here=$(cd "$(dirname "$0")" && pwd) +# Physical paths, as git gives the repository's, so the one can be taken from the other. +here=$(cd "$(dirname "$0")" && pwd -P) dest=$here/quickjs-ng # The command that restores the committed copy, from any directory. top=$(git -C "$here" rev-parse --show-toplevel) restore="git -C $(printf '%q' "$top") checkout -- $(printf '%q' "${dest#"$top"/}")" changes=$(git -C "$here" status --porcelain --untracked-files=all --ignored -- quickjs-ng) if [ -n "$changes" ]; then - echo "vendor.sh: engine/quickjs-ng holds changes or files not committed, which replacing it would erase; commit or remove them first:" >&2 + echo "vendor.sh: engine/quickjs-ng holds changes or files not committed, which replacing it would erase; commit or remove what you made, or run $restore to put back what's committed:" >&2 echo "$changes" >&2 exit 1 fi -tmp=$(mktemp -d) -# The new copy is staged beside the old one, on the same filesystem: once it's whole, the old one -# is moved aside, the stage renamed into its place, and only then is the old one removed. -stage=$(mktemp -d "$here/.quickjs-ng.XXXXXX") -old=$stage.old -# However the script ends, a committed copy left moved aside goes back, unless the new one is in -# its place, and what was staged goes. +# Whenever the script exits, short of being killed outright (SIGKILL), a committed copy left moved +# aside goes back, unless the new one is in its place, and what was staged goes. A signal waits +# for the command in progress, and one during this cleanup is ignored. +tmp= stage= old= finish() { - if [ -e "$old" ]; then - if [ ! -e "$dest" ] && mv "$old" "$dest"; then - echo "vendor.sh: stopped while replacing the copy; the committed copy is back in place." >&2 - elif [ -e "$dest" ]; then + set +e + trap '' INT TERM HUP + if [ -n "$old" ] && [ -e "$old" ]; then + if [ -e "$dest" ]; then rm -rf "$old" + elif mv "$old" "$dest"; then + echo "vendor.sh: stopped while replacing the copy; the committed copy is back in place." >&2 + else + echo "vendor.sh: stopped while replacing the copy, and couldn't move the committed copy back from $old; $restore restores it." >&2 fi fi - rm -rf "$tmp" "$stage" + [ -n "$tmp" ] && rm -rf "$tmp" + [ -n "$stage" ] && rm -rf "$stage" } trap finish EXIT +trap 'exit 129' HUP +trap 'exit 130' INT +trap 'exit 143' TERM +tmp=$(mktemp -d) +# The new copy is staged beside the old one, on the same filesystem: once it's whole, the old one +# is moved aside, the stage renamed into its place, and only then is the old one removed. Killed +# outright between the two renames, the script leaves the committed copy at $old, and $restore +# puts back what's committed. +stage=$(mktemp -d "$here/.quickjs-ng.XXXXXX") +old=$stage.old git -c advice.detachedHead=false clone --quiet --depth 1 --branch "$TAG" https://github.com/quickjs-ng/quickjs.git "$tmp/quickjs-ng" got=$(git -C "$tmp/quickjs-ng" rev-parse HEAD) if [ "$got" != "$COMMIT" ]; then @@ -70,13 +83,13 @@ patched=0 # Every entry, a dot-named one or a link included, is a regular .patch file or refused, and so is # an engine/patches that isn't a directory. if { [ -e "$here/patches" ] || [ -L "$here/patches" ]; } && { [ ! -d "$here/patches" ] || [ -L "$here/patches" ]; }; then - echo "vendor.sh: engine/patches isn't a directory; remove it, or make it one. engine/quickjs-ng is unchanged." >&2 + echo "vendor.sh: engine/patches isn't a directory (a symbolic link, even to one, isn't taken); make it one, or remove it. engine/quickjs-ng is unchanged." >&2 exit 1 fi shopt -s nullglob dotglob for patch in "$here"/patches/*; do if [ "${patch%.patch}" = "$patch" ] || [ -L "$patch" ] || [ ! -f "$patch" ]; then - echo "vendor.sh: engine/patches/$(basename "$patch") isn't a regular .patch file; rename or remove it. engine/quickjs-ng is unchanged." >&2 + echo "vendor.sh: engine/patches/$(basename "$patch") isn't a regular file named *.patch (a symbolic link, even to one, isn't taken); make it one, or remove it. engine/quickjs-ng is unchanged." >&2 exit 1 fi if ! git -C "$tmp/quickjs-ng" apply --cached "$patch"; then From 297b8e784ce5ee78953fe30124c3fb22fe1598e6 Mon Sep 17 00:00:00 2001 From: Gladys-2592 Date: Mon, 19 Oct 2026 07:41:59 -0400 Subject: [PATCH 21/41] fix(code-execution): a stop stays a stop, a throw as the run starts is the script's own ending, and what a stop cuts off a failure's read is left out From the round review of the engine's last round: - Once the host had stopped the guest, the interrupt handler still asked it at each later check, so a host that said stop only once let the guest run on, as a failure's getters do when its exception is read after a stop. The handler now stops the guest at every check after the first stop without asking again. - state() put every recorded failure ahead of a stop, a throw as the run starts (the prelude's, or a syntax error) included. That throw is the script's own ending, whose read a stop discards, so a later call found it again and read it again. Only an exception that escaped once the script ran now comes ahead of a stop; a throw at the start comes after it, as a rejection does. - When a stop cut off the read of a failure's message, the message fell back to the thrown object's JSON or string form, running more of the script's code and giving something that wasn't its message. Once a stop has come, that fallback is left out with the rest. - The messages for a failure that can't be read out give their remedy only for a callback's throw, since the engine's own failures have none. - Comments and docs: the interrupt slot's two exceptions to answering interrupted; a failure's fields as the engine reads a rejection's; what dropped means, said as the resolve or reject function never having run with the result, a then getter on Object.prototype being the script's own code that can reject the promise; that a failed run's read queues jobs that never run; and MAX_ENDING_BYTES's answers, rejected for the script's own ending and failed for an exception that ended the run. Tests, under Bun 1.4.3: with the last round's binding and module, four tests fail: a stop asked for once (the failure's name getter spins until it, its message getter would run on), a stop while a throw at the start is read (a later settle read it again), and the rows for a failure that can't be read out, whose messages changed. Taking out each new piece fails its test: the handler's latch, the order of a start's throw after a stop, and leaving the message's fallback out after a stop. The dropped answer for a resolve call stopped on its way in now has a test: it finds the k calls to an empty function before the script's await at which QuickJS's interrupt check falls on that call (the check comes at the first poll and once in 10,000 after), and taking out that dropped, or recording that stop as a failure, fails it. At the clean head, on Linux x86_64: tsc is clean; all 12 code_execution test files pass, 320 of 320, under Node 24.22.0 and Node 20.20.2 file by file and under Bun 1.4.3; and a rebuild from a fresh toolchain fetch gives the committed module, 600,080 bytes, sha256 6d774586. Co-Authored-By: Claude Opus 5.5 --- engine/interface.c | 58 ++++++++++++++----------- src/code-execution/engine.ts | 37 +++++++++------- src/code-execution/quickjs.wasm | Bin 600013 -> 600080 bytes test/code-execution-engine.test.ts | 65 ++++++++++++++++++++++++++++- 4 files changed, 118 insertions(+), 42 deletions(-) diff --git a/engine/interface.c b/engine/interface.c index af5a9bb2c..d9ae362f3 100644 --- a/engine/interface.c +++ b/engine/interface.c @@ -61,9 +61,10 @@ enum { /* Or'd into engine_settle's state when the result didn't fit and the call took the host's rejection instead. */ #define ENGINE_UNFIT 16 -/* Or'd into engine_settle's state when the call's promise never took the result, nor the host's - rejection: the run had been stopped or had failed before this settle, or the call that would - have handed it over was stopped or failed before it ran. Never with ENGINE_UNFIT. */ +/* Or'd into engine_settle's state when the call's resolve or reject function never ran with the + result, nor with the host's rejection: the run had been stopped or had failed before this + settle, or the call of that function was stopped or failed before it ran. Never with + ENGINE_UNFIT. */ #define ENGINE_DROPPED 32 static JSRuntime *rt; @@ -193,10 +194,12 @@ static const JSMallocFunctions limited_malloc_funcs = { limited_calloc, limited_malloc, limited_free, limited_realloc, limited_usable_size, }; +/* Once the host has stopped the guest, every later check stops it too, without asking again: a + failure's read runs guest code after a stop (its getters), and that code mustn't run on. */ static int interrupt_handler(JSRuntime *r, void *opaque) { (void)r; (void)opaque; - if (host_interrupt()) { + if (interrupted || host_interrupt()) { interrupted = true; return 1; } @@ -326,14 +329,18 @@ static void run_jobs(void) { } static int state(void) { - /* What ended the run comes first: nothing records a failure once the guest is stopped or an - import has failed, so a failure came before either, and stays the answer after one. */ - if (failed) - return failure_state; + /* An exception that escaped once the script ran comes first: nothing records one once the + guest is stopped or an import has failed, so it came before either, and stays the answer + after one. A throw as the run starts is the script's own ending, which a stop that comes + while it's read turns into a stop, as it does a rejection. */ + if (failed && failure_state == ENGINE_FAILED) + return ENGINE_FAILED; if (interrupted) return ENGINE_INTERRUPTED; if (host_failed) return ENGINE_HOST_FAILED; + if (failed) + return failure_state; switch (JS_PromiseState(ctx, completion)) { case JS_PROMISE_FULFILLED: return ENGINE_FULFILLED; @@ -502,10 +509,11 @@ static int settle(uint32_t id, int ok, char **json, uint32_t json_len, char **un binding's MAX_RESULT_DEPTH never gets here: under Node, a parse nested about 10,000 deep (5,000 with V8's optimizing tier alone) overflows the host's stack before QuickJS's own check sees the recursion, and the binding refuses results well short of that. When the run had been stopped - or had failed before this settle, or the call that hands the result over is stopped or fails - before it runs, the state answered has ENGINE_DROPPED set: the call's promise never took the - result. Once it has, the script's own code runs with it, a `then` getter the script put on - Object.prototype included, and a stop or failure in that code leaves the result handed over. + or had failed before this settle, or the call of the promise's resolve or reject function is + stopped or fails before it runs, the state answered has ENGINE_DROPPED set: that function never + ran with the result. Once it has, the result is handed over, and the script's own code runs + with it: a `then` getter the script put on Object.prototype, which can reject the promise + instead, and its continuations. A stop or failure in that code leaves the result handed over. json and unfit are the host's buffers from engine_alloc, each null or ending in a NUL at [len] as JS_ParseJSON requires, and this frees both, whatever it returns: the result's copy as soon @@ -538,12 +546,13 @@ static JSValue error_json(JSValue v) { stack = JS_GetPropertyStr(ctx, v, "stack"); /* An object with no message of its own, such as a plain object thrown, or one whose message can't be read, is told by its JSON, or by its string form when it has none, - rather than left blank. */ + rather than left blank. Once a stop has come, that's left out too: it would run more of + the script's code, which the stop stops, and give something other than the message. */ if (JS_IsException(message)) { JS_FreeValue(ctx, JS_GetException(ctx)); message = JS_UNDEFINED; } - if (JS_IsUndefined(message)) { + if (JS_IsUndefined(message) && !interrupted) { message = JS_JSONStringify(ctx, v, JS_UNDEFINED, JS_UNDEFINED); if (JS_IsException(message)) { JS_FreeValue(ctx, JS_GetException(ctx)); @@ -643,16 +652,17 @@ static uint8_t *read_ending(int st) { JSON.stringify leaves out), no text. Kind 1: fulfilled, the value's JSON. Kind 2: an error, as JSON {name?, message?, stack?}: what the script threw, why its value couldn't be stringified, or the exception that ended the run. - Returns 0 in any other state, when it can't allocate, or when a stop lands while the script's - own ending is read, which leaves none to give. The ending is fixed before it's read. The - script's own code that serializing runs (a toJSON, a getter), and the jobs it queues, which run - before this returns, can't reach the host; a reaction it attaches to a promise still pending - runs when that settles, as the script's other continuations do after its ending. A stop in - those jobs comes after the ending was read, so this returns the ending; a failure there ends - the run after its ending, which engine_state then answers. An exception that ended the run is - what ended it however its read goes, so a stop while it's read leaves out the parts the stop - cut off, and the rest is returned. Data nested deeper than the host's stack allows can overflow - it in JS_JSONStringify, which throws out of the module: the binding reports that as a crash. */ + Returns 0 in any other state, when it can't allocate, or when a stop lands while the script's own + ending is read, which leaves none to give. The ending is fixed before it's read. The script's own + code that serializing runs (a toJSON, a getter), and the jobs it queues, which run before this + returns unless the run has failed (then none run), can't reach the host; a reaction it attaches + to a promise still pending runs when that settles, as the script's other continuations do after + its ending. A stop in those jobs comes after the ending was read, so this returns the ending; a + failure there ends the run after its ending, which engine_state then answers. An exception that + ended the run is what ended it however its read goes, so a stop while it's read leaves out the + parts the stop cut off, and the rest is returned. Data nested deeper than the host's stack allows + can overflow it in JS_JSONStringify, which throws out of the module: the binding reports that as + a crash. */ EXPORT("engine_result") uint8_t *engine_result(void) { int st = state(); diff --git a/src/code-execution/engine.ts b/src/code-execution/engine.ts index 112d2ccf4..667aeeea9 100644 --- a/src/code-execution/engine.ts +++ b/src/code-execution/engine.ts @@ -38,9 +38,11 @@ export interface EngineSlots { hostCall(callId: number, name: string, argsJson: string): void; /** * Called while the guest runs, every few thousand of its operations. Return true to stop it for - * good: the call in progress answers `interrupted`, and so does every call after. The one - * exception is a stop in the jobs that reading the script's ending queued, after the ending was - * read whole: that call answers the ending, and the calls after it answer `interrupted`. + * good: the engine doesn't ask again, the call in progress answers `interrupted`, and so does + * every call after, with two exceptions. A stop in the jobs that reading the script's ending + * queued, after the ending was read whole, lets that call answer the ending, and the calls after + * it answer `interrupted`. And a run that has failed stays `failed`, a stop while its exception + * is read included. */ interrupt(): boolean; /** The guest called `__output(stream, text)`: stream 1 is out, 2 is err. */ @@ -67,8 +69,10 @@ export type RunState = * Once the script was running, an exception escaped its promises and ended the run, even after * its own ending: a callback it gave `queueMicrotask` or a `FinalizationRegistry` threw, or a job * failed in the engine itself; or the engine couldn't settle a call. The fields are the - * exception's, as for `rejected`, less any part a stop cut off while it was read. Every later - * call answers the same, a stop after it included, and no later result reaches the script. + * exception's, read as the engine reads a `rejected` one (an Error's name, message and stack; a + * value with no message of its own, its JSON or string form), less any part a stop cut off while + * it was read. Every later call answers the same, a stop after it included, and no later result + * reaches the script. */ | { kind: 'failed'; name: string; message: string; stack?: string } /** `interrupt()` returned true while it ran. */ @@ -77,12 +81,13 @@ export type RunState = /** * What `settle` answers: the run's state; `unfit` when the guest couldn't take the result, for * want of memory to copy it in or of memory or stack to parse it, and the call took the child's - * own error instead; and `dropped` when the call never took either, because the run had been - * stopped or had failed before this settle (`interrupted` or `failed`), or the step that hands it - * over was stopped or failed before it ran. Without `dropped`, the call's promise took the result - * or the child's error, and the script's own code runs with it, a `then` getter it put on - * Object.prototype included: a stop or failure in that code, or in the jobs after, leaves it - * taken. The two are never both set. + * own error instead; and `dropped` when neither was handed over, because the run had been stopped + * or had failed before this settle (`interrupted` or `failed`), or the call of the promise's + * resolve or reject function was stopped or failed before it ran. Without `dropped`, that function + * ran with the result or the child's error, and the script's own code runs with it: a `then` + * getter it put on Object.prototype, which can reject the promise instead, and its continuations. + * A stop or failure in that code, or in the jobs after, leaves it handed over. The two are never + * both set. */ export type SettleState = RunState & { unfit?: true; dropped?: true }; @@ -164,9 +169,9 @@ const DIAGNOSTICS_CHARS = 4096; * The longest ending text the binding decodes, in UTF-8 bytes: V8's longest string, 2^29 - 24 * characters (Node's `buffer.constants.MAX_STRING_LENGTH`), which a text of this many bytes can't * exceed. Node can't decode a longer one. Bun's longest string is 2^31 - 1, and the same bound - * holds there, so a script ends the same way on either. A longer text is answered, as the state it - * belongs to (`rejected` for the script's own ending, `failed` for an exception that ended the - * run), with a RangeError that gives its length. + * holds there, so a script ends the same way on either. A longer text is answered with a + * RangeError that gives its length: `rejected` for the script's own ending, its value's or what it + * threw, and `failed` for an exception that ended the run. */ const MAX_ENDING_BYTES = 2 ** 29 - 24; @@ -428,8 +433,8 @@ export async function loadEngine(limits: EngineLimits, slots: EngineSlots): Prom long: (n: number) => `what the script threw comes to ${n} bytes as text, more than the ${MAX_ENDING_BYTES} the engine reads back; throw something smaller`, }, [FAILED]: { - memory: 'out of memory while reading the exception that ended the run; throw something smaller, catch it in the callback that threw it, or let go of data the script no longer needs', - long: (n: number) => `the exception that ended the run comes to ${n} bytes as text, more than the ${MAX_ENDING_BYTES} the engine reads back; throw something smaller, or catch it in the callback that threw it`, + memory: 'out of memory while reading the exception that ended the run; let go of data the script no longer needs, and if a callback given to queueMicrotask or a FinalizationRegistry threw it, throw something smaller or catch it there', + long: (n: number) => `the exception that ended the run comes to ${n} bytes as text, more than the ${MAX_ENDING_BYTES} the engine reads back; if a callback given to queueMicrotask or a FinalizationRegistry threw it, throw something smaller or catch it there`, }, } as const; /** diff --git a/src/code-execution/quickjs.wasm b/src/code-execution/quickjs.wasm index c706a22842a1cff798dea6f834a93eba506656f0..4c3795f6d9c59175fb7dd04588807ac01417c94b 100644 GIT binary patch delta 271 zcmX?mUS+}sl?^d`jGs2g@+}C~(qeFAP-I|oa9r7coI#g?!Ii~9fWeiQk=v0`kpV2| z$Y{mDz^%ZrStRLes7E{_bG_q6h*~8ESH~?trUny(5~Cxldl68xBP%~Q%q$>}nIEXr zaTQcOQxQ;zMS)R)NqVyNjv~g5lh^IYWn`Hwv(rKZDD2b#1O?4t>H$L_i^JqwJGC{y zTmwZ$Zd3&ZLA;DGiyRpXc^M}!+^IbMhCPeus^T-kq|!I9C50Z43CPx=~a{FtRI>%h(qMwAgorT%^{s-dV40kQiNxK+Cf diff --git a/test/code-execution-engine.test.ts b/test/code-execution-engine.test.ts index 1e030de8d..c5a480172 100644 --- a/test/code-execution-engine.test.ts +++ b/test/code-execution-engine.test.ts @@ -444,7 +444,7 @@ test('an ending the module can\'t read out is answered as out of memory, and tha for (const [script, kind, message] of [ ['tool("a"); tool("b"); ({ ok: true })', 'rejected', 'out of memory while serializing the script\'s return value; return less, or let go of data the script no longer needs'], ['tool("a"); tool("b"); throw new Error("x")', 'rejected', 'out of memory while reading what the script threw; throw something smaller, such as an Error with a shorter message, or let go of data the script no longer needs'], - ['tool("a"); tool("b"); queueMicrotask(() => { throw new TypeError("x") }); 1', 'failed', 'out of memory while reading the exception that ended the run; throw something smaller, catch it in the callback that threw it, or let go of data the script no longer needs'], + ['tool("a"); tool("b"); queueMicrotask(() => { throw new TypeError("x") }); 1', 'failed', 'out of memory while reading the exception that ended the run; let go of data the script no longer needs, and if a callback given to queueMicrotask or a FinalizationRegistry threw it, throw something smaller or catch it there'], ] as const) { let reads = 0; const { e } = await engineAround((ex) => ({ engine_result: () => (++reads === 1 ? 0 : ex.engine_result()) })); @@ -464,7 +464,7 @@ test('an ending longer than the binding can decode is answered with its length, for (const [script, length, kind, message] of [ ['({ ok: true })', 600_000_000, 'rejected', 'the script\'s return value comes to 600000000 bytes as text, more than the 536870888 the engine reads back; return less'], ['throw new Error("x")', 600_000_000, 'rejected', 'what the script threw comes to 600000000 bytes as text, more than the 536870888 the engine reads back; throw something smaller'], - ['queueMicrotask(() => { throw new TypeError("x") }); 1', 600_000_000, 'failed', 'the exception that ended the run comes to 600000000 bytes as text, more than the 536870888 the engine reads back; throw something smaller, or catch it in the callback that threw it'], + ['queueMicrotask(() => { throw new TypeError("x") }); 1', 600_000_000, 'failed', 'the exception that ended the run comes to 600000000 bytes as text, more than the 536870888 the engine reads back; if a callback given to queueMicrotask or a FinalizationRegistry threw it, throw something smaller or catch it there'], // One byte past the bound is refused too. ['({ ok: true })', 536_870_889, 'rejected', 'the script\'s return value comes to 536870889 bytes as text, more than the 536870888 the engine reads back; return less'], ] as const) { @@ -555,6 +555,67 @@ test('a stop while the exception that ended the run is read leaves the run faile b.e.dispose(); }); +test('a stop asked for once stays a stop, and what it cuts off a failure\'s read is left out, not made up', async () => { + // The interrupt slot says stop once only. The thrown object's name getter spins until then; its + // message getter would spin on for a while, and then give "late". Neither runs on after the stop, + // and the message isn't replaced by the object's JSON or string form. + let checks = 0; + const { e } = await engine({ interrupt: () => ++checks === 101 }); + const s = e.start(PRELUDE, 'queueMicrotask(() => { throw { get name() { for (;;) {} }, get message() { for (let i = 0; i < 3e7; i++) {} return "late" } } }); 1', 's.js'); + assert.deepEqual(s, { kind: 'failed', name: '', message: '' }); + assert.equal(checks, 101); + e.dispose(); +}); + +test('a stop while a throw at the start is read stays a stop, which later calls answer without reading it again', async () => { + // The prelude makes a call, then throws an object whose message getter spins until the stop: + // the script's own ending, cut off by a stop, as a rejection's would be. + let checks = 0, reads = 0; + const { e } = await engineAround((ex) => ({ engine_result: () => { reads++; return ex.engine_result(); } }), { interrupt: () => ++checks > 100 }); + const prelude = '__host("a", "{}"); throw { get message() { for (;;) {} } };'; + assert.deepEqual(e.start(prelude, '1', 's.js'), { kind: 'interrupted' }); + assert.deepEqual(e.settle(1, true, '1', UNFIT), { kind: 'interrupted', dropped: true }); + assert.equal(reads, 1, 'the ending was read again'); + e.dispose(); +}); + +test('a settle whose resolve call is stopped on its way in is dropped, and the stop isn\'t taken for a failure', async () => { + // QuickJS asks the interrupt slot at its first check and then once in 10,000, and makes a check + // on the way into each call. A script making k calls to an empty function before it awaits call + // 2 puts the next asking at a place set by k: the largest k whose start asks no more than with + // none puts it on the first check the settle makes, the one on the way into the promise's + // resolve function. + const script = (k: number) => `const f = () => {}; tool("a"); ${'f();'.repeat(k)} const v = await tool("b"); print("ran"); v`; + async function askedInStart(k: number) { + let asked = 0; + const { e } = await engine({ interrupt: () => { asked++; return false; } }); + e.start(PRELUDE, script(k), 's.js'); + e.dispose(); + return asked; + } + const none = await askedInStart(0); + let lo = 0, hi = 20_000; + assert.ok((await askedInStart(hi)) > none); + while (hi - lo > 1) { + const mid = (lo + hi) >> 1; + if ((await askedInStart(mid)) > none) hi = mid; + else lo = mid; + } + async function settledStopped(k: number) { + let stop = false; + const { e, rec } = await engine({ interrupt: () => stop }); + e.start(PRELUDE, script(k), 's.js'); + stop = true; + const s = e.settle(2, true, '2', UNFIT); + e.dispose(); + return { s, ran: rec.output.length > 0 }; + } + assert.deepEqual(await settledStopped(lo), { s: { kind: 'interrupted', dropped: true }, ran: false }); + // One call fewer, the asking falls just after the resolve function ran: the result is handed over. + const after = await settledStopped(lo - 1); + assert.equal(after.s.dropped, undefined, JSON.stringify(after)); +}); + test('a result that reaches a stopped run is dropped', async () => { let stop = false; const { e } = await engine({ interrupt: () => stop }); From bd62c6f886baea3aea7136146dd2cbe9f74ef237 Mon Sep 17 00:00:00 2001 From: Gladys-2592 Date: Mon, 19 Oct 2026 07:41:59 -0400 Subject: [PATCH 22/41] fix(engine): vendor.sh's restore command puts back what's committed, its cleanup reports what it can't remove, and its comment says which signals run it From the round review of the engine's last round: - The restore command it prints, git checkout -- engine/quickjs-ng, restored the copy from the index, so a staged change survived it. It's now git checkout HEAD -- engine/quickjs-ng, and the refusal over changes says to remove any file it lists as untracked or ignored, which no checkout removes. - The cleanup ignores signals from its first command, before turning errexit off, and also ignores PIPE, ALRM, USR1, USR2 and XCPU, which bash runs it for as well. - When the new copy is in place but the committed copy moved aside can't be removed, the cleanup says so, as the script's own path does. - The comment says what was checked: bash 5.2 runs the exit trap on exit, on INT, TERM and HUP, trapped so they wait for the command in progress, and on PIPE, ALRM, USR1, USR2 and XCPU; SIGKILL skips it; a signal ignored when the script started, as SIGINT is for a background job of a non-interactive shell, stays ignored, and the script carries on. Checked in a scratch clone with bash 5.2.21, git 2.55.0 and quickjs-ng v0.17.0: a staged edit to list.h and an untracked file are refused, and the printed command, run from /, restored list.h, index and file, leaving only the untracked file; SIGTERM after the new copy is in place, with the committed copy moved aside made undeletable, exits 143 and says it couldn't be removed; SIGUSR1, SIGALRM and SIGPIPE while the clone runs leave the copy unchanged and nothing behind; SIGTERM between the two renames puts the committed copy back; a clean run matches the committed copy. A script with only an EXIT trap ran it on USR1, USR2, ALRM, PIPE, XCPU, HUP and TERM; SIGQUIT didn't end it. Co-Authored-By: Claude Opus 5.5 --- engine/vendor.sh | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/engine/vendor.sh b/engine/vendor.sh index 8753676e6..2afd0cc3f 100755 --- a/engine/vendor.sh +++ b/engine/vendor.sh @@ -32,25 +32,28 @@ FILES=( # Physical paths, as git gives the repository's, so the one can be taken from the other. here=$(cd "$(dirname "$0")" && pwd -P) dest=$here/quickjs-ng -# The command that restores the committed copy, from any directory. +# The command that puts back the committed copy's files, from the commit, from any directory. top=$(git -C "$here" rev-parse --show-toplevel) -restore="git -C $(printf '%q' "$top") checkout -- $(printf '%q' "${dest#"$top"/}")" +restore="git -C $(printf '%q' "$top") checkout HEAD -- $(printf '%q' "${dest#"$top"/}")" changes=$(git -C "$here" status --porcelain --untracked-files=all --ignored -- quickjs-ng) if [ -n "$changes" ]; then - echo "vendor.sh: engine/quickjs-ng holds changes or files not committed, which replacing it would erase; commit or remove what you made, or run $restore to put back what's committed:" >&2 + echo "vendor.sh: engine/quickjs-ng holds changes or files not committed, which replacing it would erase; commit what you mean to keep, or run $restore to put back what's committed and remove any file listed below as untracked (??) or ignored (!!):" >&2 echo "$changes" >&2 exit 1 fi -# Whenever the script exits, short of being killed outright (SIGKILL), a committed copy left moved -# aside goes back, unless the new one is in its place, and what was staged goes. A signal waits -# for the command in progress, and one during this cleanup is ignored. +# Whenever the script exits, a committed copy left moved aside goes back, unless the new one is in +# its place, and what was staged goes. bash runs this on a signal that ends the shell too: INT, +# TERM and HUP are trapped below, so they wait for the command in progress, and PIPE, ALRM, USR1, +# USR2 and XCPU were checked to run it as well. Signals are ignored while it runs. SIGKILL, which +# no trap sees, skips it. A signal ignored when the script started, as SIGINT is for a background +# job of a non-interactive shell, stays ignored, and the script carries on. tmp= stage= old= finish() { + trap '' HUP INT PIPE ALRM TERM USR1 USR2 XCPU set +e - trap '' INT TERM HUP if [ -n "$old" ] && [ -e "$old" ]; then if [ -e "$dest" ]; then - rm -rf "$old" + rm -rf "$old" || echo "vendor.sh: the new copy is in place, but the committed copy moved aside to $old couldn't be removed; remove it." >&2 elif mv "$old" "$dest"; then echo "vendor.sh: stopped while replacing the copy; the committed copy is back in place." >&2 else From 6f9c7bf5517a15a36a2ee52fc79076d3c7dc116b Mon Sep 17 00:00:00 2001 From: Gladys-2592 Date: Mon, 19 Oct 2026 10:37:28 -0400 Subject: [PATCH 23/41] fix(code-execution): once a stop has come, a failure with no message gets no stand-in for one, its string form included From the round review of the engine's last round: - After a stop, a failure with no message of its own was still given its string form when the stop landed inside its JSON (a toJSON that spins): the guard covered only the JSON. Both stand-ins are now left out once a stop has come, since each runs more of the script's code and neither is the message. The parts themselves are still read after a stop, and code that spins there is ended at the next check. - The last round's message claimed a test failed without the JSON's guard; none did, since that test's object had an enumerable getter its JSON reached. A test now pins each guard: a non-enumerable name getter ended by the stop (the JSON would be {"code":5}), and a toJSON ended by it (the toString would give "made up"). - Comments and docs: the interrupt handler's latch says a check ends the script's code at the next check, QuickJS checking at its first poll and once in 10,000 after, rather than at once; engine_result says the read's jobs run until the run fails or is stopped; the interrupt slot's doc says a throw at the start is answered rejected, which a stop while it's read turns into interrupted; a failed state's doc says no stand-in is made after a stop. - The test for a resolve call stopped on its way in checks one call fewer only when there is one. Tests, under Bun 1.4.3: with the last round's binding and module, the new test fails; taking out either guard fails it, and taking out the interrupt handler's latch fails the test of a stop asked for once. At the clean head, on Linux x86_64: tsc is clean; all 12 code_execution test files pass, 379 of 379, under Node 24.22.0 and Node 20.20.2 file by file and under Bun 1.4.3; and a rebuild from a fresh toolchain fetch gives the committed module, 600,099 bytes, sha256 1091cfd0. Co-Authored-By: Claude Opus 5.5 --- engine/interface.c | 30 +++++++++++++++++------------ src/code-execution/engine.ts | 9 +++++---- src/code-execution/quickjs.wasm | Bin 600080 -> 600099 bytes test/code-execution-engine.test.ts | 22 +++++++++++++++++++-- 4 files changed, 43 insertions(+), 18 deletions(-) diff --git a/engine/interface.c b/engine/interface.c index d9ae362f3..99bb8313a 100644 --- a/engine/interface.c +++ b/engine/interface.c @@ -194,8 +194,11 @@ static const JSMallocFunctions limited_malloc_funcs = { limited_calloc, limited_malloc, limited_free, limited_realloc, limited_usable_size, }; -/* Once the host has stopped the guest, every later check stops it too, without asking again: a - failure's read runs guest code after a stop (its getters), and that code mustn't run on. */ +/* Once the host has stopped the guest, every later check stops it too, without asking again, so + a stop holds for good even if the host would say otherwise later. A failure's read runs the + script's code after a stop (getters of what it threw), and a check ends that code. QuickJS + checks at its first poll and once in 10,000 after, so code runs on until the next check, not + less. */ static int interrupt_handler(JSRuntime *r, void *opaque) { (void)r; (void)opaque; @@ -531,7 +534,10 @@ int engine_settle(uint32_t id, int ok, char *json, uint32_t json_len, char *unfi } /* The text of a value or an error, for engine_result. Takes v. Returns a JS string, undefined, - or an exception. */ + or an exception. Reading an object's parts can run the script's own code (a getter, a toString), + and after a stop they're still read: code that spins is ended at the next check, which leaves + that part out. But once a stop has come, no stand-in is made for a missing message, since its + JSON or string form would run more of the script's code and wouldn't be the message. */ static JSValue error_json(JSValue v) { /* No prototype, so a toJSON the script put on Object.prototype doesn't serialize it. */ JSValue o = JS_NewObjectProto(ctx, JS_NULL); @@ -546,8 +552,7 @@ static JSValue error_json(JSValue v) { stack = JS_GetPropertyStr(ctx, v, "stack"); /* An object with no message of its own, such as a plain object thrown, or one whose message can't be read, is told by its JSON, or by its string form when it has none, - rather than left blank. Once a stop has come, that's left out too: it would run more of - the script's code, which the stop stops, and give something other than the message. */ + rather than left blank. */ if (JS_IsException(message)) { JS_FreeValue(ctx, JS_GetException(ctx)); message = JS_UNDEFINED; @@ -558,10 +563,11 @@ static JSValue error_json(JSValue v) { JS_FreeValue(ctx, JS_GetException(ctx)); message = JS_UNDEFINED; } - if (JS_IsUndefined(message)) + if (JS_IsUndefined(message) && !interrupted) message = JS_ToString(ctx, v); } } else { + /* A primitive's string form runs no script code (a Symbol has none, and is left out). */ message = JS_ToString(ctx, v); } /* A getter that throws, or a value with no string form, leaves that part out. */ @@ -655,14 +661,14 @@ static uint8_t *read_ending(int st) { Returns 0 in any other state, when it can't allocate, or when a stop lands while the script's own ending is read, which leaves none to give. The ending is fixed before it's read. The script's own code that serializing runs (a toJSON, a getter), and the jobs it queues, which run before this - returns unless the run has failed (then none run), can't reach the host; a reaction it attaches - to a promise still pending runs when that settles, as the script's other continuations do after - its ending. A stop in those jobs comes after the ending was read, so this returns the ending; a + returns until the run fails or is stopped, can't reach the host; a reaction it attaches to a + promise still pending runs when that settles, as the script's other continuations do after its + ending. A stop in those jobs comes after the ending was read, so this returns the ending; a failure there ends the run after its ending, which engine_state then answers. An exception that ended the run is what ended it however its read goes, so a stop while it's read leaves out the - parts the stop cut off, and the rest is returned. Data nested deeper than the host's stack allows - can overflow it in JS_JSONStringify, which throws out of the module: the binding reports that as - a crash. */ + parts the stop cut off (see error_json), and the rest is returned. Data nested deeper than the + host's stack allows can overflow it in JS_JSONStringify, which throws out of the module: the + binding reports that as a crash. */ EXPORT("engine_result") uint8_t *engine_result(void) { int st = state(); diff --git a/src/code-execution/engine.ts b/src/code-execution/engine.ts index 667aeeea9..36ec1f34e 100644 --- a/src/code-execution/engine.ts +++ b/src/code-execution/engine.ts @@ -41,8 +41,9 @@ export interface EngineSlots { * good: the engine doesn't ask again, the call in progress answers `interrupted`, and so does * every call after, with two exceptions. A stop in the jobs that reading the script's ending * queued, after the ending was read whole, lets that call answer the ending, and the calls after - * it answer `interrupted`. And a run that has failed stays `failed`, a stop while its exception - * is read included. + * it answer `interrupted`. And a run answered `failed` stays so, a stop while its exception is + * read included; a throw as the run starts is the script's own ending, answered `rejected`, which + * a stop while it's read turns into `interrupted`, as for any rejection. */ interrupt(): boolean; /** The guest called `__output(stream, text)`: stream 1 is out, 2 is err. */ @@ -71,8 +72,8 @@ export type RunState = * failed in the engine itself; or the engine couldn't settle a call. The fields are the * exception's, read as the engine reads a `rejected` one (an Error's name, message and stack; a * value with no message of its own, its JSON or string form), less any part a stop cut off while - * it was read. Every later call answers the same, a stop after it included, and no later result - * reaches the script. + * it was read; after a stop, no stand-in is made for a missing message. Every later call answers + * the same, a stop after it included, and no later result reaches the script. */ | { kind: 'failed'; name: string; message: string; stack?: string } /** `interrupt()` returned true while it ran. */ diff --git a/src/code-execution/quickjs.wasm b/src/code-execution/quickjs.wasm index 4c3795f6d9c59175fb7dd04588807ac01417c94b..ee2b84c7f86a88b9963b57f3652d383d85d3a38f 100644 GIT binary patch delta 427 zcmbPmL1pm;l?^d`Os$JI$MIFH;yc2|#8Ssvugu`6kgdcq`N`*y}X9Q-)|Gp%C4 delta 450 zcmZ2{L1n@Pl?^d`OrI8Qj^nFX#kY}-iM5WkUYWsBAzO)a@{=8ojBJzDb}9o&ublyM z9F81W8cYmIY?{m+tcr{ZY)%b8aDc%a$W`E&Y_Q8@@|&F!^##pJtY8^~AQlG&2=f6$ z5HFKsPycZSkOBA&QeYHN;fASIWaCy~Q(zF7&Zx-9t-z_kV$Li8q!`VaIe-+CIWvO- zry_@Ai6R@bg900i1Cs+2&?z%^DQdDqUCRY_Et3KlvTNBDm;_XK7`d5%nkGNm6&^x_ zY9?+4Mnxus%YbG$DRemf{Qv(yf2|W!6wtFUV}T52puxSnr40!i4D_HQOO_&=0w`D% oIVRuSZN { + // Each thrown object has no message. Without the stop, the engine would tell it by its JSON or + // its string form; here the first a non-enumerable name getter ends at the stop (its JSON would + // be {"code":5}), and the second's toJSON does (its toString would give "made up"). + for (const thrown of [ + 'Object.defineProperty({ code: 5 }, "name", { get() { for (;;) {} } })', + '{ toJSON() { for (;;) {} }, toString() { return "made up" } }', + ]) { + let checks = 0; + const { e } = await engine({ interrupt: () => ++checks > 100 }); + const s = e.start(PRELUDE, `queueMicrotask(() => { throw ${thrown} }); 1`, 's.js'); + assert.deepEqual(s, { kind: 'failed', name: '', message: '' }, thrown); + e.dispose(); + } +}); + test('a stop while a throw at the start is read stays a stop, which later calls answer without reading it again', async () => { // The prelude makes a call, then throws an object whose message getter spins until the stop: // the script's own ending, cut off by a stop, as a rejection's would be. @@ -612,8 +628,10 @@ test('a settle whose resolve call is stopped on its way in is dropped, and the s } assert.deepEqual(await settledStopped(lo), { s: { kind: 'interrupted', dropped: true }, ran: false }); // One call fewer, the asking falls just after the resolve function ran: the result is handed over. - const after = await settledStopped(lo - 1); - assert.equal(after.s.dropped, undefined, JSON.stringify(after)); + if (lo > 0) { + const after = await settledStopped(lo - 1); + assert.equal(after.s.dropped, undefined, JSON.stringify(after)); + } }); test('a result that reaches a stopped run is dropped', async () => { From 0fd437adcc1ecff79a931b983fb42613877c7d96 Mon Sep 17 00:00:00 2001 From: Gladys-2592 Date: Mon, 19 Oct 2026 10:37:29 -0400 Subject: [PATCH 24/41] fix(engine): vendor.sh's restore drops what the commit doesn't hold, it refuses over leftovers of a run killed outright, and its cleanup holds against a second signal From the round review of the engine's last round: - git checkout HEAD -- engine/quickjs-ng restored what the commit holds but kept a file staged as added, or a rename's new name, so following the advice left the refusal in place. The restore is git checkout --no-overlay HEAD, which drops them (git 2.22 and later). The refusal over changes says to run it and then the script again, which lists what's left that git doesn't track or ignores, rather than listing, before the restore, files it may bring back. - A run killed outright leaves its stage, or the committed copy moved aside, as a hidden .quickjs-ng.* directory in engine/, which no refusal named. The script now refuses while one is there, naming them. - Each signal trap ignores the signals that end a shell before it exits, as the cleanup's first line does, so a second signal can't cut the cleanup short; XFSZ joins them. - When the committed copy moved aside couldn't be removed, the notice was given twice, by the script and again by its cleanup; now once. - The comment names the bash version its signal claims were checked on, and that bash ignores QUIT. Checked in a scratch clone with bash 5.2.21, git 2.55.0 and quickjs-ng v0.17.0: a staged addition, a staged rename, and a file deleted from the index but kept on disk (listed as D and ??) are each refused, and the printed command, run from /, put the copy back as committed, the last on running the script again; leftover .quickjs-ng.* directories are refused by name; a removal of the old copy that fails gives one notice; SIGINT and SIGTERM while the clone runs exit 130 and 143 with nothing left behind; SIGTERM between the two renames puts the committed copy back; a clean run matches the committed copy. Co-Authored-By: Claude Opus 5.5 --- engine/vendor.sh | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/engine/vendor.sh b/engine/vendor.sh index 2afd0cc3f..49a823991 100755 --- a/engine/vendor.sh +++ b/engine/vendor.sh @@ -32,24 +32,35 @@ FILES=( # Physical paths, as git gives the repository's, so the one can be taken from the other. here=$(cd "$(dirname "$0")" && pwd -P) dest=$here/quickjs-ng -# The command that puts back the committed copy's files, from the commit, from any directory. +# The command that puts back the committed copy's files from the commit, from any directory: with +# --no-overlay (git 2.22 and later), a file the index holds and the commit doesn't goes too. top=$(git -C "$here" rev-parse --show-toplevel) -restore="git -C $(printf '%q' "$top") checkout HEAD -- $(printf '%q' "${dest#"$top"/}")" +restore="git -C $(printf '%q' "$top") checkout --no-overlay HEAD -- $(printf '%q' "${dest#"$top"/}")" +# A stage or a committed copy moved aside, left by a run that was killed outright. +shopt -s nullglob dotglob +left=("$here"/.quickjs-ng.*) +shopt -u nullglob dotglob +if [ ${#left[@]} -gt 0 ]; then + echo "vendor.sh: engine/ holds ${left[*]##*/}, left by a run that was killed outright; remove them (what's committed is in git, and $restore puts it back). engine/quickjs-ng is unchanged." >&2 + exit 1 +fi changes=$(git -C "$here" status --porcelain --untracked-files=all --ignored -- quickjs-ng) if [ -n "$changes" ]; then - echo "vendor.sh: engine/quickjs-ng holds changes or files not committed, which replacing it would erase; commit what you mean to keep, or run $restore to put back what's committed and remove any file listed below as untracked (??) or ignored (!!):" >&2 + echo "vendor.sh: engine/quickjs-ng holds changes or files not committed, which replacing it would erase; commit what you mean to keep, or run $restore to put back what's committed, then this again, which lists any file left that git doesn't track (??) or ignores (!!), for you to remove:" >&2 echo "$changes" >&2 exit 1 fi # Whenever the script exits, a committed copy left moved aside goes back, unless the new one is in -# its place, and what was staged goes. bash runs this on a signal that ends the shell too: INT, +# its place, and what was staged goes. bash 5.2 runs this on a signal that ends the shell too: INT, # TERM and HUP are trapped below, so they wait for the command in progress, and PIPE, ALRM, USR1, -# USR2 and XCPU were checked to run it as well. Signals are ignored while it runs. SIGKILL, which -# no trap sees, skips it. A signal ignored when the script started, as SIGINT is for a background -# job of a non-interactive shell, stays ignored, and the script carries on. +# USR2 and XCPU were checked to run it as well (bash ignores QUIT). The signals named in its first +# line, and in the traps below, are ignored from then on. SIGKILL, which no trap sees, skips it, and +# leaves what the next run refuses over. A signal ignored when the script started, as SIGINT is for +# a background job of a non-interactive shell, stays ignored, and the script carries on. tmp= stage= old= +ignore_signals='trap "" HUP INT PIPE ALRM TERM USR1 USR2 XCPU XFSZ' finish() { - trap '' HUP INT PIPE ALRM TERM USR1 USR2 XCPU + eval "$ignore_signals" set +e if [ -n "$old" ] && [ -e "$old" ]; then if [ -e "$dest" ]; then @@ -64,9 +75,9 @@ finish() { [ -n "$stage" ] && rm -rf "$stage" } trap finish EXIT -trap 'exit 129' HUP -trap 'exit 130' INT -trap 'exit 143' TERM +trap "$ignore_signals; exit 129" HUP +trap "$ignore_signals; exit 130" INT +trap "$ignore_signals; exit 143" TERM tmp=$(mktemp -d) # The new copy is staged beside the old one, on the same filesystem: once it's whole, the old one # is moved aside, the stage renamed into its place, and only then is the old one removed. Killed @@ -147,6 +158,7 @@ if ! mv "$stage" "$dest"; then fi if [ -e "$old" ] && ! rm -rf "$old"; then echo "vendor.sh: the new copy is in place, but the committed copy moved aside to $old couldn't be removed; remove it." >&2 + old= exit 1 fi echo "copied ${#FILES[@]} files from quickjs-ng $TAG ($COMMIT), with $patched patches" From 6062d657cb00fc0f8d1c39c210d0ec58e2949dcb Mon Sep 17 00:00:00 2001 From: Gladys-2592 Date: Mon, 19 Oct 2026 13:17:15 -0400 Subject: [PATCH 25/41] fix(code-execution): a stop ends a failure's read, and nothing after it is read From the round review of the engine's last round: - After a stop, a failure's read still read its other parts and made a non-string name or message into a string, which can run the script's getters and toString after the stop, and a stand-in made before the stop was kept beside a part the stop cut. One rule now: a stop ends the read. What was read before it stays, and nothing more is read, neither a part, a part's string form, nor a stand-in for a missing message. The message is read first, as the part most worth keeping, so a spinning name or stack doesn't take it. - With that, nothing the engine does runs the script's code after a stop, so the interrupt handler's latch is never reached today. It stays, so that a stop holds if a later change runs code after one, and its comment says so; no test can show it. - Comments and docs: a stack that isn't a string is left out; a thrown Symbol leaves the message empty, since it has no string form here; start and settle run jobs until none is left, the guest is interrupted, or the run fails. - The test for a resolve call stopped on its way in moves the check by one when the search finds k = 0, and fails if it still does, rather than skipping its second half. Tests, under Bun 1.4.3: with the last round's binding and module, the new test fails: a one-shot stop in a spinning message getter leaves a name getter that would give "made up" unread, and a stop in a spinning stack getter leaves a name object's toString unmade. Taking out each guard fails a test: before the name's read, before a part's string form, and on each stand-in; reading the name before the message fails two. Taking out the latch fails none. At the clean head, on Linux x86_64: tsc is clean; all 12 code_execution test files pass, 387 of 387, under Node 24.22.0 and Node 20.20.2 file by file and under Bun 1.4.3; and a rebuild from a fresh toolchain fetch gives the committed module, 600,155 bytes, sha256 244cd3c0. Co-Authored-By: Claude Opus 5.5 --- engine/interface.c | 32 ++++++++++------- src/code-execution/engine.ts | 10 +++--- src/code-execution/quickjs.wasm | Bin 600099 -> 600155 bytes test/code-execution-engine.test.ts | 56 +++++++++++++++++++---------- 4 files changed, 62 insertions(+), 36 deletions(-) diff --git a/engine/interface.c b/engine/interface.c index 99bb8313a..e31bff3a0 100644 --- a/engine/interface.c +++ b/engine/interface.c @@ -195,10 +195,10 @@ static const JSMallocFunctions limited_malloc_funcs = { }; /* Once the host has stopped the guest, every later check stops it too, without asking again, so - a stop holds for good even if the host would say otherwise later. A failure's read runs the - script's code after a stop (getters of what it threw), and a check ends that code. QuickJS - checks at its first poll and once in 10,000 after, so code runs on until the next check, not - less. */ + a stop holds for good even if the host would say otherwise later. Nothing the engine does runs + the script's code after a stop today: every entry point checks the flag first, and error_json + ends its read at one. This keeps the stop if that ever changes. QuickJS checks at its first + poll and once in 10,000 after, so a check ends the code running then, not at once. */ static int interrupt_handler(JSRuntime *r, void *opaque) { (void)r; (void)opaque; @@ -534,10 +534,10 @@ int engine_settle(uint32_t id, int ok, char *json, uint32_t json_len, char *unfi } /* The text of a value or an error, for engine_result. Takes v. Returns a JS string, undefined, - or an exception. Reading an object's parts can run the script's own code (a getter, a toString), - and after a stop they're still read: code that spins is ended at the next check, which leaves - that part out. But once a stop has come, no stand-in is made for a missing message, since its - JSON or string form would run more of the script's code and wouldn't be the message. */ + or an exception. Reading an object's parts can run the script's own code (a getter, a toJSON, a + toString), and a stop ends the read: what was read before it stays, and nothing more is read, + neither a part nor a stand-in for a missing message. The message is read first, as the part + most worth keeping. */ static JSValue error_json(JSValue v) { /* No prototype, so a toJSON the script put on Object.prototype doesn't serialize it. */ JSValue o = JS_NewObjectProto(ctx, JS_NULL); @@ -547,9 +547,12 @@ static JSValue error_json(JSValue v) { } JSValue name = JS_UNDEFINED, message = JS_UNDEFINED, stack = JS_UNDEFINED; if (JS_IsObject(v)) { - name = JS_GetPropertyStr(ctx, v, "name"); - message = JS_GetPropertyStr(ctx, v, "message"); - stack = JS_GetPropertyStr(ctx, v, "stack"); + if (!interrupted) + message = JS_GetPropertyStr(ctx, v, "message"); + if (!interrupted) + name = JS_GetPropertyStr(ctx, v, "name"); + if (!interrupted) + stack = JS_GetPropertyStr(ctx, v, "stack"); /* An object with no message of its own, such as a plain object thrown, or one whose message can't be read, is told by its JSON, or by its string form when it has none, rather than left blank. */ @@ -570,7 +573,8 @@ static JSValue error_json(JSValue v) { /* A primitive's string form runs no script code (a Symbol has none, and is left out). */ message = JS_ToString(ctx, v); } - /* A getter that throws, or a value with no string form, leaves that part out. */ + /* A getter that throws, a name or message with no string form, or a stack that isn't a string + leaves that part out. */ JSValue parts[3] = {name, message, stack}; const char *keys[3] = {"name", "message", "stack"}; for (int i = 0; i < 3; i++) { @@ -580,6 +584,10 @@ static JSValue error_json(JSValue v) { continue; } if (i < 2 && !JS_IsUndefined(p) && !JS_IsString(p)) { + if (interrupted) { + JS_FreeValue(ctx, p); + continue; + } JSValue s = JS_ToString(ctx, p); JS_FreeValue(ctx, p); if (JS_IsException(s)) { diff --git a/src/code-execution/engine.ts b/src/code-execution/engine.ts index 36ec1f34e..81a33ad10 100644 --- a/src/code-execution/engine.ts +++ b/src/code-execution/engine.ts @@ -62,8 +62,8 @@ export type RunState = /** * The prelude or the script threw, or its value couldn't be stringified (a BigInt, a cycle). * The fields are what was thrown: an Error's name, message and stack. A thrown value with no - * message of its own has name `''`, and as its message an object's JSON, or else - * `String(value)`. + * message of its own has name `''`, and as its message an object's JSON, or else its string + * form; a Symbol, which has none here, leaves the message `''`. */ | { kind: 'rejected'; name: string; message: string; stack?: string } /** @@ -72,7 +72,7 @@ export type RunState = * failed in the engine itself; or the engine couldn't settle a call. The fields are the * exception's, read as the engine reads a `rejected` one (an Error's name, message and stack; a * value with no message of its own, its JSON or string form), less any part a stop cut off while - * it was read; after a stop, no stand-in is made for a missing message. Every later call answers + * it was read: a stop ends the read, and nothing after it is read. Every later call answers * the same, a stop after it included, and no later result reaches the script. */ | { kind: 'failed'; name: string; message: string; stack?: string } @@ -95,8 +95,8 @@ export type SettleState = RunState & { unfit?: true; dropped?: true }; export interface Engine { /** * Evaluates the prelude as a global script, then the script as global code with top-level - * await, then runs pending jobs until none is left or the guest is interrupted. The script's - * value is that of its last expression statement. Called once. + * await, then runs pending jobs until none is left, the guest is interrupted or the run fails. + * The script's value is that of its last expression statement. Called once. */ start(prelude: string, script: string, filename: string): RunState; /** diff --git a/src/code-execution/quickjs.wasm b/src/code-execution/quickjs.wasm index ee2b84c7f86a88b9963b57f3652d383d85d3a38f..f120827d38f383a51b3ce12ccfbe2cabbbf055e3 100644 GIT binary patch delta 626 zcmZuuJ4?e*6u!?SHMPx9a8Tsj8l-|^2O*A0{($Hx`Uk2*2Nwrzp^6<8hYSjif}oS2 zAbkkk#I3s^#o0fg2=z3#FVMipxw+qWzVo=(ZTGG1-fyMo^TT#(|E~07FmWhLb0mPB ztm%@lj9-T+%KYH;{DtJ0z5*xAiaxn6h8z&bO;S51@{0E(V?OA#;->KXmp#W@bX+22 zk_@~{K!Vq9i#SI#F4-3dA58}ul-zu43IFSW?Bly;nGG~9R;MzE(W z?t>+KT!|4~))h@eSGAdB0%*KBytbl`dTvU|!EC|@8MEBRwk8_OI>pl|y&)AFcAzA% z5USxk delta 547 zcmah_Jxjw-6n&pbO0`B@L>%sm8Cn+|3R&|qI2A!~D*6YiLI)QITcL^_1d#~-0KvgY zP>|XT6$EkXE{gsEcU|Ib(iFj=56-*yo%?a_eXo7@vhUt(6zI|IW?}ET`D8HBkj(|6 zVau^x5**<9Ac`_ypPW9EB1ltPy{DW2n)Il*{tw}p;b(WAn&Dfgig)Ih%St9{_61d_ zYk}~%jfQ&MLc=^xz*45(Rg(K4#b9JJ>^!KFp{*Kj?&M0mEfs@YBHAM!KEVK%ERESR z^+qpMO1)fig-oG+CV_*;hE@1Z)>~ v)uN^(m^i&M!|7<2@^CSlvp5Fx_yMg-bX>)n%e#a=`D-y diff --git a/test/code-execution-engine.test.ts b/test/code-execution-engine.test.ts index 81f867e4e..db2edc0cf 100644 --- a/test/code-execution-engine.test.ts +++ b/test/code-execution-engine.test.ts @@ -555,16 +555,23 @@ test('a stop while the exception that ended the run is read leaves the run faile b.e.dispose(); }); -test('a stop asked for once stays a stop, and what it cuts off a failure\'s read is left out, not made up', async () => { - // The interrupt slot says stop once only. The thrown object's name getter spins until then; its - // message getter would spin on for a while, and then give "late". Neither runs on after the stop, - // and the message isn't replaced by the object's JSON or string form. +test('a stop ends a failure\'s read: nothing after it is read, a part or a part\'s string form', async () => { + // The interrupt slot says stop once only. The thrown object's message, read first, spins until + // then; its name getter would give "made up" at once, and isn't run. In the second, the message + // is read, the name is an object whose toString would give "made up", and the stack getter + // spins until the stop, after which the name's string form isn't made. let checks = 0; - const { e } = await engine({ interrupt: () => ++checks === 101 }); - const s = e.start(PRELUDE, 'queueMicrotask(() => { throw { get name() { for (;;) {} }, get message() { for (let i = 0; i < 3e7; i++) {} return "late" } } }); 1', 's.js'); + const a = await engine({ interrupt: () => ++checks === 101 }); + const s = a.e.start(PRELUDE, 'queueMicrotask(() => { throw { get message() { for (;;) {} }, get name() { return "made up" } } }); 1', 's.js'); assert.deepEqual(s, { kind: 'failed', name: '', message: '' }); assert.equal(checks, 101); - e.dispose(); + a.e.dispose(); + checks = 0; + const b = await engine({ interrupt: () => ++checks > 100 }); + const t = b.e.start(PRELUDE, 'queueMicrotask(() => { throw { message: "m", name: { toString() { return "made up" } }, get stack() { for (;;) {} } } }); 1', 's.js'); + assert.deepEqual(t, { kind: 'failed', name: '', message: 'm' }); + assert.ok(checks > 100, 'the stop came while the stack was read'); + b.e.dispose(); }); test('once a stop has come, a failure with no message gets no stand-in for one', async () => { @@ -579,6 +586,7 @@ test('once a stop has come, a failure with no message gets no stand-in for one', const { e } = await engine({ interrupt: () => ++checks > 100 }); const s = e.start(PRELUDE, `queueMicrotask(() => { throw ${thrown} }); 1`, 's.js'); assert.deepEqual(s, { kind: 'failed', name: '', message: '' }, thrown); + assert.ok(checks > 100, `${thrown}: the stop came while it was read`); e.dispose(); } }); @@ -601,7 +609,10 @@ test('a settle whose resolve call is stopped on its way in is dropped, and the s // 2 puts the next asking at a place set by k: the largest k whose start asks no more than with // none puts it on the first check the settle makes, the one on the way into the promise's // resolve function. - const script = (k: number) => `const f = () => {}; tool("a"); ${'f();'.repeat(k)} const v = await tool("b"); print("ran"); v`; + // If the asking falls on the settle with no calls at all, one more check before them moves it + // 10,000 checks on, so that there's a k - 1 to try. + let before = ''; + const script = (k: number) => `const f = () => {}; tool("a"); ${before}${'f();'.repeat(k)} const v = await tool("b"); print("ran"); v`; async function askedInStart(k: number) { let asked = 0; const { e } = await engine({ interrupt: () => { asked++; return false; } }); @@ -609,14 +620,23 @@ test('a settle whose resolve call is stopped on its way in is dropped, and the s e.dispose(); return asked; } - const none = await askedInStart(0); - let lo = 0, hi = 20_000; - assert.ok((await askedInStart(hi)) > none); - while (hi - lo > 1) { - const mid = (lo + hi) >> 1; - if ((await askedInStart(mid)) > none) hi = mid; - else lo = mid; + async function largestQuiet() { + const none = await askedInStart(0); + let lo = 0, hi = 20_000; + assert.ok((await askedInStart(hi)) > none); + while (hi - lo > 1) { + const mid = (lo + hi) >> 1; + if ((await askedInStart(mid)) > none) hi = mid; + else lo = mid; + } + return lo; } + let lo = await largestQuiet(); + if (lo === 0) { + before = 'f();'; + lo = await largestQuiet(); + } + assert.ok(lo > 0, `k = ${lo}`); async function settledStopped(k: number) { let stop = false; const { e, rec } = await engine({ interrupt: () => stop }); @@ -628,10 +648,8 @@ test('a settle whose resolve call is stopped on its way in is dropped, and the s } assert.deepEqual(await settledStopped(lo), { s: { kind: 'interrupted', dropped: true }, ran: false }); // One call fewer, the asking falls just after the resolve function ran: the result is handed over. - if (lo > 0) { - const after = await settledStopped(lo - 1); - assert.equal(after.s.dropped, undefined, JSON.stringify(after)); - } + const after = await settledStopped(lo - 1); + assert.equal(after.s.dropped, undefined, JSON.stringify(after)); }); test('a result that reaches a stopped run is dropped', async () => { From 34f6bed9cdfd544bcac6583799bc7fffa8e6a7ca Mon Sep 17 00:00:00 2001 From: Gladys-2592 Date: Mon, 19 Oct 2026 13:17:15 -0400 Subject: [PATCH 26/41] fix(engine): vendor.sh's leftover refusal says what the entries are and to restore before removing them, and each notice about the committed copy is given once From the round review of the engine's last round: - The refusal over .quickjs-ng.* entries called every one the leftover of a run killed outright and said the copy was unchanged, though one can be a run going now, and after a kill the copy can be missing or already replaced. It now says the entries are what a run makes while it replaces the copy, one that didn't finish or one running now, and, if none is running, to put back what's committed and then remove them. - When neither rename could go through, the script gave its notice and the cleanup tried the move again and gave its own. The script now clears its record of the moved-aside copy before giving a notice, on that path and the failed removal's, so the notice comes once even if a signal lands just after. - The comment says what bash 5.2 was checked to do: run the exit trap on PIPE, ALRM, USR1, USR2, XCPU, XFSZ and VTALRM, not on PROF or PWR, and ignore QUIT; that the signals ignored are those in ignore_signals, which gains VTALRM; and what SIGKILL leaves. Checked in a scratch clone with bash 5.2.21, git 2.55.0 and quickjs-ng v0.17.0: leftover entries are refused with the new message; with engine/ made read-only before the second rename, neither rename goes through and one notice is given, and the printed command, run from /, restored the copy; a removal of the old copy that fails gives one notice; SIGVTALRM while the clone runs leaves the copy unchanged and nothing behind; a clean run matches the committed copy. A script with only an EXIT trap ran it on SIGXFSZ and SIGVTALRM, and not on SIGPROF or SIGPWR. Co-Authored-By: Claude Opus 5.5 --- engine/vendor.sh | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/engine/vendor.sh b/engine/vendor.sh index 49a823991..d06240926 100755 --- a/engine/vendor.sh +++ b/engine/vendor.sh @@ -36,12 +36,13 @@ dest=$here/quickjs-ng # --no-overlay (git 2.22 and later), a file the index holds and the commit doesn't goes too. top=$(git -C "$here" rev-parse --show-toplevel) restore="git -C $(printf '%q' "$top") checkout --no-overlay HEAD -- $(printf '%q' "${dest#"$top"/}")" -# A stage or a committed copy moved aside, left by a run that was killed outright. +# A stage or a committed copy moved aside, which only this script makes: left by a run that +# didn't finish, or one running now. shopt -s nullglob dotglob left=("$here"/.quickjs-ng.*) shopt -u nullglob dotglob if [ ${#left[@]} -gt 0 ]; then - echo "vendor.sh: engine/ holds ${left[*]##*/}, left by a run that was killed outright; remove them (what's committed is in git, and $restore puts it back). engine/quickjs-ng is unchanged." >&2 + echo "vendor.sh: engine/ holds ${left[*]##*/}, which a run of this script makes while it replaces the copy: one that didn't finish, or one running now. If none is running, run $restore to put back what's committed, then remove them." >&2 exit 1 fi changes=$(git -C "$here" status --porcelain --untracked-files=all --ignored -- quickjs-ng) @@ -51,14 +52,16 @@ if [ -n "$changes" ]; then exit 1 fi # Whenever the script exits, a committed copy left moved aside goes back, unless the new one is in -# its place, and what was staged goes. bash 5.2 runs this on a signal that ends the shell too: INT, -# TERM and HUP are trapped below, so they wait for the command in progress, and PIPE, ALRM, USR1, -# USR2 and XCPU were checked to run it as well (bash ignores QUIT). The signals named in its first -# line, and in the traps below, are ignored from then on. SIGKILL, which no trap sees, skips it, and -# leaves what the next run refuses over. A signal ignored when the script started, as SIGINT is for -# a background job of a non-interactive shell, stays ignored, and the script carries on. +# its place, and what was staged goes. INT, TERM and HUP are trapped below, so they wait for the +# command in progress and end the script through this; bash 5.2 was checked to run it on PIPE, +# ALRM, USR1, USR2, XCPU, XFSZ and VTALRM too, and not on PROF or PWR, and it ignores QUIT. Once +# one of those traps runs, or this cleanup starts, the signals in ignore_signals are ignored. +# SIGKILL, which no trap sees, skips it: it leaves the clone in TMPDIR, and in engine/ a +# stage or the committed copy moved aside, which the next run refuses over. A signal ignored when +# the script started, as SIGINT is for a background job of a non-interactive shell, stays ignored, +# and the script carries on. tmp= stage= old= -ignore_signals='trap "" HUP INT PIPE ALRM TERM USR1 USR2 XCPU XFSZ' +ignore_signals='trap "" HUP INT PIPE ALRM TERM USR1 USR2 XCPU XFSZ VTALRM' finish() { eval "$ignore_signals" set +e @@ -152,13 +155,17 @@ if ! mv "$stage" "$dest"; then if [ -e "$old" ] && mv "$old" "$dest"; then echo "vendor.sh: couldn't move the new copy into $dest; the committed copy is back in place." >&2 else - echo "vendor.sh: couldn't move the new copy into $dest, nor the committed copy back from $old; $restore restores it." >&2 + # Told here, so the cleanup doesn't try again or tell it twice. + moved=$old + old= + echo "vendor.sh: couldn't move the new copy into $dest, nor the committed copy back from $moved; $restore restores it." >&2 fi exit 1 fi if [ -e "$old" ] && ! rm -rf "$old"; then - echo "vendor.sh: the new copy is in place, but the committed copy moved aside to $old couldn't be removed; remove it." >&2 + moved=$old old= + echo "vendor.sh: the new copy is in place, but the committed copy moved aside to $moved couldn't be removed; remove it." >&2 exit 1 fi echo "copied ${#FILES[@]} files from quickjs-ng $TAG ($COMMIT), with $patched patches" From d4adf51c64466afb7e5fce378a59ee134ba961b7 Mon Sep 17 00:00:00 2001 From: Gladys-2592 Date: Mon, 19 Oct 2026 14:42:22 -0400 Subject: [PATCH 27/41] fix(code-execution): the script's code QuickJS runs after a stop can't reach the host, and each part of a failure is a string before the next is read From the round review of the engine's last round: - QuickJS runs the script's own code after a stop: building an error's backtrace converts Error.stackTraceLimit, which can call a valueOf, and calls Error.prepareStackTrace, for the error a stop throws as for any other. On the last round's module, a call from each of those, and output from the second, reached the host after a stop. And a stop that lands in such code is lost when QuickJS puts back the error it was building, so the script goes on from there. __host and __output now refuse once the script is stopped, as they do while its ending is read, so nothing such code does reaches the host. - The interrupt handler's latch is what ends that code at its next check, without asking the host again. The last round's message and the comment it wrote said nothing reached the latch and no test could show it; both were wrong, and the comment now says what reaches it. - A failure's parts: each is read and made a string before the next is read, in one function, error_part, which holds the one guard that ends the read at a stop. A message that isn't a string, read before a name getter the stop cut off, was dropped when the string forms were made after all the reads; now it keeps its string form, a number's or an object's. A part that's there with no string form, such as a Symbol message, is still left out with no stand-in for it. - The last round's test of a part's string form after a stop changes with that: its name is now an object whose toString spins until the stop, with a stack getter after it that isn't run. Tests, under Bun 1.4.3: with the last round's binding and module, four tests fail. A call from a stackTraceLimit valueOf and from a prepareStackTrace, and the latter's output, reach the host after a stop; a stop that lands in a prepareStackTrace doesn't hold, and the script's call after it goes out; a number and an object read as a failure's message before the stop are left out; and in the changed test, the stack is read before the stop cuts off the name's string form. Taking out each piece fails a test: the latch (once QuickJS has lost the stop, the script runs on and the slot is asked again), each refusal, the read's guard, making each part a string at once, and leaving a part with no string form without a stand-in. The guard on a stack read after a stop, which the last round had, now has a test of its own. At the clean head, on Linux x86_64: tsc is clean; all 12 code_execution test files pass, 397 of 397, under Node 24.22.0 and Node 20.20.2 file by file; under Bun 1.4.3 the engine's file passes 65 of 65, and the 12 files 396 of 397 with bun test's default limit of 5 s a test. The one left is a routing test in test/code-execution.test.ts whose run yields at its own bound of 5 s, so it outlasts that limit, with the last round's module in place too; CI runs each file with --timeout 60000, and the test's yield is 3 s since. A rebuild from a fresh toolchain fetch gives the committed module, 600,208 bytes, sha256 2e89e400. Co-Authored-By: Claude Opus 5.5 --- engine/interface.c | 84 ++++++++++++++++------------- src/code-execution/quickjs.wasm | Bin 600155 -> 600208 bytes test/code-execution-engine.test.ts | 65 ++++++++++++++++++++-- 3 files changed, 108 insertions(+), 41 deletions(-) diff --git a/engine/interface.c b/engine/interface.c index e31bff3a0..4d73d3c32 100644 --- a/engine/interface.c +++ b/engine/interface.c @@ -195,9 +195,13 @@ static const JSMallocFunctions limited_malloc_funcs = { }; /* Once the host has stopped the guest, every later check stops it too, without asking again, so - a stop holds for good even if the host would say otherwise later. Nothing the engine does runs - the script's code after a stop today: every entry point checks the flag first, and error_json - ends its read at one. This keeps the stop if that ever changes. QuickJS checks at its first + a stop holds for good even if the host would say otherwise later. The engine runs none of the + script's code after a stop: every entry point checks the flag first, and error_json ends its + read at one. QuickJS does: building an error's backtrace converts Error.stackTraceLimit, which + can call a valueOf, and calls Error.prepareStackTrace, for the error a stop throws as for any + other. A stop that lands in that code is lost when QuickJS puts back the error it was + building, and the script goes on from there. This ends such code at its next check, and + __host and __output refuse it, so none of it reaches the host. QuickJS checks at its first poll and once in 10,000 after, so a check ends the code running then, not at once. */ static int interrupt_handler(JSRuntime *r, void *opaque) { (void)r; @@ -210,10 +214,11 @@ static int interrupt_handler(JSRuntime *r, void *opaque) { } /* __host(name, args): name and args are strings (the prelude stringifies the arguments). - Returns a promise the host settles with engine_settle. */ + Returns a promise the host settles with engine_settle. Once the script has ended, while its + ending is read, or once it's stopped, the call is refused. */ static JSValue js_host(JSContext *c, JSValueConst this_val, int argc, JSValueConst *argv) { (void)this_val; - if (reading) + if (reading || interrupted) return JS_ThrowInternalError(c, "the script has ended, so its calls no longer reach the host"); if (argc < 2 || !JS_IsString(argv[0]) || !JS_IsString(argv[1])) return JS_ThrowTypeError(c, "__host takes a tool name and its arguments as JSON text"); @@ -270,10 +275,10 @@ static JSValue js_host(JSContext *c, JSValueConst this_val, int argc, JSValueCon return promise; } -/* __output(stream, text): stream 1 is out and 2 is err. */ +/* __output(stream, text): stream 1 is out and 2 is err. Refused as __host is. */ static JSValue js_output(JSContext *c, JSValueConst this_val, int argc, JSValueConst *argv) { (void)this_val; - if (reading) + if (reading || interrupted) return JS_ThrowInternalError(c, "the script has ended, so its output no longer reaches the host"); int32_t stream; if (argc < 2 || !JS_IsNumber(argv[0]) || !JS_IsString(argv[1])) @@ -533,11 +538,39 @@ int engine_settle(uint32_t id, int ok, char *json, uint32_t json_len, char *unfi return st; } +/* One part of an object, for error_json: read, and made a string at once, so a stop in a later + part's read can't take it. Nothing is read once a stop has come. A part that isn't there, or + whose read throws, is undefined; one with no string form is null, which error_json leaves out + with no stand-in, as it does a stack that isn't a string. */ +static JSValue error_part(JSValue v, const char *key, bool any) { + if (interrupted) + return JS_UNDEFINED; + JSValue p = JS_GetPropertyStr(ctx, v, key); + if (any && !JS_IsException(p) && !JS_IsUndefined(p) && !JS_IsString(p)) { + JSValue s = JS_ToString(ctx, p); + JS_FreeValue(ctx, p); + if (JS_IsException(s)) { + JS_FreeValue(ctx, JS_GetException(ctx)); + return JS_NULL; + } + p = s; + } + if (JS_IsException(p)) { + JS_FreeValue(ctx, JS_GetException(ctx)); + return JS_UNDEFINED; + } + if (!JS_IsString(p)) { + JS_FreeValue(ctx, p); + return JS_UNDEFINED; + } + return p; +} + /* The text of a value or an error, for engine_result. Takes v. Returns a JS string, undefined, or an exception. Reading an object's parts can run the script's own code (a getter, a toJSON, a toString), and a stop ends the read: what was read before it stays, and nothing more is read, neither a part nor a stand-in for a missing message. The message is read first, as the part - most worth keeping. */ + most worth keeping, and each part is a string before the next is read. */ static JSValue error_json(JSValue v) { /* No prototype, so a toJSON the script put on Object.prototype doesn't serialize it. */ JSValue o = JS_NewObjectProto(ctx, JS_NULL); @@ -547,19 +580,12 @@ static JSValue error_json(JSValue v) { } JSValue name = JS_UNDEFINED, message = JS_UNDEFINED, stack = JS_UNDEFINED; if (JS_IsObject(v)) { - if (!interrupted) - message = JS_GetPropertyStr(ctx, v, "message"); - if (!interrupted) - name = JS_GetPropertyStr(ctx, v, "name"); - if (!interrupted) - stack = JS_GetPropertyStr(ctx, v, "stack"); + message = error_part(v, "message", true); + name = error_part(v, "name", true); + stack = error_part(v, "stack", false); /* An object with no message of its own, such as a plain object thrown, or one whose message can't be read, is told by its JSON, or by its string form when it has none, rather than left blank. */ - if (JS_IsException(message)) { - JS_FreeValue(ctx, JS_GetException(ctx)); - message = JS_UNDEFINED; - } if (JS_IsUndefined(message) && !interrupted) { message = JS_JSONStringify(ctx, v, JS_UNDEFINED, JS_UNDEFINED); if (JS_IsException(message)) { @@ -573,30 +599,14 @@ static JSValue error_json(JSValue v) { /* A primitive's string form runs no script code (a Symbol has none, and is left out). */ message = JS_ToString(ctx, v); } - /* A getter that throws, a name or message with no string form, or a stack that isn't a string - leaves that part out. */ + /* A stand-in that throws, or a primitive with no string form, such as a Symbol, is left out. */ JSValue parts[3] = {name, message, stack}; const char *keys[3] = {"name", "message", "stack"}; for (int i = 0; i < 3; i++) { JSValue p = parts[i]; - if (JS_IsException(p)) { + if (JS_IsException(p)) JS_FreeValue(ctx, JS_GetException(ctx)); - continue; - } - if (i < 2 && !JS_IsUndefined(p) && !JS_IsString(p)) { - if (interrupted) { - JS_FreeValue(ctx, p); - continue; - } - JSValue s = JS_ToString(ctx, p); - JS_FreeValue(ctx, p); - if (JS_IsException(s)) { - JS_FreeValue(ctx, JS_GetException(ctx)); - continue; - } - p = s; - } - if (!JS_IsString(p)) + else if (!JS_IsString(p)) JS_FreeValue(ctx, p); else if (JS_SetPropertyStr(ctx, o, keys[i], p) < 0) JS_FreeValue(ctx, JS_GetException(ctx)); diff --git a/src/code-execution/quickjs.wasm b/src/code-execution/quickjs.wasm index f120827d38f383a51b3ce12ccfbe2cabbbf055e3..55da1a5b13082613cbea0acb31218d7f64c3a962 100644 GIT binary patch delta 79893 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�S^_yslyFM&-RXq!YY^6!M*KryI=V6cxe0 z!F~zmut2gA3qn{*tha*OlZXWg))45{Pu0K!kPs0%)|g*{#$TuM4&d(aWT@g5)F2rW z;v$Vn<~86BZB6E70?!KaR4eN%#*}>Sq?Z#dB`B~74}sJu+k|@{gx+ex2O+kzzX|^g zJ1~`+0VY|GwLlU+dNwG7O=3DUcLn~*+G?B z@TwsML(t^Z@4laW>s#=yr7v+12c~09WDd1NH)99h`L=$FgkvwS z@!R|lbQaf=kH!c&EqV14I|MyJ;9%9T!-y-hl`uu3qLz>u+bE_L4~E@XqZM?@W@_Jx zHwxS-8t6B-ap6-pDwec@rL}^#x56rHrW>vJ+em7@gWMMS_#I4U7xj6ECzRL?@O|=B zEX5wlgF7s4I#}9Vf)2gIFCtE~sx|-0c3eVC5jW4s^DeJogV?a>xwgI>u8QH^;CFcy z@ebR^$mPGw6KuP2^H|S46gdPRaq9)|%D3U3$kS3iX7WSu67$qgi;CbR&LrUdmu;X^ zPSJ!mAh#2=s|_z1h_&zK!YOIDtS%Xj{r%1Kqz#1hPAdH#X0?@)-{bv-Pbt1lpe~u9 z@1@#=*xyGt+VR++JKSd+8p;P`<1PImw{yt%@*hJq z9;LTG=4GX0QXVOv@QO6!V}1h6@XIIsH*6R^`h=g1+zUHG?XQb1G>1MAKCj^88+83s z-ZDYFN1my5@vGlLXG&A!!Vu0L;fPmv{U(H{qU@s1pMl$M(#Fp)4PA6K@jDjy)b^vl8P@5f=VCvCyBIsJ{Qu$GL_KF$-IIw&a116c>xc^5bxTC`=a|m;mv5z5{R$!=xijkW8xF z5tz)Rb{%1W<-xl2f1(XKm}6peDr0H(ALBOyUWgg>NlT&n{S``J{Eh0^gy-UBJ6@ zDC%=qd276#K8N>$6CESI06}h{^9Fxuno}{x1ucfcK-^zhZioH&EBbp*FS`PQX%zD{IQu!(_?mY`m1$oC zKQM^C=FQ+=@o#tz3fFwYUm}#TCWW5~d(Fr)r~|yumHo7!8_dBXy3viNu?6(~w-_RW zwtNdeA#>7qJS=&&gdn-Th&F>_%?gUJYhdz)%n)@&tW{RlNFM!Sn9v?xv7ovo#guIN z_&fL@YpMTtd=pIJDuB0?CgF-@&z>G^`SW*CHoOOaE203z_sa3oy-vXc!Nu2q5By>wb5H&y z)P^w;i!kRnb{2l^$>T~C-=~F0apOEot0;V411Awz^@NQxjDG0Fo3Mqnwio{y#&}Y1 z{s|nL5xp_uOj_C-cHdxDZs(={zO#&%nblYA2G1l>IZ@%x4tw3ou47-c6f90PeOy%qg(kr5bzTm#@{5aJ z2e!;_upFmRo8J%=$f5&tc?n!CL6kP@H{Q%&{}4z3)=X%!v-IFMQ1d{l{ySzqjIRF< zeLR{v^n?F1hWhs7r`Td|yZ#(srdUtCQuzmBhC5Sv)jHxZik~~&9ipt~?vNP$(qk|# z)WT&D$1Cu*g!&vCmyNMiRzjS=$h``k5r{KK)dz5K&|}g7D4#UiGl0h_3uYSJQV)tJuwP2c;lvtL&5gT>r-_Pmu8v5C32YpR6H0qtoqq!_m|6;N26My& z5bUuC@xD*S3-$(H6qkkIytQozy#6$5Gz6&MN9~8`*cxW!vXM&x4B+F4_ zl}CU~=g}7<__**_A19OW{Wt)J^L)ap8|!tA#voIzAChoH=O{x*kr|C-II!LtwrLxvq=oB$!R-XznE5PR5-EPWMs)DLlD_ z&;=VsGKRjH%xk?nR&+1yY!x0GK#0PT7L*7R7Q^Y!!naj-nv4uCdq5=%8Dmms;~1qf zPIQ1$U8w*w4pj01wmX z$qYo{CQ+L##G^i>!Yp3VkIWE@_|gybG0~GOL<%R?;^2^}96|NoPoXsmnZkL_JOtE_Oi^K90CKmLNg6kz6XCpqeo)*mJ^Of_4<7yx!3E_dxb3n;U zXvG|aAWqTkIbfGX^u}C149DJ~UfeM8@6F|5VKa?mbFg(W0kzoR-b1D5>2(^@#VK5!V1qS16zpxf@JP{vaqqf;#*F`iW8x)yOTeBfLGU#45G}&D$J0Ib+yWURo zVc7%LvkQ2;P~581?FV^@PHJQOrz^9~nzAUwEX=CO)OjJMpGkukBB=0`PA=rBY!H32 zhaxtZ7!EaPnYWC?W1X6lC|7W-`Cs(^kk5Ht%#$s40Vs)8?5 zKsLfkd`MfCU}+wb|5Ais^JvXdOlTLKUW&-iOq#L`QfdcXSq6EuoXRcdy(7gmFn8S+ ze@pKKwv`a8J80E%$f?bgyaGxgpT1kc4+1rfRw4}hfab1*M9QSF9K^YH(Qi3W7}F^; zhkuDJ7O|8#jOzT!xDal5@6#z_74HRxAGrzwCXW`Z;$wpl-pi1D(FK6i3t`E(SM#cY z<8}Eb&iihYo$;{f$$f%{*!8PI!1ZKWv>Ga7C|z2OkT!YWUc-YJe8di3MD-?9A1@z* zh!k6k;9EM)TMGwo7WH4p8@gthV{}m|?$X_+UF+Z3Imb;RJ>J z1H0}0xc~wmrr6U$PB`yBeiMHZTKq{8D;VF>3tJhbe^Q3hjLrBU)B>u#1Kp>o(q4f_~23DV%NZA3LEoFtT|cTmh)O zp#8j}eV!4}f{XFSeqC|=ic*9(=j}%q*|d8Sqh@X1v8fUt3AALR`ru88|XiU^OlGR{e3 z4TQCVTP8<&2ZUbrMxv7WCl8w4rj%m{|IVerN9BS37!(Y9+!p?8Gx6PC@i2KQsC()x-BB^mqqTAJU%!}Ts{?B=Dzo`GLt?% z3BPm({df{$Xaqez$rD^7rG*zb{(i zg+E@1%f~QAX30RpZ>Ra!K?^140uIxH^@B)(gn=Y^d>TgZZVEjE=R1==IRiKBDJ?q# z3&%@W&hU?-PD#aIh+s;BGEnvrCK}JBkIwSA(yMS%M15&$DReG;G+fHiX9+=tLlnwt z$~X%*W)&5l5F7MC583b`D}D9p^Xz$sme6&zm9XbRN=q7v-Gi zC*fp`x`17(Y4qm>xE({ud=cOcrFs`Z#zSf6MKl;f0hf4&d9@Jz+RURB>C`1CzI1B) z2cHQi=J6lcPufm>FT)DUq`8+tl$jKI1(9Fhcm1f<6-eL_)bk4VMn=jM_$3darB}EW zWoxeRWK{p>3hyp&XUa|Qw^#Y!b{u5<;x2UTG}?F@_j|J0L^w0LdsJsxLz6Rpz6oXznLvrD%neqEy5<>eAI#)SarbzKWgsvygB$t5*H z-s3Md4p=B~j*i6#p7FIh>gg{v0F5s5m+rv{pBW(4hw?cWAT331lp>u*Jn@@AX)uUL z=vH9T`2IDz;Lvb#kTej>FeC^E^Out|82d&!G&@-8iU^-GL`n_YgPXkmGB=qR?g{AJ zZo-DoEt(Y~)xcrt6CptNeyS5H#iC*RPyo1BHVjjIpjIu54V=ZH(x<3tD5mH<<;-tQ996&5=l2kYT;q%l{$_DBgA?*ah>P1SGgN(2CnQ@~C zzz&5N$fv%M(t7{b%I&xS*(6FT16%H!C@CtsQ09=O@w}~J{UCr8j{@A+77?VZC}1Uz zjzvk;;YB&4r7CFFAX=*62fYS!U5tcN{=K86x|TvvBrL0(Xz7r=R`R%rO|pbYF37s9 zl2Qxc|7J<)7tH93Qj$A#pP)~D;q_0~nzJDJe6{O`lW0GH~fw4#xpM{4iXu`^EpiX8pmks5}t z6Hz{0gIqAh1YkB?oer-*k9K*a`i??l8_XROK!FRSs?_5{>^ilIm69Q%$Hq#DRYiYG z@ZriRVFYtE3r!!&t|*WPuP9|fLDkVI@#?uk@!Fk&JI(iErTRhlWxvSRhsEkOio^2U zr{Qr@WvunOIH^qkN3!_P3>u0+#N*=3rq?rDUe9cMJ+tHW%&ylndypw8?rr(&nURPg z>2I(P6wj%B87Tp4I--nJ5P_KrZ2DpoFdh)rkX3PS=!>$_w+I#-EGyNBK`j|Rj7dZ+ z)yqb_O0-mO00ZzK3#nW=DX|tN7J-S04^wztVnic{KnlK?A#ykyCORU0bx5ZVZ&{*;izhd3erc|vG-Pxxsw34&(gfH?XpAgPiRBX9{PPQd#ngi@7CQfT1cvMb!Exy4uW!%EoWE~H;7 zNo7GgGf+wKRZ>s-=z+FWl9Eb87~;D~Vv;e||1zN9qDK^*07+6vjT59$e}mHCX$qnI z4}G2hu~I;T6Lj)il>ia_h#n_Magk4D56%NXCW$>#{4E(V2W;7bs;GKp0JfMus0<;R zN5e&~fEHDjN|phtLdDI(5b>21VR<~l9?rk*@%bo$vRg)b|3s;7NWp(9R5x(+S)x9` z?}<|NMBqCFUEvUHH?a~Sum=6aHCGg2E&lD@Ai_MlkSLXaANqHqRLB3Bp^6P@Qa}xp zpadULmn0n>W0Is$zgOrG1hza$dM{)e&R=9JXlaNrfx!YQQ3XO6hnA~IrE$-pLlr3s zxnHYDbY(fJv$Ov`n9Swz#rHW zpLp_(LH24uWF}Rp2Ca9WI#mPsFDbQ}6p0OqjA~MAQQw%S!K6=&`VrNo@`wsFsSd4r zh`y-~QfDE6}lNtbbGgYoBMeux11FA_8DE+*qgir0$ z=$eu{2>c@>!UKMN#R&y9AwX`^`I=bcd;)S>fHvQ$B{dV8N*4fTM4P79lBxuNU-T{d zV!Ak23wSA{7r4O;!>3VgsY1O}GhoyMf=tn$^~j&m090r*xJ@^=pqnK19usa03`v~L zoKqVh+@~G2anX7sJ*f@sEu`9YV67HX=Q>g?IUin?2vi2svO4vVElvx$VJJ3}L&VOnxl}Uy zZv{byF1Kw7md1pTLfLv!!*VzqFAf>SA@ZlYw{F*~KX{;9Ehx73!jX&-^(0T&RKa+L zX9?3;d?OU%0gJz-p47GS2GFD`C?2PIUR~6WD<*6m;%tb*9Xha#5}Qbo)VsbE0<&dk zec)^-t*tLDfc4&`0TyJJcXk8Vr!3R^vZ3^|_;AS2jitd5KQ9_XSna09D##*@x~Wnh z5v)=r{Ot&;kc@uP=#ylr7rNVA3_l= zCm7N?jec$h6FZ0IH~4iPmA7=nuCfSh$M~bH3tf&(}CtvPoO9H{~_%? z;G?R7KG5C0=VX@@SV9OP38c^xdT-J$RYX9sPy|KALQ_CNvC(@IDFf1_iGqlf2ndD_ z(j!$`Xi5E>31H|l_6 zY;#qcgVg^4*il*3vxW5z5cB&M)_i0oy!)WF8%=g>X^lu6$E*iF#TZ<$kv?eu!Lc)+ z1W@QsOU!c|RcvJydUTwbr~U7{(%CC{*RFIp|GODW(&k<13?u0$**^N2bO6n72+e2( z68Vw#x3bP7h`qA4wGycsFFXXg!yc)JfF=vcdf56h;XL>Rdp;AB6>r*SL4uN{ykGwU zoq8CEvyuXBtjD9>$@8fYP@Oo_qE{QMdcq+n%Z%B@@h{9dB&C@}3qvyArZ(0*!1CEg ztQ+1#8J!+Q zrS&Pj*dF6pOOx7zL9C%K+5?xa(S`O_d-x*ib+BssmdSYsu{6E|Y{sQj_X(?O(GyZp zj7=wV?nG+1pa!h2$_^6lHa?x?-4hp0vKmk1Z*?xGn&HVpl~`Wj=zTK)}Z=I2kHT@u#c@ z!X^9*ejhwbuRmpt#aigjR!79 zc=Nw}&Weq>&J2db0LZX{&yPJy*XZ(dRxhA(hv%)|(bdB*z;?o(&KInq_|^VJYZaU% zdAnML?P1~c7P{R7X4{`su_xX{ zMW&usJv^Jx(`tjy&-Jvb~y;M@M=WvfK&O*h2E!_Ud};AA0GBIdTd$kt8O%;wRqiS~8s zWfj7r_U&bji)Dv3Ko3Pf&N%Y8tDzLv+p3mNY)R7yU_O^R_QrzGqS3uUwD5BEwrWH` z8RBRMaA(%kK5*wOrAd9PMz|d91KOBQwO+9XLFTW21wuWOzInwe64(Mq2Ny4Mv2-la2qa~ApgTBYMA>WEwHqV&N<$^Y||Fho;+K7ctbhAw)N zy7YyrGn2;m1uf2^#eLDhVA|go6Fo`a^@W5PM(O<^!RJ!fepsn&nvY*8@9Bs+UM~_X zyo*yMJ8hqQPk>n3}Q6B@VYe{-omi{Rt?Zjz5W=>EPA{@l-5}^q(8XCI9l2t z40a5i>W?=U(pzr;vBuEFH>|b!mn+Cze}w%86mXXA6KMIHu*5#4t8ZHEqu{qii6jgg zn%j6Jaaf%rmn&HNzyK`x1lsVHRXcvNj)>z`7xZENX&;0%2Z`RH{BL7~$Xb6J3fg4q z{I+%0jeI=z4i7V<=R5GbAylabT4{yeM;b66`~xasiHLt-Q^4=gXrQ$?91#I%QP=~v zi+&x5@!zCQgTNj>r4I*zf3KtD!B+h;gHf>Br9#2PJRIaPfs$ZojEhl2@NXJD*h-F< zbRxqFQ^K5O_t{%myWcw4s*-Q3FM+?t^4pvEX4nwmcPH8Vv{UnMtdLW3}he(c!@DL-hM_P~{wIKf=12{JX2|xg!1@R80axEj^Km6@Y5l zWu#Rq+=Li7+(T1F!ne4Gz8(p4cTq;!Q5KHtS^_r~*m{6gjJM)5`j55J!+>e?-vfEg zpzq$Z7DL6HFv*I85kX5PS&!#m!b#WIEMpu576PTg$<{h>oZXY*)>(oLe&~CNuW1xh zELgHR;)znGSj}<*f;f~56_8qUE+k$Gz-Age#cBa;M(LbhxYVDDHC{#^PK9r8H1#2H zgk?0BKyIUHCt0&ig%Unfg)ongIFc$d`e5VjTLmVZjO67g2tOi=rHT9KsUsKZR_TBuiuLuxb2YJest z&9btROc$oM3S*0mi}^kNLg5)2Ivf0YG_9F!T?)0qBVRJd8WYSCiUKcScyZjV@mvU& z(R5=j82K_(iGWmCLQl=JD#n<8xWKE`4Vnk@+0itAp0yl0z#|`6)gr!f)I?V4CVc?= zU_agX!0H`!6#+GFo_&;u>9zR)*fp9m9}@c--I;HtVS9>0ILIZyKtP4qJ;8%_j(gZO zUvO$BwO#JSx9>qTLWS)ivPr8 z%W*uW+}yX+`9rI2P&|Mb6M}Zg=kVAVuvff4v<$;f_ z?okW8slp^;5dHd|Hh*lrT6=+4^uJ_ra-yz2JXk(}Cb$FlS>C{b(=;lG83n)h8yj%( z4Vg+7^;!ycdX3&)3gMBJ5xvansnF5D6;|a6SG_RW(1#zQ+{eeV_)X&Xz76JEze(Vj zJ4}@PEIPZw>Vj{Zeg-5SOYePVl~xGZ{6LwX!BohiUp@ooUZaGSz^5On$x3KuKhdu% zt;P|neDTmY0B+byjaEUu$foV9Fsya-?J7w1>r`U3)iBZcuYny%Bun+%G5Dh<;(Jhc zf>|_VH8}qaTE7}<*uo6o8mlG;FYn918vjD=GeE4_^afvkr5PDkTG5~VsW{TBDcUIC z6ou%GZ7P9v8O7NQt9(2>?hxitz6N~D9K`{z7r^=(rGF0X?00JQxmB{Lqo-gMF1)+f-eT?>{wK(oA)_=T;Gwt)n98;94t*{`}ku=E;(V7zp{- zS|tjapZBy&E!JA``Tz7IH=MP`{-U+ui+*dZN~O@bkg?YuG@u!B*70q+Rgp8SF%M+L ziKxZP`_^J5vg!A=R(eJB0-ODwUhulqZsqiKo{>W}`1L2XUS}o70~~lGN|J+ad4f0m z@+UNxzi8Mxt98-8{ZRpf9sb}=KPpmkZkD}%qJt?vud^!ofA-r=P`8xq3@erzthb zbilzTONcQ;0YlU_G%KiJzyS|ZN@F%yDZI>hr$A_4)MNud^%wQr0NWyqc5HyG%BCMS zz&*}z5iViImTO$-kiAxX=vO3G-WUAKpDGk9zw8lS^ym%V_G2#zv!edI`-e|FV&9?J zARwP$Sogy-h2S&vADb^_k?x!3OEVb%-bSn8Gruy#fPG$j2+H|t19H>;9eyMe88^nI zSG?)5zj@cJf*(%}O60o5=~uaahDXp-FY3fRIOZkSY7MZ#4uY~;{C;p?0NW)zK<`1U zo_OqRcw^zq^4P<9qJw3Rr+98X_GUTLjGy5AF?M(fQ$%auGLiWBi&$O`WMXr6SnzM2 z1+))dv;KTxoTs0@1X^U#tuMi1hfwKEt1a@ZvPmaRy6&8mQsJgzWDzCk|E&qlY$IH6B0<9{UpHeQ;O+3Iy0S_a%qF z@n4|n2z!GAp#TFY7c9}03nmLiP7ZE{@pO@HZMNd`akJbIkH#AuPB;J12^sblcKw{A zwOe7yo}(jMVKR>-?>2zs93^bSuXEIN8&u^c z3mY|NX!e0C+d4R}NdFnB!7 z|9Ty;(yPnpa5f-%{9nH?>6d8bJ=%W&-u-v!>H({48)+iJbY0Y3oBK%hx|A4N*MrvFBmsK_&|Dyov+uYWtbnUdrDa)_?|tdSy)X68veIi# z3Z$|JRzQi3GAAwOpe}R*W^|Y2OE%E4&YbK3Zwq~sWzC2ZWD4~<_-m_QQMmY5=9B%F&^@{xOW)1%WeZ{3|#Ftg&($(9+6kwNl1FD+uLqu;&-=WmI-=* z`&zAk^hR$Twg%@P0y&keonor6RFNBW1XM4VRO)cVDjkM1!I5}J;d`vsE|s1+0@v44 z>TwhVxRfRywHo&15uzA@T#iBMOY^-RoXzFr(~w#wnJ1S0rv_!=m@|3Fm;2pU-FLyL zI7{W8cX0|EH*ebQ6`V~Cj#(wr_+GNNg0}|~4Cd;7`RTB~_G< zWSkgT=Tv3-!?~&8H!=um#p$XeK2ADr6#{2K_jirBi#y^+|8>MV_lO^L-);B5zuV5d z%d#N_VJ@hz$al_IecT%9gH^Zud#h#sIS4v2ERjdc;tXS)dD4mxBTlMGqu@0wHgC== z@;v*bRj=G!99O~)iUacRA=b=r{#gLf#ug-qxXFQd(=HdI_pS*){+vrU%wojJ-79P2C;HKkl@Z5c~lAIn`Mr zB>ml{&rd`7Wv*R-QZ7QF{Pi>p$MJOF8g}rXxoTC7a;IS5V3soMj8#%z_t4Wdywwj;D&*Rzv3{Y4f48RyZ~P&dN&*vmsQa^*iAMf-E2aT8uhe5T8H@xS`3|U{-YMf>k1Md;q8WSPhpA8aS|5 z4eMPWl1`z+*}S9XUsvT%?)deKR=R*8{GL^jr?)Q3uqu6PwTcf-*k^BcS#_H4SC+@q zzlF*^o;H1Jb-@K=tVoZ4Ctr^K&Z-dYe#yg0^Ig(F_ub!F)umg%!5yq~B&A(~{xhEX zU4khILb(Lz;~ctk$to2Iz_K>_FAC3O1?TRd^4e2vT#fmZf z1O=RVX{2`^FL;{!U=qKXtL@k;p|(r7!%meCzt_R}h#lMaesSOmln^tTGIxJ*L8z3c z`)}XTRjXVMrspCD=(@Kl`FpEy4u&6PlV&#E|GicGUcT_g_egK}Pp%RLTDBA?F%6^Bat7SH~aybe=a;q3%!pWAwy&b}DVKtS1f+ub~b^gg}b@zjHKUp;dieAQ}2P)>Y?+PhDqXJ7Y zXf9&Z*wc`sY}u2;x&(*`yY>_2WUe?qsM5^-z7X)ze2N$BT zycpF&8vGlC(uj<0zkzdrwf_CPRVw9wWzp2=538~TPEGl*Sp|!^14hH>=3fU%pZpoGOzx5hYj1$ren7`>Sg*i^-10B@pJvhY zzkpd&XxCr3iG0PskqkP7HvJ7tWCs2Ew}lGb)a54LoI#)7w1)YoKy*YzM$v&=Rva7+ z-2l@m^u#SFBvWW0F7iSk5{&7V6|ZsP6&`>lT9PG;yLTKs~i$^0`$0p}so$2QF7-g%pweZu(@rs^K& zPj7_13^l$nyrN;!X^Svb7=1q%raIxWB}_er2uNYSdKSOl_N)5P2RHat94<%vsx>Yp z1FCgN9tdwQWXF|4?p>zWUU}9G6FxpXHI>3=1k`Fk{oy>S4K54vsHc)a9r!Mg=J5jm zjEbu5Ou;vKI1TG~bRGB8ME3j%0we53;e|22&Gt$vqWRuQV#8O!E zhbH4FzRA(9Yy}dc;i+hfEkZ8o2c5+Sb7(jnh)@H|!^(hzBZy0QAETzZy7N^T7}R9` zf^{XfEe=Ggn0l*30H6`asKMav(dM3L;P}b*5-wMqHl$Y&s@q1PfDDG^;E$0i(Y)-M z4tSXoqLh$TPAAkZN_ApNV-r)3Liv_9N2%_qatrtu#5keAYWQ^`xItLI?t88Cs-^{} z;vi-*lznz(=@2vfmOjdh4EAqndtOzw7{eM#mF6X5P4m(6GE}5mj#U^~k$kE|-cVE8 zt^yYPP(Ep+M?UokmMt?MP9%nb0e1_kl3^2z{k_!<|L#E&8ByV&`loIFd{E&`7MdPZ zH4(GU3aT$~nUG(lhZ7c?mEz!tjLrGgK`)SSasf3PyPK-S03{JNj8Ux;eiTDM+lN0o z{E;?08-KZ-$$=$gc*Ll}aqb=C-86R91wXgEI2BoajaU1W9R8gA@yXxXUK!uU*iK>0E1L0ES0xRodvOO3c@ulTM7RUew8`h~l z!oLlRsPgguI1!siz+^UpMi)^_YOK22L59XOQp2ya#l zI>o;5HxA6EKZ>YJ@F-;$Rb`USpuiQkhYbcERElvscUz#+Nva(1rd^V%TRw!6p32Z$Y@JSEWrQMr=pQS9*>TvF99 z5{iq3YCK3tgVUaX7kuz|3ZNGZF2xQM2`^zmiaR(Ce>+w+IGv(Psp5@Bgy9VbqA6Z8 z>i`mDo$^Tbjy=xeP52(Y_q_cl#gX^;gY=|-AhW65-Y_BcJCuIk`2KV3EV9-lPtgBbFbMw`c}c4;*a zmy@MI&c`!umBvSKKb9`5#(BqPd|p;H^Ll@uT3$T|Z|C#n)sMJ5UO|0;*wBp%s#pZ7 z5j#*GMWrf2z#gZz71ax9;meBZ0feT)D}lp}pn*eFA*x$Rl}rQ-&88jZ136HD_6`^h zsHEEA?PHZx1I(asWlVl1)vT-}#10p{;T`so-3E#@tFmg71R{_UWC5-q2P{RT8M#?G zFXgSGTJTE}8`V-~?w>8aUPaxHE;m&HP|na_Rn$|MVuz|2{3z;IRW*@tKCu|%Nnr2F znyMsskcS4H4J$}L*HqDHS=UlrXQ^r})vuI%=4F>hkhletr{)|SB5<$BkLX}6zn2KxtI!m?JYt3ED>#>FQHT@5@4EuZnJ z2GqO0il=<_Kmj8usU8q>2Q{b%LH7r}QcslzB(c%HAg!ziMEiyk>Z>38r=XuVL(W;S z0Yuz!THb)MnGQ4n-X5pw2rl8$)&_SOOPg%f6qi6lRnGUVqKOZx0+W86r~TOa%*0J~ zYeB>8)lhwimkKvhT=0HeBeXN0erbeN8AY`ktA=>?R%2Ba;ocREq3125uNtd|^Up(B zCLeQjW~i79&TnXaIT(b8M3L)J%9yXMaeCIS=Z?4 z7OD{ED^5&6>hbs%>T=<0K8Nnz4U9hQ-2qEohHO&RJjhT)vn~vMTqUpoHE5#3$k!4A z=`>Yrsh*0S9~P9katILS8P9HBOSL)r8rvC=nV7j9=)=us^lgO!C7y6)5uDx5fm{5g zN#5AkTK&-I9LIY=j7Yc;VFJ++5i7Y-i(#dJ?eg0Cg3En4w8zZ6b`YZ7KmmkN8#$tP z9#R#21H-7|!|EskIDs~*jBk*pl*35v?wO$c)Vhrda0>gp6_{0zHtIeQ?uTuF*0bpA zHdyG1^jjN9s0=FnhoJHFn!zgBEeD|1o*^6YPwr$n( zkj)$0BD{3OBD5$hp~R6pnDh=Zn1?#ZVES}Ghp00;X~o{MNbuhM z1ZZ#O)Q+l&FEflLzMu-A*yd*H)Dir87WMB4>_*B~M^!2cNfi!K)1TMLAIvqghQaKZ*KPnVpnQk+W~(ZPwI&KLTCN{DwTCIS$AFwv&4MA795;%Jty^ zyy|>tv+8>8Wau{boRzW)O zw93QkX-a)04F34Eiio?#DV|QIDRP*Ty;Tt#^&kct{fv4B)9d?;ssS)8dj=5PPKTZ` zV8EJys6e(`De$ano_9xBD*K+i-O719!5#F}v+6PuU7L4NNqIAII61UurU5=Dv|kr+ z%u}?X3s!75ebYsy#fXCKP%$@)?nhGWbE*tXf`-qjIR%jBh+uCTH*k`JJ)HIN>vPaS zzM@LctAc2!+4D$Vcg+i9Q}laY&5QmjEEOyatSxxVe7FJGx3K?bQmYqKm5MwzGcJ$> zvn=y#b66?{VSaJOb|Poa#C1a+{E8P;rLtSXWHvfD0!ke)pGRbmy(kOeq6qE>LEZZ( z?~CdO9)@fRQ2!W46&}rQYS&ezH{2eU!aM^<9Rgw8c0W1qwkt5lePlTV_k}S(W>!}m zG!T(!#*IUGxLZGtIY1M)7T>nnzlmWLk2zkJBD)>Y+;KI}|1+4_0lyj!5-V zrA=W4@;QUSP3Tov$HbnhR$*u5j3&W30^vySnhS%1RURDm&=Y>G!TP-xF^TSE%=dr< zS}}?o+B$@`kWZWUCFrGxsohJ^|7O#QmsHW}=S&F?YOB7DDc!gNa@?$qLs^i7b=3XG0la!~o>X0;4BM^!j8hq{n ztbw=NN(~wN>obBUvuJQ{RTI@vWsU%kvjot-!cBdLL%o4<*>r<1C#Yy26`OVpGAIp` z^x+_2GjoGF@esxga8MAoDFDD7`vARAAFhuo)dJHIdk-}^9n#|I_!?j}Fbf8Tl-y&< z%YM0J_~KRYbXXKC11{>pyUsVM@GGij9y|kg<(%mbufTB6p86_Q^Eayfs+xlF9(Yxi z48z7IFOtA-z6uq19+m5>YWXf$NQwo&eY-E}1I--@Y?;{?T>c!*8;bn{rpys6)#H6t z{aBn-Vfh+hh@7OyLLG-kKv5{)57yv$YTFOrU7-H`)RP6bLirbL45A{aCH_> zadp-!W(|H$gMlKPoX4CTIs^*Nz=7W2IUhfDG+BHD1CA#gKzJ?^dqNt#2IcP@EqYC* ztFh2I=SN=< z;hG|PjH8509~v`c}O2L?`C@>D@&fjwT3j~E^nxI z>Vk!w>C87&A?RJl-cUWNWTUgCTuT|8m5*N&At*IFm`u!~Xj96Fmpe5ts)fAvrm83a zhwmxsVa7)LgAszFGik${aDObLV{gL7J5PbP)G%l$e28 z*Z;&cI{7xN%A-{L9ia0(YK}_{Xa~aSSd(*E(qCL&R)uo);V=%jle*{idAyj6LRK@5 zLtpR?RPxhwgf)2Ko z-W;TQ7sAUvc4ofAS^POvpJ5GFr~!45?bvegS7eV-3D6QR(H{Jc#$=_ira#2BDPkyO z?`f($6c*oUdVZ*?ZO}V8>4cGA7(D*KGUE6dP+BvgJ```I?4jygbpgkrOv-#chE5Mt z;rR0MFc<;{spfE%7y+6CVk9FqJ3tMF!cgizTt%RhVZ*TsN9dE`>NR{(a)hdyA{d9a zb401IFBDjIqzk<{Le;|>tQ!GS>JFujRE?rnI`oFjd>|gaJxQ;P#LCXfm_AaC_vTrN zCwIUL)Axp!9*Rj1~ z)il*F|DCXid>B_5@MSOl!R`B|tAVM9tcXb5X4ji1c-UewXbx1)2L4J5bZWZVV4(^g z4Lqz0r!iL$S^(mScH>eTwvM7)1$M%Yu3}tvjE34G<_CW&X=@nmcmgabQzZz-sc?0^R~UoQPA>W{Eef; z*${Ng2)lpcXuxdM0qVpzv(*?i4w=xb$0X3uIgn0sY2zI6@%%9dTK5@xU@rVOXXul; zs$l{6L&baNE9c3>><=)RRAFcum6@l)OHc6wOnBlLDB?JWnuWd2fT%FbgMT~B@~L_7 z9!#ac2VfSnsQL$LyR*I-C=ZtIO+h~fq3&)iPpDh!umCtdiC$ZvD&ewZ0S5jponN48 z7M-Y#)eOEN`@K0M70yE_mY{%ass z3x-6p{}GwY2gjYstXc?<_$)fP5R(~B;fqw|IEE8gB!Cfa-gR1|M;GCxGc{=qwg7E2q08@F<9PA8nsw$f=S%sLsbhW4luqM{0VfBoVE_aXB?$}42E@v!k39N=i`qN z$uqy=M`>|Q8YRLo366MKsKHfysd_N?9ujspvohiMn@24L;)mLNXqkGV2&OM)3_KIP zo!z@)fWy1tji7{OstWzROvMy&y!M-h-?boEQ*bIAV)Kb=87<>torE_!gvofud;$}9 zG_CstPV6(3Z@Fp`Gslm$jEHy)w4BW7AbZMQT&`Zi3rCj&?dFm9Q*}XMCyJJH3h36S zs#*vavHGkBa6}H|?&ISneQZE2q801myY03De#g_aYXzuf6qWl7#CV2Uex{08i~Xte z^iS~X4aP65*xb+H_Ss5jKT}&wd_oXZSjacDYNaYx2&Myohca!?$Qlm0=3v?nD`ENm zN=;X(s(Inc_1V{VXCjjsVGbI(O0`HnV>lJYRklyCZ>rJu9CsSL^RwueRq8=(6l|~> z9ODf2T8%+2qIXwAYM^x7YDmnPeyXrpB~j`cSgKp8)f(8#XK2M55ZfZ!w?@5>DR#|J zk7J6N8BpNP(3uP{`ZHAVb7||L&q2zksnA;0F>aJ#H^9aVrJ04wpQCBaS|HeB3g4h! zqk8L9A^%K&szS}ANx|jVd%q68$gMPS9irx==*&8By3zF4I(UuG(8Tr93E;gOoxJ^p z>X08xFPumUCqMwrp-W#t7E9~nordG2;guVt;X@l#3pAX(Q5wE)qndz*cW;!2uWnS+ z(c7>uRX+=d@=eOWya46T#Lm!BI)#y$jha!^DO1Je7i3B{HUZxrO~W(Qt7z))Olb-g zRJf_to7C$VYWdBoTI2|gb1+%gD4v{0beAEU)#K6kI`iPM-V2W@#n#e6DzHU8n+GG& zfDVvy(C2tSTLAY9v}Ow&#&hZKEy8??ZG}I5giei+hI?&Q(Z1z=slAjjZ7b#tn{n+n zHI9vqaHs(fZigxaKgf3Y(LSZ=+rdc=vq8#i^L0Sw<;I)h2^>{xu8Sc-SB1nLT~Iw+h5SG-B9@ z_hGqaQoVhuHSe#5iw$)lU@A=8r@F+Uei|2Qd5%z)KmT|PaC#hy8UJLC1I6?(mJRemfSlvf|(0cgzc=s_=BrxSr!1aigsop zMDPo}@HH@ND^2)XCE*NdV;V~F2kz+$2sqZ0*aK!@0*P&=4+Vii=0~vM!q;%SZ=^zp zR3!<_vT(wZ!%W4=aM)Bw9S^~4If6zUQbiKY;*J_>KnLF=0v{5Q;+8TG0ow&OCG!Ti z_*_Ku0nr#x7;eAAs#q*`UmNS;Zm29jj76SL`wv5Bo=M*whWGCSN;;y-M4bnK9sehq zYI{UALQ^x2U{qhw_9KAa2>Shqs$66#lw;P6fqv&gk!8U%h0!%Rs_I65;G7-+RKB0Q zY&Xq2s;Wet#g<)zkv!P5M3Ve?LwioDn8Yk@O`%byD@J3{l{JVvh<*PjftTZ{ z#wo1MW@>v1QKQZD{wb&=Ybp9P7JNLlJ*|!a9_7yfk;c>0XH;CmNp?RN%n+B0cx3XL z?aY(UsFv}(2`<8CG6(Vk*Vwa!<2L=N(KP6Ll|ZrCstc^7H?viR$eTX6aalv;#aWXL zoA)M#pH-Dn!Ie7C_a#vKv#L}{9HX*L3|M%Fk<-SZFzA_rSvx&JXU{>lqVNrBa1>zA zj$;Zi@USS$sQY=q>LVI+Uj2>G--rupbMPb0l^P|J1AENiWo~~F=Hk03oT)|^Kq+%3 za`@jcP7XAH89wr@It*Mu`P}vZ^pNjVFYJ%y+PvXTt!`gXYV~$RRPm)tDjxg4MGd(; zOpstE(Ixee!of`eBalX3Mi?{yWoQal>D9}?kBPMCvg%xMxgYT-5&OR2cu08W7akw% zDj5=|Wx?%-z$kBM!#<6{RQ-yoTxf6@O6}nmDz%~#CtP$;FxjCs;0h8`;KkWbGp_&! zyXcE6Fpj?>>nc=f_amr&_(t{!q}ivq%_py7O}El#S5;bGbeG3;hf02f>C#mg4a+F? zduSsU=*jP;)px!}b`+X3_`2Jl>NoD%{okv+m3cfoHej;*Cqi1G&LjA)?G=YN>@G-+ zuPEO&Ri$d^joib-u1W3(OUJwgm;`afe_##w0CLptntIu{+fN4Y$+(x={iq(yi@mj2{o<^xhJh1&_M@ut z=-@E8WO)S~cLHZ1^Bci^ey$*bU}YUAErsuc)uU0fl&rn@g77Mc!DO@#7nj?AeTo*I^aL?)OJ| zx!U3#e{Xx>=T;vWWf55Q*1y0;Av*F4f^E~N?ytx{Ser5HS7_UCjd*_t_8p`yzr)+` zHO=^4y@5;YAF5HzLa*o)f^t6ltKTv`DD2wv4~%XH&HFY3FtYz8JnvC;Ikcc@CMj>7X5w$pJipF`~~P>9DVbez(#javD*-GJLrkq zDx2TDBfnm~g87D49>U1|RB|Pe18BK!@|`cRFFuE-aaF;g85j zV#xy2F3zsqOx6*97q{pC!C#xkKDbg89)^AjkW{m7AO8*KcFRw&mC5Qx1X~$kUKB>|ti+aD+pl zk;7YJqIIc6vdjvffqxrmyZ83T=hTO9thA@kk7t~3} zjM`sNKLb8kIaZg4k@9pb*7hh(i`9>+Wn3lUr&!&%*n6@^nN>PJm}hceFrcIz83Pma!`{kw6@m-n5j6zITISS}WqBk4lZM{p2EU8dRY-&E9V7oQ2_ZAH7crmVhk&a>Qy)?@%bcO z1A6A{BwZ2vLH8%=2eH(N$+}ZC3j!!4_K|g&o^Ci19jtmxvTl(2E5by$ea6$2xdo~k z*e8v2kROn0gXvnbE*Z(ZoFC)P5K2nXB~TinNs3OdmivKlX6LQ_eo^;*-Jsop=ura_ zw825vn%%$$=cj<`uhDUCXe-@G0d8!i@~N6l{|@Fd++03O1q`;*3G>TeOkSx{Om_jk zO)I9WCYYoWFViUsW=umqCI=?~ah$ODT`^s`5RL*d*`h!$_&PX+8P)O+P{rcF-6iyD zas3c3+luRw1(*1Meq6o)y9B$Xz&e4%Su(YRu3Q>&M=TLvipPeWGd+YdR!JxQjAEBGIQgOlXa?Z8TzhY76iMOCYA+YKBiS=fr>}zd|5pJyzAL= zx~9}JXCAH)aa>eRkA?|fsk|;~*w-WFbxDlojqa5H z*4K$ty@Jl03?Rnb3q%78IYA4@tXI&D3!12?fJoT+GR*t}ZK!}rUZ9f|bVW6W_0eEO z-1w3zR0Q-c(mNG(^*nsoAol-ahscJCx~89hTA;U^6?Kvsa8f1xK$79NqWdu?~w2V4e)9K(vqpRtpG+-cm{G}6-!oi(> zrwSwcBAMmp$Hw3bbkKbCcQqaVC_uzglY2=8zCm`dPXoIQ?m!O=G5{gd;E$zbgeETh9rzX|V;VsYm z?Ey`N;Ji@VkHQ$nXh161yNn{apw%BpciGuGLr>kb<;!cbN|E3mbWfpmB>$KvSwx?BX zPY^T3cC@=^8sYE?YFS&yL&<%yHgMz`t*wofo<{rMM7#(?D3OeedjmL!jR+hB-}v;+ zqwogA>VnknK3y!%R8az+I^G3g01DJU={`LiZO7L^DlIJUr+bAJpoi+{a){}?QwKWf ze%e?^m#@jgWe*!N&s?t7Dy0y|XMFaJ&>coRd%~4X@J!k3M`e28X((P7@Xw|f>*``I ze<(x?>p~fsM|*=)8$NY<>`V7-fti(Bh$T?1RSwf(y zMosLm>H$VW>0&+I4AfArzAni;Cj4F=Bko0Sax`#tJ-t$2myX(LG+5}65y7MMQGH#$ z@J@e3u3m&aIK5o2kD(5tM;hqoU}vmtpliX8@k;|e1WNX6wl120T0TM%IDONCEys!{ z(-clr8A_kp5PXAB>{Wk)k0&?O?}3U-HPWYI@fEyCflOnp@U|Lg2RkUfF=W*OYSkEO zOco7jEUNv|#^5Xa=s;s#q$s|_I!i#%W1l^r$t%D%Qkb!kBANh{JE>k1{Sr#CENi0c zptUPabdfx$O2t+tDnkXEg3%wM`c1)8rqdHmp%CuM_@pWGFof#v*QXF2FVtMWfFi{M znu9t0Ky#bx+VMa5qme7@`~YC|1MTBq zwJ7XDi;vyeOaxoi1rv#DhRe0TbW>tBl0RPerqPsG*Wu@ge?7Z3f# zv$854vwM6?4)JHmAP0FuuUFw3t(bEV>XgfxJtN2wImw`IAY%)(>twnR~A~W zFt?=?-x@4-8BJ@g?*mpGYORY8xNE>*C~Qjd2HoI$0nR0jLW5GU2+fNx&c<=N!DXOY z-0shW!^N=hlEOsInC_k_`)zonTwu|Z;SRG8@e(l!h1p48JIj^xJ;Mp(hmnO^Dm3*W z-J}Z$4oU4%7#V6}^6I}8TncPy#WK~5OW9h+#mk0r){H5dL23qOZn$g0UCEuhMoRS` zh6GtcPd=<`4VZ;wye3k^%8x-hxG8`MLqr44x=Z-)=pp#Zjih)Tc-O!>hA`P{{|6%TZdJ$_2T)A$wEj_$-%0x8 zQLvpel=zq~R}VvB^st>V*ir&Z?il9wt+w+p!3ZPd7t3Jub|8XXsKMnl;xSmMm*~^S zbPB?nS&!+41*gECsO}RtKa@&I3J23pe_SV2c02Pk1f&z;L-JR7zK0A?U@8}^Wkk`z zCDiY6U2K5EpfP>t1z-xezzP%QVHV9J6Wnk(_FZ$j<6)HKTdDbkHu4$TFhdYNhop~0 z9O4RK$(O=TLw|`2+hb#dl+sogHdvew-J6$%gUZV;$Z{Hn`x0#$DH3YWJa$)Ih|B#v z+3%9Aa9;igvzFX&aG*$V*1>@$jC=sUh$i{aZ7R~8r_9o6idQdOG&B}v^8PNauXekw zE`;ov*mh7aZd2`c`sD}~)M%GQAgyYr>r~2ulFpRC{C!paQIY*Z_`Xd?T@ONdA})2o?!|;2zr~EyDWdq7t7(~7z^v=; zQ;wF}hqs%b6xm6agF41Ki^slf_Pl0M^G-0>v#4(;-MS;gQyfBkF@OWW83w2xDTs0@ zR4I^&4k()gdgzw&3BSh=d`>A!7)8$oOFm&dz=Z!UR z^PA_J5sL|;3B2S|bVcEm(nwllIvPQ_r*fW>cBThsgOQQ+#xQEjrWB_FayKB%-`qkFg<`~i*tKasSD71 zHFfHui!weK;L*@7kODVoaTi^>2qwz#!zJ2W3YZIyIluQ zaaJae7h_5B+HF#NY(5wh0;PO*1UTaw_~6Atw{PZR8GEL!^_p+%l8EsONR%WOJ8Ez@;n=%EF6~KmhOtI`KRRVm;k>UN7%&aI@z1-ONcDQ$dgT9c7;EGC#7}MyD_f=eE^?v*S!=RP` zNwy<0C7lq_T-ES;NU4!}He zElAl`&^UvY|zg8fU&C;^h%Ct86AIWCs@_vqY3?vD7kgp* zu()L2;SVx9VigLU#vVH%Q-r7V=|Sx4rbKs3$#}UQFn!ijHz|fOF#rVyd<_#ivO}Vu z0U!U8PAD(H#Een^C!iqnEL;FSzOZbkBvc2CN!~Wyp+{( z0d`+t;?bgCC&OjK*pjpMhIz(uQAx-P(Sk3n4w7g=SQ3%@~) zU)BX8pulsfUpoy`e&%Ie)tRy@)+}Zg(t>f4`Rqm@;k7U8q&8R$83W_46cgoNU?K9` z=v|Sh*o`V2!(3Q1r?%VJFlp@pY}y75vE_{f%`(lD?xj-)0Oq-5D6>(>OlR!5w6tTq zeuEw1lqBp9;)`*FIUiTobpMnAh3e6SxOS7M_{b!X7}9q+q%$Ft@;>fQ&8XAO<$qsyfHg0G_GST{SJX;u~<80m;Vh>x%P=x)$T z>b;`l2f$S!S~H_%Dh{z^l;r9?04riE|Avw-F9L^VmSa#d*_*-|oP%~;PuLNDIaFj6 z6A`KmRu-9!o6*r%bUuih?_SXZ9zF;8$Alb>io?>&QCVIYnsQ~pi|oUZr)KfxPhNcF zVo3Qdj0;?kS0&u0{tqW&{xed}nw8Thck~HQ6K#^OEp=tVyfB zx?Db&CqV{b>9)}5zB(-l@4zP$QlOe3gf9LUqk~qR!;)8oSBHV^-hhV%?uMN_XZt>0a_}IB74aoEo1)(RG$n~z1GUkQ z51=afMaCd)$7z5{y#{eOj9R{?9PfI`Z6Ev4eI>~sb0J&%z^~`YQYk1!erFqfox(VBhLgAl@K#)I; zB$YpTy#un|z**pf+!Se=YfxxTIQhSQoby#Kv@|w5a5_3zLo_!B;G$q*9g_<|F!&7a zVl!G@Xi@wNm>_HuYqpN&_1CGnw3ocRUOZYydpX%(w~GP!BAvr40&wNmnIfX-{x@`k z`|(8B#9s%@U1Lizs&T*ah2z}e*I4rpW2hO1$()&C9SrWI18?Xi1sH*uL<2?-l@&^R zQ>XGGXeXlsi@yao!->sy-dHoiNMJ)N_q<>;dbi%7Mza46a0oJh%7K`B7d4S zkw5Qdz;Y}1iiol%byB%fm$TgbI~86+ehAc#%n`gft(VIsfI=iLkKLt{rv3On2<)-n z)`bE)K`of}Wt?N93o$M^3`MY4C|?{9E?dCLxl9FR%<#HA7|ef~NW~zz>(U!9C_XW@ z4t@4Oe#jI6QZnkASr2IhJyv8sQh=w%cSO|kO8Syx5vl!(M~akyAj-Q#iVN$dlJDrE zjXp9}mRVr&3hxU^4tS_U*n_TZt~eyDf{C0BN0WLRG=?FKctn6w$FE<2{oz>KN zh%TBJBnr^TDzOeUb_nG78Tx351l)cYqMKI(lz>$Cn>1)njPe9mu@S}`%>IT2krdl7 z)NFVU1VxXbXlMb)qS$+9dp52GdAY{hL@?*O_y7qwjea$Q*Be^hBMt3g!PL@nec2FEGVr+{Hejebve7prNu98r}PGd^J05F*}U*lw}dn%3#ABf~|OcqoW zgwq)}u(iW+P@;5^@u!(^9%}=^v*M+6dx=GCFvZ7aA`jt~M_$Kyi3y|hg8XO>r$wSU zBt?Q(fIl1i%m*q8gDM$<*x?Oco1<|PDWV^a(N&r-T5(P-_!1nZ=Oks3XCVYH3Q3N;+{Gd*wk7I>}iCvhim|ggK8$*sW>chY--y4g_@>;q!R^M0gHYakj zvThG_D8U5xVf8ozWJV}Qh5-?AbW^)=x;fGwJ{SkDNEU53mn-9Rt%w;|@JM6?u}ir8 zcs+{^rvQe5(wl%KJBv#jCaDsn| z?L1~gbZdf6L^4$TyT~0_Os(EUTEt@N`Yt>S!)W}wIz4=yDBSYyrgwD#c43A3;)5>C zFJpm(9^`S<6Y@^f=|&0s*HJc_sEe~B1PZ%+D~weBx{IlW+I1#3ZK5twV08dHnCdp; zkiYva9hj(N3$u}ZKh!}O5JH`0k($W<&uqGb?rNYrs2AawQGdvEPDu=6GfqSvybWo! zE)emB*K1)n3Kq|$7^v1bXZJl_A;7c&V17pv-_s9ZE6vIG^i$F612BK!vSQ_XAnfbl zN@_Vtzn6-hz&fxI1pLhk@>^&#=RFVJ^G=4h@iQtsS=UTh8%Qwi1g8gh_eCqUK`?f)MP%7o2 zk+sJB4&8`&4Fx@+z*)H$73p`v2z@N~1^_`8)4QbO3d&3v1i85&Z6Mv&H$+Wpcccha znTEwbNAH-+u4#yzoTJ~T=_RP5H+Q;z2eG51_mRl5k=nm6I^gT?>$ct*83*3iXMC+d zGmJ%uKFL`ntY1Na22aQX9pjj%S#o$EIgC}?u=?d?%ONL0KHwSe1&QSRJ})vmZ_Uz0 z3QrHDz>tb+!h6hG$=5(rbn!k_nT;6hDC#&{*TnwTDYNwhG5`4jJU%FJs@pGo4WY!P4NAma}d6pLBr=D1q=s7&e2Jaj^X!k-q5{$VxW$bf{EYD zYgZBX;JyyZCpZ3wPpH;hT_OT7gUP3XTsUuLt}ev|k)c7koM+Zt{Y0U=ZpoQC2uS1o zeDk1_%R_jfA7zbBz?B17RD`4s^K`M$OYQ#SrJ3{ejDH7ciw|_+`!N@88LI+CgOww8 zXJNs|niFk6keu!Je^}+E9{`c2)9w$zuHUCiAHYF0iX!Gi&W@(^`MO+es3*?i2TGf6 zsKTY|+`B3$jd9G=6a#6s5b=mH)NdjD;bZB8g}Uj#FTu@)y0p#99BMj;lKzi49f-_B zz2+bgID_6^q!a4QgeAm23(awE!Ju4}3>_mEQs=P;7R8pR00&>#w|$!qEz%9DA+N%| z;K7I(=Rf6b1UTKgoX2AadM8M=ZUmM?%|JUb7~6cYe$!yM+aDcVj7>Z(ibt~y&r>@&7iQQh}q-Rz@-S$AsDw5Hf|Puu@n)VF;sk+?t!wk z6PM{;`B4mBv^=N@9y#WQqCe3!xL7)j1eXo_8i;cx)iT;Dgh_A`b^k;s#DLSG(izVj z%g7YW#oyom1S45Q1D7L8c#aM%$NT5#y17Ju3hikm)%a9*=M`fTH&{W-Kh-O-1@hSy z80svVx&p}x)9L&Q-5u-4f*13z)ZJMW=Xbe#ey88_yTm=eQ||d) zvl^fwe?O<9DYxmI+K{yP}vDv+4WKfs%YmsUC#Oq_xPw3OzfyR<|i-P8@|j!af>Gy^SB7jp+x?}Fh!v(3y`IST8)D3eU_qAP}hl%Xur+Ajc`EIR##Zh(;` zZP3Z-p9WG9nS^D=gB`Cq}sYn2NF{#Zur5Q#h4xfKIppbMCX`J2Ix?vmu;sMWnGvtJ#9-DQw zl301y17$pAAOvYWf*~M(0kX7Xvwj>~eu{39%!m$KbaGMb9g4~2AC2*W8u76oGvvML z)3zX!{X^QjMHf%l7?87Fjn~9DrB+VP1ZZ$1aO76#IIF45R+H-~J;sIlmb5>)XHqgl zj~zGI4jwd{cAPclj@B7>&bMy5rr=KKEvE^{8fT^f@nr-c#?Efl1>=lKiIfv}+*kv4 zrcvH)x+ptLIB(RBP6=mf2o22qmD4wmo7&-v`(1eEJj*>UJAX>Joe+^?n!-i{*W(Jm z9KOsA+Kt#bO`%2k%XK_BJvm>&AI>}~+;PG{kHGpjU@jtU&#+Q)7-?10Rt}(DI0x7a zkk)gsEsNoGYP&9>Mc1bTTacEwk^3cu0^bHVB*1do z%nU88+aGP*0c?ch1&PuEHvWXqafCw#_BG_zEp&tS&7l5CU~ZBJ96Zc3O{Ep%++znM zy|5E0O@lZRiGf_E@jG?={a?Zwa4`tP&ln``SiiVqLsqw#u)Ji#?f6%OaZ}A7<50>^ zJE4(gQ`jzq9WGL@UF1 zuM85){NG!lm%f6-Vhs)dN*9mW0~Z4?Df|pf>d4ncuEfT#kXv+{j(r89V8rBAD?9)@ zWIbFo0_5*!FGz&($_ZxtXV=O8avsrMLnAlf<8Y&ym*Av4l?9T&a$NAn7r}C=09FWD z)ge&G3N79pS|Rqc@7oRL{Uv?B8{*&w<=>;@@*yN8OTZ@zgYKyOJJ@)UO|AFnr=)kS_Q+VZW%q8T+wYX$IZduOBZ7x`y@Zv*8#w9!`7vKGOz2U{?nZj@~?=n?+#q zuvnn+AS%1}0M_p!T|A&u1CS*w@fTCRgF2DLaR}ecUjkFPCx7J*A`caaXctGtIm3Ec z2#1Z2Mm{#gjwCHnd2*aYkcc^co0I{5phbYu!f%h{)|r+2?M$IukBuX4T_4gYetK7D zj+)?lpWrmhS!oXYN@fOnLrmS7q*+jYi`6rBXW(>t{^??mX^ZM+Nyav`Kz=yv?$3n2 za^`~rFa@g_2>kZ{E1+CrSa7IaI=XAta81Ek>5e>~pdz<_&tI$*bJi?o>%M!>c#YB= za?;_{E}+jqt68d?0V5qdW0t?&C1kJMy=xd6t|%4S!R-I+Sq@IkffPJb{|YH!D2AR3 ziz;EV_5j(_3|8eL056#w$q3UbG}pU8W}tWvfMim9k0qj+dQUszq;}=xJ!WNI8Y4agnro>O2qAjEck>G0plxBvH9o}i& z71SL0F#51gFA4R1!2tLM7?TlUhO~s58T`eMEsP>H6i%8$jo7CTqm!R$0AGHgS%;CS zbU?IhZr5ioGW+3JFLWSh!cr`gu)vHbLLKs#>vP5GnI)6EA{^j;0Q3(^t_q7Nw2RG% zz$Sq(PlD5$aA*BL3{^h{(Vk;E{b39nlVD$d z9sZ{#|ATG7pbAzhLa+v!%8J1r_JNg3pF##c+uPlg2r|&POihnNfj>uskLv^2hiZ0e zm~?ar6O0|W$!O(0zk%->6;-~0mphAo{zliuCf154;8h+%?N8{wxVis??ubj-le%zC z2qx&jey)FW!XtSI_Eb}W%{&1Y9co&Zga#j!Ox{bopWZ&HUmUPFkSbdkc}^@#B(jrf zh!J`eI6x>C0r*!$}T+p}}_WuJk%*32w%CK0}nz7KB(|S7egA!-p zTD?pUpV95{>*F)J8T13;OYTrT_BMux#4`CFPYkE9=0uSAMjiQW$D??fnIvukwL{{Y z-}%aX#LOnA?ac-*{G1mDfs$ezWE1c?H$;~6i=Qvo~{8_sj_a!=wu5|PO)LMVwB4Z9DNd82W)nn$AxSFMaftI z1#N;wslOD4lzOk`F9%gI6DUCHOGcSO7=ok3U0+6#5_oEdh^jV0Ra06SFf?9mjw-E6 zeO1E6`o)tj7zJS4eJ>h~JfR9>Ea&^B=&T za%601lo%X>)wY^WNy4@tP96n=I~R?*RlxTn%mV)Ho^lb8pdzMJS+ve=FTqhh$*C@D z=ni?^B_k$W5DG-mrkr1$;r4Ih3zu;6M^1Hz#p0;F8ihzeIUQa;R-H{2y`+2z!6-jR zsJiKo_j3MNShAYG<%d;%Aoa#tqJXWWhIn~Q*5(ynC#NP-r3*Z*58{$hG0D`id42Hi zM_zc@cp}<#&?<^elJN3sLR|g|4p|g)%_~N#oL^T*F-U^Gi!uJv6(cT9UlTy?V)P?y z3`d*cL(;8DNCiBbv))kcQ;P!<=wS7Bl<;`&#O z_(TD`NYD6Wb%kC^C*z#9@L}cL;Ww^g=c1TbqA-ruDxX`)Jz_Z=aq+5=5CNYJ#EhvA zg3iS$Qt6rz_bM)3s{xe4S{aL=g{TfUOY}=nq6MoKa29l!wT%dtSl26cYTY3je*xNN*36C?D9XNDYt|9*0l_PRvi; zmK9|kk!(ts2Y?S9NB&mgS)(0b05S!X_7)jQk6A!2WgvmJHBeTwk?quWEZRZ?vTv%4@-QxYrcK6>|y|h+Aw*HvZMI2~&XK zD1E@(McQ%LYDOqX=ISl6#bjmGtU$#t>3QOCq`W~fl$ODw2sjkF7qD5rIf0eXoUy3iTEW|yImRz;is4ej*#1UkUy0)z<_MA1~moLcIlO~*oaE)VjP{8bCnOZ^bwK>$39%0w2f;UOKE-PhY4J9A?PzXs z+ZY-LD_$6zl7@;NO|O<1qI+)}-T!BSPCsD(YB!Jn!D#!sMoYjLCb2`9#R6sE;R1&z z&{&&1qt{ySg!BnfFosqUFDG^mf?cWZ|E)!}tX4e%tGomU5@v$zV=~qIe>9R(HI0;N zTq*!xrPf-c)e(8iBY!la>T6c~UwX^e8nxA%(8WpKj1Hni2~#vC?W*pfk?}7-g49NH z!cXep**|_VQYvEPYPwE6M>%m17sUb2`N@b^I;Wh8Fef7I03D$5sJRN7>R)A1Nqw}6 ziXefVD)gz|!v(-$cZ{YI@Ce$N5tysuzk&Zvk&1zNQSa`9lM5KKi&2p2YJNB(BiZUL<}2*0J` zKqea!cCH1iAiR1RV-+&r1mv9kUcUGncHOXsLO7-*Da-O|{EirjdxMYu4v!M_H-VoL zEtsB60DsFa4kjiiP|i->M8iW+N<^^-1AN?04~e6qw1PfP$X%)Qmb4zP-jtT>Q`H+$ z7Va@Zr5`JnC~^XFQ9Klyplza=YA?JwqRNSFqkw)7{AiJXbWv5b zkpN%YpEaQhj?tTOsV-paQ2L__-xd?Kkh3KChC@Z# z!-~#TEI1R@a?E6u{^-zeLtm7-TN(t%G)8da?0Ft8YL^g-7NVlz8KOFW>-wiY?&B zJ)cypwMu;$2=yu-Wkdf+CqEqmWnnYF9YPNxPSCOtdIAyDTt1p+zr>&VsHx|Yuu5n< zTtAdj_*WlQ#Fwa0YKO-wq4W|SYeT6%9z~(lCR`N<2f)$nzy-VH%6Oz4)j}-9e&y&6 z9=*zgiZ1cY@~VN0<>^uU)gX)>EGIQ_-2<*`zyES@SQw?-D_vEGArQz7Tj*%;CkXk8 zi`Lr^EOCRM8riP}fArIEJHjNsU4a(iK1y-~U9lhLut-`BpY4uFYL1}R6h%phtyLq6 zE&I~GFu81IOs55n%MGmU}Z?0g~j zZ0;5Zd_BhR#nIy!(f&B<6>;7(3?bTWon6CQjleZ~@UIKOj`0{cW;GxI&#gQ)f$j^N z;!>ynZcaCwI*|bAA{T9Cs%)I}eBmz)rdOuU4$N?5BDDdAZ%(9!VaFr|9N3sn2z-~Z z-CZA1bMH%{xj3GFG6^M*bG2kl@h!|G#yn_VI5rUw02TB0RGQ&Bg0Qc_*Q?TNcA&_fYQ%WFUY*{C^SYoq{eUJ9)u1fLVUHv( zPvdDdsY>&!a4tb|`+Uh5m&yg(=Q$aqUp^?v;r)CF(A7>~Ux$6TnOnHn!#AcQ5UsNo zr6Ivc-CE$&AM<0iXcWM+qZYM-k0&gR;*lCzIH4%#a#aTVqh5il9Yb!NMzzXiDAu8Z z&%+vcCyj=L1IL8&6Wy(h59cGi(KR;Gsdv-_WUN;;k(8p1@MDbW9KWAVk4p_XZfe%> zp+;dkElHT*l8dvVvw#5>J4>qTnt8S96C_dRKs=_=tqxU2l*o)aB(UDzI;viCiJ|D8+Hdqw~qMt4EVGG^$Rk zPj3GmkMG%g5JV{j!K)8D+}yA}Rd!8rz&HPNePH@@9$cT|5khrpeV|=7Z>di&;AC=2 z1M1|TRgxPP9%}=<|Gfs396DB6#b6>%WG(xq0ZBacS&bD&Ssh@ zdtf8riHO50(*9D;YD90ve4&=Z=^ujj4MYG|Yv4fkq{PPbROD4hng0EmPG5J>e?d#=wn0-$HB`4*eTPSJ-=-9cIKktZ(*5ATTbj~t z&<;1FW_avrMw9LPgWa0bCc~Z;tnm=VJCK&LO@Pkf&-7N{Q1+d{p$}6{hn<6S+R!Px zeMa#8wlv-Wa*Sw4%kgOGv^6Pci=Giw!fimW@kt6CgwUF!B^(-;EvP-f1U0K zjy;dNcEUo;<1wA69v&+@0aHKc@Xl1vJ3~jfz@_hYoiUpkJhC&jz++8kAkXJ~y)*U0 zqw7;x)_K8XX!aNrjy+T?cELT>_RSw>nPH)^1)Xqy+W>C!QX)ZZ<;uB zIif2BNG?Cx72Ns=PwYw`+D`;yy3tdv5XF_k!N3am?e4T5m%^L9hC%P-;A@mzaVl_A zs|0R;U6|tbfpTnSAs4+yb?nEp){lzfw|Y<%I7YkI=>ZoqE=}ceuT#si-+p`@vsu73 zdr*_IAI9~d7j>n}J?K}Qp1;_WmVi5d(uW? z*N2`*^1rctFznU5y$?MevH+>eKwVD6l1%PP51^%2`a*>|$nW>1>J@>5C3Ki1OkE9R zI&bVtz0pwpel!3L&Fx2Tg8n1>(^DYH9{s6q81lrC@^%RA;zG;({?q|!m~Qn4vpUHM z1F%T5xzhk3%RwGG0QGjO8H7Pnh_h?HnL2=P3;@Wp**%ct!d2UW)L-241F0G&b!s5s zgt*~vfy{DIQH31!BJV(BSfQf%yO64Y!`}j6y1hk1BG-HM#e8AyxEe7ZA?&~7>u*tv zeG_}$rk1e?rtK=DnnD{@TxSz^dm9s8z`Nh3s6;cIytL6y#bXxu!GR7nSk%|m>>31d zqZja%l|1e(YRzJCbB@OS*u5GX|^8sxWgx%kT=C1B`mZSW@XOQif7eU6l_&R8OZBt{xNxfs4dqK#k2_6|hCJ%}EO|x&{mdsxapb z#YlGZfuT?|vN`r0AkI2&`3`lfZLL4JE^445B&#q%n?&kIy(D;TP3%DI_EZV8p4;4r z4)aQ~iSpW`=mqM;zDuu0VeJ$@wMNY7Qbb(ZK!|CC)eYhEK16PMNAf<5|SHLJcrz!xidS_J# zjG$;qiybQZfqcP${y75bsm6}aM^IDY9GCgb2x<}`zrgmyxEI9_Nd6tCkEA|Wfb5af z(c%l`6>v4V+LJ>$;}k`Cpdrad?+&h=0inBt+h!5NuUFG|OH$5FjZ z#RpuH=?z+I<~=x9dJl-3!S}xh%V8(K`5tZX7R$s?r-+-sPieJwA}p0(zjOGD>;gR_ zu)*ELfqPbh(72!36L(5oxVS#yeX7@3_o?N$AgdwXTL4z?y5TqxeCLYG3QsaWXL9lT zLSCAID#~g30ePR1NdCP9E-g21FA7%0Ww75-jSdXmOgMks46MmDTss2 zF_JMy0=t^K{kYF^)JK43$uWLM0OMdiO_Yq z>xcA^e`<-kCUlQm^GiOYl!RR68IweS;-N%VCOJtUV^Jttgd#9RBF9iq`&1q=272;B zLo3C&zKb@NkD>NH7#7ONOGA$D#F`YziDN0XoUD|n!{TM!&mG57L_`jFXB*tikq%_5 z&H#W0jivPDQk=tMat`Gevf#STA!#$B`RG{ky6ym)D$A@PSK2b!K8{)jB%u^iTdT@t zGi3?jBs`x0%-+iWE<~;Gd9d@4=hYQsFh4Plrt+w9R5ez>runGw5cnhUzX^-=<0!&$ zz{`_wQ3PKcM?PGFyFHEuLV#La5A)I|O|U)(c;t9+&hPog1WK;(ADvJ|NK^M$%-@fv zzk#vU_z0!4xcx_z5P6UJfR_Lp5Jo+V-~EWjf$(9y> zb0$z~`7D=Qw{d8D$Hf`rL5#X`IjJm8H#HcYIJDtJiU3b+J5dd5`b0_+a|Tla4+^O9 zU@+SzLM)?=i6D(z+;9>l*PY~o-^7Ff%0jaH(YmBQ5@S;;!?gvziALJSp@~dXLKE{R zK|eUo$0mVG-Q~NJ2+6s&1hEczP*Gz_fbC)Ylp>DWlGjs~X zYA(;70++xzn6(f?fLw+L*E0`Ip~U26k~`kj(j1;LGaP;t#5ojOK)h1pIVKARPA)gj z!mvK&=d$2qn$EvXrHUTO^MoQ9yeNyRf+<5hRRs|PYI5Aq0jJX$e1Lz#`!P+0w_?`E z^rHWwc}hJM$M_Tt`KDrJce!H$N@e8$qtuuE++r#m9i9z)~|1DlI6%%-mk4?oYs|lom^VXUM zM!$fcnMSo^k?#xo5+tkBe*uZ^(VR+UD%pf`ra`(d;4>%&2U|FUU0>$b43T@8hcU(8 zf7vU7DDV)j0^9rhFN>aUO-35g99Rty$J61+1`?#I=0TtPm}VkRV!!F|enSC(MjOow zrc)$_w{|*JZx6uYe3>0_u>yPcZo&`)h-7~Yo+ESpg((U2G&Z=!biF2Q}BW_!SHMt)o=*Cl0#30 zpQqAx>`xrt&Ev8CTXK~8i2`7K=v1hXxCUGZSRIC z)|xg8cyWgN&4L2GipS1^=~2kbW>NJR$;;L}(Ye&o%1--6F6NNS6LRVK3dlSwZWT8aEaU@6I8_Jd=E3+0{TKAm zjoj{EwA8BRnhjkfiyxW|U(Zo~el|>PZ8lpmJj;t zzIjw5ROf0ll|q_N^<}A)-Pd70rByZkZjhoPkYo^Wj~3mOY); zE6B;Qp3kXS zObO~ja)SEeC7mmD%cGpQfaXP?A<@@$$}I?Xd_%g?Gkk0ToNbc5{ZT9hRAl8Vlg~As zHT(xTd?7_e=DFqbAHLkT5R5pFpIrz$4m~4#u}L&Y7Y2N|$%PBC&_}uIA{q&#oVSQR z2JYPd1;tt``ob4*_s!**UqA+a!^^*b!|@>3UJO~4$9)!KxeoG##q=W9O(^u55UM^&rCEk);4&nVGQDrHFw9Agtj!|}B zf=h5GiHIC?+m9wb56)slQbS5Sfs4k07l@PKJ~ahhVi5QYg54~!gX z$Dzah-+)c2T$*>j0T7q)eM^CkdHl>$SXT#m=2A-6pLZ>#ZURm(@VLn`2#+l8whZ2p zES|iK9+qtN3ZX(V(Sl{b@1w!g<+N28(6t~2b%twwOT(lA@H}4pEycxv!i4pC1Zd!X z9?TVj8q+-aEeJb{E3bf$X$il+g4$XgFIxd2oXZziP^a)>g-NPYudpuS`hzHzyRXFf zGkMfXYZdx!2 z^%^0nl2$_xS-}sj2E# z)&tKAxz~CyTVzFCkG+@zzOtUu=!8RJH*vp5f#{hJxT^898vw&CJYWO-^aXr$18_f^ zf7*bG1>E7^l;bWC?WtS{pV|PIC_FS9X$mH;+)#@*Qb*MNeIs=NbMCTe`3b^JD2)hFAvjb~iz@aI{cM&A|Dqss_ZrL+#m%K`6K@n==Lc@?KD85s%voOw8rg zdua&fq7d@2eGt`!{Nw>#4Zu!;V5C^5Zlo#5lTQV_Z6DUzEPZz$Tv4iYHLkOtQbTpt zb&)FQrOSRuF0m3JEdOj5+Yf+H%x_H&SbaW$6`6T}Dpf3S0Mp|@49Y!X4%-pS zllKBCP8|TJDB^^J)Yfk$@AV3W7=A$dAQm*6zdZ;Un!`5^QdidrG5B&HbVYHmLs-M{ zy!a5MRyMnEX|RGE;TXc>HlDRw}?N+ zBNhAV+Oa14>Ncn5^AM#NuM*5?+QYeP04U#4EY~WobQFYeiu)X;E-w^2e7+EHV%+iZ zZ*%y*v^(r0?87b7RZBLzzX+2Tk#0}KEPAV^;x0Pyt7rq-UeXiaMb( zPFlbG{;@pbBwXiL`N>lhA2G?PW?RqK!RAn~gI3hFn80BtISl4g)GoC2Rt~Rk;YeZs zX;>NM z=qz^3NAaPv^c-H+Jx9?G+;PIEV{5xujmw{j#R1j8iGaOLoamFyC)0MVr%hLhZ-BLn z2RN|bHtHOu#*QazBXga@H_UDy4k#!iV=L#M13zBJxfh^&6rY22znb0Wv0G!#rvG{1 z-CfQ)4_*@Fb>|`6%rEsWkSA)Z%7-qXS6&Njy*mAQ{P+ck2i0Z*Wb3*MU_e;n3!vF6 z+~^{h+d7_fiz0E5AJiFYUyG+^Pt6eG> zk`nv~lYkpQW3FJrS9sYKdLlG01iSY7q>91y$GVbh_JB zC|*%S&xavat0_m}nZ!JkZa`Rlz)#=6PVhR1Cxc$ z;%Dx26AEV@=iY=)IGZotgpKeEM-*cp@rck#Ys^K-n6P}8!E1{tzPz;+YCtnv>=OGj z+GDh`dcon`rtY z@7C>H(GTyhpp8wz>c7%WJD#z>(>G3Vsa+2DEB4L7x=!~DJDyuz?mie2Hz#*8{(O<# zB!B+H?M}mEqT3yd$3nNeDITZY?icV#^t-F$(cOcCAhUuWc-+(7=G*ACayGBF%@#;h z{^Xd%Xqa+q`6a*mnR^wF`Q2ymSAMv=79N+w-AnMuuHdeRqdq$;xWB|-??$*Kz0JQO z+$nf$j&P?}+#F)H3{;Fvte6URHg$=Q)f)qi?K%DB~uPP8y zIm;k+#<`zD$>eyc5NsFk9%OgUbJ+M~f;*BAC%9MQV^(DWQZQKAz1$9c_02?gBRpm& zx;Nv|Gs*oV9xIdF&}D<6$?j3&)SXxx;N8Z{YP(;K*%qSGngfN5o=GY+3x4(TZwsc^ iafj50yRGTV?&dxvaKyIpkNx1Cm)*g4UiO^)?*9P)sw?XN delta 80143 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