✨ (theme): Evita referencias cíclicas de parent()
Como `Theme::parent()` se resuelve en tiempo de ejecución, una referencia cíclica (en la que un tema acaba siendo padre de sí mismo, directa o transitivamente) no puede descartarse en compilación. Se detecta al registrar el tema y aborta el arranque si procede.
This commit is contained in:
parent
825283caa3
commit
8cb5e035dc
3 changed files with 40 additions and 0 deletions
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::core::action::add_action;
|
use crate::core::action::add_action;
|
||||||
use crate::core::extension::ExtensionRef;
|
use crate::core::extension::ExtensionRef;
|
||||||
|
use crate::core::theme::ThemeRef;
|
||||||
use crate::core::theme::all::THEMES;
|
use crate::core::theme::all::THEMES;
|
||||||
use crate::web::Router;
|
use crate::web::Router;
|
||||||
use crate::{global, serve_static_files, trace, web};
|
use crate::{global, serve_static_files, trace, web};
|
||||||
|
|
@ -45,6 +46,8 @@ fn add_to_enabled(list: &mut Vec<ExtensionRef>, extension: ExtensionRef) {
|
||||||
|
|
||||||
// Comprueba si la extensión tiene un tema asociado que deba registrarse.
|
// Comprueba si la extensión tiene un tema asociado que deba registrarse.
|
||||||
if let Some(theme) = extension.theme() {
|
if let Some(theme) = extension.theme() {
|
||||||
|
check_theme_parent_chain(theme);
|
||||||
|
|
||||||
let mut registered_themes = THEMES.write();
|
let mut registered_themes = THEMES.write();
|
||||||
// Asegura que el tema no esté ya registrado para evitar duplicados.
|
// Asegura que el tema no esté ya registrado para evitar duplicados.
|
||||||
if !registered_themes
|
if !registered_themes
|
||||||
|
|
@ -60,6 +63,31 @@ fn add_to_enabled(list: &mut Vec<ExtensionRef>, extension: ExtensionRef) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Recorre la cadena de `Theme::parent()` para detectar referencias circulares. `parent()` se
|
||||||
|
// resuelve en tiempo de ejecución, así que un ciclo no puede descartarse al compilar. Se rechaza el
|
||||||
|
// arranque al detectar uno, antes de provocar un bucle infinito (en `Theme::handle_component()`) o
|
||||||
|
// un desbordamiento de pila (en los métodos predefinidos de `Theme` que delegan recursivamente en
|
||||||
|
// el tema padre).
|
||||||
|
fn check_theme_parent_chain(theme: ThemeRef) {
|
||||||
|
let mut chain: Vec<ThemeRef> = vec![theme];
|
||||||
|
let mut current = theme;
|
||||||
|
while let Some(parent) = current.parent() {
|
||||||
|
if let Some(pos) = chain.iter().position(|t| t.type_id() == parent.type_id()) {
|
||||||
|
let cycle: Vec<&str> = chain[pos..]
|
||||||
|
.iter()
|
||||||
|
.map(|t| t.short_name())
|
||||||
|
.chain(std::iter::once(parent.short_name()))
|
||||||
|
.collect();
|
||||||
|
panic!(
|
||||||
|
"Circular theme parent chain detected: {}",
|
||||||
|
cycle.join(" -> ")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
chain.push(parent);
|
||||||
|
current = parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// **< REGISTRO DE LAS ACCIONES >*******************************************************************
|
// **< REGISTRO DE LAS ACCIONES >*******************************************************************
|
||||||
|
|
||||||
pub fn register_actions() {
|
pub fn register_actions() {
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,13 @@
|
||||||
//! de [`Theme::handle_component()`], las páginas de error, etc.). Un tema hijo puede ser a su vez
|
//! de [`Theme::handle_component()`], las páginas de error, etc.). Un tema hijo puede ser a su vez
|
||||||
//! padre de otro, basta declararlo cada vez con [`Theme::parent()`].
|
//! padre de otro, basta declararlo cada vez con [`Theme::parent()`].
|
||||||
//!
|
//!
|
||||||
|
//! Como `parent()` se resuelve en tiempo de ejecución, PageTop no puede descartar en compilación
|
||||||
|
//! referencias circulares (un tema acaba siendo padre de sí mismo, directa o transitivamente). Ese
|
||||||
|
//! ciclo provocaría un bucle infinito en [`Theme::handle_component()`] o un desbordamiento de pila
|
||||||
|
//! en los métodos predefinidos de `Theme` que delegan recursivamente en el padre. Para evitarlo,
|
||||||
|
//! PageTop recorre la cadena de cada tema al registrarlo y **aborta el arranque de la aplicación**
|
||||||
|
//! si detecta una referencia circular.
|
||||||
|
//!
|
||||||
//! Sin embargo, no dice nada sobre los componentes. Aunque un tema puede exportar su propio
|
//! Sin embargo, no dice nada sobre los componentes. Aunque un tema puede exportar su propio
|
||||||
//! catálogo de componentes, realmente no pertenecen como tal a ningún tema ni dependen de esa
|
//! catálogo de componentes, realmente no pertenecen como tal a ningún tema ni dependen de esa
|
||||||
//! cadena de herencia. Una extensión puede existir únicamente para aportar un componente genérico
|
//! cadena de herencia. Una extensión puede existir únicamente para aportar un componente genérico
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,11 @@ pub trait Theme: Extension + Send + Sync {
|
||||||
/// no los sobrescribe.
|
/// no los sobrescribe.
|
||||||
///
|
///
|
||||||
/// La implementación por defecto devuelve `None` (tema sin padre).
|
/// La implementación por defecto devuelve `None` (tema sin padre).
|
||||||
|
///
|
||||||
|
/// Una referencia circular (un tema acaba siendo padre de sí mismo, directa o transitivamente)
|
||||||
|
/// no puede descartarse en tiempo de compilación. PageTop la detecta al registrar el tema y
|
||||||
|
/// aborta el arranque de la aplicación si encuentra una, para evitar bucles infinitos o
|
||||||
|
/// desbordamientos de pila al usar el tema.
|
||||||
fn parent(&self) -> Option<ThemeRef> {
|
fn parent(&self) -> Option<ThemeRef> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue