Skip to content

Commit

Permalink
Emit a CE_Warning when CPLSet[Thread]ConfigOption() is called with a …
Browse files Browse the repository at this point in the history
…unknown config option, in debug mode

and add CPLDeclareKnownConfigOption()
  • Loading branch information
rouault committed Nov 11, 2024
1 parent 2d0fdf5 commit 09dd651
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 17 deletions.
26 changes: 18 additions & 8 deletions autotest/cpp/test_cpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
14 changes: 14 additions & 0 deletions doc/source/user/configoptions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
98 changes: 89 additions & 9 deletions port/cpl_conv.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@
#if HAVE_FCNTL_H
#include <fcntl.h>
#endif
#include <mutex>
#include <set>

#if HAVE_UNISTD_H
#include <unistd.h>
#endif
Expand All @@ -68,9 +71,6 @@
#include <unistd.h> // isatty
#endif

#ifdef DEBUG_CONFIG_OPTIONS
#include <set>
#endif
#include <string>

#if __cplusplus >= 202002L
Expand All @@ -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
Expand Down Expand Up @@ -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<CPLString> 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() */
/************************************************************************/
Expand All @@ -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.
*
Expand All @@ -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<volatile char **>(CSLSetNameValue(
const_cast<char **>(g_papszConfigOptions), pszKey, pszValue));
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions port/cpl_conv.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit 09dd651

Please sign in to comment.