1
0
mirror of https://github.com/janet-lang/janet synced 2026-04-01 20:41:27 +00:00

Compare commits

...

7 Commits

Author SHA1 Message Date
Calvin Rose
19e1dc494d Mitigation against mutated repl history. 2026-03-28 18:22:25 -05:00
Calvin Rose
c67dee7329 strncpy mitigations in mainclient.c 2026-03-28 09:47:46 -05:00
Calvin Rose
16f4f40d8e Update CHANGELOG 2026-03-27 11:34:50 -05:00
Calvin Rose
29474b915d Begin work on adding kqueue functionality to filewatch.
kqueue-based filewatch is more limited than inotify, but is still
useful in many cases. Notably absent is any ability to monitor
directories automatically without wasting a lot of file descriptors.
2026-03-25 22:16:30 -05:00
Calvin Rose
ec5a78d3dc Add pointer shift option for more nanboxing on arm64.
We now try to detect this and do it by default. This is important
as arm64 becomes more common and dominant.
2026-03-23 13:57:43 -05:00
Calvin Rose
e42b3c667f Reduce maximum size of parse number to something more manageable.
Parsing large number can have non-linear runtime behavior, and is
messing with our fuzzing.
2026-03-18 17:09:12 -05:00
Calvin Rose
93436bf973 Weaken variable shadowing to a "strict" lint.
Only get compiler lints/errors in strict mode. Also distinguish between
different kinds of shadowing for slightly different warnings and
lint-levels that better align with idiomatic code.
2026-03-17 20:28:16 -05:00
20 changed files with 486 additions and 57 deletions

View File

@@ -12,7 +12,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ ubuntu-latest, macos-latest, macos-14, macos-15-intel ]
os: [ ubuntu-latest, ubuntu-24.04-arm, macos-latest, macos-14, macos-15-intel ]
steps:
- name: Checkout the repository
uses: actions/checkout@master

View File

@@ -2,9 +2,12 @@
All notable changes to this project will be documented in this file.
## Unreleased - ???
- Add filewatch support to BSD and macos.
- Add linting support for shadowed bindings.
- Add nanboxing support for Linux on ARM64 and turn on nanboxing by default on macos on ARM64 (aarch64).
- Documentation fixes
- ev/thread-chan deadlock bug fixed
- Re-add removed support for non-blocking net/connect on windows.
- Re-add removed support for non-blocking net/connect on windows with bug fixes.
## 1.41.2 - 2026-02-18
- Fix regressions in `put` for arrays and buffers.

14
examples/filewatch.janet Normal file
View File

@@ -0,0 +1,14 @@
###
### example/filewatch.janet ...files
###
### Watch for all changes in a list of files and directories. Behavior
### depends on the filewatch module, and different operating systems will
### report different events.
(def chan (ev/chan 1000))
(def fw (filewatch/new chan))
(each arg (drop 1 (dyn *args* []))
(filewatch/add fw arg :all))
(filewatch/listen fw)
(forever (let [event (ev/take chan)] (pp event)))

View File

@@ -72,6 +72,9 @@ conf.set_quoted('JANET_VERSION', meson.project_version())
# Use options
conf.set_quoted('JANET_BUILD', get_option('git_hash'))
conf.set('JANET_NO_NANBOX', not get_option('nanbox'))
if get_option('nanbox_pointer_shift') != -1 # -1 is auto-detect
conf.set('JANET_NANBOX_64_POINTER_SHIFT', get_option('nanbox_pointer_shift'))
endif
conf.set('JANET_SINGLE_THREADED', get_option('single_threaded'))
conf.set('JANET_NO_DYNAMIC_MODULES', not get_option('dynamic_modules'))
conf.set('JANET_NO_DOCSTRINGS', not get_option('docstrings'))

View File

@@ -2,6 +2,7 @@ option('git_hash', type : 'string', value : 'meson')
option('single_threaded', type : 'boolean', value : false)
option('nanbox', type : 'boolean', value : true)
option('nanbox_pointer_shift', type : 'integer', min : -1, max : 4, value : -1)
option('dynamic_modules', type : 'boolean', value : true)
option('docstrings', type : 'boolean', value : true)
option('sourcemaps', type : 'boolean', value : true)

View File

@@ -16,6 +16,7 @@
/* #define JANET_THREAD_LOCAL _Thread_local */
/* #define JANET_NO_DYNAMIC_MODULES */
/* #define JANET_NO_NANBOX */
/* #define JANET_NANBOX_64_POINTER_SHIFT 0 */
/* #define JANET_API __attribute__((visibility ("default"))) */
/* These settings should be specified before amalgamation is

View File

@@ -29,7 +29,7 @@
#endif
/* Look up table for instructions */
enum JanetInstructionType janet_instructions[JOP_INSTRUCTION_COUNT] = {
const enum JanetInstructionType janet_instructions[JOP_INSTRUCTION_COUNT] = {
JINT_0, /* JOP_NOOP, */
JINT_S, /* JOP_ERROR, */
JINT_ST, /* JOP_TYPECHECK, */

View File

@@ -94,11 +94,21 @@ void janetc_freeslot(JanetCompiler *c, JanetSlot s) {
void janetc_nameslot(JanetCompiler *c, const uint8_t *sym, JanetSlot s, uint32_t flags) {
if (!(flags & JANET_DEFFLAG_NO_SHADOWCHECK)) {
if (sym[0] != '_') {
int check = janetc_shadowcheck(c, sym);
if (check == 2) {
janetc_lintf(c, JANET_C_LINT_NORMAL, "binding %q is shadowing a local binding", janet_wrap_symbol(sym));
} else if (check) {
janetc_lintf(c, JANET_C_LINT_NORMAL, "binding %q is shadowing a global binding", janet_wrap_symbol(sym));
switch (janetc_shadowcheck(c, sym)) {
default:
break;
case JANETC_SHADOW_MACRO:
janetc_lintf(c, JANET_C_LINT_NORMAL, "binding %q is shadowing a macro", janet_wrap_symbol(sym));
break;
case JANETC_SHADOW_LOCAL_HIDES_LOCAL:
janetc_lintf(c, JANET_C_LINT_STRICT, "binding %q is shadowing a binding", janet_wrap_symbol(sym));
break;
case JANETC_SHADOW_LOCAL_HIDES_GLOBAL:
janetc_lintf(c, JANET_C_LINT_STRICT, "binding %q is shadowing a top-level binding", janet_wrap_symbol(sym));
break;
case JANETC_SHADOW_GLOBAL_HIDES_GLOBAL:
janetc_lintf(c, JANET_C_LINT_STRICT, "top-level binding %q is shadowing another top-level binding", janet_wrap_symbol(sym));
break;
}
}
}
@@ -261,20 +271,34 @@ static int lookup_missing(
/* Check if a binding is defined in an upper scope. This lets us check for
* variable shadowing. */
int janetc_shadowcheck(JanetCompiler *c, const uint8_t *sym) {
Shadowing janetc_shadowcheck(JanetCompiler *c, const uint8_t *sym) {
/* Check locals */
JanetScope *scope = c->scope;
int is_global = (scope->flags & JANET_SCOPE_TOP);
while (scope) {
int32_t len = janet_v_count(scope->syms);
for (int32_t i = len - 1; i >= 0; i--) {
SymPair *pair = scope->syms + i;
if (pair->sym == sym) return 2;
if (pair->sym == sym) {
janet_assert(!is_global, "shadowing analysis is incorrect. compiler bug");
return JANETC_SHADOW_LOCAL_HIDES_LOCAL;
}
}
scope = scope->parent;
}
/* Check globals */
JanetBinding binding = janet_resolve_ext(c->env, sym);
return binding.type != JANET_BINDING_NONE;
if (binding.type == JANET_BINDING_MACRO || binding.type == JANET_BINDING_DYNAMIC_MACRO) {
return JANETC_SHADOW_MACRO;
} else if (binding.type == JANET_BINDING_NONE) {
return JANETC_SHADOW_NONE;
} else {
if (is_global) {
return JANETC_SHADOW_GLOBAL_HIDES_GLOBAL;
} else {
return JANETC_SHADOW_LOCAL_HIDES_GLOBAL;
}
}
}
/* Allow searching for symbols. Return information about the symbol */

View File

@@ -36,6 +36,15 @@ typedef enum {
JANET_C_LINT_STRICT
} JanetCompileLintLevel;
/* Kinds of variable shadowing for linting */
typedef enum {
JANETC_SHADOW_NONE,
JANETC_SHADOW_MACRO,
JANETC_SHADOW_GLOBAL_HIDES_GLOBAL,
JANETC_SHADOW_LOCAL_HIDES_GLOBAL,
JANETC_SHADOW_LOCAL_HIDES_LOCAL
} Shadowing;
/* Tags for some functions for the prepared inliner */
#define JANET_FUN_DEBUG 1
#define JANET_FUN_ERROR 2
@@ -276,7 +285,7 @@ JanetSlot janetc_cslot(Janet x);
JanetSlot janetc_resolve(JanetCompiler *c, const uint8_t *sym);
/* Check if a symbol is already in scope for shadowing lints */
int janetc_shadowcheck(JanetCompiler *c, const uint8_t *sym);
Shadowing janetc_shadowcheck(JanetCompiler *c, const uint8_t *sym);
/* Bytecode optimization */
void janet_bytecode_movopt(JanetFuncDef *def);

View File

@@ -70,7 +70,7 @@ JanetModule janet_native(const char *name, const uint8_t **error) {
host.minor != modconf.minor ||
host.bits != modconf.bits) {
char errbuf[128];
snprintf(errbuf, sizeof(errbuf), "config mismatch - host %d.%.d.%d(%.4x) vs. module %d.%d.%d(%.4x)",
snprintf(errbuf, sizeof(errbuf), "config mismatch - host %d.%.d.%d(%.4x) vs. module %d.%d.%d(%.4x) - native needs to be recompiled!",
host.major,
host.minor,
host.patch,

View File

@@ -1962,7 +1962,7 @@ void janet_stream_level_triggered(JanetStream *stream) {
janet_register_stream_impl(stream, 0);
}
#define JANET_KQUEUE_MAX_EVENTS 64
#define JANET_KQUEUE_MAX_EVENTS 512
void janet_loop1_impl(int has_timeout, JanetTimestamp timeout) {
/* Poll for events */
@@ -2026,6 +2026,7 @@ void janet_loop1_impl(int has_timeout, JanetTimestamp timeout) {
void janet_ev_init(void) {
janet_ev_init_common();
/* TODO - replace selfpipe with EVFILT_USER (or other events) */
janet_ev_setup_selfpipe();
janet_vm.kq = kqueue();
janet_vm.timer_enabled = 0;

View File

@@ -38,6 +38,13 @@
#include <windows.h>
#endif
#if defined(JANET_APPLE) || defined(JANET_BSD)
#include <sys/event.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#endif
typedef struct {
const char *name;
uint32_t flag;
@@ -89,7 +96,7 @@ static uint32_t decode_watch_flags(const Janet *options, int32_t n) {
sizeof(JanetWatchFlagName),
keyw);
if (!result) {
janet_panicf("unknown inotify flag %v", options[i]);
janet_panicf("unknown linux flag %v", options[i]);
}
flags |= result->flag;
}
@@ -128,8 +135,11 @@ static void janet_watcher_add(JanetWatcher *watcher, const char *path, uint32_t
static void janet_watcher_remove(JanetWatcher *watcher, const char *path) {
if (watcher->stream == NULL) janet_panic("watcher closed");
Janet check = janet_table_get(watcher->watch_descriptors, janet_cstringv(path));
janet_assert(janet_checktype(check, JANET_NUMBER), "bad watch descriptor");
Janet pathv = janet_cstringv(path);
Janet check = janet_table_get(watcher->watch_descriptors, pathv);
if (!janet_checktype(check, JANET_NUMBER)) {
janet_panic("bad watch descriptor");
}
int watch_handle = janet_unwrap_integer(check);
int result;
do {
@@ -138,6 +148,10 @@ static void janet_watcher_remove(JanetWatcher *watcher, const char *path) {
if (result == -1) {
janet_panicv(janet_ev_lasterr());
}
/*
janet_table_put(watcher->watch_descriptors, pathv, janet_wrap_nil());
janet_table_put(watcher->watch_descriptors, janet_wrap_integer(watch_handle), janet_wrap_nil());
*/
}
static void watcher_callback_read(JanetFiber *fiber, JanetAsyncEvent event) {
@@ -500,6 +514,254 @@ static void janet_watcher_unlisten(JanetWatcher *watcher) {
janet_gcunroot(janet_wrap_abstract(watcher));
}
#elif defined(JANET_APPLE) || defined(JANET_BSD)
/* kqueue implementation */
/* Cribbed from ev.c */
#define EV_SETx(ev, a, b, c, d, e, f) EV_SET((ev), (a), (b), (c), (d), (e), ((__typeof__((ev)->udata))(f)))
/* Different BSDs define different NOTE_* constants for different kinds of events. Use ifdef to
determine when they are available (assuming they are defines and not enums */
static const JanetWatchFlagName watcher_flags_kqueue[] = {
{
"all", NOTE_ATTRIB | NOTE_DELETE | NOTE_EXTEND | NOTE_RENAME | NOTE_REVOKE | NOTE_WRITE | NOTE_LINK
#ifdef NOTE_CLOSE
| NOTE_CLOSE
#endif
#ifdef NOTE_CLOSE_WRITE
| NOTE_CLOSE_WRITE
#endif
#ifdef NOTE_OPEN
| NOTE_OPEN
#endif
#ifdef NOTE_READ
| NOTE_READ
#endif
#ifdef NOTE_FUNLOCK
| NOTE_FUNLOCK
#endif
#ifdef NOTE_TRUNCATE
| NOTE_TRUNCATE
#endif
},
{"attrib", NOTE_ATTRIB},
#ifdef NOTE_CLOSE
{"close", NOTE_CLOSE},
#endif
#ifdef NOTE_CLOSE_WRITE
{"close-write", NOTE_CLOSE_WRITE},
#endif
{"delete", NOTE_DELETE},
{"extend", NOTE_EXTEND},
#ifdef NOTE_FUNLOCK
{"funlock", NOTE_FUNLOCK},
#endif
{"link", NOTE_LINK},
#ifdef NOTE_OPEN
{"open", NOTE_OPEN},
#endif
#ifdef NOTE_READ
{"read", NOTE_READ},
#endif
{"rename", NOTE_RENAME},
{"revoke", NOTE_REVOKE},
#ifdef NOTE_TRUNCATE
{"truncate", NOTE_TRUNCATE},
#endif
{"write", NOTE_WRITE},
};
static uint32_t decode_watch_flags(const Janet *options, int32_t n) {
uint32_t flags = 0;
for (int32_t i = 0; i < n; i++) {
if (!(janet_checktype(options[i], JANET_KEYWORD))) {
janet_panicf("expected keyword, got %v", options[i]);
}
JanetKeyword keyw = janet_unwrap_keyword(options[i]);
const JanetWatchFlagName *result = janet_strbinsearch(watcher_flags_kqueue,
sizeof(watcher_flags_kqueue) / sizeof(JanetWatchFlagName),
sizeof(JanetWatchFlagName),
keyw);
if (!result) {
janet_panicf("unknown bsd flag %v", options[i]);
}
flags |= result->flag;
}
return flags;
}
static void janet_watcher_init(JanetWatcher *watcher, JanetChannel *channel, uint32_t default_flags) {
int kq = kqueue();
watcher->watch_descriptors = janet_table(0);
watcher->channel = channel;
watcher->default_flags = default_flags;
watcher->is_watching = 0;
watcher->stream = janet_stream(kq, JANET_STREAM_READABLE, NULL);
janet_stream_level_triggered(watcher->stream);
}
static void janet_watcher_add(JanetWatcher *watcher, const char *path, uint32_t flags) {
if (watcher->stream == NULL) janet_panic("watcher closed");
int kq = watcher->stream->handle;
struct kevent kev = {0};
/* Get file descriptor for path */
int file_fd;
do {
file_fd = open(path, O_RDONLY);
} while (file_fd == -1 && errno == EINTR);
if (file_fd == -1) {
janet_panicf("failed to open: %v", janet_ev_lasterr());
}
/* Watch for EVFILT_VNODE on the file descriptor */
EV_SETx(&kev, file_fd, EVFILT_VNODE, EV_ADD | EV_ENABLE | EV_CLEAR, flags, 0, NULL);
int status;
do {
status = kevent(kq, &kev, 1, NULL, 0, NULL);
} while (status == -1 && errno == EINTR);
if (status == -1) {
close(file_fd);
janet_panicf("failed to listen: %v", janet_ev_lasterr());
}
/* Bookkeeping */
Janet name = janet_cstringv(path);
Janet wd = janet_wrap_integer(file_fd);
janet_table_put(watcher->watch_descriptors, name, wd);
janet_table_put(watcher->watch_descriptors, wd, name);
}
static void janet_watcher_remove(JanetWatcher *watcher, const char *path) {
if (watcher->stream == NULL) janet_panic("watcher closed");
Janet pathv = janet_cstringv(path);
Janet check = janet_table_get(watcher->watch_descriptors, pathv);
if (!janet_checktype(check, JANET_NUMBER)) {
janet_panic("bad watch descriptor");
}
/* Closing the file descriptor will also remove it from the kqueue */
int wd = janet_unwrap_integer(check);
int result;
do {
result = close(wd);
} while (result != -1 && errno == EINTR);
if (result == -1) {
janet_panicv(janet_ev_lasterr());
}
janet_table_put(watcher->watch_descriptors, pathv, janet_wrap_nil());
janet_table_put(watcher->watch_descriptors, janet_wrap_integer(wd), janet_wrap_nil());
}
typedef struct {
JanetWatcher *watcher;
uint32_t cookie;
} KqueueWatcherState;
static void watcher_callback_read(JanetFiber *fiber, JanetAsyncEvent event) {
JanetStream *stream = fiber->ev_stream;
KqueueWatcherState *state = fiber->ev_state;
JanetWatcher *watcher = state->watcher;
switch (event) {
case JANET_ASYNC_EVENT_MARK:
janet_mark(janet_wrap_abstract(watcher));
break;
case JANET_ASYNC_EVENT_CLOSE:
janet_schedule(fiber, janet_wrap_nil());
janet_async_end(fiber);
break;
case JANET_ASYNC_EVENT_ERR: {
janet_schedule(fiber, janet_wrap_nil());
janet_async_end(fiber);
break;
}
case JANET_ASYNC_EVENT_HUP:
case JANET_ASYNC_EVENT_INIT:
break;
case JANET_ASYNC_EVENT_READ: {
/* Pump events from the sub kqueue */
const int num_events = 512; /* Extra will be pumped after another event loop rotation. */
struct kevent events[num_events];
int kq = stream->handle;
int status;
do {
status = kevent(kq, NULL, 0, events, num_events, NULL);
} while (status == -1 && errno == EINTR);
if (status == -1) {
janet_schedule(fiber, janet_wrap_nil());
janet_async_end(fiber);
break;
}
for (int i = 0; i < status; i++) {
state->cookie += 6700417;
struct kevent kev = events[i];
/* TODO - avoid stat call here, maybe just when adding listener? */
struct stat stat_buf = {0};
int status;
do {
status = fstat(kev.ident, &stat_buf);
} while (status == -1 && errno == EINTR);
if (status == -1) continue;
int is_dir = S_ISDIR(stat_buf.st_mode);
Janet ident = janet_wrap_integer(kev.ident);
Janet path = janet_table_get(watcher->watch_descriptors, ident);
for (unsigned int j = 1; j < (sizeof(watcher_flags_kqueue) / sizeof(watcher_flags_kqueue[0])); j++) {
uint32_t flagcheck = watcher_flags_kqueue[j].flag;
if (kev.fflags & flagcheck) {
JanetKV *event = janet_struct_begin(6);
janet_struct_put(event, janet_ckeywordv("wd"), ident);
janet_struct_put(event, janet_ckeywordv("wd-path"), path);
janet_struct_put(event, janet_ckeywordv("cookie"), janet_wrap_number((double) state->cookie));
janet_struct_put(event, janet_ckeywordv("type"), janet_ckeywordv(watcher_flags_kqueue[j].name));
if (is_dir) {
/* Pass in directly */
janet_struct_put(event, janet_ckeywordv("file-name"), janet_cstringv(""));
janet_struct_put(event, janet_ckeywordv("dir-name"), path);
} else {
/* Split path */
JanetString spath = janet_unwrap_string(path);
const uint8_t *cursor = spath + janet_string_length(spath);
const uint8_t *cursor_end = cursor;
while (cursor > spath && cursor[0] != '/') {
cursor--;
}
if (cursor == spath) {
/* No path separators */
janet_struct_put(event, janet_ckeywordv("dir-name"), janet_cstringv("."));
janet_struct_put(event, janet_ckeywordv("file-name"), janet_wrap_string(spath));
} else {
/* Found path separator */
janet_struct_put(event, janet_ckeywordv("dir-name"), janet_wrap_string(janet_string(spath, (cursor - spath))));
janet_struct_put(event, janet_ckeywordv("file-name"), janet_wrap_string(janet_string(cursor + 1, (cursor_end - cursor - 1))));
}
}
Janet eventv = janet_wrap_struct(janet_struct_end(event));
janet_channel_give(watcher->channel, eventv);
}
}
}
break;
}
default:
break;
}
}
static void janet_watcher_listen(JanetWatcher *watcher) {
if (watcher->is_watching) janet_panic("already watching");
watcher->is_watching = 1;
JanetFunction *thunk = janet_thunk_delay(janet_wrap_nil());
JanetFiber *fiber = janet_fiber(thunk, 64, 0, NULL);
KqueueWatcherState *state = janet_malloc(sizeof(KqueueWatcherState));
state->watcher = watcher;
janet_async_start_fiber(fiber, watcher->stream, JANET_ASYNC_LISTEN_READ, watcher_callback_read, state);
janet_gcroot(janet_wrap_abstract(watcher));
}
static void janet_watcher_unlisten(JanetWatcher *watcher) {
if (!watcher->is_watching) return;
watcher->is_watching = 0;
janet_stream_close(watcher->stream);
janet_gcunroot(janet_wrap_abstract(watcher));
}
#else
/* Default implementation */
@@ -582,10 +844,10 @@ JANET_CORE_FN(cfun_filewatch_make,
"* `:dir-name` -- the directory name of the file that triggered the event.\n\n"
"Events also will contain keys specific to the host OS.\n\n"
"Windows has no extra properties on events.\n\n"
"Linux has the following extra properties on events:\n\n"
"* `:wd` -- the integer key returned by `filewatch/add` for the path that triggered this.\n\n"
"Linux and the BSDs have the following extra properties on events:\n\n"
"* `:wd` -- the integer key returned by `filewatch/add` for the path that triggered this. This is a file descriptor integer on BSD and macos.\n\n"
"* `:wd-path` -- the string path for watched directory of file. For files, will be the same as `:file-name`, and for directories, will be the same as `:dir-name`.\n\n"
"* `:cookie` -- a randomized integer used to associate related events, such as :moved-from and :moved-to events.\n\n"
"* `:cookie` -- a semi-randomized integer used to associate related events, such as :moved-from and :moved-to events.\n\n"
"") {
janet_sandbox_assert(JANET_SANDBOX_FS_READ);
janet_arity(argc, 1, -1);
@@ -600,6 +862,7 @@ JANET_CORE_FN(cfun_filewatch_add,
"(filewatch/add watcher path flag & more-flags)",
"Add a path to the watcher. Available flags depend on the current OS, and are as follows:\n\n"
"Windows/MINGW (flags correspond to `FILE_NOTIFY_CHANGE_*` flags in win32 documentation):\n\n"
"FLAGS\n\n"
"* `:all` - trigger an event for all of the below triggers.\n\n"
"* `:attributes` - `FILE_NOTIFY_CHANGE_ATTRIBUTES`\n\n"
"* `:creation` - `FILE_NOTIFY_CHANGE_CREATION`\n\n"
@@ -626,6 +889,22 @@ JANET_CORE_FN(cfun_filewatch_add,
"* `:open` - `IN_OPEN`\n\n"
"* `:q-overflow` - `IN_Q_OVERFLOW`\n\n"
"* `:unmount` - `IN_UNMOUNT`\n\n\n"
"BSDs and macos (flags correspond to `NOTE_*` flags from <sys/event.h>). Not all flags are available on all systems:\n\n"
"* `:all` - `All available NOTE_* flags on the current platform`\n\n"
"* `:attrib` - `NOTE_ATTRIB`\n\n"
"* `:close-write` - `NOTE_CLOSE_WRITE`\n\n"
"* `:close` - `NOTE_CLOSE`\n\n"
"* `:delete` - `NOTE_DELETE`\n\n"
"* `:extend` - `NOTE_EXTEND`\n\n"
"* `:funlock` - `NOTE_FUNLOCK`\n\n"
"* `:link` - `NOTE_LINK`\n\n"
"* `:open` - `NOTE_OPEN`\n\n"
"* `:read` - `NOTE_READ`\n\n"
"* `:rename` - `NOTE_RENAME`\n\n"
"* `:revoke` - `NOTE_REVOKE`\n\n"
"* `:truncate` - `NOTE_TRUNCATE`\n\n"
"* `:write` - `NOTE_WRITE`\n\n\n"
"EVENT TYPES\n\n"
"On Windows, events will have the following possible types:\n\n"
"* `:unknown`\n\n"
"* `:added`\n\n"
@@ -633,7 +912,7 @@ JANET_CORE_FN(cfun_filewatch_add,
"* `:modified`\n\n"
"* `:renamed-old`\n\n"
"* `:renamed-new`\n\n"
"On Linux, events will have a `:type` corresponding to the possible flags, excluding `:all`.\n"
"On Linux and BSDs, events will have a `:type` corresponding to the possible flags, excluding `:all`.\n"
"") {
janet_arity(argc, 2, -1);
JanetWatcher *watcher = janet_getabstract(argv, 0, &janet_filewatch_at);
@@ -648,6 +927,7 @@ JANET_CORE_FN(cfun_filewatch_remove,
"Remove a path from the watcher.") {
janet_fixarity(argc, 2);
JanetWatcher *watcher = janet_getabstract(argv, 0, &janet_filewatch_at);
/* TODO - pass string in directly to avoid extra allocation */
const char *path = janet_getcstring(argv, 1);
janet_watcher_remove(watcher, path);
return argv[0];

View File

@@ -333,7 +333,7 @@ static int compare_uint64_double(uint64_t x, double y) {
}
}
static Janet cfun_it_s64_compare(int32_t argc, Janet *argv) {
static JANET_CFUNCTION_ALIGN Janet cfun_it_s64_compare(int32_t argc, Janet *argv) {
janet_fixarity(argc, 2);
if (janet_is_int(argv[0]) != JANET_INT_S64) {
janet_panic("compare method requires int/s64 as first argument");
@@ -368,7 +368,7 @@ static Janet cfun_it_s64_compare(int32_t argc, Janet *argv) {
return janet_wrap_nil();
}
static Janet cfun_it_u64_compare(int32_t argc, Janet *argv) {
static JANET_CFUNCTION_ALIGN Janet cfun_it_u64_compare(int32_t argc, Janet *argv) {
janet_fixarity(argc, 2);
if (janet_is_int(argv[0]) != JANET_INT_U64) {
janet_panic("compare method requires int/u64 as first argument");
@@ -416,7 +416,7 @@ static Janet cfun_it_u64_compare(int32_t argc, Janet *argv) {
* This will not affect the end result (property of twos complement).
*/
#define OPMETHOD(T, type, name, oper) \
static Janet cfun_it_##type##_##name(int32_t argc, Janet *argv) { \
static JANET_CFUNCTION_ALIGN Janet cfun_it_##type##_##name(int32_t argc, Janet *argv) { \
janet_arity(argc, 2, -1); \
T *box = janet_abstract(&janet_##type##_type, sizeof(T)); \
*box = janet_unwrap_##type(argv[0]); \
@@ -427,7 +427,7 @@ static Janet cfun_it_##type##_##name(int32_t argc, Janet *argv) { \
} \
#define OPMETHODINVERT(T, type, name, oper) \
static Janet cfun_it_##type##_##name##i(int32_t argc, Janet *argv) { \
static JANET_CFUNCTION_ALIGN Janet cfun_it_##type##_##name##i(int32_t argc, Janet *argv) { \
janet_fixarity(argc, 2); \
T *box = janet_abstract(&janet_##type##_type, sizeof(T)); \
*box = janet_unwrap_##type(argv[1]); \
@@ -437,7 +437,7 @@ static Janet cfun_it_##type##_##name##i(int32_t argc, Janet *argv) { \
} \
#define UNARYMETHOD(T, type, name, oper) \
static Janet cfun_it_##type##_##name(int32_t argc, Janet *argv) { \
static JANET_CFUNCTION_ALIGN Janet cfun_it_##type##_##name(int32_t argc, Janet *argv) { \
janet_fixarity(argc, 1); \
T *box = janet_abstract(&janet_##type##_type, sizeof(T)); \
*box = oper(janet_unwrap_##type(argv[0])); \
@@ -450,7 +450,7 @@ static Janet cfun_it_##type##_##name(int32_t argc, Janet *argv) { \
#define DIVZERO_mod return janet_wrap_abstract(box)
#define DIVMETHOD(T, type, name, oper) \
static Janet cfun_it_##type##_##name(int32_t argc, Janet *argv) { \
static JANET_CFUNCTION_ALIGN Janet cfun_it_##type##_##name(int32_t argc, Janet *argv) { \
janet_arity(argc, 2, -1); \
T *box = janet_abstract(&janet_##type##_type, sizeof(T)); \
*box = janet_unwrap_##type(argv[0]); \
@@ -463,7 +463,7 @@ static Janet cfun_it_##type##_##name(int32_t argc, Janet *argv) { \
} \
#define DIVMETHODINVERT(T, type, name, oper) \
static Janet cfun_it_##type##_##name##i(int32_t argc, Janet *argv) { \
static JANET_CFUNCTION_ALIGN Janet cfun_it_##type##_##name##i(int32_t argc, Janet *argv) { \
janet_fixarity(argc, 2); \
T *box = janet_abstract(&janet_##type##_type, sizeof(T)); \
*box = janet_unwrap_##type(argv[1]); \
@@ -474,7 +474,7 @@ static Janet cfun_it_##type##_##name##i(int32_t argc, Janet *argv) { \
} \
#define DIVMETHOD_SIGNED(T, type, name, oper) \
static Janet cfun_it_##type##_##name(int32_t argc, Janet *argv) { \
static JANET_CFUNCTION_ALIGN Janet cfun_it_##type##_##name(int32_t argc, Janet *argv) { \
janet_arity(argc, 2, -1); \
T *box = janet_abstract(&janet_##type##_type, sizeof(T)); \
*box = janet_unwrap_##type(argv[0]); \
@@ -488,7 +488,7 @@ static Janet cfun_it_##type##_##name(int32_t argc, Janet *argv) { \
} \
#define DIVMETHODINVERT_SIGNED(T, type, name, oper) \
static Janet cfun_it_##type##_##name##i(int32_t argc, Janet *argv) { \
static JANET_CFUNCTION_ALIGN Janet cfun_it_##type##_##name##i(int32_t argc, Janet *argv) { \
janet_fixarity(argc, 2); \
T *box = janet_abstract(&janet_##type##_type, sizeof(T)); \
*box = janet_unwrap_##type(argv[1]); \
@@ -499,7 +499,7 @@ static Janet cfun_it_##type##_##name##i(int32_t argc, Janet *argv) { \
return janet_wrap_abstract(box); \
} \
static Janet cfun_it_s64_divf(int32_t argc, Janet *argv) {
static JANET_CFUNCTION_ALIGN Janet cfun_it_s64_divf(int32_t argc, Janet *argv) {
janet_fixarity(argc, 2);
int64_t *box = janet_abstract(&janet_s64_type, sizeof(int64_t));
int64_t op1 = janet_unwrap_s64(argv[0]);
@@ -510,7 +510,7 @@ static Janet cfun_it_s64_divf(int32_t argc, Janet *argv) {
return janet_wrap_abstract(box);
}
static Janet cfun_it_s64_divfi(int32_t argc, Janet *argv) {
static JANET_CFUNCTION_ALIGN Janet cfun_it_s64_divfi(int32_t argc, Janet *argv) {
janet_fixarity(argc, 2);
int64_t *box = janet_abstract(&janet_s64_type, sizeof(int64_t));
int64_t op2 = janet_unwrap_s64(argv[0]);
@@ -521,7 +521,7 @@ static Janet cfun_it_s64_divfi(int32_t argc, Janet *argv) {
return janet_wrap_abstract(box);
}
static Janet cfun_it_s64_mod(int32_t argc, Janet *argv) {
static JANET_CFUNCTION_ALIGN Janet cfun_it_s64_mod(int32_t argc, Janet *argv) {
janet_fixarity(argc, 2);
int64_t *box = janet_abstract(&janet_s64_type, sizeof(int64_t));
int64_t op1 = janet_unwrap_s64(argv[0]);
@@ -535,7 +535,7 @@ static Janet cfun_it_s64_mod(int32_t argc, Janet *argv) {
return janet_wrap_abstract(box);
}
static Janet cfun_it_s64_modi(int32_t argc, Janet *argv) {
static JANET_CFUNCTION_ALIGN Janet cfun_it_s64_modi(int32_t argc, Janet *argv) {
janet_fixarity(argc, 2);
int64_t *box = janet_abstract(&janet_s64_type, sizeof(int64_t));
int64_t op2 = janet_unwrap_s64(argv[0]);

View File

@@ -49,6 +49,8 @@
#include <math.h>
#include <string.h>
#define JANET_NUMBER_LENGTH_RIDICULOUS 0xFFFF
/* Lookup table for getting values of characters when parsing numbers. Handles
* digits 0-9 and a-z (and A-Z). A-Z have values of 10 to 35. */
static uint8_t digit_lookup[128] = {
@@ -266,7 +268,7 @@ int janet_scan_number_base(
* the decimal point, exponent could wrap around and become positive. It's
* easier to reject ridiculously large inputs than to check for overflows.
* */
if (len > INT32_MAX / 40) goto error;
if (len > JANET_NUMBER_LENGTH_RIDICULOUS) goto error;
/* Get sign */
if (str >= end) goto error;
@@ -410,10 +412,7 @@ static int scan_uint64(
*neg = 0;
*out = 0;
uint64_t accum = 0;
/* len max is INT64_MAX in base 2 with _ between each bits */
/* '2r' + 64 bits + 63 _ + sign = 130 => 150 for some leading */
/* zeros */
if (len > 150) return 0;
if (len > JANET_NUMBER_LENGTH_RIDICULOUS) return 0;
/* Get sign */
if (str >= end) return 0;
if (*str == '-') {

View File

@@ -573,8 +573,24 @@ static char *namebuf_name(NameBuf *namebuf, const char *suffix) {
return (char *)(namebuf->buf);
}
/* Add a little bit of safety when using nanboxing on arm. Instead of inserting run-time checks everywhere, we are
* only doing it during registration which has much less cost (1 shift and mask). */
static void janet_check_pointer_align(void *p) {
(void) p;
#if defined(JANET_NANBOX_64) && JANET_NANBOX_64_POINTER_SHIFT != 0
union {
void *p;
uintptr_t u;
} un;
un.p = p;
janet_assert(!(un.u & (uintptr_t) ((1 << JANET_NANBOX_64_POINTER_SHIFT) - 1)),
"unaligned pointer wrap - cfunction pointers and abstract types must be aligned with this nanboxing configuration.");
#endif
}
void janet_cfuns(JanetTable *env, const char *regprefix, const JanetReg *cfuns) {
while (cfuns->name) {
janet_check_pointer_align(cfuns->cfun);
Janet fun = janet_wrap_cfunction(cfuns->cfun);
if (env) janet_def(env, cfuns->name, fun, cfuns->documentation);
janet_registry_put(cfuns->cfun, cfuns->name, regprefix, NULL, 0);
@@ -584,6 +600,7 @@ void janet_cfuns(JanetTable *env, const char *regprefix, const JanetReg *cfuns)
void janet_cfuns_ext(JanetTable *env, const char *regprefix, const JanetRegExt *cfuns) {
while (cfuns->name) {
janet_check_pointer_align(cfuns->cfun);
Janet fun = janet_wrap_cfunction(cfuns->cfun);
if (env) janet_def_sm(env, cfuns->name, fun, cfuns->documentation, cfuns->source_file, cfuns->source_line);
janet_registry_put(cfuns->cfun, cfuns->name, regprefix, cfuns->source_file, cfuns->source_line);
@@ -595,6 +612,7 @@ void janet_cfuns_prefix(JanetTable *env, const char *regprefix, const JanetReg *
NameBuf nb;
if (env) namebuf_init(&nb, regprefix);
while (cfuns->name) {
janet_check_pointer_align(cfuns->cfun);
Janet fun = janet_wrap_cfunction(cfuns->cfun);
if (env) janet_def(env, namebuf_name(&nb, cfuns->name), fun, cfuns->documentation);
janet_registry_put(cfuns->cfun, cfuns->name, regprefix, NULL, 0);
@@ -607,6 +625,7 @@ void janet_cfuns_ext_prefix(JanetTable *env, const char *regprefix, const JanetR
NameBuf nb;
if (env) namebuf_init(&nb, regprefix);
while (cfuns->name) {
janet_check_pointer_align(cfuns->cfun);
Janet fun = janet_wrap_cfunction(cfuns->cfun);
if (env) janet_def_sm(env, namebuf_name(&nb, cfuns->name), fun, cfuns->documentation, cfuns->source_file, cfuns->source_line);
janet_registry_put(cfuns->cfun, cfuns->name, regprefix, cfuns->source_file, cfuns->source_line);
@@ -623,6 +642,7 @@ void janet_register(const char *name, JanetCFunction cfun) {
/* Abstract type introspection */
void janet_register_abstract_type(const JanetAbstractType *at) {
janet_check_pointer_align((void *) at);
Janet sym = janet_csymbolv(at->name);
Janet check = janet_table_get(janet_vm.abstract_registry, sym);
if (!janet_checktype(check, JANET_NIL) && at != janet_unwrap_pointer(check)) {
@@ -655,6 +675,7 @@ void janet_core_def_sm(JanetTable *env, const char *name, Janet x, const void *p
void janet_core_cfuns_ext(JanetTable *env, const char *regprefix, const JanetRegExt *cfuns) {
(void) regprefix;
while (cfuns->name) {
janet_check_pointer_align(cfuns->cfun);
Janet fun = janet_wrap_cfunction(cfuns->cfun);
janet_table_put(env, janet_csymbolv(cfuns->name), fun);
janet_registry_put(cfuns->cfun, cfuns->name, regprefix, cfuns->source_file, cfuns->source_line);

View File

@@ -50,9 +50,9 @@
#ifndef JANET_EXIT
#include <stdio.h>
#define JANET_EXIT(m) do { \
fprintf(stderr, "janet internal error at line %d in file %s: %s\n",\
__LINE__,\
fprintf(stderr, "janet abort at %s:%d: %s\n",\
__FILE__,\
__LINE__,\
(m));\
abort();\
} while (0)

View File

@@ -194,12 +194,18 @@ Janet janet_wrap_number_safe(double d) {
void *janet_nanbox_to_pointer(Janet x) {
x.i64 &= JANET_NANBOX_PAYLOADBITS;
x.u64 <<= JANET_NANBOX_64_POINTER_SHIFT; /* Alignment, usually 0 */
return x.pointer;
}
Janet janet_nanbox_from_pointer(void *p, uint64_t tagmask) {
Janet ret;
ret.pointer = p;
/* Should be noop when pointer shift is 0 */
/*
janet_assert(!(ret.u64 & (uint64_t) ((1 << JANET_NANBOX_64_POINTER_SHIFT) - 1)), "unaligned pointer wrap");
*/
ret.u64 >>= JANET_NANBOX_64_POINTER_SHIFT; /* Alignment, usually 0 */
ret.u64 |= tagmask;
return ret;
}
@@ -207,6 +213,11 @@ Janet janet_nanbox_from_pointer(void *p, uint64_t tagmask) {
Janet janet_nanbox_from_cpointer(const void *p, uint64_t tagmask) {
Janet ret;
ret.pointer = (void *)p;
/* Should be noop when pointer shift is 0 */
/*
janet_assert(!(ret.u64 & (uint64_t) ((1 << JANET_NANBOX_64_POINTER_SHIFT) - 1)), "unaligned pointer wrap");
*/
ret.u64 >>= JANET_NANBOX_64_POINTER_SHIFT; /* Alignment, usually 0 */
ret.u64 |= tagmask;
return ret;
}

View File

@@ -307,25 +307,38 @@ extern "C" {
* architectures (Nanboxing only tested on x86 and x64), comment out
* the JANET_NANBOX define.*/
#if defined(_M_ARM64) || defined(_M_ARM) || defined(__aarch64__)
#define JANET_NO_NANBOX
#endif
#ifndef JANET_NO_NANBOX
#ifdef JANET_32
#define JANET_NANBOX_32
#elif defined(__x86_64__) || defined(_WIN64) || defined(__riscv)
#elif defined(__x86_64__) || defined(_WIN64) || defined(__riscv) || defined(__aarch64__) || defined(_M_ARM64)
/* We will only enable nanboxing by default on 64 bit systems
* for x64 and risc-v. This is mainly because the approach is tied to the
* for x64, risc-v, and arm64. This is mainly because the approach is tied to the
* implicit 47 bit address space. Many arches allow/require this, but not all,
* and it requires cooperation from the OS. ARM should also work in many configurations. */
* and it requires cooperation from the OS. ARM should also work in many configurations by taking advantage
* of pointer alignment to allow for 48 or 49 bits of address space. */
#define JANET_NANBOX_64
/* Allow 64-bit nanboxing to assume aligned pointers to get back some extra bits for representation.
* This is needed to use nanboxing on systems with larger than 47-bit address spaces, such as many
* aarch64 systems. */
#ifndef JANET_NANBOX_64_POINTER_SHIFT
#if (defined(_M_ARM64) || defined(__aarch64__)) && !defined(JANET_APPLE)
/* All pointers, including function pointers, should be 4-byte aligned on aarch64 by default.
* The exception is aarch64 macos, as it uses the same 47-bit userland address-space as on amd64. */
#define JANET_NANBOX_64_POINTER_SHIFT 2
#endif
#endif
#endif
#endif
/* Allow for custom pointer alignment as well */
#if defined(JANET_NANBOX_64) && !defined(JANET_NANBOX_64_POINTER_SHIFT)
#define JANET_NANBOX_64_POINTER_SHIFT 0
#endif
/* Runtime config constants */
#ifdef JANET_NO_NANBOX
#define JANET_NANBOX_BIT 0
#define JANET_NANBOX_BIT 0x0
#else
#define JANET_NANBOX_BIT 0x1
#endif
@@ -336,9 +349,16 @@ extern "C" {
#define JANET_SINGLE_THREADED_BIT 0
#endif
#ifdef JANET_NANBOX_64_POINTER_SHIFT
#define JANET_NANBOX_POINTER_SHIFT_BITS (JANET_NANBOX_64_POINTER_SHIFT ? (0x4 << JANET_NANBOX_64_POINTER_SHIFT) : 0)
#else
#define JANET_NANBOX_POINTER_SHIFT_BITS 0
#endif
#define JANET_CURRENT_CONFIG_BITS \
(JANET_SINGLE_THREADED_BIT | \
JANET_NANBOX_BIT)
JANET_NANBOX_BIT | \
JANET_NANBOX_POINTER_SHIFT_BITS)
/* Represents the settings used to compile Janet, as well as the version */
typedef struct {
@@ -1415,7 +1435,7 @@ enum JanetOpCode {
};
/* Info about all instructions */
extern enum JanetInstructionType janet_instructions[JOP_INSTRUCTION_COUNT];
extern const enum JanetInstructionType janet_instructions[JOP_INSTRUCTION_COUNT];
/***** END SECTION OPCODES *****/
@@ -2063,8 +2083,14 @@ JANET_API Janet janet_resolve_core(const char *name);
*
* */
#if defined(JANET_NANBOX_64) && (JANET_NANBOX_64_POINTER_SHIFT != 0) && !defined(JANET_MSVC)
#define JANET_CFUNCTION_ALIGN __attribute__((aligned(1 << JANET_NANBOX_64_POINTER_SHIFT)))
#else
#define JANET_CFUNCTION_ALIGN
#endif
/* Shorthand for janet C function declarations */
#define JANET_CFUN(name) Janet name (int32_t argc, Janet *argv)
#define JANET_CFUN(name) JANET_CFUNCTION_ALIGN Janet name (int32_t argc, Janet *argv)
/* Declare a C function with documentation and source mapping */
#define JANET_REG_END {NULL, NULL, NULL, NULL, 0}
@@ -2080,7 +2106,7 @@ JANET_API Janet janet_resolve_core(const char *name);
#define JANET_REG_S(JNAME, CNAME) {JNAME, CNAME, NULL, __FILE__, CNAME##_sourceline_}
#define JANET_FN_S(CNAME, USAGE, DOCSTRING) \
static const int32_t CNAME##_sourceline_ = __LINE__; \
Janet CNAME (int32_t argc, Janet *argv)
Janet JANET_CFUNCTION_ALIGN CNAME (int32_t argc, Janet *argv)
#define JANET_DEF_S(ENV, JNAME, VAL, DOC) \
janet_def_sm(ENV, JNAME, VAL, NULL, __FILE__, __LINE__)
@@ -2088,7 +2114,7 @@ JANET_API Janet janet_resolve_core(const char *name);
#define JANET_REG_D(JNAME, CNAME) {JNAME, CNAME, CNAME##_docstring_, NULL, 0}
#define JANET_FN_D(CNAME, USAGE, DOCSTRING) \
static const char CNAME##_docstring_[] = USAGE "\n\n" DOCSTRING; \
Janet CNAME (int32_t argc, Janet *argv)
Janet JANET_CFUNCTION_ALIGN CNAME (int32_t argc, Janet *argv)
#define JANET_DEF_D(ENV, JNAME, VAL, DOC) \
janet_def(ENV, JNAME, VAL, DOC)
@@ -2097,7 +2123,7 @@ JANET_API Janet janet_resolve_core(const char *name);
#define JANET_FN_SD(CNAME, USAGE, DOCSTRING) \
static const int32_t CNAME##_sourceline_ = __LINE__; \
static const char CNAME##_docstring_[] = USAGE "\n\n" DOCSTRING; \
Janet CNAME (int32_t argc, Janet *argv)
Janet JANET_CFUNCTION_ALIGN CNAME (int32_t argc, Janet *argv)
#define JANET_DEF_SD(ENV, JNAME, VAL, DOC) \
janet_def_sm(ENV, JNAME, VAL, DOC, __FILE__, __LINE__)

View File

@@ -519,8 +519,13 @@ static void historymove(int delta) {
} else if (gbl_historyi >= gbl_history_count) {
gbl_historyi = gbl_history_count - 1;
}
gbl_len = (int) strlen(gbl_history[gbl_historyi]);
/* If history element is longer the JANET_LINE_MAX - 1, truncate */
if (gbl_len > JANET_LINE_MAX - 1) {
gbl_len = JANET_LINE_MAX - 1;
}
gbl_pos = gbl_len;
strncpy(gbl_buf, gbl_history[gbl_historyi], JANET_LINE_MAX - 1);
gbl_pos = gbl_len = (int) strlen(gbl_buf);
gbl_buf[gbl_len] = '\0';
refresh();
@@ -1232,7 +1237,7 @@ int main(int argc, char **argv) {
#endif
#if defined(JANET_PRF)
uint8_t hash_key[JANET_HASH_KEY_SIZE + 1];
uint8_t hash_key[JANET_HASH_KEY_SIZE + 1] = {0};
#ifdef JANET_REDUCED_OS
char *envvar = NULL;
#else
@@ -1240,6 +1245,7 @@ int main(int argc, char **argv) {
#endif
if (NULL != envvar) {
strncpy((char *) hash_key, envvar, sizeof(hash_key) - 1);
hash_key[JANET_HASH_KEY_SIZE] = '\0'; /* in case copy didn't get null byte */
} else if (janet_cryptorand(hash_key, JANET_HASH_KEY_SIZE) != 0) {
fputs("unable to initialize janet PRF hash function.\n", stderr);
return 1;

View File

@@ -26,6 +26,8 @@
(def chan (ev/chan 1000))
(var is-win (or (= :mingw (os/which)) (= :windows (os/which))))
(var is-linux (= :linux (os/which)))
(def bsds [:freebsd :macos :openbsd :bsd :dragonfly :netbsd])
(var is-kqueue (index-of (os/which) bsds))
# If not supported, exit early
(def [supported msg] (protect (filewatch/new chan)))
@@ -97,6 +99,10 @@
(filewatch/add fw (string td3 "/file3.txt") :close-write :create :delete)
(filewatch/add fw td1 :close-write :create :delete)
(filewatch/add fw td2 :close-write :create :delete :ignored))
(when is-kqueue
(filewatch/add fw (string td3 "/file3.txt") :all)
(filewatch/add fw td1 :all)
(filewatch/add fw td2 :all))
(assert-no-error "filewatch/listen no error" (filewatch/listen fw))
#
@@ -196,6 +202,30 @@
(expect-empty)
(gccollect))
#
# Macos and BSD file writing
#
# TODO - kqueue capabilities here are a bit more limited than inotify and windows by default.
# This could be ammended with some heavier-weight functionality in userspace, though.
(when is-kqueue
(spit-file td1 "file1.txt")
(expect :wd-path td1 :type :write)
(expect-empty)
(gccollect)
(spit-file td1 "file1.txt")
# Currently, only operations that modify the parent vnode do anything
(expect-empty)
(gccollect)
# Check that we don't get anymore events from test directory 2
(spit-file td2 "file2.txt")
(expect :wd-path td2 :type :write)
(expect-empty)
# Remove a file, then wait for remove event
(rmrf (string td1 "/file1.txt"))
(expect :type :write) # a "write" to the vnode
(expect-empty))
(assert-no-error "filewatch/unlisten no error" (filewatch/unlisten fw))
(assert-no-error "cleanup 1" (rmrf td1))
(assert-no-error "cleanup 2" (rmrf td2))