fix(emboss): crash in slicing prep after embossing all-space text - #12137
Draft
adele-with-a-b wants to merge 1 commit into
Draft
fix(emboss): crash in slicing prep after embossing all-space text#12137adele-with-a-b wants to merge 1 commit into
adele-with-a-b wants to merge 1 commit into
Conversation
GenerateTextJob::process has three early-exit paths that return normally while leaving m_input.m_final_text_mesh empty. finalize() then commits that empty mesh into a ModelVolume, and the next background slicing prep dereferences a null vector data pointer. Line numbers below are against master at 66e4054. The three sites, all of which now throw JobException instead: EmbossJob.cpp:1404 m_chars_mesh_result.empty() in process() EmbossJob.cpp:1935 bbs.empty() in generate_mesh_according_points() EmbossJob.cpp:1940 ms.mesh->empty() in generate_mesh_according_points() The reachable trigger for the first site is text consisting only of spaces. create_all_char_mesh() clears its out-parameter at EmbossJob.cpp:1284 and only then tests for all-space input, at EmbossJob.cpp:1291-1293: wxRegEx re("^ +$"); bool is_all_space = re.Matches(input_text); if (is_all_space) { return; } Because the clear precedes that early return, m_chars_mesh_result comes back empty rather than unmodified, process() takes the bare return at :1404, and generate_mesh_according_points() never runs. m_final_text_mesh is consequently still default-constructed: InputInfo is a stack local (GLGizmoText.cpp:3336) and generate_mesh_according_points() at EmbossJob.cpp:1922 is its only writer. finalize() then commits that empty mesh. Why returning is wrong here specifically: - generate_mesh_according_points() calls mesh.clear() on input_info.m_final_text_mesh before either of its two early exits, so both leave the mesh empty rather than untouched. - GenerateTextJob::finalize() bails only on `canceled || eptr`. It has no emptiness check, so a normal return from process() is indistinguishable from success and the empty mesh is committed via create_text_volume() / recreate_model_volume(). - Print::apply -> update_volume_bboxes -> transformed_its_bbox2d then evaluates `its.vertices[its.indices.front()(0)]`. The guarding assert(!its.indices.empty()) at PrintApply.cpp:586 is compiled out in release, so front() reads through a null data pointer. Every other failure path in these two functions already throws JobException (EmbossJob.cpp:1408, 1411, 1415, 1932, 1964) — including the identical empty-mesh condition at the end of generate_mesh_according_points, which throws on mesh.its.empty(). These three sites are the outliers, not the rule. The sibling CreateObjectTextJob has the same bare return in its process() (EmbossJob.cpp:1978) but does not have this bug: its finalize() guards `m_input.m_position_points.empty()` before building geometry. That is the contract GenerateTextJob::finalize is missing, and throwing restores it without changing any signature. Note that this stops the crash but does not by itself tell the user anything. GenerateTextJob::finalize is one of only two finalize() methods in this file that do not route through _finalize() -- five others (UpdateSurfaceVolumeJob, UpdateJob, CreateObjectJob, CreateSurfaceVolumeJob, CreateVolumeJob at EmbossJob.cpp:350, 373, 456, 535, 552) call `if (!_finalize(canceled, eptr, *m_input.base)) return;`, which hands the exception to exception_process() and shows the user create_message(). GenerateTextJob and CreateObjectTextJob instead test `canceled || eptr` and discard eptr, so a JobException raised in their process() produces no message at all. That is also why the five JobExceptions already present in GenerateTextJob::process are silent today. Rewiring those two finalize() methods is a user-visible change to error reporting and is left out of this commit deliberately. No new translatable strings are introduced. Both messages already exist in this file for the same conditions — "Font doesn't have any shape for given text." at EmbossJob.cpp:1415 and 1932, and "There is no valid surface for text projection." at EmbossJob.cpp:1004 and 1024. Same shape and scope as ef96c20 ("FIX:A special font does not generate x characters, causing software crashes"), which fixed the neighbouring instance of this bug class in this file. Hardening update_volume_bboxes() itself against an empty indexed_triangle_set is a separate concern and is deliberately not bundled here: that code is shared slicing-core, and any change there has to keep PrintObjectRegions::cached_volume_ids in sync with the volumes it is walking (PrintApply.cpp:687-690 indexes it without a bounds check). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What breaks
Embossing text that contains only spaces commits a
ModelVolumeholding an empty mesh. The next background slicing prep then dereferences a null vector data pointer insidePrint::apply, taking the application down.Root cause
GenerateTextJob::processhas three early-exit paths thatreturnnormally while leavingm_input.m_final_text_meshempty.finalize()cannot tell that apart from success, so it commits the empty mesh.Line numbers are against
masterat66e405477.EmbossJob.cpp:1404m_chars_mesh_result.empty()GenerateTextJob::processEmbossJob.cpp:1935bbs.empty()generate_mesh_according_pointsEmbossJob.cpp:1940ms.mesh->empty()generate_mesh_according_pointsThree things make this fatal rather than merely a no-op:
generate_mesh_according_pointscallsmesh.clear()oninput_info.m_final_text_meshatEmbossJob.cpp:1922before either of its two early exits, so both leave the mesh empty rather than untouched.GenerateTextJob::finalizetests onlycanceled || eptr(EmbossJob.cpp:1423-1425). It has no emptiness check, so a normal return fromprocess()reads as success and the mesh is committed throughcreate_text_volume()/recreate_model_volume().Print::apply→update_volume_bboxes→transformed_its_bbox2dthen evaluatesits.vertices[its.indices.front()(0)](PrintApply.cpp:587). The guardingassert(! its.indices.empty())one line above is compiled out in release, sofront()reads through a null data pointer.Trigger
All-space text.
create_all_char_meshclears its out-parameter atEmbossJob.cpp:1284and only afterwards tests for all-space input, atEmbossJob.cpp:1291-1293:Because the clear precedes the early return,
m_chars_mesh_resultcomes back empty rather than unmodified.m_final_text_meshis then still default-constructed —InputInfois a stack local (GLGizmoText.cpp:3336) andgenerate_mesh_according_pointsis its only writer — so the volume that reaches slicing prep has an emptyindexed_triangle_set.The fix
The three sites now
throw JobExceptioninstead of returning. That is what every other failure path in these two functions already does —EmbossJob.cpp:1408,1411,1415,1932, and1964. The last of those is especially relevant: it throws on exactly this condition (mesh.its.empty()) at the end ofgenerate_mesh_according_points. The three sites being changed are the outliers, not a new convention.No signature changes, no new translatable strings. Both messages already exist in this file for these conditions — "Font doesn't have any shape for given text." at
EmbossJob.cpp:1415and1932, and "There is no valid surface for text projection." atEmbossJob.cpp:1004and1024.This is the same shape and scope as
ef96c2001("FIX:A special font does not generate x characters, causing software crashes"), which fixed the neighbouring instance of this bug class in this file.Why the sibling job doesn't have this bug
CreateObjectTextJob::processhas the identical bare return atEmbossJob.cpp:1978, but it is safe: itsfinalize()guardsm_input.m_position_points.empty()before building any geometry (EmbossJob.cpp:1990-1992). That is the contractGenerateTextJob::finalizeis missing, and throwing restores it without touching either signature.Deliberately out of scope
Two adjacent problems are left alone, because bundling either would make this change much harder to reason about:
Hardening
update_volume_bboxesagainst an emptyindexed_triangle_set. That would defend against any source of an empty volume, not just this one, and it is worth doing. But it is shared slicing-core code, and the obvious implementation — dropping empty volumes from the vector it walks — is unsafe:update_volume_bboxesis the sole writer ofPrintObjectRegions::cached_volume_ids(PrintApply.cpp:955-959), and the consumer atPrintApply.cpp:687-690indexes that vector in a loop whose condition has no bounds check. Removing volumes from the walk desynchronises the two and turns a deterministic fault into an out-of-bounds read. Doing it properly needs its own change.Error reporting for these jobs. This stops the crash but does not tell the user anything.
GenerateTextJob::finalizeandCreateObjectTextJob::finalizeare the only two of sevenfinalize()methods in this file that don't route through_finalize(); the other five (EmbossJob.cpp:350,373,456,535,552) callif (!_finalize(canceled, eptr, *m_input.base)) return;, which hands the exception toexception_process()and shows the user a message. These two testcanceled || eptrand discardeptr, which is also why the fiveJobExceptions already present inGenerateTextJob::processare silent today. Rewiring them changes user-visible error reporting and belongs in its own change.