3 Commits

Author SHA1 Message Date
John Alanbrook
a74231563a further loading 2026-02-08 22:26:13 -06:00
John Alanbrook
6799d90a7d engine is at least running 2026-02-08 11:09:01 -06:00
John Alanbrook
1fee8f9f8b condense jsruntime and jscontext 2026-02-08 10:10:42 -06:00
28 changed files with 1569 additions and 1682 deletions

View File

@@ -382,13 +382,13 @@ static const JSCFunctionListEntry js_reader_funcs[] = {
JSValue js_miniz_use(JSContext *js) JSValue js_miniz_use(JSContext *js)
{ {
JS_NewClassID(&js_reader_class_id); JS_NewClassID(&js_reader_class_id);
JS_NewClass(JS_GetRuntime(js), js_reader_class_id, &js_reader_class); JS_NewClass(js, js_reader_class_id, &js_reader_class);
JSValue reader_proto = JS_NewObject(js); JSValue reader_proto = JS_NewObject(js);
JS_SetPropertyFunctionList(js, reader_proto, js_reader_funcs, sizeof(js_reader_funcs) / sizeof(JSCFunctionListEntry)); JS_SetPropertyFunctionList(js, reader_proto, js_reader_funcs, sizeof(js_reader_funcs) / sizeof(JSCFunctionListEntry));
JS_SetClassProto(js, js_reader_class_id, reader_proto); JS_SetClassProto(js, js_reader_class_id, reader_proto);
JS_NewClassID(&js_writer_class_id); JS_NewClassID(&js_writer_class_id);
JS_NewClass(JS_GetRuntime(js), js_writer_class_id, &js_writer_class); JS_NewClass(js, js_writer_class_id, &js_writer_class);
JSValue writer_proto = JS_NewObject(js); JSValue writer_proto = JS_NewObject(js);
JS_SetPropertyFunctionList(js, writer_proto, js_writer_funcs, sizeof(js_writer_funcs) / sizeof(JSCFunctionListEntry)); JS_SetPropertyFunctionList(js, writer_proto, js_writer_funcs, sizeof(js_writer_funcs) / sizeof(JSCFunctionListEntry));
JS_SetClassProto(js, js_writer_class_id, writer_proto); JS_SetClassProto(js, js_writer_class_id, writer_proto);

View File

@@ -49,9 +49,13 @@ function ends_with(str, suffix) {
return search(str, suffix, -length(suffix)) != null return search(str, suffix, -length(suffix)) != null
} }
print("engine: loading js")
var js = use_embed('js') var js = use_embed('js')
print("engine: loading fd")
var fd = use_embed('fd') var fd = use_embed('fd')
print("engine: loaded fd")
print("engine: getting home")
// Get the shop path from HOME environment // Get the shop path from HOME environment
var home = os.getenv('HOME') || os.getenv('USERPROFILE') var home = os.getenv('HOME') || os.getenv('USERPROFILE')
if (!home) { if (!home) {
@@ -76,34 +80,49 @@ function use_core(path) {
if (use_cache[cache_key]) if (use_cache[cache_key])
return use_cache[cache_key]; return use_cache[cache_key];
print(`engine: use_core('${path}') - embed`)
var sym = use_embed(replace(path, '/', '_')) var sym = use_embed(replace(path, '/', '_'))
// Core scripts are in packages/core/ // Core scripts are in packages/core/
var file_path = core_path + '/' + path + MOD_EXT var file_path = core_path + '/' + path + MOD_EXT
if (fd.is_file(file_path)) { if (fd.is_file(file_path)) {
print(`engine: use_core('${path}') - loading script`)
var script_blob = fd.slurp(file_path) var script_blob = fd.slurp(file_path)
var script = text(script_blob) var script = text(script_blob)
var mod = `(function setup_module(use){${script}})` var mod = `(function setup_module(use){${script}})`
var fn = mach_eval('core:' + path, mod) var fn = mach_eval('core:' + path, mod)
if (fn == null) {
print(`engine: use_core('${path}') - mach_eval returned null!`)
return null
}
print(`engine: use_core('${path}') - calling (fn type: ${typeof(fn)})`)
var result = call(fn,sym, [use_core]) var result = call(fn,sym, [use_core])
print(`engine: use_core('${path}') - done`)
use_cache[cache_key] = result; use_cache[cache_key] = result;
return result; return result;
} }
print(`engine: use_core('${path}') - using embed directly`)
use_cache[cache_key] = sym; use_cache[cache_key] = sym;
return sym; return sym;
} }
print("engine: loading blob")
var blob = use_core('blob') var blob = use_core('blob')
print("engine: loaded blob")
function actor() { function actor() {
} }
print("engine: loading actor")
var actor_mod = use_core('actor') var actor_mod = use_core('actor')
print("engine: loading wota")
var wota = use_core('wota') var wota = use_core('wota')
print("engine: loading nota")
var nota = use_core('nota') var nota = use_core('nota')
print("engine: loaded nota")
function is_actor(value) { function is_actor(value) {
return is_object(value) && value[ACTORDATA] return is_object(value) && value[ACTORDATA]
@@ -191,10 +210,15 @@ function actor_die(err)
print("engine: setting up actor_die")
actor_mod.on_exception(actor_die) actor_mod.on_exception(actor_die)
print("engine: setting cell args")
print(`engine: init is ${init}`)
_cell.args = init != null ? init : {} _cell.args = init != null ? init : {}
print("engine: args set")
_cell.id = "newguy" _cell.id = "newguy"
print("engine: id set")
function create_actor(desc = {id:guid()}) { function create_actor(desc = {id:guid()}) {
var actor = {} var actor = {}
@@ -202,19 +226,25 @@ function create_actor(desc = {id:guid()}) {
return actor return actor
} }
print("engine: creating $_")
var $_ = {} var $_ = {}
print("engine: creating self actor")
$_.self = create_actor() $_.self = create_actor()
print("engine: setting os props")
os.use_cache = use_cache os.use_cache = use_cache
os.global_shop_path = shop_path os.global_shop_path = shop_path
os.$_ = $_ os.$_ = $_
print("engine: os props set")
print("engine: loading shop")
var shop = use_core('internal/shop') var shop = use_core('internal/shop')
print("engine: loaded shop, loading json")
var json = use_core('json') var json = use_core('json')
var time = use_core('time') var time = use_core('time')
print("engine: loading pronto")
var pronto = use_core('pronto') var pronto = use_core('pronto')
print("engine: loaded pronto")
var fallback = pronto.fallback var fallback = pronto.fallback
var parallel = pronto.parallel var parallel = pronto.parallel
var race = pronto.race var race = pronto.race

View File

@@ -75,7 +75,11 @@ static const JSCFunctionListEntry js_kim_funcs[] = {
JSValue js_kim_use(JSContext *js) JSValue js_kim_use(JSContext *js)
{ {
JSValue mod = JS_NewObject(js); JSGCRef mod_ref;
JS_SetPropertyFunctionList(js, mod, js_kim_funcs, countof(js_kim_funcs)); JS_PushGCRef(js, &mod_ref);
return mod; mod_ref.val = JS_NewObject(js);
JS_SetPropertyFunctionList(js, mod_ref.val, js_kim_funcs, countof(js_kim_funcs));
JSValue result = mod_ref.val;
JS_PopGCRef(js, &mod_ref);
return result;
} }

View File

@@ -577,9 +577,13 @@ static const JSCFunctionListEntry js_os_funcs[] = {
JSValue js_os_use(JSContext *js) { JSValue js_os_use(JSContext *js) {
JS_NewClassID(&js_dylib_class_id); JS_NewClassID(&js_dylib_class_id);
JS_NewClass(JS_GetRuntime(js), js_dylib_class_id, &js_dylib_class); JS_NewClass(js, js_dylib_class_id, &js_dylib_class);
JSValue mod = JS_NewObject(js); JSGCRef mod_ref;
JS_SetPropertyFunctionList(js,mod,js_os_funcs,countof(js_os_funcs)); JS_PushGCRef(js, &mod_ref);
return mod; mod_ref.val = JS_NewObject(js);
JS_SetPropertyFunctionList(js, mod_ref.val, js_os_funcs, countof(js_os_funcs));
JSValue result = mod_ref.val;
JS_PopGCRef(js, &mod_ref);
return result;
} }

View File

@@ -172,7 +172,11 @@ static const JSCFunctionListEntry js_os_funcs[] = {
}; };
JSValue js_os_use(JSContext *js) { JSValue js_os_use(JSContext *js) {
JSValue mod = JS_NewObject(js); JSGCRef mod_ref;
JS_SetPropertyFunctionList(js, mod, js_os_funcs, countof(js_os_funcs)); JS_PushGCRef(js, &mod_ref);
return mod; mod_ref.val = JS_NewObject(js);
JS_SetPropertyFunctionList(js, mod_ref.val, js_os_funcs, countof(js_os_funcs));
JSValue result = mod_ref.val;
JS_PopGCRef(js, &mod_ref);
return result;
} }

View File

@@ -172,7 +172,11 @@ static const JSCFunctionListEntry js_os_funcs[] = {
}; };
JSValue js_os_use(JSContext *js) { JSValue js_os_use(JSContext *js) {
JSValue mod = JS_NewObject(js); JSGCRef mod_ref;
JS_SetPropertyFunctionList(js, mod, js_os_funcs, countof(js_os_funcs)); JS_PushGCRef(js, &mod_ref);
return mod; mod_ref.val = JS_NewObject(js);
JS_SetPropertyFunctionList(js, mod_ref.val, js_os_funcs, countof(js_os_funcs));
JSValue result = mod_ref.val;
JS_PopGCRef(js, &mod_ref);
return result;
} }

View File

@@ -1,5 +1,6 @@
print("shop: starting")
var toml = use('toml') var toml = use('toml')
print("shop: loaded toml")
var json = use('json') var json = use('json')
var fd = use('fd') var fd = use('fd')
var http = use('http') var http = use('http')
@@ -142,8 +143,10 @@ function package_in_shop(package) {
function abs_path_to_package(package_dir) function abs_path_to_package(package_dir)
{ {
if (!fd.is_file(package_dir + '/cell.toml')) if (!fd.is_file(package_dir + '/cell.toml')) {
throw Error('Not a valid package directory (no cell.toml): ' + package_dir) log.error('Not a valid package directory (no cell.toml): ' + package_dir)
disrupt
}
var packages_prefix = get_packages_dir() + '/' var packages_prefix = get_packages_dir() + '/'
var core_dir = packages_prefix + core_package var core_dir = packages_prefix + core_package
@@ -175,14 +178,15 @@ function abs_path_to_package(package_dir)
return package_dir return package_dir
// For local directories (e.g., linked targets), read the package name from cell.toml // For local directories (e.g., linked targets), read the package name from cell.toml
try { var toml_pkg = null
var read_toml = function() {
var content = text(fd.slurp(package_dir + '/cell.toml')) var content = text(fd.slurp(package_dir + '/cell.toml'))
var cfg = toml.decode(content) var cfg = toml.decode(content)
if (cfg.package) if (cfg.package)
return cfg.package toml_pkg = cfg.package
} catch (e) { } disruption {}
// Fall through read_toml()
} if (toml_pkg) return toml_pkg
return null return null
} }
@@ -299,12 +303,14 @@ Shop.resolve_package_info = function(pkg) {
// Verify if a package name is valid and return status // Verify if a package name is valid and return status
Shop.verify_package_name = function(pkg) { Shop.verify_package_name = function(pkg) {
if (!pkg) throw Error("Empty package name") if (!pkg) { log.error("Empty package name"); disrupt }
if (pkg == 'local') throw Error("local is not a valid package name") if (pkg == 'local') { log.error("local is not a valid package name"); disrupt }
if (pkg == 'core') throw Error("core is not a valid package name") if (pkg == 'core') { log.error("core is not a valid package name"); disrupt }
if (search(pkg, '://') != null) if (search(pkg, '://') != null) {
throw Error(`Invalid package name: ${pkg}; did you mean ${array(pkg, '://')[1]}?`) log.error(`Invalid package name: ${pkg}; did you mean ${array(pkg, '://')[1]}?`)
disrupt
}
} }
// Convert module package to download URL // Convert module package to download URL
@@ -441,7 +447,7 @@ ${script}
// Resolve module function, hashing it in the process // Resolve module function, hashing it in the process
// path is the exact path to the script file // path is the exact path to the script file
function resolve_mod_fn(path, pkg) { function resolve_mod_fn(path, pkg) {
if (!fd.is_file(path)) throw Error(`path ${path} is not a file`) if (!fd.is_file(path)) { log.error(`path ${path} is not a file`); disrupt }
var file_info = Shop.file_info(path) var file_info = Shop.file_info(path)
var file_pkg = file_info.package var file_pkg = file_info.package
@@ -576,32 +582,38 @@ Shop.open_package_dylib = function(pkg) {
var toml_path = pkg_dir + '/cell.toml' var toml_path = pkg_dir + '/cell.toml'
if (fd.is_file(toml_path)) { if (fd.is_file(toml_path)) {
try { var read_toml_disrupted = false
var do_read_toml = function() {
var content = text(fd.slurp(toml_path)) var content = text(fd.slurp(toml_path))
var cfg = toml.decode(content) var cfg = toml.decode(content)
if (cfg.dependencies) { if (cfg.dependencies) {
arrfor(array(cfg.dependencies), function(alias, i) { arrfor(array(cfg.dependencies), function(alias, i) {
var dep_pkg = cfg.dependencies[alias] var dep_pkg = cfg.dependencies[alias]
try { var open_dep = function() {
Shop.open_package_dylib(dep_pkg) Shop.open_package_dylib(dep_pkg)
} catch (dep_e) { } disruption {}
// Dependency dylib load failed, continue with others open_dep()
}
}) })
} }
} catch (e) { } disruption {
// Error reading toml, continue read_toml_disrupted = true
} }
do_read_toml()
} }
var dl_path = get_lib_path(pkg) var dl_path = get_lib_path(pkg)
if (fd.is_file(dl_path)) { if (fd.is_file(dl_path)) {
if (!open_dls[dl_path]) { if (!open_dls[dl_path]) {
try { var open_disrupted = false
var do_open = function() {
open_dls[dl_path] = os.dylib_open(dl_path) open_dls[dl_path] = os.dylib_open(dl_path)
} catch (e) { } disruption {
open_disrupted = true
}
do_open()
if (open_disrupted) {
dylib_visited[pkg] = false dylib_visited[pkg] = false
throw e disrupt
} }
} }
} }
@@ -836,14 +848,14 @@ function execute_module(info)
// C only // C only
used = call_c_module(c_resolve) used = call_c_module(c_resolve)
} else { } else {
throw Error(`Module ${info.path} could not be found`) log.error(`Module ${info.path} could not be found`); disrupt
} }
// if (is_function(used)) // if (is_function(used))
// throw Error('C module loader returned a function; did you forget to call it?') // throw Error('C module loader returned a function; did you forget to call it?')
if (!used) if (!used)
throw Error(`Module ${info} returned null`) log.error(`Module ${info} returned null`); disrupt
// stone(used) // stone(used)
return used return used
@@ -852,16 +864,20 @@ function execute_module(info)
function get_module(path, package_context) { function get_module(path, package_context) {
var info = resolve_module_info(path, package_context) var info = resolve_module_info(path, package_context)
if (!info) if (!info) {
throw Error(`Module ${path} could not be found in ${package_context}`) log.error(`Module ${path} could not be found in ${package_context}`)
disrupt
}
return execute_module(info) return execute_module(info)
} }
Shop.use = function use(path, package_context) { Shop.use = function use(path, package_context) {
var info = resolve_module_info(path, package_context) var info = resolve_module_info(path, package_context)
if (!info) if (!info) {
throw Error(`Module ${path} could not be found in ${package_context}`) log.error(`Module ${path} could not be found in ${package_context}`)
disrupt
}
if (use_cache[info.cache_key]) if (use_cache[info.cache_key])
return use_cache[info.cache_key] return use_cache[info.cache_key]
@@ -889,13 +905,20 @@ function fetch_remote_hash(pkg) {
if (!api_url) return null if (!api_url) return null
try { var result = null
var fetch_disrupted = false
var do_fetch = function() {
var resp = http.fetch(api_url) var resp = http.fetch(api_url)
return Shop.extract_commit_hash(pkg, text(resp)) result = Shop.extract_commit_hash(pkg, text(resp))
} catch (e) { } disruption {
fetch_disrupted = true
}
do_fetch()
if (fetch_disrupted) {
log.console("Warning: Could not check for updates for " + pkg) log.console("Warning: Could not check for updates for " + pkg)
return null return null
} }
return result
} }
// Download a zip for a package at a specific commit and cache it // Download a zip for a package at a specific commit and cache it
@@ -909,14 +932,20 @@ function download_zip(pkg, commit_hash) {
return null return null
} }
try { var zip_blob = null
var zip_blob = http.fetch(download_url) var dl_disrupted = false
var do_download = function() {
zip_blob = http.fetch(download_url)
fd.slurpwrite(cache_path, zip_blob) fd.slurpwrite(cache_path, zip_blob)
return zip_blob } disruption {
} catch (e) { dl_disrupted = true
log.error("Download failed for " + pkg + ": " + e) }
do_download()
if (dl_disrupted) {
log.error("Download failed for " + pkg)
return null return null
} }
return zip_blob
} }
// Get zip from cache, returns null if not cached // Get zip from cache, returns null if not cached
@@ -1027,8 +1056,7 @@ Shop.extract = function(pkg) {
var zip_blob = get_package_zip(pkg) var zip_blob = get_package_zip(pkg)
if (!zip_blob) if (!zip_blob) { log.error("No zip blob available for " + pkg); disrupt }
throw Error("No zip blob available for " + pkg)
// Extract zip for remote package // Extract zip for remote package
install_zip(zip_blob, target_dir) install_zip(zip_blob, target_dir)
@@ -1113,7 +1141,7 @@ Shop.update = function(pkg) {
function install_zip(zip_blob, target_dir) { function install_zip(zip_blob, target_dir) {
var zip = miniz.read(zip_blob) var zip = miniz.read(zip_blob)
if (!zip) throw Error("Failed to read zip archive") if (!zip) { log.error("Failed to read zip archive"); disrupt }
if (fd.is_link(target_dir)) fd.unlink(target_dir) if (fd.is_link(target_dir)) fd.unlink(target_dir)
if (fd.is_dir(target_dir)) fd.rmdir(target_dir, 1) if (fd.is_dir(target_dir)) fd.rmdir(target_dir, 1)
@@ -1165,14 +1193,14 @@ Shop.get = function(pkg) {
if (!lock[pkg]) { if (!lock[pkg]) {
var info = Shop.resolve_package_info(pkg) var info = Shop.resolve_package_info(pkg)
if (!info) { if (!info) {
throw Error("Invalid package: " + pkg) log.error("Invalid package: " + pkg); disrupt
} }
var commit = null var commit = null
if (info != 'local') { if (info != 'local') {
commit = fetch_remote_hash(pkg) commit = fetch_remote_hash(pkg)
if (!commit) { if (!commit) {
throw Error("Could not resolve commit for " + pkg) log.error("Could not resolve commit for " + pkg); disrupt
} }
} }

View File

@@ -11,12 +11,13 @@ function is_valid_package(dir) {
// Get current package name from cell.toml or null // Get current package name from cell.toml or null
function get_current_package_name() { function get_current_package_name() {
if (!is_valid_package('.')) return null if (!is_valid_package('.')) return null
try { var pkg_name = 'local'
var do_load = function() {
var config = pkg.load_config(null) var config = pkg.load_config(null)
return config.package || 'local' if (config.package) pkg_name = config.package
} catch (e) { } disruption {}
return 'local' do_load()
} return pkg_name
} }
// Get the directory for a package // Get the directory for a package

81
link.cm
View File

@@ -65,13 +65,18 @@ Link.load = function() {
link_cache = {} link_cache = {}
return link_cache return link_cache
} }
try { var load_disrupted = false
var do_load = function() {
var content = text(fd.slurp(path)) var content = text(fd.slurp(path))
var cfg = toml.decode(content) var cfg = toml.decode(content)
link_cache = cfg.links || {} link_cache = cfg.links || {}
} catch (e) { } disruption {
log.console("Warning: Failed to load link.toml: " + e) load_disrupted = true
}
do_load()
if (load_disrupted) {
log.console("Warning: Failed to load link.toml")
link_cache = {} link_cache = {}
} }
return link_cache return link_cache
@@ -90,14 +95,16 @@ Link.add = function(canonical, target, shop) {
// Validate canonical package exists in shop // Validate canonical package exists in shop
var lock = shop.load_lock() var lock = shop.load_lock()
if (!lock[canonical]) { if (!lock[canonical]) {
throw Error('Package ' + canonical + ' is not installed. Install it first with: cell get ' + canonical) log.error('Package ' + canonical + ' is not installed. Install it first with: cell get ' + canonical)
disrupt
} }
// Validate target is a valid package // Validate target is a valid package
if (starts_with(target, '/')) { if (starts_with(target, '/')) {
// Local path - must have cell.toml // Local path - must have cell.toml
if (!fd.is_file(target + '/cell.toml')) { if (!fd.is_file(target + '/cell.toml')) {
throw Error('Target ' + target + ' is not a valid package (no cell.toml)') log.error('Target ' + target + ' is not a valid package (no cell.toml)')
disrupt
} }
} else { } else {
// Remote package target - ensure it's installed // Remote package target - ensure it's installed
@@ -116,7 +123,8 @@ Link.add = function(canonical, target, shop) {
var target_path = starts_with(target, '/') ? target : get_package_abs_dir(target) var target_path = starts_with(target, '/') ? target : get_package_abs_dir(target)
var toml_path = target_path + '/cell.toml' var toml_path = target_path + '/cell.toml'
if (fd.is_file(toml_path)) { if (fd.is_file(toml_path)) {
try { var read_deps_disrupted = false
var do_read_deps = function() {
var content = text(fd.slurp(toml_path)) var content = text(fd.slurp(toml_path))
var cfg = toml.decode(content) var cfg = toml.decode(content)
if (cfg.dependencies) { if (cfg.dependencies) {
@@ -128,16 +136,24 @@ Link.add = function(canonical, target, shop) {
return return
} }
// Install the dependency if not already in shop // Install the dependency if not already in shop
try { var install_disrupted = false
var do_install = function() {
shop.get(dep_locator) shop.get(dep_locator)
shop.extract(dep_locator) shop.extract(dep_locator)
} catch (e) { } disruption {
log.console(` Warning: Could not install dependency ${dep_locator}: ${e.message}`) install_disrupted = true
log.error(e) }
do_install()
if (install_disrupted) {
log.console(` Warning: Could not install dependency ${dep_locator}`)
} }
}) })
} }
} catch (e) { } disruption {
read_deps_disrupted = true
}
do_read_deps()
if (read_deps_disrupted) {
log.console(` Warning: Could not read dependencies from ${toml_path}`) log.console(` Warning: Could not read dependencies from ${toml_path}`)
} }
} }
@@ -149,14 +165,14 @@ Link.add = function(canonical, target, shop) {
Link.remove = function(canonical) { Link.remove = function(canonical) {
var links = Link.load() var links = Link.load()
if (!links[canonical]) return false if (!links[canonical]) return false
// Remove the symlink if it exists // Remove the symlink if it exists
var target_dir = get_package_abs_dir(canonical) var target_dir = get_package_abs_dir(canonical)
if (fd.is_link(target_dir)) { if (fd.is_link(target_dir)) {
fd.unlink(target_dir) fd.unlink(target_dir)
log.console("Removed symlink at " + target_dir) log.console("Removed symlink at " + target_dir)
} }
delete links[canonical] delete links[canonical]
Link.save(links) Link.save(links)
log.console("Unlinked " + canonical) log.console("Unlinked " + canonical)
@@ -172,7 +188,7 @@ Link.clear = function() {
fd.unlink(target_dir) fd.unlink(target_dir)
} }
}) })
Link.save({}) Link.save({})
log.console("Cleared all links") log.console("Cleared all links")
return true return true
@@ -186,25 +202,25 @@ Link.sync_one = function(canonical, target, shop) {
// Ensure parent directories exist // Ensure parent directories exist
var parent = fd.dirname(target_dir) var parent = fd.dirname(target_dir)
ensure_dir(parent) ensure_dir(parent)
// Check current state // Check current state
var current_link = null var current_link = null
if (fd.is_link(target_dir)) { if (fd.is_link(target_dir)) {
current_link = fd.readlink(target_dir) current_link = fd.readlink(target_dir)
} }
// If already correctly linked, nothing to do // If already correctly linked, nothing to do
if (current_link == link_target) { if (current_link == link_target) {
return true return true
} }
// Remove existing file/dir/link // Remove existing file/dir/link
if (fd.is_link(target_dir)) { if (fd.is_link(target_dir)) {
fd.unlink(target_dir) fd.unlink(target_dir)
} else if (fd.is_dir(target_dir)) { } else if (fd.is_dir(target_dir)) {
fd.rmdir(target_dir, 1) fd.rmdir(target_dir, 1)
} }
// Create symlink // Create symlink
fd.symlink(link_target, target_dir) fd.symlink(link_target, target_dir)
return true return true
@@ -218,7 +234,9 @@ Link.sync_all = function(shop) {
arrfor(array(links), function(canonical) { arrfor(array(links), function(canonical) {
var target = links[canonical] var target = links[canonical]
try { var sync_disrupted = false
var sync_error_msg = ""
var do_sync = function() {
// Validate target exists // Validate target exists
var link_target = resolve_link_target(target) var link_target = resolve_link_target(target)
if (!fd.is_dir(link_target)) { if (!fd.is_dir(link_target)) {
@@ -234,7 +252,8 @@ Link.sync_all = function(shop) {
// Install dependencies of the linked package // Install dependencies of the linked package
var toml_path = link_target + '/cell.toml' var toml_path = link_target + '/cell.toml'
try { var read_deps_disrupted = false
var do_read_deps = function() {
var content = text(fd.slurp(toml_path)) var content = text(fd.slurp(toml_path))
var cfg = toml.decode(content) var cfg = toml.decode(content)
if (cfg.dependencies) { if (cfg.dependencies) {
@@ -245,21 +264,25 @@ Link.sync_all = function(shop) {
return return
} }
// Install the dependency if not already in shop // Install the dependency if not already in shop
try { var install_dep = function() {
shop.get(dep_locator) shop.get(dep_locator)
shop.extract(dep_locator) shop.extract(dep_locator)
} catch (e) { } disruption {}
// Silently continue - dependency may already be installed install_dep()
}
}) })
} }
} catch (e) { } disruption {
// Could not read dependencies - continue anyway read_deps_disrupted = true
} }
do_read_deps()
count++ count++
} catch (e) { } disruption {
push(errors, canonical + ': ' + e.message) sync_disrupted = true
}
do_sync()
if (sync_disrupted) {
push(errors, canonical + ': sync failed')
} }
}) })

View File

@@ -571,13 +571,13 @@ static const JSCFunctionListEntry js_enet_peer_funcs[] = {
JSValue js_enet_use(JSContext *ctx) JSValue js_enet_use(JSContext *ctx)
{ {
JS_NewClassID(&enet_host_id); JS_NewClassID(&enet_host_id);
JS_NewClass(JS_GetRuntime(ctx), enet_host_id, &enet_host); JS_NewClass(ctx, enet_host_id, &enet_host);
JSValue host_proto = JS_NewObject(ctx); JSValue host_proto = JS_NewObject(ctx);
JS_SetPropertyFunctionList(ctx, host_proto, js_enet_host_funcs, countof(js_enet_host_funcs)); JS_SetPropertyFunctionList(ctx, host_proto, js_enet_host_funcs, countof(js_enet_host_funcs));
JS_SetClassProto(ctx, enet_host_id, host_proto); JS_SetClassProto(ctx, enet_host_id, host_proto);
JS_NewClassID(&enet_peer_class_id); JS_NewClassID(&enet_peer_class_id);
JS_NewClass(JS_GetRuntime(ctx), enet_peer_class_id, &enet_peer_class); JS_NewClass(ctx, enet_peer_class_id, &enet_peer_class);
JSValue peer_proto = JS_NewObject(ctx); JSValue peer_proto = JS_NewObject(ctx);
JS_SetPropertyFunctionList(ctx, peer_proto, js_enet_peer_funcs, countof(js_enet_peer_funcs)); JS_SetPropertyFunctionList(ctx, peer_proto, js_enet_peer_funcs, countof(js_enet_peer_funcs));
JS_SetClassProto(ctx, enet_peer_class_id, peer_proto); JS_SetClassProto(ctx, enet_peer_class_id, peer_proto);

View File

@@ -51,7 +51,8 @@ package.load_config = function(name)
return config_cache[config_path] return config_cache[config_path]
if (!fd.is_file(config_path)) { if (!fd.is_file(config_path)) {
throw Error(`${config_path} does not exist`) log.error(`${config_path} does not exist`)
disrupt
} }
var content = text(fd.slurp(config_path)) var content = text(fd.slurp(config_path))
@@ -158,19 +159,20 @@ package.split_alias = function(name, path)
var parts = array(path, '/') var parts = array(path, '/')
var first_part = parts[0] var first_part = parts[0]
try { var split_result = null
var do_split = function() {
var config = package.load_config(name) var config = package.load_config(name)
if (!config) return null if (!config) return
var deps = config.dependencies var deps = config.dependencies
if (deps && deps[first_part]) { if (deps && deps[first_part]) {
var dep_locator = deps[first_part] var dep_locator = deps[first_part]
var remaining_path = text(array(parts, 1), '/') var remaining_path = text(array(parts, 1), '/')
return { package: dep_locator, path: remaining_path } split_result = { package: dep_locator, path: remaining_path }
} }
} catch (e) { } disruption {}
// Config doesn't exist or couldn't be loaded do_split()
} if (split_result) return split_result
return null return null
} }

View File

@@ -3,6 +3,8 @@
// Based on Douglas Crockford's parseq, adapted for Cell. // Based on Douglas Crockford's parseq, adapted for Cell.
// Time is in seconds. // Time is in seconds.
function safe_call(fn, arg) { fn(arg) } disruption {}
function make_reason(factory, excuse, evidence) { function make_reason(factory, excuse, evidence) {
def reason = Error(`pronto.${factory}${excuse ? ': ' + excuse : ''}`) def reason = Error(`pronto.${factory}${excuse ? ': ' + excuse : ''}`)
reason.evidence = evidence reason.evidence = evidence
@@ -15,12 +17,12 @@ function is_requestor(fn) {
function check_requestors(list, factory) { function check_requestors(list, factory) {
if (!is_array(list) || some(list, r => !is_requestor(r))) if (!is_array(list) || some(list, r => !is_requestor(r)))
throw make_reason(factory, 'Bad requestor array.', list) disrupt
} }
function check_callback(cb, factory) { function check_callback(cb, factory) {
if (!is_function(cb) || length(cb) != 2) if (!is_function(cb) || length(cb) != 2)
throw make_reason(factory, 'Not a callback.', cb) disrupt
} }
// fallback(requestor_array) // fallback(requestor_array)
@@ -28,7 +30,7 @@ function check_callback(cb, factory) {
function fallback(requestor_array) { function fallback(requestor_array) {
def factory = 'fallback' def factory = 'fallback'
if (!is_array(requestor_array) || length(requestor_array) == 0) if (!is_array(requestor_array) || length(requestor_array) == 0)
throw make_reason(factory, 'Empty requestor array.') disrupt
check_requestors(requestor_array, factory) check_requestors(requestor_array, factory)
return function fallback_requestor(callback, value) { return function fallback_requestor(callback, value) {
@@ -40,7 +42,7 @@ function fallback(requestor_array) {
function cancel(reason) { function cancel(reason) {
cancelled = true cancelled = true
if (current_cancel) { if (current_cancel) {
try { current_cancel(reason) } catch (_) {} safe_call(current_cancel, reason)
current_cancel = null current_cancel = null
} }
} }
@@ -55,7 +57,8 @@ function fallback(requestor_array) {
def requestor = requestor_array[index] def requestor = requestor_array[index]
index += 1 index += 1
try { var requestor_disrupted = false
var start_requestor = function() {
current_cancel = requestor(function(val, reason) { current_cancel = requestor(function(val, reason) {
if (cancelled) return if (cancelled) return
current_cancel = null current_cancel = null
@@ -65,7 +68,11 @@ function fallback(requestor_array) {
try_next() try_next()
} }
}, value) }, value)
} catch (ex) { } disruption {
requestor_disrupted = true
}
start_requestor()
if (requestor_disrupted) {
try_next() try_next()
} }
} }
@@ -80,7 +87,7 @@ function fallback(requestor_array) {
function parallel(requestor_array, throttle, need) { function parallel(requestor_array, throttle, need) {
def factory = 'parallel' def factory = 'parallel'
if (!is_array(requestor_array)) if (!is_array(requestor_array))
throw make_reason(factory, 'Not an array.', requestor_array) disrupt
check_requestors(requestor_array, factory) check_requestors(requestor_array, factory)
def length = length(requestor_array) def length = length(requestor_array)
@@ -89,10 +96,10 @@ function parallel(requestor_array, throttle, need) {
if (need == null) need = length if (need == null) need = length
if (!is_number(need) || need < 0 || need > length) if (!is_number(need) || need < 0 || need > length)
throw make_reason(factory, 'Bad need.', need) disrupt
if (throttle != null && (!is_number(throttle) || throttle < 1)) if (throttle != null && (!is_number(throttle) || throttle < 1))
throw make_reason(factory, 'Bad throttle.', throttle) disrupt
return function parallel_requestor(callback, value) { return function parallel_requestor(callback, value) {
check_callback(callback, factory) check_callback(callback, factory)
@@ -107,7 +114,8 @@ function parallel(requestor_array, throttle, need) {
if (finished) return if (finished) return
finished = true finished = true
arrfor(cancel_list, c => { arrfor(cancel_list, c => {
try { if (is_function(c)) c(reason) } catch (_) {} var do_cancel = function() { if (is_function(c)) c(reason) } disruption {}
do_cancel()
}) })
} }
@@ -117,7 +125,9 @@ function parallel(requestor_array, throttle, need) {
next_index += 1 next_index += 1
def requestor = requestor_array[idx] def requestor = requestor_array[idx]
try { var requestor_disrupted = false
var requestor_ex = null
var run_requestor = function() {
cancel_list[idx] = requestor(function(val, reason) { cancel_list[idx] = requestor(function(val, reason) {
if (finished) return if (finished) return
cancel_list[idx] = null cancel_list[idx] = null
@@ -142,11 +152,15 @@ function parallel(requestor_array, throttle, need) {
start_one() start_one()
}, value) }, value)
} catch (ex) { } disruption {
requestor_disrupted = true
}
run_requestor()
if (requestor_disrupted) {
failures += 1 failures += 1
if (failures > length - need) { if (failures > length - need) {
cancel(ex) cancel(requestor_ex)
callback(null, ex) callback(null, requestor_ex)
return return
} }
start_one() start_one()
@@ -166,16 +180,16 @@ function parallel(requestor_array, throttle, need) {
function race(requestor_array, throttle, need) { function race(requestor_array, throttle, need) {
def factory = 'race' def factory = 'race'
if (!is_array(requestor_array) || length(requestor_array) == 0) if (!is_array(requestor_array) || length(requestor_array) == 0)
throw make_reason(factory, 'Empty requestor array.') disrupt
check_requestors(requestor_array, factory) check_requestors(requestor_array, factory)
def length = length(requestor_array) def length = length(requestor_array)
if (need == null) need = 1 if (need == null) need = 1
if (!is_number(need) || need < 1 || need > length) if (!is_number(need) || need < 1 || need > length)
throw make_reason(factory, 'Bad need.', need) disrupt
if (throttle != null && (!is_number(throttle) || throttle < 1)) if (throttle != null && (!is_number(throttle) || throttle < 1))
throw make_reason(factory, 'Bad throttle.', throttle) disrupt
return function race_requestor(callback, value) { return function race_requestor(callback, value) {
check_callback(callback, factory) check_callback(callback, factory)
@@ -190,7 +204,8 @@ function race(requestor_array, throttle, need) {
if (finished) return if (finished) return
finished = true finished = true
arrfor(cancel_list, c => { arrfor(cancel_list, c => {
try { if (is_function(c)) c(reason) } catch (_) {} var do_cancel = function() { if (is_function(c)) c(reason) } disruption {}
do_cancel()
}) })
} }
@@ -200,7 +215,9 @@ function race(requestor_array, throttle, need) {
next_index += 1 next_index += 1
def requestor = requestor_array[idx] def requestor = requestor_array[idx]
try { var requestor_disrupted = false
var requestor_ex = null
var run_requestor = function() {
cancel_list[idx] = requestor(function(val, reason) { cancel_list[idx] = requestor(function(val, reason) {
if (finished) return if (finished) return
cancel_list[idx] = null cancel_list[idx] = null
@@ -228,11 +245,15 @@ function race(requestor_array, throttle, need) {
start_one() start_one()
}, value) }, value)
} catch (ex) { } disruption {
requestor_disrupted = true
}
run_requestor()
if (requestor_disrupted) {
failures += 1 failures += 1
if (failures > length - need) { if (failures > length - need) {
cancel(ex) cancel(requestor_ex)
callback(null, ex) callback(null, requestor_ex)
return return
} }
start_one() start_one()
@@ -251,7 +272,7 @@ function race(requestor_array, throttle, need) {
function sequence(requestor_array) { function sequence(requestor_array) {
def factory = 'sequence' def factory = 'sequence'
if (!is_array(requestor_array)) if (!is_array(requestor_array))
throw make_reason(factory, 'Not an array.', requestor_array) disrupt
check_requestors(requestor_array, factory) check_requestors(requestor_array, factory)
if (length(requestor_array) == 0) if (length(requestor_array) == 0)
@@ -266,7 +287,7 @@ function sequence(requestor_array) {
function cancel(reason) { function cancel(reason) {
cancelled = true cancelled = true
if (current_cancel) { if (current_cancel) {
try { current_cancel(reason) } catch (_) {} safe_call(current_cancel, reason)
current_cancel = null current_cancel = null
} }
} }
@@ -281,7 +302,9 @@ function sequence(requestor_array) {
def requestor = requestor_array[index] def requestor = requestor_array[index]
index += 1 index += 1
try { var requestor_disrupted = false
var requestor_ex = null
var run_requestor = function() {
current_cancel = requestor(function(result, reason) { current_cancel = requestor(function(result, reason) {
if (cancelled) return if (cancelled) return
current_cancel = null current_cancel = null
@@ -291,8 +314,12 @@ function sequence(requestor_array) {
run_next(result) run_next(result)
} }
}, val) }, val)
} catch (ex) { } disruption {
callback(null, ex) requestor_disrupted = true
}
run_requestor()
if (requestor_disrupted) {
callback(null, requestor_ex)
} }
} }
@@ -306,15 +333,21 @@ function sequence(requestor_array) {
function requestorize(unary) { function requestorize(unary) {
def factory = 'requestorize' def factory = 'requestorize'
if (!is_function(unary)) if (!is_function(unary))
throw make_reason(factory, 'Not a function.', unary) disrupt
return function requestorized(callback, value) { return function requestorized(callback, value) {
check_callback(callback, factory) check_callback(callback, factory)
try { var call_disrupted = false
var call_ex = null
var do_call = function() {
def result = unary(value) def result = unary(value)
callback(result == null ? true : result) callback(result == null ? true : result)
} catch (ex) { } disruption {
callback(null, ex) call_disrupted = true
}
do_call()
if (call_disrupted) {
callback(null, call_ex)
} }
} }
} }

4
qop.c
View File

@@ -458,13 +458,13 @@ static const JSCFunctionListEntry js_qop_funcs[] = {
JSValue js_qop_use(JSContext *js) { JSValue js_qop_use(JSContext *js) {
JS_NewClassID(&js_qop_archive_class_id); JS_NewClassID(&js_qop_archive_class_id);
JS_NewClass(JS_GetRuntime(js), js_qop_archive_class_id, &js_qop_archive_class); JS_NewClass(js, js_qop_archive_class_id, &js_qop_archive_class);
JSValue archive_proto = JS_NewObject(js); JSValue archive_proto = JS_NewObject(js);
JS_SetPropertyFunctionList(js, archive_proto, js_qop_archive_funcs, countof(js_qop_archive_funcs)); JS_SetPropertyFunctionList(js, archive_proto, js_qop_archive_funcs, countof(js_qop_archive_funcs));
JS_SetClassProto(js, js_qop_archive_class_id, archive_proto); JS_SetClassProto(js, js_qop_archive_class_id, archive_proto);
JS_NewClassID(&js_qop_writer_class_id); JS_NewClassID(&js_qop_writer_class_id);
JS_NewClass(JS_GetRuntime(js), js_qop_writer_class_id, &js_qop_writer_class); JS_NewClass(js, js_qop_writer_class_id, &js_qop_writer_class);
JSValue writer_proto = JS_NewObject(js); JSValue writer_proto = JS_NewObject(js);
JS_SetPropertyFunctionList(js, writer_proto, js_qop_writer_funcs, countof(js_qop_writer_funcs)); JS_SetPropertyFunctionList(js, writer_proto, js_qop_writer_funcs, countof(js_qop_writer_funcs));
JS_SetClassProto(js, js_qop_writer_class_id, writer_proto); JS_SetClassProto(js, js_qop_writer_class_id, writer_proto);

View File

@@ -26,6 +26,7 @@ static int run_test_suite(size_t heap_size);
cell_rt *root_cell = NULL; cell_rt *root_cell = NULL;
static char *core_path = NULL; static char *core_path = NULL;
static JSRuntime *g_runtime = NULL;
// Get the home directory // Get the home directory
static const char* get_home_dir(void) { static const char* get_home_dir(void) {
@@ -165,13 +166,27 @@ JSValue js_wota_use(JSContext *js);
void script_startup(cell_rt *prt) void script_startup(cell_rt *prt)
{ {
JSRuntime *rt = JS_NewRuntime(); if (!g_runtime) {
JS_SetInterruptHandler(rt, (JSInterruptHandler *)actor_interrupt_cb, prt); g_runtime = JS_NewRuntime();
JSContext *js = JS_NewContext(rt); }
JSContext *js = JS_NewContext(g_runtime);
JS_SetInterruptHandler(js, (JSInterruptHandler *)actor_interrupt_cb, prt);
JS_SetContextOpaque(js, prt); JS_SetContextOpaque(js, prt);
prt->context = js; prt->context = js;
/* Register all GCRef fields so the Cheney GC can relocate them. */
JS_AddGCRef(js, &prt->idx_buffer_ref);
JS_AddGCRef(js, &prt->on_exception_ref);
JS_AddGCRef(js, &prt->message_handle_ref);
JS_AddGCRef(js, &prt->unneeded_ref);
JS_AddGCRef(js, &prt->actor_sym_ref);
prt->idx_buffer_ref.val = JS_NULL;
prt->on_exception_ref.val = JS_NULL;
prt->message_handle_ref.val = JS_NULL;
prt->unneeded_ref.val = JS_NULL;
prt->actor_sym_ref.val = JS_NULL;
cell_rt *crt = JS_GetContextOpaque(js); cell_rt *crt = JS_GetContextOpaque(js);
JS_FreeValue(js, js_blob_use(js)); JS_FreeValue(js, js_blob_use(js));
@@ -195,31 +210,54 @@ void script_startup(cell_rt *prt)
return; return;
} }
// Create hidden environment // Create hidden environment (GC-protected to survive allocations)
JSValue hidden_env = JS_NewObject(js); JSGCRef env_ref;
JS_SetPropertyStr(js, hidden_env, "os", js_os_use(js)); JS_PushGCRef(js, &env_ref);
JS_SetPropertyStr(js, hidden_env, "json", js_json_use(js)); env_ref.val = JS_NewObject(js);
JS_SetPropertyStr(js, hidden_env, "nota", js_nota_use(js));
JS_SetPropertyStr(js, hidden_env, "wota", js_wota_use(js));
crt->actor_sym = JS_NewObject(js); // Create modules with GC rooting
JS_SetPropertyStr(js, hidden_env, "actorsym", JS_DupValue(js, crt->actor_sym)); JSGCRef os_ref, json_ref, nota_ref, wota_ref;
JS_PushGCRef(js, &os_ref);
JS_PushGCRef(js, &json_ref);
JS_PushGCRef(js, &nota_ref);
JS_PushGCRef(js, &wota_ref);
os_ref.val = js_os_use(js);
json_ref.val = js_json_use(js);
nota_ref.val = js_nota_use(js);
wota_ref.val = js_wota_use(js);
// Set properties on env (SetPropertyStr internally roots its args)
JS_SetPropertyStr(js, env_ref.val, "os", os_ref.val);
JS_SetPropertyStr(js, env_ref.val, "json", json_ref.val);
JS_SetPropertyStr(js, env_ref.val, "nota", nota_ref.val);
JS_SetPropertyStr(js, env_ref.val, "wota", wota_ref.val);
JS_PopGCRef(js, &wota_ref);
JS_PopGCRef(js, &nota_ref);
JS_PopGCRef(js, &json_ref);
JS_PopGCRef(js, &os_ref);
crt->actor_sym_ref.val = JS_NewObject(js);
JS_SetPropertyStr(js, env_ref.val, "actorsym", JS_DupValue(js, crt->actor_sym_ref.val));
// Always set init (even if null)
if (crt->init_wota) { if (crt->init_wota) {
JS_SetPropertyStr(js, hidden_env, "init", wota2value(js, crt->init_wota)); JSValue init_val = wota2value(js, crt->init_wota);
JS_SetPropertyStr(js, env_ref.val, "init", init_val);
free(crt->init_wota); free(crt->init_wota);
crt->init_wota = NULL; crt->init_wota = NULL;
} else { } else {
JS_SetPropertyStr(js, hidden_env, "init", JS_NULL); JS_SetPropertyStr(js, env_ref.val, "init", JS_NULL);
} }
if (core_path) { if (core_path) {
JS_SetPropertyStr(js, hidden_env, "core_path", JS_NewString(js, core_path)); JSValue path_val = JS_NewString(js, core_path);
JS_SetPropertyStr(js, env_ref.val, "core_path", path_val);
} }
// Stone the environment // Stone the environment
hidden_env = JS_Stone(js, hidden_env); JSValue hidden_env = JS_Stone(js, env_ref.val);
JS_PopGCRef(js, &env_ref);
// Run through MACH VM // Run through MACH VM
crt->state = ACTOR_RUNNING; crt->state = ACTOR_RUNNING;

View File

@@ -144,7 +144,7 @@ JS_SetPropertyFunctionList(js, TYPE##_proto, js_##TYPE##_funcs, countof(js_##TYP
#define QJSCLASSPREP_NO_FUNCS(TYPE) \ #define QJSCLASSPREP_NO_FUNCS(TYPE) \
JS_NewClassID(&js_##TYPE##_id);\ JS_NewClassID(&js_##TYPE##_id);\
JS_NewClass(JS_GetRuntime(js), js_##TYPE##_id, &js_##TYPE##_class);\ JS_NewClass(js, js_##TYPE##_id, &js_##TYPE##_class);\
JSValue TYPE##_proto = JS_NewObject(js); \ JSValue TYPE##_proto = JS_NewObject(js); \
JS_SetClassProto(js, js_##TYPE##_id, TYPE##_proto); \ JS_SetClassProto(js, js_##TYPE##_id, TYPE##_proto); \

View File

@@ -24,21 +24,26 @@ typedef struct letter {
typedef struct cell_rt { typedef struct cell_rt {
JSContext *context; JSContext *context;
JSValue idx_buffer;
JSValue on_exception; /* JSValues on the GC heap — each paired with a JSGCRef so the
JSValue message_handle; Cheney GC can relocate them during compaction. */
JSGCRef idx_buffer_ref;
JSGCRef on_exception_ref;
JSGCRef message_handle_ref;
JSGCRef unneeded_ref;
JSGCRef actor_sym_ref;
void *init_wota; void *init_wota;
/* Protects JSContext usage */ /* Protects JSContext usage */
pthread_mutex_t *mutex; /* for everything else */ pthread_mutex_t *mutex; /* for everything else */
pthread_mutex_t *msg_mutex; /* For message queue and timers queue */ pthread_mutex_t *msg_mutex; /* For message queue and timers queue */
char *id; char *id;
int idx_count; int idx_count;
/* The mailbox for incoming messages + a dedicated lock for it: */ /* The "mailbox" for incoming messages + a dedicated lock for it: */
letter *letters; letter *letters;
/* CHANGED FOR EVENTS: a separate lock for the actor->events queue */ /* CHANGED FOR EVENTS: a separate lock for the actor->events queue */
@@ -47,14 +52,11 @@ typedef struct cell_rt {
int state; int state;
uint32_t ar; // timer for unneeded uint32_t ar; // timer for unneeded
double ar_secs; // time for unneeded double ar_secs; // time for unneeded
JSValue unneeded; // fn to call before unneeded
int disrupt; int disrupt;
int main_thread_only; int main_thread_only;
int affinity; int affinity;
JSValue actor_sym;
const char *name; // human friendly name const char *name; // human friendly name
cell_hook trace_hook; cell_hook trace_hook;
} cell_rt; } cell_rt;
@@ -63,8 +65,6 @@ cell_rt *create_actor(void *wota);
const char *register_actor(const char *id, cell_rt *actor, int mainthread, double ar); const char *register_actor(const char *id, cell_rt *actor, int mainthread, double ar);
void actor_disrupt(cell_rt *actor); void actor_disrupt(cell_rt *actor);
JSValue actor_sym(cell_rt *actor);
const char *send_message(const char *id, void *msg); const char *send_message(const char *id, void *msg);
const char *register_actor(const char *id, cell_rt *actor, int mainthread, double ar); const char *register_actor(const char *id, cell_rt *actor, int mainthread, double ar);
void actor_unneeded(cell_rt *actor, JSValue fn, double seconds); void actor_unneeded(cell_rt *actor, JSValue fn, double seconds);

View File

@@ -74,7 +74,7 @@ JSC_CCALL(os_register_actor,
JS_ToFloat64(js, &ar, argv[3]); JS_ToFloat64(js, &ar, argv[3]);
const char *err = register_actor(id, rt, JS_ToBool(js, argv[2]), ar); const char *err = register_actor(id, rt, JS_ToBool(js, argv[2]), ar);
if (err) return JS_ThrowInternalError(js, "Could not register actor: %s", err); if (err) return JS_ThrowInternalError(js, "Could not register actor: %s", err);
rt->message_handle = JS_DupValue(js, argv[1]); rt->message_handle_ref.val = argv[1];
rt->context = js; rt->context = js;
JS_FreeCString(js, id); JS_FreeCString(js, id);
) )
@@ -106,8 +106,7 @@ JSC_SCALL(actor_setname,
JSC_CCALL(actor_on_exception, JSC_CCALL(actor_on_exception,
cell_rt *rt = JS_GetContextOpaque(js); cell_rt *rt = JS_GetContextOpaque(js);
JS_FreeValue(js, rt->on_exception); rt->on_exception_ref.val = argv[0];
rt->on_exception = JS_DupValue(js,argv[0]);
) )
JSC_CCALL(actor_clock, JSC_CCALL(actor_clock,

View File

@@ -373,31 +373,11 @@ struct JSRuntime {
/* Buddy allocator for actor memory blocks */ /* Buddy allocator for actor memory blocks */
BuddyAllocator buddy; BuddyAllocator buddy;
int class_count; /* size of class_array */
JSClass *class_array;
/* see JS_SetStripInfo() */ /* see JS_SetStripInfo() */
uint8_t strip_flags; uint8_t strip_flags;
/* Stack overflow protection */
size_t stack_size;
const uint8_t *stack_top;
const uint8_t *stack_limit;
/* Current exception (for error propagation) */
JSValue current_exception;
struct JSStackFrame *current_stack_frame;
/* User data */ /* User data */
void *user_opaque; void *user_opaque;
/* Interrupt handler for checking execution limits */
JSInterruptHandler *interrupt_handler;
void *interrupt_opaque;
/* Register VM frame tracking for stack traces */
void *current_register_frame; /* JSFrameRegister* at exception time */
uint32_t current_register_pc; /* PC at exception time */
}; };
struct JSClass { struct JSClass {
@@ -1052,6 +1032,8 @@ struct JSContext {
JSGCRef *top_gc_ref; /* used to reference temporary GC roots (stack top) */ JSGCRef *top_gc_ref; /* used to reference temporary GC roots (stack top) */
JSGCRef *last_gc_ref; /* used to reference temporary GC roots (list) */ JSGCRef *last_gc_ref; /* used to reference temporary GC roots (list) */
int class_count; /* size of class_array and class_proto */
JSClass *class_array;
JSValue *class_proto; JSValue *class_proto;
JSValue regexp_ctor; JSValue regexp_ctor;
JSValue native_error_proto[JS_NATIVE_ERROR_COUNT]; JSValue native_error_proto[JS_NATIVE_ERROR_COUNT];
@@ -1082,14 +1064,22 @@ struct JSContext {
/* Register VM frame root (updated by GC when frame moves) */ /* Register VM frame root (updated by GC when frame moves) */
JSValue reg_current_frame; /* current JSFrameRegister being executed */ JSValue reg_current_frame; /* current JSFrameRegister being executed */
uint32_t current_register_pc; /* PC at exception time */
/* Execution state (moved from JSRuntime — per-actor) */
JSValue current_exception; JSValue current_exception;
struct JSStackFrame *current_stack_frame;
BOOL current_exception_is_uncatchable : 8; BOOL current_exception_is_uncatchable : 8;
BOOL in_out_of_memory : 8; BOOL in_out_of_memory : 8;
JSInterruptHandler *interrupt_handler; JSInterruptHandler *interrupt_handler;
void *interrupt_opaque; void *interrupt_opaque;
/* Stack overflow protection */
size_t stack_size;
const uint8_t *stack_top;
const uint8_t *stack_limit;
/* Parser state (for GC to scan cpool during parsing) */ /* Parser state (for GC to scan cpool during parsing) */
struct JSFunctionDef *current_parse_fd; struct JSFunctionDef *current_parse_fd;
}; };
@@ -2066,7 +2056,7 @@ static JSValue js_new_string8_len (JSContext *ctx, const char *buf, int len);
static JSValue pretext_end (JSContext *ctx, JSText *s); static JSValue pretext_end (JSContext *ctx, JSText *s);
static JSValue js_compile_regexp (JSContext *ctx, JSValue pattern, JSValue flags); static JSValue js_compile_regexp (JSContext *ctx, JSValue pattern, JSValue flags);
static JSValue js_regexp_constructor_internal (JSContext *ctx, JSValue pattern, JSValue bc); static JSValue js_regexp_constructor_internal (JSContext *ctx, JSValue pattern, JSValue bc);
static int JS_NewClass1 (JSRuntime *rt, JSClassID class_id, const JSClassDef *class_def, const char *name); static int JS_NewClass1 (JSContext *ctx, JSClassID class_id, const JSClassDef *class_def, const char *name);
static BOOL js_strict_eq (JSContext *ctx, JSValue op1, JSValue op2); static BOOL js_strict_eq (JSContext *ctx, JSValue op1, JSValue op2);
static JSValue js_cell_text (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); static JSValue js_cell_text (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
@@ -2450,7 +2440,7 @@ static JSClass const js_std_class_def[] = {
{ "blob", NULL, NULL }, /* JS_CLASS_BLOB - registered separately */ { "blob", NULL, NULL }, /* JS_CLASS_BLOB - registered separately */
}; };
static int init_class_range (JSRuntime *rt, JSClass const *tab, int start, int count) { static int init_class_range (JSContext *ctx, JSClass const *tab, int start, int count) {
JSClassDef cm_s, *cm = &cm_s; JSClassDef cm_s, *cm = &cm_s;
int i, class_id; int i, class_id;
@@ -2459,7 +2449,7 @@ static int init_class_range (JSRuntime *rt, JSClass const *tab, int start, int c
memset (cm, 0, sizeof (*cm)); memset (cm, 0, sizeof (*cm));
cm->finalizer = tab[i].finalizer; cm->finalizer = tab[i].finalizer;
cm->gc_mark = tab[i].gc_mark; cm->gc_mark = tab[i].gc_mark;
if (JS_NewClass1 (rt, class_id, cm, tab[i].class_name) < 0) return -1; if (JS_NewClass1 (ctx, class_id, cm, tab[i].class_name) < 0) return -1;
} }
return 0; return 0;
} }
@@ -2468,7 +2458,7 @@ static int init_class_range (JSRuntime *rt, JSClass const *tab, int start, int c
/* no stack limitation */ /* no stack limitation */
static inline uintptr_t js_get_stack_pointer (void) { return 0; } static inline uintptr_t js_get_stack_pointer (void) { return 0; }
static inline BOOL js_check_stack_overflow (JSRuntime *rt, static inline BOOL js_check_stack_overflow (JSContext *ctx,
size_t alloca_size) { size_t alloca_size) {
return FALSE; return FALSE;
} }
@@ -2478,11 +2468,11 @@ static inline uintptr_t js_get_stack_pointer (void) {
return (uintptr_t)__builtin_frame_address (0); return (uintptr_t)__builtin_frame_address (0);
} }
static inline BOOL js_check_stack_overflow (JSRuntime *rt, static inline BOOL js_check_stack_overflow (JSContext *ctx,
size_t alloca_size) { size_t alloca_size) {
uintptr_t sp; uintptr_t sp;
sp = js_get_stack_pointer () - alloca_size; sp = js_get_stack_pointer () - alloca_size;
return unlikely (sp < (uintptr_t)rt->stack_limit); return unlikely (sp < (uintptr_t)ctx->stack_limit);
} }
#endif #endif
@@ -2849,7 +2839,7 @@ static int ctx_gc (JSContext *ctx, int allow_grow, size_t alloc_size) {
#ifdef DUMP_GC_DETAIL #ifdef DUMP_GC_DETAIL
printf(" roots: current_exception\n"); fflush(stdout); printf(" roots: current_exception\n"); fflush(stdout);
#endif #endif
rt->current_exception = gc_copy_value (ctx, rt->current_exception, from_base, from_end, to_base, &to_free, to_end); ctx->current_exception = gc_copy_value (ctx, ctx->current_exception, from_base, from_end, to_base, &to_free, to_end);
#ifdef DUMP_GC_DETAIL #ifdef DUMP_GC_DETAIL
printf(" roots: native_error_proto\n"); fflush(stdout); printf(" roots: native_error_proto\n"); fflush(stdout);
@@ -2860,9 +2850,9 @@ static int ctx_gc (JSContext *ctx, int allow_grow, size_t alloc_size) {
/* Copy class prototypes */ /* Copy class prototypes */
#ifdef DUMP_GC_DETAIL #ifdef DUMP_GC_DETAIL
printf(" roots: class_proto (count=%d)\n", rt->class_count); fflush(stdout); printf(" roots: class_proto (count=%d)\n", ctx->class_count); fflush(stdout);
#endif #endif
for (int i = 0; i < rt->class_count; i++) { for (int i = 0; i < ctx->class_count; i++) {
ctx->class_proto[i] = gc_copy_value (ctx, ctx->class_proto[i], from_base, from_end, to_base, &to_free, to_end); ctx->class_proto[i] = gc_copy_value (ctx, ctx->class_proto[i], from_base, from_end, to_base, &to_free, to_end);
} }
@@ -2894,7 +2884,7 @@ static int ctx_gc (JSContext *ctx, int allow_grow, size_t alloc_size) {
#ifdef DUMP_GC_DETAIL #ifdef DUMP_GC_DETAIL
printf(" roots: current_stack_frame chain\n"); fflush(stdout); printf(" roots: current_stack_frame chain\n"); fflush(stdout);
#endif #endif
for (JSStackFrame *sf = rt->current_stack_frame; sf != NULL; sf = sf->prev_frame) { for (JSStackFrame *sf = ctx->current_stack_frame; sf != NULL; sf = sf->prev_frame) {
sf->cur_func = gc_copy_value (ctx, sf->cur_func, from_base, from_end, to_base, &to_free, to_end); sf->cur_func = gc_copy_value (ctx, sf->cur_func, from_base, from_end, to_base, &to_free, to_end);
/* Also scan bytecode cpool if it's a bytecode object outside GC heap */ /* Also scan bytecode cpool if it's a bytecode object outside GC heap */
gc_scan_bytecode_cpool (ctx, sf->cur_func, from_base, from_end, to_base, &to_free, to_end); gc_scan_bytecode_cpool (ctx, sf->cur_func, from_base, from_end, to_base, &to_free, to_end);
@@ -3036,20 +3026,7 @@ JSRuntime *JS_NewRuntime (void) {
if (!rt) return NULL; if (!rt) return NULL;
memset (rt, 0, sizeof (*rt)); memset (rt, 0, sizeof (*rt));
/* create the object, array and function classes */
if (init_class_range (rt, js_std_class_def, JS_CLASS_OBJECT, countof (js_std_class_def))
< 0)
goto fail;
rt->stack_size = JS_DEFAULT_STACK_SIZE;
JS_UpdateStackTop (rt);
rt->current_exception = JS_UNINITIALIZED;
return rt; return rt;
fail:
JS_FreeRuntime (rt);
return NULL;
} }
void *JS_GetRuntimeOpaque (JSRuntime *rt) { return rt->user_opaque; } void *JS_GetRuntimeOpaque (JSRuntime *rt) { return rt->user_opaque; }
@@ -3071,9 +3048,9 @@ static void *sys_realloc (void *ptr, size_t size) { return realloc (ptr, size);
#define free(p) free_is_forbidden (p) #define free(p) free_is_forbidden (p)
#define realloc(p, s) realloc_is_forbidden (p, s) #define realloc(p, s) realloc_is_forbidden (p, s)
void JS_SetInterruptHandler (JSRuntime *rt, JSInterruptHandler *cb, void *opaque) { void JS_SetInterruptHandler (JSContext *ctx, JSInterruptHandler *cb, void *opaque) {
rt->interrupt_handler = cb; ctx->interrupt_handler = cb;
rt->interrupt_opaque = opaque; ctx->interrupt_opaque = opaque;
} }
void JS_SetStripInfo (JSRuntime *rt, int flags) { rt->strip_flags = flags; } void JS_SetStripInfo (JSRuntime *rt, int flags) { rt->strip_flags = flags; }
@@ -3104,11 +3081,9 @@ void JS_SetRuntimeInfo (JSRuntime *rt, const char *s) {
} }
void JS_FreeRuntime (JSRuntime *rt) { void JS_FreeRuntime (JSRuntime *rt) {
/* free the classes */
js_free_rt (rt->class_array);
/* Destroy buddy allocator */ /* Destroy buddy allocator */
buddy_destroy (&rt->buddy); buddy_destroy (&rt->buddy);
sys_free (rt);
} }
/* Forward declarations for intrinsics */ /* Forward declarations for intrinsics */
@@ -3118,7 +3093,6 @@ static void JS_AddIntrinsicRegExp (JSContext *ctx);
JSContext *JS_NewContextRawWithHeapSize (JSRuntime *rt, size_t heap_size) { JSContext *JS_NewContextRawWithHeapSize (JSRuntime *rt, size_t heap_size) {
JSContext *ctx; JSContext *ctx;
int i;
/* Round up to buddy allocator minimum */ /* Round up to buddy allocator minimum */
size_t min_size = 1ULL << BUDDY_MIN_ORDER; size_t min_size = 1ULL << BUDDY_MIN_ORDER;
@@ -3135,17 +3109,15 @@ JSContext *JS_NewContextRawWithHeapSize (JSRuntime *rt, size_t heap_size) {
if (!ctx) return NULL; if (!ctx) return NULL;
ctx->trace_hook = NULL; ctx->trace_hook = NULL;
ctx->rt = rt;
ctx->class_proto /* Bootstrap class_array and class_proto together via JS_NewClass1 */
= js_malloc_rt (sizeof (ctx->class_proto[0]) * rt->class_count); if (init_class_range (ctx, js_std_class_def, JS_CLASS_OBJECT, countof (js_std_class_def)) < 0) {
if (!ctx->class_proto) { js_free_rt (ctx->class_array);
js_free_rt (ctx->class_proto);
js_free_rt (ctx); js_free_rt (ctx);
return NULL; return NULL;
} }
ctx->rt = rt;
for (i = 0; i < rt->class_count; i++)
ctx->class_proto[i] = JS_NULL;
ctx->regexp_ctor = JS_NULL; ctx->regexp_ctor = JS_NULL;
@@ -3154,6 +3126,7 @@ JSContext *JS_NewContextRawWithHeapSize (JSRuntime *rt, size_t heap_size) {
ctx->frame_stack ctx->frame_stack
= js_malloc_rt (sizeof (struct VMFrame) * ctx->frame_stack_capacity); = js_malloc_rt (sizeof (struct VMFrame) * ctx->frame_stack_capacity);
if (!ctx->frame_stack) { if (!ctx->frame_stack) {
js_free_rt (ctx->class_array);
js_free_rt (ctx->class_proto); js_free_rt (ctx->class_proto);
js_free_rt (ctx); js_free_rt (ctx);
return NULL; return NULL;
@@ -3165,6 +3138,7 @@ JSContext *JS_NewContextRawWithHeapSize (JSRuntime *rt, size_t heap_size) {
= js_malloc_rt (sizeof (JSValue) * ctx->value_stack_capacity); = js_malloc_rt (sizeof (JSValue) * ctx->value_stack_capacity);
if (!ctx->value_stack) { if (!ctx->value_stack) {
js_free_rt (ctx->frame_stack); js_free_rt (ctx->frame_stack);
js_free_rt (ctx->class_array);
js_free_rt (ctx->class_proto); js_free_rt (ctx->class_proto);
js_free_rt (ctx); js_free_rt (ctx);
return NULL; return NULL;
@@ -3174,6 +3148,12 @@ JSContext *JS_NewContextRawWithHeapSize (JSRuntime *rt, size_t heap_size) {
/* Initialize register VM frame root */ /* Initialize register VM frame root */
ctx->reg_current_frame = JS_NULL; ctx->reg_current_frame = JS_NULL;
/* Initialize per-context execution state (moved from JSRuntime) */
ctx->current_exception = JS_UNINITIALIZED;
ctx->current_stack_frame = NULL;
ctx->stack_size = JS_DEFAULT_STACK_SIZE;
JS_UpdateStackTop (ctx);
/* Initialize stone text intern table */ /* Initialize stone text intern table */
ctx->st_pages = NULL; ctx->st_pages = NULL;
ctx->st_text_array = NULL; ctx->st_text_array = NULL;
@@ -3184,6 +3164,7 @@ JSContext *JS_NewContextRawWithHeapSize (JSRuntime *rt, size_t heap_size) {
if (st_text_resize (ctx) < 0) { if (st_text_resize (ctx) < 0) {
js_free_rt (ctx->value_stack); js_free_rt (ctx->value_stack);
js_free_rt (ctx->frame_stack); js_free_rt (ctx->frame_stack);
js_free_rt (ctx->class_array);
js_free_rt (ctx->class_proto); js_free_rt (ctx->class_proto);
js_free_rt (ctx); js_free_rt (ctx);
return NULL; return NULL;
@@ -3198,6 +3179,7 @@ JSContext *JS_NewContextRawWithHeapSize (JSRuntime *rt, size_t heap_size) {
js_free_rt (ctx->st_text_array); js_free_rt (ctx->st_text_array);
js_free_rt (ctx->value_stack); js_free_rt (ctx->value_stack);
js_free_rt (ctx->frame_stack); js_free_rt (ctx->frame_stack);
js_free_rt (ctx->class_array);
js_free_rt (ctx->class_proto); js_free_rt (ctx->class_proto);
js_free_rt (ctx); js_free_rt (ctx);
return NULL; return NULL;
@@ -3253,14 +3235,12 @@ static inline void set_value (JSContext *ctx, JSValue *pval, JSValue new_val) {
} }
void JS_SetClassProto (JSContext *ctx, JSClassID class_id, JSValue obj) { void JS_SetClassProto (JSContext *ctx, JSClassID class_id, JSValue obj) {
JSRuntime *rt = ctx->rt; assert (class_id < ctx->class_count);
assert (class_id < rt->class_count);
set_value (ctx, &ctx->class_proto[class_id], obj); set_value (ctx, &ctx->class_proto[class_id], obj);
} }
JSValue JS_GetClassProto (JSContext *ctx, JSClassID class_id) { JSValue JS_GetClassProto (JSContext *ctx, JSClassID class_id) {
JSRuntime *rt = ctx->rt; assert (class_id < ctx->class_count);
assert (class_id < rt->class_count);
return ctx->class_proto[class_id]; return ctx->class_proto[class_id];
} }
@@ -3278,8 +3258,9 @@ void JS_FreeContext (JSContext *ctx) {
for (i = 0; i < JS_NATIVE_ERROR_COUNT; i++) { for (i = 0; i < JS_NATIVE_ERROR_COUNT; i++) {
} }
for (i = 0; i < rt->class_count; i++) { for (i = 0; i < ctx->class_count; i++) {
} }
js_free_rt (ctx->class_array);
js_free_rt (ctx->class_proto); js_free_rt (ctx->class_proto);
/* Free VM stacks */ /* Free VM stacks */
@@ -3298,26 +3279,28 @@ void JS_FreeContext (JSContext *ctx) {
ctx->heap_free = NULL; ctx->heap_free = NULL;
ctx->heap_end = NULL; ctx->heap_end = NULL;
} }
js_free_rt (ctx);
} }
JSRuntime *JS_GetRuntime (JSContext *ctx) { return ctx->rt; } JSRuntime *JS_GetRuntime (JSContext *ctx) { return ctx->rt; }
static void update_stack_limit (JSRuntime *rt) { static void update_stack_limit (JSContext *ctx) {
if (rt->stack_size == 0) { if (ctx->stack_size == 0) {
rt->stack_limit = 0; /* no limit */ ctx->stack_limit = 0; /* no limit */
} else { } else {
rt->stack_limit = rt->stack_top - rt->stack_size; ctx->stack_limit = ctx->stack_top - ctx->stack_size;
} }
} }
void JS_SetMaxStackSize (JSRuntime *rt, size_t stack_size) { void JS_SetMaxStackSize (JSContext *ctx, size_t stack_size) {
rt->stack_size = stack_size; ctx->stack_size = stack_size;
update_stack_limit (rt); update_stack_limit (ctx);
} }
void JS_UpdateStackTop (JSRuntime *rt) { void JS_UpdateStackTop (JSContext *ctx) {
rt->stack_top = (const uint8_t *)js_get_stack_pointer (); ctx->stack_top = (const uint8_t *)js_get_stack_pointer ();
update_stack_limit (rt); update_stack_limit (ctx);
} }
static JSText *js_alloc_string (JSContext *ctx, int max_len); static JSText *js_alloc_string (JSContext *ctx, int max_len);
@@ -3376,46 +3359,54 @@ JSClassID JS_GetClassID (JSValue v) {
return REC_GET_CLASS_ID(rec); return REC_GET_CLASS_ID(rec);
} }
BOOL JS_IsRegisteredClass (JSRuntime *rt, JSClassID class_id) { BOOL JS_IsRegisteredClass (JSContext *ctx, JSClassID class_id) {
return (class_id < rt->class_count return (class_id < ctx->class_count
&& rt->class_array[class_id].class_id != 0); && ctx->class_array[class_id].class_id != 0);
} }
/* create a new object internal class. Return -1 if error, 0 if /* create a new object internal class. Return -1 if error, 0 if
OK. The finalizer can be NULL if none is needed. */ OK. The finalizer can be NULL if none is needed. */
static int JS_NewClass1 (JSRuntime *rt, JSClassID class_id, const JSClassDef *class_def, const char *name) { static int JS_NewClass1 (JSContext *ctx, JSClassID class_id, const JSClassDef *class_def, const char *name) {
int new_size; int new_size, i;
JSClass *cl, *new_class_array; JSClass *cl, *new_class_array;
JSValue *new_class_proto;
if (class_id >= (1 << 16)) return -1; if (class_id >= (1 << 16)) return -1;
if (class_id < rt->class_count && rt->class_array[class_id].class_id != 0) if (class_id < ctx->class_count && ctx->class_array[class_id].class_id != 0)
return -1; return -1;
if (class_id >= rt->class_count) { if (class_id >= ctx->class_count) {
new_size = max_int (JS_CLASS_INIT_COUNT, new_size = max_int (JS_CLASS_INIT_COUNT,
max_int (class_id + 1, rt->class_count * 3 / 2)); max_int (class_id + 1, ctx->class_count * 3 / 2));
/* reallocate the class array */ /* reallocate the class array */
new_class_array new_class_array
= js_realloc_rt (rt->class_array, sizeof (JSClass) * new_size); = js_realloc_rt (ctx->class_array, sizeof (JSClass) * new_size);
if (!new_class_array) return -1; if (!new_class_array) return -1;
memset (new_class_array + rt->class_count, 0, (new_size - rt->class_count) * sizeof (JSClass)); memset (new_class_array + ctx->class_count, 0, (new_size - ctx->class_count) * sizeof (JSClass));
rt->class_array = new_class_array; ctx->class_array = new_class_array;
rt->class_count = new_size;
/* reallocate the class proto array */
new_class_proto
= js_realloc_rt (ctx->class_proto, sizeof (JSValue) * new_size);
if (!new_class_proto) return -1;
for (i = ctx->class_count; i < new_size; i++)
new_class_proto[i] = JS_NULL;
ctx->class_proto = new_class_proto;
ctx->class_count = new_size;
} }
cl = &rt->class_array[class_id]; cl = &ctx->class_array[class_id];
cl->class_id = class_id; cl->class_id = class_id;
cl->class_name = name; /* name is already a const char* */ cl->class_name = name; /* name is already a const char* */
cl->finalizer = class_def->finalizer; cl->finalizer = class_def->finalizer;
cl->gc_mark = class_def->gc_mark; cl->gc_mark = class_def->gc_mark;
/* call field removed from JSClass - use class_def->call directly if needed
*/
return 0; return 0;
} }
int JS_NewClass (JSRuntime *rt, JSClassID class_id, const JSClassDef *class_def) { int JS_NewClass (JSContext *ctx, JSClassID class_id, const JSClassDef *class_def) {
/* class_name is stored directly as const char* */ /* class_name is stored directly as const char* */
return JS_NewClass1 (rt, class_id, class_def, class_def->class_name); return JS_NewClass1 (ctx, class_id, class_def, class_def->class_name);
} }
static JSValue js_new_string8_len (JSContext *ctx, const char *buf, int len) { static JSValue js_new_string8_len (JSContext *ctx, const char *buf, int len) {
@@ -4250,24 +4241,20 @@ void JS_DumpMemoryUsage (FILE *fp, const JSMemoryUsage *s, JSRuntime *rt) {
/* WARNING: obj is freed */ /* WARNING: obj is freed */
JSValue JS_Throw (JSContext *ctx, JSValue obj) { JSValue JS_Throw (JSContext *ctx, JSValue obj) {
JSRuntime *rt = ctx->rt; ctx->current_exception = obj;
rt->current_exception = obj;
/* uncatchable flag removed - not needed with copying GC */
return JS_EXCEPTION; return JS_EXCEPTION;
} }
/* return the pending exception (cannot be called twice). */ /* return the pending exception (cannot be called twice). */
JSValue JS_GetException (JSContext *ctx) { JSValue JS_GetException (JSContext *ctx) {
JSValue val; JSValue val = ctx->current_exception;
JSRuntime *rt = ctx->rt; ctx->current_exception = JS_UNINITIALIZED;
val = rt->current_exception;
rt->current_exception = JS_UNINITIALIZED;
return val; return val;
} }
JS_BOOL JS_BOOL
JS_HasException (JSContext *ctx) { JS_HasException (JSContext *ctx) {
return !JS_IsUninitialized (ctx->rt->current_exception); return !JS_IsUninitialized (ctx->current_exception);
} }
static void dbuf_put_leb128 (DynBuf *s, uint32_t v) { static void dbuf_put_leb128 (DynBuf *s, uint32_t v) {
@@ -4438,7 +4425,7 @@ static void build_backtrace (JSContext *ctx, JSValue error_obj, const char *file
return; return;
} }
} }
for (sf = ctx->rt->current_stack_frame; sf != NULL; sf = sf->prev_frame) { for (sf = ctx->current_stack_frame; sf != NULL; sf = sf->prev_frame) {
if (sf->js_mode & JS_MODE_BACKTRACE_BARRIER) break; if (sf->js_mode & JS_MODE_BACKTRACE_BARRIER) break;
if (backtrace_flags & JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL) { if (backtrace_flags & JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL) {
backtrace_flags &= ~JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL; backtrace_flags &= ~JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL;
@@ -4527,12 +4514,11 @@ static JSValue JS_ThrowError2 (JSContext *ctx, JSErrorEnum error_num, const char
} }
static JSValue JS_ThrowError (JSContext *ctx, JSErrorEnum error_num, const char *fmt, va_list ap) { static JSValue JS_ThrowError (JSContext *ctx, JSErrorEnum error_num, const char *fmt, va_list ap) {
JSRuntime *rt = ctx->rt;
JSStackFrame *sf; JSStackFrame *sf;
BOOL add_backtrace; BOOL add_backtrace;
/* the backtrace is added later if called from a bytecode function */ /* the backtrace is added later if called from a bytecode function */
sf = rt->current_stack_frame; sf = ctx->current_stack_frame;
add_backtrace = (!sf || (JS_GetFunctionBytecode (sf->cur_func) == NULL)); add_backtrace = (!sf || (JS_GetFunctionBytecode (sf->cur_func) == NULL));
return JS_ThrowError2 (ctx, error_num, fmt, ap, add_backtrace); return JS_ThrowError2 (ctx, error_num, fmt, ap, add_backtrace);
} }
@@ -4628,8 +4614,7 @@ static JSValue JS_ThrowReferenceErrorUninitialized2 (JSContext *ctx,
} }
static JSValue JS_ThrowTypeErrorInvalidClass (JSContext *ctx, int class_id) { static JSValue JS_ThrowTypeErrorInvalidClass (JSContext *ctx, int class_id) {
JSRuntime *rt = ctx->rt; const char *name = ctx->class_array[class_id].class_name;
const char *name = rt->class_array[class_id].class_name;
return JS_ThrowTypeError (ctx, "%s object expected", name ? name : "unknown"); return JS_ThrowTypeError (ctx, "%s object expected", name ? name : "unknown");
} }
@@ -4639,10 +4624,9 @@ static void JS_ThrowInterrupted (JSContext *ctx) {
} }
static no_inline __exception int __js_poll_interrupts (JSContext *ctx) { static no_inline __exception int __js_poll_interrupts (JSContext *ctx) {
JSRuntime *rt = ctx->rt;
ctx->interrupt_counter = JS_INTERRUPT_COUNTER_INIT; ctx->interrupt_counter = JS_INTERRUPT_COUNTER_INIT;
if (rt->interrupt_handler) { if (ctx->interrupt_handler) {
if (rt->interrupt_handler (rt, rt->interrupt_opaque)) { if (ctx->interrupt_handler (ctx->rt, ctx->interrupt_opaque)) {
JS_ThrowInterrupted (ctx); JS_ThrowInterrupted (ctx);
return -1; return -1;
} }
@@ -6609,7 +6593,6 @@ fail:
#define JS_CALL_FLAG_COPY_ARGV (1 << 1) #define JS_CALL_FLAG_COPY_ARGV (1 << 1)
static JSValue js_call_c_function (JSContext *ctx, JSValue func_obj, JSValue this_obj, int argc, JSValue *argv) { static JSValue js_call_c_function (JSContext *ctx, JSValue func_obj, JSValue this_obj, int argc, JSValue *argv) {
JSRuntime *rt = ctx->rt;
JSCFunctionType func; JSCFunctionType func;
JSFunction *f; JSFunction *f;
JSStackFrame sf_s, *sf = &sf_s, *prev_sf; JSStackFrame sf_s, *sf = &sf_s, *prev_sf;
@@ -6624,12 +6607,12 @@ static JSValue js_call_c_function (JSContext *ctx, JSValue func_obj, JSValue thi
arg_count = f->length; arg_count = f->length;
/* better to always check stack overflow */ /* better to always check stack overflow */
if (js_check_stack_overflow (rt, sizeof (arg_buf[0]) * arg_count)) if (js_check_stack_overflow (ctx, sizeof (arg_buf[0]) * arg_count))
return JS_ThrowStackOverflow (ctx); return JS_ThrowStackOverflow (ctx);
prev_sf = rt->current_stack_frame; prev_sf = ctx->current_stack_frame;
sf->prev_frame = prev_sf; sf->prev_frame = prev_sf;
rt->current_stack_frame = sf; ctx->current_stack_frame = sf;
sf->js_mode = 0; sf->js_mode = 0;
sf->cur_func = (JSValue)func_obj; sf->cur_func = (JSValue)func_obj;
sf->arg_count = argc; sf->arg_count = argc;
@@ -6728,7 +6711,7 @@ static JSValue js_call_c_function (JSContext *ctx, JSValue func_obj, JSValue thi
abort (); abort ();
} }
rt->current_stack_frame = sf->prev_frame; ctx->current_stack_frame = sf->prev_frame;
/* Restore value stack if we used it for arg padding */ /* Restore value stack if we used it for arg padding */
if (saved_vs_top >= 0) if (saved_vs_top >= 0)
@@ -6748,7 +6731,6 @@ typedef enum {
/* argv[] is modified if (flags & JS_CALL_FLAG_COPY_ARGV) = 0. */ /* argv[] is modified if (flags & JS_CALL_FLAG_COPY_ARGV) = 0. */
static JSValue JS_CallInternal (JSContext *caller_ctx, JSValue func_obj, JSValue this_obj, int argc, JSValue *argv, int flags) { static JSValue JS_CallInternal (JSContext *caller_ctx, JSValue func_obj, JSValue this_obj, int argc, JSValue *argv, int flags) {
JSRuntime *rt = caller_ctx->rt;
JSContext *ctx = caller_ctx; JSContext *ctx = caller_ctx;
JSFunction *f; JSFunction *f;
JSFunctionBytecode *b; JSFunctionBytecode *b;
@@ -6830,7 +6812,7 @@ static JSValue JS_CallInternal (JSContext *caller_ctx, JSValue func_obj, JSValue
alloca_size alloca_size
= sizeof (JSValue) * (arg_allocated_size + b->var_count + b->stack_size); = sizeof (JSValue) * (arg_allocated_size + b->var_count + b->stack_size);
if (js_check_stack_overflow (rt, alloca_size)) if (js_check_stack_overflow (ctx, alloca_size))
return JS_ThrowStackOverflow (caller_ctx); return JS_ThrowStackOverflow (caller_ctx);
sf->js_mode = b->js_mode; sf->js_mode = b->js_mode;
@@ -6877,8 +6859,8 @@ static JSValue JS_CallInternal (JSContext *caller_ctx, JSValue func_obj, JSValue
pc = b->byte_code_buf; pc = b->byte_code_buf;
sf->stack_buf = stack_buf; sf->stack_buf = stack_buf;
sf->p_sp = &sp; /* GC uses this to find current stack top */ sf->p_sp = &sp; /* GC uses this to find current stack top */
sf->prev_frame = rt->current_stack_frame; sf->prev_frame = ctx->current_stack_frame;
rt->current_stack_frame = sf; ctx->current_stack_frame = sf;
restart: restart:
for (;;) { for (;;) {
@@ -8579,12 +8561,12 @@ restart:
} }
} }
exception: exception:
if (is_backtrace_needed (ctx, rt->current_exception)) { if (is_backtrace_needed (ctx, ctx->current_exception)) {
/* add the backtrace information now (it is not done /* add the backtrace information now (it is not done
before if the exception happens in a bytecode before if the exception happens in a bytecode
operation */ operation */
sf->cur_pc = pc; sf->cur_pc = pc;
build_backtrace (ctx, rt->current_exception, NULL, 0, 0, 0); build_backtrace (ctx, ctx->current_exception, NULL, 0, 0, 0);
} }
/* All exceptions are catchable in the simplified runtime */ /* All exceptions are catchable in the simplified runtime */
while (sp > stack_buf) { while (sp > stack_buf) {
@@ -8592,8 +8574,8 @@ exception:
if (JS_VALUE_GET_TAG (val) == JS_TAG_CATCH_OFFSET) { if (JS_VALUE_GET_TAG (val) == JS_TAG_CATCH_OFFSET) {
int pos = JS_VALUE_GET_INT (val); int pos = JS_VALUE_GET_INT (val);
if (pos != 0) { if (pos != 0) {
*sp++ = rt->current_exception; *sp++ = ctx->current_exception;
rt->current_exception = JS_UNINITIALIZED; ctx->current_exception = JS_UNINITIALIZED;
pc = b->byte_code_buf + pos; pc = b->byte_code_buf + pos;
goto restart; goto restart;
} }
@@ -8601,7 +8583,7 @@ exception:
} }
ret_val = JS_EXCEPTION; ret_val = JS_EXCEPTION;
done: done:
rt->current_stack_frame = sf->prev_frame; ctx->current_stack_frame = sf->prev_frame;
if (unlikely (caller_ctx->trace_hook) if (unlikely (caller_ctx->trace_hook)
&& (caller_ctx->trace_type & JS_HOOK_RET)) && (caller_ctx->trace_type & JS_HOOK_RET))
@@ -9549,7 +9531,7 @@ static int js_parse_error_v (JSParseState *s, const uint8_t *ptr, const char *fm
int line_num, col_num; int line_num, col_num;
line_num = get_line_col (&col_num, s->buf_start, ptr - s->buf_start); line_num = get_line_col (&col_num, s->buf_start, ptr - s->buf_start);
JS_ThrowError2 (ctx, JS_SYNTAX_ERROR, fmt, ap, FALSE); JS_ThrowError2 (ctx, JS_SYNTAX_ERROR, fmt, ap, FALSE);
build_backtrace (ctx, ctx->rt->current_exception, s->filename, line_num + 1, col_num + 1, 0); build_backtrace (ctx, ctx->current_exception, s->filename, line_num + 1, col_num + 1, 0);
return -1; return -1;
} }
@@ -10098,7 +10080,7 @@ static __exception int next_token (JSParseState *s) {
BOOL ident_has_escape; BOOL ident_has_escape;
JSValue ident_str; JSValue ident_str;
if (js_check_stack_overflow (s->ctx->rt, 0)) { if (js_check_stack_overflow (s->ctx, 0)) {
return js_parse_error (s, "stack overflow"); return js_parse_error (s, "stack overflow");
} }
@@ -10711,7 +10693,7 @@ static __exception int json_next_token (JSParseState *s) {
int c; int c;
JSValue ident_str; JSValue ident_str;
if (js_check_stack_overflow (s->ctx->rt, 0)) { if (js_check_stack_overflow (s->ctx, 0)) {
return js_parse_error (s, "stack overflow"); return js_parse_error (s, "stack overflow");
} }
@@ -12490,7 +12472,7 @@ static __exception int js_parse_postfix_expr (JSParseState *s,
int line_num, col_num; int line_num, col_num;
line_num line_num
= get_line_col (&col_num, s->buf_start, s->token.ptr - s->buf_start); = get_line_col (&col_num, s->buf_start, s->token.ptr - s->buf_start);
build_backtrace (s->ctx, s->ctx->rt->current_exception, s->filename, line_num + 1, col_num + 1, 0); build_backtrace (s->ctx, s->ctx->current_exception, s->filename, line_num + 1, col_num + 1, 0);
return -1; return -1;
} }
ret = emit_push_const (s, str); ret = emit_push_const (s, str);
@@ -17479,7 +17461,7 @@ fail:
static int JS_WriteObjectRec (BCWriterState *s, JSValue obj) { static int JS_WriteObjectRec (BCWriterState *s, JSValue obj) {
uint32_t tag; uint32_t tag;
if (js_check_stack_overflow (s->ctx->rt, 0)) { if (js_check_stack_overflow (s->ctx, 0)) {
JS_ThrowStackOverflow (s->ctx); JS_ThrowStackOverflow (s->ctx);
return -1; return -1;
} }
@@ -18127,7 +18109,7 @@ static JSValue JS_ReadObjectRec (BCReaderState *s) {
uint8_t tag; uint8_t tag;
JSValue obj = JS_NULL; JSValue obj = JS_NULL;
if (js_check_stack_overflow (ctx->rt, 0)) return JS_ThrowStackOverflow (ctx); if (js_check_stack_overflow (ctx, 0)) return JS_ThrowStackOverflow (ctx);
if (bc_get_u8 (s, &tag)) return JS_EXCEPTION; if (bc_get_u8 (s, &tag)) return JS_EXCEPTION;
@@ -20129,14 +20111,13 @@ static JSValue js_regexp_toString (JSContext *ctx, JSValue this_val, int argc, J
int lre_check_stack_overflow (void *opaque, size_t alloca_size) { int lre_check_stack_overflow (void *opaque, size_t alloca_size) {
JSContext *ctx = opaque; JSContext *ctx = opaque;
return js_check_stack_overflow (ctx->rt, alloca_size); return js_check_stack_overflow (ctx, alloca_size);
} }
int lre_check_timeout (void *opaque) { int lre_check_timeout (void *opaque) {
JSContext *ctx = opaque; JSContext *ctx = opaque;
JSRuntime *rt = ctx->rt; return (ctx->interrupt_handler
return (rt->interrupt_handler && ctx->interrupt_handler (ctx->rt, ctx->interrupt_opaque));
&& rt->interrupt_handler (rt, rt->interrupt_opaque));
} }
void *lre_realloc (void *opaque, void *ptr, size_t size) { void *lre_realloc (void *opaque, void *ptr, size_t size) {
@@ -20653,7 +20634,7 @@ static JSValue internalize_json_property (JSContext *ctx, JSValue holder, JSValu
uint32_t i, len = 0; uint32_t i, len = 0;
JSValue prop; JSValue prop;
if (js_check_stack_overflow (ctx->rt, 0)) { if (js_check_stack_overflow (ctx, 0)) {
return JS_ThrowStackOverflow (ctx); return JS_ThrowStackOverflow (ctx);
} }
@@ -20808,7 +20789,7 @@ static int js_json_to_str (JSContext *ctx, JSONStringifyContext *jsc, JSValue ho
tab_ref.val = JS_NULL; tab_ref.val = JS_NULL;
prop_ref.val = JS_NULL; prop_ref.val = JS_NULL;
if (js_check_stack_overflow (ctx->rt, 0)) { if (js_check_stack_overflow (ctx, 0)) {
JS_ThrowStackOverflow (ctx); JS_ThrowStackOverflow (ctx);
goto exception; goto exception;
} }
@@ -26397,7 +26378,7 @@ static void JS_AddIntrinsicBaseObjects (JSContext *ctx) {
.class_name = "blob", .class_name = "blob",
.finalizer = js_blob_finalizer, .finalizer = js_blob_finalizer,
}; };
JS_NewClass (JS_GetRuntime (ctx), JS_CLASS_BLOB, &blob_class); JS_NewClass (ctx, JS_CLASS_BLOB, &blob_class);
ctx->class_proto[JS_CLASS_BLOB] = JS_NewObject (ctx); ctx->class_proto[JS_CLASS_BLOB] = JS_NewObject (ctx);
JS_SetPropertyFunctionList (ctx, ctx->class_proto[JS_CLASS_BLOB], js_blob_proto_funcs, countof (js_blob_proto_funcs)); JS_SetPropertyFunctionList (ctx, ctx->class_proto[JS_CLASS_BLOB], js_blob_proto_funcs, countof (js_blob_proto_funcs));
@@ -26559,7 +26540,7 @@ void js_debug_sethook (JSContext *ctx, js_hook hook, int type, void *user) {
uint32_t js_debugger_stack_depth (JSContext *ctx) { uint32_t js_debugger_stack_depth (JSContext *ctx) {
uint32_t stack_index = 0; uint32_t stack_index = 0;
JSStackFrame *sf = ctx->rt->current_stack_frame; JSStackFrame *sf = ctx->current_stack_frame;
while (sf != NULL) { while (sf != NULL) {
sf = sf->prev_frame; sf = sf->prev_frame;
stack_index++; stack_index++;
@@ -26572,7 +26553,7 @@ JSValue js_debugger_backtrace_fns (JSContext *ctx, const uint8_t *cur_pc) {
JSStackFrame *sf; JSStackFrame *sf;
uint32_t stack_index = 0; uint32_t stack_index = 0;
for (sf = ctx->rt->current_stack_frame; sf != NULL; sf = sf->prev_frame) { for (sf = ctx->current_stack_frame; sf != NULL; sf = sf->prev_frame) {
uint32_t id = stack_index++; uint32_t id = stack_index++;
JS_SetPropertyUint32 (ctx, ret, id, sf->cur_func); JS_SetPropertyUint32 (ctx, ret, id, sf->cur_func);
} }
@@ -26586,7 +26567,7 @@ JSValue js_debugger_build_backtrace (JSContext *ctx, const uint8_t *cur_pc) {
JSValue ret = JS_NewArray (ctx); JSValue ret = JS_NewArray (ctx);
uint32_t stack_index = 0; uint32_t stack_index = 0;
for (sf = ctx->rt->current_stack_frame; sf != NULL; sf = sf->prev_frame) { for (sf = ctx->current_stack_frame; sf != NULL; sf = sf->prev_frame) {
JSValue current_frame = JS_NewObject (ctx); JSValue current_frame = JS_NewObject (ctx);
uint32_t id = stack_index++; uint32_t id = stack_index++;
@@ -26607,7 +26588,7 @@ JSValue js_debugger_build_backtrace (JSContext *ctx, const uint8_t *cur_pc) {
b = f->u.func.function_bytecode; b = f->u.func.function_bytecode;
if (b->has_debug) { if (b->has_debug) {
const uint8_t *pc = sf != ctx->rt->current_stack_frame || !cur_pc const uint8_t *pc = sf != ctx->current_stack_frame || !cur_pc
? sf->cur_pc ? sf->cur_pc
: cur_pc; : cur_pc;
int col_num; int col_num;
@@ -27099,7 +27080,7 @@ JSValue js_debugger_local_variables (JSContext *ctx, int stack_index) {
JSStackFrame *sf; JSStackFrame *sf;
int cur_index = 0; int cur_index = 0;
for (sf = ctx->rt->current_stack_frame; sf != NULL; sf = sf->prev_frame) { for (sf = ctx->current_stack_frame; sf != NULL; sf = sf->prev_frame) {
// this val is one frame up // this val is one frame up
if (cur_index == stack_index - 1) { if (cur_index == stack_index - 1) {
if (js_is_bytecode_function (sf->cur_func)) { if (js_is_bytecode_function (sf->cur_func)) {
@@ -31675,6 +31656,8 @@ char *JS_Tokenize (const char *source, size_t len, const char *filename) {
s.function_nr = 0; s.function_nr = 0;
s.errors = NULL; s.errors = NULL;
s.has_error = 0; s.has_error = 0;
s.lc_cache.ptr = s.buf_start;
s.lc_cache.buf_start = s.buf_start;
cJSON *root = cJSON_CreateObject (); cJSON *root = cJSON_CreateObject ();
cJSON_AddStringToObject (root, "filename", filename); cJSON_AddStringToObject (root, "filename", filename);
@@ -33876,11 +33859,10 @@ static JSValue reg_vm_binop(JSContext *ctx, int op, JSValue a, JSValue b) {
/* Check for interrupt */ /* Check for interrupt */
static int reg_vm_check_interrupt(JSContext *ctx) { static int reg_vm_check_interrupt(JSContext *ctx) {
JSRuntime *rt = ctx->rt;
if (--ctx->interrupt_counter <= 0) { if (--ctx->interrupt_counter <= 0) {
ctx->interrupt_counter = JS_INTERRUPT_COUNTER_INIT; ctx->interrupt_counter = JS_INTERRUPT_COUNTER_INIT;
if (rt->interrupt_handler) { if (ctx->interrupt_handler) {
if (rt->interrupt_handler(rt, rt->interrupt_opaque)) { if (ctx->interrupt_handler(ctx->rt, ctx->interrupt_opaque)) {
return -1; return -1;
} }
} }
@@ -33894,7 +33876,7 @@ void __asan_on_error(void) {
if (!ctx) return; if (!ctx) return;
if (JS_IsNull(ctx->reg_current_frame)) return; if (JS_IsNull(ctx->reg_current_frame)) return;
JSFrameRegister *frame = (JSFrameRegister *)JS_VALUE_GET_PTR(ctx->reg_current_frame); JSFrameRegister *frame = (JSFrameRegister *)JS_VALUE_GET_PTR(ctx->reg_current_frame);
uint32_t cur_pc = ctx->rt->current_register_pc; uint32_t cur_pc = ctx->current_register_pc;
fprintf(stderr, "\n=== ASAN error: VM stack trace ===\n"); fprintf(stderr, "\n=== ASAN error: VM stack trace ===\n");
int is_first = 1; int is_first = 1;
while (frame) { while (frame) {
@@ -34018,7 +34000,7 @@ static JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
MachInstr32 instr = code->instructions[pc++]; MachInstr32 instr = code->instructions[pc++];
ctx->reg_current_frame = frame_ref.val; ctx->reg_current_frame = frame_ref.val;
ctx->rt->current_register_pc = pc > 0 ? pc - 1 : 0; ctx->current_register_pc = pc > 0 ? pc - 1 : 0;
int op = MACH_GET_OP(instr); int op = MACH_GET_OP(instr);
int a = MACH_GET_A(instr); int a = MACH_GET_A(instr);
int b = MACH_GET_B(instr); int b = MACH_GET_B(instr);
@@ -34347,7 +34329,7 @@ static JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
ctx->value_stack[vs_base + i] = frame->slots[base + 1 + i]; ctx->value_stack[vs_base + i] = frame->slots[base + 1 + i];
ctx->value_stack_top = vs_base + nargs; ctx->value_stack_top = vs_base + nargs;
ctx->reg_current_frame = frame_ref.val; ctx->reg_current_frame = frame_ref.val;
ctx->rt->current_register_pc = pc > 0 ? pc - 1 : 0; ctx->current_register_pc = pc > 0 ? pc - 1 : 0;
JSValue ret = js_call_c_function(ctx, func_val, JS_NULL, nargs, &ctx->value_stack[vs_base]); JSValue ret = js_call_c_function(ctx, func_val, JS_NULL, nargs, &ctx->value_stack[vs_base]);
ctx->value_stack_top = vs_base; ctx->value_stack_top = vs_base;
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
@@ -34423,7 +34405,7 @@ static JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
ctx->value_stack[vs_base + 1] = frame->slots[base + 1]; /* the array */ ctx->value_stack[vs_base + 1] = frame->slots[base + 1]; /* the array */
ctx->value_stack_top = vs_base + 2; ctx->value_stack_top = vs_base + 2;
ctx->reg_current_frame = frame_ref.val; ctx->reg_current_frame = frame_ref.val;
ctx->rt->current_register_pc = pc > 0 ? pc - 1 : 0; ctx->current_register_pc = pc > 0 ? pc - 1 : 0;
JSValue ret = JS_CallInternal(ctx, frame->slots[base], JS_NULL, 2, &ctx->value_stack[vs_base], 0); JSValue ret = JS_CallInternal(ctx, frame->slots[base], JS_NULL, 2, &ctx->value_stack[vs_base], 0);
ctx->value_stack_top = vs_base; ctx->value_stack_top = vs_base;
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
@@ -34452,7 +34434,7 @@ static JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
ctx->value_stack[vs_base + i] = frame->slots[base + 2 + i]; ctx->value_stack[vs_base + i] = frame->slots[base + 2 + i];
ctx->value_stack_top = vs_base + nargs; ctx->value_stack_top = vs_base + nargs;
ctx->reg_current_frame = frame_ref.val; ctx->reg_current_frame = frame_ref.val;
ctx->rt->current_register_pc = pc > 0 ? pc - 1 : 0; ctx->current_register_pc = pc > 0 ? pc - 1 : 0;
JSValue ret = js_call_c_function(ctx, method, frame->slots[base], nargs, &ctx->value_stack[vs_base]); JSValue ret = js_call_c_function(ctx, method, frame->slots[base], nargs, &ctx->value_stack[vs_base]);
ctx->value_stack_top = vs_base; ctx->value_stack_top = vs_base;
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
@@ -34686,7 +34668,7 @@ done:
ctx->reg_current_frame = JS_NULL; ctx->reg_current_frame = JS_NULL;
if (JS_IsException(result)) { if (JS_IsException(result)) {
ctx->reg_current_frame = frame_ref.val; ctx->reg_current_frame = frame_ref.val;
ctx->rt->current_register_pc = pc > 0 ? pc - 1 : 0; ctx->current_register_pc = pc > 0 ? pc - 1 : 0;
} }
JS_DeleteGCRef(ctx, &frame_ref); JS_DeleteGCRef(ctx, &frame_ref);
return result; return result;
@@ -37606,7 +37588,7 @@ static JSValue mcode_exec(JSContext *ctx, JSMCode *code, JSValue this_obj,
} }
ctx->value_stack_top = vs_base + c_argc; ctx->value_stack_top = vs_base + c_argc;
ctx->reg_current_frame = frame_ref.val; ctx->reg_current_frame = frame_ref.val;
ctx->rt->current_register_pc = pc > 0 ? pc - 1 : 0; ctx->current_register_pc = pc > 0 ? pc - 1 : 0;
JSValue c_result = JS_Call(ctx, new_frame->function, new_frame->slots[0], c_argc, &ctx->value_stack[vs_base]); JSValue c_result = JS_Call(ctx, new_frame->function, new_frame->slots[0], c_argc, &ctx->value_stack[vs_base]);
ctx->value_stack_top = vs_base; ctx->value_stack_top = vs_base;
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
@@ -37651,7 +37633,7 @@ static JSValue mcode_exec(JSContext *ctx, JSMCode *code, JSValue this_obj,
ctx->value_stack[vs_base + 1] = frame->slots[dest]; ctx->value_stack[vs_base + 1] = frame->slots[dest];
ctx->value_stack_top = vs_base + 2; ctx->value_stack_top = vs_base + 2;
ctx->reg_current_frame = frame_ref.val; ctx->reg_current_frame = frame_ref.val;
ctx->rt->current_register_pc = pc > 0 ? pc - 1 : 0; ctx->current_register_pc = pc > 0 ? pc - 1 : 0;
JSValue ret = JS_CallInternal(ctx, frame->slots[obj_reg], JS_NULL, 2, &ctx->value_stack[vs_base], 0); JSValue ret = JS_CallInternal(ctx, frame->slots[obj_reg], JS_NULL, 2, &ctx->value_stack[vs_base], 0);
ctx->value_stack_top = vs_base; ctx->value_stack_top = vs_base;
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
@@ -37703,7 +37685,7 @@ static JSValue mcode_exec(JSContext *ctx, JSMCode *code, JSValue this_obj,
} }
ctx->value_stack_top = vs_base + nargs; ctx->value_stack_top = vs_base + nargs;
ctx->reg_current_frame = frame_ref.val; ctx->reg_current_frame = frame_ref.val;
ctx->rt->current_register_pc = pc > 0 ? pc - 1 : 0; ctx->current_register_pc = pc > 0 ? pc - 1 : 0;
JSValue ret = JS_Call(ctx, method, frame->slots[obj_reg], nargs, &ctx->value_stack[vs_base]); JSValue ret = JS_Call(ctx, method, frame->slots[obj_reg], nargs, &ctx->value_stack[vs_base]);
ctx->value_stack_top = vs_base; ctx->value_stack_top = vs_base;
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
@@ -37747,7 +37729,7 @@ static JSValue mcode_exec(JSContext *ctx, JSMCode *code, JSValue this_obj,
ctx->value_stack[vs_base + 1] = frame->slots[dest]; ctx->value_stack[vs_base + 1] = frame->slots[dest];
ctx->value_stack_top = vs_base + 2; ctx->value_stack_top = vs_base + 2;
ctx->reg_current_frame = frame_ref.val; ctx->reg_current_frame = frame_ref.val;
ctx->rt->current_register_pc = pc > 0 ? pc - 1 : 0; ctx->current_register_pc = pc > 0 ? pc - 1 : 0;
JSValue ret = JS_CallInternal(ctx, frame->slots[obj_reg], JS_NULL, 2, &ctx->value_stack[vs_base], 0); JSValue ret = JS_CallInternal(ctx, frame->slots[obj_reg], JS_NULL, 2, &ctx->value_stack[vs_base], 0);
ctx->value_stack_top = vs_base; ctx->value_stack_top = vs_base;
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
@@ -37797,7 +37779,7 @@ static JSValue mcode_exec(JSContext *ctx, JSMCode *code, JSValue this_obj,
} }
ctx->value_stack_top = vs_base + nargs; ctx->value_stack_top = vs_base + nargs;
ctx->reg_current_frame = frame_ref.val; ctx->reg_current_frame = frame_ref.val;
ctx->rt->current_register_pc = pc > 0 ? pc - 1 : 0; ctx->current_register_pc = pc > 0 ? pc - 1 : 0;
JSValue ret = JS_Call(ctx, method, frame->slots[obj_reg], nargs, &ctx->value_stack[vs_base]); JSValue ret = JS_Call(ctx, method, frame->slots[obj_reg], nargs, &ctx->value_stack[vs_base]);
ctx->value_stack_top = vs_base; ctx->value_stack_top = vs_base;
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
@@ -37897,7 +37879,7 @@ static JSValue mcode_exec(JSContext *ctx, JSMCode *code, JSValue this_obj,
} }
ctx->value_stack_top = vs_base + len; ctx->value_stack_top = vs_base + len;
ctx->reg_current_frame = frame_ref.val; ctx->reg_current_frame = frame_ref.val;
ctx->rt->current_register_pc = pc > 0 ? pc - 1 : 0; ctx->current_register_pc = pc > 0 ? pc - 1 : 0;
result = JS_Call(ctx, frame->slots[func_slot], JS_NULL, len, &ctx->value_stack[vs_base]); result = JS_Call(ctx, frame->slots[func_slot], JS_NULL, len, &ctx->value_stack[vs_base]);
ctx->value_stack_top = vs_base; ctx->value_stack_top = vs_base;
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
@@ -38091,7 +38073,7 @@ static JSValue mcode_exec(JSContext *ctx, JSMCode *code, JSValue this_obj,
done: done:
if (JS_IsException(result)) { if (JS_IsException(result)) {
ctx->reg_current_frame = frame_ref.val; ctx->reg_current_frame = frame_ref.val;
ctx->rt->current_register_pc = pc > 0 ? pc - 1 : 0; ctx->current_register_pc = pc > 0 ? pc - 1 : 0;
} }
JS_DeleteGCRef(ctx, &frame_ref); JS_DeleteGCRef(ctx, &frame_ref);
return result; return result;
@@ -38099,10 +38081,9 @@ done:
/* Public API: get stack trace as cJSON array */ /* Public API: get stack trace as cJSON array */
cJSON *JS_GetStack(JSContext *ctx) { cJSON *JS_GetStack(JSContext *ctx) {
JSRuntime *rt = ctx->rt;
if (JS_IsNull(ctx->reg_current_frame)) return NULL; if (JS_IsNull(ctx->reg_current_frame)) return NULL;
JSFrameRegister *frame = (JSFrameRegister *)JS_VALUE_GET_PTR(ctx->reg_current_frame); JSFrameRegister *frame = (JSFrameRegister *)JS_VALUE_GET_PTR(ctx->reg_current_frame);
uint32_t cur_pc = rt->current_register_pc; uint32_t cur_pc = ctx->current_register_pc;
cJSON *arr = cJSON_CreateArray(); cJSON *arr = cJSON_CreateArray();
int is_first = 1; int is_first = 1;

View File

@@ -367,10 +367,10 @@ JSRuntime *JS_NewRuntime (void);
void JS_SetRuntimeInfo (JSRuntime *rt, const char *info); void JS_SetRuntimeInfo (JSRuntime *rt, const char *info);
void JS_SetMemoryLimit (JSRuntime *rt, size_t limit); void JS_SetMemoryLimit (JSRuntime *rt, size_t limit);
/* use 0 to disable maximum stack size check */ /* use 0 to disable maximum stack size check */
void JS_SetMaxStackSize (JSRuntime *rt, size_t stack_size); void JS_SetMaxStackSize (JSContext *ctx, size_t stack_size);
/* should be called when changing thread to update the stack top value /* should be called when changing thread to update the stack top value
used to check stack overflow. */ used to check stack overflow. */
void JS_UpdateStackTop (JSRuntime *rt); void JS_UpdateStackTop (JSContext *ctx);
void JS_FreeRuntime (JSRuntime *rt); void JS_FreeRuntime (JSRuntime *rt);
void *JS_GetRuntimeOpaque (JSRuntime *rt); void *JS_GetRuntimeOpaque (JSRuntime *rt);
void JS_SetRuntimeOpaque (JSRuntime *rt, void *opaque); void JS_SetRuntimeOpaque (JSRuntime *rt, void *opaque);
@@ -429,9 +429,9 @@ JSClassID JS_NewClassID (JSClassID *pclass_id);
/* Returns the class ID if `v` is an object, otherwise returns /* Returns the class ID if `v` is an object, otherwise returns
* JS_INVALID_CLASS_ID. */ * JS_INVALID_CLASS_ID. */
JSClassID JS_GetClassID (JSValue v); JSClassID JS_GetClassID (JSValue v);
int JS_NewClass (JSRuntime *rt, JSClassID class_id, int JS_NewClass (JSContext *ctx, JSClassID class_id,
const JSClassDef *class_def); const JSClassDef *class_def);
int JS_IsRegisteredClass (JSRuntime *rt, JSClassID class_id); int JS_IsRegisteredClass (JSContext *ctx, JSClassID class_id);
extern JSClassID js_class_id_alloc; extern JSClassID js_class_id_alloc;
@@ -740,7 +740,7 @@ JSValue JS_JSONStringify (JSContext *ctx, JSValue obj,
/* return != 0 if the JS code needs to be interrupted */ /* return != 0 if the JS code needs to be interrupted */
typedef int JSInterruptHandler (JSRuntime *rt, void *opaque); typedef int JSInterruptHandler (JSRuntime *rt, void *opaque);
void JS_SetInterruptHandler (JSRuntime *rt, JSInterruptHandler *cb, void JS_SetInterruptHandler (JSContext *ctx, JSInterruptHandler *cb,
void *opaque); void *opaque);
/* select which debug info is stripped from the compiled code */ /* select which debug info is stripped from the compiled code */
#define JS_STRIP_SOURCE (1 << 0) /* strip source code */ #define JS_STRIP_SOURCE (1 << 0) /* strip source code */

View File

@@ -266,11 +266,11 @@ void actor_free(cell_rt *actor)
JSContext *js = actor->context; JSContext *js = actor->context;
JS_FreeValue(js, actor->idx_buffer); JS_DeleteGCRef(js, &actor->idx_buffer_ref);
JS_FreeValue(js, actor->message_handle); JS_DeleteGCRef(js, &actor->on_exception_ref);
JS_FreeValue(js, actor->on_exception); JS_DeleteGCRef(js, &actor->message_handle_ref);
JS_FreeValue(js, actor->unneeded); JS_DeleteGCRef(js, &actor->unneeded_ref);
JS_FreeValue(js, actor->actor_sym); JS_DeleteGCRef(js, &actor->actor_sym_ref);
for (int i = 0; i < hmlen(actor->timers); i++) { for (int i = 0; i < hmlen(actor->timers); i++) {
JS_FreeValue(js, actor->timers[i].value); JS_FreeValue(js, actor->timers[i].value);
@@ -289,10 +289,8 @@ void actor_free(cell_rt *actor)
arrfree(actor->letters); arrfree(actor->letters);
JSRuntime *rt = JS_GetRuntime(js); JS_SetInterruptHandler(js, NULL, NULL);
JS_SetInterruptHandler(rt, NULL, NULL);
JS_FreeContext(js); JS_FreeContext(js);
JS_FreeRuntime(rt);
free(actor->id); free(actor->id);
pthread_mutex_unlock(actor->mutex); pthread_mutex_unlock(actor->mutex);
@@ -419,8 +417,8 @@ uint32_t actor_remove_cb(cell_rt *actor, uint32_t id, uint32_t interval)
actor->disrupt = 1; actor->disrupt = 1;
if (!JS_IsNull(actor->unneeded)) { if (!JS_IsNull(actor->unneeded_ref.val)) {
JSValue ret = JS_Call(actor->context, actor->unneeded, JS_NULL, 0, NULL); JSValue ret = JS_Call(actor->context, actor->unneeded_ref.val, JS_NULL, 0, NULL);
uncaught_exception(actor->context, ret); uncaught_exception(actor->context, ret);
} }
@@ -434,14 +432,13 @@ uint32_t actor_remove_cb(cell_rt *actor, uint32_t id, uint32_t interval)
void actor_unneeded(cell_rt *actor, JSValue fn, double seconds) void actor_unneeded(cell_rt *actor, JSValue fn, double seconds)
{ {
if (actor->disrupt) return; if (actor->disrupt) return;
JS_FreeValue(actor->context, actor->unneeded);
if (!JS_IsFunction(fn)) { if (!JS_IsFunction(fn)) {
actor->unneeded = JS_NULL; actor->unneeded_ref.val = JS_NULL;
goto END; goto END;
} }
actor->unneeded = JS_DupValue(actor->context, fn); actor->unneeded_ref.val = fn;
actor->ar_secs = seconds; actor->ar_secs = seconds;
END: END:
@@ -493,11 +490,10 @@ cell_rt *create_actor(void *wota)
actor->heap = mi_heap_new(); actor->heap = mi_heap_new();
#endif #endif
actor->init_wota = wota; actor->init_wota = wota;
actor->idx_buffer = JS_NULL; /* GCRef fields are registered after JSContext creation in script_startup.
actor->message_handle = JS_NULL; For now, zero-init from calloc is sufficient (val = 0 = JS_MKVAL(JS_TAG_INT,0),
actor->unneeded = JS_NULL; which is not a pointer so GC-safe). The actual JS_NULL assignment and
actor->on_exception = JS_NULL; JS_AddGCRef happen in script_startup. */
actor->actor_sym = JS_NULL;
arrsetcap(actor->letters, 5); arrsetcap(actor->letters, 5);
@@ -587,7 +583,7 @@ void actor_turn(cell_rt *actor)
size_t size = blob_length(l.blob_data) / 8; // Convert bits to bytes size_t size = blob_length(l.blob_data) / 8; // Convert bits to bytes
JSValue arg = js_new_blob_stoned_copy(actor->context, (void*)blob_data(l.blob_data), size); JSValue arg = js_new_blob_stoned_copy(actor->context, (void*)blob_data(l.blob_data), size);
blob_destroy(l.blob_data); blob_destroy(l.blob_data);
result = JS_Call(actor->context, actor->message_handle, JS_NULL, 1, &arg); result = JS_Call(actor->context, actor->message_handle_ref.val, JS_NULL, 1, &arg);
uncaught_exception(actor->context, result); uncaught_exception(actor->context, result);
JS_FreeValue(actor->context, arg); JS_FreeValue(actor->context, arg);
} else if (l.type == LETTER_CALLBACK) { } else if (l.type == LETTER_CALLBACK) {

View File

@@ -118,11 +118,11 @@ void actor_free(cell_rt *actor)
JSContext *js = actor->context; JSContext *js = actor->context;
JS_FreeValue(js, actor->idx_buffer); JS_DeleteGCRef(js, &actor->idx_buffer_ref);
JS_FreeValue(js, actor->message_handle); JS_DeleteGCRef(js, &actor->on_exception_ref);
JS_FreeValue(js, actor->on_exception); JS_DeleteGCRef(js, &actor->message_handle_ref);
JS_FreeValue(js, actor->unneeded); JS_DeleteGCRef(js, &actor->unneeded_ref);
JS_FreeValue(js, actor->actor_sym); JS_DeleteGCRef(js, &actor->actor_sym_ref);
/* Free timer callbacks stored in actor */ /* Free timer callbacks stored in actor */
for (int i = 0; i < hmlen(actor->timers); i++) { for (int i = 0; i < hmlen(actor->timers); i++) {
@@ -142,10 +142,8 @@ void actor_free(cell_rt *actor)
arrfree(actor->letters); arrfree(actor->letters);
JSRuntime *rt = JS_GetRuntime(js); JS_SetInterruptHandler(js, NULL, NULL);
JS_SetInterruptHandler(rt, NULL, NULL);
JS_FreeContext(js); JS_FreeContext(js);
JS_FreeRuntime(rt);
free(actor->id); free(actor->id);
free(actor); free(actor);
@@ -157,14 +155,13 @@ void actor_free(cell_rt *actor)
void actor_unneeded(cell_rt *actor, JSValue fn, double seconds) void actor_unneeded(cell_rt *actor, JSValue fn, double seconds)
{ {
if (actor->disrupt) return; if (actor->disrupt) return;
JS_FreeValue(actor->context, actor->unneeded);
if (!JS_IsFunction(fn)) {
if (!JS_IsFunction(actor->context, fn)) { actor->unneeded_ref.val = JS_NULL;
actor->unneeded = JS_NULL;
goto END; goto END;
} }
actor->unneeded = JS_DupValue(actor->context, fn); actor->unneeded_ref.val = fn;
actor->ar_secs = seconds; actor->ar_secs = seconds;
END: END:
@@ -257,8 +254,8 @@ uint32_t actor_remove_cb(cell_rt *actor, uint32_t id, uint32_t interval)
actor->disrupt = 1; actor->disrupt = 1;
if (!JS_IsNull(actor->unneeded)) { if (!JS_IsNull(actor->unneeded_ref.val)) {
JSValue ret = JS_Call(actor->context, actor->unneeded, JS_NULL, 0, NULL); JSValue ret = JS_Call(actor->context, actor->unneeded_ref.val, JS_NULL, 0, NULL);
uncaught_exception(actor->context, ret); uncaught_exception(actor->context, ret);
} }
@@ -328,11 +325,7 @@ cell_rt *create_actor(void *wota)
{ {
cell_rt *actor = calloc(sizeof(*actor), 1); cell_rt *actor = calloc(sizeof(*actor), 1);
actor->init_wota = wota; actor->init_wota = wota;
actor->idx_buffer = JS_NULL; /* GCRef fields are registered after JSContext creation in script_startup. */
actor->message_handle = JS_NULL;
actor->unneeded = JS_NULL;
actor->on_exception = JS_NULL;
actor->actor_sym = JS_NULL;
arrsetcap(actor->letters, 5); arrsetcap(actor->letters, 5);
@@ -408,7 +401,7 @@ void actor_turn(cell_rt *actor)
size_t size = blob_length(l.blob_data) / 8; // Convert bits to bytes size_t size = blob_length(l.blob_data) / 8; // Convert bits to bytes
JSValue arg = js_new_blob_stoned_copy(actor->context, (void *)blob_data(l.blob_data), size); JSValue arg = js_new_blob_stoned_copy(actor->context, (void *)blob_data(l.blob_data), size);
blob_destroy(l.blob_data); blob_destroy(l.blob_data);
result = JS_Call(actor->context, actor->message_handle, JS_NULL, 1, &arg); result = JS_Call(actor->context, actor->message_handle_ref.val, JS_NULL, 1, &arg);
uncaught_exception(actor->context, result); uncaught_exception(actor->context, result);
JS_FreeValue(actor->context, arg); JS_FreeValue(actor->context, arg);
} else if (l.type == LETTER_CALLBACK) { } else if (l.type == LETTER_CALLBACK) {

92
test.ce
View File

@@ -29,12 +29,13 @@ function is_valid_package(dir) {
// Get current package name from cell.toml or null // Get current package name from cell.toml or null
function get_current_package_name() { function get_current_package_name() {
if (!is_valid_package('.')) return null if (!is_valid_package('.')) return null
try { var pkg_name = 'local'
var do_load = function() {
var config = pkg.load_config(null) var config = pkg.load_config(null)
return config.package || 'local' if (config.package) pkg_name = config.package
} catch (e) { } disruption {}
return 'local' do_load()
} return pkg_name
} }
// Parse arguments // Parse arguments
@@ -229,21 +230,48 @@ function spawn_actor_test(test_info) {
actor: null actor: null
} }
try { var spawn_disrupted = false
var do_spawn = function() {
// Spawn the actor test - it should send back results // Spawn the actor test - it should send back results
var actor_path = text(test_info.path, 0, -3) // remove .ce var actor_path = text(test_info.path, 0, -3) // remove .ce
entry.actor = $start(actor_path) entry.actor = $start(actor_path)
push(pending_actor_tests, entry) push(pending_actor_tests, entry)
} catch (e) { } disruption {
spawn_disrupted = true
}
do_spawn()
if (spawn_disrupted) {
entry.status = "failed" entry.status = "failed"
entry.error = { message: `Failed to spawn actor: ${e}` } entry.error = { message: `Failed to spawn actor: ${test_name}` }
entry.duration_ns = 0 entry.duration_ns = 0
push(actor_test_results, entry) push(actor_test_results, entry)
log.console(` FAIL ${test_name}: `) log.console(` FAIL ${test_name}`)
log.error(e)
} }
} }
// Test runner with disruption support
var test_passed = true
var test_error_msg = ""
var test_error_stack = ""
var run_test = function(fn) {
test_passed = true
test_error_msg = ""
test_error_stack = ""
var ret = fn()
if (is_text(ret)) {
test_passed = false
test_error_msg = ret
} else if (ret && (is_text(ret.message) || is_proto(ret, Error))) {
test_passed = false
test_error_msg = ret.message || text(ret)
if (ret.stack) test_error_stack = ret.stack
}
} disruption {
test_passed = false
if (test_error_msg == "") test_error_msg = "test disrupted"
}
function run_tests(package_name, specific_test) { function run_tests(package_name, specific_test) {
var prefix = get_pkg_dir(package_name) var prefix = get_pkg_dir(package_name)
var tests_dir = prefix + '/tests' var tests_dir = prefix + '/tests'
@@ -293,7 +321,9 @@ function run_tests(package_name, specific_test) {
failed: 0 failed: 0
} }
try { var load_disrupted = false
var load_error_msg = ""
var do_load = function() {
var test_mod var test_mod
// For local packages (null), use the current directory as package context // For local packages (null), use the current directory as package context
var use_pkg = package_name ? package_name : fd.realpath('.') var use_pkg = package_name ? package_name : fd.realpath('.')
@@ -322,34 +352,23 @@ function run_tests(package_name, specific_test) {
} }
var start_time = time.number() var start_time = time.number()
try { run_test(t.fn)
var ret = t.fn()
if (is_text(ret)) {
throw Error(ret)
} else if (ret && (is_text(ret.message) || is_proto(ret, Error))) {
throw ret
}
if (test_passed) {
test_entry.status = "passed" test_entry.status = "passed"
log.console(` PASS ${t.name}`) log.console(` PASS ${t.name}`)
pkg_result.passed++ pkg_result.passed++
file_result.passed++ file_result.passed++
} catch (e) { } else {
test_entry.status = "failed" test_entry.status = "failed"
test_entry.error = { test_entry.error = {
message: e, message: test_error_msg
stack: e.stack || ""
} }
if (e.name) test_entry.error.name = e.name if (test_error_stack) test_entry.error.stack = test_error_stack
if (is_object(e) && e.message) { log.console(` FAIL ${t.name} ${test_error_msg}`)
test_entry.error.message = e.message if (test_error_stack) {
} log.console(` ${text(array(test_error_stack, '\n'), '\n ')}`)
log.console(` FAIL ${t.name} ${test_entry.error.message}`)
if (test_entry.error.stack) {
log.console(` ${text(array(test_entry.error.stack, '\n'), '\n ')}`)
} }
pkg_result.failed++ pkg_result.failed++
@@ -365,15 +384,18 @@ function run_tests(package_name, specific_test) {
} }
} }
} }
} disruption {
} catch (e) { load_disrupted = true
log.console(` Error loading ${f}: ${e}`) }
var test_entry = { do_load()
if (load_disrupted) {
log.console(` Error loading ${f}`)
var test_entry = {
package: pkg_result.package, package: pkg_result.package,
test: "load_module", test: "load_module",
status: "failed", status: "failed",
duration_ns: 0, duration_ns: 0,
error: { message: `Error loading module: ${e}` } error: { message: `Error loading module: ${f}` }
} }
push(file_result.tests, test_entry) push(file_result.tests, test_entry)
pkg_result.failed++ pkg_result.failed++

View File

@@ -4,64 +4,65 @@ var os = use('os');
function assert(condition, message) { function assert(condition, message) {
if (!condition) { if (!condition) {
throw Error(message || "Assertion failed"); return message || "Assertion failed"
} }
} }
function assertEqual(actual, expected, message) { function assertEqual(actual, expected, message) {
if (actual != expected) { if (actual != expected) {
throw Error(message || "Expected " + expected + ", got " + actual); return message || "Expected " + expected + ", got " + actual
} }
} }
function should_disrupt(fn) {
var caught = false
var wrapper = function() { fn() } disruption { caught = true }
wrapper()
return caught
}
return { return {
test_create_empty_blob: function() { test_create_empty_blob: function() {
var b = Blob(); var b = Blob();
assertEqual(length(b), 0, "Empty blob should have length 0"); return assertEqual(length(b), 0, "Empty blob should have length 0")
}, },
test_create_blob_with_capacity: function() { test_create_blob_with_capacity: function() {
var b = Blob(100); var b = Blob(100);
assertEqual(length(b), 0, "New blob with capacity should still have length 0"); return assertEqual(length(b), 0, "New blob with capacity should still have length 0")
}, },
test_write_and_read_single_bit: function() { test_write_and_read_single_bit: function() {
var b = Blob(); var b = Blob();
b.write_bit(true); b.write_bit(true);
b.write_bit(false); b.write_bit(false);
b.write_bit(1); b.write_bit(1);
b.write_bit(0); b.write_bit(0);
assertEqual(length(b), 4, "Should have 4 bits after writing"); var r = assertEqual(length(b), 4, "Should have 4 bits after writing")
if (r) return r
stone(b); stone(b);
assertEqual(b.read_logical(0), true, "First bit should be true"); r = assertEqual(b.read_logical(0), true, "First bit should be true")
assertEqual(b.read_logical(1), false, "Second bit should be false"); if (r) return r
assertEqual(b.read_logical(2), true, "Third bit should be true (1)"); r = assertEqual(b.read_logical(1), false, "Second bit should be false")
assertEqual(b.read_logical(3), false, "Fourth bit should be false (0)"); if (r) return r
r = assertEqual(b.read_logical(2), true, "Third bit should be true (1)")
if (r) return r
return assertEqual(b.read_logical(3), false, "Fourth bit should be false (0)")
}, },
test_out_of_range_read_throws_error: function() { test_out_of_range_read_throws_error: function() {
var b = Blob(); var b = Blob();
b.write_bit(true); b.write_bit(true);
stone(b); stone(b);
var threw = false; if (!should_disrupt(function() { b.read_logical(100) }))
try { return "Out of range read should disrupt"
b.read_logical(100);
} catch (e) { if (!should_disrupt(function() { b.read_logical(-1) }))
threw = true; return "Negative index read should disrupt"
}
assert(threw, "Out of range read should throw");
threw = false;
try {
b.read_logical(-1);
} catch (e) {
threw = true;
}
assert(threw, "Negative index read should throw");
}, },
test_write_and_read_numbers: function() { test_write_and_read_numbers: function() {
var b = Blob(); var b = Blob();
b.write_number(3.14159); b.write_number(3.14159);
@@ -69,41 +70,48 @@ return {
b.write_number(0); b.write_number(0);
b.write_number(1e20); b.write_number(1e20);
stone(b); stone(b);
assertEqual(b.read_number(0), 3.14159, "First number should match"); var r = assertEqual(b.read_number(0), 3.14159, "First number should match")
assertEqual(b.read_number(64), -42, "Second number should match"); if (r) return r
assertEqual(b.read_number(128), 0, "Third number should match"); r = assertEqual(b.read_number(64), -42, "Second number should match")
assertEqual(b.read_number(192), 1e20, "Fourth number should match"); if (r) return r
r = assertEqual(b.read_number(128), 0, "Third number should match")
if (r) return r
return assertEqual(b.read_number(192), 1e20, "Fourth number should match")
}, },
test_write_and_read_text: function() { test_write_and_read_text: function() {
var b = Blob(); var b = Blob();
b.write_text("Hello"); b.write_text("Hello");
b.write_text("World"); b.write_text("World");
b.write_text("🎉"); b.write_text("🎉");
stone(b); stone(b);
assertEqual(b.read_text(0), "Hello", "First text should match"); return assertEqual(b.read_text(0), "Hello", "First text should match")
}, },
test_write_and_read_blobs: function() { test_write_and_read_blobs: function() {
var b1 = Blob(); var b1 = Blob();
b1.write_bit(true); b1.write_bit(true);
b1.write_bit(false); b1.write_bit(false);
b1.write_bit(true); b1.write_bit(true);
var b2 = Blob(10); var b2 = Blob(10);
b2.write_blob(b1); b2.write_blob(b1);
b2.write_bit(false); b2.write_bit(false);
assertEqual(length(b2), 4, "Combined blob should have 4 bits"); var r = assertEqual(length(b2), 4, "Combined blob should have 4 bits")
if (r) return r
stone(b2); stone(b2);
assertEqual(b2.read_logical(0), true); r = assertEqual(b2.read_logical(0), true)
assertEqual(b2.read_logical(1), false); if (r) return r
assertEqual(b2.read_logical(2), true); r = assertEqual(b2.read_logical(1), false)
assertEqual(b2.read_logical(3), false); if (r) return r
r = assertEqual(b2.read_logical(2), true)
if (r) return r
return assertEqual(b2.read_logical(3), false)
}, },
test_blob_copy_constructor: function() { test_blob_copy_constructor: function() {
var b1 = Blob(); var b1 = Blob();
b1.write_bit(true); b1.write_bit(true);
@@ -111,249 +119,219 @@ return {
b1.write_bit(true); b1.write_bit(true);
b1.write_bit(true); b1.write_bit(true);
stone(b1); stone(b1);
var b2 = Blob(b1); var b2 = Blob(b1);
stone(b2); stone(b2);
assertEqual(length(b2), 4, "Copied blob should have same length"); var r = assertEqual(length(b2), 4, "Copied blob should have same length")
assertEqual(b2.read_logical(0), true); if (r) return r
assertEqual(b2.read_logical(3), true); r = assertEqual(b2.read_logical(0), true)
if (r) return r
return assertEqual(b2.read_logical(3), true)
}, },
test_blob_partial_copy_constructor: function() { test_blob_partial_copy_constructor: function() {
var b1 = Blob(); var b1 = Blob();
for (var i = 0; i < 10; i++) { for (var i = 0; i < 10; i++) {
b1.write_bit(i % 2 == 0); b1.write_bit(i % 2 == 0);
} }
stone(b1); stone(b1);
var b2 = Blob(b1, 2, 7); var b2 = Blob(b1, 2, 7);
stone(b2); stone(b2);
assertEqual(length(b2), 5, "Partial copy should have 5 bits"); var r = assertEqual(length(b2), 5, "Partial copy should have 5 bits")
assertEqual(b2.read_logical(0), true); if (r) return r
assertEqual(b2.read_logical(2), true); r = assertEqual(b2.read_logical(0), true)
if (r) return r
return assertEqual(b2.read_logical(2), true)
}, },
test_create_blob_with_fill: function() { test_create_blob_with_fill: function() {
var b1 = Blob(8, true); var b1 = Blob(8, true);
var b2 = Blob(8, false); var b2 = Blob(8, false);
stone(b1); stone(b1);
stone(b2); stone(b2);
for (var i = 0; i < 8; i++) { for (var i = 0; i < 8; i++) {
assertEqual(b1.read_logical(i), true, "Bit " + i + " should be true"); var r = assertEqual(b1.read_logical(i), true, "Bit " + i + " should be true")
assertEqual(b2.read_logical(i), false, "Bit " + i + " should be false"); if (r) return r
r = assertEqual(b2.read_logical(i), false, "Bit " + i + " should be false")
if (r) return r
} }
}, },
test_create_blob_with_random_function: function() { test_create_blob_with_random_function: function() {
var sequence = [true, false, true, true, false]; var sequence = [true, false, true, true, false];
var index = 0; var index = 0;
var b = Blob(5, function() { var b = Blob(5, function() {
return sequence[index++] ? 1 : 0; return sequence[index++] ? 1 : 0;
}); });
stone(b); stone(b);
for (var i = 0; i < 5; i++) { for (var i = 0; i < 5; i++) {
assertEqual(b.read_logical(i), sequence[i], "Bit " + i + " should match sequence"); var r = assertEqual(b.read_logical(i), sequence[i], "Bit " + i + " should match sequence")
if (r) return r
} }
}, },
test_write_pad_and_check_padding: function() { test_write_pad_and_check_padding: function() {
var b = Blob(); var b = Blob();
b.write_bit(true); b.write_bit(true);
b.write_bit(false); b.write_bit(false);
b.write_bit(true); b.write_bit(true);
b.write_pad(8); b.write_pad(8);
assertEqual(length(b), 8, "Should be padded to 8 bits"); var r = assertEqual(length(b), 8, "Should be padded to 8 bits")
if (r) return r
stone(b); stone(b);
assert(b['pad?'](3, 8), "Should detect valid padding at position 3"); r = assert(b['pad?'](3, 8), "Should detect valid padding at position 3")
assert(!b['pad?'](2, 8), "Should detect invalid padding at position 2"); if (r) return r
return assert(!b['pad?'](2, 8), "Should detect invalid padding at position 2")
}, },
test_read_blob_from_stone_blob: function() { test_read_blob_from_stone_blob: function() {
var b1 = Blob(); var b1 = Blob();
for (var i = 0; i < 16; i++) { for (var i = 0; i < 16; i++) {
b1.write_bit(i % 3 == 0); b1.write_bit(i % 3 == 0);
} }
stone(b1); stone(b1);
var b2 = b1.read_blob(4, 12); var b2 = b1.read_blob(4, 12);
stone(b2); stone(b2);
assertEqual(length(b2), 8, "Read blob should have 8 bits"); var r = assertEqual(length(b2), 8, "Read blob should have 8 bits")
if (r) return r
assertEqual(b2.read_logical(2), true);
assertEqual(b2.read_logical(5), true); r = assertEqual(b2.read_logical(2), true)
if (r) return r
return assertEqual(b2.read_logical(5), true)
}, },
test_stone_blob_is_immutable: function() { test_stone_blob_is_immutable: function() {
var b = Blob(); var b = Blob();
b.write_bit(true); b.write_bit(true);
stone(b); stone(b);
var threw = false; if (!should_disrupt(function() { b.write_bit(false) }))
try { return "Writing to stone blob should disrupt"
b.write_bit(false);
} catch (e) {
threw = true;
}
assert(threw, "Writing to stone blob should throw error");
}, },
test_multiple_stone_calls_are_safe: function() { test_multiple_stone_calls_are_safe: function() {
var b = Blob(); var b = Blob();
b.write_bit(true); b.write_bit(true);
assert(!stone.p(b), "Blob should not be a stone before stone() call"); var r = assert(!stone.p(b), "Blob should not be a stone before stone() call")
if (r) return r
stone(b); stone(b);
assert(stone.p(b), "Blob should be a stone after stone() call"); r = assert(stone.p(b), "Blob should be a stone after stone() call")
if (r) return r
stone(b); stone(b);
assertEqual(b.read_logical(0), true, "Blob data should remain intact"); r = assertEqual(b.read_logical(0), true, "Blob data should remain intact")
if (r) return r
assert(b.stone == null, "blob.stone should not be available as a method");
return assert(b.stone == null, "blob.stone should not be available as a method")
}, },
test_invalid_constructor_arguments: function() { test_invalid_constructor_arguments: function() {
var threw = false; if (!should_disrupt(function() { Blob("invalid") }))
try { return "Invalid constructor arguments should disrupt"
var b = Blob("invalid");
} catch (e) {
threw = true;
}
assert(threw, "Invalid constructor arguments should throw");
}, },
test_write_bit_validation: function() { test_write_bit_validation: function() {
var b = Blob(); var b = Blob();
b.write_bit(0); b.write_bit(0);
b.write_bit(1); b.write_bit(1);
var threw = false; if (!should_disrupt(function() { b.write_bit(2) }))
try { return "write_bit with value 2 should disrupt"
b.write_bit(2);
} catch (e) {
threw = true;
}
assert(threw, "write_bit with value 2 should throw");
}, },
test_complex_data_round_trip: function() { test_complex_data_round_trip: function() {
var b = Blob(); var b = Blob();
b.write_text("Test"); b.write_text("Test");
b.write_number(123.456); b.write_number(123.456);
b.write_bit(true); b.write_bit(true);
b.write_bit(false); b.write_bit(false);
b.write_number(-999.999); b.write_number(-999.999);
var originalLength = length(b); var originalLength = length(b);
stone(b); stone(b);
var b2 = Blob(b); var b2 = Blob(b);
stone(b2); stone(b2);
assertEqual(length(b2), originalLength, "Copy should have same length"); var r = assertEqual(length(b2), originalLength, "Copy should have same length")
assertEqual(b2.read_text(0), "Test", "First text should match"); if (r) return r
return assertEqual(b2.read_text(0), "Test", "First text should match")
}, },
test_zero_capacity_blob: function() { test_zero_capacity_blob: function() {
var b = Blob(0); var b = Blob(0);
assertEqual(length(b), 0, "Zero capacity blob should have length 0"); var r = assertEqual(length(b), 0, "Zero capacity blob should have length 0")
if (r) return r
b.write_bit(true); b.write_bit(true);
assertEqual(length(b), 1, "Should expand when writing"); return assertEqual(length(b), 1, "Should expand when writing")
}, },
test_large_blob_handling: function() { test_large_blob_handling: function() {
var b = Blob(); var b = Blob();
var testSize = 1000; var testSize = 1000;
for (var i = 0; i < testSize; i++) { for (var i = 0; i < testSize; i++) {
b.write_bit(i % 7 == 0); b.write_bit(i % 7 == 0);
} }
assertEqual(length(b), testSize, "Should have " + testSize + " bits"); var r = assertEqual(length(b), testSize, "Should have " + testSize + " bits")
if (r) return r
stone(b); stone(b);
assertEqual(b.read_logical(0), true, "Bit 0 should be true"); r = assertEqual(b.read_logical(0), true, "Bit 0 should be true")
assertEqual(b.read_logical(7), true, "Bit 7 should be true"); if (r) return r
assertEqual(b.read_logical(14), true, "Bit 14 should be true"); r = assertEqual(b.read_logical(7), true, "Bit 7 should be true")
assertEqual(b.read_logical(15), false, "Bit 15 should be false"); if (r) return r
r = assertEqual(b.read_logical(14), true, "Bit 14 should be true")
if (r) return r
return assertEqual(b.read_logical(15), false, "Bit 15 should be false")
}, },
test_non_stone_blob_read_methods_should_throw: function() { test_non_stone_blob_read_methods_should_throw: function() {
var b = Blob(); var b = Blob();
b.write_bit(true); b.write_bit(true);
b.write_number(42); b.write_number(42);
b.write_text("test"); b.write_text("test");
var threw = false; if (!should_disrupt(function() { b.read_logical(0) }))
try { return "read_logical on non-stone blob should disrupt"
b.read_logical(0);
} catch (e) { if (!should_disrupt(function() { b.read_number(0) }))
threw = true; return "read_number on non-stone blob should disrupt"
}
assert(threw, "read_logical on non-stone blob should throw"); if (!should_disrupt(function() { b.read_text(0) }))
return "read_text on non-stone blob should disrupt"
threw = false;
try { if (!should_disrupt(function() { b.read_blob(0, 10) }))
b.read_number(0); return "read_blob on non-stone blob should disrupt"
} catch (e) {
threw = true; if (!should_disrupt(function() { b['pad?'](0, 8) }))
} return "pad? on non-stone blob should disrupt"
assert(threw, "read_number on non-stone blob should throw");
threw = false;
try {
b.read_text(0);
} catch (e) {
threw = true;
}
assert(threw, "read_text on non-stone blob should throw");
threw = false;
try {
b.read_blob(0, 10);
} catch (e) {
threw = true;
}
assert(threw, "read_blob on non-stone blob should throw");
threw = false;
try {
b['pad?'](0, 8);
} catch (e) {
threw = true;
}
assert(threw, "pad? on non-stone blob should throw");
}, },
test_empty_text_write_and_read: function() { test_empty_text_write_and_read: function() {
var b = Blob(); var b = Blob();
b.write_text(""); b.write_text("");
stone(b); stone(b);
assertEqual(b.read_text(0), "", "Empty string should round-trip"); return assertEqual(b.read_text(0), "", "Empty string should round-trip")
}, },
test_invalid_read_positions: function() { test_invalid_read_positions: function() {
var b = Blob(); var b = Blob();
b.write_number(42); b.write_number(42);
stone(b); stone(b);
var threw = false; if (!should_disrupt(function() { b.read_number(-10) }))
try { return "Negative position should disrupt"
b.read_number(-10);
} catch (e) { if (!should_disrupt(function() { b.read_number(1000) }))
threw = true; return "Position beyond length should disrupt"
}
assert(threw, "Negative position should throw");
threw = false;
try {
b.read_number(1000);
} catch (e) {
threw = true;
}
assert(threw, "Position beyond length should throw");
} }
} }

View File

@@ -1,5 +1,5 @@
return { return {
test_disrupt: function() { test_disrupt: function() {
throw 1 disrupt
} }
} }

View File

@@ -2,85 +2,101 @@ var fd = use("fd")
var miniz = use("miniz") var miniz = use("miniz")
var utf8 = use("utf8") var utf8 = use("utf8")
function safe_unlink(p) { fd.unlink(p) } disruption {}
return { return {
create_and_read_zip: function() { create_and_read_zip: function() {
var ZIP_PATH = "miniz_test.zip" var ZIP_PATH = "miniz_test.zip"
var SOURCE_PATH = "miniz_source.txt" var SOURCE_PATH = "miniz_source.txt"
var ENTRY_PATH = "sample/hello.txt" var ENTRY_PATH = "sample/hello.txt"
var PAYLOAD = "Miniz integration test payload." var PAYLOAD = "Miniz integration test payload."
function write_text_file(path, text) { function write_text_file(path, text) {
var handle = fd.open(path, "w") var handle = fd.open(path, "w")
fd.write(handle, text) fd.write(handle, text)
fd.close(handle) fd.close(handle)
} }
try { var error_msg = null
var do_test = function() {
write_text_file(SOURCE_PATH, PAYLOAD) write_text_file(SOURCE_PATH, PAYLOAD)
var source_blob = fd.slurp(SOURCE_PATH) var source_blob = fd.slurp(SOURCE_PATH)
var writer = miniz.write(ZIP_PATH) var writer = miniz.write(ZIP_PATH)
writer.add_file(ENTRY_PATH, source_blob) writer.add_file(ENTRY_PATH, source_blob)
writer = null writer = null
var zip_blob = fd.slurp(ZIP_PATH) var zip_blob = fd.slurp(ZIP_PATH)
var reader = miniz.read(zip_blob) var reader = miniz.read(zip_blob)
if (!reader.exists(ENTRY_PATH)) if (!reader.exists(ENTRY_PATH))
throw "entry missing in archive" error_msg = "entry missing in archive"
var extracted_blob = reader.slurp(ENTRY_PATH) if (!error_msg) {
var extracted_text = utf8.decode(extracted_blob) var extracted_blob = reader.slurp(ENTRY_PATH)
var extracted_text = utf8.decode(extracted_blob)
if (extracted_text != PAYLOAD)
throw "extracted text mismatch" if (extracted_text != PAYLOAD)
} finally { error_msg = "extracted text mismatch"
try { fd.unlink(ZIP_PATH) } catch(e) {} }
try { fd.unlink(SOURCE_PATH) } catch(e) {} } disruption {
if (!error_msg) error_msg = "test disrupted"
} }
do_test()
safe_unlink(ZIP_PATH)
safe_unlink(SOURCE_PATH)
if (error_msg) return error_msg
}, },
list_and_count: function() { list_and_count: function() {
var ZIP_PATH = "miniz_list_test.zip" var ZIP_PATH = "miniz_list_test.zip"
var ENTRY1 = "file1.txt" var ENTRY1 = "file1.txt"
var ENTRY2 = "dir/file2.txt" var ENTRY2 = "dir/file2.txt"
try { var error_msg = null
var do_test = function() {
var writer = miniz.write(ZIP_PATH) var writer = miniz.write(ZIP_PATH)
writer.add_file(ENTRY1, utf8.encode("content1")) writer.add_file(ENTRY1, utf8.encode("content1"))
writer.add_file(ENTRY2, utf8.encode("content2")) writer.add_file(ENTRY2, utf8.encode("content2"))
writer = null writer = null
var zip_blob = fd.slurp(ZIP_PATH) var zip_blob = fd.slurp(ZIP_PATH)
var reader = miniz.read(zip_blob) var reader = miniz.read(zip_blob)
var listed = reader.list() var listed = reader.list()
if (length(listed) != reader.count()) if (length(listed) != reader.count())
throw "list/count mismatch" error_msg = "list/count mismatch"
if (length(listed) != 2) if (!error_msg && length(listed) != 2)
throw "unexpected entry count" error_msg = "unexpected entry count"
} finally { } disruption {
try { fd.unlink(ZIP_PATH) } catch(e) {} if (!error_msg) error_msg = "test disrupted"
} }
do_test()
safe_unlink(ZIP_PATH)
if (error_msg) return error_msg
}, },
exists_check: function() { exists_check: function() {
var ZIP_PATH = "miniz_exists_test.zip" var ZIP_PATH = "miniz_exists_test.zip"
var ENTRY_PATH = "existing.txt" var ENTRY_PATH = "existing.txt"
try { var error_msg = null
var do_test = function() {
var writer = miniz.write(ZIP_PATH) var writer = miniz.write(ZIP_PATH)
writer.add_file(ENTRY_PATH, utf8.encode("data")) writer.add_file(ENTRY_PATH, utf8.encode("data"))
writer = null writer = null
var zip_blob = fd.slurp(ZIP_PATH) var zip_blob = fd.slurp(ZIP_PATH)
var reader = miniz.read(zip_blob) var reader = miniz.read(zip_blob)
if (!reader.exists(ENTRY_PATH)) if (!reader.exists(ENTRY_PATH))
throw "existing entry not found" error_msg = "existing entry not found"
if (reader.exists("nonexistent.txt")) if (!error_msg && reader.exists("nonexistent.txt"))
throw "nonexistent entry reported as existing" error_msg = "nonexistent entry reported as existing"
} finally { } disruption {
try { fd.unlink(ZIP_PATH) } catch(e) {} if (!error_msg) error_msg = "test disrupted"
} }
do_test()
safe_unlink(ZIP_PATH)
if (error_msg) return error_msg
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
var cmds = { var cmds = {
stop: $stop, stop: $stop,
disrupt: _ => { disrupt: _ => {
$delay(_ => { throw Error() }, 0.5) $delay(_ => { disrupt }, 0.5)
} }
} }

View File

@@ -1,5 +1,10 @@
try { var load_disrupted = false
var do_load = function() {
var u = use('tests/use') var u = use('tests/use')
} catch(e) { } disruption {
log.console(e) load_disrupted = true
}
do_load()
if (load_disrupted) {
log.console("use self-load disrupted")
} }