From be071072115059726846163c6f28f62dc01573ec Mon Sep 17 00:00:00 2001 From: Jo-Philipp Wich Date: Fri, 14 Jul 2023 10:54:38 +0200 Subject: treewide: consolidate platform specific code in platform.c Get rid of most __APPLE__ guards by introducing a central platform.c unit providing drop-in replacements for missing APIs. Also move system signal definitions into the new platform file to be able to share them with the upcoming debug library. Signed-off-by: Jo-Philipp Wich --- CMakeLists.txt | 2 +- include/endian.h | 23 ----- include/ucode/platform.h | 79 ++++++++++++++++ include/ucode/types.h | 3 +- lexer.c | 2 +- lib.c | 209 ++-------------------------------------- lib/fs.c | 5 +- lib/nl80211.c | 2 +- lib/rtnl.c | 2 +- lib/uloop.c | 6 +- platform.c | 242 +++++++++++++++++++++++++++++++++++++++++++++++ program.c | 2 +- source.c | 2 +- types.c | 1 - vallist.c | 2 +- vm.c | 35 +------ 16 files changed, 344 insertions(+), 273 deletions(-) delete mode 100644 include/endian.h create mode 100644 include/ucode/platform.h create mode 100644 platform.c diff --git a/CMakeLists.txt b/CMakeLists.txt index d8dd34a..280cbb4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -50,7 +50,7 @@ INCLUDE(FindPkgConfig) PKG_CHECK_MODULES(JSONC REQUIRED json-c) INCLUDE_DIRECTORIES(${JSONC_INCLUDE_DIRS}) -SET(UCODE_SOURCES lexer.c lib.c vm.c chunk.c vallist.c compiler.c source.c types.c program.c) +SET(UCODE_SOURCES lexer.c lib.c vm.c chunk.c vallist.c compiler.c source.c types.c program.c platform.c) ADD_LIBRARY(libucode SHARED ${UCODE_SOURCES}) SET(SOVERSION 0 CACHE STRING "Override ucode library version") SET_TARGET_PROPERTIES(libucode PROPERTIES OUTPUT_NAME ucode SOVERSION ${SOVERSION}) diff --git a/include/endian.h b/include/endian.h deleted file mode 100644 index 198cf7c..0000000 --- a/include/endian.h +++ /dev/null @@ -1,23 +0,0 @@ -#ifdef __APPLE__ - -# include -# include - -# define htobe16(x) OSSwapHostToBigInt16(x) -# define htole16(x) OSSwapHostToLittleInt16(x) -# define be16toh(x) OSSwapBigToHostInt16(x) -# define le16toh(x) OSSwapLittleToHostInt16(x) - -# define htobe32(x) OSSwapHostToBigInt32(x) -# define htole32(x) OSSwapHostToLittleInt32(x) -# define be32toh(x) OSSwapBigToHostInt32(x) -# define le32toh(x) OSSwapLittleToHostInt32(x) - -# define htobe64(x) OSSwapHostToBigInt64(x) -# define htole64(x) OSSwapHostToLittleInt64(x) -# define be64toh(x) OSSwapBigToHostInt64(x) -# define le64toh(x) OSSwapLittleToHostInt64(x) - -#else -# include_next -#endif diff --git a/include/ucode/platform.h b/include/ucode/platform.h new file mode 100644 index 0000000..52eb391 --- /dev/null +++ b/include/ucode/platform.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2023 Jo-Philipp Wich + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, 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 THIS SOFTWARE. + */ + +#ifndef UCODE_PLATFORM_H +#define UCODE_PLATFORM_H + +#include +#include +#include + +#include "ucode/util.h" + +#ifdef NSIG +# define UC_SYSTEM_SIGNAL_COUNT NSIG +#else +# define UC_SYSTEM_SIGNAL_COUNT (_SIGMAX + 1) +#endif + +extern const char *uc_system_signal_names[]; + +#if defined(__linux__) +# include +# include +#elif defined(__APPLE__) +# include +# include +# include +# include + +# define htobe16(x) OSSwapHostToBigInt16(x) +# define htole16(x) OSSwapHostToLittleInt16(x) +# define be16toh(x) OSSwapBigToHostInt16(x) +# define le16toh(x) OSSwapLittleToHostInt16(x) + +# define htobe32(x) OSSwapHostToBigInt32(x) +# define htole32(x) OSSwapHostToLittleInt32(x) +# define be32toh(x) OSSwapBigToHostInt32(x) +# define le32toh(x) OSSwapLittleToHostInt32(x) + +# define htobe64(x) OSSwapHostToBigInt64(x) +# define htole64(x) OSSwapHostToLittleInt64(x) +# define be64toh(x) OSSwapBigToHostInt64(x) +# define le64toh(x) OSSwapLittleToHostInt64(x) + +# define environ (*_NSGetEnviron()) + +__hidden int pipe2(int[2], int); +__hidden int sigtimedwait(const sigset_t *, siginfo_t *, const struct timespec *); + +static inline int +execvpe(const char *program, char **argv, char **envp) +{ + char **saved = environ; + int rc; + + environ = envp; + rc = execvp(program, argv); + environ = saved; + + return rc; +} +#else +# error Unsupported platform +#endif + +#endif /* UCODE_PLATFORM_H */ diff --git a/include/ucode/types.h b/include/ucode/types.h index 641c469..7fcc7f1 100644 --- a/include/ucode/types.h +++ b/include/ucode/types.h @@ -24,6 +24,7 @@ #include #include "util.h" +#include "platform.h" /* Value types and generic value header */ @@ -314,7 +315,7 @@ struct uc_vm { uc_exception_handler_t *exhandler; FILE *output; struct { - uint64_t raised[((NSIG + 63) & ~63) / 64]; + uint64_t raised[((UC_SYSTEM_SIGNAL_COUNT + 63) & ~63) / 64]; uc_value_t *handler; struct sigaction sa; int sigpipe[2]; diff --git a/lexer.c b/lexer.c index 934a531..28188c3 100644 --- a/lexer.c +++ b/lexer.c @@ -23,11 +23,11 @@ #include #include #include -#include #include "ucode/vm.h" #include "ucode/lib.h" #include "ucode/lexer.h" +#include "ucode/platform.h" struct keyword { unsigned type; diff --git a/lib.c b/lib.c index b06c02b..a16190c 100644 --- a/lib.c +++ b/lib.c @@ -50,6 +50,7 @@ #include "ucode/lib.h" #include "ucode/source.h" #include "ucode/program.h" +#include "ucode/platform.h" static void format_context_line(uc_stringbuf_t *buf, const char *line, size_t off, bool compact) @@ -3734,107 +3735,6 @@ uc_warn(uc_vm_t *vm, size_t nargs) return uc_print_common(vm, nargs, stderr); } -#ifdef __APPLE__ -/* - * sigtimedwait() implementation based on - * https://comp.unix.programmer.narkive.com/rEDH0sPT/sigtimedwait-implementation - * and - * https://github.com/wahern/lunix/blob/master/src/unix.c - */ -static void -sigtimedwait_consume_signal(int signo) -{ -} - -static int -sigtimedwait(const sigset_t *set, siginfo_t *info, const struct timespec *timeout) -{ - struct timespec elapsed = { 0, 0 }, sleep, rem; - sigset_t pending, unblock, omask; - struct sigaction sa, osa; - int signo; - bool lt; - - while (true) { - sigemptyset(&pending); - sigpending(&pending); - - for (signo = 1; signo < NSIG; signo++) { - if (!sigismember(set, signo) || !sigismember(&pending, signo)) - continue; - - sa.sa_handler = sigtimedwait_consume_signal; - sa.sa_flags = 0; - sigfillset(&sa.sa_mask); - - sigaction(signo, &sa, &osa); - - sigemptyset(&unblock); - sigaddset(&unblock, signo); - sigprocmask(SIG_UNBLOCK, &unblock, &omask); - sigprocmask(SIG_SETMASK, &omask, NULL); - - sigaction(signo, &osa, NULL); - - if (info) { - memset(info, 0, sizeof(*info)); - info->si_signo = signo; - } - - return signo; - } - - sleep.tv_sec = 0; - sleep.tv_nsec = 200000000L; /* 2/10th second */ - rem = sleep; - - if (nanosleep(&sleep, &rem) == 0) { - elapsed.tv_sec += sleep.tv_sec; - elapsed.tv_nsec += sleep.tv_nsec; - - if (elapsed.tv_nsec > 1000000000) { - elapsed.tv_sec++; - elapsed.tv_nsec -= 1000000000; - } - } - else if (errno == EINTR) { - sleep.tv_sec -= rem.tv_sec; - sleep.tv_nsec -= rem.tv_nsec; - - if (sleep.tv_nsec < 0) { - sleep.tv_sec--; - sleep.tv_nsec += 1000000000; - } - - elapsed.tv_sec += sleep.tv_sec; - elapsed.tv_nsec += sleep.tv_nsec; - - if (elapsed.tv_nsec > 1000000000) { - elapsed.tv_sec++; - elapsed.tv_nsec -= 1000000000; - } - } - else { - return errno; - } - - lt = timeout - ? ((elapsed.tv_sec == timeout->tv_sec) - ? (elapsed.tv_nsec < timeout->tv_nsec) - : (elapsed.tv_sec < timeout->tv_sec)) - : true; - - if (!lt) - break; - } - - errno = EAGAIN; - - return -1; -} - -#endif - /** * Executes the given command, waits for completion, and returns the resulting * exit code. @@ -5501,7 +5401,6 @@ uc_loadstring(uc_vm_t *vm, size_t nargs) * @example * loadfile("./templates/example.uc"); // function main() { ... } */ - static uc_value_t * uc_loadfile(uc_vm_t *vm, size_t nargs) { @@ -5646,100 +5545,6 @@ uc_callfunc(uc_vm_t *vm, size_t nargs) return res; } - -static const char *signal_names[] = { -#if defined(SIGINT) - [SIGINT] = "INT", -#endif -#if defined(SIGILL) - [SIGILL] = "ILL", -#endif -#if defined(SIGABRT) - [SIGABRT] = "ABRT", -#endif -#if defined(SIGFPE) - [SIGFPE] = "FPE", -#endif -#if defined(SIGSEGV) - [SIGSEGV] = "SEGV", -#endif -#if defined(SIGTERM) - [SIGTERM] = "TERM", -#endif -#if defined(SIGHUP) - [SIGHUP] = "HUP", -#endif -#if defined(SIGQUIT) - [SIGQUIT] = "QUIT", -#endif -#if defined(SIGTRAP) - [SIGTRAP] = "TRAP", -#endif -#if defined(SIGKILL) - [SIGKILL] = "KILL", -#endif -#if defined(SIGPIPE) - [SIGPIPE] = "PIPE", -#endif -#if defined(SIGALRM) - [SIGALRM] = "ALRM", -#endif -#if defined(SIGSTKFLT) - [SIGSTKFLT] = "STKFLT", -#endif -#if defined(SIGPWR) - [SIGPWR] = "PWR", -#endif -#if defined(SIGBUS) - [SIGBUS] = "BUS", -#endif -#if defined(SIGSYS) - [SIGSYS] = "SYS", -#endif -#if defined(SIGURG) - [SIGURG] = "URG", -#endif -#if defined(SIGSTOP) - [SIGSTOP] = "STOP", -#endif -#if defined(SIGTSTP) - [SIGTSTP] = "TSTP", -#endif -#if defined(SIGCONT) - [SIGCONT] = "CONT", -#endif -#if defined(SIGCHLD) - [SIGCHLD] = "CHLD", -#endif -#if defined(SIGTTIN) - [SIGTTIN] = "TTIN", -#endif -#if defined(SIGTTOU) - [SIGTTOU] = "TTOU", -#endif -#if defined(SIGPOLL) - [SIGPOLL] = "POLL", -#endif -#if defined(SIGXFSZ) - [SIGXFSZ] = "XFSZ", -#endif -#if defined(SIGXCPU) - [SIGXCPU] = "XCPU", -#endif -#if defined(SIGVTALRM) - [SIGVTALRM] = "VTALRM", -#endif -#if defined(SIGPROF) - [SIGPROF] = "PROF", -#endif -#if defined(SIGUSR1) - [SIGUSR1] = "USR1", -#endif -#if defined(SIGUSR2) - [SIGUSR2] = "USR2", -#endif -}; - /** * Set or query process signal handler function. * @@ -5822,7 +5627,10 @@ uc_signal(uc_vm_t *vm, size_t nargs) if (ucv_type(signame) == UC_INTEGER) { sig = (int)ucv_int64_get(signame); - if (errno || sig >= (int)ARRAY_SIZE(signal_names) || !signal_names[sig]) + if (errno || sig < 0 || sig >= UC_SYSTEM_SIGNAL_COUNT) + return NULL; + + if (!uc_system_signal_names[sig]) return NULL; } else if (ucv_type(signame) == UC_STRING) { @@ -5831,11 +5639,12 @@ uc_signal(uc_vm_t *vm, size_t nargs) if (!strncasecmp(sigstr, "SIG", 3)) sigstr += 3; - for (sig = 0; sig < (int)ARRAY_SIZE(signal_names); sig++) - if (signal_names[sig] && !strcasecmp(signal_names[sig], sigstr)) + for (sig = 0; sig < UC_SYSTEM_SIGNAL_COUNT; sig++) + if (uc_system_signal_names[sig] && + !strcasecmp(uc_system_signal_names[sig], sigstr)) break; - if (sig == (int)ARRAY_SIZE(signal_names)) + if (sig == UC_SYSTEM_SIGNAL_COUNT) return NULL; } else { diff --git a/lib/fs.c b/lib/fs.c index e00f7fa..0f03ad4 100644 --- a/lib/fs.c +++ b/lib/fs.c @@ -60,11 +60,8 @@ #include #include -#ifndef __APPLE__ -#include /* major(), minor() */ -#endif - #include "ucode/module.h" +#include "ucode/platform.h" #define err_return(err) do { last_error = err; return NULL; } while(0) diff --git a/lib/nl80211.c b/lib/nl80211.c index c7bd5fa..49aea32 100644 --- a/lib/nl80211.c +++ b/lib/nl80211.c @@ -24,7 +24,6 @@ limitations under the License. #include #include #include -#include #include #include @@ -43,6 +42,7 @@ limitations under the License. #include #include "ucode/module.h" +#include "ucode/platform.h" #define DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d)) diff --git a/lib/rtnl.c b/lib/rtnl.c index b5afde4..e5efa25 100644 --- a/lib/rtnl.c +++ b/lib/rtnl.c @@ -24,7 +24,6 @@ limitations under the License. #include #include #include -#include #include #include @@ -52,6 +51,7 @@ limitations under the License. #include #include "ucode/module.h" +#include "ucode/platform.h" #define DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d)) diff --git a/lib/uloop.c b/lib/uloop.c index 99cd984..d8d702f 100644 --- a/lib/uloop.c +++ b/lib/uloop.c @@ -23,6 +23,7 @@ #include #include "ucode/module.h" +#include "ucode/platform.h" #define err_return(err) do { last_error = err; return NULL; } while(0) @@ -535,13 +536,8 @@ uc_uloop_process(uc_vm_t *vm, size_t nargs) free(buf); } -#ifdef __APPLE__ - execve((const char *)ucv_string_get(executable), - (char * const *)argp, (char * const *)envp); -#else execvpe((const char *)ucv_string_get(executable), (char * const *)argp, (char * const *)envp); -#endif _exit(-1); } diff --git a/platform.c b/platform.c new file mode 100644 index 0000000..63a79d4 --- /dev/null +++ b/platform.c @@ -0,0 +1,242 @@ +/* + * Copyright (C) 2023 Jo-Philipp Wich + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, 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 THIS SOFTWARE. + */ + +#include + +#include "ucode/platform.h" + +const char *uc_system_signal_names[UC_SYSTEM_SIGNAL_COUNT] = { +#if defined(SIGINT) + [SIGINT] = "INT", +#endif +#if defined(SIGILL) + [SIGILL] = "ILL", +#endif +#if defined(SIGABRT) + [SIGABRT] = "ABRT", +#endif +#if defined(SIGFPE) + [SIGFPE] = "FPE", +#endif +#if defined(SIGSEGV) + [SIGSEGV] = "SEGV", +#endif +#if defined(SIGTERM) + [SIGTERM] = "TERM", +#endif +#if defined(SIGHUP) + [SIGHUP] = "HUP", +#endif +#if defined(SIGQUIT) + [SIGQUIT] = "QUIT", +#endif +#if defined(SIGTRAP) + [SIGTRAP] = "TRAP", +#endif +#if defined(SIGKILL) + [SIGKILL] = "KILL", +#endif +#if defined(SIGPIPE) + [SIGPIPE] = "PIPE", +#endif +#if defined(SIGALRM) + [SIGALRM] = "ALRM", +#endif +#if defined(SIGSTKFLT) + [SIGSTKFLT] = "STKFLT", +#endif +#if defined(SIGPWR) + [SIGPWR] = "PWR", +#endif +#if defined(SIGBUS) + [SIGBUS] = "BUS", +#endif +#if defined(SIGSYS) + [SIGSYS] = "SYS", +#endif +#if defined(SIGURG) + [SIGURG] = "URG", +#endif +#if defined(SIGSTOP) + [SIGSTOP] = "STOP", +#endif +#if defined(SIGTSTP) + [SIGTSTP] = "TSTP", +#endif +#if defined(SIGCONT) + [SIGCONT] = "CONT", +#endif +#if defined(SIGCHLD) + [SIGCHLD] = "CHLD", +#endif +#if defined(SIGTTIN) + [SIGTTIN] = "TTIN", +#endif +#if defined(SIGTTOU) + [SIGTTOU] = "TTOU", +#endif +#if defined(SIGPOLL) + [SIGPOLL] = "POLL", +#endif +#if defined(SIGXFSZ) + [SIGXFSZ] = "XFSZ", +#endif +#if defined(SIGXCPU) + [SIGXCPU] = "XCPU", +#endif +#if defined(SIGVTALRM) + [SIGVTALRM] = "VTALRM", +#endif +#if defined(SIGPROF) + [SIGPROF] = "PROF", +#endif +#if defined(SIGUSR1) + [SIGUSR1] = "USR1", +#endif +#if defined(SIGUSR2) + [SIGUSR2] = "USR2", +#endif +}; + + +#ifdef __APPLE__ +int +pipe2(int pipefd[2], int flags) +{ + if (pipe(pipefd) != 0) + return -1; + + if (flags & O_CLOEXEC) { + if (fcntl(pipefd[0], F_SETFD, FD_CLOEXEC) != 0 || + fcntl(pipefd[1], F_SETFD, FD_CLOEXEC) != 0) { + close(pipefd[0]); + close(pipefd[1]); + + return -1; + } + + flags &= ~O_CLOEXEC; + } + + if (fcntl(pipefd[0], F_SETFL, flags) != 0 || + fcntl(pipefd[1], F_SETFL, flags) != 0) { + close(pipefd[0]); + close(pipefd[1]); + + return -1; + } + + return 0; +} + +/* + * sigtimedwait() implementation based on + * https://comp.unix.programmer.narkive.com/rEDH0sPT/sigtimedwait-implementation + * and + * https://github.com/wahern/lunix/blob/master/src/unix.c + */ +static void +sigtimedwait_consume_signal(int signo) +{ +} + +int +sigtimedwait(const sigset_t *set, siginfo_t *info, const struct timespec *timeout) +{ + struct timespec elapsed = { 0, 0 }, sleep, rem; + sigset_t pending, unblock, omask; + struct sigaction sa, osa; + int signo; + bool lt; + + while (true) { + sigemptyset(&pending); + sigpending(&pending); + + for (signo = 1; signo < NSIG; signo++) { + if (!sigismember(set, signo) || !sigismember(&pending, signo)) + continue; + + sa.sa_handler = sigtimedwait_consume_signal; + sa.sa_flags = 0; + sigfillset(&sa.sa_mask); + + sigaction(signo, &sa, &osa); + + sigemptyset(&unblock); + sigaddset(&unblock, signo); + sigprocmask(SIG_UNBLOCK, &unblock, &omask); + sigprocmask(SIG_SETMASK, &omask, NULL); + + sigaction(signo, &osa, NULL); + + if (info) { + memset(info, 0, sizeof(*info)); + info->si_signo = signo; + } + + return signo; + } + + sleep.tv_sec = 0; + sleep.tv_nsec = 200000000L; /* 2/10th second */ + rem = sleep; + + if (nanosleep(&sleep, &rem) == 0) { + elapsed.tv_sec += sleep.tv_sec; + elapsed.tv_nsec += sleep.tv_nsec; + + if (elapsed.tv_nsec > 1000000000) { + elapsed.tv_sec++; + elapsed.tv_nsec -= 1000000000; + } + } + else if (errno == EINTR) { + sleep.tv_sec -= rem.tv_sec; + sleep.tv_nsec -= rem.tv_nsec; + + if (sleep.tv_nsec < 0) { + sleep.tv_sec--; + sleep.tv_nsec += 1000000000; + } + + elapsed.tv_sec += sleep.tv_sec; + elapsed.tv_nsec += sleep.tv_nsec; + + if (elapsed.tv_nsec > 1000000000) { + elapsed.tv_sec++; + elapsed.tv_nsec -= 1000000000; + } + } + else { + return errno; + } + + lt = timeout + ? ((elapsed.tv_sec == timeout->tv_sec) + ? (elapsed.tv_nsec < timeout->tv_nsec) + : (elapsed.tv_sec < timeout->tv_sec)) + : true; + + if (!lt) + break; + } + + errno = EAGAIN; + + return -1; +} +#endif diff --git a/program.c b/program.c index abd525a..d2d27fb 100644 --- a/program.c +++ b/program.c @@ -16,12 +16,12 @@ #include #include -#include #include "ucode/program.h" #include "ucode/source.h" #include "ucode/vallist.h" #include "ucode/chunk.h" +#include "ucode/platform.h" uc_program_t * diff --git a/source.c b/source.c index ba218ff..39295f6 100644 --- a/source.c +++ b/source.c @@ -16,9 +16,9 @@ #include #include -#include #include "ucode/source.h" +#include "ucode/platform.h" uc_source_t * diff --git a/types.c b/types.c index d3aae0a..226c874 100644 --- a/types.c +++ b/types.c @@ -17,7 +17,6 @@ #include #include #include -#include #include #include #include diff --git a/vallist.c b/vallist.c index 44709bf..886ede0 100644 --- a/vallist.c +++ b/vallist.c @@ -15,7 +15,6 @@ */ #include /* memcpy(), memset() */ -#include /* htobe64(), be64toh() */ #include /* isnan(), INFINITY */ #include /* isspace(), isdigit(), isxdigit() */ #include @@ -27,6 +26,7 @@ #include "ucode/program.h" #include "ucode/vallist.h" #include "ucode/vm.h" +#include "ucode/platform.h" #define TAG_TYPE uint64_t #define TAG_BITS 3 diff --git a/vm.c b/vm.c index d3c26bf..f4bc308 100644 --- a/vm.c +++ b/vm.c @@ -28,6 +28,7 @@ #include "ucode/compiler.h" #include "ucode/program.h" #include "ucode/lib.h" /* uc_error_context_format() */ +#include "ucode/platform.h" #undef __insn #define __insn(_name) #_name, @@ -154,36 +155,6 @@ uc_vm_signal_handler(int sig) uc_vm_signal_raise(signal_handler_vm, sig); } -#ifdef __APPLE__ -static int pipe2(int pipefd[2], int flags) -{ - if (pipe(pipefd) != 0) - return -1; - - if (flags & O_CLOEXEC) { - if (fcntl(pipefd[0], F_SETFD, FD_CLOEXEC) != 0 || - fcntl(pipefd[1], F_SETFD, FD_CLOEXEC) != 0) { - close(pipefd[0]); - close(pipefd[1]); - - return -1; - } - - flags &= ~O_CLOEXEC; - } - - if (fcntl(pipefd[0], F_SETFL, flags) != 0 || - fcntl(pipefd[1], F_SETFL, flags) != 0) { - close(pipefd[0]); - close(pipefd[1]); - - return -1; - } - - return 0; -} -#endif - static void uc_vm_signal_handlers_setup(uc_vm_t *vm) { @@ -200,7 +171,7 @@ uc_vm_signal_handlers_setup(uc_vm_t *vm) signal_handler_vm = vm; - vm->signal.handler = ucv_array_new_length(vm, NSIG - 1); + vm->signal.handler = ucv_array_new_length(vm, UC_SYSTEM_SIGNAL_COUNT); vm->signal.sa.sa_handler = uc_vm_signal_handler; vm->signal.sa.sa_flags = SA_RESTART | SA_ONSTACK; @@ -3182,7 +3153,7 @@ uc_vm_signal_raise(uc_vm_t *vm, int signo) { uint8_t signum = signo; - if (signo <= 0 || signo >= NSIG) + if (signo <= 0 || signo >= UC_SYSTEM_SIGNAL_COUNT) return; vm->signal.raised[signo / 64] |= (1ull << (signo % 64)); -- cgit v1.2.3 From 6940c283d57d2c4e2ddab689fdba770823b8bcc0 Mon Sep 17 00:00:00 2001 From: Jo-Philipp Wich Date: Thu, 27 Jul 2023 14:42:11 +0200 Subject: lib: introduce debug library Introduce a new debug library which provides introspection facilities for debugging ucode scripts. Signed-off-by: Jo-Philipp Wich --- CMakeLists.txt | 15 + include/ucode/chunk.h | 4 +- include/ucode/lib.h | 2 +- include/ucode/program.h | 4 +- include/ucode/source.h | 2 +- lib/debug.c | 1631 +++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 1652 insertions(+), 6 deletions(-) create mode 100644 lib/debug.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 280cbb4..9c2aead 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,6 +19,7 @@ IF(NOT COMPILE_SUPPORT) ADD_DEFINITIONS(-DNO_COMPILE) ENDIF() +OPTION(DEBUG_SUPPORT "Debug plugin support" ON) OPTION(FS_SUPPORT "Filesystem plugin support" ON) OPTION(MATH_SUPPORT "Math plugin support" ON) OPTION(UBUS_SUPPORT "Ubus plugin support" ON) @@ -89,6 +90,20 @@ UNSET(CMAKE_REQUIRED_LIBRARIES) SET(LIBRARIES "") +IF(DEBUG_SUPPORT) + SET(LIBRARIES ${LIBRARIES} debug_lib) + ADD_LIBRARY(debug_lib MODULE lib/debug.c) + SET_TARGET_PROPERTIES(debug_lib PROPERTIES OUTPUT_NAME debug PREFIX "") + TARGET_LINK_OPTIONS(debug_lib PRIVATE ${UCODE_MODULE_LINK_OPTIONS}) + FIND_LIBRARY(ubox NAMES ubox) + IF(ubox) + FIND_PATH(uloop_include_dir NAMES libubox/uloop.h) + INCLUDE_DIRECTORIES(${uloop_include_dir}) + TARGET_LINK_LIBRARIES(debug_lib ${ubox} ${libucode}) + SET_TARGET_PROPERTIES(debug_lib PROPERTIES COMPILE_DEFINITIONS HAVE_ULOOP) + ENDIF() +ENDIF() + IF(FS_SUPPORT) SET(LIBRARIES ${LIBRARIES} fs_lib) ADD_LIBRARY(fs_lib MODULE lib/fs.c) diff --git a/include/ucode/chunk.h b/include/ucode/chunk.h index a5f0b1c..1e6ab1f 100644 --- a/include/ucode/chunk.h +++ b/include/ucode/chunk.h @@ -30,8 +30,8 @@ __hidden size_t uc_chunk_add(uc_chunk_t *chunk, uint8_t byte, size_t line); __hidden void uc_chunk_pop(uc_chunk_t *chunk); -__hidden size_t uc_chunk_debug_get_srcpos(uc_chunk_t *chunk, size_t off); +size_t uc_chunk_debug_get_srcpos(uc_chunk_t *chunk, size_t offset); __hidden void uc_chunk_debug_add_variable(uc_chunk_t *chunk, size_t from, size_t to, size_t slot, bool upval, uc_value_t *name); -__hidden uc_value_t *uc_chunk_debug_get_variable(uc_chunk_t *chunk, size_t off, size_t slot, bool upval); +uc_value_t *uc_chunk_debug_get_variable(uc_chunk_t *chunk, size_t offset, size_t slot, bool upval); #endif /* UCODE_CHUNK_H */ diff --git a/include/ucode/lib.h b/include/ucode/lib.h index 74d8866..cf5f12f 100644 --- a/include/ucode/lib.h +++ b/include/ucode/lib.h @@ -31,7 +31,7 @@ extern const uc_function_list_t uc_stdlib_functions[]; void uc_stdlib_load(uc_value_t *scope); uc_cfn_ptr_t uc_stdlib_function(const char *name); -__hidden bool uc_source_context_format(uc_stringbuf_t *buf, uc_source_t *src, size_t off, bool compact); +bool uc_source_context_format(uc_stringbuf_t *buf, uc_source_t *src, size_t off, bool compact); __hidden bool uc_error_context_format(uc_stringbuf_t *buf, uc_source_t *src, uc_value_t *stacktrace, size_t off); __hidden void uc_error_message_indent(char **msg); diff --git a/include/ucode/program.h b/include/ucode/program.h index 19c8bdf..76b54b4 100644 --- a/include/ucode/program.h +++ b/include/ucode/program.h @@ -51,8 +51,8 @@ uc_program_put(uc_program_t *prog) { __hidden uc_function_t *uc_program_function_new(uc_program_t *, const char *, uc_source_t *, size_t); __hidden size_t uc_program_function_id(uc_program_t *, uc_function_t *); __hidden uc_function_t *uc_program_function_load(uc_program_t *, size_t); -__hidden uc_source_t *uc_program_function_source(uc_function_t *); -__hidden size_t uc_program_function_srcpos(uc_function_t *, size_t); +uc_source_t *uc_program_function_source(uc_function_t *); +size_t uc_program_function_srcpos(uc_function_t *, size_t); __hidden void uc_program_function_free(uc_function_t *); __hidden uc_value_t *uc_program_get_constant(uc_program_t *, size_t); diff --git a/include/ucode/source.h b/include/ucode/source.h index e1fd211..92011d8 100644 --- a/include/ucode/source.h +++ b/include/ucode/source.h @@ -35,7 +35,7 @@ typedef enum { uc_source_t *uc_source_new_file(const char *path); uc_source_t *uc_source_new_buffer(const char *name, char *buf, size_t len); -__hidden size_t uc_source_get_line(uc_source_t *source, size_t *offset); +size_t uc_source_get_line(uc_source_t *source, size_t *offset); static inline uc_source_t * uc_source_get(uc_source_t *source) { diff --git a/lib/debug.c b/lib/debug.c new file mode 100644 index 0000000..de2711b --- /dev/null +++ b/lib/debug.c @@ -0,0 +1,1631 @@ +/* + * Copyright (C) 2023 Jo-Philipp Wich + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, 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 THIS SOFTWARE. + */ + +/** + * This module provides runtime debug functionality for ucode scripts. + * + * Functions can be individually imported and directly accessed using the + * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#named_import named import} + * syntax: + * + * ``` + * import { memdump, traceback } from 'debug'; + * + * let stacktrace = traceback(1); + * + * memdump("/tmp/dump.txt"); + * ``` + * + * Alternatively, the module namespace can be imported + * using a wildcard import statement: + * + * ``` + * import * as debug from 'debug'; + * + * let stacktrace = debug.traceback(1); + * + * debug.memdump("/tmp/dump.txt"); + * ``` + * + * Additionally, the debug module namespace may also be imported by invoking the + * `ucode` interpreter with the `-ldebug` switch. + * + * Upon loading, the `debug` module will register a `SIGUSR2` signal handler + * which, upon receipt of the signal, will write a memory dump of the currently + * running program to `/tmp/ucode.$timestamp.$pid.memdump`. This default + * behavior can be inhibited by setting the `UCODE_DEBUG_MEMDUMP_ENABLED` + * environment variable to `0` when starting the process. The memory dump signal + * and output directory can be overridden with the `UCODE_DEBUG_MEMDUMP_SIGNAL` + * and `UCODE_DEBUG_MEMDUMP_PATH` environment variables respectively. + * + * @module debug + */ + +#include +#include +#include +#include +#include + +#ifdef HAVE_ULOOP +#include +#endif + +#include +#include + +#include "ucode/module.h" +#include "ucode/platform.h" + + +static char *memdump_signal = "USR2"; +static char *memdump_directory = "/tmp"; + +struct memdump_walk_ctx { + FILE *out; + uc_closure_t *current_closure; + struct lh_table *seen; +}; + +static uc_callframe_t * +debuginfo_stackslot_to_callframe(uc_vm_t *vm, size_t slot) +{ + size_t stackframe, i; + + for (i = vm->callframes.count; i > 0; i--) { + stackframe = vm->callframes.entries[i - 1].stackframe; + + if (vm->callframes.entries[i - 1].mcall) + stackframe--; + + if (stackframe <= slot) + return &vm->callframes.entries[i - 1]; + } + + return NULL; +} + +static void +uc_debug_discover_ucv(uc_value_t *uv, struct lh_table *seen); + +static void +uc_debug_discover_ucv(uc_value_t *uv, struct lh_table *seen) +{ + uc_function_t *function; + uc_closure_t *closure; + uc_upvalref_t *upval; + uc_object_t *object; + uc_array_t *array; + uc_resource_t *resource; + uc_program_t *program; + struct lh_entry *entry; + unsigned long hash; + size_t i; + + hash = lh_get_hash(seen, uv); + + if (ucv_is_scalar(uv)) + return; + + if (lh_table_lookup_entry_w_hash(seen, uv, hash)) + return; + + lh_table_insert_w_hash(seen, uv, NULL, hash, 0); + + switch (ucv_type(uv)) { + case UC_ARRAY: + array = (uc_array_t *)uv; + + uc_debug_discover_ucv(array->proto, seen); + + for (i = 0; i < array->count; i++) + uc_debug_discover_ucv(array->entries[i], seen); + + break; + + case UC_OBJECT: + object = (uc_object_t *)uv; + + uc_debug_discover_ucv(object->proto, seen); + + lh_foreach(object->table, entry) + uc_debug_discover_ucv((uc_value_t *)lh_entry_v(entry), seen); + + break; + + case UC_CLOSURE: + closure = (uc_closure_t *)uv; + function = closure->function; + + for (i = 0; i < function->nupvals; i++) + uc_debug_discover_ucv(&closure->upvals[i]->header, seen); + + uc_debug_discover_ucv(&function->program->header, seen); + + break; + + case UC_UPVALUE: + upval = (uc_upvalref_t *)uv; + uc_debug_discover_ucv(upval->value, seen); + break; + + case UC_RESOURCE: + resource = (uc_resource_t *)uv; + + if (resource->type) + uc_debug_discover_ucv(resource->type->proto, seen); + + break; + + case UC_PROGRAM: + program = (uc_program_t *)uv; + + for (i = 0; i < program->sources.count; i++) + uc_debug_discover_ucv(&program->sources.entries[i]->header, seen); + + for (i = 0; i < program->exports.count; i++) + uc_debug_discover_ucv(&program->exports.entries[i]->header, seen); + + break; + + default: + break; + } +} + +static void +print_value(FILE *out, size_t pad, struct lh_table *seen, + uc_vm_t *vm, uc_value_t *uv); + +static void +print_value(FILE *out, size_t pad, struct lh_table *seen, + uc_vm_t *vm, uc_value_t *uv) +{ + uc_resource_t *resource; + uc_closure_t *closure; + uc_object_t *object; + uc_array_t *array; + size_t i, j; + char *s; + + fprintf(out, "%s", ucv_typename(uv)); + + if (!uv) { + fprintf(out, "\n"); + + return; + } + + if (!ucv_is_scalar(uv)) + fprintf(out, "; %" PRIu32 " refs", uv->refcount); + + if (!lh_table_lookup_entry(seen, uv)) + fprintf(out, "; unreachable"); + + if (ucv_is_constant(uv)) + fprintf(out, "; constant"); + + fprintf(out, "\n"); + + for (j = 0; j < pad + 1; j++) + fprintf(out, " "); + + s = ucv_to_string(NULL, uv); + fprintf(out, "#value = %s\n", s); + free(s); + + if (ucv_type(uv) == UC_CLOSURE) { + closure = (uc_closure_t *)uv; + + for (i = 0; i < closure->function->nupvals; i++) { + for (j = 0; j < pad + 1; j++) + fprintf(out, " "); + + fprintf(out, "#upvalue[%zu] ", i); + + if (closure->upvals[i]->closed) { + fprintf(out, "closed; "); + print_value(out, pad + 1, seen, vm, closure->upvals[i]->value); + } + else { + fprintf(out, "open; stack slot %zu\n", + closure->upvals[i]->slot); + } + } + } + else if (ucv_type(uv) == UC_OBJECT) { + object = (uc_object_t *)uv; + + if (object->proto) { + for (j = 0; j < pad + 1; j++) + fprintf(out, " "); + + fprintf(out, "#prototype = "); + print_value(out, pad + 1, seen, vm, object->proto); + } + } + else if (ucv_type(uv) == UC_ARRAY) { + array = (uc_array_t *)uv; + + if (array->proto) { + for (j = 0; j < pad + 1; j++) + fprintf(out, " "); + + fprintf(out, "#prototype = "); + print_value(out, pad + 1, seen, vm, array->proto); + } + } + else if (ucv_type(uv) == UC_RESOURCE) { + resource = (uc_resource_t *)uv; + + if (resource->type) { + for (j = 0; j < pad + 1; j++) + fprintf(out, " "); + + fprintf(out, "#type %s\n", resource->type->name); + + if (resource->type->proto) { + for (j = 0; j < pad + 2; j++) + fprintf(out, " "); + + fprintf(out, "#prototype = "); + print_value(out, pad + 2, seen, vm, resource->type->proto); + } + } + } +} + +static size_t +insnoff_to_srcpos(uc_function_t *function, size_t *insnoff) +{ + size_t byteoff, lineno; + uc_source_t *source; + + source = uc_program_function_source(function); + byteoff = uc_program_function_srcpos(function, *insnoff); + lineno = uc_source_get_line(source, &byteoff); + + *insnoff = byteoff; + + return lineno; +} + +static void +print_declaration_srcpos(FILE *out, uc_callframe_t *frame, size_t off, size_t slot, bool upval) +{ + uc_function_t *function = frame->closure->function; + uc_variables_t *variables = &function->chunk.debuginfo.variables; + size_t i, line; + + assert(slot <= ((size_t)-1 / 2)); + + if (upval) + slot += (size_t)-1 / 2; + + for (i = 0; i < variables->count; i++) { + if (variables->entries[i].slot != slot || + variables->entries[i].from > off || + variables->entries[i].to < off) + continue; + + off = variables->entries[i].from; + line = insnoff_to_srcpos(function, &off); + + fprintf(out, "%s:%zu:%zu", + uc_program_function_source(function)->filename, line, off); + + return; + } + + fprintf(out, "[unknown source position]"); +} + +static void +print_function_srcpos(FILE *out, uc_closure_t *closure) +{ + size_t line, off; + + if (!closure) + return; + + off = 0; + line = insnoff_to_srcpos(closure->function, &off); + + fprintf(out, " @ %s:%zu:%zu", + uc_program_function_source(closure->function)->filename, line, off); +} + +static void +print_ip_srcpos(FILE *out, uc_callframe_t *frame) +{ + uc_function_t *function; + size_t line, off; + + if (!frame->closure) + return; + + function = frame->closure->function; + off = frame->ip - function->chunk.entries; + line = insnoff_to_srcpos(function, &off); + + fprintf(out, " @ %s:%zu:%zu", + uc_program_function_source(function)->filename, line, off); +} + +static void +print_memdump(uc_vm_t *vm, FILE *out) +{ + struct memdump_walk_ctx ctx = { 0 }; + uc_callframe_t *frame; + uc_chunk_t *chunk; + uc_weakref_t *ref; + uc_value_t *uv; + size_t i; + char *s; + + ctx.out = out; + ctx.seen = lh_kptr_table_new(16, NULL); + + if (!ctx.seen) { + fprintf(stderr, "Unable to allocate lookup table: %m\n"); + + return; + } + + fprintf(ctx.out, "STACK\n"); + + for (i = 0; i < vm->stack.count; i++) { + fprintf(ctx.out, "[%zu]", i); + + frame = debuginfo_stackslot_to_callframe(vm, i); + + if (frame) { + chunk = frame->closure ? &frame->closure->function->chunk : NULL; + uv = chunk ? uc_chunk_debug_get_variable( + chunk, + frame->ip - chunk->entries + 1, + i - frame->stackframe, + false) : NULL; + + if (uv) { + fprintf(ctx.out, " %s @ ", + ucv_string_get(uv)); + + print_declaration_srcpos(ctx.out, frame, + frame->ip - chunk->entries + 1, + i - frame->stackframe, false); + + ucv_put(uv); + } + else if (frame->mcall && i == frame->stackframe - 1) { + fprintf(ctx.out, " (this)"); + + if (frame->closure) + print_function_srcpos(ctx.out, frame->closure); + else + fprintf(ctx.out, " @ [C function \"%s\"]", + frame->cfunction->name); + } + else if (i == frame->stackframe) { + fprintf(ctx.out, " (callee)"); + + if (frame->closure) + print_function_srcpos(ctx.out, frame->closure); + else + fprintf(ctx.out, " @ [C function \"%s\"]", + frame->cfunction->name); + } + else if (i > frame->stackframe) { + fprintf(ctx.out, " (argument #%zu)", + i - frame->stackframe); + + if (frame->closure) + print_function_srcpos(ctx.out, frame->closure); + else + fprintf(ctx.out, " @ [C function \"%s\"]", + frame->cfunction->name); + } + } + + fprintf(ctx.out, "\n"); + + uc_debug_discover_ucv(vm->stack.entries[i], ctx.seen); + + s = ucv_to_string(NULL, vm->stack.entries[i]); + fprintf(ctx.out, " #value = %s\n", s); + free(s); + } + + fprintf(ctx.out, "---\n\n"); + + fprintf(ctx.out, "CALLFRAMES\n"); + + for (i = 0; i < vm->callframes.count; i++) { + fprintf(ctx.out, "[%zu]", i); + print_ip_srcpos(ctx.out, &vm->callframes.entries[i]); + fprintf(ctx.out, "\n"); + + uc_debug_discover_ucv(vm->callframes.entries[i].ctx, + ctx.seen); + + uc_debug_discover_ucv(&vm->callframes.entries[i].closure->header, + ctx.seen); + + uc_debug_discover_ucv(&vm->callframes.entries[i].cfunction->header, + ctx.seen); + + s = ucv_to_string(NULL, vm->callframes.entries[i].ctx); + fprintf(ctx.out, " #context = %s\n", s); + free(s); + + if (vm->callframes.entries[i].closure) { + s = ucv_to_string(NULL, + &vm->callframes.entries[i].closure->header); + fprintf(ctx.out, " #closure = %s\n", s); + free(s); + } + + if (vm->callframes.entries[i].cfunction) { + s = ucv_to_string(NULL, + &vm->callframes.entries[i].cfunction->header); + + fprintf(ctx.out, " #cfunction = %s\n", s); + free(s); + } + } + + fprintf(ctx.out, "---\n\n"); + + fprintf(ctx.out, "GLOBALS\n"); + uc_debug_discover_ucv(vm->globals, ctx.seen); + i = 0; + ucv_object_foreach(vm->globals, gk, gv) { + s = ucv_to_string(NULL, gv); + fprintf(ctx.out, "[%zu] %s\n", i++, gk); + fprintf(ctx.out, " #value = %s\n", s); + free(s); + } + fprintf(ctx.out, "---\n\n"); + + fprintf(ctx.out, "REGISTRY\n"); + uc_debug_discover_ucv(vm->registry, ctx.seen); + i = 0; + ucv_object_foreach(vm->registry, rk, rv) { + s = ucv_to_string(NULL, rv); + fprintf(ctx.out, "[%zu] %s\n", i++, rk); + fprintf(ctx.out, " #value = %s\n", s); + free(s); + } + fprintf(ctx.out, "---\n\n"); + + fprintf(ctx.out, "EXCEPTION\n"); + uc_debug_discover_ucv(vm->exception.stacktrace, ctx.seen); + s = ucv_to_string(NULL, vm->exception.stacktrace); + fprintf(ctx.out, "%s\n", s); + free(s); + fprintf(ctx.out, "---\n\n"); + + fprintf(ctx.out, "RESOURCE TYPES\n"); + + for (i = 0; i < vm->restypes.count; i++) { + fprintf(ctx.out, "[%zu] %s\n", i, + vm->restypes.entries[i]->name); + + uc_debug_discover_ucv(vm->restypes.entries[i]->proto, ctx.seen); + + s = ucv_to_string(NULL, vm->restypes.entries[i]->proto); + fprintf(ctx.out, " #prototype = %s\n", s); + free(s); + } + + fprintf(ctx.out, "---\n\n"); + + fprintf(ctx.out, "OBJECT POOL\n"); + + for (ref = vm->values.next, i = 0; + ref != &vm->values; + ref = ref->next, i++) { + + uv = (uc_value_t *)((uintptr_t)ref - offsetof(uc_array_t, ref)); + + fprintf(ctx.out, "[%zu] ", i); + print_value(ctx.out, 0, ctx.seen, vm, uv); + } + + lh_table_free(ctx.seen); +} + +static uc_value_t * +debug_handle_memdump(uc_vm_t *vm, size_t nargs) +{ + char *path; + FILE *out; + + xasprintf(&path, "%s/ucode.%llu.%llu.memdump", + memdump_directory, + (long long unsigned int)time(NULL), + (long long unsigned int)getpid()); + + out = fopen(path, "w"); + + if (!out) { + fprintf(stderr, "Unable to open memdump file '%s': %m\n", path); + + return NULL; + } + + print_memdump(vm, out); + + fclose(out); + free(path); + + return NULL; +} + +#ifdef HAVE_ULOOP +/* The uloop signal handling activation has been intentionally copied from + the uloop module here to ensure that uloop signal dispatching also works + when just loading the debug module without the uloop one. */ +static struct { + struct uloop_fd ufd; + uc_vm_t *vm; +} signal_handle; + +static void +uc_uloop_signal_cb(struct uloop_fd *ufd, unsigned int events) +{ + if (uc_vm_signal_dispatch(signal_handle.vm) != EXCEPTION_NONE) + uloop_end(); +} + +static void +debug_setup_uloop(uc_vm_t *vm) +{ + int signal_fd = uc_vm_signal_notifyfd(vm); + + if (signal_fd != -1 && uloop_init() == 0) { + signal_handle.vm = vm; + signal_handle.ufd.cb = uc_uloop_signal_cb; + signal_handle.ufd.fd = signal_fd; + + uloop_fd_add(&signal_handle.ufd, ULOOP_READ); + } +} +#else +static void debug_setup_uloop(uc_vm_t *vm) {} +#endif + +static void +debug_setup_memdump(uc_vm_t *vm) +{ + uc_cfn_ptr_t ucsignal = uc_stdlib_function("signal"); + uc_value_t *memdump = ucv_cfunction_new("memdump", debug_handle_memdump); + char *ev; + + ev = getenv("UCODE_DEBUG_MEMDUMP_PATH"); + memdump_directory = ev ? ev : memdump_directory; + + ev = getenv("UCODE_DEBUG_MEMDUMP_SIGNAL"); + memdump_signal = ev ? ev : memdump_signal; + + debug_setup_uloop(vm); + + uc_vm_stack_push(vm, ucv_string_new(memdump_signal)); + uc_vm_stack_push(vm, memdump); + + if (ucsignal(vm, 2) != memdump) + fprintf(stderr, "Unable to install debug signal handler\n"); + + ucv_put(uc_vm_stack_pop(vm)); + ucv_put(uc_vm_stack_pop(vm)); +} + +static void +debug_setup(uc_vm_t *vm) +{ + char *ev; + + ev = getenv("UCODE_DEBUG_MEMDUMP_ENABLED"); + + if (!ev || !strcmp(ev, "1") || !strcmp(ev, "yes") || !strcmp(ev, "true")) + debug_setup_memdump(vm); +} + + +/** + * Write a memory dump report to the given file. + * + * This function generates a human readable memory dump of ucode values + * currently managed by the running VM which is useful to track down logical + * memory leaks in scripts. + * + * The file parameter can be either a string value containing a file path, in + * which case this function tries to create and write the report file at the + * given location, or an already open file handle this function should write to. + * + * Returns `true` if the report has been written. + * + * Returns `null` if the file could not be opened or if the handle was invalid. + * + * @function module:debug#memdump + * + * @param {string|module:fs.file|module:fs.proc} file + * The file path or open file handle to write report to. + * + * @return {boolean|null} + */ +static uc_value_t * +uc_memdump(uc_vm_t *vm, size_t nargs) +{ + uc_value_t *file = uc_fn_arg(0); + FILE *fp = NULL; + + if (ucv_type(file) == UC_RESOURCE) { + fp = ucv_resource_data(file, "fs.file"); + + if (!fp) + fp = ucv_resource_data(file, "fs.proc"); + } + else if (ucv_type(file) == UC_STRING) { + fp = fopen(ucv_string_get(file), "w"); + } + + if (!fp) + return NULL; + + print_memdump(vm, fp); + + return ucv_boolean_new(true); +} + +/** + * Capture call stack trace. + * + * This function captures the current call stack and returns it. The optional + * level parameter controls how many calls up the trace should start. It + * defaults to `1`, that is the function calling this `traceback()` function. + * + * Returns an array of stack trace entries describing the function invocations + * up to the point where `traceback()` is called. + * + * @function module:debug#traceback + * + * @param {number} [level=1] + * The number of callframes up the call trace should start, `0` is this function + * itself, `1` the function calling it and so on. + * + * @return {module:debug.StackTraceEntry[]} + */ + +/** + * @typedef {Object} module:debug.StackTraceEntry + * + * @property {function} callee + * The function that was called. + * + * @property {*} this + * The `this` context the function was called with. + * + * @property {boolean} mcall + * Indicates whether the function was invoked as a method. + * + * @property {boolean} [strict] + * Indicates whether the VM was running in strict mode when the function was + * called (only applicable to non-C, pure ucode calls). + * + * @property {string} [filename] + * The name of the source file that called the function (only applicable to + * non-C, pure ucode calls). + * + * @property {number} [line] + * The source line of the function call (only applicable to non-C, pure ucode + * calls). + * + * @property {number} [byte] + * The source line offset of the function call (only applicable to non-C, pure + * ucode calls). + * + * @property {string} [context] + * The surrounding source code context formatted as human-readable string, + * useful for generating debug messages (only applicable to non-C, pure ucode + * calls). + */ + +static uc_value_t * +uc_traceback(uc_vm_t *vm, size_t nargs) +{ + uc_value_t *stacktrace, *entry, *level = uc_fn_arg(0); + uc_function_t *function; + uc_stringbuf_t *context; + uc_callframe_t *frame; + uc_source_t *source; + size_t off, srcpos; + size_t i, lv; + + lv = level ? ucv_uint64_get(level) : 1; + + if (level && errno) + return NULL; + + stacktrace = ucv_array_new(vm); + + for (i = (lv < vm->callframes.count) ? vm->callframes.count - lv : 0; + i > 0; + i--) { + + frame = &vm->callframes.entries[i - 1]; + entry = ucv_object_new(vm); + + if (frame->closure) { + function = frame->closure->function; + source = uc_program_function_source(function); + + off = (frame->ip - function->chunk.entries) - 1; + srcpos = uc_program_function_srcpos(function, off); + + context = ucv_stringbuf_new(); + + uc_source_context_format(context, + uc_program_function_source(function), + srcpos, false); + + ucv_object_add(entry, "callee", ucv_get(&frame->closure->header)); + ucv_object_add(entry, "this", ucv_get(frame->ctx)); + ucv_object_add(entry, "mcall", ucv_boolean_new(frame->mcall)); + ucv_object_add(entry, "strict", ucv_boolean_new(frame->strict)); + ucv_object_add(entry, "filename", ucv_string_new(source->filename)); + ucv_object_add(entry, "line", ucv_int64_new(uc_source_get_line(source, &srcpos))); + ucv_object_add(entry, "byte", ucv_int64_new(srcpos)); + ucv_object_add(entry, "context", ucv_stringbuf_finish(context)); + } + else if (frame->cfunction) { + ucv_object_add(entry, "callee", ucv_get(&frame->cfunction->header)); + ucv_object_add(entry, "this", ucv_get(frame->ctx)); + ucv_object_add(entry, "mcall", ucv_boolean_new(frame->mcall)); + } + + ucv_array_push(stacktrace, entry); + } + + return stacktrace; +} + +/** + * Obtain information about the current source position. + * + * The `sourcepos()` function determines the source code position of the + * current instruction invoking this function. + * + * Returns a dictionary containing the filename, line number and line byte + * offset of the call site. + * + * Returns `null` if this function was invoked from C code. + * + * @function module:debug#sourcepos + * + * @return {module:debug.SourcePosition|null} + */ + +/** + * @typedef {Object} module:debug.SourcePosition + * + * @property {string} filename + * The name of the source file that called this function. + * + * @property {number} line + * The source line of the function call. + * + * @property {number} byte + * The source line offset of the function call. + */ + +static uc_value_t * +uc_sourcepos(uc_vm_t *vm, size_t nargs) +{ + uc_function_t *function; + uc_callframe_t *frame; + uc_source_t *source; + uc_value_t *rv; + size_t byte; + + if (vm->callframes.count < 2) + return NULL; + + frame = &vm->callframes.entries[vm->callframes.count - 2]; + + if (!frame->closure) + return NULL; + + function = frame->closure->function; + source = uc_program_function_source(function); + byte = uc_program_function_srcpos(function, + (frame->ip - function->chunk.entries) - 1); + + rv = ucv_object_new(vm); + + ucv_object_add(rv, "filename", ucv_string_new(source->filename)); + ucv_object_add(rv, "line", ucv_int64_new(uc_source_get_line(source, &byte))); + ucv_object_add(rv, "byte", ucv_int64_new(byte)); + + return rv; +} + +static uc_value_t * +uc_getinfo_fnargs(uc_vm_t *vm, uc_function_t *function) +{ + uc_value_t *rv = NULL, *name; + size_t i; + + for (i = 0; i < function->nargs; i++) { + name = uc_chunk_debug_get_variable(&function->chunk, i, i + 1, false); + + if (!name) + continue; + + if (!rv) + rv = ucv_array_new_length(vm, function->nargs); + + ucv_array_push(rv, name); + } + + return rv; +} + +/** + * @typedef {Object} module:debug.UpvalRef + * + * @property {string} name + * The name of the captured variable. + * + * @property {boolean} closed + * Indicates whether the captured variable (upvalue) is closed or not. A closed + * upvalue means that the function value outlived the declaration scope of the + * captured variable. + * + * @property {*} value + * The current value of the captured variable. + * + * @property {number} [slot] + * The stack slot of the captured variable. Only applicable to open (non-closed) + * captured variables. + */ +static uc_value_t * +uc_getinfo_upvals(uc_vm_t *vm, uc_closure_t *closure) +{ + uc_function_t *function = closure->function; + uc_upvalref_t **upvals = closure->upvals; + uc_value_t *rv = NULL, *up, *name; + size_t i; + + for (i = 0; i < function->nupvals; i++) { + up = ucv_object_new(vm); + name = uc_chunk_debug_get_variable(&function->chunk, 0, i, true); + + if (name) + ucv_object_add(up, "name", name); + + if (upvals[i]->closed) { + ucv_object_add(up, "closed", ucv_boolean_new(true)); + ucv_object_add(up, "value", ucv_get(upvals[i]->value)); + } + else { + ucv_object_add(up, "closed", ucv_boolean_new(false)); + ucv_object_add(up, "slot", ucv_uint64_new(upvals[i]->slot)); + ucv_object_add(up, "value", + ucv_get(vm->stack.entries[upvals[i]->slot])); + } + + if (!rv) + rv = ucv_array_new_length(vm, function->nupvals); + + ucv_array_push(rv, up); + } + + return rv; +} + +/** + * Obtain information about the given value. + * + * The `getinfo()` function allows querying internal information about the + * given ucode value, such as the current reference count, the mark bit state + * etc. + * + * Returns a dictionary with value type specific details. + * + * Returns `null` if a `null` value was provided. + * + * @function module:debug#getinfo + * + * @param {*} value + * The value to query information for. + * + * @return {module:debug.ValueInformation|null} + */ + +/** + * @typedef {Object} module:debug.ValueInformation + * + * @property {string} type + * The name of the value type, one of `integer`, `boolean`, `string`, `double`, + * `array`, `object`, `regexp`, `cfunction`, `closure`, `upvalue` or `resource`. + * + * @property {*} value + * The value itself. + * + * @property {boolean} tagged + * Indicates whether the given value is internally stored as tagged pointer + * without an additional heap allocation. + * + * @property {boolean} [mark] + * Indicates whether the value has it's mark bit set, which is used for loop + * detection during recursive object traversal on garbage collection cycles or + * complex value stringification. Only applicable to non-tagged values. + * + * @property {number} [refcount] + * The current reference count of the value. Note that the `getinfo()` function + * places a reference to the value into the `value` field of the resulting + * information dictionary, so the ref count will always be at least 2 - one + * reference for the function call argument and one for the value property in + * the result dictionary. Only applicable to non-tagged values. + * + * @property {boolean} [unsigned] + * Whether the number value is internally stored as unsigned integer. Only + * applicable to non-tagged integer values. + * + * @property {number} [address] + * The address of the underlying C heap memory. Only applicable to non-tagged + * `string`, `array`, `object`, `cfunction` or `resource` values. + * + * @property {number} [length] + * The length of the underlying string memory. Only applicable to non-tagged + * `string` values. + * + * @property {number} [count] + * The amount of elements in the underlying memory structure. Only applicable to + * `array` and `object` values. + * + * @property {boolean} [constant] + * Indicates whether the value is constant (immutable). Only applicable to + * `array` and `object` values. + * + * @property {*} [prototype] + * The associated prototype value, if any. Only applicable to `array`, `object` + * and `prototype` values. + * + * @property {string} [source] + * The original regex source pattern. Only applicable to `regexp` values. + * + * @property {boolean} [icase] + * Whether the compiled regex has the `i` (ignore case) flag set. Only + * applicable to `regexp` values. + * + * @property {boolean} [global] + * Whether the compiled regex has the `g` (global) flag set. Only applicable to + * `regexp` values. + * + * @property {boolean} [newline] + * Whether the compiled regex has the `s` (single line) flag set. Only + * applicable to `regexp` values. + * + * @property {number} [nsub] + * The amount of capture groups within the regular expression. Only applicable + * to `regexp` values. + * + * @property {string} [name] + * The name of the non-anonymous function. Only applicable to `cfunction` and + * `closure` values. Set to `null` for anonymous function values. + * + * @property {boolean} [arrow] + * Indicates whether the ucode function value is an arrow function. Only + * applicable to `closure` values. + * + * @property {boolean} [module] + * Indicates whether the ucode function value is a module entry point. Only + * applicable to `closure` values. + * + * @property {boolean} [strict] + * Indicates whether the function body will be executed in strict mode. Only + * applicable to `closure` values. + * + * @property {boolean} [vararg] + * Indicates whether the ucode function takes a variable number of arguments. + * Only applicable to `closure` values. + * + * @property {number} [nargs] + * The number of arguments expected by the ucode function, excluding a potential + * final variable argument ellipsis. Only applicable to `closure` values. + * + * @property {string[]} [argnames] + * The names of the function arguments in their declaration order. Only + * applicable to `closure` values. + * + * @property {number} [nupvals] + * The number of upvalues associated with the ucode function. Only applicable to + * `closure` values. + * + * @property {module:debug.UpvalRef[]} [upvals] + * An array of upvalue information objects. Only applicable to `closure` values. + * + * @property {string} [filename] + * The path of the source file the function was declared in. Only applicable to + * `closure` values. + * + * @property {number} [line] + * The source line number the function was declared at. Only applicable to + * `closure` values. + * + * @property {number} [byte] + * The source line offset the function was declared at. Only applicable to + * `closure` values. + * + * @property {string} [type] + * The resource type name. Only applicable to `resource` values. + */ + +static uc_value_t * +uc_getinfo(uc_vm_t *vm, size_t nargs) +{ + uc_value_t *uv = uc_fn_arg(0), *rv; + uintptr_t pv = (uintptr_t)uv; + uc_cfunction_t *uvcfn; + uc_resource_t *uvres; + uc_closure_t *uvfun; + uc_source_t *source; + uc_regexp_t *uvreg; + uc_string_t *uvstr; + uc_object_t *uvobj; + uc_array_t *uvarr; + size_t byte; + + if (!uv) + return NULL; + + rv = ucv_object_new(vm); + + ucv_object_add(rv, "type", ucv_string_new(ucv_typename(uv))); + ucv_object_add(rv, "value", ucv_get(uv)); + + if (pv & 3) { + ucv_object_add(rv, "tagged", ucv_boolean_new(true)); + } + else { + ucv_object_add(rv, "tagged", ucv_boolean_new(false)); + ucv_object_add(rv, "mark", ucv_boolean_new(uv->mark)); + ucv_object_add(rv, "refcount", ucv_uint64_new(uv->refcount)); + } + + switch (ucv_type(uv)) { + case UC_INTEGER: + ucv_object_add(rv, "unsigned", + ucv_boolean_new(!(pv & 3) && uv->u64_or_constant)); + + break; + + case UC_STRING: + if (pv & 3) { + uvstr = (uc_string_t *)uv; + + ucv_object_add(rv, "address", + ucv_uint64_new((uintptr_t)uvstr->str)); + + ucv_object_add(rv, "length", ucv_uint64_new(uvstr->length)); + } + + break; + + case UC_ARRAY: + uvarr = (uc_array_t *)uv; + + ucv_object_add(rv, "address", + ucv_uint64_new((uintptr_t)uvarr->entries)); + + ucv_object_add(rv, "count", ucv_uint64_new(uvarr->count)); + ucv_object_add(rv, "constant", ucv_boolean_new(uv->u64_or_constant)); + ucv_object_add(rv, "prototype", ucv_get(uvarr->proto)); + + break; + + case UC_OBJECT: + uvobj = (uc_object_t *)uv; + + ucv_object_add(rv, "address", + ucv_uint64_new((uintptr_t)uvobj->table)); + + ucv_object_add(rv, "count", + ucv_uint64_new(lh_table_length(uvobj->table))); + + ucv_object_add(rv, "constant", ucv_boolean_new(uv->u64_or_constant)); + ucv_object_add(rv, "prototype", ucv_get(uvobj->proto)); + + break; + + case UC_REGEXP: + uvreg = (uc_regexp_t *)uv; + + ucv_object_add(rv, "source", ucv_string_new(uvreg->source)); + ucv_object_add(rv, "icase", ucv_boolean_new(uvreg->icase)); + ucv_object_add(rv, "global", ucv_boolean_new(uvreg->global)); + ucv_object_add(rv, "newline", ucv_boolean_new(uvreg->newline)); + ucv_object_add(rv, "nsub", ucv_uint64_new(uvreg->regexp.re_nsub)); + + break; + + case UC_CFUNCTION: + uvcfn = (uc_cfunction_t *)uv; + + ucv_object_add(rv, "name", ucv_string_new(uvcfn->name)); + ucv_object_add(rv, "address", ucv_uint64_new((uintptr_t)uvcfn->cfn)); + + break; + + case UC_CLOSURE: + uvfun = (uc_closure_t *)uv; + byte = uvfun->function->srcpos; + source = uc_program_function_source(uvfun->function); + + ucv_object_add(rv, "name", ucv_string_new(uvfun->function->name)); + ucv_object_add(rv, "arrow", ucv_boolean_new(uvfun->function->arrow)); + ucv_object_add(rv, "module", ucv_boolean_new(uvfun->function->module)); + ucv_object_add(rv, "strict", ucv_boolean_new(uvfun->function->strict)); + ucv_object_add(rv, "vararg", ucv_boolean_new(uvfun->function->vararg)); + ucv_object_add(rv, "nargs", ucv_uint64_new(uvfun->function->nargs)); + ucv_object_add(rv, "argnames", uc_getinfo_fnargs(vm, uvfun->function)); + ucv_object_add(rv, "nupvals", ucv_uint64_new(uvfun->function->nupvals)); + ucv_object_add(rv, "upvals", uc_getinfo_upvals(vm, uvfun)); + ucv_object_add(rv, "filename", ucv_string_new(source->filename)); + ucv_object_add(rv, "line", ucv_int64_new(uc_source_get_line(source, &byte))); + ucv_object_add(rv, "byte", ucv_int64_new(byte)); + + break; + + case UC_RESOURCE: + uvres = (uc_resource_t *)uv; + + ucv_object_add(rv, "address", ucv_uint64_new((uintptr_t)uvres->data)); + + if (uvres->type) { + ucv_object_add(rv, "type", ucv_string_new(uvres->type->name)); + ucv_object_add(rv, "prototype", ucv_get(uvres->type->proto)); + } + + break; + + default: + break; + } + + return rv; +} + +/** + * @typedef {Object} module:debug.LocalInfo + * + * @property {number} index + * The index of the local variable. + * + * @property {string} name + * The name of the local variable. + * + * @property {*} value + * The current value of the local variable. + * + * @property {number} linefrom + * The source line number of the local variable declaration. + * + * @property {number} bytefrom + * The source line offset of the local variable declaration. + * + * @property {number} lineto + * The source line number where the local variable goes out of scope. + * + * @property {number} byteto + * The source line offset where the local vatiable goes out of scope. + */ +static uc_value_t * +uc_xlocal(uc_vm_t *vm, uc_value_t *level, uc_value_t *var, uc_value_t **set) +{ + size_t lv, vn, vi, i, pos, slot = 0; + uc_value_t *vname = NULL, *rv; + uc_variables_t *variables; + uc_callframe_t *frame; + uc_source_t *source; + uc_chunk_t *chunk; + + lv = level ? ucv_uint64_get(level) : 1; + + if ((level && errno) || lv >= vm->callframes.count) + return NULL; + + frame = &vm->callframes.entries[vm->callframes.count - lv - 1]; + + if (!frame->closure) + return NULL; + + source = uc_program_function_source(frame->closure->function); + chunk = &frame->closure->function->chunk; + variables = &chunk->debuginfo.variables; + + if (ucv_type(var) == UC_INTEGER) { + vn = ucv_uint64_get(var); + var = NULL; + + if (errno || vn >= variables->count) + return NULL; + } + else if (ucv_type(var) == UC_STRING) { + vn = 0; + } + else { + return NULL; + } + + pos = frame->ip - chunk->entries; + + for (i = 0, vi = 0; i < variables->count; i++) { + slot = variables->entries[i].slot; + + if (slot >= (size_t)-1 / 2) + continue; + + if (variables->entries[i].from > pos || variables->entries[i].to < pos) + continue; + + vname = uc_chunk_debug_get_variable(chunk, pos, slot, false); + + if (var ? ucv_is_equal(var, vname) : (vi == vn)) + break; + + ucv_put(vname); + vname = NULL; + vi++; + } + + if (i == variables->count) + return NULL; + + if (set) { + ucv_put(vm->stack.entries[frame->stackframe + slot]); + vm->stack.entries[frame->stackframe + slot] = ucv_get(*set); + } + + rv = ucv_object_new(vm); + + ucv_object_add(rv, "index", ucv_uint64_new(vi)); + ucv_object_add(rv, "name", vname); + ucv_object_add(rv, "value", + ucv_get(vm->stack.entries[frame->stackframe + slot])); + + pos = uc_program_function_srcpos(frame->closure->function, + variables->entries[i].from); + + ucv_object_add(rv, "linefrom", + ucv_uint64_new(uc_source_get_line(source, &pos))); + + ucv_object_add(rv, "bytefrom", + ucv_uint64_new(pos)); + + pos = uc_program_function_srcpos(frame->closure->function, + variables->entries[i].to); + + ucv_object_add(rv, "lineto", + ucv_uint64_new(uc_source_get_line(source, &pos))); + + ucv_object_add(rv, "byteto", + ucv_uint64_new(pos)); + + return rv; +} + +/** + * Obtain local variable. + * + * The `getlocal()` function retrieves information about the specified local + * variable at the given call stack depth. + * + * The call stack depth specifies the amount of levels up local variables should + * be queried. A value of `0` refers to this `getlocal()` function call itself, + * `1` to the function calling `getlocal()` and so on. + * + * The variable to query might be either specified by name or by its index with + * index numbers following the source code declaration order. + * + * Returns a dictionary holding information about the given variable. + * + * Returns `null` if the stack depth exceeds the size of the current call stack. + * + * Returns `null` if the invocation at the given stack depth is a C call. + * + * Returns `null` if the given variable name is not found or the given variable + * index is invalid. + * + * @function module:debug#getlocal + * + * @param {number} [level=1] + * The amount of call stack levels up local variables should be queried. + * + * @param {string|number} variable + * The variable index or variable name to obtain information for. + * + * @returns {module:debug.LocalInfo|null} + */ +static uc_value_t * +uc_getlocal(uc_vm_t *vm, size_t nargs) +{ + uc_value_t *level = uc_fn_arg(0); + uc_value_t *var = uc_fn_arg(1); + + return uc_xlocal(vm, level, var, NULL); +} + +/** + * Set local variable. + * + * The `setlocal()` function manipulates the value of the specified local + * variable at the given call stack depth. + * + * The call stack depth specifies the amount of levels up local variables should + * be updated. A value of `0` refers to this `setlocal()` function call itself, + * `1` to the function calling `setlocal()` and so on. + * + * The variable to update might be either specified by name or by its index with + * index numbers following the source code declaration order. + * + * Returns a dictionary holding information about the updated variable. + * + * Returns `null` if the stack depth exceeds the size of the current call stack. + * + * Returns `null` if the invocation at the given stack depth is a C call. + * + * Returns `null` if the given variable name is not found or the given variable + * index is invalid. + * + * @function module:debug#setlocal + * + * @param {number} [level=1] + * The amount of call stack levels up local variables should be updated. + * + * @param {string|number} variable + * The variable index or variable name to update. + * + * @param {*} [value=null] + * The value to set the local variable to. + * + * @returns {module:debug.LocalInfo|null} + */ +static uc_value_t * +uc_setlocal(uc_vm_t *vm, size_t nargs) +{ + uc_value_t *level = uc_fn_arg(0); + uc_value_t *var = uc_fn_arg(1); + uc_value_t *val = uc_fn_arg(2); + + return uc_xlocal(vm, level, var, &val); +} + + +/** + * @typedef {Object} module:debug.UpvalInfo + * + * @property {number} index + * The index of the captured variable (upvalue). + * + * @property {string} name + * The name of the captured variable. + * + * @property {boolean} closed + * Indicates whether the captured variable is closed or not. A closed upvalue + * means that the function outlived the declaration scope of the captured + * variable. + * + * @property {*} value + * The current value of the captured variable. + */ +static uc_value_t * +uc_xupval(uc_vm_t *vm, uc_value_t *target, uc_value_t *var, uc_value_t **set) +{ + uc_value_t *vname = NULL, *rv; + uc_closure_t *closure = NULL; + uc_upvalref_t *uref = NULL; + uc_chunk_t *chunk; + size_t vn, depth; + + if (ucv_type(target) == UC_INTEGER) { + depth = ucv_uint64_get(target); + + if (errno || depth >= vm->callframes.count) + return NULL; + + depth = vm->callframes.count - depth - 1; + closure = vm->callframes.entries[depth].closure; + } + else if (ucv_type(target) == UC_CLOSURE) { + closure = (uc_closure_t *)target; + } + + if (!closure) + return NULL; + + chunk = &closure->function->chunk; + + if (ucv_type(var) == UC_INTEGER) { + vn = ucv_uint64_get(var); + var = NULL; + + if (errno || vn >= closure->function->nupvals) + return NULL; + + uref = closure->upvals[vn]; + vname = uc_chunk_debug_get_variable(chunk, 0, vn, true); + } + else if (ucv_type(var) == UC_STRING) { + for (vn = 0; vn < closure->function->nupvals; vn++) { + vname = uc_chunk_debug_get_variable(chunk, 0, vn, true); + + if (ucv_is_equal(vname, var)) { + uref = closure->upvals[vn]; + break; + } + + ucv_put(vname); + vname = NULL; + } + } + + if (!uref) + return NULL; + + rv = ucv_object_new(vm); + + ucv_object_add(rv, "index", ucv_uint64_new(vn)); + ucv_object_add(rv, "name", vname); + + if (uref->closed) { + if (set) { + ucv_put(uref->value); + uref->value = ucv_get(*set); + } + + ucv_object_add(rv, "closed", ucv_boolean_new(true)); + ucv_object_add(rv, "value", ucv_get(uref->value)); + } + else { + if (set) { + ucv_put(vm->stack.entries[uref->slot]); + vm->stack.entries[uref->slot] = ucv_get(*set); + } + + ucv_object_add(rv, "closed", ucv_boolean_new(false)); + ucv_object_add(rv, "value", ucv_get(vm->stack.entries[uref->slot])); + } + + return rv; +} + +/** + * Obtain captured variable (upvalue). + * + * The `getupval()` function retrieves information about the specified captured + * variable associated with the given function value or the invoked function at + * the given call stack depth. + * + * The call stack depth specifies the amount of levels up the function should be + * selected to query associated captured variables for. A value of `0` refers to + * this `getupval()` function call itself, `1` to the function calling + * `getupval()` and so on. + * + * The variable to query might be either specified by name or by its index with + * index numbers following the source code declaration order. + * + * Returns a dictionary holding information about the given variable. + * + * Returns `null` if the given function value is not a closure. + * + * Returns `null` if the stack depth exceeds the size of the current call stack. + * + * Returns `null` if the invocation at the given stack depth is not a closure. + * + * Returns `null` if the given variable name is not found or the given variable + * index is invalid. + * + * @function module:debug#getupval + * + * @param {function|number} target + * Either a function value referring to a closure to query upvalues for or a + * stack depth number selecting a closure that many levels up. + * + * @param {string|number} variable + * The variable index or variable name to obtain information for. + * + * @returns {module:debug.UpvalInfo|null} + */ +static uc_value_t * +uc_getupval(uc_vm_t *vm, size_t nargs) +{ + uc_value_t *target = uc_fn_arg(0); + uc_value_t *var = uc_fn_arg(1); + + return uc_xupval(vm, target, var, NULL); +} + +/** + * Set upvalue. + * + * The `setupval()` function manipulates the value of the specified captured + * variable associated with the given function value or the invoked function at + * the given call stack depth. + * + * The call stack depth specifies the amount of levels up the function should be + * selected to update associated captured variables for. A value of `0` refers + * to this `setupval()` function call itself, `1` to the function calling + * `setupval()` and so on. + * + * The variable to update might be either specified by name or by its index with + * index numbers following the source code declaration order. + * + * Returns a dictionary holding information about the updated variable. + * + * Returns `null` if the given function value is not a closure. + * + * Returns `null` if the stack depth exceeds the size of the current call stack. + * + * Returns `null` if the invocation at the given stack depth is not a closure. + * + * Returns `null` if the given variable name is not found or the given variable + * index is invalid. + * + * @function module:debug#setupval + * + * @param {function|number} target + * Either a function value referring to a closure to update upvalues for or a + * stack depth number selecting a closure that many levels up. + * + * @param {string|number} variable + * The variable index or variable name to update. + * + * @param {*} value + * The value to set the variable to. + * + * @returns {module:debug.UpvalInfo|null} + */ +static uc_value_t * +uc_setupval(uc_vm_t *vm, size_t nargs) +{ + uc_value_t *target = uc_fn_arg(0); + uc_value_t *var = uc_fn_arg(1); + uc_value_t *val = uc_fn_arg(2); + + return uc_xupval(vm, target, var, &val); +} + + +static const uc_function_list_t debug_fns[] = { + { "memdump", uc_memdump }, + { "traceback", uc_traceback }, + { "sourcepos", uc_sourcepos }, + { "getinfo", uc_getinfo }, + { "getlocal", uc_getlocal }, + { "setlocal", uc_setlocal }, + { "getupval", uc_getupval }, + { "setupval", uc_setupval }, +}; + +void uc_module_init(uc_vm_t *vm, uc_value_t *scope) +{ + uc_function_list_register(scope, debug_fns); + + debug_setup(vm); +} -- cgit v1.2.3