Skip to content

Commit

Permalink
Introduce formal policy for APIs that return strings.
Browse files Browse the repository at this point in the history
This declares that any `const char *` returned from SDL is owned by SDL, and
promises to be valid _at least_ until the next time the event queue runs, or
SDL_Quit() is called, even if the thing that owns the string gets destroyed
or changed before then.

This is noted in the headers as "the SDL_GetStringRule", so this will both be
greppable to find a detailed explaination in docs/README-strings.md and
wikiheaders will automatically turn it into a link we can point at the
appropriate documentation.

Fixes #9902.

(and several FIXMEs, both known and yet-undocumented.)
  • Loading branch information
icculus committed Jun 2, 2024
1 parent 49b6c24 commit e7b9d0f
Show file tree
Hide file tree
Showing 51 changed files with 257 additions and 118 deletions.
58 changes: 58 additions & 0 deletions docs/README-strings.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# String policies in SDL3.

## Encoding.

Unless otherwise specified, all strings in SDL, across all platforms, are
UTF-8 encoded.


## The SDL Get String Rule.

Did you see 'SDL_GetStringRule' in the wiki or headers? Here are the details
that aren't worth copying across dozens of functions' documentation.

tl;dr: If an SDL function returns a `const char *` string, do not modify or
free it, and if you need to save it, make a copy right away.

In several cases, SDL wants to return a string to the app, and the question
in any library that does this is: _who owns this thing?_

The answer in almost all cases, is that SDL does, but not for long.

The pointer is only guaranteed to be valid until the next time the event
queue is updated, or SDL_Quit is called.

The reason for this is memory safety.

There are several strings that SDL provides that could be freed at
any moment. For example, an app calls SDL_GetAudioDeviceName(), which returns
a string that is part of the internal audio device structure. But, while this
function is returning, the user yanks the USB audio device out of the
computer, and SDL decides to deallocate the structure...and the string!
Now the app is holding a pointer that didn't live long enough to be useful,
and could crash if accessed.

To avoid this, instead of calling SDL_free on a string as soon as it's done
with it, SDL adds the pointer to a list. This list is freed at specific
points: when the event queue is run (for ongoing cleanup) and when SDL_Quit
is called (to catch things that are just hanging around). This allows the
app to use a string without worrying about it becoming bogus in the middle
of a printf() call. If the app needs it for longer, it should copy it.

When does "the event queue run"? There are several points:

- If the app calls SDL_PumpEvents() _from any thread_.
- SDL_PumpEvents is also called by several other APIs internally:
SDL_PollEvent(), SDL_PeepEvents(), SDL_WaitEvent(),
SDL_WaitEventTimeout(), and maybe others.
- If you are using [the main callbacks](README-main-functions), the
event queue can run immediately after any of the callback functions return.

Note that these are just guaranteed minimum lifespans; any given string
might live much longer--some might even be static memory that is _never_
deallocated--but this rule promises that the app has a safe window.

Note that none of this applies if the return value is `char *` instead of
`const char *`. Please see the specific function's documentation for how
to handle those pointers.

19 changes: 10 additions & 9 deletions include/SDL3/SDL_audio.h
Original file line number Diff line number Diff line change
Expand Up @@ -374,9 +374,11 @@ extern SDL_DECLSPEC int SDLCALL SDL_GetNumAudioDrivers(void);
* first (as far as the SDL developers believe) are earlier in the list.
*
* The names of drivers are all simple, low-ASCII identifiers, like "alsa",
* "coreaudio" or "xaudio2". These never have Unicode characters, and are not
* "coreaudio" or "wasapi". These never have Unicode characters, and are not
* meant to be proper names.
*
* The returned string follows the SDL_GetStringRule.
*
* \param index the index of the audio driver; the value ranges from 0 to
* SDL_GetNumAudioDrivers() - 1
* \returns the name of the audio driver at the requested index, or NULL if an
Expand All @@ -394,11 +396,11 @@ extern SDL_DECLSPEC const char *SDLCALL SDL_GetAudioDriver(int index);
/**
* Get the name of the current audio driver.
*
* The returned string points to internal static memory and thus never becomes
* invalid, even if you quit the audio subsystem and initialize a new driver
* (although such a case would return a different static string from another
* call to this function, of course). As such, you should not modify or free
* the returned string.
* The names of drivers are all simple, low-ASCII identifiers, like "alsa",
* "coreaudio" or "wasapi". These never have Unicode characters, and are not
* meant to be proper names.
*
* The returned string follows the SDL_GetStringRule.
*
* \returns the name of the current audio driver or NULL if no driver has been
* initialized.
Expand Down Expand Up @@ -470,8 +472,7 @@ extern SDL_DECLSPEC SDL_AudioDeviceID *SDLCALL SDL_GetAudioCaptureDevices(int *c
/**
* Get the human-readable name of a specific audio device.
*
* The string returned by this function is UTF-8 encoded. The caller should
* call SDL_free on the return value when done with it.
* The returned string follows the SDL_GetStringRule.
*
* \param devid the instance ID of the device to query.
* \returns the name of the audio device, or NULL on error.
Expand All @@ -484,7 +485,7 @@ extern SDL_DECLSPEC SDL_AudioDeviceID *SDLCALL SDL_GetAudioCaptureDevices(int *c
* \sa SDL_GetAudioCaptureDevices
* \sa SDL_GetDefaultAudioInfo
*/
extern SDL_DECLSPEC char *SDLCALL SDL_GetAudioDeviceName(SDL_AudioDeviceID devid);
extern SDL_DECLSPEC const char *SDLCALL SDL_GetAudioDeviceName(SDL_AudioDeviceID devid);

/**
* Get the current audio format of a specific audio device.
Expand Down
12 changes: 7 additions & 5 deletions include/SDL3/SDL_camera.h
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@ extern SDL_DECLSPEC int SDLCALL SDL_GetNumCameraDrivers(void);
* "coremedia" or "android". These never have Unicode characters, and are not
* meant to be proper names.
*
* The returned string follows the SDL_GetStringRule.
*
* \param index the index of the camera driver; the value ranges from 0 to
* SDL_GetNumCameraDrivers() - 1
* \returns the name of the camera driver at the requested index, or NULL if
Expand All @@ -150,11 +152,11 @@ extern SDL_DECLSPEC const char *SDLCALL SDL_GetCameraDriver(int index);
/**
* Get the name of the current camera driver.
*
* The returned string points to internal static memory and thus never becomes
* invalid, even if you quit the camera subsystem and initialize a new driver
* (although such a case would return a different static string from another
* call to this function, of course). As such, you should not modify or free
* the returned string.
* The names of drivers are all simple, low-ASCII identifiers, like "v4l2",
* "coremedia" or "android". These never have Unicode characters, and are not
* meant to be proper names.
*
* The returned string follows the SDL_GetStringRule.
*
* \returns the name of the current camera driver or NULL if no driver has
* been initialized.
Expand Down
11 changes: 5 additions & 6 deletions include/SDL3/SDL_error.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,15 +96,14 @@ extern SDL_DECLSPEC int SDLCALL SDL_OutOfMemory(void);
* Error strings are set per-thread, so an error set in a different thread
* will not interfere with the current thread's operation.
*
* The returned string is internally allocated and must not be freed by the
* application.
* The returned string does **NOT** follow the SDL_GetStringRule! The
* pointer is valid until the current thread's error string is changed, so
* the caller should make a copy if the string is to be used after calling
* into SDL again.
*
* \returns a message with information about the specific error that occurred,
* or an empty string if there hasn't been an error message set since
* the last call to SDL_ClearError(). The message is only applicable
* when an SDL function has signaled an error. You must check the
* return values of SDL function calls to determine when to
* appropriately call SDL_GetError().
* the last call to SDL_ClearError().
*
* \since This function is available since SDL 3.0.0.
*
Expand Down
20 changes: 17 additions & 3 deletions include/SDL3/SDL_gamepad.h
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,8 @@ extern SDL_DECLSPEC SDL_bool SDLCALL SDL_IsGamepad(SDL_JoystickID instance_id);
*
* This can be called before any gamepads are opened.
*
* The returned string follows the SDL_GetStringRule.
*
* \param instance_id the joystick instance ID
* \returns the name of the selected gamepad. If no name can be found, this
* function returns NULL; call SDL_GetError() for more information.
Expand All @@ -516,6 +518,8 @@ extern SDL_DECLSPEC const char *SDLCALL SDL_GetGamepadInstanceName(SDL_JoystickI
*
* This can be called before any gamepads are opened.
*
* The returned string follows the SDL_GetStringRule.
*
* \param instance_id the joystick instance ID
* \returns the path of the selected gamepad. If no path can be found, this
* function returns NULL; call SDL_GetError() for more information.
Expand Down Expand Up @@ -748,6 +752,8 @@ extern SDL_DECLSPEC SDL_JoystickID SDLCALL SDL_GetGamepadInstanceID(SDL_Gamepad
/**
* Get the implementation-dependent name for an opened gamepad.
*
* The returned string follows the SDL_GetStringRule.
*
* \param gamepad a gamepad identifier previously returned by
* SDL_OpenGamepad()
* \returns the implementation dependent name for the gamepad, or NULL if
Expand All @@ -762,6 +768,8 @@ extern SDL_DECLSPEC const char *SDLCALL SDL_GetGamepadName(SDL_Gamepad *gamepad)
/**
* Get the implementation-dependent path for an opened gamepad.
*
* The returned string follows the SDL_GetStringRule.
*
* \param gamepad a gamepad identifier previously returned by
* SDL_OpenGamepad()
* \returns the implementation dependent path for the gamepad, or NULL if
Expand Down Expand Up @@ -887,6 +895,8 @@ extern SDL_DECLSPEC Uint16 SDLCALL SDL_GetGamepadFirmwareVersion(SDL_Gamepad *ga
*
* Returns the serial number of the gamepad, or NULL if it is not available.
*
* The returned string follows the SDL_GetStringRule.
*
* \param gamepad the gamepad object to query.
* \returns the serial number, or NULL if unavailable.
*
Expand Down Expand Up @@ -1045,7 +1055,7 @@ extern SDL_DECLSPEC SDL_GamepadType SDLCALL SDL_GetGamepadTypeFromString(const c
/**
* Convert from an SDL_GamepadType enum to a string.
*
* The caller should not SDL_free() the returned string.
* The returned string follows the SDL_GetStringRule.
*
* \param type an enum value for a given SDL_GamepadType
* \returns a string for the given type, or NULL if an invalid type is
Expand Down Expand Up @@ -1083,7 +1093,7 @@ extern SDL_DECLSPEC SDL_GamepadAxis SDLCALL SDL_GetGamepadAxisFromString(const c
/**
* Convert from an SDL_GamepadAxis enum to a string.
*
* The caller should not SDL_free() the returned string.
* The returned string follows the SDL_GetStringRule.
*
* \param axis an enum value for a given SDL_GamepadAxis
* \returns a string for the given axis, or NULL if an invalid axis is
Expand Down Expand Up @@ -1158,7 +1168,7 @@ extern SDL_DECLSPEC SDL_GamepadButton SDLCALL SDL_GetGamepadButtonFromString(con
/**
* Convert from an SDL_GamepadButton enum to a string.
*
* The caller should not SDL_free() the returned string.
* The returned string follows the SDL_GetStringRule.
*
* \param button an enum value for a given SDL_GamepadButton
* \returns a string for the given button, or NULL if an invalid button is
Expand Down Expand Up @@ -1446,6 +1456,8 @@ extern SDL_DECLSPEC void SDLCALL SDL_CloseGamepad(SDL_Gamepad *gamepad);
* Return the sfSymbolsName for a given button on a gamepad on Apple
* platforms.
*
* The returned string follows the SDL_GetStringRule.
*
* \param gamepad the gamepad to query
* \param button a button on the gamepad
* \returns the sfSymbolsName or NULL if the name can't be found
Expand All @@ -1459,6 +1471,8 @@ extern SDL_DECLSPEC const char* SDLCALL SDL_GetGamepadAppleSFSymbolsNameForButto
/**
* Return the sfSymbolsName for a given axis on a gamepad on Apple platforms.
*
* The returned string follows the SDL_GetStringRule.
*
* \param gamepad the gamepad to query
* \param axis an axis on the gamepad
* \returns the sfSymbolsName or NULL if the name can't be found
Expand Down
4 changes: 4 additions & 0 deletions include/SDL3/SDL_haptic.h
Original file line number Diff line number Diff line change
Expand Up @@ -948,6 +948,8 @@ extern SDL_DECLSPEC SDL_HapticID *SDLCALL SDL_GetHaptics(int *count);
*
* This can be called before any haptic devices are opened.
*
* The returned string follows the SDL_GetStringRule.
*
* \param instance_id the haptic device instance ID
* \returns the name of the selected haptic device. If no name can be found,
* this function returns NULL; call SDL_GetError() for more
Expand Down Expand Up @@ -1011,6 +1013,8 @@ extern SDL_DECLSPEC SDL_HapticID SDLCALL SDL_GetHapticInstanceID(SDL_Haptic *hap
/**
* Get the implementation dependent name of a haptic device.
*
* The returned string follows the SDL_GetStringRule.
*
* \param haptic the SDL_Haptic obtained from SDL_OpenJoystick()
* \returns the name of the selected haptic device. If no name can be found,
* this function returns NULL; call SDL_GetError() for more
Expand Down
2 changes: 2 additions & 0 deletions include/SDL3/SDL_hints.h
Original file line number Diff line number Diff line change
Expand Up @@ -3806,6 +3806,8 @@ extern SDL_DECLSPEC void SDLCALL SDL_ResetHints(void);
/**
* Get the value of a hint.
*
* The returned string follows the SDL_GetStringRule.
*
* \param name the hint to query
* \returns the string value of a hint or NULL if the hint isn't set.
*
Expand Down
10 changes: 10 additions & 0 deletions include/SDL3/SDL_joystick.h
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,8 @@ extern SDL_DECLSPEC SDL_JoystickID *SDLCALL SDL_GetJoysticks(int *count);
*
* This can be called before any joysticks are opened.
*
* The returned string follows the SDL_GetStringRule.
*
* \param instance_id the joystick instance ID
* \returns the name of the selected joystick. If no name can be found, this
* function returns NULL; call SDL_GetError() for more information.
Expand All @@ -247,6 +249,8 @@ extern SDL_DECLSPEC const char *SDLCALL SDL_GetJoystickInstanceName(SDL_Joystick
*
* This can be called before any joysticks are opened.
*
* The returned string follows the SDL_GetStringRule.
*
* \param instance_id the joystick instance ID
* \returns the path of the selected joystick. If no path can be found, this
* function returns NULL; call SDL_GetError() for more information.
Expand Down Expand Up @@ -669,6 +673,8 @@ extern SDL_DECLSPEC SDL_PropertiesID SDLCALL SDL_GetJoystickProperties(SDL_Joyst
/**
* Get the implementation dependent name of a joystick.
*
* The returned string follows the SDL_GetStringRule.
*
* \param joystick the SDL_Joystick obtained from SDL_OpenJoystick()
* \returns the name of the selected joystick. If no name can be found, this
* function returns NULL; call SDL_GetError() for more information.
Expand All @@ -682,6 +688,8 @@ extern SDL_DECLSPEC const char *SDLCALL SDL_GetJoystickName(SDL_Joystick *joysti
/**
* Get the implementation dependent path of a joystick.
*
* The returned string follows the SDL_GetStringRule.
*
* \param joystick the SDL_Joystick obtained from SDL_OpenJoystick()
* \returns the path of the selected joystick. If no path can be found, this
* function returns NULL; call SDL_GetError() for more information.
Expand Down Expand Up @@ -799,6 +807,8 @@ extern SDL_DECLSPEC Uint16 SDLCALL SDL_GetJoystickFirmwareVersion(SDL_Joystick *
*
* Returns the serial number of the joystick, or NULL if it is not available.
*
* The returned string follows the SDL_GetStringRule.
*
* \param joystick the SDL_Joystick obtained from SDL_OpenJoystick()
* \returns the serial number of the selected joystick, or NULL if
* unavailable.
Expand Down
6 changes: 6 additions & 0 deletions include/SDL3/SDL_keyboard.h
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ extern SDL_DECLSPEC SDL_KeyboardID *SDLCALL SDL_GetKeyboards(int *count);
*
* This function returns "" if the keyboard doesn't have a name.
*
* The returned string follows the SDL_GetStringRule.
*
* \param instance_id the keyboard instance ID
* \returns the name of the selected keyboard, or NULL on failure; call
* SDL_GetError() for more information.
Expand Down Expand Up @@ -255,6 +257,8 @@ extern SDL_DECLSPEC SDL_Scancode SDLCALL SDL_GetScancodeFromKey(SDL_Keycode key)
*
* See SDL_Scancode for details.
*
* The returned string follows the SDL_GetStringRule.
*
* **Warning**: The returned name is by design not stable across platforms,
* e.g. the name for `SDL_SCANCODE_LGUI` is "Left GUI" under Linux but "Left
* Windows" under Microsoft Windows, and some scancodes like
Expand Down Expand Up @@ -295,6 +299,8 @@ extern SDL_DECLSPEC SDL_Scancode SDLCALL SDL_GetScancodeFromName(const char *nam
*
* See SDL_Scancode and SDL_Keycode for details.
*
* The returned string follows the SDL_GetStringRule.
*
* \param key the desired SDL_Keycode to query
* \returns a pointer to a UTF-8 string that stays valid at least until the
* next call to this function. If you need it around any longer, you
Expand Down
2 changes: 2 additions & 0 deletions include/SDL3/SDL_mouse.h
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,8 @@ extern SDL_DECLSPEC SDL_MouseID *SDLCALL SDL_GetMice(int *count);
*
* This function returns "" if the mouse doesn't have a name.
*
* The returned string follows the SDL_GetStringRule.
*
* \param instance_id the mouse instance ID
* \returns the name of the selected mouse, or NULL on failure; call
* SDL_GetError() for more information.
Expand Down
2 changes: 2 additions & 0 deletions include/SDL3/SDL_pen.h
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,8 @@ extern SDL_DECLSPEC SDL_bool SDLCALL SDL_PenConnected(SDL_PenID instance_id);
/**
* Retrieves a human-readable description for a SDL_PenID.
*
* The returned string follows the SDL_GetStringRule.
*
* \param instance_id The pen to query.
* \returns A string that contains the name of the pen, intended for human
* consumption. The string might or might not be localised, depending
Expand Down
2 changes: 2 additions & 0 deletions include/SDL3/SDL_pixels.h
Original file line number Diff line number Diff line change
Expand Up @@ -758,6 +758,8 @@ typedef struct SDL_PixelFormat
/**
* Get the human readable name of a pixel format.
*
* The returned string follows the SDL_GetStringRule.
*
* \param format the pixel format to query
* \returns the human readable name of the specified pixel format or
* `SDL_PIXELFORMAT_UNKNOWN` if the format isn't recognized.
Expand Down
4 changes: 3 additions & 1 deletion include/SDL3/SDL_platform.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,14 @@ extern "C" {
* - "iOS"
* - "Android"
*
* The returned string follows the SDL_GetStringRule.
*
* \returns the name of the platform. If the correct platform name is not
* available, returns a string beginning with the text "Unknown".
*
* \since This function is available since SDL 3.0.0.
*/
extern SDL_DECLSPEC const char * SDLCALL SDL_GetPlatform (void);
extern SDL_DECLSPEC const char * SDLCALL SDL_GetPlatform(void);

/* Ends C function definitions when using C++ */
#ifdef __cplusplus
Expand Down
2 changes: 2 additions & 0 deletions include/SDL3/SDL_properties.h
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,8 @@ extern SDL_DECLSPEC void *SDLCALL SDL_GetProperty(SDL_PropertiesID props, const
/**
* Get a string property on a set of properties.
*
* The returned string follows the SDL_GetStringRule.
*
* \param props the properties to query
* \param name the name of the property to query
* \param default_value the default value of the property
Expand Down
Loading

0 comments on commit e7b9d0f

Please sign in to comment.