fix: memory-safety and playback regressions found via ASan

- nodeparamview: add visited set to get_distance_between_nodes, fixing
  unbounded recursion (stack overflow) when the node graph has a cycle
- viewerdisplay: give texture_ a consistent owner via assign_texture();
  borrowed queue textures are now retained, created ones freed, fixing
  a dangling pointer that corrupted the heap and crashed in the GL driver
- playbackcache: resignal_requests() iterates a copy, handlers may
  clear_request_range() while iterating (ASan container-overflow)
- preview C API: preview request ticket lambdas captured the request
  state raw; after oakengine_preview_request_free the ticket outlived
  the request and the finished callback wrote into freed memory
  (heap-use-after-free). The finished flag is now a shared_ptr captured
  weakly by the callbacks
- playback: oak_playback_frame regains a timestamp (num/den) filled
  from olive::Frame; the viewer queue append no longer uses Rational()
  for every frame, which made append_timewise drop all but the first
  frame and froze the picture during playback
- mainwindow: open_node_in_viewer refuses sequence nodes; sequences
  already have the Sequence Viewer, and saved layouts could otherwise
  resurrect a redundant floating Viewer bound to the sequence
This commit is contained in:
2026-08-01 19:14:31 +08:00
parent 66d761b4b7
commit a4dfc62f0f
8 changed files with 120 additions and 30 deletions
+15 -2
View File
@@ -951,18 +951,25 @@ void NodeParamView::add_node(OakEngineNode *n, OakEngineNode *ctx, NodeParamView
}
}
int get_distance_between_nodes(OakEngineNode *start, OakEngineNode *end)
int get_distance_between_nodes(OakEngineNode *start, OakEngineNode *end,
QSet<OakEngineNode *> *visited)
{
if (start == end) {
return 0;
}
if (visited->contains(start)) {
return -1;
}
visited->insert(start);
const oak::Node start_node(start);
const int connection_count = start_node.input_connection_count_all();
for (int i = 0; i < connection_count; i++) {
OakEngineNode *upstream =
start_node.input_connection_at_all(i).node.handle();
int this_node_dist = get_distance_between_nodes(upstream, end);
int this_node_dist =
get_distance_between_nodes(upstream, end, visited);
if (this_node_dist != -1) {
return 1 + this_node_dist;
}
@@ -971,6 +978,12 @@ int get_distance_between_nodes(OakEngineNode *start, OakEngineNode *end)
return -1;
}
int get_distance_between_nodes(OakEngineNode *start, OakEngineNode *end)
{
QSet<OakEngineNode *> visited;
return get_distance_between_nodes(start, end, &visited);
}
void NodeParamView::sort_items_in_context(NodeParamViewContext *context_item)
{
QVector<QPair<NodeParamViewItem *, int>> distances;
+3 -1
View File
@@ -1836,7 +1836,9 @@ void ViewerWidget::renderer_generated_frame_for_queue()
QVariant frame = QVariant::fromValue(f);
foreach (ViewerDisplayWidget *dw, playback_devices_) {
dw->queue()->append_timewise({ Rational(), frame },
dw->queue()->append_timewise({ Rational(pf.timestamp_num,
pf.timestamp_den),
frame },
playback_speed_);
}
}
+28 -14
View File
@@ -108,6 +108,18 @@ ViewerDisplayWidget::~ViewerDisplayWidget()
MANAGEDDISPLAYWIDGET_DEFAULT_DESTRUCTOR_INNER;
}
void ViewerDisplayWidget::assign_texture(void *t, bool owned)
{
if (owned_texture_) {
oakengine_display_texture_free(owned_texture_);
}
if (t && !owned) {
oakengine_display_texture_retain(t);
}
owned_texture_ = t;
texture_ = t;
}
void ViewerDisplayWidget::set_matrix_translate(const QMatrix4x4 &mat)
{
translate_matrix_ = mat;
@@ -483,10 +495,11 @@ void ViewerDisplayWidget::on_paint()
oakengine_display_texture_height(texture_) != frame_vp.height ||
oakengine_display_texture_format(texture_) != frame_vp.format ||
oakengine_display_texture_channel_count(texture_) != 4)) {
texture_ = oakengine_display_texture_create(
renderer(), &frame_vp,
oakengine_codec_frame_data(load_handle),
oakengine_codec_frame_linesize(load_handle));
assign_texture(oakengine_display_texture_create(
renderer(), &frame_vp,
oakengine_codec_frame_data(load_handle),
oakengine_codec_frame_linesize(load_handle)),
true);
} else if (!drew_backend_neutral_frame) {
oakengine_display_texture_upload(
texture_, oakengine_codec_frame_data(load_handle),
@@ -499,7 +512,7 @@ void ViewerDisplayWidget::on_paint()
src_ren && src_ren != renderer()) {
if (oakengine_display_renderer_is_open_gl(src_ren) &&
oakengine_display_renderer_is_open_gl(renderer())) {
texture_ = load_handle;
assign_texture(load_handle, false);
} else {
// Cross-backend: download and re-upload
void *tmp_frame = oakengine_codec_frame_create();
@@ -513,24 +526,25 @@ void ViewerDisplayWidget::on_paint()
&tex_params,
oakengine_codec_frame_data(tmp_frame),
oakengine_codec_frame_linesize(tmp_frame));
texture_ = oakengine_display_texture_create(
renderer(), &tex_params,
oakengine_codec_frame_data(tmp_frame),
oakengine_codec_frame_linesize(tmp_frame));
assign_texture(oakengine_display_texture_create(
renderer(), &tex_params,
oakengine_codec_frame_data(tmp_frame),
oakengine_codec_frame_linesize(tmp_frame)),
true);
} else {
texture_ = load_handle;
assign_texture(load_handle, false);
}
oakengine_codec_frame_free(tmp_frame);
}
} else if (!drew_backend_neutral_frame) {
texture_ = load_handle;
assign_texture(load_handle, false);
}
} else {
texture_ = load_custom_texture_from_frame(load_frame_);
assign_texture(load_custom_texture_from_frame(load_frame_), true);
}
if (drew_backend_neutral_frame) {
texture_ = nullptr;
assign_texture(nullptr, true);
}
emit texture_changed(texture_);
@@ -762,7 +776,7 @@ void ViewerDisplayWidget::on_destroy()
super::on_destroy();
texture_ = nullptr;
assign_texture(nullptr, true);
if (deinterlace_texture_) {
oakengine_display_texture_free(deinterlace_texture_);
deinterlace_texture_ = nullptr;
+20
View File
@@ -338,9 +338,29 @@ private:
/**
* @brief Internal reference to the OpenGL texture to draw. Set in SetTexture() and used in paintGL().
*
* Never assign directly; use assign_texture() so ownership stays consistent.
*/
void *texture_;
/**
* @brief Owned (retained or created) reference backing texture_.
*
* texture_ may point to a texture owned by the playback queue's
* OakSharedBuffer; this member holds our own reference so texture_ can
* never dangle after the queue entry is replaced.
*/
void *owned_texture_ = nullptr;
/**
* @brief Assign texture_ with consistent ownership.
*
* Releases the previous owned reference. If owned is true, takes over the
* passed reference (e.g. freshly created); otherwise retains it. texture_
* is set to the same pointer (possibly nullptr).
*/
void assign_texture(void *t, bool owned);
/**
* @brief Internal texture to deinterlace to
*/