git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH 7/8] gettext: introduce GIT_GETTEXT_POISON=scrambled

From
SZEDER Gábor <szeder.dev@gmail.com>
Date
Oct 22, 2018, 20:22 UTC
Message-ID
<20181022202241.18629-8-szeder.dev@gmail.com>
In-Reply-To
<20181022202241.18629-1-szeder.dev@gmail.com>

Sometimes tests run with GETTEXT POISON fail because of a reason other than a translated string that should not have been translated. In such a case an error message from a git command in the test's verbose output is usually, well, less than helpful:

  error: # GETTEXT POISON #
or
  fatal: # GETTEXT POISON #: No such file or directory

It's especially annoying on those rare occasions when a heisenbug decides it's a good time to suddenly reveal its presence during a GETTEXT POISON test run, and all we get is an error message like these (yes, I did actually see both of the above error messages only once).

Make builtin commands' GETTEXT POISON-ed error messages more useful for debugging failures by introducing a new mode of poisoning: if $GIT_GETTEXT_POISON is set to 'scrambled', then include the original untranslated message after that "# GETTEXT_POISON #" string in a scrambled form, interspersing a '.' after each character. This way the messages will remain gibberish enough for machine consumption as they were before, but at the same time they will be relatively easily legible for humans. Take extra care to preserve printf() format conversion specifiers unaltered when inserting those dots.

Leave 'git-sh-i18n.sh' unchanged, because translatable messages in scripts often include shell variables, and they could (though currently they don't) include printf format specifiers, parameter expansions, command substitutions and whatnot, too. Dealing with those in a shell script would be too much hassle without its worth.

There is an additional benefit: as this change considerably increases the size of translated messages, it could detect cases when we try to format a translated string into a too small buffer. E.g. this change applied on old versions causes test failures because of the bug that was fixed in 2cfa83574c (bisect_next_all: convert xsnprintf to xstrfmt, 2017-02-16).

[TODO: Fallout?
       A 'printf(_("foo: %s"), var);' call includes the contents of
       'var' unscrambled in the output.  Could that hide the
       translation of a string that should not have been translated?
       I'm afraid yes: to check the output of that printf() a sloppy
       test could do:
         git plumbing-cmd >out && grep "var's content" out
       which would fail in a regular GETTEXT_POISON test run, but
       would succeed in a scrambled test run.  Does this matter in
       practice, do we care at all?
       Does gettext_scramble() need a FORMAT_PRESERVING annotation?
       Seems to work fine without it so far...]
Signed-off-by: SZEDER Gábor <szeder.dev@gmail.com>
---
 gettext.c | 54 +++++++++++++++++++++++++++++++++++++++++++++++++++---
 gettext.h | 11 +++++++++--
 2 files changed, 60 insertions(+), 5 deletions(-)
diff --git a/gettext.c b/gettext.c
index c50d1e0377..8ba7fd0bea 100644
--- a/gettext.c
+++ b/gettext.c
@@ -52,13 +52,61 @@ enum poison_mode use_gettext_poison(void)
 	static enum poison_mode poison_mode = poison_mode_uninitialized;
 	if (poison_mode == poison_mode_uninitialized) {
 		const char *v = getenv("GIT_GETTEXT_POISON");
-		if (v && *v)
-			poison_mode = poison_mode_default;
-		else
+		if (v && *v) {
+			if (!strcmp(v, "scrambled"))
+				poison_mode = poison_mode_scrambled;
+			else
+				poison_mode = poison_mode_default;
+		} else
 			poison_mode = poison_mode_none;
 	}
 	return poison_mode;
 }
+
+static int conversion_specifier_len(const char *s)
+{
+	const char printf_conversion_specifiers[] = "diouxXeEfFgGaAcsCSpnm%";
+	const char *format_end;
+
+	if (*s != '%')
+		return 0;
+
+	format_end = strpbrk(s + 1, printf_conversion_specifiers);
+	if (format_end)
+		return format_end - s;
+	else
+		return 0;
+}
+
+const char *gettext_scramble(const char *msg)
+{
+	struct strbuf sb;
+
+	strbuf_init(&sb,
+		    /* "# GETTEXT_POISON #" + ' ' + "m.e.s.s.a.g.e." + '\0' */
+		    strlen(GETTEXT_POISON_MAGIC) + 1 + 2 * strlen(msg) + 1);
+
+	strbuf_addch(&sb, ' ');
+	while (*msg) {
+		if (*msg == '\n') {
+			strbuf_addch(&sb, *(msg++));
+			continue;
+		} else if (*msg == '%') {
+			int spec_len = conversion_specifier_len(msg);
+			if (spec_len) {
+				strbuf_add(&sb, msg, spec_len);
+				msg += spec_len;
+				continue;
+			}
+		}
+
+		strbuf_addch(&sb, *(msg++));
+		strbuf_addch(&sb, '.');
+	}
+
+	/* This will be leaked... */
+	return strbuf_detach(&sb, NULL);
+}
 #endif
 
 #ifndef NO_GETTEXT
diff --git a/gettext.h b/gettext.h
index fcb6bfaa2c..d21346d9fa 100644
--- a/gettext.h
+++ b/gettext.h
@@ -45,10 +45,12 @@ static inline int gettext_width(const char *s)
 enum poison_mode {
 	poison_mode_uninitialized = -1,
 	poison_mode_none = 0,
-	poison_mode_default
+	poison_mode_default,
+	poison_mode_scrambled
 };
 
 extern enum poison_mode use_gettext_poison(void);
+extern const char *gettext_scramble(const char *msg);
 
 #define GETTEXT_POISON_MAGIC "# GETTEXT POISON #"
 #endif
@@ -60,6 +62,8 @@ static inline FORMAT_PRESERVING(1) const char *_(const char *msgid)
 #ifdef GETTEXT_POISON
 	if (use_gettext_poison() == poison_mode_default)
 		return GETTEXT_POISON_MAGIC;
+	else if (use_gettext_poison() == poison_mode_scrambled)
+		return gettext_scramble(gettext(msgid));
 #endif
 	return gettext(msgid);
 }
@@ -67,11 +71,14 @@ static inline FORMAT_PRESERVING(1) const char *_(const char *msgid)
 static inline FORMAT_PRESERVING(1) FORMAT_PRESERVING(2)
 const char *Q_(const char *msgid, const char *plu, unsigned long n)
 {
+	const char *msg = ngettext(msgid, plu, n);
 #ifdef GETTEXT_POISON
 	if (use_gettext_poison() == poison_mode_default)
 		return GETTEXT_POISON_MAGIC;
+	else if (use_gettext_poison() == poison_mode_scrambled)
+		return gettext_scramble(msg);
 #endif
-	return ngettext(msgid, plu, n);
+	return msg;
 }
 
 /* Mark msgid for translation but do not translate it. */
-- 
2.19.1.681.g6bd79da3f5
Previous: SZEDER GáborNext: Duy Nguyen
Message 10 of 49 in “Poison gettext with the Ook language”
  1. Poison gettext with the Ook languageNguyễn Thái Ngọc Duy, Oct 22, 2018
  2. SZEDER GáborOct 22, 2018
  3. 1/8 test-lib.sh: preserve GIT_GETTEXT_POISON from the environmentSZEDER Gábor, Oct 22, 2018
  4. 2/8 gettext: don't poison if GIT_GETTEXT_POISON is set but emptySZEDER Gábor, Oct 22, 2018
  5. Ævar Arnfjörð BjarmasonOct 22, 2018
  6. 3/8 lib-rebase: loosen GETTEXT_POISON check in fake editorSZEDER Gábor, Oct 22, 2018
  7. 4/8 gettext: #ifdef away GETTEXT POISON-related code from _() and Q_()SZEDER Gábor, Oct 22, 2018
  8. 5/8 gettext: put "# GETTEXT POISON #" string literal into a macroSZEDER Gábor, Oct 22, 2018
  9. 6/8 gettext: use an enum for the mode of GETTEXT POISONingSZEDER Gábor, Oct 22, 2018
  10. 7/8 gettext: introduce GIT_GETTEXT_POISON=scrambledSZEDER Gábor, Oct 22, 2018
  11. Duy NguyenOct 23, 2018
  12. 8/8 travis-ci: run GETTEXT POISON build job in scrambled mode, tooSZEDER Gábor, Oct 22, 2018
  13. Duy NguyenOct 23, 2018
  14. Jakub NarebskiOct 27, 2018
  15. Johannes SchindelinOct 22, 2018
  16. Ævar Arnfjörð BjarmasonOct 22, 2018
  17. Ævar Arnfjörð BjarmasonOct 22, 2018
  18. Junio C HamanoOct 22, 2018
  19. i18n: make GETTEXT_POISON a runtime optionÆvar Arnfjörð Bjarmason, Oct 23, 2018
  20. Junio C HamanoOct 24, 2018
  21. Jeff KingOct 24, 2018
  22. Junio C HamanoOct 25, 2018
  23. Jeff KingOct 25, 2018
  24. Ramsay JonesOct 25, 2018
  25. Jeff KingOct 25, 2018
  26. Ævar Arnfjörð BjarmasonOct 26, 2018
  27. Jeff KingOct 27, 2018
  28. Ævar Arnfjörð BjarmasonOct 27, 2018
  29. i18n: make GETTEXT_POISON a runtime optionÆvar Arnfjörð Bjarmason, Oct 24, 2018
  30. i18n: make GETTEXT_POISON a runtime optionÆvar Arnfjörð Bjarmason, Nov 1, 2018
  31. Junio C HamanoNov 2, 2018
  32. SZEDER GáborNov 2, 2018
  33. Ævar Arnfjörð BjarmasonNov 8, 2018
  34. SZEDER GáborNov 8, 2018
  35. Junio C HamanoNov 8, 2018
  36. Eric SunshineNov 8, 2018
  37. 0/2 i18n: make GETTEXT_POISON a runtime optionÆvar Arnfjörð Bjarmason, Nov 8, 2018
  38. 1/2 i18n: make GETTEXT_POISON a runtime optionÆvar Arnfjörð Bjarmason, Nov 8, 2018
  39. 2/2 Makefile: ease dynamic-gettext-poison transitionÆvar Arnfjörð Bjarmason, Nov 8, 2018
  40. Johannes SchindelinOct 23, 2018
  41. Ævar Arnfjörð BjarmasonOct 23, 2018
  42. Johannes SchindelinOct 23, 2018
  43. Duy NguyenOct 23, 2018
  44. Ævar Arnfjörð BjarmasonOct 23, 2018
  45. Duy NguyenOct 24, 2018
  46. Ævar Arnfjörð BjarmasonOct 24, 2018
  47. Junio C HamanoOct 25, 2018
  48. Jeff KingOct 25, 2018
  49. Junio C HamanoOct 27, 2018

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.