This is an automated email from the ASF dual-hosted git repository.
jimjag pushed a commit to branch AOO42X
in repository https://gitbox.apache.org/repos/asf/openoffice.git
The following commit(s) were added to refs/heads/AOO42X by this push:
new a3755bb196 rtl: guard rtl_str/rtl_ustr entry points against NULL in
debug builds
a3755bb196 is described below
commit a3755bb1965cae69a2694ff18ee39835b152d832
Author: Jim Jagielski <[email protected]>
AuthorDate: Fri Aug 7 06:54:38 2026 -0400
rtl: guard rtl_str/rtl_ustr entry points against NULL in debug builds
(cherry picked from commit f0853378f1a90e669189f0f58595ee993d034643,
source-only —
the accompanying qa/rtl/ostring and qa/rtl/oustring test rewrites are not
part of AOO42X's build.lst and ENABLE_UNIT_TESTS is off by default, so they
don't apply here)
---
main/sal/rtl/source/strtmpl.c | 78 +++++++++++++++++++++++++++++++++++++++++++
main/sal/rtl/source/ustring.c | 64 +++++++++++++++++++++++++++++++++--
2 files changed, 140 insertions(+), 2 deletions(-)
diff --git a/main/sal/rtl/source/strtmpl.c b/main/sal/rtl/source/strtmpl.c
index 94877c31d4..e27a52f4de 100644
--- a/main/sal/rtl/source/strtmpl.c
+++ b/main/sal/rtl/source/strtmpl.c
@@ -55,6 +55,63 @@ inline void rtl_str_ImplCopy( IMPL_RTL_STRCODE* pDest,
} \
}
+/* ======================================================================= */
+/* NULL-pointer guards */
+/* */
+/* The C-string functions below document (see rtl/string.h, rtl/ustring.h) */
+/* that their string arguments must be non-NULL, null-terminated strings. */
+/* Passing NULL is a caller error. In non-product builds (OSL_DEBUG_LEVEL */
+/* > 0) we diagnose it loudly via OSL_PRECOND and then fall back to defined */
+/* behaviour so the diagnostic build never dereferences a NULL pointer. */
+/* */
+/* In product builds the guards compile away entirely: callers must honour */
+/* the documented non-NULL contract, and we add zero release-build cost */
+/* (not even a pointer test) to these hot string primitives. */
+/* */
+/* Even in debug, the guards run exactly once, at function entry: they are */
+/* OUTSIDE the per-character processing loops, so they do not change */
+/* string-processing throughput. */
+/* ======================================================================= */
+
+#define IMPL_RTL_STR_GUARD_MSG \
+ "rtl string function: NULL pointer passed; the documented contract " \
+ "requires a non-NULL, null-terminated string"
+
+#if OSL_DEBUG_LEVEL > 0
+
+/* Read-only argument: treat a NULL pointer as the empty string. */
+static const IMPL_RTL_STRCODE aImplGuardEmptyStr = 0;
+#define IMPL_RTL_STR_NULL_AS_EMPTY( pStr ) \
+ do { \
+ OSL_PRECOND( (pStr) != NULL, IMPL_RTL_STR_GUARD_MSG ); \
+ if ( !(pStr) ) \
+ (pStr) = &aImplGuardEmptyStr; \
+ } while (0)
+
+/* Argument that cannot be substituted (returns a value): bail out early. */
+#define IMPL_RTL_STR_NULL_RETURN( pStr, _ret ) \
+ do { \
+ OSL_PRECOND( (pStr) != NULL, IMPL_RTL_STR_GUARD_MSG ); \
+ if ( !(pStr) ) \
+ return _ret; \
+ } while (0)
+
+/* Same, for functions returning void. */
+#define IMPL_RTL_STR_NULL_RETURN_VOID( pStr ) \
+ do { \
+ OSL_PRECOND( (pStr) != NULL, IMPL_RTL_STR_GUARD_MSG ); \
+ if ( !(pStr) ) \
+ return; \
+ } while (0)
+
+#else /* product build: guards compile away, callers must honour contract */
+
+#define IMPL_RTL_STR_NULL_AS_EMPTY( pStr ) ((void)0)
+#define IMPL_RTL_STR_NULL_RETURN( pStr, _ret ) ((void)0)
+#define IMPL_RTL_STR_NULL_RETURN_VOID( pStr ) ((void)0)
+
+#endif
+
/* ======================================================================= */
/* C-String functions which could be used without the String-Class */
/* ======================================================================= */
@@ -62,6 +119,10 @@ inline void rtl_str_ImplCopy( IMPL_RTL_STRCODE* pDest,
sal_Int32 SAL_CALL IMPL_RTL_STRNAME( getLength )( const IMPL_RTL_STRCODE* pStr
)
{
const IMPL_RTL_STRCODE* pTempStr = pStr;
+ /* A NULL string has length 0. Guarding getLength here also protects
+ hashCode, lastIndexOfChar, indexOfStr, lastIndexOfStr and trim, which
+ all start by calling getLength and then a length-bounded helper. */
+ IMPL_RTL_STR_NULL_RETURN( pStr, 0 );
while( *pTempStr )
pTempStr++;
return pTempStr-pStr;
@@ -73,6 +134,10 @@ sal_Int32 SAL_CALL IMPL_RTL_STRNAME( compare )( const
IMPL_RTL_STRCODE* pStr1,
const IMPL_RTL_STRCODE* pStr2 )
{
sal_Int32 nRet;
+ /* A NULL argument is treated as the empty string; the loop below then
+ yields the correct ordering (empty < any non-empty string). */
+ IMPL_RTL_STR_NULL_AS_EMPTY( pStr1 );
+ IMPL_RTL_STR_NULL_AS_EMPTY( pStr2 );
while ( ((nRet = ((sal_Int32)(IMPL_RTL_USTRCODE(*pStr1)))-
((sal_Int32)(IMPL_RTL_USTRCODE(*pStr2)))) == 0) &&
*pStr2 )
@@ -165,6 +230,9 @@ sal_Int32 SAL_CALL IMPL_RTL_STRNAME( compareIgnoreAsciiCase
)( const IMPL_RTL_ST
sal_Int32 nRet;
sal_Int32 c1;
sal_Int32 c2;
+ /* A NULL argument is treated as the empty string. */
+ IMPL_RTL_STR_NULL_AS_EMPTY( pStr1 );
+ IMPL_RTL_STR_NULL_AS_EMPTY( pStr2 );
do
{
/* If character between 'A' and 'Z', than convert it to lowercase */
@@ -324,6 +392,8 @@ sal_Int32 SAL_CALL IMPL_RTL_STRNAME( indexOfChar )( const
IMPL_RTL_STRCODE* pStr
IMPL_RTL_STRCODE c )
{
const IMPL_RTL_STRCODE* pTempStr = pStr;
+ /* Nothing can be found in a NULL (empty) string. */
+ IMPL_RTL_STR_NULL_RETURN( pStr, -1 );
while ( *pTempStr )
{
if ( *pTempStr == c )
@@ -524,6 +594,8 @@ void SAL_CALL IMPL_RTL_STRNAME( replaceChar )(
IMPL_RTL_STRCODE* pStr,
IMPL_RTL_STRCODE cOld,
IMPL_RTL_STRCODE cNew )
{
+ /* Nothing to replace in a NULL (empty) string. */
+ IMPL_RTL_STR_NULL_RETURN_VOID( pStr );
while ( *pStr )
{
if ( *pStr == cOld )
@@ -554,6 +626,8 @@ void SAL_CALL IMPL_RTL_STRNAME( replaceChar_WithLength )(
IMPL_RTL_STRCODE* pStr
void SAL_CALL IMPL_RTL_STRNAME( toAsciiLowerCase )( IMPL_RTL_STRCODE* pStr )
{
+ /* Nothing to convert in a NULL (empty) string. */
+ IMPL_RTL_STR_NULL_RETURN_VOID( pStr );
while ( *pStr )
{
/* Between A-Z (65-90), than to lowercase (+32) */
@@ -584,6 +658,8 @@ void SAL_CALL IMPL_RTL_STRNAME( toAsciiLowerCase_WithLength
)( IMPL_RTL_STRCODE*
void SAL_CALL IMPL_RTL_STRNAME( toAsciiUpperCase )( IMPL_RTL_STRCODE* pStr )
{
+ /* Nothing to convert in a NULL (empty) string. */
+ IMPL_RTL_STR_NULL_RETURN_VOID( pStr );
while ( *pStr )
{
/* Between a-z (97-122), than to uppercase (-32) */
@@ -699,6 +775,8 @@ sal_Int32 SAL_CALL IMPL_RTL_STRNAME( valueOfBoolean )(
IMPL_RTL_STRCODE* pStr, s
sal_Int32 SAL_CALL IMPL_RTL_STRNAME( valueOfChar )( IMPL_RTL_STRCODE* pStr,
IMPL_RTL_STRCODE c )
{
+ /* The caller must supply a writable buffer; on NULL write nothing. */
+ IMPL_RTL_STR_NULL_RETURN( pStr, 0 );
*pStr++ = c;
*pStr = 0;
return 1;
diff --git a/main/sal/rtl/source/ustring.c b/main/sal/rtl/source/ustring.c
index c85df91889..ca51caf235 100644
--- a/main/sal/rtl/source/ustring.c
+++ b/main/sal/rtl/source/ustring.c
@@ -151,11 +151,59 @@ double SAL_CALL rtl_ustr_toDouble(sal_Unicode const *
pStr)
}
/* ======================================================================= */
+/* NULL-pointer guards for the mixed UTF-16 / ASCII comparison helpers. */
+/* */
+/* These follow the same policy as strtmpl.c (which is #included above and */
+/* already defines the sal_Unicode empty string aImplGuardEmptyStr): the */
+/* public functions document a non-NULL, null-terminated contract; a NULL */
+/* argument is diagnosed in non-product builds via OSL_PRECOND and treated */
+/* as the empty string otherwise, so NULL is never dereferenced. The */
+/* guards are at function entry, outside the per-character loops. */
+/* ======================================================================= */
+
+static const sal_Char aImplGuardEmptyAscii = 0;
+
+/* Null-terminated ASCII argument: treat NULL as the empty string. */
+#define IMPL_RTL_ASCII_NULL_AS_EMPTY( pAscii ) \
+ do { \
+ OSL_PRECOND( (pAscii) != NULL, \
+ "rtl_ustr_ascii_*: NULL ASCII pointer passed; contract " \
+ "requires a non-NULL, null-terminated string" ); \
+ if ( !(pAscii) ) \
+ (pAscii) = &aImplGuardEmptyAscii; \
+ } while (0)
+
+/* Null-terminated UTF-16 argument: treat NULL as the empty string. */
+#define IMPL_RTL_UNI_NULL_AS_EMPTY( pUni ) \
+ do { \
+ OSL_PRECOND( (pUni) != NULL, \
+ "rtl_ustr_ascii_*: NULL string pointer passed; contract " \
+ "requires a non-NULL, null-terminated string" ); \
+ if ( !(pUni) ) \
+ (pUni) = &aImplGuardEmptyStr; \
+ } while (0)
+
+/* Length-bounded UTF-16 argument: a NULL pointer is an empty (length 0) */
+/* string. Clamp the length to 0 so the pointer is never dereferenced and */
+/* substitute a valid buffer to avoid NULL pointer arithmetic (pStr + len). */
+#define IMPL_RTL_UNI_NULL_AS_EMPTY_LEN( pUni, nLen ) \
+ do { \
+ OSL_PRECOND( (pUni) != NULL, \
+ "rtl_ustr_ascii_*: NULL string pointer passed; contract " \
+ "requires a valid buffer of the given length" ); \
+ if ( !(pUni) ) \
+ { \
+ (pUni) = &aImplGuardEmptyStr; \
+ (nLen) = 0; \
+ } \
+ } while (0)
sal_Int32 SAL_CALL rtl_ustr_ascii_compare( const sal_Unicode* pStr1,
const sal_Char* pStr2 )
{
sal_Int32 nRet;
+ IMPL_RTL_UNI_NULL_AS_EMPTY( pStr1 );
+ IMPL_RTL_ASCII_NULL_AS_EMPTY( pStr2 );
while ( ((nRet = ((sal_Int32)(*pStr1))-
((sal_Int32)((unsigned char)(*pStr2)))) == 0) &&
*pStr2 )
@@ -174,6 +222,8 @@ sal_Int32 SAL_CALL rtl_ustr_ascii_compare_WithLength( const
sal_Unicode* pStr1,
const sal_Char* pStr2 )
{
sal_Int32 nRet = 0;
+ IMPL_RTL_UNI_NULL_AS_EMPTY_LEN( pStr1, nStr1Len );
+ IMPL_RTL_ASCII_NULL_AS_EMPTY( pStr2 );
while( ((nRet = (nStr1Len ? (sal_Int32)(*pStr1) : 0)-
((sal_Int32)((unsigned char)(*pStr2)))) == 0) &&
nStr1Len && *pStr2 )
@@ -193,8 +243,11 @@ sal_Int32 SAL_CALL
rtl_ustr_ascii_shortenedCompare_WithLength( const sal_Unicode
const sal_Char*
pStr2,
sal_Int32
nShortenedLength )
{
- const sal_Unicode* pStr1End = pStr1 + nStr1Len;
+ const sal_Unicode* pStr1End;
sal_Int32 nRet;
+ IMPL_RTL_UNI_NULL_AS_EMPTY_LEN( pStr1, nStr1Len );
+ IMPL_RTL_ASCII_NULL_AS_EMPTY( pStr2 );
+ pStr1End = pStr1 + nStr1Len;
while ( (nShortenedLength > 0) &&
(pStr1 < pStr1End) && *pStr2 )
{
@@ -278,6 +331,8 @@ sal_Int32 SAL_CALL rtl_ustr_ascii_compareIgnoreAsciiCase(
const sal_Unicode* pSt
sal_Int32 nRet;
sal_Int32 c1;
sal_Int32 c2;
+ IMPL_RTL_UNI_NULL_AS_EMPTY( pStr1 );
+ IMPL_RTL_ASCII_NULL_AS_EMPTY( pStr2 );
do
{
/* If character between 'A' and 'Z', than convert it to lowercase */
@@ -308,6 +363,8 @@ sal_Int32 SAL_CALL
rtl_ustr_ascii_compareIgnoreAsciiCase_WithLength( const sal_U
sal_Int32 nRet;
sal_Int32 c1;
sal_Int32 c2;
+ IMPL_RTL_UNI_NULL_AS_EMPTY_LEN( pStr1, nStr1Len );
+ IMPL_RTL_ASCII_NULL_AS_EMPTY( pStr2 );
do
{
if ( !nStr1Len )
@@ -364,10 +421,13 @@ sal_Int32 SAL_CALL
rtl_ustr_ascii_shortenedCompareIgnoreAsciiCase_WithLength( co
const sal_Char* pStr2,
sal_Int32 nShortenedLength )
{
- const sal_Unicode* pStr1End = pStr1 + nStr1Len;
+ const sal_Unicode* pStr1End;
sal_Int32 nRet;
sal_Int32 c1;
sal_Int32 c2;
+ IMPL_RTL_UNI_NULL_AS_EMPTY_LEN( pStr1, nStr1Len );
+ IMPL_RTL_ASCII_NULL_AS_EMPTY( pStr2 );
+ pStr1End = pStr1 + nStr1Len;
while ( (nShortenedLength > 0) &&
(pStr1 < pStr1End) && *pStr2 )
{