Skip to content

Fix release handling#17

Merged
kimkulling merged 1 commit intomainfrom
bugfix/fix_tinyui_release
Mar 10, 2026
Merged

Fix release handling#17
kimkulling merged 1 commit intomainfrom
bugfix/fix_tinyui_release

Conversation

@kimkulling
Copy link
Owner

@kimkulling kimkulling commented Mar 10, 2026

Summary by CodeRabbit

  • Bug Fixes

    • Improved resource cleanup and deallocation of graphics subsystem resources.
    • Enhanced pointer safety with proper null initialization.
  • Refactor

    • Simplified the public API by removing the standalone initialization method.
    • Refined surface and image resource management.

@coderabbitai
Copy link

coderabbitai bot commented Mar 10, 2026

Walkthrough

This pull request refactors resource management throughout the codebase by removing the standalone TinyUi::init() function, adding cleanup mechanisms for image caches and SDL resources, initializing pointers safely, and updating a submodule reference.

Changes

Cohort / File(s) Summary
Submodule & Sample Update
contrib/vcpkg, samples/main.cpp
Updated vcpkg submodule pointer; changed comment text "Item 1" to "Item 1.1".
SDL Resource Management
src/backends/sdl2_renderer.h, src/backends/sdl2_renderer.cpp
Initialized SurfaceImpl::mSurface to nullptr; added new public static method releaseSurfaceImpl() for safe surface cleanup; introduced IMG_Quit() call during renderer release.
Core Initialization & Cleanup
src/tinyui.h, src/tinyui.cpp
Removed public API function TinyUi::init(); removed initialization guard logic; added pointer zeroing for renderer, window, and root during release().
Image Cache Management
src/widgets.cpp
Added static function releaseImageCache() to free cached Image objects; integrated image cache release into Widgets::clear().

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~35 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title 'Fix release handling' directly addresses the main objective of the changeset, which involves improving memory cleanup and resource release across multiple components (SDL renderer, image caches, and UI context).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch bugfix/fix_tinyui_release

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@sonarqubecloud
Copy link

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/tinyui.cpp (1)

154-168: ⚠️ Potential issue | 🔴 Critical

Critical: Image cache is not released before SDL_image shutdown, causing potential undefined behavior.

The current release order is:

  1. Renderer::releaseRenderer(ctx) → calls IMG_Quit() (shuts down SDL_image)
  2. ctx.mRoot = nullptr → just nullifies the pointer without releasing resources

The image cache containing SDL_Surface objects is never explicitly released before IMG_Quit() is called. If Widgets::clear() is called later (e.g., during destruction), releaseImageCache() will call SDL_FreeSurface() on surfaces after SDL_image has already been torn down, leading to undefined behavior or crashes.

Call Widgets::clear() before Renderer::releaseRenderer() to ensure cached surfaces are freed while SDL_image is still active.

🐛 Proposed fix for release ordering
 ret_code TinyUi::release() {
     auto &ctx = TinyUi::getContext();
     if (!ctx.mCreated) {
         return ErrorCode;
     }
+    Widgets::clear();
     Renderer::releaseRenderer(ctx);
     Renderer::releaseScreen(ctx);
     ctx.mSDLContext.mRenderer = nullptr;
     ctx.mSDLContext.mWindow = nullptr;
     ctx.mRoot = nullptr;

     ctx.mCreated = false;

     return ResultOk;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tinyui.cpp` around lines 154 - 168, TinyUi::release currently calls
Renderer::releaseRenderer(ctx) (which triggers IMG_Quit()) before freeing the
image cache, so cached SDL_Surface objects may be freed after SDL_image
shutdown; move the cache cleanup to run before shutdown by invoking
Widgets::clear() (or the function that calls releaseImageCache()) on ctx.mRoot /
widget hierarchy before calling Renderer::releaseRenderer(ctx), then proceed to
releaseScreen, null out ctx.mRoot/SDL pointers and set ctx.mCreated = false.
🧹 Nitpick comments (2)
src/widgets.cpp (1)

82-91: Good addition for proper resource cleanup.

The releaseImageCache function correctly releases surface implementations and deletes cached images. The logic properly handles nullptr checks and clears the map afterwards.

Consider using smart pointers (e.g., std::unique_ptr<Image>) in the future to avoid manual delete calls, which would address the SonarCloud warning. However, this is consistent with the existing codebase style.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/widgets.cpp` around lines 82 - 91, The current releaseImageCache
implementation manually deletes Image* from Context::mImageCache and calls
Renderer::releaseSurfaceImpl(image->mSurfaceImpl); to free resources; to
eliminate manual delete and satisfy SonarCloud warnings, refactor
Context::mImageCache to hold std::unique_ptr<Image> instead of raw Image* and
update releaseImageCache (and any insertions/uses) to release the surface via
Renderer::releaseSurfaceImpl(ptr->mSurfaceImpl) and then simply clear the map
(unique_ptr will auto-delete), ensuring all places that construct or store Image
objects use std::make_unique<Image> or transfer ownership into the map.
src/backends/sdl2_renderer.cpp (1)

489-494: Redundant clear() call before delete.

The SurfaceImpl destructor already calls clear(), so the explicit surfaceImpl->clear() call on line 491 is redundant. While harmless (due to the nullptr check in clear()), it can be simplified.

♻️ Simplified implementation
 void Renderer::releaseSurfaceImpl(SurfaceImpl *surfaceImpl) {
     if (surfaceImpl != nullptr) {
-        surfaceImpl->clear();
         delete surfaceImpl;
     }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/backends/sdl2_renderer.cpp` around lines 489 - 494, The
releaseSurfaceImpl function currently calls surfaceImpl->clear() before delete,
which is redundant because SurfaceImpl's destructor already calls clear();
update Renderer::releaseSurfaceImpl to remove the explicit surfaceImpl->clear()
call and simply delete surfaceImpl when non-null (or just call delete on the
pointer, since delete nullptr is safe), relying on SurfaceImpl's destructor to
perform cleanup.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@contrib/vcpkg`:
- Line 1: Decide whether the contrib/vcpkg submodule update should be split into
its own PR: if the vcpkg bump (contrib/vcpkg) is unrelated to the release
handling/resource cleanup fixes, move the submodule update into a separate
commit/PR that only changes contrib/vcpkg and update CI docs; if you keep it in
this PR, add a brief note in the commit message and PR description explaining
why the vcpkg revision bump (from c941d5e... to f1fe3acb...) is necessary for
the release handling fixes and include any CI/build impact details so reviewers
know the scope.

In `@src/backends/sdl2_renderer.cpp`:
- Around line 182-185: The IMG_Quit() call in Renderer::releaseRenderer() is
executed too early and can cause SDL_FreeSurface() on SDL_image-created surfaces
to be undefined; ensure Widgets::clear() (which calls releaseImageCache()) runs
before releasing the renderer and calling IMG_Quit()/SDL_Quit(). Move or remove
the IMG_Quit()/SDL_Quit() invocation from Renderer::releaseRenderer() and
instead call Widgets::clear() then releaseRenderer() (or call
IMG_Quit()/SDL_Quit() after Widgets::clear())—update TinyUi::release() flow to
call Widgets::clear() prior to invoking Renderer::releaseRenderer() so image
caches are freed while IMG subsystem is still active.

---

Outside diff comments:
In `@src/tinyui.cpp`:
- Around line 154-168: TinyUi::release currently calls
Renderer::releaseRenderer(ctx) (which triggers IMG_Quit()) before freeing the
image cache, so cached SDL_Surface objects may be freed after SDL_image
shutdown; move the cache cleanup to run before shutdown by invoking
Widgets::clear() (or the function that calls releaseImageCache()) on ctx.mRoot /
widget hierarchy before calling Renderer::releaseRenderer(ctx), then proceed to
releaseScreen, null out ctx.mRoot/SDL pointers and set ctx.mCreated = false.

---

Nitpick comments:
In `@src/backends/sdl2_renderer.cpp`:
- Around line 489-494: The releaseSurfaceImpl function currently calls
surfaceImpl->clear() before delete, which is redundant because SurfaceImpl's
destructor already calls clear(); update Renderer::releaseSurfaceImpl to remove
the explicit surfaceImpl->clear() call and simply delete surfaceImpl when
non-null (or just call delete on the pointer, since delete nullptr is safe),
relying on SurfaceImpl's destructor to perform cleanup.

In `@src/widgets.cpp`:
- Around line 82-91: The current releaseImageCache implementation manually
deletes Image* from Context::mImageCache and calls
Renderer::releaseSurfaceImpl(image->mSurfaceImpl); to free resources; to
eliminate manual delete and satisfy SonarCloud warnings, refactor
Context::mImageCache to hold std::unique_ptr<Image> instead of raw Image* and
update releaseImageCache (and any insertions/uses) to release the surface via
Renderer::releaseSurfaceImpl(ptr->mSurfaceImpl) and then simply clear the map
(unique_ptr will auto-delete), ensuring all places that construct or store Image
objects use std::make_unique<Image> or transfer ownership into the map.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0a2b1a35-5c33-45bc-b2b2-79002b9c4c4c

📥 Commits

Reviewing files that changed from the base of the PR and between 91009ce and 338c1b1.

📒 Files selected for processing (7)
  • contrib/vcpkg
  • samples/main.cpp
  • src/backends/sdl2_renderer.cpp
  • src/backends/sdl2_renderer.h
  • src/tinyui.cpp
  • src/tinyui.h
  • src/widgets.cpp
💤 Files with no reviewable changes (1)
  • src/tinyui.h

@@ -1 +1 @@
Subproject commit c941d5e450738629213a60ab8911fd26efca7c6e
Subproject commit f1fe3acb62b7aba476e48e3395c40d88478ac444
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check the vcpkg submodule update details and verify if it's related to the PR objectives.

cd contrib/vcpkg 2>/dev/null || {
  echo "Note: vcpkg submodule not yet cloned in this environment"
  exit 0
}

# Show the commit range
echo "=== Commit range ==="
echo "Old: c941d5e450738629213a60ab8911fd3aba476e48e3395c40d88478ac444"
echo "New: f1fe3acb62b7aba476e48e3395c40d88478ac444"
echo ""

# Get the short commit messages between the two commits
echo "=== Changes in vcpkg submodule ==="
git log --oneline c941d5e..f1fe3acb 2>/dev/null || echo "Could not fetch git log"

Repository: kimkulling/tiny_ui

Length of output: 253


🏁 Script executed:

# Check if .gitmodules exists and inspect submodule configuration
if [ -f .gitmodules ]; then
  echo "=== .gitmodules file ==="
  cat -n .gitmodules
else
  echo ".gitmodules not found"
fi

# Check if there's any submodule reference in git config
echo ""
echo "=== Git submodule status ==="
git config --file .gitmodules --get-regexp path

# List files in the contrib directory to see vcpkg
echo ""
echo "=== contrib directory structure ==="
ls -la contrib/ 2>/dev/null || echo "contrib directory not accessible"

# Check for any reference to vcpkg in tracked files
echo ""
echo "=== References to vcpkg in repository ==="
rg -l "vcpkg" --max-count 10 2>/dev/null || echo "No vcpkg references found via rg"

Repository: kimkulling/tiny_ui

Length of output: 605


🏁 Script executed:

# Check the actual git diff for the submodule
echo "=== Git diff for submodule change ==="
git diff --submodule=short HEAD -- contrib/vcpkg 2>/dev/null || echo "No git diff available"

# Check git status
echo ""
echo "=== Git status for contrib/vcpkg ==="
git status contrib/vcpkg 2>/dev/null || echo "No status available"

# Look at CMakePresets.json to understand vcpkg usage
echo ""
echo "=== CMakePresets.json (vcpkg-related lines) ==="
rg -A 3 -B 3 "vcpkg" CMakePresets.json 2>/dev/null | head -40 || echo "Could not find vcpkg references in CMakePresets.json"

# Check AGENTS.md for vcpkg context
echo ""
echo "=== AGENTS.md (vcpkg-related lines) ==="
rg -A 2 -B 2 "vcpkg" AGENTS.md 2>/dev/null | head -30 || echo "Could not find vcpkg references in AGENTS.md"

# Try to find any PR description or similar context
echo ""
echo "=== Checking for PR-related context ==="
git log --oneline -n 10 2>/dev/null || echo "No git log available"

Repository: kimkulling/tiny_ui

Length of output: 1154


🏁 Script executed:

# Check what's tracked for contrib/vcpkg in git
echo "=== Git ls-files for contrib/vcpkg ==="
git ls-files --stage contrib/vcpkg 2>/dev/null | head -5

# Check the HEAD commit details
echo ""
echo "=== Current commit details ==="
git show --name-status -q 2>/dev/null | head -20

# Check vcpkg.json to understand why vcpkg is needed
echo ""
echo "=== vcpkg.json content ==="
cat -n vcpkg.json 2>/dev/null | head -30 || echo "vcpkg.json not found"

# Check if there are any other dependency-related files changed
echo ""
echo "=== CMakeLists.txt (dependency-related lines) ==="
rg -B 2 -A 2 "find_package|vcpkg" CMakeLists.txt 2>/dev/null | head -40 || echo "Could not find dependency references"

Repository: kimkulling/tiny_ui

Length of output: 558


🏁 Script executed:

# Find the commit with "Fix release handling"
echo "=== Searching for PR commit ==="
git log --oneline --all | grep -i "release handling" | head -5

# Get more details on the latest commit
echo ""
echo "=== Latest commit ==="
git log -1 --format="%h %s" 2>/dev/null || echo "No commits found"

# Check if there's a previous state to compare against
echo ""
echo "=== Check git log with diff stat ==="
git log -1 --stat 2>/dev/null | head -20 || echo "No diff available"

# Try to check parent commit to see what changed
echo ""
echo "=== Check parent commit ==="
git log -2 --format="%h %s" 2>/dev/null

# List all changed files in the current commit
echo ""
echo "=== Changed files in current commit ==="
git diff --name-only HEAD^ HEAD 2>/dev/null || git diff --name-only HEAD 2>/dev/null || echo "Cannot determine changed files"

# Check for any reference to the old hash mentioned in review
echo ""
echo "=== Check if old hash is referenced anywhere ==="
rg "c941d5e450738629213a60ab8911fd3aba476e48e3395c40d88478ac444" . 2>/dev/null || echo "Old hash not found in repository"

Repository: kimkulling/tiny_ui

Length of output: 1231


🏁 Script executed:

# Check if .gitmodules is being added or modified
echo "=== Git show for .gitmodules ==="
git show HEAD:.gitmodules 2>/dev/null | head -10

# Check parent commit status
echo ""
echo "=== Git log with extended info ==="
git log --oneline --all -n 5 2>/dev/null

# Check if this is the initial commit
echo ""
echo "=== Check commit parent count ==="
git rev-list --parents HEAD 2>/dev/null | wc -l

# Get the full diff stat for submodule
echo ""
echo "=== Full git diff-tree for this commit ==="
git diff-tree --no-commit-id --name-status -r HEAD 2>/dev/null | head -20

# Verify the actual submodule pointer difference
echo ""
echo "=== Submodule info from commit ==="
git ls-tree -r HEAD contrib/vcpkg 2>/dev/null

Repository: kimkulling/tiny_ui

Length of output: 541


🏁 Script executed:

# Check submodule pointer in parent commit
echo "=== Submodule in parent commit ==="
git ls-tree -r HEAD^ contrib/vcpkg 2>/dev/null || echo "No parent commit or submodule not in parent"

# Check the commit before parent
echo ""
echo "=== Check if submodule exists in other commits ==="
git rev-list --all --source --objects | grep contrib/vcpkg | head -10

# Check what actually changed in the commit that introduced the submodule
echo ""
echo "=== Full commit message and details ==="
git show --no-patch 338c1b1 2>/dev/null

# List all commits that touched contrib/vcpkg
echo ""
echo "=== Commits touching contrib/vcpkg ==="
git log --all --oneline -- contrib/vcpkg 2>/dev/null | head -10

# Check the actual diff for contrib/vcpkg across recent commits
echo ""
echo "=== Git diff between recent commits for submodule ==="
git diff 91009ce..338c1b1 -- contrib/vcpkg 2>/dev/null | head -20 || echo "No diff or commits not comparable"

Repository: kimkulling/tiny_ui

Length of output: 1794


🏁 Script executed:

# Check if there are actual code changes related to "release handling" or SDL cleanup
echo "=== Looking for SDL/resource-related changes ==="
git diff 91009ce..338c1b1 -- src/ 2>/dev/null | head -100 || echo "No src changes or commits not comparable"

# Check full list of files changed
echo ""
echo "=== Files changed in 338c1b1 ==="
git show --name-only --pretty="" 338c1b1 2>/dev/null | head -30

# Check if there are recent changes focusing on resource/release handling
echo ""
echo "=== Check recent commits for resource/release patterns ==="
git log --all --oneline --grep="release\|cleanup\|resource\|SDL\|destructor" -i 2>/dev/null | head -10

# Check if vcpkg update frequency indicates maintenance
echo ""
echo "=== Frequency of vcpkg updates in recent history ==="
git log --all --oneline -- contrib/vcpkg 2>/dev/null | head -15

Repository: kimkulling/tiny_ui

Length of output: 4513


Clarify whether the vcpkg submodule update is essential to the release handling fixes or should be separated.

The vcpkg submodule is being updated (from c941d5e450738629213a60ab8911fd26efca7c6e to f1fe3acb62b7aba476e48e3395c40d88478ac444) as part of this commit. While vcpkg is a critical build dependency, consider whether this update is necessary for the resource cleanup improvements or if it should be addressed in a separate PR to keep the scope focused on release handling.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contrib/vcpkg` at line 1, Decide whether the contrib/vcpkg submodule update
should be split into its own PR: if the vcpkg bump (contrib/vcpkg) is unrelated
to the release handling/resource cleanup fixes, move the submodule update into a
separate commit/PR that only changes contrib/vcpkg and update CI docs; if you
keep it in this PR, add a brief note in the commit message and PR description
explaining why the vcpkg revision bump (from c941d5e... to f1fe3acb...) is
necessary for the release handling fixes and include any CI/build impact details
so reviewers know the scope.

Comment on lines +182 to 185
IMG_Quit();
SDL_DestroyRenderer(ctx.mSDLContext.mRenderer);
ctx.mSDLContext.mRenderer = nullptr;
SDL_Quit();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Verify: IMG_Quit() is called before image cache surfaces are released.

The IMG_Quit() call here is problematic because Renderer::releaseRenderer() is invoked before Widgets::clear() (which releases cached surfaces via releaseImageCache()). Calling SDL_FreeSurface() on surfaces created with SDL_image after IMG_Quit() may lead to undefined behavior.

This issue is addressed in the TinyUi::release() fix by calling Widgets::clear() before releaseRenderer().

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/backends/sdl2_renderer.cpp` around lines 182 - 185, The IMG_Quit() call
in Renderer::releaseRenderer() is executed too early and can cause
SDL_FreeSurface() on SDL_image-created surfaces to be undefined; ensure
Widgets::clear() (which calls releaseImageCache()) runs before releasing the
renderer and calling IMG_Quit()/SDL_Quit(). Move or remove the
IMG_Quit()/SDL_Quit() invocation from Renderer::releaseRenderer() and instead
call Widgets::clear() then releaseRenderer() (or call IMG_Quit()/SDL_Quit()
after Widgets::clear())—update TinyUi::release() flow to call Widgets::clear()
prior to invoking Renderer::releaseRenderer() so image caches are freed while
IMG subsystem is still active.

@kimkulling kimkulling merged commit c655f4f into main Mar 10, 2026
4 checks passed
@kimkulling kimkulling deleted the bugfix/fix_tinyui_release branch March 10, 2026 23:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant