common: improved instance memory handling

- prevent dangling instances in failure scenarios by utilizing a reference counter.
- update the test suite to treat null pointer arguments as invalid argument failures.
This commit is contained in:
Hermet Park 2024-11-19 19:38:04 +09:00 committed by Hermet Park
parent df8fc1949c
commit 0b4f2a49fe
17 changed files with 60 additions and 35 deletions

View file

@ -2116,7 +2116,7 @@ public:
* *
* @note Experimental API * @note Experimental API
*/ */
Result set(const Picture* picture, std::function<bool(const Paint* paint, void* data)> func, void* data) noexcept; Result set(Picture* picture, std::function<bool(const Paint* paint, void* data)> func, void* data) noexcept;
/** /**
* @brief Generate a unique ID (hash key) from a given name. * @brief Generate a unique ID (hash key) from a given name.

View file

@ -50,20 +50,28 @@ static bool accessChildren(Iterator* it, function<bool(const Paint* paint, void*
/* External Class Implementation */ /* External Class Implementation */
/************************************************************************/ /************************************************************************/
Result Accessor::set(const Picture* picture, function<bool(const Paint* paint, void* data)> func, void* data) noexcept Result Accessor::set(Picture* picture, function<bool(const Paint* paint, void* data)> func, void* data) noexcept
{ {
if (!picture || !func) return Result::InvalidArguments; if (!picture || !func) return Result::InvalidArguments;
//Use the Preorder Tree-Search //Use the Preorder Tree-Search
picture->ref();
//Root //Root
if (!func(picture, data)) return Result::Success; if (!func(picture, data)) {
picture->unref();
return Result::Success;
}
//Children //Children
if (auto it = IteratorAccessor::iterator(picture)) { if (auto it = IteratorAccessor::iterator(picture)) {
accessChildren(it, func, data); accessChildren(it, func, data);
delete(it); delete(it);
} }
picture->unref(false);
return Result::Success; return Result::Success;
} }

View file

@ -68,6 +68,8 @@ Result Canvas::draw() noexcept
Result Canvas::update(Paint* paint) noexcept Result Canvas::update(Paint* paint) noexcept
{ {
TVGLOG("RENDERER", "Update S. ------------------------------ Canvas(%p)", this); TVGLOG("RENDERER", "Update S. ------------------------------ Canvas(%p)", this);
if (pImpl->paints.empty() || pImpl->status == Status::Drawing) return Result::InsufficientCondition;
auto ret = pImpl->update(paint, false); auto ret = pImpl->update(paint, false);
TVGLOG("RENDERER", "Update E. ------------------------------ Canvas(%p)", this); TVGLOG("RENDERER", "Update E. ------------------------------ Canvas(%p)", this);

View file

@ -60,10 +60,14 @@ struct Canvas::Impl
Result push(Paint* paint) Result push(Paint* paint)
{ {
//You cannot push paints during rendering. if (!paint) return Result::InvalidArguments;
if (status == Status::Drawing) return Result::InsufficientCondition;
//You cannot push paints during rendering.
if (status == Status::Drawing) {
TVG_DELETE(paint);
return Result::InsufficientCondition;
}
if (!paint) return Result::MemoryCorruption;
paint->ref(); paint->ref();
paints.push_back(paint); paints.push_back(paint);
@ -88,8 +92,6 @@ struct Canvas::Impl
Result update(Paint* paint, bool force) Result update(Paint* paint, bool force)
{ {
if (paints.empty() || status == Status::Drawing) return Result::InsufficientCondition;
Array<RenderData> clips; Array<RenderData> clips;
auto flag = RenderUpdateFlag::None; auto flag = RenderUpdateFlag::None;
if (status == Status::Damaged || force) flag = RenderUpdateFlag::All; if (status == Status::Damaged || force) flag = RenderUpdateFlag::All;
@ -109,8 +111,11 @@ struct Canvas::Impl
Result draw() Result draw()
{ {
if (status == Status::Drawing || paints.empty()) return Result::InsufficientCondition;
if (status == Status::Damaged) update(nullptr, false); if (status == Status::Damaged) update(nullptr, false);
if (status == Status::Drawing || paints.empty() || !renderer->preRender()) return Result::InsufficientCondition;
if (!renderer->preRender()) return Result::InsufficientCondition;
bool rendered = false; bool rendered = false;
for (auto paint : paints) { for (auto paint : paints) {

View file

@ -80,6 +80,9 @@ uint16_t THORVG_VERSION_NUMBER();
#define PP(A) (((Paint*)(A))->pImpl) //Access to pimpl. #define PP(A) (((Paint*)(A))->pImpl) //Access to pimpl.
#define TVG_DELETE(PAINT) \
if (PAINT->refCnt() == 0) delete(PAINT)
//for debugging //for debugging
#if 0 #if 0
#include <sys/time.h> #include <sys/time.h>

View file

@ -163,8 +163,8 @@ Paint* Paint::Impl::duplicate(Paint* ret)
ret->pImpl->opacity = opacity; ret->pImpl->opacity = opacity;
if (maskData) ret->pImpl->mask(ret, maskData->target->duplicate(), maskData->method); if (maskData) ret->mask(maskData->target->duplicate(), maskData->method);
if (clipper) ret->pImpl->clip(clipper->duplicate()); if (clipper) ret->clip(clipper->duplicate());
return ret; return ret;
} }
@ -357,7 +357,7 @@ bool Paint::Impl::bounds(float* x, float* y, float* w, float* h, bool transforme
void Paint::Impl::reset() void Paint::Impl::reset()
{ {
if (clipper) { if (clipper) {
delete(clipper); clipper->unref();
clipper = nullptr; clipper = nullptr;
} }
@ -446,6 +446,7 @@ Result Paint::clip(Paint* clipper) noexcept
{ {
if (clipper && clipper->type() != Type::Shape) { if (clipper && clipper->type() != Type::Shape) {
TVGERR("RENDERER", "Clipping only supports the Shape!"); TVGERR("RENDERER", "Clipping only supports the Shape!");
TVG_DELETE(clipper);
return Result::NonSupport; return Result::NonSupport;
} }
pImpl->clip(clipper); pImpl->clip(clipper);
@ -455,9 +456,8 @@ Result Paint::clip(Paint* clipper) noexcept
Result Paint::mask(Paint* target, MaskMethod method) noexcept Result Paint::mask(Paint* target, MaskMethod method) noexcept
{ {
if (pImpl->mask(this, target, method)) return Result::Success; if (pImpl->mask(target, method)) return Result::Success;
if (target) TVG_DELETE(target);
delete(target);
return Result::InvalidArguments; return Result::InvalidArguments;
} }

View file

@ -119,7 +119,7 @@ namespace tvg
clipper->ref(); clipper->ref();
} }
bool mask(Paint* source, Paint* target, MaskMethod method) bool mask(Paint* target, MaskMethod method)
{ {
//Invalid case //Invalid case
if ((!target && method != MaskMethod::None) || (target && method == MaskMethod::None)) return false; if ((!target && method != MaskMethod::None) || (target && method == MaskMethod::None)) return false;
@ -138,7 +138,7 @@ namespace tvg
} }
target->ref(); target->ref();
maskData->target = target; maskData->target = target;
maskData->source = source; maskData->source = paint;
maskData->method = method; maskData->method = method;
return true; return true;
} }

View file

@ -40,7 +40,7 @@ struct Saver::Impl
~Impl() ~Impl()
{ {
delete(saveModule); delete(saveModule);
delete(bg); if (bg) bg->unref();
} }
}; };
@ -102,11 +102,11 @@ Saver::~Saver()
Result Saver::save(Paint* paint, const char* filename, uint32_t quality) noexcept Result Saver::save(Paint* paint, const char* filename, uint32_t quality) noexcept
{ {
if (!paint) return Result::MemoryCorruption; if (!paint) return Result::InvalidArguments;
//Already on saving another resource. //Already on saving another resource.
if (pImpl->saveModule) { if (pImpl->saveModule) {
if (paint->refCnt() == 0) delete(paint); TVG_DELETE(paint);
return Result::InsufficientCondition; return Result::InsufficientCondition;
} }
@ -115,19 +115,22 @@ Result Saver::save(Paint* paint, const char* filename, uint32_t quality) noexcep
pImpl->saveModule = saveModule; pImpl->saveModule = saveModule;
return Result::Success; return Result::Success;
} else { } else {
if (paint->refCnt() == 0) delete(paint); TVG_DELETE(paint);
delete(saveModule); delete(saveModule);
return Result::Unknown; return Result::Unknown;
} }
} }
if (paint->refCnt() == 0) delete(paint); TVG_DELETE(paint);
return Result::NonSupport; return Result::NonSupport;
} }
Result Saver::background(Paint* paint) noexcept Result Saver::background(Paint* paint) noexcept
{ {
delete(pImpl->bg); if (!paint) return Result::InvalidArguments;
if (pImpl->bg) TVG_DELETE(pImpl->bg);
paint->ref();
pImpl->bg = paint; pImpl->bg = paint;
return Result::Success; return Result::Success;
@ -136,7 +139,7 @@ Result Saver::background(Paint* paint) noexcept
Result Saver::save(Animation* animation, const char* filename, uint32_t quality, uint32_t fps) noexcept Result Saver::save(Animation* animation, const char* filename, uint32_t quality, uint32_t fps) noexcept
{ {
if (!animation) return Result::MemoryCorruption; if (!animation) return Result::InvalidArguments;
//animation holds the picture, it must be 1 at the bottom. //animation holds the picture, it must be 1 at the bottom.
auto remove = animation->picture()->refCnt() <= 1 ? true : false; auto remove = animation->picture()->refCnt() <= 1 ? true : false;

View file

@ -69,7 +69,7 @@ Type Scene::type() const noexcept
Result Scene::push(Paint* paint) noexcept Result Scene::push(Paint* paint) noexcept
{ {
if (!paint) return Result::MemoryCorruption; if (!paint) return Result::InvalidArguments;
paint->ref(); paint->ref();
pImpl->paints.push_back(paint); pImpl->paints.push_back(paint);

View file

@ -212,7 +212,7 @@ Result Shape::fill(uint8_t r, uint8_t g, uint8_t b, uint8_t a) noexcept
Result Shape::fill(Fill* f) noexcept Result Shape::fill(Fill* f) noexcept
{ {
if (!f) return Result::MemoryCorruption; if (!f) return Result::InvalidArguments;
if (pImpl->rs.fill && pImpl->rs.fill != f) delete(pImpl->rs.fill); if (pImpl->rs.fill && pImpl->rs.fill != f) delete(pImpl->rs.fill);
pImpl->rs.fill = f; pImpl->rs.fill = f;

View file

@ -280,7 +280,7 @@ struct Shape::Impl
Result strokeFill(Fill* f) Result strokeFill(Fill* f)
{ {
if (!f) return Result::MemoryCorruption; if (!f) return Result::InvalidArguments;
if (!rs.stroke) rs.stroke = new RenderStroke(); if (!rs.stroke) rs.stroke = new RenderStroke();
if (rs.stroke->fill && rs.stroke->fill != f) delete(rs.stroke->fill); if (rs.stroke->fill && rs.stroke->fill != f) delete(rs.stroke->fill);

View file

@ -96,7 +96,7 @@ bool GifSaver::close()
{ {
this->done(); this->done();
delete(bg); if (bg) bg->unref();
bg = nullptr; bg = nullptr;
//animation holds the picture, it must be 1 at the bottom. //animation holds the picture, it must be 1 at the bottom.
@ -143,7 +143,10 @@ bool GifSaver::save(Animation* animation, Paint* bg, const char* filename, TVG_U
this->animation = animation; this->animation = animation;
if (bg) this->bg = bg->duplicate(); if (bg) {
bg->ref();
this->bg = bg;
}
this->fps = static_cast<float>(fps); this->fps = static_cast<float>(fps);
TaskScheduler::request(this); TaskScheduler::request(this);

View file

@ -71,7 +71,7 @@ TEST_CASE("Linear Gradient in shape", "[capiLinearGradient]")
REQUIRE(tvg_shape_get_gradient(shape, &gradient_ret) == TVG_RESULT_SUCCESS); REQUIRE(tvg_shape_get_gradient(shape, &gradient_ret) == TVG_RESULT_SUCCESS);
REQUIRE(gradient_ret); REQUIRE(gradient_ret);
REQUIRE(tvg_shape_set_gradient(shape, NULL) == TVG_RESULT_MEMORY_CORRUPTION); REQUIRE(tvg_shape_set_gradient(shape, NULL) == TVG_RESULT_INVALID_ARGUMENT);
REQUIRE(tvg_paint_del(shape) == TVG_RESULT_SUCCESS); REQUIRE(tvg_paint_del(shape) == TVG_RESULT_SUCCESS);
} }

View file

@ -72,7 +72,7 @@ TEST_CASE("Set gradient in shape", "[capiRadialGradient]")
REQUIRE(tvg_shape_get_gradient(shape, &gradient_ret) == TVG_RESULT_SUCCESS); REQUIRE(tvg_shape_get_gradient(shape, &gradient_ret) == TVG_RESULT_SUCCESS);
REQUIRE(gradient_ret); REQUIRE(gradient_ret);
REQUIRE(tvg_shape_set_gradient(shape, NULL) == TVG_RESULT_MEMORY_CORRUPTION); REQUIRE(tvg_shape_set_gradient(shape, NULL) == TVG_RESULT_INVALID_ARGUMENT);
REQUIRE(tvg_paint_del(shape) == TVG_RESULT_SUCCESS); REQUIRE(tvg_paint_del(shape) == TVG_RESULT_SUCCESS);
} }

View file

@ -85,6 +85,7 @@ TEST_CASE("Canvas draw", "[capiSwCanvas]")
REQUIRE(tvg_swcanvas_set_target(canvas, buffer, 200, 200, 200, TVG_COLORSPACE_ARGB8888) == TVG_RESULT_SUCCESS); REQUIRE(tvg_swcanvas_set_target(canvas, buffer, 200, 200, 200, TVG_COLORSPACE_ARGB8888) == TVG_RESULT_SUCCESS);
REQUIRE(tvg_canvas_draw(canvas) == TVG_RESULT_INSUFFICIENT_CONDITION); REQUIRE(tvg_canvas_draw(canvas) == TVG_RESULT_INSUFFICIENT_CONDITION);
REQUIRE(tvg_canvas_sync(canvas) == TVG_RESULT_INSUFFICIENT_CONDITION); REQUIRE(tvg_canvas_sync(canvas) == TVG_RESULT_INSUFFICIENT_CONDITION);
Tvg_Paint* paint = tvg_shape_new(); Tvg_Paint* paint = tvg_shape_new();

View file

@ -153,11 +153,11 @@ TEST_CASE("Set gradient text fill", "[capiText]")
REQUIRE(tvg_font_load(TEST_DIR"/Arial.ttf") == TVG_RESULT_SUCCESS); REQUIRE(tvg_font_load(TEST_DIR"/Arial.ttf") == TVG_RESULT_SUCCESS);
REQUIRE(tvg_text_set_gradient(text, NULL) == TVG_RESULT_MEMORY_CORRUPTION); REQUIRE(tvg_text_set_gradient(text, NULL) == TVG_RESULT_INVALID_ARGUMENT);
REQUIRE(tvg_text_set_font(text, "Arial", 10.0f, "") == TVG_RESULT_SUCCESS); REQUIRE(tvg_text_set_font(text, "Arial", 10.0f, "") == TVG_RESULT_SUCCESS);
REQUIRE(tvg_text_set_gradient(text, NULL) == TVG_RESULT_MEMORY_CORRUPTION); REQUIRE(tvg_text_set_gradient(text, NULL) == TVG_RESULT_INVALID_ARGUMENT);
REQUIRE(tvg_text_set_gradient(NULL, NULL) == TVG_RESULT_INVALID_ARGUMENT); REQUIRE(tvg_text_set_gradient(NULL, NULL) == TVG_RESULT_INVALID_ARGUMENT);
REQUIRE(tvg_text_set_gradient(text, gradientLin) == TVG_RESULT_SUCCESS); REQUIRE(tvg_text_set_gradient(text, gradientLin) == TVG_RESULT_SUCCESS);
REQUIRE(tvg_text_set_gradient(text, gradientRad) == TVG_RESULT_SUCCESS); REQUIRE(tvg_text_set_gradient(text, gradientRad) == TVG_RESULT_SUCCESS);

View file

@ -53,10 +53,10 @@ TEST_CASE("Pushing Paints Into Scene", "[tvgScene]")
REQUIRE(scene->push(paints[2]) == Result::Success); REQUIRE(scene->push(paints[2]) == Result::Success);
//Pushing Null Pointer //Pushing Null Pointer
REQUIRE(scene->push(nullptr) == Result::MemoryCorruption); REQUIRE(scene->push(nullptr) == Result::InvalidArguments);
//Pushing Invalid Paint //Pushing Invalid Paint
REQUIRE(scene->push(nullptr) == Result::MemoryCorruption); REQUIRE(scene->push(nullptr) == Result::InvalidArguments);
//Check list of paints //Check list of paints
auto list = scene->paints(); auto list = scene->paints();