fix(oakengine): facade bugs found by integration tests
undo: - NULL/empty label no longer crosses to oakundo as a dangling 0x1 pointer (push, group_begin/end) — fixed SIGSEGV - group_abort now undoes each executed child in reverse order task: - create_project_import addrefs the borrowed project handle instead of freeing it under the async task — fixed UAF/SIGSEGV timeline: - toggle_enabled/delete_clips guard NULL+0 slices — fixed SIGABRT - BlockSplitCommand halves placed correctly (oaktimeline undosplit) - PreservingLinks / ripple remove / ripple delete-gaps commands self-prepare on first redo — fixes silent no-op split/ripple - trim_clips_to targets the block containing the point, not the track - delete_empty_tracks applies the live track removal - ripple facades no longer free borrowed track handles still referenced by commands — fixed UAF node: - project_add_node releases the factory handle — fixes per-call leak - inputs_from(recursive=0) matches direct feeders (BFS off-by-one) - group passthrough id/resolve treat two-stage string length as success - node_connect(_command) reject duplicate connects with E_STATE - folder_add_child enforces one-folder-per-node - value_split_to_tracks splits vector/color per component - set_context_position/expanded establish the first entry - node_get_flags on an empty box returns 0, not u64::MAX - footage_borrow addrefs its wrapper — fixes double-free render: - renderer_create rejects invalid pixel formats (real range check) - render_frame forwards renderer width/height to the ticket tests: repro #[ignore]s removed, bug-behavior assertions corrected, it_undo global-stack tests serialized with a shared lock
This commit is contained in:
@@ -82,6 +82,11 @@ struct TaskMeta {
|
||||
/// The project a save task writes (addref'd at creation, released at
|
||||
/// free) — the module has no save-project getter.
|
||||
save_project: Option<CHandle>,
|
||||
/// The project an import task borrows (addref'd at creation, released
|
||||
/// at free): the module's import task stores its project handle WITHOUT
|
||||
/// addref, so this facade-side ref keeps the shared box alive while the
|
||||
/// task runs (see `oakengine_task_create_project_import`).
|
||||
import_project: Option<CHandle>,
|
||||
/// The encoding-params box an export task owns, dropped at free
|
||||
/// (mirrors the C++ `FacadeExportTask` destructor; stored as `usize` so
|
||||
/// the map stays `Send`).
|
||||
@@ -97,6 +102,7 @@ impl TaskMeta {
|
||||
started: false,
|
||||
cancelled: false,
|
||||
save_project: None,
|
||||
import_project: None,
|
||||
export_params: None,
|
||||
export_color_manager: None,
|
||||
}
|
||||
@@ -141,13 +147,16 @@ fn meta_set_cancelled(key: usize) {
|
||||
}
|
||||
|
||||
/// Release every facade-side sidecar of a task (called by
|
||||
/// [`oakengine_task_free`]): the addref'd save project, the owned
|
||||
/// [`oakengine_task_free`]): the addref'd save/import projects, the owned
|
||||
/// encoding-params box and the derived color manager of an export task.
|
||||
fn drop_task_meta(key: usize) {
|
||||
if let Some(meta) = meta_lock().remove(&key) {
|
||||
if let Some(mut project) = meta.save_project {
|
||||
unsafe { n::oaknode_project_free(&mut project) };
|
||||
}
|
||||
if let Some(mut project) = meta.import_project {
|
||||
unsafe { n::oaknode_project_free(&mut project) };
|
||||
}
|
||||
if let Some(ptr) = meta.export_params {
|
||||
unsafe {
|
||||
crate::codec::oakengine_encoding_params_destroy(ptr as *mut OakEngineEncodingParams)
|
||||
@@ -534,6 +543,13 @@ fn project_filename_of(project: CHandle) -> Result<String> {
|
||||
/// `oakengine_task_import_file_count` documents 0 as "nothing to import,
|
||||
/// free instead of run". `url_count < 0`, a NULL URL inside the array, or a
|
||||
/// folder with no project yield NULL.
|
||||
///
|
||||
/// The module's import task stores the project handle WITHOUT addref, so
|
||||
/// the facade keeps an addref'd copy in [`TaskMeta::import_project`]
|
||||
/// (released at free) — without it, releasing the transient borrowed
|
||||
/// handle here would drop the shared box while the task still references
|
||||
/// it, and the run would read freed memory (the former SIGSEGV reproduced
|
||||
/// by `it_task::import_run_single_file`).
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn oakengine_task_create_project_import(
|
||||
folder: *mut OakEngineNode,
|
||||
@@ -551,10 +567,22 @@ pub unsafe extern "C" fn oakengine_task_create_project_import(
|
||||
return Ok(std::ptr::null_mut());
|
||||
}
|
||||
let h = t::oaktask_create_project_import(fh, project, urls, url_count);
|
||||
// Release the transient borrowed project handle (the import task
|
||||
// keeps its own copy).
|
||||
if h.is_null() {
|
||||
// Creation failed: release the transient borrowed handle.
|
||||
n::oaknode_project_free(&mut project);
|
||||
return Ok(std::ptr::null_mut());
|
||||
}
|
||||
// Keep the project borrowed for the task's lifetime (the module's
|
||||
// import task stores the handle without addref): addref before the
|
||||
// transient handle below is released, so the shared box stays alive
|
||||
// until `oakengine_task_free` drops the meta-side copy.
|
||||
let mut meta = TaskMeta::new();
|
||||
meta.import_project = Some(project.addref());
|
||||
// Release the transient borrowed project handle (the task's copy and
|
||||
// the facade-side addref above keep the box alive).
|
||||
n::oaknode_project_free(&mut project);
|
||||
Ok(box_task(h))
|
||||
meta_insert(h.ctx as usize, meta);
|
||||
Ok(box_handle::<OakEngineTask>(h))
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user