Improved handling of function parameter validation

SDL supports the following use cases:
 * Normal operation with fast parameter checks (default):
    SDL_SetHint(SDL_HINT_INVALID_PARAM_CHECKS, "1");
 * Object parameters are checked for use-after-free issues:
    SDL_SetHint(SDL_HINT_INVALID_PARAM_CHECKS, "2");
 * Enable full validation, plus assert on invalid parameters:
    #define SDL_ASSERT_INVALID_PARAMS
 * Disable all parameter validation:
    #define SDL_DISABLE_INVALID_PARAMS
This commit is contained in:
Sam Lantinga
2025-09-16 18:23:06 -07:00
parent 49e15904ae
commit ee1c90a358
4 changed files with 592 additions and 353 deletions

View File

@@ -685,6 +685,20 @@ extern "C" {
*/ */
#define SDL_HINT_DISPLAY_USABLE_BOUNDS "SDL_DISPLAY_USABLE_BOUNDS" #define SDL_HINT_DISPLAY_USABLE_BOUNDS "SDL_DISPLAY_USABLE_BOUNDS"
/**
* Set the level of checking for invalid parameters passed to SDL functions.
*
* The variable can be set to the following values:
*
* - "1": Enable fast parameter error checking, e.g. quick NULL checks, etc. (default)
* - "2": Enable full parameter error checking, e.g. validating objects are the correct type, etc.
*
* This hint can be set anytime.
*
* \since This hint is available since SDL 3.4.0.
*/
#define SDL_HINT_INVALID_PARAM_CHECKS "SDL_INVALID_PARAM_CHECKS"
/** /**
* Disable giving back control to the browser automatically when running with * Disable giving back control to the browser automatically when running with
* asyncify. * asyncify.

View File

@@ -290,6 +290,21 @@ extern SDL_NORETURN void SDL_ExitProcess(int exitcode);
#define POP_SDL_ERROR() \ #define POP_SDL_ERROR() \
SDL_SetError("%s", _error); SDL_free(_error); } SDL_SetError("%s", _error); SDL_free(_error); }
#if defined(SDL_DISABLE_INVALID_PARAMS)
#ifdef DEBUG
// If you define SDL_DISABLE_INVALID_PARAMS, you're promising that you'll
// never pass an invalid parameter to SDL, since it may crash or lead to
// hard to diagnose bugs. Let's assert that this is true in debug builds.
#define CHECK_PARAM(invalid) SDL_assert_always(!(invalid)); if (false)
#else
#define CHECK_PARAM(invalid) if (false)
#endif
#elif defined(SDL_ASSERT_INVALID_PARAMS)
#define CHECK_PARAM(invalid) SDL_assert_always(!(invalid)); if (invalid)
#else
#define CHECK_PARAM(invalid) if (invalid)
#endif
// Do any initialization that needs to happen before threads are started // Do any initialization that needs to happen before threads are started
extern void SDL_InitMainThread(void); extern void SDL_InitMainThread(void);

View File

@@ -137,6 +137,32 @@ Uint32 SDL_GetNextObjectID(void)
static SDL_InitState SDL_objects_init; static SDL_InitState SDL_objects_init;
static SDL_HashTable *SDL_objects; static SDL_HashTable *SDL_objects;
static bool SDL_object_validation;
static void SDLCALL SDL_InvalidParamChecksChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
{
bool validation_enabled = false;
#ifdef SDL_ASSERT_INVALID_PARAMS
// Full validation is enabled by default
validation_enabled = true;
#endif
if (hint) {
switch (*hint) {
case '0':
case '1':
validation_enabled = false;
break;
case '2':
validation_enabled = true;
break;
default:
break;
}
}
SDL_object_validation = validation_enabled;
}
static Uint32 SDLCALL SDL_HashObject(void *unused, const void *key) static Uint32 SDLCALL SDL_HashObject(void *unused, const void *key)
{ {
@@ -159,6 +185,7 @@ void SDL_SetObjectValid(void *object, SDL_ObjectType type, bool valid)
if (!initialized) { if (!initialized) {
return; return;
} }
SDL_AddHintCallback(SDL_HINT_INVALID_PARAM_CHECKS, SDL_InvalidParamChecksChanged, NULL);
} }
if (valid) { if (valid) {
@@ -174,6 +201,10 @@ bool SDL_ObjectValid(void *object, SDL_ObjectType type)
return false; return false;
} }
if (!SDL_object_validation) {
return true;
}
const void *object_type; const void *object_type;
if (!SDL_FindInHashTable(SDL_objects, object, &object_type)) { if (!SDL_FindInHashTable(SDL_objects, object, &object_type)) {
return false; return false;
@@ -242,6 +273,7 @@ void SDL_SetObjectsInvalid(void)
SDL_DestroyHashTable(SDL_objects); SDL_DestroyHashTable(SDL_objects);
SDL_objects = NULL; SDL_objects = NULL;
SDL_SetInitialized(&SDL_objects_init, false); SDL_SetInitialized(&SDL_objects_init, false);
SDL_RemoveHintCallback(SDL_HINT_INVALID_PARAM_CHECKS, SDL_InvalidParamChecksChanged, NULL);
} }
} }

File diff suppressed because it is too large Load Diff