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 )
     {

Reply via email to