shop audit
This commit is contained in:
@@ -425,6 +425,22 @@ static JSValue js_os_dylib_has_symbol(JSContext *js, JSValue self, int argc, JSV
|
||||
return JS_NewBool(js, symbol != NULL);
|
||||
}
|
||||
|
||||
static JSValue js_os_dylib_close(JSContext *js, JSValue self, int argc, JSValue *argv)
|
||||
{
|
||||
if (argc < 1)
|
||||
return JS_ThrowTypeError(js, "dylib_close requires a dylib object");
|
||||
void *handle = JS_GetOpaque(argv[0], js_dylib_class_id);
|
||||
if (handle) {
|
||||
#ifdef _WIN32
|
||||
FreeLibrary((HMODULE)handle);
|
||||
#else
|
||||
dlclose(handle);
|
||||
#endif
|
||||
JS_SetOpaque(argv[0], NULL);
|
||||
}
|
||||
return JS_NULL;
|
||||
}
|
||||
|
||||
/* Load a native .cm module from a dylib handle.
|
||||
Uses cell_rt_native_module_load from qbe_helpers.c */
|
||||
extern JSValue cell_rt_native_module_load(JSContext *ctx, void *dl_handle);
|
||||
@@ -613,6 +629,7 @@ static const JSCFunctionListEntry js_os_funcs[] = {
|
||||
MIST_FUNC_DEF(os, exit, 0),
|
||||
MIST_FUNC_DEF(os, sleep, 1),
|
||||
MIST_FUNC_DEF(os, dylib_open, 1),
|
||||
MIST_FUNC_DEF(os, dylib_close, 1),
|
||||
MIST_FUNC_DEF(os, dylib_symbol, 2),
|
||||
MIST_FUNC_DEF(os, dylib_has_symbol, 2),
|
||||
MIST_FUNC_DEF(os, native_module_load, 1),
|
||||
|
||||
231
internal/shop.cm
231
internal/shop.cm
@@ -265,11 +265,6 @@ function package_cache_path(pkg)
|
||||
return global_shop_path + '/cache/' + replace(replace(pkg, '/', '_'), '@', '_')
|
||||
}
|
||||
|
||||
function get_shared_lib_path()
|
||||
{
|
||||
return get_global_build_dir() + '/' + 'lib'
|
||||
}
|
||||
|
||||
// Load lock.toml configuration (from global shop)
|
||||
var _lock = null
|
||||
Shop.load_lock = function() {
|
||||
@@ -372,9 +367,7 @@ Shop.extract_commit_hash = function(pkg, response) {
|
||||
return null
|
||||
}
|
||||
|
||||
var dylib_visited = {}
|
||||
var open_dls = {}
|
||||
var loaded_manifests = {}
|
||||
|
||||
// Host target detection for native dylib resolution
|
||||
function detect_host_target() {
|
||||
@@ -391,12 +384,12 @@ function detect_host_target() {
|
||||
|
||||
var host_target = detect_host_target()
|
||||
|
||||
// Check for a native .cm dylib in the content-addressed cache
|
||||
// Check for a native .cm dylib at the deterministic lib path
|
||||
// Returns the loaded module value, or null if no native dylib exists
|
||||
function try_native_dylib(content_key) {
|
||||
var native_path = hash_path(content_key) + '.' + host_target + dylib_ext
|
||||
if (!fd.is_file(native_path)) return null
|
||||
var handle = os.dylib_open(native_path)
|
||||
function try_native_mod_dylib(pkg, stem) {
|
||||
var dylib_path = get_dylib_path(pkg, stem)
|
||||
if (!fd.is_file(dylib_path)) return null
|
||||
var handle = os.dylib_open(dylib_path)
|
||||
if (!handle) return null
|
||||
return os.native_module_load(handle)
|
||||
}
|
||||
@@ -465,11 +458,19 @@ function resolve_mod_fn(path, pkg) {
|
||||
var optimized = null
|
||||
var mcode_json = null
|
||||
var cached_mcode_path = null
|
||||
var _pkg_dir = null
|
||||
var _stem = null
|
||||
|
||||
// Check for native .cm dylib first (highest performance)
|
||||
native_result = try_native_dylib(content_key)
|
||||
if (native_result != null) {
|
||||
return {_native: true, value: native_result}
|
||||
// Check for native .cm dylib at deterministic path first
|
||||
if (pkg) {
|
||||
_pkg_dir = get_packages_dir() + '/' + safe_package_path(pkg)
|
||||
if (starts_with(path, _pkg_dir + '/')) {
|
||||
_stem = fd.stem(text(path, length(_pkg_dir) + 1))
|
||||
native_result = try_native_mod_dylib(pkg, _stem)
|
||||
if (native_result != null) {
|
||||
return {_native: true, value: native_result}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check cache for pre-compiled .mach blob
|
||||
@@ -616,31 +617,12 @@ function make_c_symbol(pkg, file) {
|
||||
return 'js_' + pkg_safe + '_' + file_safe + '_use'
|
||||
}
|
||||
|
||||
// Get the library path for a package in .cell/lib
|
||||
// Library names are based on canonical package names, not link targets
|
||||
function get_lib_path(pkg) {
|
||||
var lib_name = replace(replace(replace(pkg, '/', '_'), '.', '_'), '-', '_')
|
||||
return global_shop_path + '/lib/' + lib_name + dylib_ext
|
||||
// Get the deterministic dylib path for a module in lib/<pkg>/<stem>.dylib
|
||||
function get_dylib_path(pkg, stem) {
|
||||
return global_shop_path + '/lib/' + safe_package_path(pkg) + '/' + stem + dylib_ext
|
||||
}
|
||||
|
||||
// Load the manifest for a package's per-module dylibs
|
||||
// Returns a map of symbol_name -> dylib_path, or null if no manifest
|
||||
function load_package_manifest(pkg) {
|
||||
if (loaded_manifests[pkg] != null) return loaded_manifests[pkg]
|
||||
|
||||
var lib_name = replace(replace(replace(pkg, '/', '_'), '.', '_'), '-', '_')
|
||||
var manifest_path = global_shop_path + '/lib/' + lib_name + '.manifest.json'
|
||||
if (!fd.is_file(manifest_path)) {
|
||||
loaded_manifests[pkg] = false
|
||||
return null
|
||||
}
|
||||
var content = text(fd.slurp(manifest_path))
|
||||
var manifest = json.decode(content)
|
||||
loaded_manifests[pkg] = manifest
|
||||
return manifest
|
||||
}
|
||||
|
||||
// Open a per-module dylib from a manifest and return the dlopen handle
|
||||
// Open a per-module dylib and return the dlopen handle
|
||||
function open_module_dylib(dylib_path) {
|
||||
if (open_dls[dylib_path]) return open_dls[dylib_path]
|
||||
if (!fd.is_file(dylib_path)) return null
|
||||
@@ -648,57 +630,18 @@ function open_module_dylib(dylib_path) {
|
||||
return open_dls[dylib_path]
|
||||
}
|
||||
|
||||
// Resolve a C symbol from per-module dylibs for a package
|
||||
// Try to resolve a C symbol from the deterministic dylib path
|
||||
// Returns a loader function or null
|
||||
function resolve_dylib_symbol(sym, pkg) {
|
||||
var manifest = load_package_manifest(pkg)
|
||||
if (!manifest) return null
|
||||
var dylib_path = manifest[sym]
|
||||
if (!dylib_path) return null
|
||||
function try_dylib_symbol(sym, pkg, file_stem) {
|
||||
var dylib_path = get_dylib_path(pkg, file_stem)
|
||||
var handle = open_module_dylib(dylib_path)
|
||||
if (!handle) return null
|
||||
if (!os.dylib_has_symbol(handle, sym)) return null
|
||||
return function() { return os.dylib_symbol(handle, sym) }
|
||||
}
|
||||
|
||||
// Open a package's dynamic libraries (loads manifest + dependency manifests)
|
||||
Shop.open_package_dylib = function(pkg) {
|
||||
if (pkg == 'core' || !pkg) return
|
||||
if (dylib_visited[pkg]) return
|
||||
dylib_visited[pkg] = true
|
||||
|
||||
var link_target = link.get_target(pkg)
|
||||
var resolved_pkg = link_target ? link_target : pkg
|
||||
|
||||
var pkg_dir = null
|
||||
if (starts_with(resolved_pkg, '/')) {
|
||||
pkg_dir = resolved_pkg
|
||||
} else {
|
||||
pkg_dir = get_packages_dir() + '/' + safe_package_path(resolved_pkg)
|
||||
}
|
||||
|
||||
var toml_path = pkg_dir + '/cell.toml'
|
||||
var content = null
|
||||
var cfg = null
|
||||
if (fd.is_file(toml_path)) {
|
||||
content = text(fd.slurp(toml_path))
|
||||
cfg = toml.decode(content)
|
||||
if (cfg.dependencies) {
|
||||
arrfor(array(cfg.dependencies), function(alias, i) {
|
||||
var dep_pkg = cfg.dependencies[alias]
|
||||
Shop.open_package_dylib(dep_pkg)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Pre-load the manifest
|
||||
load_package_manifest(pkg)
|
||||
}
|
||||
|
||||
// Resolve a C symbol by searching:
|
||||
// 1. If package_context is null, only check core internal symbols
|
||||
// 2. Otherwise: own package (internal then per-module dylib) -> aliased packages -> core (internal only)
|
||||
// Core is never loaded as a dynamic library via dlopen
|
||||
// At each scope: check lib/ dylib first, then internal (static)
|
||||
function resolve_c_symbol(path, package_context) {
|
||||
var explicit = split_explicit_package_import(path)
|
||||
var sym = null
|
||||
@@ -707,6 +650,7 @@ function resolve_c_symbol(path, package_context) {
|
||||
var core_sym = null
|
||||
var canon_pkg = null
|
||||
var mod_name = null
|
||||
var file_stem = null
|
||||
|
||||
if (explicit) {
|
||||
if (is_internal_path(explicit.path) && package_context && explicit.package != package_context)
|
||||
@@ -714,17 +658,10 @@ function resolve_c_symbol(path, package_context) {
|
||||
}
|
||||
if (explicit) {
|
||||
sym = make_c_symbol(explicit.package, explicit.path)
|
||||
if (os.internal_exists(sym)) {
|
||||
return {
|
||||
symbol: function() { return os.load_internal(sym) },
|
||||
scope: SCOPE_PACKAGE,
|
||||
package: explicit.package,
|
||||
path: sym
|
||||
}
|
||||
}
|
||||
file_stem = replace(explicit.path, '.c', '')
|
||||
|
||||
Shop.open_package_dylib(explicit.package)
|
||||
loader = resolve_dylib_symbol(sym, explicit.package)
|
||||
// Check lib/ dylib first
|
||||
loader = try_dylib_symbol(sym, explicit.package, file_stem)
|
||||
if (loader) {
|
||||
return {
|
||||
symbol: loader,
|
||||
@@ -733,12 +670,33 @@ function resolve_c_symbol(path, package_context) {
|
||||
path: sym
|
||||
}
|
||||
}
|
||||
|
||||
// Then check internal/static
|
||||
if (os.internal_exists(sym)) {
|
||||
return {
|
||||
symbol: function() { return os.load_internal(sym) },
|
||||
scope: SCOPE_PACKAGE,
|
||||
package: explicit.package,
|
||||
path: sym
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no package context, only check core internal symbols
|
||||
// If no package context, only check core
|
||||
if (!package_context || package_context == 'core') {
|
||||
_path = replace(path, '/', '_')
|
||||
core_sym = `js_${_path}_use`
|
||||
|
||||
// Check lib/ dylib first for core
|
||||
loader = try_dylib_symbol(core_sym, 'core', path)
|
||||
if (loader) {
|
||||
return {
|
||||
symbol: loader,
|
||||
scope: SCOPE_CORE,
|
||||
path: core_sym
|
||||
}
|
||||
}
|
||||
|
||||
if (os.internal_exists(core_sym)) {
|
||||
return {
|
||||
symbol: function() { return os.load_internal(core_sym) },
|
||||
@@ -749,21 +707,21 @@ function resolve_c_symbol(path, package_context) {
|
||||
return null
|
||||
}
|
||||
|
||||
// 1. Check own package first (internal, then per-module dylib)
|
||||
// 1. Check own package (dylib first, then internal)
|
||||
sym = make_c_symbol(package_context, path)
|
||||
if (os.internal_exists(sym)) {
|
||||
|
||||
loader = try_dylib_symbol(sym, package_context, path)
|
||||
if (loader) {
|
||||
return {
|
||||
symbol: function() { return os.load_internal(sym) },
|
||||
symbol: loader,
|
||||
scope: SCOPE_LOCAL,
|
||||
path: sym
|
||||
}
|
||||
}
|
||||
|
||||
Shop.open_package_dylib(package_context)
|
||||
loader = resolve_dylib_symbol(sym, package_context)
|
||||
if (loader) {
|
||||
if (os.internal_exists(sym)) {
|
||||
return {
|
||||
symbol: loader,
|
||||
symbol: function() { return os.load_internal(sym) },
|
||||
scope: SCOPE_LOCAL,
|
||||
path: sym
|
||||
}
|
||||
@@ -780,17 +738,7 @@ function resolve_c_symbol(path, package_context) {
|
||||
mod_name = get_import_name(path)
|
||||
sym = make_c_symbol(canon_pkg, mod_name)
|
||||
|
||||
if (os.internal_exists(sym)) {
|
||||
return {
|
||||
symbol: function() { return os.load_internal(sym) },
|
||||
scope: SCOPE_PACKAGE,
|
||||
package: canon_pkg,
|
||||
path: sym
|
||||
}
|
||||
}
|
||||
|
||||
Shop.open_package_dylib(canon_pkg)
|
||||
loader = resolve_dylib_symbol(sym, canon_pkg)
|
||||
loader = try_dylib_symbol(sym, canon_pkg, mod_name)
|
||||
if (loader) {
|
||||
return {
|
||||
symbol: loader,
|
||||
@@ -799,11 +747,30 @@ function resolve_c_symbol(path, package_context) {
|
||||
path: sym
|
||||
}
|
||||
}
|
||||
|
||||
if (os.internal_exists(sym)) {
|
||||
return {
|
||||
symbol: function() { return os.load_internal(sym) },
|
||||
scope: SCOPE_PACKAGE,
|
||||
package: canon_pkg,
|
||||
path: sym
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Check core internal symbols (core is never a dynamic library)
|
||||
// 3. Check core (dylib first, then internal)
|
||||
core_sym = `js_${replace(path, '/', '_')}_use`
|
||||
|
||||
loader = try_dylib_symbol(core_sym, 'core', path)
|
||||
if (loader) {
|
||||
return {
|
||||
symbol: loader,
|
||||
scope: SCOPE_CORE,
|
||||
path: core_sym
|
||||
}
|
||||
}
|
||||
|
||||
if (os.internal_exists(core_sym)) {
|
||||
return {
|
||||
symbol: function() { return os.load_internal(core_sym) },
|
||||
@@ -1322,26 +1289,38 @@ Shop.file_reload = function(file)
|
||||
|
||||
Shop.module_reload = function(path, package) {
|
||||
if (!Shop.is_loaded(path,package)) return
|
||||
|
||||
|
||||
// Clear the module info cache for this path
|
||||
var lookup_key = package ? package + ':' + path : ':' + path
|
||||
module_info_cache[lookup_key] = null
|
||||
|
||||
|
||||
// Close old dylib handle if any
|
||||
var old_dylib_path = null
|
||||
if (package) {
|
||||
old_dylib_path = get_dylib_path(package, path)
|
||||
if (open_dls[old_dylib_path]) {
|
||||
os.dylib_close(open_dls[old_dylib_path])
|
||||
open_dls[old_dylib_path] = null
|
||||
}
|
||||
}
|
||||
|
||||
var info = resolve_module_info(path, package)
|
||||
if (!info) return
|
||||
|
||||
var cache_key = info.cache_key
|
||||
var old = use_cache[cache_key]
|
||||
use_cache[cache_key] = null
|
||||
|
||||
var newmod = get_module(path, package)
|
||||
use_cache[cache_key] = newmod
|
||||
|
||||
arrfor(array(newmod), function(i, idx) {
|
||||
old[i] = newmod[i]
|
||||
})
|
||||
|
||||
arrfor(array(old), function(i, idx) {
|
||||
if (!(i in newmod))
|
||||
old[i] = null
|
||||
})
|
||||
if (old && is_object(old) && is_object(newmod)) {
|
||||
arrfor(array(newmod), function(k) { old[k] = newmod[k] })
|
||||
arrfor(array(old), function(k) {
|
||||
if (!(k in newmod)) old[k] = null
|
||||
})
|
||||
use_cache[cache_key] = old
|
||||
}
|
||||
}
|
||||
|
||||
function get_package_scripts(package)
|
||||
@@ -1403,7 +1382,8 @@ Shop.get_package_dir = function(pkg) {
|
||||
Shop.c_symbol_for_file = function(pkg, file) {
|
||||
var pkg_safe = replace(replace(replace(pkg, '/', '_'), '.', '_'), '-', '_')
|
||||
var file_safe = replace(replace(fd.stem(file), '/', '_'), '.', '_')
|
||||
return 'js_' + pkg_safe + '_' + file_safe + '_use'
|
||||
var suffix = ends_with(file, '.ce') ? '_program' : '_use'
|
||||
return 'js_' + pkg_safe + '_' + file_safe + suffix
|
||||
}
|
||||
|
||||
// Generate C symbol prefix for a package
|
||||
@@ -1420,6 +1400,11 @@ Shop.lib_name_for_package = function(pkg) {
|
||||
}
|
||||
|
||||
// Returns { ok: bool, results: [{pkg, ok, error}] }
|
||||
// Get the deterministic dylib path for a module (public API)
|
||||
Shop.get_dylib_path = function(pkg, stem) {
|
||||
return get_dylib_path(pkg, stem)
|
||||
}
|
||||
|
||||
Shop.audit_packages = function() {
|
||||
var packages = Shop.list_packages()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user