fix(diagram): set an empty PlantUML operand id when there is none

dg_puml_operand set *label up front but *id only on its success paths, so an
opener without its closer (`Alice ->: x`, `[-> Alice: x`, `class [Unclosed`)
returned with *id unwritten. dg_puml_relation and dg_puml_decl then read an
uninitialized dg_str_t and passed it to dg_element, which crashed the index
worker on some stacks. The operand now starts with an empty id, so those lines
take the existing `!lid.n` / `!rid.n` exits and declarations make no element.

Fixes #2818

Signed-off-by: Rodion Kazennov <nefayran@gmail.com>
This commit is contained in:
Rodion Kazennov committed 2026-10-29 05:01:23 +00:00
1 parent 3be5a02708
commit d68cd2e2af
2 files changed
+34 -1

No files matched your search

+3 -1
View File
@@ -1225,9 +1225,11 @@ static bool dg_word_at(dg_str_t s, const char *word) {
}
/* An operand of a PlantUML line: `[Comp]`, `(use case)`, `:actor:`, `"text"`,
* an id, or `[*]`; its id in *id; the position after it (i when none). */
* an id, or `[*]`; its id in *id; the position after it (i and an empty *id
* when none, as for the diagram edge in `A ->: x` or `[-> A: x`). */
static size_t dg_puml_operand(dg_str_t s, size_t i, dg_str_t *id, dg_str_t *label) {
i = dg_skip_ws(s, i);
*id = dg_s(NULL, 0);
*label = dg_s(NULL, 0);
if (i + CBM_SZ_3 <= s.n && memcmp(s.p + i, "[*]", CBM_SZ_3) == 0) {
*id = dg_s(s.p + i, CBM_SZ_3);
+31
View File
@@ -329,6 +329,36 @@ TEST(diagrams_plantuml) {
PASS();
}
/* #2818: a sequence message to or from the diagram edge (`A ->: x`, `A <-: x`,
* `[-> A: x`) and a declaration with an unclosed bracket have no operand id;
* they used to read an uninitialized one and crash the index worker. */
TEST(diagrams_plantuml_edge_messages) {
CBMFileResult *r = dg_file("@startuml\n"
"participant Alice\n"
"Alice ->: out\n"
"Alice <-: in\n"
"[-> Alice: from the edge\n"
"Alice -> Bob : hello\n"
"class [Unclosed\n"
"@enduml\n",
"docs/edge.puml");
ASSERT_NOT_NULL(r);
ASSERT_NOT_NULL(dg_def(r, "edge#diagram-1.Alice"));
ASSERT_NOT_NULL(dg_def(r, "edge#diagram-1.Bob"));
ASSERT_EQ(r->diagram_arrows.count, 1);
ASSERT_EQ(dg_arrows(r, "#diagram-1.Alice", "#diagram-1.Bob", "message", "hello"), 1);
int elements = 0;
for (int i = 0; i < r->defs.count; i++) {
const char *label = r->defs.items[i].label;
if (label && strcmp(label, "DiagramElement") == 0) {
elements++;
}
}
ASSERT_EQ(elements, 2);
cbm_free_result(r);
PASS();
}
TEST(diagrams_dot) {
CBMFileResult *r =
dg_file("digraph G {\n"
@@ -1101,6 +1131,7 @@ SUITE(diagrams) {
RUN_TEST(diagrams_mermaid_kinds);
RUN_TEST(diagrams_other_fences);
RUN_TEST(diagrams_plantuml);
RUN_TEST(diagrams_plantuml_edge_messages);
RUN_TEST(diagrams_dot);
RUN_TEST(diagrams_d2);
RUN_TEST(diagrams_drawio);