From 9ea83477868a1ca8c710a3388bdffd324a7b00da Mon Sep 17 00:00:00 2001 From: Matthew Fernandez Date: Mon, 20 Jul 2020 17:18:40 -0700 Subject: [PATCH] fix: unescaped backslashes in node labels in xdot output Fixes #165. --- CHANGELOG.md | 1 + plugin/core/gvrender_core_dot.c | 37 ++++++++++++++++++++++++++++++++- rtest/165.dot | 4 ++++ rtest/test_regression.py | 23 ++++++++++++++++++++ 4 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 rtest/165.dot diff --git a/CHANGELOG.md b/CHANGELOG.md index f293b7ed1..98a4f6554 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - gvpr: line numbers in gvpr errors/warnings are incorrect #1594 +- Escaped backslashes are not correctly handled when producing xdot with dot #165 ## [2.44.1] - 2020-06-29 diff --git a/plugin/core/gvrender_core_dot.c b/plugin/core/gvrender_core_dot.c index f086278f2..f6b29ea02 100644 --- a/plugin/core/gvrender_core_dot.c +++ b/plugin/core/gvrender_core_dot.c @@ -245,13 +245,48 @@ static void xdot_style (GVJ_t *job) } +/** set the value of a symbol within a node, escaping backslashes + * + * The back ends that output certain text-based formats, e.g. xdot, assume that + * all characters that have special meaning within a string have already been + * escaped, with the exception of double quote ("). Hence, any string being + * constructed from user-provided input needs to be escaped for other + * problematic characters (namely \) beforehand. This is a little utility + * function to do such. + * + * @param n Node to operate on + * @param sym Symbol to set + * @param value Unescaped string + */ +static void put_escaping_backslashes(Agnode_t* n, Agsym_t *sym, const char *value) +{ + agxbuf buf; + + /* create a temporary buffer */ + agxbinit(&buf, 0, NULL); + + /* print the given string to the buffer, escaping as we go */ + for (; *value != '\0'; ++value) { + if (*value == '\\') { + agxbputc(&buf, '\\'); + } + agxbputc(&buf, *value); + } + + /* update the node's symbol to the escaped text */ + agxset(n, sym, agxbuse(&buf)); + + /* discard the buffer */ + agxbfree(&buf); +} + static void xdot_end_node(GVJ_t* job) { Agnode_t* n = job->obj->u.n; if (agxblen(xbufs[EMIT_NDRAW])) agxset(n, xd->n_draw, agxbuse(xbufs[EMIT_NDRAW])); if (agxblen(xbufs[EMIT_NLABEL])) - agxset(n, xd->n_l_draw, agxbuse(xbufs[EMIT_NLABEL])); + put_escaping_backslashes(n, xd->n_l_draw, agxbuse(xbufs[EMIT_NLABEL])); penwidth[EMIT_NDRAW] = 1; penwidth[EMIT_NLABEL] = 1; textflags[EMIT_NDRAW] = 0; diff --git a/rtest/165.dot b/rtest/165.dot new file mode 100644 index 000000000..bdc0c4b57 --- /dev/null +++ b/rtest/165.dot @@ -0,0 +1,4 @@ +// see test_regression.py:test_165() +digraph { + a [label="hello \\\" world"]; +} diff --git a/rtest/test_regression.py b/rtest/test_regression.py index 04d8f6a15..744435454 100644 --- a/rtest/test_regression.py +++ b/rtest/test_regression.py @@ -1,5 +1,6 @@ import subprocess import os +import re # The terminology used in rtest.sh is a little inconsistent. At the # end it reports the total number of tests, the number of "failures" @@ -32,6 +33,28 @@ def test_regression_failure(): # FIXME: re-enable when all tests pass on all platforms # assert result.returncode == 0 +def test_165(): + ''' + dot should be able to produce properly escaped xdot output + https://gitlab.com/graphviz/graphviz/-/issues/165 + ''' + + # locate our associated test case in this directory + input = os.path.join(os.path.dirname(__file__), '165.dot') + assert os.path.exists(input), 'unexpectedly missing test case' + + # ask Graphviz to translate it to xdot + output = subprocess.check_output(['dot', '-Txdot', input], + universal_newlines=True) + + # find the line containing the _ldraw_ attribute + ldraw = re.search(r'^\s*_ldraw_\s*=(?P.*?)$', output, re.MULTILINE) + assert ldraw is not None, 'no _ldraw_ attribute in graph' + + # this should contain the label correctly escaped + assert r'hello \\\" world' in ldraw.group('value'), \ + 'unexpected ldraw contents' + def test_1436(): ''' test a segfault from https://gitlab.com/graphviz/graphviz/-/issues/1436 has -- 2.40.0