This is an automated email from the ASF dual-hosted git repository.
swebb2066 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/logging-log4cxx.git
The following commit(s) were added to refs/heads/master by this push:
new ba4d59c6 Fix UB and locale-dependent behavior in
StringHelper::toLowerCase (#687)
ba4d59c6 is described below
commit ba4d59c6bb5eb6a7859f84ee952bc9c5ba597b3d
Author: metsw24-max <[email protected]>
AuthorDate: Mon May 25 07:35:04 2026 +0530
Fix UB and locale-dependent behavior in StringHelper::toLowerCase (#687)
---
src/main/cpp/stringhelper.cpp | 15 ++++++++++++--
src/test/cpp/helpers/stringhelpertestcase.cpp | 28 +++++++++++++++++++++++++++
2 files changed, 41 insertions(+), 2 deletions(-)
diff --git a/src/main/cpp/stringhelper.cpp b/src/main/cpp/stringhelper.cpp
index 6971ba60..c8142c11 100644
--- a/src/main/cpp/stringhelper.cpp
+++ b/src/main/cpp/stringhelper.cpp
@@ -67,9 +67,20 @@ bool StringHelper::equalsIgnoreCase(const LogString& s1,
const LogString& upper,
LogString StringHelper::toLowerCase(const LogString& s)
{
+ // Fold ASCII A-Z only. Passing a value not representable as unsigned
char
+ // (or EOF) to std::tolower(int) is undefined behaviour; with signed
char,
+ // any byte > 0x7F sign-extends to a negative int. The previous
implementation
+ // also produced locale-dependent output for the same input, which is
wrong
+ // for the callers (class names, color names, column names — all ASCII).
LogString d;
- std::transform(s.begin(), s.end(),
- std::insert_iterator<LogString>(d, d.begin()), tolower);
+ d.reserve(s.size());
+ for (auto ch : s)
+ {
+ if (ch >= static_cast<logchar>('A') && ch <=
static_cast<logchar>('Z'))
+ d.push_back(static_cast<logchar>(ch + ('a' - 'A')));
+ else
+ d.push_back(ch);
+ }
return d;
}
diff --git a/src/test/cpp/helpers/stringhelpertestcase.cpp
b/src/test/cpp/helpers/stringhelpertestcase.cpp
index d4cc2165..56064042 100644
--- a/src/test/cpp/helpers/stringhelpertestcase.cpp
+++ b/src/test/cpp/helpers/stringhelpertestcase.cpp
@@ -44,6 +44,8 @@ LOGUNIT_CLASS(StringHelperTestCase)
LOGUNIT_TEST( testEndsWith5 );
LOGUNIT_TEST( testFormatEmptyPattern );
LOGUNIT_TEST( testFormatMissingArgument );
+ LOGUNIT_TEST( testToLowerCaseAscii );
+ LOGUNIT_TEST( testToLowerCaseNonAsciiPassesThrough );
LOGUNIT_TEST_SUITE_END();
@@ -144,6 +146,32 @@ public:
LOGUNIT_ASSERT_EQUAL((LogString) LOG4CXX_STR("first {1}"),
StringHelper::format(LOG4CXX_STR("{0} {1}"), params));
}
+ void testToLowerCaseAscii()
+ {
+ LOGUNIT_ASSERT_EQUAL((LogString) LOG4CXX_STR("hello world"),
+ StringHelper::toLowerCase(LOG4CXX_STR("Hello World")));
+ }
+
+ // Regression: passing a non-ASCII byte (negative when char is signed,
or > 0xFF
+ // for wchar_t/UniChar) to ::tolower(int) is undefined behaviour. The
prior
+ // implementation also varied with the active C locale, producing
different
+ // output on different machines for the same configuration file. Verify
the
+ // non-ASCII bytes pass through unchanged regardless of locale.
+ void testToLowerCaseNonAsciiPassesThrough()
+ {
+ LogString input;
+ input.push_back(static_cast<logchar>('A'));
+ input.push_back(static_cast<logchar>(0xC9)); // uppercase 'É'
in Latin-1 / Windows-1252
+ input.push_back(static_cast<logchar>(0xE9)); // lowercase 'é'
in Latin-1 / Windows-1252
+ input.push_back(static_cast<logchar>('Z'));
+ LogString expected;
+ expected.push_back(static_cast<logchar>('a'));
+ expected.push_back(static_cast<logchar>(0xC9));
+ expected.push_back(static_cast<logchar>(0xE9));
+ expected.push_back(static_cast<logchar>('z'));
+ LOGUNIT_ASSERT_EQUAL(expected,
StringHelper::toLowerCase(input));
+ }
+
};