From fee05a55ebec4ebb0c87c8943909a268e1ea3cf4 Mon Sep 17 00:00:00 2001 From: jabrailkhalil Date: Wed, 30 Sep 2026 18:43:15 +0300 Subject: [PATCH 1/5] internal/graph: escape source filenames in DOT labels --- internal/graph/dotgraph.go | 2 +- internal/graph/dotgraph_test.go | 52 +++++++++++++++++++ internal/graph/testdata/compose_filenames.dot | 7 +++ 3 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 internal/graph/testdata/compose_filenames.dot diff --git a/internal/graph/dotgraph.go b/internal/graph/dotgraph.go index d704fc4e13..873aa62313 100644 --- a/internal/graph/dotgraph.go +++ b/internal/graph/dotgraph.go @@ -390,7 +390,7 @@ func multilinePrintableName(info *NodeInfo) string { infoCopy.Name = strings.ReplaceAll(infoCopy.Name, "[...]", "[…]") infoCopy.Name = strings.ReplaceAll(infoCopy.Name, ".", `\n`) if infoCopy.File != "" { - infoCopy.File = filepath.Base(infoCopy.File) + infoCopy.File = escapeForDot(filepath.Base(infoCopy.File)) } return strings.Join(infoCopy.NameComponents(), `\n`) + `\n` } diff --git a/internal/graph/dotgraph_test.go b/internal/graph/dotgraph_test.go index b375bb6225..9106606749 100644 --- a/internal/graph/dotgraph_test.go +++ b/internal/graph/dotgraph_test.go @@ -150,6 +150,18 @@ func TestComposeWithNamesThatNeedEscaping(t *testing.T) { compareGraphs(t, buf.Bytes(), "compose7.dot") } +func TestComposeWithFilenamesThatNeedEscaping(t *testing.T) { + g := baseGraph() + a, c := baseAttrsAndConfig() + g.Nodes[0].Info = NodeInfo{Name: "src", File: `src/file"name.cc`, Lineno: 10} + g.Nodes[1].Info = NodeInfo{File: "src/file\nname.cc"} + + var buf bytes.Buffer + ComposeDot(&buf, g, a, c) + + compareGraphs(t, buf.Bytes(), "compose_filenames.dot") +} + func TestComposeWithCommentsWithNewlines(t *testing.T) { g := baseGraph() a, c := baseAttrsAndConfig() @@ -301,6 +313,46 @@ func TestMultilinePrintableName(t *testing.T) { } } +func TestMultilinePrintableNameWithFilenamesThatNeedEscaping(t *testing.T) { + for _, tc := range []struct { + desc string + file string + want string + }{ + { + desc: "double quote", + file: `src/file"name.cc`, + want: `file\"name.cc`, + }, + { + desc: "newline", + file: "src/file\nname.cc", + want: `file\lname.cc`, + }, + { + desc: "backslash", + file: `src/file\name.cc`, + want: `file\\name.cc`, + }, + } { + t.Run(tc.desc, func(t *testing.T) { + if tc.desc == "backslash" && filepath.Separator == '\\' { + // On Windows a backslash separates path components. + t.Skip("backslashes cannot be part of a Windows filename") + } + ni := NodeInfo{Name: "pkg.fn", File: tc.file, Lineno: 10, Columnno: 2} + original := ni + want := `pkg\nfn\n` + tc.want + `:10:2\n` + if got := multilinePrintableName(&ni); got != want { + t.Errorf("multilinePrintableName(%#v) = %q, want %q", ni, got, want) + } + if ni != original { + t.Errorf("multilinePrintableName changed NodeInfo: got %#v, want %#v", ni, original) + } + }) + } +} + func TestTagCollapse(t *testing.T) { makeTag := func(name, unit string, value, flat, cum int64) *Tag { diff --git a/internal/graph/testdata/compose_filenames.dot b/internal/graph/testdata/compose_filenames.dot new file mode 100644 index 0000000000..b65311c20f --- /dev/null +++ b/internal/graph/testdata/compose_filenames.dot @@ -0,0 +1,7 @@ +digraph "testtitle" { +node [style=filled fillcolor="#f8f8f8"] +subgraph cluster_L { "label1" [shape=box fontsize=16 label="label1\llabel2\llabel3: \"foo\"\l" tooltip="testtitle"] } +N1 [label="src\nfile\"name.cc:10\n10 (10.00%)\nof 25 (25.00%)" id="node1" fontsize=22 shape=box tooltip="src src/file\"name.cc:10 (25)" color="#b23c00" fillcolor="#edddd5"] +N2 [label="file\lname.cc\n15 (15.00%)\nof 25 (25.00%)" id="node2" fontsize=24 shape=box tooltip="src/file\lname.cc (25)" color="#b23c00" fillcolor="#edddd5"] +N1 -> N2 [label=" 10" weight=11 color="#b28559" tooltip="src src/file\"name.cc:10 -> src/file\lname.cc (10)" labeltooltip="src src/file\"name.cc:10 -> src/file\lname.cc (10)"] +} From 0424813d6709454609188edc82d51f1409fc596f Mon Sep 17 00:00:00 2001 From: jabrailkhalil Date: Thu, 1 Oct 2026 12:59:15 +0300 Subject: [PATCH 2/5] internal/graph: consolidate filename escaping coverage Signed-off-by: jabrailkhalil --- internal/graph/dotgraph_test.go | 97 ++++++++----------- .../testdata/compose_filename_backslash.dot | 7 ++ internal/graph/testdata/compose_filenames.dot | 4 +- 3 files changed, 47 insertions(+), 61 deletions(-) create mode 100644 internal/graph/testdata/compose_filename_backslash.dot diff --git a/internal/graph/dotgraph_test.go b/internal/graph/dotgraph_test.go index 9106606749..2e251e6134 100644 --- a/internal/graph/dotgraph_test.go +++ b/internal/graph/dotgraph_test.go @@ -139,27 +139,46 @@ func TestComposeWithStandardGraphAndURL(t *testing.T) { } func TestComposeWithNamesThatNeedEscaping(t *testing.T) { - g := baseGraph() - a, c := baseAttrsAndConfig() - g.Nodes[0].Info = NodeInfo{Name: `var"src"`} - g.Nodes[1].Info = NodeInfo{Name: `var"#dest#"`} - - var buf bytes.Buffer - ComposeDot(&buf, g, a, c) - - compareGraphs(t, buf.Bytes(), "compose7.dot") -} + for _, tc := range []struct { + name string + src NodeInfo + dest NodeInfo + want string + }{ + { + name: "function names", + src: NodeInfo{Name: `var"src"`}, + dest: NodeInfo{Name: `var"#dest#"`}, + want: "compose7.dot", + }, + { + name: "filenames with quotes and newlines", + src: NodeInfo{Name: "src", File: `src/file"name.cc`, Lineno: 10, Columnno: 2}, + dest: NodeInfo{File: "src/file\nname.cc"}, + want: "compose_filenames.dot", + }, + { + name: "filenames with backslashes", + src: NodeInfo{Name: "src", File: `src/file\name.cc`, Lineno: 10}, + dest: NodeInfo{Name: "dest"}, + want: "compose_filename_backslash.dot", + }, + } { + t.Run(tc.name, func(t *testing.T) { + if tc.name == "filenames with backslashes" && filepath.Separator == '\\' { + t.Skip("backslashes cannot be part of a Windows filename") + } + g := baseGraph() + a, c := baseAttrsAndConfig() + g.Nodes[0].Info = tc.src + g.Nodes[1].Info = tc.dest -func TestComposeWithFilenamesThatNeedEscaping(t *testing.T) { - g := baseGraph() - a, c := baseAttrsAndConfig() - g.Nodes[0].Info = NodeInfo{Name: "src", File: `src/file"name.cc`, Lineno: 10} - g.Nodes[1].Info = NodeInfo{File: "src/file\nname.cc"} + var buf bytes.Buffer + ComposeDot(&buf, g, a, c) - var buf bytes.Buffer - ComposeDot(&buf, g, a, c) - - compareGraphs(t, buf.Bytes(), "compose_filenames.dot") + compareGraphs(t, buf.Bytes(), tc.want) + }) + } } func TestComposeWithCommentsWithNewlines(t *testing.T) { @@ -313,46 +332,6 @@ func TestMultilinePrintableName(t *testing.T) { } } -func TestMultilinePrintableNameWithFilenamesThatNeedEscaping(t *testing.T) { - for _, tc := range []struct { - desc string - file string - want string - }{ - { - desc: "double quote", - file: `src/file"name.cc`, - want: `file\"name.cc`, - }, - { - desc: "newline", - file: "src/file\nname.cc", - want: `file\lname.cc`, - }, - { - desc: "backslash", - file: `src/file\name.cc`, - want: `file\\name.cc`, - }, - } { - t.Run(tc.desc, func(t *testing.T) { - if tc.desc == "backslash" && filepath.Separator == '\\' { - // On Windows a backslash separates path components. - t.Skip("backslashes cannot be part of a Windows filename") - } - ni := NodeInfo{Name: "pkg.fn", File: tc.file, Lineno: 10, Columnno: 2} - original := ni - want := `pkg\nfn\n` + tc.want + `:10:2\n` - if got := multilinePrintableName(&ni); got != want { - t.Errorf("multilinePrintableName(%#v) = %q, want %q", ni, got, want) - } - if ni != original { - t.Errorf("multilinePrintableName changed NodeInfo: got %#v, want %#v", ni, original) - } - }) - } -} - func TestTagCollapse(t *testing.T) { makeTag := func(name, unit string, value, flat, cum int64) *Tag { diff --git a/internal/graph/testdata/compose_filename_backslash.dot b/internal/graph/testdata/compose_filename_backslash.dot new file mode 100644 index 0000000000..ed899fd2b4 --- /dev/null +++ b/internal/graph/testdata/compose_filename_backslash.dot @@ -0,0 +1,7 @@ +digraph "testtitle" { +node [style=filled fillcolor="#f8f8f8"] +subgraph cluster_L { "label1" [shape=box fontsize=16 label="label1\llabel2\llabel3: \"foo\"\l" tooltip="testtitle"] } +N1 [label="src\nfile\\name.cc:10\n10 (10.00%)\nof 25 (25.00%)" id="node1" fontsize=22 shape=box tooltip="src src/file\\name.cc:10 (25)" color="#b23c00" fillcolor="#edddd5"] +N2 [label="dest\n15 (15.00%)\nof 25 (25.00%)" id="node2" fontsize=24 shape=box tooltip="dest (25)" color="#b23c00" fillcolor="#edddd5"] +N1 -> N2 [label=" 10" weight=11 color="#b28559" tooltip="src src/file\\name.cc:10 -> dest (10)" labeltooltip="src src/file\\name.cc:10 -> dest (10)"] +} diff --git a/internal/graph/testdata/compose_filenames.dot b/internal/graph/testdata/compose_filenames.dot index b65311c20f..8598096180 100644 --- a/internal/graph/testdata/compose_filenames.dot +++ b/internal/graph/testdata/compose_filenames.dot @@ -1,7 +1,7 @@ digraph "testtitle" { node [style=filled fillcolor="#f8f8f8"] subgraph cluster_L { "label1" [shape=box fontsize=16 label="label1\llabel2\llabel3: \"foo\"\l" tooltip="testtitle"] } -N1 [label="src\nfile\"name.cc:10\n10 (10.00%)\nof 25 (25.00%)" id="node1" fontsize=22 shape=box tooltip="src src/file\"name.cc:10 (25)" color="#b23c00" fillcolor="#edddd5"] +N1 [label="src\nfile\"name.cc:10:2\n10 (10.00%)\nof 25 (25.00%)" id="node1" fontsize=22 shape=box tooltip="src src/file\"name.cc:10:2 (25)" color="#b23c00" fillcolor="#edddd5"] N2 [label="file\lname.cc\n15 (15.00%)\nof 25 (25.00%)" id="node2" fontsize=24 shape=box tooltip="src/file\lname.cc (25)" color="#b23c00" fillcolor="#edddd5"] -N1 -> N2 [label=" 10" weight=11 color="#b28559" tooltip="src src/file\"name.cc:10 -> src/file\lname.cc (10)" labeltooltip="src src/file\"name.cc:10 -> src/file\lname.cc (10)"] +N1 -> N2 [label=" 10" weight=11 color="#b28559" tooltip="src src/file\"name.cc:10:2 -> src/file\lname.cc (10)" labeltooltip="src src/file\"name.cc:10:2 -> src/file\lname.cc (10)"] } From b35da650c9b29654f64f715951f23ba012da7254 Mon Sep 17 00:00:00 2001 From: jabrailkhalil Date: Fri, 2 Oct 2026 13:48:12 +0300 Subject: [PATCH 3/5] internal/graph: keep one golden file per compose test --- internal/graph/dotgraph_test.go | 58 +++++++------------ internal/graph/testdata/compose7.dot | 6 +- internal/graph/testdata/compose_filenames.dot | 7 --- 3 files changed, 24 insertions(+), 47 deletions(-) delete mode 100644 internal/graph/testdata/compose_filenames.dot diff --git a/internal/graph/dotgraph_test.go b/internal/graph/dotgraph_test.go index 2e251e6134..97739744bf 100644 --- a/internal/graph/dotgraph_test.go +++ b/internal/graph/dotgraph_test.go @@ -139,46 +139,30 @@ func TestComposeWithStandardGraphAndURL(t *testing.T) { } func TestComposeWithNamesThatNeedEscaping(t *testing.T) { - for _, tc := range []struct { - name string - src NodeInfo - dest NodeInfo - want string - }{ - { - name: "function names", - src: NodeInfo{Name: `var"src"`}, - dest: NodeInfo{Name: `var"#dest#"`}, - want: "compose7.dot", - }, - { - name: "filenames with quotes and newlines", - src: NodeInfo{Name: "src", File: `src/file"name.cc`, Lineno: 10, Columnno: 2}, - dest: NodeInfo{File: "src/file\nname.cc"}, - want: "compose_filenames.dot", - }, - { - name: "filenames with backslashes", - src: NodeInfo{Name: "src", File: `src/file\name.cc`, Lineno: 10}, - dest: NodeInfo{Name: "dest"}, - want: "compose_filename_backslash.dot", - }, - } { - t.Run(tc.name, func(t *testing.T) { - if tc.name == "filenames with backslashes" && filepath.Separator == '\\' { - t.Skip("backslashes cannot be part of a Windows filename") - } - g := baseGraph() - a, c := baseAttrsAndConfig() - g.Nodes[0].Info = tc.src - g.Nodes[1].Info = tc.dest + g := baseGraph() + a, c := baseAttrsAndConfig() + g.Nodes[0].Info = NodeInfo{Name: `var"src"`, File: `src/file"name.cc`, Lineno: 10, Columnno: 2} + g.Nodes[1].Info = NodeInfo{Name: `var"#dest#"`, File: "src/file\nname.cc"} - var buf bytes.Buffer - ComposeDot(&buf, g, a, c) + var buf bytes.Buffer + ComposeDot(&buf, g, a, c) - compareGraphs(t, buf.Bytes(), tc.want) - }) + compareGraphs(t, buf.Bytes(), "compose7.dot") +} + +func TestComposeWithFilenamesWithBackslashes(t *testing.T) { + if filepath.Separator == '\\' { + t.Skip("backslashes cannot be part of a Windows filename") } + g := baseGraph() + a, c := baseAttrsAndConfig() + g.Nodes[0].Info = NodeInfo{Name: "src", File: `src/file\name.cc`, Lineno: 10} + g.Nodes[1].Info = NodeInfo{Name: "dest"} + + var buf bytes.Buffer + ComposeDot(&buf, g, a, c) + + compareGraphs(t, buf.Bytes(), "compose_filename_backslash.dot") } func TestComposeWithCommentsWithNewlines(t *testing.T) { diff --git a/internal/graph/testdata/compose7.dot b/internal/graph/testdata/compose7.dot index 8f749a7797..642bbb63c0 100644 --- a/internal/graph/testdata/compose7.dot +++ b/internal/graph/testdata/compose7.dot @@ -1,7 +1,7 @@ digraph "testtitle" { node [style=filled fillcolor="#f8f8f8"] subgraph cluster_L { "label1" [shape=box fontsize=16 label="label1\llabel2\llabel3: \"foo\"\l" tooltip="testtitle"] } -N1 [label="var\"src\"\n10 (10.00%)\nof 25 (25.00%)" id="node1" fontsize=22 shape=box tooltip="var\"src\" (25)" color="#b23c00" fillcolor="#edddd5"] -N2 [label="var\"#dest#\"\n15 (15.00%)\nof 25 (25.00%)" id="node2" fontsize=24 shape=box tooltip="var\"#dest#\" (25)" color="#b23c00" fillcolor="#edddd5"] -N1 -> N2 [label=" 10" weight=11 color="#b28559" tooltip="var\"src\" -> var\"#dest#\" (10)" labeltooltip="var\"src\" -> var\"#dest#\" (10)"] +N1 [label="var\"src\"\nfile\"name.cc:10:2\n10 (10.00%)\nof 25 (25.00%)" id="node1" fontsize=22 shape=box tooltip="var\"src\" src/file\"name.cc:10:2 (25)" color="#b23c00" fillcolor="#edddd5"] +N2 [label="var\"#dest#\"\nfile\lname.cc\n15 (15.00%)\nof 25 (25.00%)" id="node2" fontsize=24 shape=box tooltip="var\"#dest#\" src/file\lname.cc (25)" color="#b23c00" fillcolor="#edddd5"] +N1 -> N2 [label=" 10" weight=11 color="#b28559" tooltip="var\"src\" src/file\"name.cc:10:2 -> var\"#dest#\" src/file\lname.cc (10)" labeltooltip="var\"src\" src/file\"name.cc:10:2 -> var\"#dest#\" src/file\lname.cc (10)"] } diff --git a/internal/graph/testdata/compose_filenames.dot b/internal/graph/testdata/compose_filenames.dot deleted file mode 100644 index 8598096180..0000000000 --- a/internal/graph/testdata/compose_filenames.dot +++ /dev/null @@ -1,7 +0,0 @@ -digraph "testtitle" { -node [style=filled fillcolor="#f8f8f8"] -subgraph cluster_L { "label1" [shape=box fontsize=16 label="label1\llabel2\llabel3: \"foo\"\l" tooltip="testtitle"] } -N1 [label="src\nfile\"name.cc:10:2\n10 (10.00%)\nof 25 (25.00%)" id="node1" fontsize=22 shape=box tooltip="src src/file\"name.cc:10:2 (25)" color="#b23c00" fillcolor="#edddd5"] -N2 [label="file\lname.cc\n15 (15.00%)\nof 25 (25.00%)" id="node2" fontsize=24 shape=box tooltip="src/file\lname.cc (25)" color="#b23c00" fillcolor="#edddd5"] -N1 -> N2 [label=" 10" weight=11 color="#b28559" tooltip="src src/file\"name.cc:10:2 -> src/file\lname.cc (10)" labeltooltip="src src/file\"name.cc:10:2 -> src/file\lname.cc (10)"] -} From cb195a710d1e9be69493270bf13ea870f2c4ad6b Mon Sep 17 00:00:00 2001 From: jabrailkhalil Date: Mon, 12 Oct 2026 01:37:40 +0300 Subject: [PATCH 4/5] test: consolidate filename escaping coverage --- internal/graph/dotgraph_test.go | 21 +++++++------------ .../testdata/compose_filename_backslash.dot | 7 ------- 2 files changed, 8 insertions(+), 20 deletions(-) delete mode 100644 internal/graph/testdata/compose_filename_backslash.dot diff --git a/internal/graph/dotgraph_test.go b/internal/graph/dotgraph_test.go index 97739744bf..3988ac78ab 100644 --- a/internal/graph/dotgraph_test.go +++ b/internal/graph/dotgraph_test.go @@ -148,21 +148,16 @@ func TestComposeWithNamesThatNeedEscaping(t *testing.T) { ComposeDot(&buf, g, a, c) compareGraphs(t, buf.Bytes(), "compose7.dot") -} -func TestComposeWithFilenamesWithBackslashes(t *testing.T) { - if filepath.Separator == '\\' { - t.Skip("backslashes cannot be part of a Windows filename") + // Backslashes are filename characters only on Unix. + if filepath.Separator != '\\' { + g.Nodes[0].Info.File = `src/file\name.cc` + buf.Reset() + ComposeDot(&buf, g, a, c) + if !strings.Contains(buf.String(), `\nfile\\name.cc:10:2\n`) { + t.Errorf("Compose did not escape the backslash in the filename label: %s", &buf) + } } - g := baseGraph() - a, c := baseAttrsAndConfig() - g.Nodes[0].Info = NodeInfo{Name: "src", File: `src/file\name.cc`, Lineno: 10} - g.Nodes[1].Info = NodeInfo{Name: "dest"} - - var buf bytes.Buffer - ComposeDot(&buf, g, a, c) - - compareGraphs(t, buf.Bytes(), "compose_filename_backslash.dot") } func TestComposeWithCommentsWithNewlines(t *testing.T) { diff --git a/internal/graph/testdata/compose_filename_backslash.dot b/internal/graph/testdata/compose_filename_backslash.dot deleted file mode 100644 index ed899fd2b4..0000000000 --- a/internal/graph/testdata/compose_filename_backslash.dot +++ /dev/null @@ -1,7 +0,0 @@ -digraph "testtitle" { -node [style=filled fillcolor="#f8f8f8"] -subgraph cluster_L { "label1" [shape=box fontsize=16 label="label1\llabel2\llabel3: \"foo\"\l" tooltip="testtitle"] } -N1 [label="src\nfile\\name.cc:10\n10 (10.00%)\nof 25 (25.00%)" id="node1" fontsize=22 shape=box tooltip="src src/file\\name.cc:10 (25)" color="#b23c00" fillcolor="#edddd5"] -N2 [label="dest\n15 (15.00%)\nof 25 (25.00%)" id="node2" fontsize=24 shape=box tooltip="dest (25)" color="#b23c00" fillcolor="#edddd5"] -N1 -> N2 [label=" 10" weight=11 color="#b28559" tooltip="src src/file\\name.cc:10 -> dest (10)" labeltooltip="src src/file\\name.cc:10 -> dest (10)"] -} From e2366c6e3b1fa110c74342a1e59f0839222dd3ef Mon Sep 17 00:00:00 2001 From: jabrailkhalil Date: Mon, 12 Oct 2026 15:45:14 +0300 Subject: [PATCH 5/5] test: keep filename escaping regression concise --- internal/graph/dotgraph_test.go | 12 +----------- internal/graph/testdata/compose7.dot | 4 ++-- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/internal/graph/dotgraph_test.go b/internal/graph/dotgraph_test.go index 3988ac78ab..1a7444b396 100644 --- a/internal/graph/dotgraph_test.go +++ b/internal/graph/dotgraph_test.go @@ -142,22 +142,12 @@ func TestComposeWithNamesThatNeedEscaping(t *testing.T) { g := baseGraph() a, c := baseAttrsAndConfig() g.Nodes[0].Info = NodeInfo{Name: `var"src"`, File: `src/file"name.cc`, Lineno: 10, Columnno: 2} - g.Nodes[1].Info = NodeInfo{Name: `var"#dest#"`, File: "src/file\nname.cc"} + g.Nodes[1].Info = NodeInfo{Name: `var"#dest#"`} var buf bytes.Buffer ComposeDot(&buf, g, a, c) compareGraphs(t, buf.Bytes(), "compose7.dot") - - // Backslashes are filename characters only on Unix. - if filepath.Separator != '\\' { - g.Nodes[0].Info.File = `src/file\name.cc` - buf.Reset() - ComposeDot(&buf, g, a, c) - if !strings.Contains(buf.String(), `\nfile\\name.cc:10:2\n`) { - t.Errorf("Compose did not escape the backslash in the filename label: %s", &buf) - } - } } func TestComposeWithCommentsWithNewlines(t *testing.T) { diff --git a/internal/graph/testdata/compose7.dot b/internal/graph/testdata/compose7.dot index 642bbb63c0..f83dc084bd 100644 --- a/internal/graph/testdata/compose7.dot +++ b/internal/graph/testdata/compose7.dot @@ -2,6 +2,6 @@ digraph "testtitle" { node [style=filled fillcolor="#f8f8f8"] subgraph cluster_L { "label1" [shape=box fontsize=16 label="label1\llabel2\llabel3: \"foo\"\l" tooltip="testtitle"] } N1 [label="var\"src\"\nfile\"name.cc:10:2\n10 (10.00%)\nof 25 (25.00%)" id="node1" fontsize=22 shape=box tooltip="var\"src\" src/file\"name.cc:10:2 (25)" color="#b23c00" fillcolor="#edddd5"] -N2 [label="var\"#dest#\"\nfile\lname.cc\n15 (15.00%)\nof 25 (25.00%)" id="node2" fontsize=24 shape=box tooltip="var\"#dest#\" src/file\lname.cc (25)" color="#b23c00" fillcolor="#edddd5"] -N1 -> N2 [label=" 10" weight=11 color="#b28559" tooltip="var\"src\" src/file\"name.cc:10:2 -> var\"#dest#\" src/file\lname.cc (10)" labeltooltip="var\"src\" src/file\"name.cc:10:2 -> var\"#dest#\" src/file\lname.cc (10)"] +N2 [label="var\"#dest#\"\n15 (15.00%)\nof 25 (25.00%)" id="node2" fontsize=24 shape=box tooltip="var\"#dest#\" (25)" color="#b23c00" fillcolor="#edddd5"] +N1 -> N2 [label=" 10" weight=11 color="#b28559" tooltip="var\"src\" src/file\"name.cc:10:2 -> var\"#dest#\" (10)" labeltooltip="var\"src\" src/file\"name.cc:10:2 -> var\"#dest#\" (10)"] }