From 09dd6512351bce0b7f994e899baba24b835afdcd Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sun, 10 Nov 2024 01:19:50 +0100 Subject: [PATCH] Emit a CE_Warning when CPLSet[Thread]ConfigOption() is called with a unknown config option, in debug mode and add CPLDeclareKnownConfigOption() --- autotest/cpp/test_cpl.cpp | 26 +++++--- doc/source/user/configoptions.rst | 14 +++++ port/cpl_conv.cpp | 98 ++++++++++++++++++++++++++++--- port/cpl_conv.h | 2 + 4 files changed, 123 insertions(+), 17 deletions(-) diff --git a/autotest/cpp/test_cpl.cpp b/autotest/cpp/test_cpl.cpp index ba43804e23b4..111510f688ad 100644 --- a/autotest/cpp/test_cpl.cpp +++ b/autotest/cpp/test_cpl.cpp @@ -1293,16 +1293,26 @@ TEST_F(test_cpl, CPLExpandTilde) CPLSetConfigOption("HOME", nullptr); } -TEST_F(test_cpl, CPLString_constructors) +TEST_F(test_cpl, CPLDeclareKnownConfigOption) { - // CPLString(std::string) constructor - ASSERT_STREQ(CPLString(std::string("abc")).c_str(), "abc"); - - // CPLString(const char*) constructor - ASSERT_STREQ(CPLString("abc").c_str(), "abc"); + CPLConfigOptionSetter oDebugSetter("CPL_DEBUG", "ON", false); + { + CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler); + CPLErrorReset(); + CPLConfigOptionSetter oDeclaredConfigOptionSetter("UNDECLARED_OPTION", + "FOO", false); + EXPECT_STREQ(CPLGetLastErrorMsg(), + "Unknown configuration option 'UNDECLARED_OPTION'."); + } + { + CPLDeclareKnownConfigOption("DECLARED_OPTION", nullptr); - // CPLString(const char*, n) constructor - ASSERT_STREQ(CPLString("abc", 1).c_str(), "a"); + CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler); + CPLErrorReset(); + CPLConfigOptionSetter oDeclaredConfigOptionSetter("DECLARED_OPTION", + "FOO", false); + EXPECT_STREQ(CPLGetLastErrorMsg(), ""); + } } TEST_F(test_cpl, CPLErrorSetState) diff --git a/doc/source/user/configoptions.rst b/doc/source/user/configoptions.rst index 3c9c4b7d8eaa..8c948bcb7cf5 100644 --- a/doc/source/user/configoptions.rst +++ b/doc/source/user/configoptions.rst @@ -68,6 +68,20 @@ they can be limited to only the current thread with For boolean options, the values YES, TRUE or ON can be used to turn the option on; NO, FALSE or OFF to turn it off. +How to detect if the passed configuration option is known to GDAL +----------------------------------------------------------------- + +By default GDAL will not warn if the name of the configuration option is unknown. +Starting with GDAL 3.11, if you set the :config:`CPL_DEBUG` configuration +option to ``ON`` (or any value that is not ``OFF``, ``FALSE``, ``NO``), a GDAL +warning will be emitted for unknown configuration options. + +.. code-block:: shell + + $ gdalinfo --config BAD_OPTION=TEST --debug on --version + Warning 1: CPLSetConfigOption() called with key=BAD_OPTION, which is unknown to GDAL + [...] + .. _gdal_configuration_file: diff --git a/port/cpl_conv.cpp b/port/cpl_conv.cpp index 82ad36ae00e5..0f160d6faaaa 100644 --- a/port/cpl_conv.cpp +++ b/port/cpl_conv.cpp @@ -55,6 +55,9 @@ #if HAVE_FCNTL_H #include #endif +#include +#include + #if HAVE_UNISTD_H #include #endif @@ -68,9 +71,6 @@ #include // isatty #endif -#ifdef DEBUG_CONFIG_OPTIONS -#include -#endif #include #if __cplusplus >= 202002L @@ -82,6 +82,7 @@ #include "cpl_string.h" #include "cpl_vsi.h" #include "cpl_vsil_curl_priv.h" +#include "cpl_known_config_options.h" #ifdef DEBUG #define OGRAPISPY_ENABLED @@ -1917,12 +1918,88 @@ bool CPLIsDebugEnabled() { if (gnDebug < 0) { + // Check that apszKnownConfigOptions is correctly sorted with + // STRCASECMP() criterion. + for (size_t i = 1; i < CPL_ARRAYSIZE(apszKnownConfigOptions); ++i) + { + if (STRCASECMP(apszKnownConfigOptions[i - 1], + apszKnownConfigOptions[i]) >= 0) + { + CPLError(CE_Failure, CPLE_AppDefined, + "ERROR: apszKnownConfigOptions[] isn't correctly " + "sorted: %s >= %s", + apszKnownConfigOptions[i - 1], + apszKnownConfigOptions[i]); + } + } gnDebug = CPLTestBool(CPLGetConfigOption("CPL_DEBUG", "OFF")); } return gnDebug != 0; } +/************************************************************************/ +/* CPLDeclareKnownConfigOption() */ +/************************************************************************/ + +static std::mutex goMutexDeclaredKnownConfigOptions; +static std::set goSetKnownConfigOptions; + +/** Declare that the specified configuration option is known. + * + * This is useful to avoid a warning to be emitted on unknown configuration + * options when CPL_DEBUG is enabled. + * + * @param pszKey Name of the configuration option to declare. + * @param pszDefinition Unused for now. Must be set to nullptr. + * @since 3.11 + */ +void CPLDeclareKnownConfigOption(const char *pszKey, + [[maybe_unused]] const char *pszDefinition) +{ + std::lock_guard oLock(goMutexDeclaredKnownConfigOptions); + goSetKnownConfigOptions.insert(CPLString(pszKey).toupper()); +} + +/************************************************************************/ +/* CPLSetConfigOptionDetectUnknownConfigOption() */ +/************************************************************************/ + +static void CPLSetConfigOptionDetectUnknownConfigOption(const char *pszKey, + const char *pszValue) +{ + if (EQUAL(pszKey, "CPL_DEBUG")) + { + gnDebug = pszValue ? CPLTestBool(pszValue) : false; + } + else if (CPLIsDebugEnabled()) + { + if (!std::binary_search(std::begin(apszKnownConfigOptions), + std::end(apszKnownConfigOptions), pszKey, + [](const char *a, const char *b) + { return STRCASECMP(a, b) < 0; })) + { + bool bFound; + { + std::lock_guard oLock(goMutexDeclaredKnownConfigOptions); + bFound = cpl::contains(goSetKnownConfigOptions, + CPLString(pszKey).toupper()); + } + if (!bFound) + { + const char *pszOldValue = CPLGetConfigOption(pszKey, nullptr); + if (!((!pszValue && !pszOldValue) || + (pszValue && pszOldValue && + EQUAL(pszValue, pszOldValue)))) + { + CPLError(CE_Warning, CPLE_AppDefined, + "Unknown configuration option '%s'.", pszKey); + } + } + } + } +} + /************************************************************************/ /* CPLSetConfigOption() */ /************************************************************************/ @@ -1941,13 +2018,18 @@ bool CPLIsDebugEnabled() * value provided during the last call will be used. * * Options can also be passed on the command line of most GDAL utilities - * with the with '--config KEY VALUE'. For example, - * ogrinfo --config CPL_DEBUG ON ~/data/test/point.shp + * with '--config KEY VALUE' (or '--config KEY=VALUE' since GDAL 3.10). + * For example, ogrinfo --config CPL_DEBUG ON ~/data/test/point.shp * * This function can also be used to clear a setting by passing NULL as the * value (note: passing NULL will not unset an existing environment variable; * it will just unset a value previously set by CPLSetConfigOption()). * + * Starting with GDAL 3.11, if CPL_DEBUG is enabled prior to this call, and + * CPLSetConfigOption() is called with a key that is neither a known + * configuration option of GDAL itself, or one that has been declared with + * CPLDeclareKnownConfigOption(), a warning will be emitted. + * * @param pszKey the key of the option * @param pszValue the value of the option, or NULL to clear a setting. * @@ -1965,8 +2047,7 @@ void CPL_STDCALL CPLSetConfigOption(const char *pszKey, const char *pszValue) OGRAPISPYCPLSetConfigOption(pszKey, pszValue); #endif - if (EQUAL(pszKey, "CPL_DEBUG") && pszValue) - gnDebug = CPLTestBool(pszValue); + CPLSetConfigOptionDetectUnknownConfigOption(pszKey, pszValue); g_papszConfigOptions = const_cast(CSLSetNameValue( const_cast(g_papszConfigOptions), pszKey, pszValue)); @@ -2028,8 +2109,7 @@ void CPL_STDCALL CPLSetThreadLocalConfigOption(const char *pszKey, if (bMemoryError) return; - if (EQUAL(pszKey, "CPL_DEBUG") && pszValue) - gnDebug = CPLTestBool(pszValue); + CPLSetConfigOptionDetectUnknownConfigOption(pszKey, pszValue); papszTLConfigOptions = CSLSetNameValue(papszTLConfigOptions, pszKey, pszValue); diff --git a/port/cpl_conv.h b/port/cpl_conv.h index 94670233f110..cdaaf34d0234 100644 --- a/port/cpl_conv.h +++ b/port/cpl_conv.h @@ -47,6 +47,8 @@ CPLGetGlobalConfigOption(const char *, const char *) CPL_WARN_UNUSED_RESULT; void CPL_DLL CPL_STDCALL CPLSetConfigOption(const char *, const char *); void CPL_DLL CPL_STDCALL CPLSetThreadLocalConfigOption(const char *pszKey, const char *pszValue); +void CPL_DLL CPLDeclareKnownConfigOption(const char *pszKey, + const char *pszDefinition); /** Callback for CPLSubscribeToSetConfigOption() */ typedef void (*CPLSetConfigOptionSubscriber)(const char *pszKey,