aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe/src/event_dispatcher/event_dispatcher.rs
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-07-04 03:20:23 +0900
committernsfisis <nsfisis@gmail.com>2026-07-04 16:58:29 +0900
commit31f6ac69794361cdaee52cba0213a1b4da4932ac (patch)
tree7cdc57124a13daea5c74ef03161098bb4664fa0c /crates/shirabe/src/event_dispatcher/event_dispatcher.rs
parentf7b0eee15f17a4fa5db717cda5ddc387f884afd0 (diff)
downloadphp-shirabe-31f6ac69794361cdaee52cba0213a1b4da4932ac.tar.gz
php-shirabe-31f6ac69794361cdaee52cba0213a1b4da4932ac.tar.zst
php-shirabe-31f6ac69794361cdaee52cba0213a1b4da4932ac.zip
fix(event-dispatcher): avoid reentrant RefCell panics
Two related "already borrowed" panics reachable from AutoloadGenerator::dump() (which holds the local-repository, installation-manager, and config RefCells for the duration of its own statement, per the temporary-lifetime-extension pattern fixed separately in create_project_command.rs): - ensure_bin_dir_is_in_path called config.borrow_mut() to read "bin-dir", but Config::get only needs &self; use borrow() so it can coexist with an outer borrow instead of conflicting with it. - make_autoloader's real body needed composer_handle.borrow_mut() plus the same local-repository/installation-manager RefCells the caller already holds mutably, which cannot be made reentrant-safe without a larger restructuring. Since all 3 call sites already discard its return value, and its only effect (registering a Composer-generated ClassLoader for autoloading during event-listener PHP execution) is unobservable in this port — there's no embedded PHP interpreter to register it into, and class_exists for user-defined classes is a hardcoded-false shim so the caller's very next check always treats the class as unavailable regardless — make it a genuine no-op. This unblocks the post-autoload-dump event for any script listener naming a PHP class (e.g. Illuminate\Foundation\ComposerScripts), which every create-project/install run reaches once real packages get installed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe/src/event_dispatcher/event_dispatcher.rs')
-rw-r--r--crates/shirabe/src/event_dispatcher/event_dispatcher.rs99
1 files changed, 17 insertions, 82 deletions
diff --git a/crates/shirabe/src/event_dispatcher/event_dispatcher.rs b/crates/shirabe/src/event_dispatcher/event_dispatcher.rs
index 87552a9..88d8217 100644
--- a/crates/shirabe/src/event_dispatcher/event_dispatcher.rs
+++ b/crates/shirabe/src/event_dispatcher/event_dispatcher.rs
@@ -24,11 +24,11 @@ use shirabe_external_packages::symfony::process::ExecutableFinder;
use shirabe_external_packages::symfony::process::PhpExecutableFinder;
use shirabe_php_shim::{
InvalidArgumentException, PATH_SEPARATOR, PhpMixed, RuntimeException, array_pop, array_push,
- array_search_in_vec, array_splice, class_exists, defined, file_exists, get_class, hash,
- implode, ini_get, is_a, is_array, is_callable, is_object, is_string, krsort, preg_quote,
- realpath, spl_autoload_functions, spl_autoload_register, spl_autoload_unregister,
- spl_object_hash, str_contains, str_ends_with, str_replace, str_starts_with, strlen, strpos,
- strtoupper, substr, trim,
+ array_search_in_vec, array_splice, class_exists, defined, file_exists, get_class, implode,
+ ini_get, is_a, is_array, is_callable, is_object, is_string, krsort, preg_quote, realpath,
+ spl_autoload_functions, spl_autoload_register, spl_autoload_unregister, spl_object_hash,
+ str_contains, str_ends_with, str_replace, str_starts_with, strlen, strpos, strtoupper, substr,
+ trim,
};
/// Represents a callable listener. PHP's `callable` may be a string (command, script, or
@@ -1071,7 +1071,7 @@ impl EventDispatcher {
.composer()
.borrow_partial()
.get_config()
- .borrow_mut()
+ .borrow()
.get("bin-dir")
.as_string()
.map(|s| s.to_string())
@@ -1137,82 +1137,17 @@ impl EventDispatcher {
event: &dyn EventInterface,
callable: &Callable,
) -> anyhow::Result<()> {
- let composer = self.composer();
- // TODO(plugin): full autoloader rebuild on plugin-supplied callables — currently a stub.
- let Some(composer) = composer.as_full() else {
- return Ok(());
- };
- let composer = composer.borrow_mut();
-
- let callable_key = match callable {
- Callable::ArrayCallable(first, method) => {
- let prefix = if let PhpMixed::String(s) = first.as_ref() {
- s.clone()
- } else {
- get_class(first.as_ref())
- };
- format!("{}::{}", prefix, method)
- }
- Callable::String(s) => s.clone(),
- Callable::Closure => "closure".to_string(),
- };
- if self.previous_listeners.contains_key(&callable_key) {
- return Ok(());
- }
- self.previous_listeners.insert(callable_key, true);
-
- let package = composer.get_package();
- let packages = composer
- .get_repository_manager()
- .borrow()
- .get_local_repository()
- .get_canonical_packages()?;
- let generator = composer.get_autoload_generator().clone();
- let generator = generator.borrow();
- let mut hash_input = packages
- .iter()
- .map(|p: &crate::package::PackageInterfaceHandle| {
- format!("{}/{}", p.get_name(), p.get_version())
- })
- .collect::<Vec<_>>()
- .join(",");
- // TODO(plugin): polymorphic isDevMode propagation for ScriptEvent / PackageEvent / InstallerEvent
- let _ = event;
- hash_input.push_str("");
- let hash_value = hash("sha256", &hash_input);
-
- if self.previous_hash.as_deref() == Some(hash_value.as_str()) {
- return Ok(());
- }
-
- self.previous_hash = Some(hash_value);
-
- let installation_manager = composer.get_installation_manager();
- let package_map = generator.build_package_map(
- &mut *installation_manager.borrow_mut(),
- package.clone(),
- packages,
- )?;
- let map = generator.parse_autoloads(
- package_map,
- package.clone(),
- shirabe_php_shim::PhpMixed::Bool(false),
- );
-
- if let Some(loader) = self.loader.as_mut() {
- loader.unregister();
- }
-
- let vendor_dir = composer
- .get_config()
- .borrow_mut()
- .get("vendor-dir")
- .as_string()
- .map(|s| s.to_string())
- .unwrap_or_default();
- let loader = generator.create_loader(&map, Some(vendor_dir.clone()));
- loader.register(false);
- self.loader = Some(loader);
+ // TODO(plugin): full autoloader rebuild on plugin-supplied/script-listener callables —
+ // a genuine no-op here, not merely a stub. All 3 call sites already discard the return
+ // value, and rebuilding+registering a ClassLoader has no observable effect in this port:
+ // there is no embedded PHP interpreter to register it into, and `class_exists` for
+ // user-defined classes is a hardcoded-false shim, so the caller's very next check always
+ // treats the class as unavailable regardless of what this function does. Also, every
+ // caller reaches this from inside AutoloadGenerator::dump(), which is invoked while a
+ // caller higher up the stack still holds the local-repository/installation-manager
+ // RefCells borrowed for the duration of its own statement — doing the real work here
+ // (which needs those same RefCells) would panic with "already borrowed".
+ let _ = (event, callable);
Ok(())
}