From ed6e73b796368b6dd036b0aaa1a975635ec49b32 Mon Sep 17 00:00:00 2001 From: Eric Date: Wed, 12 Jul 2023 13:21:37 -0700 Subject: [PATCH] updated cjson --- .efrocachemap | 56 +- .idea/dictionaries/ericf.xml | 7 + CHANGELOG.md | 10 +- .../.idea/dictionaries/ericf.xml | 7 + src/assets/ba_data/python/baenv.py | 2 +- src/ballistica/base/audio/al_sys.h | 4 + src/ballistica/base/audio/audio_server.cc | 36 +- src/ballistica/core/core.cc | 1 - src/ballistica/core/python/core_python.cc | 36 +- src/ballistica/shared/ballistica.cc | 2 +- src/ballistica/shared/generic/json.cc | 3230 +++++++++++++---- src/ballistica/shared/generic/json.h | 341 +- src/ballistica/shared/generic/utf8.cc | 2 +- tools/batools/build.py | 334 -- tools/batools/pcommand.py | 50 - tools/batools/spinoff/_main.py | 2 +- tools/efrotools/openalbuild.py | 137 + tools/efrotools/pcommand2.py | 27 + tools/pcommand | 7 +- 19 files changed, 2929 insertions(+), 1362 deletions(-) create mode 100644 tools/efrotools/openalbuild.py diff --git a/.efrocachemap b/.efrocachemap index a60f082c..164c9f2c 100644 --- a/.efrocachemap +++ b/.efrocachemap @@ -4068,26 +4068,26 @@ "build/assets/windows/Win32/ucrtbased.dll": "https://files.ballistica.net/cache/ba1/2d/ef/5335207d41b21b9823f6805997f1", "build/assets/windows/Win32/vc_redist.x86.exe": "https://files.ballistica.net/cache/ba1/b0/8a/55e2e77623fe657bea24f223a3ae", "build/assets/windows/Win32/vcruntime140d.dll": "https://files.ballistica.net/cache/ba1/86/5b/2af4d1e26a1a8073c89acb06e599", - "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/b9/b3/2fa4ee60a830770b0fdfd27d0662", - "build/prefab/full/linux_arm64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/58/5d/aafcd4e057f4048e0ab4b097b614", - "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/49/96/45734d046f665c33cf041c5e0751", - "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/49/e3/e76596a602fec3be164171b678f8", - "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/24/6c/b0e065c2d5ee684ef782e940c815", - "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/af/ef/f3c900b2b76b3483ba67feab931f", - "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/65/9b/b420fd2a80e6293340e93bace338", - "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/a5/47/434af2120591774986a41e84bc1d", - "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/08/48/6c1af81372a988542ff82704eb95", - "build/prefab/full/mac_arm64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/7a/d1/cf9ef6bdf9d7c0fd99deedb54170", - "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/a6/30/1279e53155667db8b5c9a2bbf7d0", - "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/4c/a5/6a2334a97a6c48dc04403e29274e", - "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/9f/60/6159ef28401646f9e654c29e0673", - "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/09/cb/4eeb647b2bb083ab7056a1231bd7", - "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/08/dd/37874122d5d465c62deac533b648", - "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/6e/fd/00e796c943776ae69971931d249b", - "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "https://files.ballistica.net/cache/ba1/9a/e4/2fea9c0d36d3b7238eda94783b6f", - "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "https://files.ballistica.net/cache/ba1/fc/31/d7c0f7629642530d41c4d209ecef", - "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "https://files.ballistica.net/cache/ba1/98/41/f5401e303ed81ad91d7f4022e572", - "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "https://files.ballistica.net/cache/ba1/4f/3e/a371bc2c1946057f5053342643e0", + "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/07/f3/286b2cf0e1b50530b18965cb8b80", + "build/prefab/full/linux_arm64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/76/ca/9a12f8c55e4b80e3b7125864c5e8", + "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/55/5d/9677204cb8a81f44cdf36cbe16ed", + "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/ed/d9/5d087b681d88ce9fbf847e2e18ac", + "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/9f/2f/efef4e5a2e1a6faf675eba4d371a", + "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/0c/8b/e07ebcb43cab26c8b7c086d364ee", + "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/98/a7/b52e3fc93f750bc906e13b0f73f4", + "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/95/f2/31a57f21956ca9db25b291d9e73f", + "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/98/30/6664dbd278148938969de4340336", + "build/prefab/full/mac_arm64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/b1/c4/acfecf05f122c2748e976d03d2a1", + "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/fe/19/1e894d75b761b3e5b1e352e69bb4", + "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/3b/ba/086f1125c06960c1c7e5a713e1a4", + "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/70/77/a779069bfaf1b77f57606e84e8c2", + "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/b1/cf/fdf91926b6c70fe52f06224cae39", + "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/52/99/7a6bcb5ab85bf4a1d075d1c751fb", + "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/ec/d7/aff376d79fd0a3778b4e3cdbad0f", + "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "https://files.ballistica.net/cache/ba1/7b/36/09f5279721eb692d7132b9217abe", + "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "https://files.ballistica.net/cache/ba1/48/23/d3ecc696eb84836db814a0f03746", + "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "https://files.ballistica.net/cache/ba1/f7/a1/beffa3b641e5f0212eeaadbf94c0", + "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "https://files.ballistica.net/cache/ba1/4b/ec/3fe45fb55be8d95a853536387269", "build/prefab/lib/linux_arm64_gui/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/84/aa/534f35b6499762739646ea173382", "build/prefab/lib/linux_arm64_gui/release/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/25/2f/3bd787d6debb2c4073fd6c2e8098", "build/prefab/lib/linux_arm64_server/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/84/aa/534f35b6499762739646ea173382", @@ -4104,14 +4104,14 @@ "build/prefab/lib/mac_x86_64_gui/release/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/47/61/eca0961c54b2eae2cf65fac7848d", "build/prefab/lib/mac_x86_64_server/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/06/5c/90c3a49e16a004e2db71909af919", "build/prefab/lib/mac_x86_64_server/release/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/47/61/eca0961c54b2eae2cf65fac7848d", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "https://files.ballistica.net/cache/ba1/72/70/aab14b866e1c3562421a757abfeb", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "https://files.ballistica.net/cache/ba1/0a/df/c8bb059be642ad2a905ef2da6679", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "https://files.ballistica.net/cache/ba1/1d/47/3441b90233f32aca5657acc410f7", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "https://files.ballistica.net/cache/ba1/3d/54/f255fe1f8eced421101a4ae392bf", - "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "https://files.ballistica.net/cache/ba1/34/5d/f1544e97b2677c9ef3aef62e569e", - "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "https://files.ballistica.net/cache/ba1/9b/f7/e821d0e43e2f7a34980b6556e69d", - "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "https://files.ballistica.net/cache/ba1/07/af/60dfd76561968bee5a00d2530ba4", - "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "https://files.ballistica.net/cache/ba1/65/e9/338ef36cbefafd0f718bb42417d1", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "https://files.ballistica.net/cache/ba1/de/a9/add7fc92d73241fb32df336c4012", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "https://files.ballistica.net/cache/ba1/b5/88/dc175d964962a3fec5b604d6a5cc", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "https://files.ballistica.net/cache/ba1/a2/20/867922da68023be227e26d04d96c", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "https://files.ballistica.net/cache/ba1/ce/9d/6df11d4bb08bfbb239fcf9b1bc10", + "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "https://files.ballistica.net/cache/ba1/a0/42/96482e90951d3c141820cb413fc6", + "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "https://files.ballistica.net/cache/ba1/56/cc/980a9c4a5b0d11d5a84ab895b354", + "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "https://files.ballistica.net/cache/ba1/7a/3d/7a4a4f0bb522c2ab7912dcd3d1cc", + "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "https://files.ballistica.net/cache/ba1/47/f0/aebfc9f112aaf48b69e2dbb3b823", "src/assets/ba_data/python/babase/_mgen/__init__.py": "https://files.ballistica.net/cache/ba1/f8/85/fed7f2ed98ff2ba271f9dbe3391c", "src/assets/ba_data/python/babase/_mgen/enums.py": "https://files.ballistica.net/cache/ba1/f8/cd/3af311ac63147882590123b78318", "src/ballistica/base/mgen/pyembed/binding_base.inc": "https://files.ballistica.net/cache/ba1/3e/7a/203e2a5d2b5bb42cfe3fd2fe16c2", diff --git a/.idea/dictionaries/ericf.xml b/.idea/dictionaries/ericf.xml index 147886bb..ddecd372 100644 --- a/.idea/dictionaries/ericf.xml +++ b/.idea/dictionaries/ericf.xml @@ -82,6 +82,7 @@ aname anamorphosis andr + andrarch androidaddr androideabi androidstudiocode @@ -658,6 +659,7 @@ cyaml cycledelay cygwinccompiler + dandroid darwiin darwiinremote datab @@ -743,6 +745,7 @@ distro distroot distros + dlibtype dline dliwk dlldir @@ -1555,6 +1558,7 @@ libegl libext libffi + libfile libgen libinst liblzma @@ -1634,6 +1638,7 @@ locs logcallobj logcat + loggingpath logincode loginid logintoken @@ -1989,6 +1994,8 @@ onslaughtplug opcode opdir + opealsoft + openalsoft opendiff openssh operasinger diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fe3663a..d6d47450 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ +### 1.7.22 (build 21167, api 8, 2023-07-12) + ### 1.7.23 (build 21165, api 8, 2023-07-11) -- Network security improvements. +- Network security improvements. (Thanks Dliwk!) +- You can now double click a chat message to copy it (Thanks Vishal332008!) +- Android's audio library has been updated to the latest version (and is now + much easier for me to keep up to date). Please holler if you run into anything + wonky related to audio. +- Updated our C json handling code to the latest version of cJSON. Should fix + some potential vulnerabilities. ### 1.7.22 (build 21165, api 8, 2023-07-11) diff --git a/ballisticakit-cmake/.idea/dictionaries/ericf.xml b/ballisticakit-cmake/.idea/dictionaries/ericf.xml index f7bfac48..2ff36119 100644 --- a/ballisticakit-cmake/.idea/dictionaries/ericf.xml +++ b/ballisticakit-cmake/.idea/dictionaries/ericf.xml @@ -58,6 +58,7 @@ alsa alsoft anchorx + andrarch androideabi animcurve aniso @@ -396,6 +397,7 @@ curv cutef cvar + dandroid data databytes datac @@ -457,6 +459,7 @@ displaytime displaytimer dlfcn + dlibtype dlife dliwk dllpath @@ -913,6 +916,7 @@ libballistica libbz libbzip + libfile libutf libuuid lifecyclelog @@ -934,6 +938,7 @@ lockstr locktype logcallobj + loggingpath logincode loginid logmsg @@ -1177,7 +1182,9 @@ ooooooooooooooooooooooooooooooooooo ooooooooooooooooooooooooooooooooooooo opcode + opealsoft openal + openalsoft opengl opensl oper diff --git a/src/assets/ba_data/python/baenv.py b/src/assets/ba_data/python/baenv.py index afdec83a..59f62065 100644 --- a/src/assets/ba_data/python/baenv.py +++ b/src/assets/ba_data/python/baenv.py @@ -28,7 +28,7 @@ if TYPE_CHECKING: # Build number and version of the ballistica binary we expect to be # using. -TARGET_BALLISTICA_BUILD = 21165 +TARGET_BALLISTICA_BUILD = 21167 TARGET_BALLISTICA_VERSION = '1.7.22' _g_env_config: EnvConfig | None = None diff --git a/src/ballistica/base/audio/al_sys.h b/src/ballistica/base/audio/al_sys.h index 316d3f3c..f1d017dd 100644 --- a/src/ballistica/base/audio/al_sys.h +++ b/src/ballistica/base/audio/al_sys.h @@ -17,6 +17,10 @@ #include #endif +#if BA_OSTYPE_ANDROID +#include +#endif + #define CHECK_AL_ERROR _check_al_error(__FILE__, __LINE__) #if BA_DEBUG_BUILD #define DEBUG_CHECK_AL_ERROR CHECK_AL_ERROR diff --git a/src/ballistica/base/audio/audio_server.cc b/src/ballistica/base/audio/audio_server.cc index 4d571b53..03d9178f 100644 --- a/src/ballistica/base/audio/audio_server.cc +++ b/src/ballistica/base/audio/audio_server.cc @@ -25,9 +25,10 @@ namespace ballistica::base { extern std::string g_rift_audio_device_name; #endif -// FIXME: move these to platform. -extern "C" void opensl_pause_playback(); -extern "C" void opensl_resume_playback(); +#if BA_OSTYPE_ANDROID +LPALCDEVICEPAUSESOFT alcDevicePauseSOFT; +LPALCDEVICERESUMESOFT alcDeviceResumeSOFT; +#endif const int kAudioProcessIntervalNormal{500}; const int kAudioProcessIntervalFade{50}; @@ -41,7 +42,7 @@ struct AudioServer::Impl { ~Impl() = default; #if BA_ENABLE_AUDIO - ALCcontext* alc_context_{}; + ALCcontext* alc_context{}; #endif }; @@ -203,10 +204,21 @@ void AudioServer::OnAppStartInThread() { "No audio devices found. Do you have speakers/headphones/etc. " "connected?"); } - impl_->alc_context_ = alcCreateContext(device, nullptr); - BA_PRECONDITION(impl_->alc_context_); - BA_PRECONDITION(alcMakeContextCurrent(impl_->alc_context_)); + impl_->alc_context = alcCreateContext(device, nullptr); + BA_PRECONDITION(impl_->alc_context); + BA_PRECONDITION(alcMakeContextCurrent(impl_->alc_context)); CHECK_AL_ERROR; + +#if BA_OSTYPE_ANDROID + if (alcIsExtensionPresent(device, "ALC_SOFT_pause_device")) { + alcDevicePauseSOFT = reinterpret_cast( + alcGetProcAddress(device, "alcDevicePauseSOFT")); + alcDeviceResumeSOFT = reinterpret_cast( + alcGetProcAddress(device, "alcDeviceResumeSOFT")); + } else { + FatalError("ALC_SOFT pause/resume functionality not found."); + } +#endif } ALfloat listener_pos[] = {0.0f, 0.0f, 0.0f}; @@ -250,8 +262,8 @@ AudioServer::~AudioServer() { { ALCdevice* device; BA_PRECONDITION_LOG(alcMakeContextCurrent(nullptr)); - device = alcGetContextsDevice(impl_->alc_context_); - alcDestroyContext(impl_->alc_context_); + device = alcGetContextsDevice(impl_->alc_context); + alcDestroyContext(impl_->alc_context); assert(alcGetError(device) == ALC_NO_ERROR); alcCloseDevice(device); } @@ -288,7 +300,7 @@ void AudioServer::SetPaused(bool pause) { // On android lets tell open-sl to stop its processing. #if BA_OSTYPE_ANDROID - opensl_pause_playback(); + alcDevicePauseSOFT(alcGetContextsDevice(impl_->alc_context)); #endif // BA_OSTYPE_ANDROID paused_ = true; @@ -304,12 +316,12 @@ void AudioServer::SetPaused(bool pause) { // Conceptual/AudioSessionProgrammingGuide/Cookbook/ // Cookbook.html#//apple_ref/doc/uid/TP40007875-CH6-SW38 #if BA_ENABLE_AUDIO - alcMakeContextCurrent(impl_->alc_context_); // hmm is this necessary?.. + alcMakeContextCurrent(impl_->alc_context); // hmm is this necessary?.. #endif #endif // On android lets tell openal-soft to stop processing. #if BA_OSTYPE_ANDROID - opensl_resume_playback(); + alcDeviceResumeSOFT(alcGetContextsDevice(impl_->alc_context)); #endif // BA_OSTYPE_ANDROID paused_ = false; diff --git a/src/ballistica/core/core.cc b/src/ballistica/core/core.cc index c2d38850..effbeb24 100644 --- a/src/ballistica/core/core.cc +++ b/src/ballistica/core/core.cc @@ -178,7 +178,6 @@ void CoreFeatureSet::LifecycleLog(const char* msg, double offset_seconds) { return; } char buffer[128]; - // It's not safe to use Log until snprintf(buffer, sizeof(buffer), "LIFE: %s @ %.3fs.", msg, g_core->GetAppTimeSeconds() + offset_seconds); Log(LogLevel::kInfo, buffer); diff --git a/src/ballistica/core/python/core_python.cc b/src/ballistica/core/python/core_python.cc index a92ca1c7..3ff2d5cc 100644 --- a/src/ballistica/core/python/core_python.cc +++ b/src/ballistica/core/python/core_python.cc @@ -22,6 +22,13 @@ void CorePython::ApplyBaEnvConfig() { g_core->platform->SetBaEnvVals(envcfg); } +static void CheckPyInitStatus(const char* where, const PyStatus& status) { + if (PyStatus_Exception(status)) { + FatalError(std::string("Error in ") + where + ": " + + (status.err_msg ? status.err_msg : "(nullptr err_msg)") + "."); + } +} + void CorePython::InitPython() { assert(g_core->InMainThread()); assert(g_buildconfig.monolithic_build()); @@ -49,8 +56,7 @@ void CorePython::InitPython() { // windows-specific file encodings, etc.) preconfig.utf8_mode = 1; - PyStatus status = Py_PreInitialize(&preconfig); - BA_PRECONDITION(!PyStatus_Exception(status)); + CheckPyInitStatus("Py_PreInitialize", Py_PreInitialize(&preconfig)); // Configure as isolated if we include our own Python and as standard // otherwise. @@ -71,12 +77,22 @@ void CorePython::InitPython() { // In cases where we bundle Python, set up all paths explicitly. // https://docs.python.org/3/c-api/init_config.html#path-configuration if (g_buildconfig.contains_python_dist()) { - PyConfig_SetBytesString(&config, &config.base_exec_prefix, ""); - PyConfig_SetBytesString(&config, &config.base_executable, ""); - PyConfig_SetBytesString(&config, &config.base_prefix, ""); - PyConfig_SetBytesString(&config, &config.exec_prefix, ""); - PyConfig_SetBytesString(&config, &config.executable, ""); - PyConfig_SetBytesString(&config, &config.prefix, ""); + CheckPyInitStatus( + "pyconfig base_exec_prefix set", + PyConfig_SetBytesString(&config, &config.base_exec_prefix, "")); + CheckPyInitStatus( + "pyconfig base_executable set", + PyConfig_SetBytesString(&config, &config.base_executable, "")); + CheckPyInitStatus( + "pyconfig base_prefix set", + PyConfig_SetBytesString(&config, &config.base_prefix, "")); + CheckPyInitStatus( + "pyconfig exec_prefix set", + PyConfig_SetBytesString(&config, &config.exec_prefix, "")); + CheckPyInitStatus("pyconfig executable set", + PyConfig_SetBytesString(&config, &config.executable, "")); + CheckPyInitStatus("pyconfig prefix set", + PyConfig_SetBytesString(&config, &config.prefix, "")); // Note: we're using utf-8 mode above so Py_DecodeLocale will convert // from utf-8. @@ -121,8 +137,8 @@ void CorePython::InitPython() { } // Init Python. - status = Py_InitializeFromConfig(&config); - BA_PRECONDITION_FATAL(!PyStatus_Exception(status)); + CheckPyInitStatus("Py_InitializeFromConfig", + Py_InitializeFromConfig(&config)); PyConfig_Clear(&config); } diff --git a/src/ballistica/shared/ballistica.cc b/src/ballistica/shared/ballistica.cc index 3bed55a3..2393a2a2 100644 --- a/src/ballistica/shared/ballistica.cc +++ b/src/ballistica/shared/ballistica.cc @@ -39,7 +39,7 @@ auto main(int argc, char** argv) -> int { namespace ballistica { // These are set automatically via script; don't modify them here. -const int kEngineBuildNumber = 21165; +const int kEngineBuildNumber = 21167; const char* kEngineVersion = "1.7.22"; auto MonolithicMain(const core::CoreConfig& core_config) -> int { diff --git a/src/ballistica/shared/generic/json.cc b/src/ballistica/shared/generic/json.cc index bedbba5c..0ad22347 100644 --- a/src/ballistica/shared/generic/json.cc +++ b/src/ballistica/shared/generic/json.cc @@ -2,7 +2,7 @@ // Derived from code licensed as follows: /* - Copyright (c) 2009 Dave Gamble + Copyright (c) 2009-2017 Dave Gamble and cJSON contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -26,1088 +26,2708 @@ /* cJSON */ /* JSON parser in C. */ +// ericf note: Changes here from vanilla cJSON: +// - Lives under ballistica C++ namespace. +// - Changed some sprintfs to snprintfs to keep compiler quiet. +// - Removed ENABLE_LOCALES conditionals (want consistent decimal parsing). +// - Changed some c headers to c++ ones -> , etc. +// - Removed a few warnings and workarounds for gcc/msvc +// (will reenable if I see said warnings but assuming they're old). +// - Using std::isnan and std::isinf in place of potentially homespun +// versions. + #include "ballistica/shared/generic/json.h" #include -#include - -#if BA_OSTYPE_LINUX #include +#include +#include +#include #include + +/* define our own boolean type */ +#ifdef true +#undef true #endif +#define true ((cJSON_bool)1) + +#ifdef false +#undef false +#endif +#define false ((cJSON_bool)0) namespace ballistica { -// Should tidy this up but don't want to risk breaking it at the moment. -#pragma clang diagnostic push -#pragma ide diagnostic ignored "hicpp-signed-bitwise" -#pragma ide diagnostic ignored "bugprone-narrowing-conversions" -#pragma ide diagnostic ignored "cppcoreguidelines-narrowing-conversions" +typedef struct { + const unsigned char* json; + size_t position; +} error; +static error global_error = {NULL, 0}; -static const char* ep; - -auto cJSON_GetErrorPtr() -> const char* { return ep; } - -static auto cJSON_strcasecmp(const char* s1, const char* s2) -> int { - if (!s1) return (s1 == s2) ? 0 : 1; - if (!s2) return 1; - for (; tolower(*s1) == tolower(*s2); ++s1, ++s2) - if (*s1 == 0) return 0; - return tolower(*(const unsigned char*)s1) - - tolower(*(const unsigned char*)s2); +const char* cJSON_GetErrorPtr(void) { + return (const char*)(global_error.json + global_error.position); } -static void* (*cJSON_malloc)(size_t sz) = malloc; -static void (*cJSON_free)(void* ptr) = free; - -static auto cJSON_strdup(const char* str) -> char* { - size_t len; - char* copy; - - len = strlen(str) + 1; - if (!(copy = static_cast(cJSON_malloc(len)))) { - return nullptr; +char* cJSON_GetStringValue(const cJSON* const item) { + if (!cJSON_IsString(item)) { + return NULL; } - memcpy(copy, str, len); + + return item->valuestring; +} + +double cJSON_GetNumberValue(const cJSON* const item) { + if (!cJSON_IsNumber(item)) { + return (double)NAN; + } + + return item->valuedouble; +} + +/* This is a safeguard to prevent copy-pasters from using incompatible C and + * header files */ +#if (CJSON_VERSION_MAJOR != 1) || (CJSON_VERSION_MINOR != 7) \ + || (CJSON_VERSION_PATCH != 16) +#error cJSON.h and cJSON.c have different versions. Make sure that both have the same. +#endif + +const char* cJSON_Version(void) { + static char version[15]; + snprintf(version, sizeof(version), "%i.%i.%i", CJSON_VERSION_MAJOR, + CJSON_VERSION_MINOR, CJSON_VERSION_PATCH); + + return version; +} + +/* Case insensitive string comparison, doesn't consider two NULL pointers equal + * though */ +static int case_insensitive_strcmp(const unsigned char* string1, + const unsigned char* string2) { + if ((string1 == NULL) || (string2 == NULL)) { + return 1; + } + + if (string1 == string2) { + return 0; + } + + for (; tolower(*string1) == tolower(*string2); (void)string1++, string2++) { + if (*string1 == '\0') { + return 0; + } + } + + return tolower(*string1) - tolower(*string2); +} + +typedef struct internal_hooks { + void* (*allocate)(size_t size); + void (*deallocate)(void* pointer); + void* (*reallocate)(void* pointer, size_t size); +} internal_hooks; + +#if defined(_MSC_VER) +/* work around MSVC error C2322: '...' address of dllimport '...' is not static + */ +static void* internal_malloc(size_t size) { return malloc(size); } +static void internal_free(void* pointer) { free(pointer); } +static void* internal_realloc(void* pointer, size_t size) { + return realloc(pointer, size); +} +#else +#define internal_malloc malloc +#define internal_free free +#define internal_realloc realloc +#endif + +/* strlen of character literals resolved at compile time */ +#define static_strlen(string_literal) (sizeof(string_literal) - sizeof("")) + +static internal_hooks global_hooks = {internal_malloc, internal_free, + internal_realloc}; + +static unsigned char* cJSON_strdup(const unsigned char* string, + const internal_hooks* const hooks) { + size_t length = 0; + unsigned char* copy = NULL; + + if (string == NULL) { + return NULL; + } + + length = strlen((const char*)string) + sizeof(""); + copy = (unsigned char*)hooks->allocate(length); + if (copy == NULL) { + return NULL; + } + memcpy(copy, string, length); + return copy; } void cJSON_InitHooks(cJSON_Hooks* hooks) { - if (!hooks) { /* Reset hooks */ - cJSON_malloc = malloc; - cJSON_free = free; + if (hooks == NULL) { + /* Reset hooks */ + global_hooks.allocate = malloc; + global_hooks.deallocate = free; + global_hooks.reallocate = realloc; return; } - cJSON_malloc = (hooks->malloc_fn) ? hooks->malloc_fn : malloc; - cJSON_free = (hooks->free_fn) ? hooks->free_fn : free; + global_hooks.allocate = malloc; + if (hooks->malloc_fn != NULL) { + global_hooks.allocate = hooks->malloc_fn; + } + + global_hooks.deallocate = free; + if (hooks->free_fn != NULL) { + global_hooks.deallocate = hooks->free_fn; + } + + /* use realloc only if both free and malloc are used */ + global_hooks.reallocate = NULL; + if ((global_hooks.allocate == malloc) && (global_hooks.deallocate == free)) { + global_hooks.reallocate = realloc; + } } /* Internal constructor. */ -static auto cJSON_New_Item() -> cJSON* { - auto* node = static_cast(cJSON_malloc(sizeof(cJSON))); - if (node) memset(node, 0, sizeof(cJSON)); +static cJSON* cJSON_New_Item(const internal_hooks* const hooks) { + cJSON* node = (cJSON*)hooks->allocate(sizeof(cJSON)); + if (node) { + memset(node, '\0', sizeof(cJSON)); + } + return node; } /* Delete a cJSON structure. */ -void cJSON_Delete(cJSON* c) { - cJSON* next; - while (c) { - next = c->next; - if (!(c->type & cJSON_IsReference) && c->child) cJSON_Delete(c->child); - if (!(c->type & cJSON_IsReference) && c->valuestring) - cJSON_free(c->valuestring); - if (c->string) cJSON_free(c->string); - cJSON_free(c); - c = next; +void cJSON_Delete(cJSON* item) { + cJSON* next = NULL; + while (item != NULL) { + next = item->next; + if (!(item->type & cJSON_IsReference) && (item->child != NULL)) { + cJSON_Delete(item->child); + } + if (!(item->type & cJSON_IsReference) && (item->valuestring != NULL)) { + global_hooks.deallocate(item->valuestring); + } + if (!(item->type & cJSON_StringIsConst) && (item->string != NULL)) { + global_hooks.deallocate(item->string); + } + global_hooks.deallocate(item); + item = next; } } +/* get the decimal point character of the current locale */ +static unsigned char get_decimal_point(void) { return '.'; } + +typedef struct { + const unsigned char* content; + size_t length; + size_t offset; + size_t depth; /* How deeply nested (in arrays/objects) is the input at the + current offset. */ + internal_hooks hooks; +} parse_buffer; + +/* check if the given size is left to read in a given parse buffer (starting + * with 1) */ +#define can_read(buffer, size) \ + ((buffer != NULL) && (((buffer)->offset + size) <= (buffer)->length)) +/* check if the buffer can be accessed at the given index (starting with 0) */ +#define can_access_at_index(buffer, index) \ + ((buffer != NULL) && (((buffer)->offset + index) < (buffer)->length)) +#define cannot_access_at_index(buffer, index) \ + (!can_access_at_index(buffer, index)) +/* get a pointer to the buffer at the position */ +#define buffer_at_offset(buffer) ((buffer)->content + (buffer)->offset) + /* Parse the input text to generate a number, and populate the result into item. */ -static auto parse_number(cJSON* item, const char* num) -> const char* { - double n = 0, sign = 1, scale = 0; - int subscale = 0, signsubscale = 1; +static cJSON_bool parse_number(cJSON* const item, + parse_buffer* const input_buffer) { + double number = 0; + unsigned char* after_end = NULL; + unsigned char number_c_string[64]; + unsigned char decimal_point = get_decimal_point(); + size_t i = 0; - if (*num == '-') { - sign = -1; - num++; - } /* Has sign? */ - if (*num == '0') num++; /* is zero */ - if (*num >= '1' && *num <= '9') do - n = (n * 10.0f) + (*num++ - '0'); - while (*num >= '0' && *num <= '9'); /* Number? */ - if (*num == '.' && num[1] >= '0' && num[1] <= '9') { - num++; - do { - n = (n * 10.0f) + (*num++ - '0'); - scale--; - } while (*num >= '0' && *num <= '9'); - } /* Fractional part? */ - if (*num == 'e' || *num == 'E') /* Exponent? */ - { - num++; - if (*num == '+') - num++; - else if (*num == '-') { - signsubscale = -1; - num++; /* With sign? */ - } - while (*num >= '0' && *num <= '9') - subscale = (subscale * 10) + (*num++ - '0'); /* Number? */ + if ((input_buffer == NULL) || (input_buffer->content == NULL)) { + return false; } - n = sign * n - * pow(10.0f, - (scale + subscale * signsubscale)); /* number = +/- number.fraction - * 10^+/- exponent */ + /* copy the number into a temporary buffer and replace '.' with the decimal + * point of the current locale (for strtod) This also takes care of '\0' not + * necessarily being available for marking the end of the input */ + for (i = 0; (i < (sizeof(number_c_string) - 1)) + && can_access_at_index(input_buffer, i); + i++) { + switch (buffer_at_offset(input_buffer)[i]) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '+': + case '-': + case 'e': + case 'E': + number_c_string[i] = buffer_at_offset(input_buffer)[i]; + break; + + case '.': + number_c_string[i] = decimal_point; + break; + + default: + goto loop_end; + } + } +loop_end: + number_c_string[i] = '\0'; + + number = strtod((const char*)number_c_string, (char**)&after_end); + if (number_c_string == after_end) { + return false; /* parse_error */ + } + + item->valuedouble = number; + + /* use saturation in case of overflow */ + if (number >= INT_MAX) { + item->valueint = INT_MAX; + } else if (number <= (double)INT_MIN) { + item->valueint = INT_MIN; + } else { + item->valueint = (int)number; + } - item->valuedouble = n; - item->valueint = (int)n; item->type = cJSON_Number; - return num; + + input_buffer->offset += (size_t)(after_end - number_c_string); + return true; +} + +/* don't ask me, but the original cJSON_SetNumberValue returns an integer or + * double */ +double cJSON_SetNumberHelper(cJSON* object, double number) { + if (number >= INT_MAX) { + object->valueint = INT_MAX; + } else if (number <= (double)INT_MIN) { + object->valueint = INT_MIN; + } else { + object->valueint = (int)number; + } + + return object->valuedouble = number; +} + +char* cJSON_SetValuestring(cJSON* object, const char* valuestring) { + char* copy = NULL; + /* if object's type is not cJSON_String or is cJSON_IsReference, it should not + * set valuestring */ + if (!(object->type & cJSON_String) || (object->type & cJSON_IsReference)) { + return NULL; + } + if (strlen(valuestring) <= strlen(object->valuestring)) { + strcpy(object->valuestring, valuestring); + return object->valuestring; + } + copy = (char*)cJSON_strdup((const unsigned char*)valuestring, &global_hooks); + if (copy == NULL) { + return NULL; + } + if (object->valuestring != NULL) { + cJSON_free(object->valuestring); + } + object->valuestring = copy; + + return copy; +} + +typedef struct { + unsigned char* buffer; + size_t length; + size_t offset; + size_t depth; /* current nesting depth (for formatted printing) */ + cJSON_bool noalloc; + cJSON_bool format; /* is this print a formatted print */ + internal_hooks hooks; +} printbuffer; + +/* realloc printbuffer if necessary to have at least "needed" bytes more */ +static unsigned char* ensure(printbuffer* const p, size_t needed) { + unsigned char* newbuffer = NULL; + size_t newsize = 0; + + if ((p == NULL) || (p->buffer == NULL)) { + return NULL; + } + + if ((p->length > 0) && (p->offset >= p->length)) { + /* make sure that offset is valid */ + return NULL; + } + + if (needed > INT_MAX) { + /* sizes bigger than INT_MAX are currently not supported */ + return NULL; + } + + needed += p->offset + 1; + if (needed <= p->length) { + return p->buffer + p->offset; + } + + if (p->noalloc) { + return NULL; + } + + /* calculate new buffer size */ + if (needed > (INT_MAX / 2)) { + /* overflow of int, use INT_MAX if possible */ + if (needed <= INT_MAX) { + newsize = INT_MAX; + } else { + return NULL; + } + } else { + newsize = needed * 2; + } + + if (p->hooks.reallocate != NULL) { + /* reallocate with realloc if available */ + newbuffer = (unsigned char*)p->hooks.reallocate(p->buffer, newsize); + if (newbuffer == NULL) { + p->hooks.deallocate(p->buffer); + p->length = 0; + p->buffer = NULL; + + return NULL; + } + } else { + /* otherwise reallocate manually */ + newbuffer = (unsigned char*)p->hooks.allocate(newsize); + if (!newbuffer) { + p->hooks.deallocate(p->buffer); + p->length = 0; + p->buffer = NULL; + + return NULL; + } + + memcpy(newbuffer, p->buffer, p->offset + 1); + p->hooks.deallocate(p->buffer); + } + p->length = newsize; + p->buffer = newbuffer; + + return newbuffer + p->offset; +} + +/* calculate the new length of the string in a printbuffer and update the offset + */ +static void update_offset(printbuffer* const buffer) { + const unsigned char* buffer_pointer = NULL; + if ((buffer == NULL) || (buffer->buffer == NULL)) { + return; + } + buffer_pointer = buffer->buffer + buffer->offset; + + buffer->offset += strlen((const char*)buffer_pointer); +} + +/* securely comparison of floating-point variables */ +static cJSON_bool compare_double(double a, double b) { + double maxVal = fabs(a) > fabs(b) ? fabs(a) : fabs(b); + return (fabs(a - b) <= maxVal * DBL_EPSILON); } /* Render the number nicely from the given item into a string. */ -static auto print_number(cJSON* item) -> char* { - char* str; +static cJSON_bool print_number(const cJSON* const item, + printbuffer* const output_buffer) { + unsigned char* output_pointer = NULL; double d = item->valuedouble; - if (fabs(((double)item->valueint) - d) <= DBL_EPSILON && d <= INT_MAX - && d >= INT_MIN) { - size_t sz{21}; - str = (char*)cJSON_malloc(sz); /* 2^64+1 can be represented in 21 chars. */ - if (str) snprintf(str, sz, "%d", item->valueint); + int length = 0; + size_t i = 0; + unsigned char number_buffer[26] = { + 0}; /* temporary buffer to print the number into */ + unsigned char decimal_point = get_decimal_point(); + double test = 0.0; + + if (output_buffer == NULL) { + return false; + } + + /* This checks for NaN and Infinity */ + if (std::isnan(d) || std::isinf(d)) { + length = snprintf((char*)number_buffer, sizeof(number_buffer), "null"); + } else if (d == (double)item->valueint) { + length = snprintf((char*)number_buffer, sizeof(number_buffer), "%d", + item->valueint); } else { - size_t sz{64}; - str = (char*)cJSON_malloc(sz); /* This is a nice tradeoff. */ - if (str) { - if (fabs(floor(d) - d) <= DBL_EPSILON && fabs(d) < 1.0e60) - snprintf(str, sz, "%.0f", d); - else if (fabs(d) < 1.0e-6 || fabs(d) > 1.0e9) - snprintf(str, sz, "%e", d); - else - snprintf(str, sz, "%f", d); + /* Try 15 decimal places of precision to avoid nonsignificant nonzero digits + */ + length = snprintf((char*)number_buffer, sizeof(number_buffer), "%1.15g", d); + + /* Check whether the original double can be recovered */ + if ((sscanf((char*)number_buffer, "%lg", &test) != 1) + || !compare_double((double)test, d)) { + /* If not, print with 17 decimal places of precision */ + length = + snprintf((char*)number_buffer, sizeof(number_buffer), "%1.17g", d); } } - return str; + + /* sprintf failed or buffer overrun occurred */ + if ((length < 0) || (length > (int)(sizeof(number_buffer) - 1))) { + return false; + } + + /* reserve appropriate space in the output */ + output_pointer = ensure(output_buffer, (size_t)length + sizeof("")); + if (output_pointer == NULL) { + return false; + } + + /* copy the printed number to the output and replace locale + * dependent decimal point with '.' */ + for (i = 0; i < ((size_t)length); i++) { + if (number_buffer[i] == decimal_point) { + output_pointer[i] = '.'; + continue; + } + + output_pointer[i] = number_buffer[i]; + } + output_pointer[i] = '\0'; + + output_buffer->offset += (size_t)length; + + return true; } -static auto parse_hex4(const char* str) -> unsigned { - unsigned h = 0; - if (*str >= '0' && *str <= '9') - h += (*str) - '0'; - else if (*str >= 'A' && *str <= 'F') - h += 10 + (*str) - 'A'; - else if (*str >= 'a' && *str <= 'f') - h += 10 + (*str) - 'a'; - else - return 0; - h = h << 4; - str++; - if (*str >= '0' && *str <= '9') - h += (*str) - '0'; - else if (*str >= 'A' && *str <= 'F') - h += 10 + (*str) - 'A'; - else if (*str >= 'a' && *str <= 'f') - h += 10 + (*str) - 'a'; - else - return 0; - h = h << 4; - str++; - if (*str >= '0' && *str <= '9') - h += (*str) - '0'; - else if (*str >= 'A' && *str <= 'F') - h += 10 + (*str) - 'A'; - else if (*str >= 'a' && *str <= 'f') - h += 10 + (*str) - 'a'; - else - return 0; - h = h << 4; - str++; - if (*str >= '0' && *str <= '9') - h += (*str) - '0'; - else if (*str >= 'A' && *str <= 'F') - h += 10 + (*str) - 'A'; - else if (*str >= 'a' && *str <= 'f') - h += 10 + (*str) - 'a'; - else - return 0; +/* parse 4 digit hexadecimal number */ +static unsigned parse_hex4(const unsigned char* const input) { + unsigned int h = 0; + size_t i = 0; + + for (i = 0; i < 4; i++) { + /* parse digit */ + if ((input[i] >= '0') && (input[i] <= '9')) { + h += (unsigned int)input[i] - '0'; + } else if ((input[i] >= 'A') && (input[i] <= 'F')) { + h += (unsigned int)10 + input[i] - 'A'; + } else if ((input[i] >= 'a') && (input[i] <= 'f')) { + h += (unsigned int)10 + input[i] - 'a'; + } else /* invalid */ + { + return 0; + } + + if (i < 3) { + /* shift left to make place for the next nibble */ + h = h << 4; + } + } + return h; } -/* Parse the input text into an unescaped cstring, and populate item. */ -static const unsigned char firstByteMark[7] = {0x00, 0x00, 0xC0, 0xE0, - 0xF0, 0xF8, 0xFC}; -static auto parse_string(cJSON* item, const char* str) -> const char* { - const char* ptr = str + 1; - char* ptr2; - char* out; - size_t len = 0; - unsigned uc, uc2; - if (*str != '\"') { - ep = str; - return nullptr; - } /* not a string! */ +/* converts a UTF-16 literal to UTF-8 + * A literal can be one or two sequences of the form \uXXXX */ +static unsigned char utf16_literal_to_utf8( + const unsigned char* const input_pointer, + const unsigned char* const input_end, unsigned char** output_pointer) { + long unsigned int codepoint = 0; + unsigned int first_code = 0; + const unsigned char* first_sequence = input_pointer; + unsigned char utf8_length = 0; + unsigned char utf8_position = 0; + unsigned char sequence_length = 0; + unsigned char first_byte_mark = 0; - while (*ptr != '\"' && *ptr && ++len) { - if (*ptr++ == '\\') { - ptr++; /* Skip escaped quotes. */ + if ((input_end - first_sequence) < 6) { + /* input ends unexpectedly */ + goto fail; + } + + /* get the first utf16 sequence */ + first_code = parse_hex4(first_sequence + 2); + + /* check that the code is valid */ + if (((first_code >= 0xDC00) && (first_code <= 0xDFFF))) { + goto fail; + } + + /* UTF16 surrogate pair */ + if ((first_code >= 0xD800) && (first_code <= 0xDBFF)) { + const unsigned char* second_sequence = first_sequence + 6; + unsigned int second_code = 0; + sequence_length = 12; /* \uXXXX\uXXXX */ + + if ((input_end - second_sequence) < 6) { + /* input ends unexpectedly */ + goto fail; + } + + if ((second_sequence[0] != '\\') || (second_sequence[1] != 'u')) { + /* missing second half of the surrogate pair */ + goto fail; + } + + /* get the second utf16 sequence */ + second_code = parse_hex4(second_sequence + 2); + /* check that the code is valid */ + if ((second_code < 0xDC00) || (second_code > 0xDFFF)) { + /* invalid second half of the surrogate pair */ + goto fail; + } + + /* calculate the unicode codepoint from the surrogate pair */ + codepoint = + 0x10000 + (((first_code & 0x3FF) << 10) | (second_code & 0x3FF)); + } else { + sequence_length = 6; /* \uXXXX */ + codepoint = first_code; + } + + /* encode as UTF-8 + * takes at maximum 4 bytes to encode: + * 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */ + if (codepoint < 0x80) { + /* normal ascii, encoding 0xxxxxxx */ + utf8_length = 1; + } else if (codepoint < 0x800) { + /* two bytes, encoding 110xxxxx 10xxxxxx */ + utf8_length = 2; + first_byte_mark = 0xC0; /* 11000000 */ + } else if (codepoint < 0x10000) { + /* three bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx */ + utf8_length = 3; + first_byte_mark = 0xE0; /* 11100000 */ + } else if (codepoint <= 0x10FFFF) { + /* four bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx 10xxxxxx */ + utf8_length = 4; + first_byte_mark = 0xF0; /* 11110000 */ + } else { + /* invalid unicode codepoint */ + goto fail; + } + + /* encode as utf8 */ + for (utf8_position = (unsigned char)(utf8_length - 1); utf8_position > 0; + utf8_position--) { + /* 10xxxxxx */ + (*output_pointer)[utf8_position] = + (unsigned char)((codepoint | 0x80) & 0xBF); + codepoint >>= 6; + } + /* encode first byte */ + if (utf8_length > 1) { + (*output_pointer)[0] = + (unsigned char)((codepoint | first_byte_mark) & 0xFF); + } else { + (*output_pointer)[0] = (unsigned char)(codepoint & 0x7F); + } + + *output_pointer += utf8_length; + + return sequence_length; + +fail: + return 0; +} + +/* Parse the input text into an unescaped cinput, and populate item. */ +static cJSON_bool parse_string(cJSON* const item, + parse_buffer* const input_buffer) { + const unsigned char* input_pointer = buffer_at_offset(input_buffer) + 1; + const unsigned char* input_end = buffer_at_offset(input_buffer) + 1; + unsigned char* output_pointer = NULL; + unsigned char* output = NULL; + + /* not a string */ + if (buffer_at_offset(input_buffer)[0] != '\"') { + goto fail; + } + + { + /* calculate approximate size of the output (overestimate) */ + size_t allocation_length = 0; + size_t skipped_bytes = 0; + while (((size_t)(input_end - input_buffer->content) < input_buffer->length) + && (*input_end != '\"')) { + /* is escape sequence */ + if (input_end[0] == '\\') { + if ((size_t)(input_end + 1 - input_buffer->content) + >= input_buffer->length) { + /* prevent buffer overflow when last input character is a backslash */ + goto fail; + } + skipped_bytes++; + input_end++; + } + input_end++; + } + if (((size_t)(input_end - input_buffer->content) >= input_buffer->length) + || (*input_end != '\"')) { + goto fail; /* string ended unexpectedly */ + } + + /* This is at most how much we need for the output */ + allocation_length = + (size_t)(input_end - buffer_at_offset(input_buffer)) - skipped_bytes; + output = (unsigned char*)input_buffer->hooks.allocate(allocation_length + + sizeof("")); + if (output == NULL) { + goto fail; /* allocation failure */ } } - // This is how long we need for the string, roughly. - out = (char*)cJSON_malloc(len + 1); - if (!out) return nullptr; + output_pointer = output; + /* loop through the string literal */ + while (input_pointer < input_end) { + if (*input_pointer != '\\') { + *output_pointer++ = *input_pointer++; + } + /* escape sequence */ + else { + unsigned char sequence_length = 2; + if ((input_end - input_pointer) < 1) { + goto fail; + } - ptr = str + 1; - ptr2 = out; - while (*ptr != '\"' && *ptr) { - if (*ptr != '\\') { - *ptr2++ = *ptr++; - } else { - ptr++; - switch (*ptr) { + switch (input_pointer[1]) { case 'b': - *ptr2++ = '\b'; + *output_pointer++ = '\b'; break; case 'f': - *ptr2++ = '\f'; + *output_pointer++ = '\f'; break; case 'n': - *ptr2++ = '\n'; + *output_pointer++ = '\n'; break; case 'r': - *ptr2++ = '\r'; + *output_pointer++ = '\r'; break; case 't': - *ptr2++ = '\t'; + *output_pointer++ = '\t'; break; - case 'u': /* transcode utf16 to utf8. */ - uc = parse_hex4(ptr + 1); - ptr += 4; /* get the unicode char. */ - - if ((uc >= 0xDC00 && uc <= 0xDFFF) || uc == 0) { - break; // check for invalid. - } - - // UTF16 surrogate pairs. - if (uc >= 0xD800 && uc <= 0xDBFF) { - if (ptr[1] != '\\' || ptr[2] != 'u') - break; /* missing second-half of surrogate. */ - uc2 = parse_hex4(ptr + 3); - ptr += 6; - if (uc2 < 0xDC00 || uc2 > 0xDFFF) - break; /* invalid second-half of surrogate. */ - uc = 0x10000 + (((uc & 0x3FF) << 10) | (uc2 & 0x3FF)); - } - - len = 4; - if (uc < 0x80) { - len = 1; - } else if (uc < 0x800) { - len = 2; - } else if (uc < 0x10000) { - len = 3; - } - ptr2 += len; - - switch (len) { - case 4: // NOLINT(bugprone-branch-clone) - *--ptr2 = static_cast((uc | 0x80) & 0xBF); - uc >>= 6; - case 3: - *--ptr2 = static_cast((uc | 0x80) & 0xBF); - uc >>= 6; - case 2: - *--ptr2 = static_cast((uc | 0x80) & 0xBF); - uc >>= 6; - case 1: - *--ptr2 = static_cast(uc | firstByteMark[len]); - default: - break; - } - ptr2 += len; + case '\"': + case '\\': + case '/': + *output_pointer++ = input_pointer[1]; break; + + /* UTF-16 literal */ + case 'u': + sequence_length = + utf16_literal_to_utf8(input_pointer, input_end, &output_pointer); + if (sequence_length == 0) { + /* failed to convert UTF16-literal to UTF-8 */ + goto fail; + } + break; + default: - *ptr2++ = *ptr; - break; + goto fail; } - ptr++; + input_pointer += sequence_length; } } - *ptr2 = 0; - if (*ptr == '\"') { - ptr++; - } - item->valuestring = out; + + /* zero terminate the output */ + *output_pointer = '\0'; + item->type = cJSON_String; - return ptr; + item->valuestring = (char*)output; + + input_buffer->offset = (size_t)(input_end - input_buffer->content); + input_buffer->offset++; + + return true; + +fail: + if (output != NULL) { + input_buffer->hooks.deallocate(output); + } + + if (input_pointer != NULL) { + input_buffer->offset = (size_t)(input_pointer - input_buffer->content); + } + + return false; } /* Render the cstring provided to an escaped version that can be printed. */ -static auto print_string_ptr(const char* str) -> char* { - const char* ptr; - char *ptr2, *out; - size_t len = 0; - unsigned char token; +static cJSON_bool print_string_ptr(const unsigned char* const input, + printbuffer* const output_buffer) { + const unsigned char* input_pointer = NULL; + unsigned char* output = NULL; + unsigned char* output_pointer = NULL; + size_t output_length = 0; + /* numbers of additional characters needed for escaping */ + size_t escape_characters = 0; - if (!str) { - return cJSON_strdup(""); + if (output_buffer == NULL) { + return false; } - ptr = str; - while ((token = static_cast(*ptr)) && ++len) { - if (strchr("\"\\\b\f\n\r\t", token)) { - len++; - } else if (token < 32) { - len += 5; + + /* empty string */ + if (input == NULL) { + output = ensure(output_buffer, sizeof("\"\"")); + if (output == NULL) { + return false; } - ptr++; + strcpy((char*)output, "\"\""); + + return true; } - out = (char*)cJSON_malloc(len + 3); - if (!out) { - return nullptr; + /* set "flag" to 1 if something needs to be escaped */ + for (input_pointer = input; *input_pointer; input_pointer++) { + switch (*input_pointer) { + case '\"': + case '\\': + case '\b': + case '\f': + case '\n': + case '\r': + case '\t': + /* one character escape sequence */ + escape_characters++; + break; + default: + if (*input_pointer < 32) { + /* UTF-16 escape sequence uXXXX */ + escape_characters += 5; + } + break; + } + } + output_length = (size_t)(input_pointer - input) + escape_characters; + + output = ensure(output_buffer, output_length + sizeof("\"\"")); + if (output == NULL) { + return false; } - ptr2 = out; - ptr = str; - *ptr2++ = '\"'; - while (*ptr) { - if ((unsigned char)*ptr > 31 && *ptr != '\"' && *ptr != '\\') { - *ptr2++ = *ptr++; + /* no characters have to be escaped */ + if (escape_characters == 0) { + output[0] = '\"'; + memcpy(output + 1, input, output_length); + output[output_length + 1] = '\"'; + output[output_length + 2] = '\0'; + + return true; + } + + output[0] = '\"'; + output_pointer = output + 1; + /* copy the string */ + for (input_pointer = input; *input_pointer != '\0'; + (void)input_pointer++, output_pointer++) { + if ((*input_pointer > 31) && (*input_pointer != '\"') + && (*input_pointer != '\\')) { + /* normal character, copy */ + *output_pointer = *input_pointer; } else { - *ptr2++ = '\\'; - switch (token = static_cast(*ptr++)) { + /* character needs to be escaped */ + *output_pointer++ = '\\'; + switch (*input_pointer) { case '\\': - *ptr2++ = '\\'; + *output_pointer = '\\'; break; case '\"': - *ptr2++ = '\"'; + *output_pointer = '\"'; break; case '\b': - *ptr2++ = 'b'; + *output_pointer = 'b'; break; case '\f': - *ptr2++ = 'f'; + *output_pointer = 'f'; break; case '\n': - *ptr2++ = 'n'; + *output_pointer = 'n'; break; case '\r': - *ptr2++ = 'r'; + *output_pointer = 'r'; break; case '\t': - *ptr2++ = 't'; + *output_pointer = 't'; break; default: - snprintf(ptr2, 20, "u%04x", token); - ptr2 += 5; - break; /* escape and print */ + /* escape and print as unicode codepoint */ + snprintf((char*)output_pointer, 32, "u%04x", *input_pointer); + output_pointer += 4; + break; } } } - *ptr2++ = '\"'; - *ptr2 = 0; - return out; + output[output_length + 1] = '\"'; + output[output_length + 2] = '\0'; + + return true; } -/* Invote print_string_ptr (which is useful) on an item. */ -static auto print_string(cJSON* item) -> char* { - return print_string_ptr(item->valuestring); + +/* Invoke print_string_ptr (which is useful) on an item. */ +static cJSON_bool print_string(const cJSON* const item, printbuffer* const p) { + return print_string_ptr((unsigned char*)item->valuestring, p); } /* Predeclare these prototypes. */ -static auto parse_value(cJSON* item, const char* value) -> const char*; -static auto print_value(cJSON* item, int depth, int fmt) -> char*; -static auto parse_array(cJSON* item, const char* value) -> const char*; -static auto print_array(cJSON* item, int depth, int fmt) -> char*; -static auto parse_object(cJSON* item, const char* value) -> const char*; -static auto print_object(cJSON* item, int depth, int fmt) -> char*; +static cJSON_bool parse_value(cJSON* const item, + parse_buffer* const input_buffer); +static cJSON_bool print_value(const cJSON* const item, + printbuffer* const output_buffer); +static cJSON_bool parse_array(cJSON* const item, + parse_buffer* const input_buffer); +static cJSON_bool print_array(const cJSON* const item, + printbuffer* const output_buffer); +static cJSON_bool parse_object(cJSON* const item, + parse_buffer* const input_buffer); +static cJSON_bool print_object(const cJSON* const item, + printbuffer* const output_buffer); /* Utility to jump whitespace and cr/lf */ -static auto skip(const char* in) -> const char* { - while (in && *in && (unsigned char)*in <= 32) in++; - return in; +static parse_buffer* buffer_skip_whitespace(parse_buffer* const buffer) { + if ((buffer == NULL) || (buffer->content == NULL)) { + return NULL; + } + + if (cannot_access_at_index(buffer, 0)) { + return buffer; + } + + while (can_access_at_index(buffer, 0) + && (buffer_at_offset(buffer)[0] <= 32)) { + buffer->offset++; + } + + if (buffer->offset == buffer->length) { + buffer->offset--; + } + + return buffer; +} + +/* skip the UTF-8 BOM (byte order mark) if it is at the beginning of a buffer */ +static parse_buffer* skip_utf8_bom(parse_buffer* const buffer) { + if ((buffer == NULL) || (buffer->content == NULL) || (buffer->offset != 0)) { + return NULL; + } + + if (can_access_at_index(buffer, 4) + && (strncmp((const char*)buffer_at_offset(buffer), "\xEF\xBB\xBF", 3) + == 0)) { + buffer->offset += 3; + } + + return buffer; +} + +cJSON* cJSON_ParseWithOpts(const char* value, const char** return_parse_end, + cJSON_bool require_null_terminated) { + size_t buffer_length; + + if (NULL == value) { + return NULL; + } + + /* Adding null character size due to require_null_terminated. */ + buffer_length = strlen(value) + sizeof(""); + + return cJSON_ParseWithLengthOpts(value, buffer_length, return_parse_end, + require_null_terminated); } /* Parse an object - create a new root, and populate. */ -auto cJSON_ParseWithOpts(const char* value, const char** return_parse_end, - int require_null_terminated) -> cJSON* { - cJSON* c = cJSON_New_Item(); - ep = nullptr; - if (!c) { - return nullptr; /* memory fail */ +cJSON* cJSON_ParseWithLengthOpts(const char* value, size_t buffer_length, + const char** return_parse_end, + cJSON_bool require_null_terminated) { + parse_buffer buffer = {0, 0, 0, 0, {0, 0, 0}}; + cJSON* item = NULL; + + /* reset error position */ + global_error.json = NULL; + global_error.position = 0; + + if (value == NULL || 0 == buffer_length) { + goto fail; } - const char* end = parse_value(c, skip(value)); - if (!end) { - cJSON_Delete(c); - return nullptr; - } /* parse failure. ep is set. */ + buffer.content = (const unsigned char*)value; + buffer.length = buffer_length; + buffer.offset = 0; + buffer.hooks = global_hooks; + + item = cJSON_New_Item(&global_hooks); + if (item == NULL) /* memory fail */ + { + goto fail; + } + + if (!parse_value(item, buffer_skip_whitespace(skip_utf8_bom(&buffer)))) { + /* parse failure. ep is set. */ + goto fail; + } /* if we require null-terminated JSON without appended garbage, skip and then * check for a null terminator */ if (require_null_terminated) { - end = skip(end); - if (*end) { - cJSON_Delete(c); - ep = end; - return nullptr; + buffer_skip_whitespace(&buffer); + if ((buffer.offset >= buffer.length) + || buffer_at_offset(&buffer)[0] != '\0') { + goto fail; } } - if (return_parse_end) *return_parse_end = end; - return c; + if (return_parse_end) { + *return_parse_end = (const char*)buffer_at_offset(&buffer); + } + + return item; + +fail: + if (item != NULL) { + cJSON_Delete(item); + } + + if (value != NULL) { + error local_error; + local_error.json = (const unsigned char*)value; + local_error.position = 0; + + if (buffer.offset < buffer.length) { + local_error.position = buffer.offset; + } else if (buffer.length > 0) { + local_error.position = buffer.length - 1; + } + + if (return_parse_end != NULL) { + *return_parse_end = (const char*)local_error.json + local_error.position; + } + + global_error = local_error; + } + + return NULL; } + /* Default options for cJSON_Parse */ -auto cJSON_Parse(const char* value) -> cJSON* { - return cJSON_ParseWithOpts(value, nullptr, 0); +cJSON* cJSON_Parse(const char* value) { + return cJSON_ParseWithOpts(value, 0, 0); +} + +cJSON* cJSON_ParseWithLength(const char* value, size_t buffer_length) { + return cJSON_ParseWithLengthOpts(value, buffer_length, 0, 0); +} + +#define cjson_min(a, b) (((a) < (b)) ? (a) : (b)) + +static unsigned char* print(const cJSON* const item, cJSON_bool format, + const internal_hooks* const hooks) { + static const size_t default_buffer_size = 256; + printbuffer buffer[1]; + unsigned char* printed = NULL; + + memset(buffer, 0, sizeof(buffer)); + + /* create buffer */ + buffer->buffer = (unsigned char*)hooks->allocate(default_buffer_size); + buffer->length = default_buffer_size; + buffer->format = format; + buffer->hooks = *hooks; + if (buffer->buffer == NULL) { + goto fail; + } + + /* print the value */ + if (!print_value(item, buffer)) { + goto fail; + } + update_offset(buffer); + + /* check if reallocate is available */ + if (hooks->reallocate != NULL) { + printed = + (unsigned char*)hooks->reallocate(buffer->buffer, buffer->offset + 1); + if (printed == NULL) { + goto fail; + } + buffer->buffer = NULL; + } else /* otherwise copy the JSON over to a new buffer */ + { + printed = (unsigned char*)hooks->allocate(buffer->offset + 1); + if (printed == NULL) { + goto fail; + } + memcpy(printed, buffer->buffer, + cjson_min(buffer->length, buffer->offset + 1)); + printed[buffer->offset] = '\0'; /* just to be sure */ + + /* free the buffer */ + hooks->deallocate(buffer->buffer); + } + + return printed; + +fail: + if (buffer->buffer != NULL) { + hooks->deallocate(buffer->buffer); + } + + if (printed != NULL) { + hooks->deallocate(printed); + } + + return NULL; } /* Render a cJSON item/entity/structure to text. */ -auto cJSON_Print(cJSON* item) -> char* { return print_value(item, 0, 1); } -auto cJSON_PrintUnformatted(cJSON* item) -> char* { - return print_value(item, 0, 0); +char* cJSON_Print(const cJSON* item) { + return (char*)print(item, true, &global_hooks); +} + +char* cJSON_PrintUnformatted(const cJSON* item) { + return (char*)print(item, false, &global_hooks); +} + +char* cJSON_PrintBuffered(const cJSON* item, int prebuffer, cJSON_bool fmt) { + printbuffer p = {0, 0, 0, 0, 0, 0, {0, 0, 0}}; + + if (prebuffer < 0) { + return NULL; + } + + p.buffer = (unsigned char*)global_hooks.allocate((size_t)prebuffer); + if (!p.buffer) { + return NULL; + } + + p.length = (size_t)prebuffer; + p.offset = 0; + p.noalloc = false; + p.format = fmt; + p.hooks = global_hooks; + + if (!print_value(item, &p)) { + global_hooks.deallocate(p.buffer); + return NULL; + } + + return (char*)p.buffer; +} + +cJSON_bool cJSON_PrintPreallocated(cJSON* item, char* buffer, const int length, + const cJSON_bool format) { + printbuffer p = {0, 0, 0, 0, 0, 0, {0, 0, 0}}; + + if ((length < 0) || (buffer == NULL)) { + return false; + } + + p.buffer = (unsigned char*)buffer; + p.length = (size_t)length; + p.offset = 0; + p.noalloc = true; + p.format = format; + p.hooks = global_hooks; + + return print_value(item, &p); } /* Parser core - when encountering text, process appropriately. */ -static auto parse_value(cJSON* item, const char* value) -> const char* { - if (!value) { - return nullptr; /* Fail on null. */ - } - if (!strncmp(value, "null", 4)) { - item->type = cJSON_NULL; - return value + 4; - } - if (!strncmp(value, "false", 5)) { - item->type = cJSON_False; - return value + 5; - } - if (!strncmp(value, "true", 4)) { - item->type = cJSON_True; - item->valueint = 1; - return value + 4; - } - if (*value == '\"') { - return parse_string(item, value); - } - if (*value == '-' || (*value >= '0' && *value <= '9')) { - return parse_number(item, value); - } - if (*value == '[') { - return parse_array(item, value); - } - if (*value == '{') { - return parse_object(item, value); +static cJSON_bool parse_value(cJSON* const item, + parse_buffer* const input_buffer) { + if ((input_buffer == NULL) || (input_buffer->content == NULL)) { + return false; /* no input */ } - ep = value; - return nullptr; /* failure. */ + /* parse the different types of values */ + /* null */ + if (can_read(input_buffer, 4) + && (strncmp((const char*)buffer_at_offset(input_buffer), "null", 4) + == 0)) { + item->type = cJSON_NULL; + input_buffer->offset += 4; + return true; + } + /* false */ + if (can_read(input_buffer, 5) + && (strncmp((const char*)buffer_at_offset(input_buffer), "false", 5) + == 0)) { + item->type = cJSON_False; + input_buffer->offset += 5; + return true; + } + /* true */ + if (can_read(input_buffer, 4) + && (strncmp((const char*)buffer_at_offset(input_buffer), "true", 4) + == 0)) { + item->type = cJSON_True; + item->valueint = 1; + input_buffer->offset += 4; + return true; + } + /* string */ + if (can_access_at_index(input_buffer, 0) + && (buffer_at_offset(input_buffer)[0] == '\"')) { + return parse_string(item, input_buffer); + } + /* number */ + if (can_access_at_index(input_buffer, 0) + && ((buffer_at_offset(input_buffer)[0] == '-') + || ((buffer_at_offset(input_buffer)[0] >= '0') + && (buffer_at_offset(input_buffer)[0] <= '9')))) { + return parse_number(item, input_buffer); + } + /* array */ + if (can_access_at_index(input_buffer, 0) + && (buffer_at_offset(input_buffer)[0] == '[')) { + return parse_array(item, input_buffer); + } + /* object */ + if (can_access_at_index(input_buffer, 0) + && (buffer_at_offset(input_buffer)[0] == '{')) { + return parse_object(item, input_buffer); + } + + return false; } /* Render a value to text. */ -static auto print_value(cJSON* item, int depth, int fmt) -> char* { - char* out = nullptr; - if (!item) { - return nullptr; +static cJSON_bool print_value(const cJSON* const item, + printbuffer* const output_buffer) { + unsigned char* output = NULL; + + if ((item == NULL) || (output_buffer == NULL)) { + return false; } - switch ((item->type) & 255) { + + switch ((item->type) & 0xFF) { case cJSON_NULL: - out = cJSON_strdup("null"); - break; + output = ensure(output_buffer, 5); + if (output == NULL) { + return false; + } + strcpy((char*)output, "null"); + return true; + case cJSON_False: - out = cJSON_strdup("false"); - break; + output = ensure(output_buffer, 6); + if (output == NULL) { + return false; + } + strcpy((char*)output, "false"); + return true; + case cJSON_True: - out = cJSON_strdup("true"); - break; + output = ensure(output_buffer, 5); + if (output == NULL) { + return false; + } + strcpy((char*)output, "true"); + return true; + case cJSON_Number: - out = print_number(item); - break; + return print_number(item, output_buffer); + + case cJSON_Raw: { + size_t raw_length = 0; + if (item->valuestring == NULL) { + return false; + } + + raw_length = strlen(item->valuestring) + sizeof(""); + output = ensure(output_buffer, raw_length); + if (output == NULL) { + return false; + } + memcpy(output, item->valuestring, raw_length); + return true; + } + case cJSON_String: - out = print_string(item); - break; + return print_string(item, output_buffer); + case cJSON_Array: - out = print_array(item, depth, fmt); - break; + return print_array(item, output_buffer); + case cJSON_Object: - out = print_object(item, depth, fmt); - break; + return print_object(item, output_buffer); + default: - break; + return false; } - return out; } /* Build an array from input text. */ -static auto parse_array(cJSON* item, const char* value) -> const char* { - cJSON* child; - if (*value != '[') { - ep = value; - return nullptr; - } /* not an array! */ +static cJSON_bool parse_array(cJSON* const item, + parse_buffer* const input_buffer) { + cJSON* head = NULL; /* head of the linked list */ + cJSON* current_item = NULL; + + if (input_buffer->depth >= CJSON_NESTING_LIMIT) { + return false; /* to deeply nested */ + } + input_buffer->depth++; + + if (buffer_at_offset(input_buffer)[0] != '[') { + /* not an array */ + goto fail; + } + + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (can_access_at_index(input_buffer, 0) + && (buffer_at_offset(input_buffer)[0] == ']')) { + /* empty array */ + goto success; + } + + /* check if we skipped to the end of the buffer */ + if (cannot_access_at_index(input_buffer, 0)) { + input_buffer->offset--; + goto fail; + } + + /* step back to character in front of the first element */ + input_buffer->offset--; + /* loop through the comma separated array elements */ + do { + /* allocate next item */ + cJSON* new_item = cJSON_New_Item(&(input_buffer->hooks)); + if (new_item == NULL) { + goto fail; /* allocation failure */ + } + + /* attach next item to list */ + if (head == NULL) { + /* start the linked list */ + current_item = head = new_item; + } else { + /* add to the end and advance */ + current_item->next = new_item; + new_item->prev = current_item; + current_item = new_item; + } + + /* parse next value */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_value(current_item, input_buffer)) { + goto fail; /* failed to parse value */ + } + buffer_skip_whitespace(input_buffer); + } while (can_access_at_index(input_buffer, 0) + && (buffer_at_offset(input_buffer)[0] == ',')); + + if (cannot_access_at_index(input_buffer, 0) + || buffer_at_offset(input_buffer)[0] != ']') { + goto fail; /* expected end of array */ + } + +success: + input_buffer->depth--; + + if (head != NULL) { + head->prev = current_item; + } item->type = cJSON_Array; - value = skip(value + 1); - if (*value == ']') { - return value + 1; /* empty array. */ + item->child = head; + + input_buffer->offset++; + + return true; + +fail: + if (head != NULL) { + cJSON_Delete(head); } - item->child = child = cJSON_New_Item(); - if (!item->child) { - return nullptr; /* memory fail */ - } - value = skip( - parse_value(child, skip(value))); /* skip any spacing, get the value. */ - if (!value) return nullptr; - - while (*value == ',') { - cJSON* new_item; - if (!(new_item = cJSON_New_Item())) { - return nullptr; /* memory fail */ - } - child->next = new_item; - new_item->prev = child; - child = new_item; - value = skip(parse_value(child, skip(value + 1))); - if (!value) { - return nullptr; /* memory fail */ - } - } - - if (*value == ']') { - return value + 1; /* end of array */ - } - ep = value; - return nullptr; /* malformed. */ + return false; } /* Render an array to text */ -static auto print_array(cJSON* item, int depth, int fmt) -> char* { - char** entries; - char *out = nullptr, *ptr, *ret; - size_t len = 5; - cJSON* child = item->child; - int numentries = 0, i = 0, fail = 0; +static cJSON_bool print_array(const cJSON* const item, + printbuffer* const output_buffer) { + unsigned char* output_pointer = NULL; + size_t length = 0; + cJSON* current_element = item->child; - /* How many entries in the array? */ - while (child) { - numentries++; - child = child->next; - } - /* Explicitly handle numentries==0 */ - if (!numentries) { - out = (char*)cJSON_malloc(3); - if (out) { - strcpy(out, "[]"); // NOLINT - } - return out; - } - /* Allocate an array to hold the values for each */ - entries = (char**)cJSON_malloc(numentries * sizeof(char*)); - if (!entries) { - return nullptr; - } - memset(entries, 0, numentries * sizeof(char*)); - /* Retrieve all the results: */ - child = item->child; - while (child && !fail) { - ret = print_value(child, depth + 1, fmt); - entries[i++] = ret; - if (ret) - len += strlen(ret) + 2 + (fmt ? 1 : 0); - else - fail = 1; - child = child->next; - } - - /* If we didn't fail, try to malloc the output string */ - if (!fail) { - out = (char*)cJSON_malloc(len); - } - /* If that fails, we fail. */ - if (!out) { - fail = 1; - } - - /* Handle failure. */ - if (fail) { - for (i = 0; i < numentries; i++) - if (entries[i]) cJSON_free(entries[i]); - cJSON_free(entries); - return nullptr; + if (output_buffer == NULL) { + return false; } /* Compose the output array. */ - *out = '['; - ptr = out + 1; - *ptr = 0; - for (i = 0; i < numentries; i++) { - strcpy(ptr, entries[i]); // NOLINT - ptr += strlen(entries[i]); - if (i != numentries - 1) { - *ptr++ = ','; - if (fmt) *ptr++ = ' '; - *ptr = 0; - } - cJSON_free(entries[i]); + /* opening square bracket */ + output_pointer = ensure(output_buffer, 1); + if (output_pointer == NULL) { + return false; } - cJSON_free(entries); - *ptr++ = ']'; - *ptr = 0; - return out; + + *output_pointer = '['; + output_buffer->offset++; + output_buffer->depth++; + + while (current_element != NULL) { + if (!print_value(current_element, output_buffer)) { + return false; + } + update_offset(output_buffer); + if (current_element->next) { + length = (size_t)(output_buffer->format ? 2 : 1); + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) { + return false; + } + *output_pointer++ = ','; + if (output_buffer->format) { + *output_pointer++ = ' '; + } + *output_pointer = '\0'; + output_buffer->offset += length; + } + current_element = current_element->next; + } + + output_pointer = ensure(output_buffer, 2); + if (output_pointer == NULL) { + return false; + } + *output_pointer++ = ']'; + *output_pointer = '\0'; + output_buffer->depth--; + + return true; } /* Build an object from the text. */ -static auto parse_object(cJSON* item, const char* value) -> const char* { - cJSON* child; - if (*value != '{') { - ep = value; - return nullptr; - } /* not an object! */ +static cJSON_bool parse_object(cJSON* const item, + parse_buffer* const input_buffer) { + cJSON* head = NULL; /* linked list head */ + cJSON* current_item = NULL; - item->type = cJSON_Object; - value = skip(value + 1); - if (*value == '}') return value + 1; /* empty array. */ + if (input_buffer->depth >= CJSON_NESTING_LIMIT) { + return false; /* to deeply nested */ + } + input_buffer->depth++; - item->child = child = cJSON_New_Item(); - if (!item->child) return nullptr; - value = skip(parse_string(child, skip(value))); - if (!value) return nullptr; - child->string = child->valuestring; - child->valuestring = nullptr; - if (*value != ':') { - ep = value; - return nullptr; - } /* fail! */ - value = skip(parse_value( - child, skip(value + 1))); /* skip any spacing, get the value. */ - if (!value) return nullptr; - - while (*value == ',') { - cJSON* new_item; - if (!(new_item = cJSON_New_Item())) return nullptr; /* memory fail */ - child->next = new_item; - new_item->prev = child; - child = new_item; - value = skip(parse_string(child, skip(value + 1))); - if (!value) return nullptr; - child->string = child->valuestring; - child->valuestring = nullptr; - if (*value != ':') { - ep = value; - return nullptr; - } /* fail! */ - value = skip(parse_value( - child, skip(value + 1))); /* skip any spacing, get the value. */ - if (!value) return nullptr; + if (cannot_access_at_index(input_buffer, 0) + || (buffer_at_offset(input_buffer)[0] != '{')) { + goto fail; /* not an object */ } - if (*value == '}') return value + 1; /* end of array */ - ep = value; - return nullptr; /* malformed. */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (can_access_at_index(input_buffer, 0) + && (buffer_at_offset(input_buffer)[0] == '}')) { + goto success; /* empty object */ + } + + /* check if we skipped to the end of the buffer */ + if (cannot_access_at_index(input_buffer, 0)) { + input_buffer->offset--; + goto fail; + } + + /* step back to character in front of the first element */ + input_buffer->offset--; + /* loop through the comma separated array elements */ + do { + /* allocate next item */ + cJSON* new_item = cJSON_New_Item(&(input_buffer->hooks)); + if (new_item == NULL) { + goto fail; /* allocation failure */ + } + + /* attach next item to list */ + if (head == NULL) { + /* start the linked list */ + current_item = head = new_item; + } else { + /* add to the end and advance */ + current_item->next = new_item; + new_item->prev = current_item; + current_item = new_item; + } + + /* parse the name of the child */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_string(current_item, input_buffer)) { + goto fail; /* failed to parse name */ + } + buffer_skip_whitespace(input_buffer); + + /* swap valuestring and string, because we parsed the name */ + current_item->string = current_item->valuestring; + current_item->valuestring = NULL; + + if (cannot_access_at_index(input_buffer, 0) + || (buffer_at_offset(input_buffer)[0] != ':')) { + goto fail; /* invalid object */ + } + + /* parse the value */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_value(current_item, input_buffer)) { + goto fail; /* failed to parse value */ + } + buffer_skip_whitespace(input_buffer); + } while (can_access_at_index(input_buffer, 0) + && (buffer_at_offset(input_buffer)[0] == ',')); + + if (cannot_access_at_index(input_buffer, 0) + || (buffer_at_offset(input_buffer)[0] != '}')) { + goto fail; /* expected end of object */ + } + +success: + input_buffer->depth--; + + if (head != NULL) { + head->prev = current_item; + } + + item->type = cJSON_Object; + item->child = head; + + input_buffer->offset++; + return true; + +fail: + if (head != NULL) { + cJSON_Delete(head); + } + + return false; } /* Render an object to text. */ -static auto print_object(cJSON* item, int depth, int fmt) -> char* { - char *out = nullptr, *ptr, *ret, *str; - size_t len = 7; - int i = 0; - int j; - cJSON* child = item->child; - int numentries = 0, fail = 0; - /* Count the number of entries. */ - while (child) { - numentries++; - child = child->next; - } - // Explicitly handle empty object case. - if (!numentries) { - out = (char*)cJSON_malloc(static_cast(fmt ? depth + 4 : 3)); - if (!out) { - return nullptr; - } - ptr = out; - *ptr++ = '{'; - if (fmt) { - *ptr++ = '\n'; - for (i = 0; i < depth - 1; i++) *ptr++ = '\t'; - } - *ptr++ = '}'; - *ptr = 0; - return out; +static cJSON_bool print_object(const cJSON* const item, + printbuffer* const output_buffer) { + unsigned char* output_pointer = NULL; + size_t length = 0; + cJSON* current_item = item->child; + + if (output_buffer == NULL) { + return false; } - // Allocate space for the names and the objects. - char** entries = (char**)cJSON_malloc(numentries * sizeof(char*)); - if (!entries) return nullptr; - char** names = (char**)cJSON_malloc(numentries * sizeof(char*)); - if (!names) { - cJSON_free(entries); - return nullptr; + /* Compose the output: */ + length = (size_t)(output_buffer->format ? 2 : 1); /* fmt: {\n */ + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) { + return false; } - memset(entries, 0, sizeof(char*) * numentries); - memset(names, 0, sizeof(char*) * numentries); - // Collect all the results into our arrays. - child = item->child; - depth++; - if (fmt) len += depth; - while (child) { - names[i] = str = print_string_ptr(child->string); - entries[i++] = ret = print_value(child, depth, fmt); - if (str && ret) - len += strlen(ret) + strlen(str) + 2 + (fmt ? 2 + depth : 0); - else - fail = 1; + *output_pointer++ = '{'; + output_buffer->depth++; + if (output_buffer->format) { + *output_pointer++ = '\n'; + } + output_buffer->offset += length; + + while (current_item) { + if (output_buffer->format) { + size_t i; + output_pointer = ensure(output_buffer, output_buffer->depth); + if (output_pointer == NULL) { + return false; + } + for (i = 0; i < output_buffer->depth; i++) { + *output_pointer++ = '\t'; + } + output_buffer->offset += output_buffer->depth; + } + + /* print key */ + if (!print_string_ptr((unsigned char*)current_item->string, + output_buffer)) { + return false; + } + update_offset(output_buffer); + + length = (size_t)(output_buffer->format ? 2 : 1); + output_pointer = ensure(output_buffer, length); + if (output_pointer == NULL) { + return false; + } + *output_pointer++ = ':'; + if (output_buffer->format) { + *output_pointer++ = '\t'; + } + output_buffer->offset += length; + + /* print value */ + if (!print_value(current_item, output_buffer)) { + return false; + } + update_offset(output_buffer); + + /* print comma if not last */ + length = ((size_t)(output_buffer->format ? 1 : 0) + + (size_t)(current_item->next ? 1 : 0)); + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) { + return false; + } + if (current_item->next) { + *output_pointer++ = ','; + } + + if (output_buffer->format) { + *output_pointer++ = '\n'; + } + *output_pointer = '\0'; + output_buffer->offset += length; + + current_item = current_item->next; + } + + output_pointer = ensure( + output_buffer, output_buffer->format ? (output_buffer->depth + 1) : 2); + if (output_pointer == NULL) { + return false; + } + if (output_buffer->format) { + size_t i; + for (i = 0; i < (output_buffer->depth - 1); i++) { + *output_pointer++ = '\t'; + } + } + *output_pointer++ = '}'; + *output_pointer = '\0'; + output_buffer->depth--; + + return true; +} + +/* Get Array size/item / object item. */ +int cJSON_GetArraySize(const cJSON* array) { + cJSON* child = NULL; + size_t size = 0; + + if (array == NULL) { + return 0; + } + + child = array->child; + + while (child != NULL) { + size++; child = child->next; } - // Try to allocate the output string. - if (!fail) out = (char*)cJSON_malloc(len); - if (!out) fail = 1; + /* FIXME: Can overflow here. Cannot be fixed without breaking the API */ - // Handle failure. - if (fail) { - for (i = 0; i < numentries; i++) { - if (names[i]) cJSON_free(names[i]); - if (entries[i]) cJSON_free(entries[i]); + return (int)size; +} + +static cJSON* get_array_item(const cJSON* array, size_t index) { + cJSON* current_child = NULL; + + if (array == NULL) { + return NULL; + } + + current_child = array->child; + while ((current_child != NULL) && (index > 0)) { + index--; + current_child = current_child->next; + } + + return current_child; +} + +cJSON* cJSON_GetArrayItem(const cJSON* array, int index) { + if (index < 0) { + return NULL; + } + + return get_array_item(array, (size_t)index); +} + +static cJSON* get_object_item(const cJSON* const object, const char* const name, + const cJSON_bool case_sensitive) { + cJSON* current_element = NULL; + + if ((object == NULL) || (name == NULL)) { + return NULL; + } + + current_element = object->child; + if (case_sensitive) { + while ((current_element != NULL) && (current_element->string != NULL) + && (strcmp(name, current_element->string) != 0)) { + current_element = current_element->next; + } + } else { + while ((current_element != NULL) + && (case_insensitive_strcmp( + (const unsigned char*)name, + (const unsigned char*)(current_element->string)) + != 0)) { + current_element = current_element->next; } - cJSON_free(names); - cJSON_free(entries); - return nullptr; } - // Compose the output. - *out = '{'; - ptr = out + 1; - if (fmt) *ptr++ = '\n'; - *ptr = 0; - for (i = 0; i < numentries; i++) { - if (fmt) - for (j = 0; j < depth; j++) *ptr++ = '\t'; - strcpy(ptr, names[i]); // NOLINT - ptr += strlen(names[i]); - *ptr++ = ':'; - if (fmt) *ptr++ = '\t'; - strcpy(ptr, entries[i]); // NOLINT - ptr += strlen(entries[i]); - if (i != numentries - 1) *ptr++ = ','; - if (fmt) *ptr++ = '\n'; - *ptr = 0; - cJSON_free(names[i]); - cJSON_free(entries[i]); + if ((current_element == NULL) || (current_element->string == NULL)) { + return NULL; } - cJSON_free(names); - cJSON_free(entries); - if (fmt) - for (i = 0; i < depth - 1; i++) *ptr++ = '\t'; - *ptr++ = '}'; - *ptr = 0; - return out; + return current_element; } -// Get Array size/item / object item. -auto cJSON_GetArraySize(cJSON* array) -> int { - cJSON* c = array->child; - int i = 0; - while (c) { - i++; - c = c->next; - } - return i; -} -auto cJSON_GetArrayItem(cJSON* array, int item) -> cJSON* { - cJSON* c = array->child; - while (c && item > 0) { - item--; - c = c->next; - } - return c; -} -auto cJSON_GetObjectItem(cJSON* object, const char* string) -> cJSON* { - cJSON* c = object->child; - while (c && cJSON_strcasecmp(c->string, string)) c = c->next; - return c; +cJSON* cJSON_GetObjectItem(const cJSON* const object, + const char* const string) { + return get_object_item(object, string, false); } -// Utility for array list handling. +cJSON* cJSON_GetObjectItemCaseSensitive(const cJSON* const object, + const char* const string) { + return get_object_item(object, string, true); +} + +cJSON_bool cJSON_HasObjectItem(const cJSON* object, const char* string) { + return cJSON_GetObjectItem(object, string) ? 1 : 0; +} + +/* Utility for array list handling. */ static void suffix_object(cJSON* prev, cJSON* item) { prev->next = item; item->prev = prev; } -// Utility for handling references. -static auto create_reference(cJSON* item) -> cJSON* { - cJSON* ref = cJSON_New_Item(); - if (!ref) return nullptr; - memcpy(ref, item, sizeof(cJSON)); - ref->string = nullptr; - ref->type |= cJSON_IsReference; - ref->next = ref->prev = nullptr; - return ref; + +/* Utility for handling references. */ +static cJSON* create_reference(const cJSON* item, + const internal_hooks* const hooks) { + cJSON* reference = NULL; + if (item == NULL) { + return NULL; + } + + reference = cJSON_New_Item(hooks); + if (reference == NULL) { + return NULL; + } + + memcpy(reference, item, sizeof(cJSON)); + reference->string = NULL; + reference->type |= cJSON_IsReference; + reference->next = reference->prev = NULL; + return reference; } -// Add item to array/object. -void cJSON_AddItemToArray(cJSON* array, cJSON* item) { - cJSON* c = array->child; - if (!item) return; - if (!c) { +static cJSON_bool add_item_to_array(cJSON* array, cJSON* item) { + cJSON* child = NULL; + + if ((item == NULL) || (array == NULL) || (array == item)) { + return false; + } + + child = array->child; + /* + * To find the last item in array quickly, we use prev in array + */ + if (child == NULL) { + /* list is empty, start new one */ array->child = item; + item->prev = item; + item->next = NULL; } else { - while (c && c->next) c = c->next; - suffix_object(c, item); + /* append to the end */ + if (child->prev) { + suffix_object(child->prev, item); + array->child->prev = item; + } } -} -void cJSON_AddItemToObject(cJSON* object, const char* string, cJSON* item) { - if (!item) return; - if (item->string) cJSON_free(item->string); - item->string = cJSON_strdup(string); - cJSON_AddItemToArray(object, item); -} -void cJSON_AddItemReferenceToArray(cJSON* array, cJSON* item) { - cJSON_AddItemToArray(array, create_reference(item)); -} -void cJSON_AddItemReferenceToObject(cJSON* object, const char* string, - cJSON* item) { - cJSON_AddItemToObject(object, string, create_reference(item)); + + return true; } -auto cJSON_DetachItemFromArray(cJSON* array, int which) -> cJSON* { - cJSON* c = array->child; - while (c && which > 0) { - c = c->next; - which--; - } - if (!c) return nullptr; - if (c->prev) c->prev->next = c->next; - if (c->next) c->next->prev = c->prev; - if (c == array->child) array->child = c->next; - c->prev = c->next = nullptr; - return c; +/* Add item to array/object. */ +cJSON_bool cJSON_AddItemToArray(cJSON* array, cJSON* item) { + return add_item_to_array(array, item); } + +#if defined(__clang__) \ + || (defined(__GNUC__) \ + && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) +#pragma GCC diagnostic push +#endif +#ifdef __GNUC__ +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif +/* helper function to cast away const */ +static void* cast_away_const(const void* string) { return (void*)string; } +#if defined(__clang__) \ + || (defined(__GNUC__) \ + && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) +#pragma GCC diagnostic pop +#endif + +static cJSON_bool add_item_to_object(cJSON* const object, + const char* const string, + cJSON* const item, + const internal_hooks* const hooks, + const cJSON_bool constant_key) { + char* new_key = NULL; + int new_type = cJSON_Invalid; + + if ((object == NULL) || (string == NULL) || (item == NULL) + || (object == item)) { + return false; + } + + if (constant_key) { + new_key = (char*)cast_away_const(string); + new_type = item->type | cJSON_StringIsConst; + } else { + new_key = (char*)cJSON_strdup((const unsigned char*)string, hooks); + if (new_key == NULL) { + return false; + } + + new_type = item->type & ~cJSON_StringIsConst; + } + + if (!(item->type & cJSON_StringIsConst) && (item->string != NULL)) { + hooks->deallocate(item->string); + } + + item->string = new_key; + item->type = new_type; + + return add_item_to_array(object, item); +} + +cJSON_bool cJSON_AddItemToObject(cJSON* object, const char* string, + cJSON* item) { + return add_item_to_object(object, string, item, &global_hooks, false); +} + +/* Add an item to an object with constant string as key */ +cJSON_bool cJSON_AddItemToObjectCS(cJSON* object, const char* string, + cJSON* item) { + return add_item_to_object(object, string, item, &global_hooks, true); +} + +cJSON_bool cJSON_AddItemReferenceToArray(cJSON* array, cJSON* item) { + if (array == NULL) { + return false; + } + + return add_item_to_array(array, create_reference(item, &global_hooks)); +} + +cJSON_bool cJSON_AddItemReferenceToObject(cJSON* object, const char* string, + cJSON* item) { + if ((object == NULL) || (string == NULL)) { + return false; + } + + return add_item_to_object(object, string, + create_reference(item, &global_hooks), + &global_hooks, false); +} + +cJSON* cJSON_AddNullToObject(cJSON* const object, const char* const name) { + cJSON* null = cJSON_CreateNull(); + if (add_item_to_object(object, name, null, &global_hooks, false)) { + return null; + } + + cJSON_Delete(null); + return NULL; +} + +cJSON* cJSON_AddTrueToObject(cJSON* const object, const char* const name) { + cJSON* true_item = cJSON_CreateTrue(); + if (add_item_to_object(object, name, true_item, &global_hooks, false)) { + return true_item; + } + + cJSON_Delete(true_item); + return NULL; +} + +cJSON* cJSON_AddFalseToObject(cJSON* const object, const char* const name) { + cJSON* false_item = cJSON_CreateFalse(); + if (add_item_to_object(object, name, false_item, &global_hooks, false)) { + return false_item; + } + + cJSON_Delete(false_item); + return NULL; +} + +cJSON* cJSON_AddBoolToObject(cJSON* const object, const char* const name, + const cJSON_bool boolean) { + cJSON* bool_item = cJSON_CreateBool(boolean); + if (add_item_to_object(object, name, bool_item, &global_hooks, false)) { + return bool_item; + } + + cJSON_Delete(bool_item); + return NULL; +} + +cJSON* cJSON_AddNumberToObject(cJSON* const object, const char* const name, + const double number) { + cJSON* number_item = cJSON_CreateNumber(number); + if (add_item_to_object(object, name, number_item, &global_hooks, false)) { + return number_item; + } + + cJSON_Delete(number_item); + return NULL; +} + +cJSON* cJSON_AddStringToObject(cJSON* const object, const char* const name, + const char* const string) { + cJSON* string_item = cJSON_CreateString(string); + if (add_item_to_object(object, name, string_item, &global_hooks, false)) { + return string_item; + } + + cJSON_Delete(string_item); + return NULL; +} + +cJSON* cJSON_AddRawToObject(cJSON* const object, const char* const name, + const char* const raw) { + cJSON* raw_item = cJSON_CreateRaw(raw); + if (add_item_to_object(object, name, raw_item, &global_hooks, false)) { + return raw_item; + } + + cJSON_Delete(raw_item); + return NULL; +} + +cJSON* cJSON_AddObjectToObject(cJSON* const object, const char* const name) { + cJSON* object_item = cJSON_CreateObject(); + if (add_item_to_object(object, name, object_item, &global_hooks, false)) { + return object_item; + } + + cJSON_Delete(object_item); + return NULL; +} + +cJSON* cJSON_AddArrayToObject(cJSON* const object, const char* const name) { + cJSON* array = cJSON_CreateArray(); + if (add_item_to_object(object, name, array, &global_hooks, false)) { + return array; + } + + cJSON_Delete(array); + return NULL; +} + +cJSON* cJSON_DetachItemViaPointer(cJSON* parent, cJSON* const item) { + if ((parent == NULL) || (item == NULL)) { + return NULL; + } + + if (item != parent->child) { + /* not the first element */ + item->prev->next = item->next; + } + if (item->next != NULL) { + /* not the last element */ + item->next->prev = item->prev; + } + + if (item == parent->child) { + /* first element */ + parent->child = item->next; + } else if (item->next == NULL) { + /* last element */ + parent->child->prev = item->prev; + } + + /* make sure the detached item doesn't point anywhere anymore */ + item->prev = NULL; + item->next = NULL; + + return item; +} + +cJSON* cJSON_DetachItemFromArray(cJSON* array, int which) { + if (which < 0) { + return NULL; + } + + return cJSON_DetachItemViaPointer(array, + get_array_item(array, (size_t)which)); +} + void cJSON_DeleteItemFromArray(cJSON* array, int which) { cJSON_Delete(cJSON_DetachItemFromArray(array, which)); } -#pragma clang diagnostic push -#pragma ide diagnostic ignored "ConstantFunctionResult" -auto cJSON_DetachItemFromObject(cJSON* object, const char* string) -> cJSON* { - int i = 0; - cJSON* c = object->child; - while (c && cJSON_strcasecmp(c->string, string)) { - i++; - c = c->next; - } - if (c) return cJSON_DetachItemFromArray(object, i); - return nullptr; +cJSON* cJSON_DetachItemFromObject(cJSON* object, const char* string) { + cJSON* to_detach = cJSON_GetObjectItem(object, string); + + return cJSON_DetachItemViaPointer(object, to_detach); } -#pragma clang diagnostic pop +cJSON* cJSON_DetachItemFromObjectCaseSensitive(cJSON* object, + const char* string) { + cJSON* to_detach = cJSON_GetObjectItemCaseSensitive(object, string); + + return cJSON_DetachItemViaPointer(object, to_detach); +} void cJSON_DeleteItemFromObject(cJSON* object, const char* string) { cJSON_Delete(cJSON_DetachItemFromObject(object, string)); } -// Replace array/object items with new ones. -void cJSON_ReplaceItemInArray(cJSON* array, int which, cJSON* newitem) { - cJSON* c = array->child; - while (c && which > 0) { - c = c->next; - which--; - } - if (!c) return; - newitem->next = c->next; - newitem->prev = c->prev; - if (newitem->next) newitem->next->prev = newitem; - if (c == array->child) - array->child = newitem; - else - newitem->prev->next = newitem; - c->next = c->prev = nullptr; - cJSON_Delete(c); -} -void cJSON_ReplaceItemInObject(cJSON* object, const char* string, - cJSON* newitem) { - int i = 0; - cJSON* c = object->child; - while (c && cJSON_strcasecmp(c->string, string)) { - i++; - c = c->next; - } - if (c) { - newitem->string = cJSON_strdup(string); - cJSON_ReplaceItemInArray(object, i, newitem); - } +void cJSON_DeleteItemFromObjectCaseSensitive(cJSON* object, + const char* string) { + cJSON_Delete(cJSON_DetachItemFromObjectCaseSensitive(object, string)); } -// Create basic types. -auto cJSON_CreateNull() -> cJSON* { - cJSON* item = cJSON_New_Item(); - if (item) item->type = cJSON_NULL; +/* Replace array/object items with new ones. */ +cJSON_bool cJSON_InsertItemInArray(cJSON* array, int which, cJSON* newitem) { + cJSON* after_inserted = NULL; + + if (which < 0) { + return false; + } + + after_inserted = get_array_item(array, (size_t)which); + if (after_inserted == NULL) { + return add_item_to_array(array, newitem); + } + + newitem->next = after_inserted; + newitem->prev = after_inserted->prev; + after_inserted->prev = newitem; + if (after_inserted == array->child) { + array->child = newitem; + } else { + newitem->prev->next = newitem; + } + return true; +} + +cJSON_bool cJSON_ReplaceItemViaPointer(cJSON* const parent, cJSON* const item, + cJSON* replacement) { + if ((parent == NULL) || (parent->child == NULL) || (replacement == NULL) + || (item == NULL)) { + return false; + } + + if (replacement == item) { + return true; + } + + replacement->next = item->next; + replacement->prev = item->prev; + + if (replacement->next != NULL) { + replacement->next->prev = replacement; + } + if (parent->child == item) { + if (parent->child->prev == parent->child) { + replacement->prev = replacement; + } + parent->child = replacement; + } else { /* + * To find the last item in array quickly, we use prev in array. + * We can't modify the last item's next pointer where this item was + * the parent's child + */ + if (replacement->prev != NULL) { + replacement->prev->next = replacement; + } + if (replacement->next == NULL) { + parent->child->prev = replacement; + } + } + + item->next = NULL; + item->prev = NULL; + cJSON_Delete(item); + + return true; +} + +cJSON_bool cJSON_ReplaceItemInArray(cJSON* array, int which, cJSON* newitem) { + if (which < 0) { + return false; + } + + return cJSON_ReplaceItemViaPointer( + array, get_array_item(array, (size_t)which), newitem); +} + +static cJSON_bool replace_item_in_object(cJSON* object, const char* string, + cJSON* replacement, + cJSON_bool case_sensitive) { + if ((replacement == NULL) || (string == NULL)) { + return false; + } + + /* replace the name in the replacement */ + if (!(replacement->type & cJSON_StringIsConst) + && (replacement->string != NULL)) { + cJSON_free(replacement->string); + } + replacement->string = + (char*)cJSON_strdup((const unsigned char*)string, &global_hooks); + if (replacement->string == NULL) { + return false; + } + + replacement->type &= ~cJSON_StringIsConst; + + return cJSON_ReplaceItemViaPointer( + object, get_object_item(object, string, case_sensitive), replacement); +} + +cJSON_bool cJSON_ReplaceItemInObject(cJSON* object, const char* string, + cJSON* newitem) { + return replace_item_in_object(object, string, newitem, false); +} + +cJSON_bool cJSON_ReplaceItemInObjectCaseSensitive(cJSON* object, + const char* string, + cJSON* newitem) { + return replace_item_in_object(object, string, newitem, true); +} + +/* Create basic types: */ +cJSON* cJSON_CreateNull(void) { + cJSON* item = cJSON_New_Item(&global_hooks); + if (item) { + item->type = cJSON_NULL; + } + return item; } -auto cJSON_CreateTrue() -> cJSON* { - cJSON* item = cJSON_New_Item(); - if (item) item->type = cJSON_True; + +cJSON* cJSON_CreateTrue(void) { + cJSON* item = cJSON_New_Item(&global_hooks); + if (item) { + item->type = cJSON_True; + } + return item; } -auto cJSON_CreateFalse() -> cJSON* { - cJSON* item = cJSON_New_Item(); - if (item) item->type = cJSON_False; + +cJSON* cJSON_CreateFalse(void) { + cJSON* item = cJSON_New_Item(&global_hooks); + if (item) { + item->type = cJSON_False; + } + return item; } -auto cJSON_CreateBool(int b) -> cJSON* { - cJSON* item = cJSON_New_Item(); - if (item) item->type = b ? cJSON_True : cJSON_False; + +cJSON* cJSON_CreateBool(cJSON_bool boolean) { + cJSON* item = cJSON_New_Item(&global_hooks); + if (item) { + item->type = boolean ? cJSON_True : cJSON_False; + } + return item; } -auto cJSON_CreateNumber(double num) -> cJSON* { - cJSON* item = cJSON_New_Item(); + +cJSON* cJSON_CreateNumber(double num) { + cJSON* item = cJSON_New_Item(&global_hooks); if (item) { item->type = cJSON_Number; item->valuedouble = num; - item->valueint = (int)num; + + /* use saturation in case of overflow */ + if (num >= INT_MAX) { + item->valueint = INT_MAX; + } else if (num <= (double)INT_MIN) { + item->valueint = INT_MIN; + } else { + item->valueint = (int)num; + } } + return item; } -auto cJSON_CreateString(const char* string) -> cJSON* { - cJSON* item = cJSON_New_Item(); + +cJSON* cJSON_CreateString(const char* string) { + cJSON* item = cJSON_New_Item(&global_hooks); if (item) { item->type = cJSON_String; - item->valuestring = cJSON_strdup(string); + item->valuestring = + (char*)cJSON_strdup((const unsigned char*)string, &global_hooks); + if (!item->valuestring) { + cJSON_Delete(item); + return NULL; + } } - return item; -} -auto cJSON_CreateArray() -> cJSON* { - cJSON* item = cJSON_New_Item(); - if (item) item->type = cJSON_Array; - return item; -} -auto cJSON_CreateObject() -> cJSON* { - cJSON* item = cJSON_New_Item(); - if (item) item->type = cJSON_Object; + return item; } -// Create Arrays. -auto cJSON_CreateIntArray(const int* numbers, int count) -> cJSON* { - int i; - cJSON *n, *p = nullptr, *a = cJSON_CreateArray(); - for (i = 0; a && i < count; i++) { +cJSON* cJSON_CreateStringReference(const char* string) { + cJSON* item = cJSON_New_Item(&global_hooks); + if (item != NULL) { + item->type = cJSON_String | cJSON_IsReference; + item->valuestring = (char*)cast_away_const(string); + } + + return item; +} + +cJSON* cJSON_CreateObjectReference(const cJSON* child) { + cJSON* item = cJSON_New_Item(&global_hooks); + if (item != NULL) { + item->type = cJSON_Object | cJSON_IsReference; + item->child = (cJSON*)cast_away_const(child); + } + + return item; +} + +cJSON* cJSON_CreateArrayReference(const cJSON* child) { + cJSON* item = cJSON_New_Item(&global_hooks); + if (item != NULL) { + item->type = cJSON_Array | cJSON_IsReference; + item->child = (cJSON*)cast_away_const(child); + } + + return item; +} + +cJSON* cJSON_CreateRaw(const char* raw) { + cJSON* item = cJSON_New_Item(&global_hooks); + if (item) { + item->type = cJSON_Raw; + item->valuestring = + (char*)cJSON_strdup((const unsigned char*)raw, &global_hooks); + if (!item->valuestring) { + cJSON_Delete(item); + return NULL; + } + } + + return item; +} + +cJSON* cJSON_CreateArray(void) { + cJSON* item = cJSON_New_Item(&global_hooks); + if (item) { + item->type = cJSON_Array; + } + + return item; +} + +cJSON* cJSON_CreateObject(void) { + cJSON* item = cJSON_New_Item(&global_hooks); + if (item) { + item->type = cJSON_Object; + } + + return item; +} + +/* Create Arrays: */ +cJSON* cJSON_CreateIntArray(const int* numbers, int count) { + size_t i = 0; + cJSON* n = NULL; + cJSON* p = NULL; + cJSON* a = NULL; + + if ((count < 0) || (numbers == NULL)) { + return NULL; + } + + a = cJSON_CreateArray(); + + for (i = 0; a && (i < (size_t)count); i++) { n = cJSON_CreateNumber(numbers[i]); - if (!i) + if (!n) { + cJSON_Delete(a); + return NULL; + } + if (!i) { a->child = n; - else + } else { suffix_object(p, n); + } p = n; } + + if (a && a->child) { + a->child->prev = n; + } + return a; } -auto cJSON_CreateFloatArray(const float* numbers, int count) -> cJSON* { - int i; - cJSON *n, *p = nullptr, *a = cJSON_CreateArray(); - for (i = 0; a && i < count; i++) { - n = cJSON_CreateNumber(numbers[i]); - if (!i) + +cJSON* cJSON_CreateFloatArray(const float* numbers, int count) { + size_t i = 0; + cJSON* n = NULL; + cJSON* p = NULL; + cJSON* a = NULL; + + if ((count < 0) || (numbers == NULL)) { + return NULL; + } + + a = cJSON_CreateArray(); + + for (i = 0; a && (i < (size_t)count); i++) { + n = cJSON_CreateNumber((double)numbers[i]); + if (!n) { + cJSON_Delete(a); + return NULL; + } + if (!i) { a->child = n; - else + } else { suffix_object(p, n); + } p = n; } + + if (a && a->child) { + a->child->prev = n; + } + return a; } -auto cJSON_CreateDoubleArray(const double* numbers, int count) -> cJSON* { - int i; - cJSON *n, *p = nullptr, *a = cJSON_CreateArray(); - for (i = 0; a && i < count; i++) { + +cJSON* cJSON_CreateDoubleArray(const double* numbers, int count) { + size_t i = 0; + cJSON* n = NULL; + cJSON* p = NULL; + cJSON* a = NULL; + + if ((count < 0) || (numbers == NULL)) { + return NULL; + } + + a = cJSON_CreateArray(); + + for (i = 0; a && (i < (size_t)count); i++) { n = cJSON_CreateNumber(numbers[i]); - if (!i) + if (!n) { + cJSON_Delete(a); + return NULL; + } + if (!i) { a->child = n; - else + } else { suffix_object(p, n); + } p = n; } + + if (a && a->child) { + a->child->prev = n; + } + return a; } -auto cJSON_CreateStringArray(const char** strings, int count) -> cJSON* { - int i; - cJSON *n, *p = nullptr, *a = cJSON_CreateArray(); - for (i = 0; a && i < count; i++) { + +cJSON* cJSON_CreateStringArray(const char* const* strings, int count) { + size_t i = 0; + cJSON* n = NULL; + cJSON* p = NULL; + cJSON* a = NULL; + + if ((count < 0) || (strings == NULL)) { + return NULL; + } + + a = cJSON_CreateArray(); + + for (i = 0; a && (i < (size_t)count); i++) { n = cJSON_CreateString(strings[i]); - if (!i) + if (!n) { + cJSON_Delete(a); + return NULL; + } + if (!i) { a->child = n; - else + } else { suffix_object(p, n); + } p = n; } + + if (a && a->child) { + a->child->prev = n; + } + return a; } -// Duplication. -auto cJSON_Duplicate(cJSON* item, int recurse) -> cJSON* { - cJSON *newitem, *cptr, *nptr = nullptr, *newchild; +/* Duplication */ +cJSON* cJSON_Duplicate(const cJSON* item, cJSON_bool recurse) { + cJSON* newitem = NULL; + cJSON* child = NULL; + cJSON* next = NULL; + cJSON* newchild = NULL; + /* Bail on bad ptr */ - if (!item) return nullptr; + if (!item) { + goto fail; + } /* Create new item */ - newitem = cJSON_New_Item(); - if (!newitem) return nullptr; + newitem = cJSON_New_Item(&global_hooks); + if (!newitem) { + goto fail; + } /* Copy over all vars */ newitem->type = item->type & (~cJSON_IsReference); newitem->valueint = item->valueint; newitem->valuedouble = item->valuedouble; if (item->valuestring) { - newitem->valuestring = cJSON_strdup(item->valuestring); + newitem->valuestring = + (char*)cJSON_strdup((unsigned char*)item->valuestring, &global_hooks); if (!newitem->valuestring) { - cJSON_Delete(newitem); - return nullptr; + goto fail; } } if (item->string) { - newitem->string = cJSON_strdup(item->string); + newitem->string = + (item->type & cJSON_StringIsConst) + ? item->string + : (char*)cJSON_strdup((unsigned char*)item->string, &global_hooks); if (!newitem->string) { - cJSON_Delete(newitem); - return nullptr; + goto fail; } } /* If non-recursive, then we're done! */ - if (!recurse) return newitem; - /* Walk the ->next chain for the child. */ - cptr = item->child; - while (cptr) { - newchild = cJSON_Duplicate( - cptr, 1); /* Duplicate (with recurse) each item in the ->next chain */ - if (!newchild) { - cJSON_Delete(newitem); - return nullptr; - } - if (nptr) { - nptr->next = newchild; - newchild->prev = nptr; - nptr = newchild; - } /* If newitem->child already set, then crosswire ->prev and ->next and - move on */ - else { - newitem->child = newchild; - nptr = newchild; - } /* Set newitem->child and move to it */ - cptr = cptr->next; + if (!recurse) { + return newitem; } + /* Walk the ->next chain for the child. */ + child = item->child; + while (child != NULL) { + newchild = cJSON_Duplicate( + child, + true); /* Duplicate (with recurse) each item in the ->next chain */ + if (!newchild) { + goto fail; + } + if (next != NULL) { + /* If newitem->child already set, then crosswire ->prev and ->next and + * move on */ + next->next = newchild; + newchild->prev = next; + next = newchild; + } else { + /* Set newitem->child and move to it */ + newitem->child = newchild; + next = newchild; + } + child = child->next; + } + if (newitem && newitem->child) { + newitem->child->prev = newchild; + } + return newitem; + +fail: + if (newitem != NULL) { + cJSON_Delete(newitem); + } + + return NULL; +} + +static void skip_oneline_comment(char** input) { + *input += static_strlen("//"); + + for (; (*input)[0] != '\0'; ++(*input)) { + if ((*input)[0] == '\n') { + *input += static_strlen("\n"); + return; + } + } +} + +static void skip_multiline_comment(char** input) { + *input += static_strlen("/*"); + + for (; (*input)[0] != '\0'; ++(*input)) { + if (((*input)[0] == '*') && ((*input)[1] == '/')) { + *input += static_strlen("*/"); + return; + } + } +} + +static void minify_string(char** input, char** output) { + (*output)[0] = (*input)[0]; + *input += static_strlen("\""); + *output += static_strlen("\""); + + for (; (*input)[0] != '\0'; (void)++(*input), ++(*output)) { + (*output)[0] = (*input)[0]; + + if ((*input)[0] == '\"') { + (*output)[0] = '\"'; + *input += static_strlen("\""); + *output += static_strlen("\""); + return; + } else if (((*input)[0] == '\\') && ((*input)[1] == '\"')) { + (*output)[1] = (*input)[1]; + *input += static_strlen("\""); + *output += static_strlen("\""); + } + } } void cJSON_Minify(char* json) { char* into = json; - while (*json) { - if (*json == ' ') - json++; // NOLINT(bugprone-branch-clone) - else if (*json == '\t') - json++; // Whitespace characters. - else if (*json == '\r') - json++; - else if (*json == '\n') - json++; - else if (*json == '/' && json[1] == '/') - while (*json && *json != '\n') - json++; // double-slash comments, to end of line. - else if (*json == '/' && json[1] == '*') { - while (*json && !(*json == '*' && json[1] == '/')) json++; - json += 2; - } // multiline comments. - else if (*json == '\"') { - *into++ = *json++; - while (*json && *json != '\"') { - if (*json == '\\') *into++ = *json++; - *into++ = *json++; - } - *into++ = *json++; - } // string literals, which are \" sensitive. - else { - *into++ = *json++; // All other characters. + + if (json == NULL) { + return; + } + + while (json[0] != '\0') { + switch (json[0]) { + case ' ': + case '\t': + case '\r': + case '\n': + json++; + break; + + case '/': + if (json[1] == '/') { + skip_oneline_comment(&json); + } else if (json[1] == '*') { + skip_multiline_comment(&json); + } else { + json++; + } + break; + + case '\"': + minify_string(&json, (char**)&into); + break; + + default: + into[0] = json[0]; + json++; + into++; } } - *into = 0; // and null-terminate. + + /* and null-terminate. */ + *into = '\0'; } -#pragma clang diagnostic pop +cJSON_bool cJSON_IsInvalid(const cJSON* const item) { + if (item == NULL) { + return false; + } + + return (item->type & 0xFF) == cJSON_Invalid; +} + +cJSON_bool cJSON_IsFalse(const cJSON* const item) { + if (item == NULL) { + return false; + } + + return (item->type & 0xFF) == cJSON_False; +} + +cJSON_bool cJSON_IsTrue(const cJSON* const item) { + if (item == NULL) { + return false; + } + + return (item->type & 0xff) == cJSON_True; +} + +cJSON_bool cJSON_IsBool(const cJSON* const item) { + if (item == NULL) { + return false; + } + + return (item->type & (cJSON_True | cJSON_False)) != 0; +} +cJSON_bool cJSON_IsNull(const cJSON* const item) { + if (item == NULL) { + return false; + } + + return (item->type & 0xFF) == cJSON_NULL; +} + +cJSON_bool cJSON_IsNumber(const cJSON* const item) { + if (item == NULL) { + return false; + } + + return (item->type & 0xFF) == cJSON_Number; +} + +cJSON_bool cJSON_IsString(const cJSON* const item) { + if (item == NULL) { + return false; + } + + return (item->type & 0xFF) == cJSON_String; +} + +cJSON_bool cJSON_IsArray(const cJSON* const item) { + if (item == NULL) { + return false; + } + + return (item->type & 0xFF) == cJSON_Array; +} + +cJSON_bool cJSON_IsObject(const cJSON* const item) { + if (item == NULL) { + return false; + } + + return (item->type & 0xFF) == cJSON_Object; +} + +cJSON_bool cJSON_IsRaw(const cJSON* const item) { + if (item == NULL) { + return false; + } + + return (item->type & 0xFF) == cJSON_Raw; +} + +cJSON_bool cJSON_Compare(const cJSON* const a, const cJSON* const b, + const cJSON_bool case_sensitive) { + if ((a == NULL) || (b == NULL) || ((a->type & 0xFF) != (b->type & 0xFF))) { + return false; + } + + /* check if type is valid */ + switch (a->type & 0xFF) { + case cJSON_False: + case cJSON_True: + case cJSON_NULL: + case cJSON_Number: + case cJSON_String: + case cJSON_Raw: + case cJSON_Array: + case cJSON_Object: + break; + + default: + return false; + } + + /* identical objects are equal */ + if (a == b) { + return true; + } + + switch (a->type & 0xFF) { + /* in these cases and equal type is enough */ + case cJSON_False: + case cJSON_True: + case cJSON_NULL: + return true; + + case cJSON_Number: + if (compare_double(a->valuedouble, b->valuedouble)) { + return true; + } + return false; + + case cJSON_String: + case cJSON_Raw: + if ((a->valuestring == NULL) || (b->valuestring == NULL)) { + return false; + } + if (strcmp(a->valuestring, b->valuestring) == 0) { + return true; + } + + return false; + + case cJSON_Array: { + cJSON* a_element = a->child; + cJSON* b_element = b->child; + + for (; (a_element != NULL) && (b_element != NULL);) { + if (!cJSON_Compare(a_element, b_element, case_sensitive)) { + return false; + } + + a_element = a_element->next; + b_element = b_element->next; + } + + /* one of the arrays is longer than the other */ + if (a_element != b_element) { + return false; + } + + return true; + } + + case cJSON_Object: { + cJSON* a_element = NULL; + cJSON* b_element = NULL; + cJSON_ArrayForEach(a_element, a) { + /* TODO This has O(n^2) runtime, which is horrible! */ + b_element = get_object_item(b, a_element->string, case_sensitive); + if (b_element == NULL) { + return false; + } + + if (!cJSON_Compare(a_element, b_element, case_sensitive)) { + return false; + } + } + + /* doing this twice, once on a and b to prevent true comparison if a + * subset of b + * TODO: Do this the proper way, this is just a fix for now */ + cJSON_ArrayForEach(b_element, b) { + a_element = get_object_item(a, b_element->string, case_sensitive); + if (a_element == NULL) { + return false; + } + + if (!cJSON_Compare(b_element, a_element, case_sensitive)) { + return false; + } + } + + return true; + } + + default: + return false; + } +} + +void* cJSON_malloc(size_t size) { return global_hooks.allocate(size); } + +void cJSON_free(void* object) { global_hooks.deallocate(object); } } // namespace ballistica diff --git a/src/ballistica/shared/generic/json.h b/src/ballistica/shared/generic/json.h index cde7ff73..63a68432 100644 --- a/src/ballistica/shared/generic/json.h +++ b/src/ballistica/shared/generic/json.h @@ -4,7 +4,7 @@ #define BALLISTICA_SHARED_GENERIC_JSON_H_ /* - Copyright (c) 2009 Dave Gamble + Copyright (c) 2009-2017 Dave Gamble and cJSON contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -27,152 +27,282 @@ #include "ballistica/shared/ballistica.h" +// ericf note: Changes here from vanilla cJSON: +// - Placed under ballistica namespace. +// - Removed extern "C" braces. +// - Removed CJSON_CDECL, CJSON_PUBLIC, and other visibility controls. +// - In general, just treating this like our other internal C++ source. +// - Also added a few simple C++ wrappers (see bottom of this file) + namespace ballistica { -#pragma clang diagnostic push -#pragma ide diagnostic ignored "OCUnusedMacroInspection" - -// #ifdef __cplusplus -// extern "C" { -// #endif +/* project version */ +#define CJSON_VERSION_MAJOR 1 +#define CJSON_VERSION_MINOR 7 +#define CJSON_VERSION_PATCH 16 /* cJSON Types: */ -#define cJSON_False 0u -#define cJSON_True 1u -#define cJSON_NULL 2u -#define cJSON_Number 3u -#define cJSON_String 4u -#define cJSON_Array 5u -#define cJSON_Object 6u +#define cJSON_Invalid (0) +#define cJSON_False (1 << 0) +#define cJSON_True (1 << 1) +#define cJSON_NULL (1 << 2) +#define cJSON_Number (1 << 3) +#define cJSON_String (1 << 4) +#define cJSON_Array (1 << 5) +#define cJSON_Object (1 << 6) +#define cJSON_Raw (1 << 7) /* raw json */ -#define cJSON_IsReference 256u +#define cJSON_IsReference 256 +#define cJSON_StringIsConst 512 /* The cJSON structure: */ typedef struct cJSON { - struct cJSON *next, - *prev; /* next/prev allow you to walk array/object chains. Alternatively, - use GetArraySize/GetArrayItem/GetObjectItem */ - struct cJSON* - child; /* An array or object item will have a child pointer pointing to a - chain of the items in the array/object. */ + /* next/prev allow you to walk array/object chains. Alternatively, use + * GetArraySize/GetArrayItem/GetObjectItem */ + struct cJSON* next; + struct cJSON* prev; + /* An array or object item will have a child pointer pointing to a chain of + * the items in the array/object. */ + struct cJSON* child; - uint32_t type; /* The type of the item, as above. */ + /* The type of the item, as above. */ + int type; - char* valuestring; /* The item's string, if type==cJSON_String */ - int valueint; /* The item's number, if type==cJSON_Number */ - double valuedouble; /* The item's number, if type==cJSON_Number */ + /* The item's string, if type==cJSON_String and type == cJSON_Raw */ + char* valuestring; + /* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */ + int valueint; + /* The item's number, if type==cJSON_Number */ + double valuedouble; - char* string; /* The item's name string, if this item is the child of, or is - in the list of subitems of an object. */ + /* The item's name string, if this item is the child of, or is in the list of + * subitems of an object. */ + char* string; } cJSON; typedef struct cJSON_Hooks { + /* malloc/free are CDECL on Windows regardless of the default calling + * convention of the compiler, so ensure the hooks allow passing those + * functions directly. */ void* (*malloc_fn)(size_t sz); void (*free_fn)(void* ptr); } cJSON_Hooks; -/* Supply malloc, realloc and free functions to cJSON */ -extern void cJSON_InitHooks(cJSON_Hooks* hooks); +typedef int cJSON_bool; +/* Limits how deeply nested arrays/objects can be before cJSON rejects to parse + * them. This is to prevent stack overflows. */ +#ifndef CJSON_NESTING_LIMIT +#define CJSON_NESTING_LIMIT 1000 +#endif + +/* returns the version of cJSON as a string */ +const char* cJSON_Version(void); + +/* Supply malloc, realloc and free functions to cJSON */ +void cJSON_InitHooks(cJSON_Hooks* hooks); + +/* Memory Management: the caller is always responsible to free the results from + * all variants of cJSON_Parse (with cJSON_Delete) and cJSON_Print (with stdlib + * free, cJSON_Hooks.free_fn, or cJSON_free as appropriate). The exception is + * cJSON_PrintPreallocated, where the caller has full responsibility of the + * buffer. */ /* Supply a block of JSON, and this returns a cJSON object you can interrogate. - * Call cJSON_Delete when finished. */ -extern auto cJSON_Parse(const char* value) -> cJSON*; -/* Render a cJSON entity to text for transfer/storage. Free the char* when - * finished. */ -extern auto cJSON_Print(cJSON* item) -> char*; -/* Render a cJSON entity to text for transfer/storage without any formatting. - * Free the char* when finished. */ -extern auto cJSON_PrintUnformatted(cJSON* item) -> char*; + */ +cJSON* cJSON_Parse(const char* value); +cJSON* cJSON_ParseWithLength(const char* value, size_t buffer_length); +/* ParseWithOpts allows you to require (and check) that the JSON is null + * terminated, and to retrieve the pointer to the final byte parsed. */ +/* If you supply a ptr in return_parse_end and parsing fails, then + * return_parse_end will contain a pointer to the error so will match + * cJSON_GetErrorPtr(). */ +cJSON* cJSON_ParseWithOpts(const char* value, const char** return_parse_end, + cJSON_bool require_null_terminated); +cJSON* cJSON_ParseWithLengthOpts(const char* value, size_t buffer_length, + const char** return_parse_end, + cJSON_bool require_null_terminated); + +/* Render a cJSON entity to text for transfer/storage. */ +char* cJSON_Print(const cJSON* item); +/* Render a cJSON entity to text for transfer/storage without any formatting. */ +char* cJSON_PrintUnformatted(const cJSON* item); +/* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess + * at the final size. guessing well reduces reallocation. fmt=0 gives + * unformatted, =1 gives formatted */ +char* cJSON_PrintBuffered(const cJSON* item, int prebuffer, cJSON_bool fmt); +/* Render a cJSON entity to text using a buffer already allocated in memory with + * given length. Returns 1 on success and 0 on failure. */ +/* NOTE: cJSON is not always 100% accurate in estimating how much memory it will + * use, so to be safe allocate 5 bytes more than you actually need */ +cJSON_bool cJSON_PrintPreallocated(cJSON* item, char* buffer, const int length, + const cJSON_bool format); /* Delete a cJSON entity and all subentities. */ -extern void cJSON_Delete(cJSON* c); +void cJSON_Delete(cJSON* item); /* Returns the number of items in an array (or object). */ -extern auto cJSON_GetArraySize(cJSON* array) -> int; -/* Retrieve item number "item" from array "array". Returns NULL if unsuccessful. - */ -extern auto cJSON_GetArrayItem(cJSON* array, int item) -> cJSON*; +int cJSON_GetArraySize(const cJSON* array); +/* Retrieve item number "index" from array "array". Returns NULL if + * unsuccessful. */ +cJSON* cJSON_GetArrayItem(const cJSON* array, int index); /* Get item "string" from object. Case insensitive. */ -extern auto cJSON_GetObjectItem(cJSON* object, const char* string) -> cJSON*; - +cJSON* cJSON_GetObjectItem(const cJSON* const object, const char* const string); +cJSON* cJSON_GetObjectItemCaseSensitive(const cJSON* const object, + const char* const string); +cJSON_bool cJSON_HasObjectItem(const cJSON* object, const char* string); /* For analysing failed parses. This returns a pointer to the parse error. * You'll probably need to look a few chars back to make sense of it. Defined * when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */ -extern auto cJSON_GetErrorPtr() -> const char*; +const char* cJSON_GetErrorPtr(void); + +/* Check item type and return its value */ +char* cJSON_GetStringValue(const cJSON* const item); +double cJSON_GetNumberValue(const cJSON* const item); + +/* These functions check the type of an item */ +cJSON_bool cJSON_IsInvalid(const cJSON* const item); +cJSON_bool cJSON_IsFalse(const cJSON* const item); +cJSON_bool cJSON_IsTrue(const cJSON* const item); +cJSON_bool cJSON_IsBool(const cJSON* const item); +cJSON_bool cJSON_IsNull(const cJSON* const item); +cJSON_bool cJSON_IsNumber(const cJSON* const item); +cJSON_bool cJSON_IsString(const cJSON* const item); +cJSON_bool cJSON_IsArray(const cJSON* const item); +cJSON_bool cJSON_IsObject(const cJSON* const item); +cJSON_bool cJSON_IsRaw(const cJSON* const item); /* These calls create a cJSON item of the appropriate type. */ -extern auto cJSON_CreateNull() -> cJSON*; -extern auto cJSON_CreateTrue() -> cJSON*; -extern auto cJSON_CreateFalse() -> cJSON*; -extern auto cJSON_CreateBool(int b) -> cJSON*; -extern auto cJSON_CreateNumber(double num) -> cJSON*; -extern auto cJSON_CreateString(const char* string) -> cJSON*; -extern auto cJSON_CreateArray() -> cJSON*; -extern auto cJSON_CreateObject() -> cJSON*; +cJSON* cJSON_CreateNull(void); +cJSON* cJSON_CreateTrue(void); +cJSON* cJSON_CreateFalse(void); +cJSON* cJSON_CreateBool(cJSON_bool boolean); +cJSON* cJSON_CreateNumber(double num); +cJSON* cJSON_CreateString(const char* string); +/* raw json */ +cJSON* cJSON_CreateRaw(const char* raw); +cJSON* cJSON_CreateArray(void); +cJSON* cJSON_CreateObject(void); -/* These utilities create an Array of count items. */ -extern auto cJSON_CreateIntArray(const int* numbers, int count) -> cJSON*; -extern auto cJSON_CreateFloatArray(const float* numbers, int count) -> cJSON*; -extern auto cJSON_CreateDoubleArray(const double* numbers, int count) -> cJSON*; -extern auto cJSON_CreateStringArray(const char** strings, int count) -> cJSON*; +/* Create a string where valuestring references a string so + * it will not be freed by cJSON_Delete */ +cJSON* cJSON_CreateStringReference(const char* string); +/* Create an object/array that only references it's elements so + * they will not be freed by cJSON_Delete */ +cJSON* cJSON_CreateObjectReference(const cJSON* child); +cJSON* cJSON_CreateArrayReference(const cJSON* child); + +/* These utilities create an Array of count items. + * The parameter count cannot be greater than the number of elements in the + * number array, otherwise array access will be out of bounds.*/ +cJSON* cJSON_CreateIntArray(const int* numbers, int count); +cJSON* cJSON_CreateFloatArray(const float* numbers, int count); +cJSON* cJSON_CreateDoubleArray(const double* numbers, int count); +cJSON* cJSON_CreateStringArray(const char* const* strings, int count); /* Append item to the specified array/object. */ -extern void cJSON_AddItemToArray(cJSON* array, cJSON* item); -extern void cJSON_AddItemToObject(cJSON* object, const char* string, - cJSON* item); +cJSON_bool cJSON_AddItemToArray(cJSON* array, cJSON* item); +cJSON_bool cJSON_AddItemToObject(cJSON* object, const char* string, + cJSON* item); +/* Use this when string is definitely const (i.e. a literal, or as good as), and + * will definitely survive the cJSON object. WARNING: When this function was + * used, make sure to always check that (item->type & cJSON_StringIsConst) is + * zero before writing to `item->string` */ +cJSON_bool cJSON_AddItemToObjectCS(cJSON* object, const char* string, + cJSON* item); /* Append reference to item to the specified array/object. Use this when you * want to add an existing cJSON to a new cJSON, but don't want to corrupt your * existing cJSON. */ -extern void cJSON_AddItemReferenceToArray(cJSON* array, cJSON* item); -extern void cJSON_AddItemReferenceToObject(cJSON* object, const char* string, - cJSON* item); +cJSON_bool cJSON_AddItemReferenceToArray(cJSON* array, cJSON* item); +cJSON_bool cJSON_AddItemReferenceToObject(cJSON* object, const char* string, + cJSON* item); /* Remove/Detach items from Arrays/Objects. */ -extern auto cJSON_DetachItemFromArray(cJSON* array, int which) -> cJSON*; -extern void cJSON_DeleteItemFromArray(cJSON* array, int which); -extern auto cJSON_DetachItemFromObject(cJSON* object, const char* string) - -> cJSON*; -extern void cJSON_DeleteItemFromObject(cJSON* object, const char* string); +cJSON* cJSON_DetachItemViaPointer(cJSON* parent, cJSON* const item); +cJSON* cJSON_DetachItemFromArray(cJSON* array, int which); +void cJSON_DeleteItemFromArray(cJSON* array, int which); +cJSON* cJSON_DetachItemFromObject(cJSON* object, const char* string); +cJSON* cJSON_DetachItemFromObjectCaseSensitive(cJSON* object, + const char* string); +void cJSON_DeleteItemFromObject(cJSON* object, const char* string); +void cJSON_DeleteItemFromObjectCaseSensitive(cJSON* object, const char* string); /* Update array items. */ -extern void cJSON_ReplaceItemInArray(cJSON* array, int which, cJSON* newitem); -extern void cJSON_ReplaceItemInObject(cJSON* object, const char* string, - cJSON* newitem); +cJSON_bool cJSON_InsertItemInArray( + cJSON* array, int which, + cJSON* newitem); /* Shifts pre-existing items to the right. */ +cJSON_bool cJSON_ReplaceItemViaPointer(cJSON* const parent, cJSON* const item, + cJSON* replacement); +cJSON_bool cJSON_ReplaceItemInArray(cJSON* array, int which, cJSON* newitem); +cJSON_bool cJSON_ReplaceItemInObject(cJSON* object, const char* string, + cJSON* newitem); +cJSON_bool cJSON_ReplaceItemInObjectCaseSensitive(cJSON* object, + const char* string, + cJSON* newitem); /* Duplicate a cJSON item */ -extern auto cJSON_Duplicate(cJSON* item, int recurse) -> cJSON*; +cJSON* cJSON_Duplicate(const cJSON* item, cJSON_bool recurse); /* Duplicate will create a new, identical cJSON item to the one you pass, in new -memory that will need to be released. With recurse!=0, it will duplicate any -children connected to the item. The item->next and ->prev pointers are always -zero on return from Duplicate. */ + * memory that will need to be released. With recurse!=0, it will duplicate any + * children connected to the item. The item->next and ->prev pointers are always + * zero on return from Duplicate. */ +/* Recursively compare two cJSON items for equality. If either a or b is NULL or + * invalid, they will be considered unequal. case_sensitive determines if object + * keys are treated case sensitive (1) or case insensitive (0) */ +cJSON_bool cJSON_Compare(const cJSON* const a, const cJSON* const b, + const cJSON_bool case_sensitive); -/* ParseWithOpts allows you to require (and check) that the JSON is null - * terminated, and to retrieve the pointer to the final byte parsed. */ -extern auto cJSON_ParseWithOpts(const char* value, - const char** return_parse_end, - int require_null_terminated) -> cJSON*; +/* Minify a strings, remove blank characters(such as ' ', '\t', '\r', '\n') from + * strings. The input pointer json cannot point to a read-only address area, + * such as a string constant, but should point to a readable and writable + * address area. */ +void cJSON_Minify(char* json); -extern void cJSON_Minify(char* json); - -/* Macros for creating things quickly. */ -#define cJSON_AddNullToObject(object, name) \ - cJSON_AddItemToObject(object, name, cJSON_CreateNull()) -#define cJSON_AddTrueToObject(object, name) \ - cJSON_AddItemToObject(object, name, cJSON_CreateTrue()) -#define cJSON_AddFalseToObject(object, name) \ - cJSON_AddItemToObject(object, name, cJSON_CreateFalse()) -#define cJSON_AddBoolToObject(object, name, b) \ - cJSON_AddItemToObject(object, name, cJSON_CreateBool(b)) -#define cJSON_AddNumberToObject(object, name, n) \ - cJSON_AddItemToObject(object, name, cJSON_CreateNumber(n)) -#define cJSON_AddStringToObject(object, name, s) \ - cJSON_AddItemToObject(object, name, cJSON_CreateString(s)) +/* Helper functions for creating and adding items to an object at the same time. + * They return the added item or NULL on failure. */ +cJSON* cJSON_AddNullToObject(cJSON* const object, const char* const name); +cJSON* cJSON_AddTrueToObject(cJSON* const object, const char* const name); +cJSON* cJSON_AddFalseToObject(cJSON* const object, const char* const name); +cJSON* cJSON_AddBoolToObject(cJSON* const object, const char* const name, + const cJSON_bool boolean); +cJSON* cJSON_AddNumberToObject(cJSON* const object, const char* const name, + const double number); +cJSON* cJSON_AddStringToObject(cJSON* const object, const char* const name, + const char* const string); +cJSON* cJSON_AddRawToObject(cJSON* const object, const char* const name, + const char* const raw); +cJSON* cJSON_AddObjectToObject(cJSON* const object, const char* const name); +cJSON* cJSON_AddArrayToObject(cJSON* const object, const char* const name); /* When assigning an integer value, it needs to be propagated to valuedouble * too. */ -#define cJSON_SetIntValue(object, val) \ - ((object) ? (object)->valueint = (object)->valuedouble = (val) : (val)) +#define cJSON_SetIntValue(object, number) \ + ((object) ? (object)->valueint = (object)->valuedouble = (number) : (number)) +/* helper for the cJSON_SetNumberValue macro */ +double cJSON_SetNumberHelper(cJSON* object, double number); +#define cJSON_SetNumberValue(object, number) \ + ((object != NULL) ? cJSON_SetNumberHelper(object, (double)number) : (number)) +/* Change the valuestring of a cJSON_String object, only takes effect when type + * of object is cJSON_String */ +char* cJSON_SetValuestring(cJSON* object, const char* valuestring); -// ericf addition: c++ wrapper for this stuff. +/* If the object is not a boolean type this does nothing and returns + * cJSON_Invalid else it returns the new type*/ +#define cJSON_SetBoolValue(object, boolValue) \ + ((object != NULL && ((object)->type & (cJSON_False | cJSON_True))) \ + ? (object)->type = ((object)->type & (~(cJSON_False | cJSON_True))) \ + | ((boolValue) ? cJSON_True : cJSON_False) \ + : cJSON_Invalid) + +/* Macro for iterating over an array or object */ +#define cJSON_ArrayForEach(element, array) \ + for (element = (array != NULL) ? (array)->child : NULL; element != NULL; \ + element = element->next) + +/* malloc/free objects using the malloc/free functions that have been set with + * cJSON_InitHooks */ +void* cJSON_malloc(size_t size); +void cJSON_free(void* object); + +// ericf addition: simple c++ wrappers for a few things here. // NOTE: once added to a dict/list/etc, the underlying cJSON's // lifecycle is dependent on its parent, not this object. @@ -221,19 +351,6 @@ class JsonDict : public JsonObject { } }; -// class JsonNumber : public JsonObject { -// public: -// JsonNumber(double val) { set_obj(cJSON_CreateNumber(val)); } -// }; - -// class JsonString : public JsonObject { -// public: -// JsonString(const std::string& s) { set_obj(cJSON_CreateString(s.c_str())); -// } JsonString(const char* s) { set_obj(cJSON_CreateString(s)); } -// }; - -#pragma clang diagnostic pop - } // namespace ballistica #endif // BALLISTICA_SHARED_GENERIC_JSON_H_ diff --git a/src/ballistica/shared/generic/utf8.cc b/src/ballistica/shared/generic/utf8.cc index 6687f1fa..b2cc83b0 100644 --- a/src/ballistica/shared/generic/utf8.cc +++ b/src/ballistica/shared/generic/utf8.cc @@ -14,7 +14,7 @@ with these routines reserved for higher performance on data known to be valid. */ -#include "utf8.h" +#include "ballistica/shared/generic/utf8.h" #if _WIN32 || _WIN64 #include diff --git a/tools/batools/build.py b/tools/batools/build.py index cd31fe69..4277c4dc 100644 --- a/tools/batools/build.py +++ b/tools/batools/build.py @@ -2,13 +2,10 @@ # """General functionality related to running builds.""" -# pylint: disable=too-many-lines - from __future__ import annotations import os import sys -import datetime import subprocess from enum import Enum from dataclasses import dataclass @@ -63,29 +60,6 @@ PY_REQUIREMENTS = [ PyRequirement(pipname='filelock', minversion=[3, 12, 0]), ] -# Parts of full-tests suite we only run on particular days. This runs in -# listed order so should be randomized by hand to avoid clustering -# similar tests too much. -SPARSE_TEST_BUILDS: list[list[str]] = [ - ['ios.pylibs.debug', 'android.pylibs.arm'], - ['linux.package', 'android.pylibs.arm64'], - ['windows.package', 'mac.pylibs'], - ['tvos.pylibs', 'android.pylibs.x86'], - ['android.pylibs.arm.debug'], - ['windows.package.server'], - ['ios.pylibs', 'android.pylibs.arm64.debug'], - ['linux.package.server'], - ['android.pylibs.x86.debug', 'mac.package'], - ['mac.package.server.arm64', 'android.pylibs.x86_64'], - ['windows.package.oculus'], - ['mac.package.server.x86_64', 'android.pylibs.x86_64.debug'], - ['mac.pylibs.debug', 'android.package'], -] - -# Currently only doing sparse-tests in core; not spinoffs (whole word -# will get subbed out in spinoffs so this will evaluate to False). -DO_SPARSE_TEST_BUILDS = 'ballistica' + 'kit' == 'ballisticakit' - class PrefabTarget(Enum): """Types of prefab builds able to be run.""" @@ -309,314 +283,6 @@ def archive_old_builds( ) -def gen_fulltest_buildfile_android() -> None: - """Generate fulltest command list for jenkins. - - (so we see nice pretty split-up build trees) - """ - # pylint: disable=too-many-branches - - # Its a pretty big time-suck building all architectures for all of - # our subplatforms, so lets usually just build a single one. We'll - # rotate it though and occasionally do all 4 at once just to be - # safe. - dayoffset = datetime.datetime.now().timetuple().tm_yday - - # Let's only do a full 'prod' once every two times through the loop. - # (it really should never catch anything that individual platforms - # don't) - modes = ['arm', 'arm64', 'x86', 'x86_64'] - modes += modes - modes.append('prod') - - # By default we cycle through build architectures for each flavor. - # However, for minor flavor with low risk of platform-dependent - # breakage we stick to a single one to keep disk space costs lower. - # (build files amount to several gigs per mode per flavor) - # - # UPDATE: Now that we have CPU time to spare, we simply always do - # 'arm64' or 'prod' depending on build type; this results in 1 or 4 - # architectures worth of build files per flavor instead of 8 (prod + - # 4 singles) and keeps our daily runs identical. - lightweight_flavors = {'template', 'arcade', 'demo', 'iircade'} - - lines = [] - for _i, flavor in enumerate( - sorted(os.listdir('ballisticakit-android/BallisticaKit/src')) - ): - if flavor == 'main' or flavor.startswith('.'): - continue - - if flavor in lightweight_flavors: - mode = 'arm64' - else: - # mode = modes[(dayoffset + i) % len(modes)] - mode = 'prod' - lines.append( - 'ANDROID_PLATFORM=' - + flavor - + ' ANDROID_MODE=' - + mode - + ' make android-cloud-build' - ) - - # Now add sparse tests that land on today. - if DO_SPARSE_TEST_BUILDS: - extras = SPARSE_TEST_BUILDS[dayoffset % len(SPARSE_TEST_BUILDS)] - extras = [e for e in extras if e.startswith('android.')] - cspre = 'tools/cloudshell linbeast --env ba-android-alldeps --' - - # This is currently broken; turning off. - # Update: should be working again; hooray! - do_py_android = True - - for extra in extras: - if extra == 'android.pylibs.arm': - if do_py_android: - lines.append( - f'{cspre} tools/pcommand' f' python_build_android arm' - ) - elif extra == 'android.pylibs.arm.debug': - if do_py_android: - lines.append( - f'{cspre} tools/pcommand' - f' python_build_android_debug arm' - ) - elif extra == 'android.pylibs.arm64': - if do_py_android: - lines.append( - f'{cspre} tools/pcommand python_build_android arm64' - ) - elif extra == 'android.pylibs.arm64.debug': - if do_py_android: - lines.append( - f'{cspre} tools/pcommand' - f' python_build_android_debug arm64' - ) - elif extra == 'android.pylibs.x86': - if do_py_android: - lines.append( - f'{cspre} tools/pcommand' f' python_build_android x86' - ) - elif extra == 'android.pylibs.x86.debug': - if do_py_android: - lines.append( - f'{cspre} tools/pcommand' - f' python_build_android_debug x86' - ) - elif extra == 'android.pylibs.x86_64': - if do_py_android: - lines.append( - f'{cspre} tools/pcommand' - f' python_build_android x86_64' - ) - elif extra == 'android.pylibs.x86_64.debug': - if do_py_android: - lines.append( - f'{cspre} tools/pcommand' - f' python_build_android_debug x86_64' - ) - elif extra == 'android.package': - lines.append('make android-package-cloud') - else: - raise RuntimeError(f'Unknown extra: {extra}') - - os.makedirs('build', exist_ok=True) - with open( - 'build/fulltest_buildfile_android', 'w', encoding='utf-8' - ) as outfile: - outfile.write('\n'.join(lines)) - - -def gen_fulltest_buildfile_windows() -> None: - """Generate fulltest command list for jenkins. - - (so we see nice pretty split-up build trees) - """ - - dayoffset = datetime.datetime.now().timetuple().tm_yday - - lines: list[str] = [] - - # We want to do one regular, one headless, and one oculus build, but - # let's switch up 32 or 64 bit based on the day. Also occasionally - # throw a release build in but stick to mostly debug builds to keep - # build times speedier. - pval1 = 'Win32' if dayoffset % 2 == 0 else 'x64' - pval2 = 'Win32' if (dayoffset + 1) % 2 == 0 else 'x64' - pval3 = 'Win32' if (dayoffset + 2) % 2 == 0 else 'x64' - cfg1 = 'Release' if dayoffset % 7 == 0 else 'Debug' - cfg2 = 'Release' if (dayoffset + 1) % 7 == 0 else 'Debug' - cfg3 = 'Release' if (dayoffset + 2) % 7 == 0 else 'Debug' - - lines.append( - f'WINDOWS_PROJECT=Generic WINDOWS_PLATFORM={pval1} ' - f'WINDOWS_CONFIGURATION={cfg1} make windows-cloud-build' - ) - lines.append( - f'WINDOWS_PROJECT=Headless WINDOWS_PLATFORM={pval2} ' - f'WINDOWS_CONFIGURATION={cfg2} make windows-cloud-build' - ) - lines.append( - f'WINDOWS_PROJECT=Oculus WINDOWS_PLATFORM={pval3} ' - f'WINDOWS_CONFIGURATION={cfg3} make windows-cloud-build' - ) - - # Now add sparse tests that land on today. - if DO_SPARSE_TEST_BUILDS: - extras = SPARSE_TEST_BUILDS[dayoffset % len(SPARSE_TEST_BUILDS)] - extras = [e for e in extras if e.startswith('windows.')] - for extra in extras: - if extra == 'windows.package': - lines.append('make windows-package') - elif extra == 'windows.package.server': - lines.append('make windows-server-package') - elif extra == 'windows.package.oculus': - lines.append('make windows-oculus-package') - else: - raise RuntimeError(f'Unknown extra: {extra}') - - os.makedirs('build', exist_ok=True) - with open( - 'build/fulltest_buildfile_windows', 'w', encoding='utf-8' - ) as outfile: - outfile.write('\n'.join(lines)) - - -def gen_fulltest_buildfile_apple() -> None: - """Generate fulltest command list for jenkins. - - (so we see nice pretty split-up build trees) - """ - # pylint: disable=too-many-branches - - dayoffset = datetime.datetime.now().timetuple().tm_yday - - # noinspection PyListCreation - lines = [] - - # pybuildapple = 'tools/pcommand python_build_apple' - pybuildapple = ( - 'tools/cloudshell --env tools fromini -- ' - 'tools/pcommand python_build_apple' - ) - - # iOS stuff - lines.append('make ios-cloud-build') - lines.append('make ios-new-cloud-build') - if DO_SPARSE_TEST_BUILDS: - extras = SPARSE_TEST_BUILDS[dayoffset % len(SPARSE_TEST_BUILDS)] - extras = [e for e in extras if e.startswith('ios.')] - for extra in extras: - if extra == 'ios.pylibs': - lines.append(f'{pybuildapple} ios') - elif extra == 'ios.pylibs.debug': - lines.append(f'{pybuildapple}_debug ios') - else: - raise RuntimeError(f'Unknown extra: {extra}') - - # tvOS stuff - lines.append('make tvos-cloud-build') - if DO_SPARSE_TEST_BUILDS: - extras = SPARSE_TEST_BUILDS[dayoffset % len(SPARSE_TEST_BUILDS)] - extras = [e for e in extras if e.startswith('tvos.')] - for extra in extras: - if extra == 'tvos.pylibs': - lines.append(f'{pybuildapple} tvos') - elif extra == 'tvos.pylibs.debug': - lines.append(f'{pybuildapple}_debug tvos') - else: - raise RuntimeError(f'Unknown extra: {extra}') - - # macOS stuff - lines.append('make mac-cloud-build') - # (throw release build in the mix to hopefully catch opt-mode-only errors). - lines.append('MAC_CONFIGURATION=Release make mac-appstore-cloud-build') - lines.append('make mac-new-cloud-build') - lines.append('CMAKE_CLOUDSHELL_HOST=fromini make cmake-cloud-server-build') - lines.append('CMAKE_CLOUDSHELL_HOST=fromini make cmake-cloud-build') - if DO_SPARSE_TEST_BUILDS: - extras = SPARSE_TEST_BUILDS[dayoffset % len(SPARSE_TEST_BUILDS)] - extras = [e for e in extras if e.startswith('mac.')] - for extra in extras: - if extra == 'mac.package': - lines.append( - 'BA_MAC_DISK_IMAGE_SKIP_NOTARIZATION=1' - ' make mac-package-cloud' - # 'make mac-package-cloud' - ) - elif extra == 'mac.package.server.x86_64': - lines.append('make mac-server-package-x86-64') - elif extra == 'mac.package.server.arm64': - lines.append('make mac-server-package-arm64') - elif extra == 'mac.pylibs': - lines.append(f'{pybuildapple} mac') - elif extra == 'mac.pylibs.debug': - lines.append(f'{pybuildapple}_debug mac') - else: - raise RuntimeError(f'Unknown extra: {extra}') - - os.makedirs('build', exist_ok=True) - with open( - 'build/fulltest_buildfile_apple', 'w', encoding='utf-8' - ) as outfile: - outfile.write('\n'.join(lines)) - - -def gen_fulltest_buildfile_linux() -> None: - """Generate fulltest command list for jenkins. - - (so we see nice pretty split-up build trees) - """ - - dayoffset = datetime.datetime.now().timetuple().tm_yday - - targets = ['build', 'server-build'] - lines = [] - for target in targets: - lines.append(f'make cmake-cloud-{target}') - - if DO_SPARSE_TEST_BUILDS: - extras = SPARSE_TEST_BUILDS[dayoffset % len(SPARSE_TEST_BUILDS)] - extras = [e for e in extras if e.startswith('linux.')] - for extra in extras: - if extra == 'linux.package': - lines.append('make linux-package') - elif extra == 'linux.package.server': - lines.append('make linux-server-package') - else: - raise RuntimeError(f'Unknown extra: {extra}') - - os.makedirs('build', exist_ok=True) - with open( - 'build/fulltest_buildfile_linux', 'w', encoding='utf-8' - ) as outfile: - outfile.write('\n'.join(lines)) - - -def gen_fulltest_buildfile_spinoff() -> None: - """Generate fulltest command list for jenkins. - - (so we see nice pretty split-up build trees) - """ - from batools.featureset import FeatureSet - - # Run a spinoff test with each of our feature-sets individually. - # Note that there will likely be redundant tests with the same final - # resolved sets of feature sets. We can filter those out later if it - # seems worthwhile. - targets = sorted(f.name for f in FeatureSet.get_all_for_project('.')) - lines = [] - for target in targets: - lines.append(f'SPINOFF_TEST_TARGET={target} make spinoff-test-cloud') - - os.makedirs('build', exist_ok=True) - with open( - 'build/fulltest_buildfile_spinoff', 'w', encoding='utf-8' - ) as outfile: - outfile.write('\n'.join(lines)) - - def get_current_prefab_platform(wsl_gives_windows: bool = True) -> str: """Get an identifier for the platform running this build. diff --git a/tools/batools/pcommand.py b/tools/batools/pcommand.py index acfe5756..e84651d5 100644 --- a/tools/batools/pcommand.py +++ b/tools/batools/pcommand.py @@ -240,56 +240,6 @@ def printcolors() -> None: ) -def gen_fulltest_buildfile_android() -> None: - """Generate fulltest command list for jenkins. - - (so we see nice pretty split-up build trees) - """ - import batools.build - - batools.build.gen_fulltest_buildfile_android() - - -def gen_fulltest_buildfile_windows() -> None: - """Generate fulltest command list for jenkins. - - (so we see nice pretty split-up build trees) - """ - import batools.build - - batools.build.gen_fulltest_buildfile_windows() - - -def gen_fulltest_buildfile_apple() -> None: - """Generate fulltest command list for jenkins. - - (so we see nice pretty split-up build trees) - """ - import batools.build - - batools.build.gen_fulltest_buildfile_apple() - - -def gen_fulltest_buildfile_linux() -> None: - """Generate fulltest command list for jenkins. - - (so we see nice pretty split-up build trees) - """ - import batools.build - - batools.build.gen_fulltest_buildfile_linux() - - -def gen_fulltest_buildfile_spinoff() -> None: - """Generate fulltest command list for jenkins. - - (so we see nice pretty split-up build trees) - """ - import batools.build - - batools.build.gen_fulltest_buildfile_spinoff() - - def python_version_android_base() -> None: """Print built Python base version.""" from efrotools.pybuild import PY_VER_ANDROID diff --git a/tools/batools/spinoff/_main.py b/tools/batools/spinoff/_main.py index 164189a0..b9191772 100644 --- a/tools/batools/spinoff/_main.py +++ b/tools/batools/spinoff/_main.py @@ -109,7 +109,7 @@ def _main() -> None: if '--soft' in sys.argv: return raise CleanError( - 'This only works on dst projects;' + 'Spinoff only works from dst projects;' ' you appear to be in a src project.' " To silently no-op in this case, pass '--soft'." ) diff --git a/tools/efrotools/openalbuild.py b/tools/efrotools/openalbuild.py new file mode 100644 index 00000000..bfbfa061 --- /dev/null +++ b/tools/efrotools/openalbuild.py @@ -0,0 +1,137 @@ +# Released under the MIT License. See LICENSE for details. +# +"""Functionality to build the openal library.""" + +from __future__ import annotations + +import os +import subprocess +from typing import TYPE_CHECKING + +from efro.error import CleanError + +if TYPE_CHECKING: + pass + +# Arch names we take and their official android versions. +ARCHS = { + 'arm': 'armeabi-v7a', + 'arm64': 'arm64-v8a', + 'x86': 'x86', + 'x86_64': 'x86_64', +} + +MODES = {'debug', 'release'} + + +def _build_dir(arch: str, mode: str) -> str: + """Build dir given an arch and mode.""" + return f'build/openal_build_android_{arch}_{mode}' + + +def build(arch: str, mode: str) -> None: + """Do the thing.""" + from efrotools import replace_exact + + if arch not in ARCHS: + raise CleanError(f"Invalid arch '{arch}'.") + + if mode not in MODES: + raise CleanError(f"Invalid mode '{mode}'.") + + # Get ndk path. + ndk_path = ( + subprocess.run( + ['tools/pcommand', 'android_sdk_utils', 'get-ndk-path'], + check=True, + capture_output=True, + ) + .stdout.decode() + .strip() + ) + + # Grab from git and build. + builddir = _build_dir(arch, mode) + subprocess.run(['rm', '-rf', builddir], check=True) + subprocess.run(['mkdir', '-p', os.path.dirname(builddir)], check=True) + subprocess.run( + ['git', 'clone', 'https://github.com/kcat/openal-soft.git', builddir], + check=True, + ) + + commit = 'd3875f3' # Version 1.23.1 + subprocess.run(['git', 'checkout', commit], check=True, cwd=builddir) + + # One bit of filtering: by default, openalsoft sends all sorts of + # log messages to the android log. This is reasonable since its + # possible to filter by tag/level. However I'd prefer it to send + # only the ones that it would send to stderr so I don't always have + # to worry about filtering. + loggingpath = f'{builddir}/core/logging.cpp' + with open(loggingpath, encoding='utf-8') as infile: + txt = infile.read() + + txt = replace_exact( + txt, + ' __android_log_print(android_severity(level),' + ' "openal", "%s", str);', + ' // ericf tweak; only send logs to' + ' android that we\'d send to stderr.\n' + ' if (gLogLevel >= level) {\n' + ' __android_log_print(android_severity(level),' + ' "openal", "%s", str);\n' + ' }', + ) + with open(loggingpath, 'w', encoding='utf-8') as outfile: + outfile.write(txt) + + subprocess.run( + [ + 'cmake', + '.', + f'-DANDROID_ABI={ARCHS[arch]}', + '-DANDROID_NATIVE_API_LEVEL=21', + f'-DCMAKE_BUILD_TYPE={mode}', + '-DLIBTYPE=STATIC', + '-DCMAKE_TOOLCHAIN_FILE=' + f'{ndk_path}/build/cmake/android.toolchain.cmake', + ], + cwd=builddir, + check=True, + ) + subprocess.run(['make'], cwd=builddir, check=True) + + print('SUCCESS!') + + +def gather() -> None: + """Gather the things. Assumes all have been built.""" + + # Sanity-check; make sure everything appears to be built. + for arch in ARCHS: + for mode in MODES: + builddir = _build_dir(arch, mode) + libfile = os.path.join(builddir, 'libopenal.a') + if not os.path.exists(libfile): + raise CleanError(f"Built lib not found: '{libfile}'.") + + outdir = 'src/external/openal-android' + subprocess.run(['rm', '-rf', outdir], check=True) + + subprocess.run(['mkdir', '-p', f'{outdir}/include'], check=True) + + builddir = _build_dir('arm', 'debug') # Doesn't matter here. + subprocess.run( + ['cp', '-r', f'{builddir}/include/AL', f'{outdir}/include'], + check=True, + ) + + for arch, andrarch in ARCHS.items(): + for mode in MODES: + builddir = _build_dir(arch, mode) + installdir = f'{outdir}/lib/{andrarch}_{mode}' + subprocess.run(['mkdir', '-p', installdir], check=True) + subprocess.run( + ['cp', f'{builddir}/libopenal.a', installdir], check=True + ) + print('OpenAL gather successful!') diff --git a/tools/efrotools/pcommand2.py b/tools/efrotools/pcommand2.py index 82dc2bd3..77c0fbf6 100644 --- a/tools/efrotools/pcommand2.py +++ b/tools/efrotools/pcommand2.py @@ -40,3 +40,30 @@ def sortlines() -> None: val = sys.argv[2] lines = val.splitlines() print('\n'.join(sorted(lines, key=lambda l: l.lower()))) + + +def openal_build_android() -> None: + """Build openalsoft for android.""" + from efro.error import CleanError + from efrotools.openalbuild import build + + args = sys.argv[2:] + if len(args) != 2: + raise CleanError( + 'Expected one arg: arm, arm64, x86, x86_64' + ' and one arg: debug, release' + ) + + build(args[0], args[1]) + + +def openal_gather() -> None: + """Gather built opealsoft libs into src.""" + from efro.error import CleanError + from efrotools.openalbuild import gather + + args = sys.argv[2:] + if args: + raise CleanError('No args expected.') + + gather() diff --git a/tools/pcommand b/tools/pcommand index 5307b4d9..094a4a5a 100755 --- a/tools/pcommand +++ b/tools/pcommand @@ -54,6 +54,8 @@ from efrotools.pcommand import ( from efrotools.pcommand2 import ( with_build_lock, sortlines, + openal_build_android, + openal_gather, ) from batools.pcommand import ( resize_image, @@ -64,11 +66,6 @@ from batools.pcommand import ( androidaddr, push_ipa, printcolors, - gen_fulltest_buildfile_android, - gen_fulltest_buildfile_windows, - gen_fulltest_buildfile_apple, - gen_fulltest_buildfile_linux, - gen_fulltest_buildfile_spinoff, prune_includes, python_version_android, python_version_apple,