From c803b93e8736ed255ff1a6db5ab6add7ccea736c Mon Sep 17 00:00:00 2001
From: Snild Dolkow <snild@sony.com>
Date: Fri, 25 Aug 2023 14:49:29 +0200
Subject: [PATCH] minicheck: Add simple subtest support

This will be useful when a test runs through several examples and
fails somewhere in the middle. The subtest string replaces the
phase_info string (i.e. "during actual test") in the failure output.

Added subtest info to various tests where I found for loops.

CVE: CVE-2024-45490

Upstream-Status: Backport [https://github.com/libexpat/libexpat/commit/c803b93e8736ed255ff1a6db5ab6add7ccea736c]

Signed-off-by: Archana Polampalli <archana.polampalli@windriver.com>
---
 tests/minicheck.c | 25 +++++++++++++++++++++++++
 tests/minicheck.h | 16 ++++++++++++++++
 tests/runtests.c  | 13 +++++++++++++
 3 files changed, 54 insertions(+)

diff --git a/tests/minicheck.c b/tests/minicheck.c
index 1c65748..46db355 100644
--- a/tests/minicheck.c
+++ b/tests/minicheck.c
@@ -15,6 +15,7 @@
    Copyright (c) 2017      Rhodri James <rhodri@wildebeest.org.uk>
    Copyright (c) 2018      Marco Maggi <marco.maggi-ipsu@poste.it>
    Copyright (c) 2019      David Loffredo <loffredo@steptools.com>
+   Copyright (c) 2023      Sony Corporation / Snild Dolkow <snild@sony.com>
    Licensed under the MIT license:

    Permission is  hereby granted,  free of charge,  to any  person obtaining
@@ -37,6 +38,7 @@
    USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

+#include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <setjmp.h>
@@ -132,17 +134,35 @@ srunner_create(Suite *suite) {

 static jmp_buf env;

+#define SUBTEST_LEN (50) // informative, but not too long
 static char const *_check_current_function = NULL;
+static char _check_current_subtest[SUBTEST_LEN];
 static int _check_current_lineno = -1;
 static char const *_check_current_filename = NULL;

 void
 _check_set_test_info(char const *function, char const *filename, int lineno) {
   _check_current_function = function;
+  set_subtest("%s", "");
   _check_current_lineno = lineno;
   _check_current_filename = filename;
 }

+void
+set_subtest(char const *fmt, ...) {
+  va_list ap;
+  va_start(ap, fmt);
+  vsnprintf(_check_current_subtest, SUBTEST_LEN, fmt, ap);
+  va_end(ap);
+  // replace line feeds with spaces, for nicer error logs
+  for (size_t i = 0; i < SUBTEST_LEN; ++i) {
+    if (_check_current_subtest[i] == '\n') {
+      _check_current_subtest[i] = ' ';
+    }
+  }
+  _check_current_subtest[SUBTEST_LEN - 1] = '\0'; // ensure termination
+}
+
 static void
 handle_success(int verbosity) {
   if (verbosity >= CK_VERBOSE) {
@@ -154,6 +174,9 @@ static void
 handle_failure(SRunner *runner, int verbosity, const char *phase_info) {
   runner->nfailures++;
   if (verbosity != CK_SILENT) {
+    if (strlen(_check_current_subtest) != 0) {
+      phase_info = _check_current_subtest;
+    }
     printf("FAIL: %s (%s at %s:%d)\n", _check_current_function, phase_info,
            _check_current_filename, _check_current_lineno);
   }
@@ -170,6 +193,7 @@ srunner_run_all(SRunner *runner, int verbosity) {
     volatile int i;
     for (i = 0; i < tc->ntests; ++i) {
       runner->nchecks++;
+      set_subtest("%s", "");

       if (tc->setup != NULL) {
         /* setup */
@@ -185,6 +209,7 @@ srunner_run_all(SRunner *runner, int verbosity) {
         continue;
       }
       (tc->tests[i])();
+      set_subtest("%s", "");

       /* teardown */
       if (tc->teardown != NULL) {
diff --git a/tests/minicheck.h b/tests/minicheck.h
index cc1f835..a0ff333 100644
--- a/tests/minicheck.h
+++ b/tests/minicheck.h
@@ -15,6 +15,7 @@
    Copyright (c) 2004-2006 Fred L. Drake, Jr. <fdrake@users.sourceforge.net>
    Copyright (c) 2006-2012 Karl Waclawek <karl@waclawek.net>
    Copyright (c) 2016-2017 Sebastian Pipping <sebastian@pipping.org>
+   Copyright (c) 2023      Sony Corporation / Snild Dolkow <snild@sony.com>
    Licensed under the MIT license:

    Permission is  hereby granted,  free of charge,  to any  person obtaining
@@ -56,6 +57,19 @@ extern "C" {
 #  define __func__ __FUNCTION__
 #endif

+/* PRINTF_LIKE has two effects:
+    1. Make clang's -Wformat-nonliteral stop warning about non-literal format
+       strings in annotated functions' code.
+    2. Make both clang and gcc's -Wformat-nonliteral warn about *callers* of
+       the annotated function that use a non-literal format string.
+*/
+#  if defined(__GNUC__)
+#    define PRINTF_LIKE(fmtpos, argspos)                                       \
+      __attribute__((format(printf, fmtpos, argspos)))
+#  else
+#    define PRINTF_LIKE(fmtpos, argspos)
+#  endif
+
 #define START_TEST(testname)                                                   \
   static void testname(void) {                                                 \
     _check_set_test_info(__func__, __FILE__, __LINE__);                        \
@@ -64,6 +78,8 @@ extern "C" {
   }                                                                            \
   }

+void PRINTF_LIKE(1, 2) set_subtest(char const *fmt, ...);
+
 #define fail(msg) _fail_unless(0, __FILE__, __LINE__, msg)

 typedef void (*tcase_setup_function)(void);
diff --git a/tests/runtests.c b/tests/runtests.c
index 915fa52..3e610f7 100644
--- a/tests/runtests.c
+++ b/tests/runtests.c
@@ -18,6 +18,7 @@
    Copyright (c) 2019      David Loffredo <loffredo@steptools.com>
    Copyright (c) 2020      Tim Gates <tim.gates@iress.com>
    Copyright (c) 2021      Dong-hee Na <donghee.na@python.org>
+   Copyright (c) 2023      Sony Corporation / Snild Dolkow <snild@sony.com>
    Licensed under the MIT license:

    Permission is  hereby granted,  free of charge,  to any  person obtaining
@@ -1804,6 +1805,7 @@ START_TEST(test_ext_entity_invalid_parse) {
   const ExtFaults *fault = faults;

   for (; fault->parse_text != NULL; fault++) {
+    set_subtest("\"%s\"", fault->parse_text);
     XML_SetParamEntityParsing(g_parser, XML_PARAM_ENTITY_PARSING_ALWAYS);
     XML_SetExternalEntityRefHandler(g_parser, external_entity_faulter);
     XML_SetUserData(g_parser, (void *)fault);
@@ -1904,6 +1906,7 @@ START_TEST(test_dtd_attr_handling) {
   AttTest *test;

   for (test = attr_data; test->definition != NULL; test++) {
+    set_subtest("%s", test->definition);
     XML_SetAttlistDeclHandler(g_parser, verify_attlist_decl_handler);
     XML_SetUserData(g_parser, test);
     if (_XML_Parse_SINGLE_BYTES(g_parser, prolog, (int)strlen(prolog),
@@ -2356,6 +2359,7 @@ START_TEST(test_bad_cdata) {

   size_t i = 0;
   for (; i < sizeof(cases) / sizeof(struct CaseData); i++) {
+    set_subtest("%s", cases[i].text);
     const enum XML_Status actualStatus = _XML_Parse_SINGLE_BYTES(
         g_parser, cases[i].text, (int)strlen(cases[i].text), XML_TRUE);
     const enum XML_Error actualError = XML_GetErrorCode(g_parser);
@@ -2423,6 +2427,7 @@ START_TEST(test_bad_cdata_utf16) {
   size_t i;

   for (i = 0; i < sizeof(cases) / sizeof(struct CaseData); i++) {
+    set_subtest("case %lu", (long unsigned)(i + 1));
     enum XML_Status actual_status;
     enum XML_Error actual_error;

@@ -3323,6 +3328,7 @@ START_TEST(test_ext_entity_invalid_suspended_parse) {
   ExtFaults *fault;

   for (fault = &faults[0]; fault->parse_text != NULL; fault++) {
+    set_subtest("%s", fault->parse_text);
     XML_SetParamEntityParsing(g_parser, XML_PARAM_ENTITY_PARSING_ALWAYS);
     XML_SetExternalEntityRefHandler(g_parser,
                                     external_entity_suspending_faulter);
@@ -4311,6 +4317,7 @@ START_TEST(test_bad_ignore_section) {
   ExtFaults *fault;

   for (fault = &faults[0]; fault->parse_text != NULL; fault++) {
+    set_subtest("%s", fault->parse_text);
     XML_SetParamEntityParsing(g_parser, XML_PARAM_ENTITY_PARSING_ALWAYS);
     XML_SetExternalEntityRefHandler(g_parser, external_entity_faulter);
     XML_SetUserData(g_parser, fault);
@@ -4400,6 +4407,7 @@ START_TEST(test_external_entity_values) {
   int i;

   for (i = 0; data_004_2[i].parse_text != NULL; i++) {
+    set_subtest("%s", data_004_2[i].parse_text);
     XML_SetParamEntityParsing(g_parser, XML_PARAM_ENTITY_PARSING_ALWAYS);
     XML_SetExternalEntityRefHandler(g_parser, external_entity_valuer);
     XML_SetUserData(g_parser, &data_004_2[i]);
@@ -7585,6 +7593,7 @@ START_TEST(test_ns_separator_in_uri) {
   size_t i = 0;
   size_t failCount = 0;
   for (; i < sizeof(cases) / sizeof(cases[0]); i++) {
+    set_subtest("%s", cases[i].doc);
     XML_Parser parser = XML_ParserCreateNS(NULL, cases[i].namesep);
     XML_SetElementHandler(parser, dummy_start_element, dummy_end_element);
     if (XML_Parse(parser, cases[i].doc, (int)strlen(cases[i].doc),
@@ -7932,6 +7941,7 @@ START_TEST(test_misc_deny_internal_entity_closing_doctype_issue_317) {
   size_t inputIndex = 0;

   for (; inputIndex < sizeof(inputs) / sizeof(inputs[0]); inputIndex++) {
+    set_subtest("%s", inputs[inputIndex]);
     XML_Parser parser;
     enum XML_Status parseResult;
     int setParamEntityResult;
@@ -12078,6 +12088,7 @@ START_TEST(test_helper_unsigned_char_to_printable) {
   // Smoke test
   unsigned char uc = 0;
   for (; uc < (unsigned char)-1; uc++) {
+    set_subtest("char %u", (unsigned)uc);
     const char *const printable = unsignedCharToPrintable(uc);
     if (printable == NULL)
       fail("unsignedCharToPrintable returned NULL");
@@ -12086,8 +12097,10 @@ START_TEST(test_helper_unsigned_char_to_printable) {
   }

   // Two concrete samples
+  set_subtest("char 'A'");
   if (strcmp(unsignedCharToPrintable('A'), "A") != 0)
     fail("unsignedCharToPrintable result mistaken");
+  set_subtest("char '\\'");
   if (strcmp(unsignedCharToPrintable('\\'), "\\\\") != 0)
     fail("unsignedCharToPrintable result mistaken");
 }
--
2.40.0
