Browse Source

Core ubsan fixes

This fixes UBSAN errors reported by running our testsuite, importing the
TPS demo, and running the TPS demo. I have tried, wherever possible, to
fix issues related to reported issues but not directly reported by UBSAN
because thse code paths just happened to not have been exercised in
these cases.

These fixes apply only to errors reported, and caused by, core/

The following things have been changed:

* Make sure there are no implicit sign changing casts in core.
* Explicitly type enums that are part of a public API such that users of
  the API cannot pass in wrongly-sized values leading to potential stack
  corruption.
* Ensure that memcpy is never called with invalid or null pointers as
  this is undefined behavior, and when the engine is built with
  optimizations turned on leads to memory corruption and hard to debug
  crashes.
* Replace enum values only used as static values with constexpr static
  const values instead. This has no runtime overhead. This makes it so
  that the size of the enums is explicit.
* Make sure that nan and inf is handled consistently in String.
* Implement a _to_int template to ensure that all of the paths use the
  same algorhithm, and correct the negative integer case.
* Changed the way the json serializer precision work, and added tests to
  verify the new behavior. The behavior doesn't quite match master in
  particulary for negative doubles as the original code tried to cast -inf
  to an int. This then led to negative doubles losing all but one of
  their decimal points when serializing. Behavior in GDScript remains
  unchanged.
HP van Braam 10 tháng trước cách đây
mục cha
commit
240f510fa7

+ 4 - 4
core/config/project_settings.cpp

@@ -898,7 +898,7 @@ Error ProjectSettings::_save_settings_binary(const String &p_file, const RBMap<S
 
 	if (!p_custom_features.is_empty()) {
 		// Store how many properties are saved, add one for custom features, which must always go first.
-		file->store_32(count + 1);
+		file->store_32(uint32_t(count + 1));
 		String key = CoreStringName(_custom_features);
 		file->store_pascal_string(key);
 
@@ -911,12 +911,12 @@ Error ProjectSettings::_save_settings_binary(const String &p_file, const RBMap<S
 
 		err = encode_variant(p_custom_features, buff.ptrw(), len, false);
 		ERR_FAIL_COND_V(err != OK, err);
-		file->store_32(len);
+		file->store_32(uint32_t(len));
 		file->store_buffer(buff.ptr(), buff.size());
 
 	} else {
 		// Store how many properties are saved.
-		file->store_32(count);
+		file->store_32(uint32_t(count));
 	}
 
 	for (const KeyValue<String, List<String>> &E : p_props) {
@@ -943,7 +943,7 @@ Error ProjectSettings::_save_settings_binary(const String &p_file, const RBMap<S
 
 			err = encode_variant(value, buff.ptrw(), len, true);
 			ERR_FAIL_COND_V_MSG(err != OK, ERR_INVALID_DATA, "Error when trying to encode Variant.");
-			file->store_32(len);
+			file->store_32(uint32_t(len));
 			file->store_buffer(buff.ptr(), buff.size());
 		}
 	}

+ 2 - 4
core/config/project_settings.h

@@ -47,10 +47,8 @@ public:
 	typedef HashMap<String, Variant> CustomMap;
 	static const String PROJECT_DATA_DIR_NAME_SUFFIX;
 
-	enum {
-		// Properties that are not for built in values begin from this value, so builtin ones are displayed first.
-		NO_BUILTIN_ORDER_BASE = 1 << 16
-	};
+	// Properties that are not for built in values begin from this value, so builtin ones are displayed first.
+	constexpr static const int32_t NO_BUILTIN_ORDER_BASE = 1 << 16;
 
 #ifdef TOOLS_ENABLED
 	const static PackedStringArray get_required_features();

+ 1 - 1
core/crypto/aes_context.h

@@ -38,7 +38,7 @@ class AESContext : public RefCounted {
 	GDCLASS(AESContext, RefCounted);
 
 public:
-	enum Mode {
+	enum Mode : int32_t {
 		MODE_ECB_ENCRYPT,
 		MODE_ECB_DECRYPT,
 		MODE_CBC_ENCRYPT,

+ 1 - 1
core/crypto/hashing_context.h

@@ -37,7 +37,7 @@ class HashingContext : public RefCounted {
 	GDCLASS(HashingContext, RefCounted);
 
 public:
-	enum HashType {
+	enum HashType : int32_t {
 		HASH_MD5,
 		HASH_SHA1,
 		HASH_SHA256

+ 4 - 0
core/input/input_enums.h

@@ -31,6 +31,8 @@
 #ifndef INPUT_ENUMS_H
 #define INPUT_ENUMS_H
 
+#include "core/error/error_macros.h"
+
 enum class HatDir {
 	UP = 0,
 	RIGHT = 1,
@@ -131,6 +133,8 @@ enum class MouseButtonMask {
 };
 
 inline MouseButtonMask mouse_button_to_mask(MouseButton button) {
+	ERR_FAIL_COND_V(button == MouseButton::NONE, MouseButtonMask::NONE);
+
 	return MouseButtonMask(1 << ((int)button - 1));
 }
 

+ 1 - 1
core/io/compression.h

@@ -43,7 +43,7 @@ public:
 	static int zstd_window_log_size;
 	static int gzip_chunk;
 
-	enum Mode {
+	enum Mode : int32_t {
 		MODE_FASTLZ,
 		MODE_DEFLATE,
 		MODE_ZSTD,

+ 1 - 1
core/io/dir_access.h

@@ -40,7 +40,7 @@ class DirAccess : public RefCounted {
 	GDCLASS(DirAccess, RefCounted);
 
 public:
-	enum AccessType {
+	enum AccessType : int32_t {
 		ACCESS_RESOURCES,
 		ACCESS_USERDATA,
 		ACCESS_FILESYSTEM,

+ 5 - 5
core/io/file_access.cpp

@@ -383,7 +383,7 @@ double FileAccess::get_double() const {
 String FileAccess::get_token() const {
 	CharString token;
 
-	char32_t c = get_8();
+	uint8_t c = get_8();
 
 	while (!eof_reached()) {
 		if (c <= ' ') {
@@ -391,7 +391,7 @@ String FileAccess::get_token() const {
 				break;
 			}
 		} else {
-			token += c;
+			token += char(c);
 		}
 		c = get_8();
 	}
@@ -448,14 +448,14 @@ public:
 String FileAccess::get_line() const {
 	CharBuffer line;
 
-	char32_t c = get_8();
+	uint8_t c = get_8();
 
 	while (!eof_reached()) {
 		if (c == '\n' || c == '\0') {
 			line.push_back(0);
 			return String::utf8(line.get_data());
 		} else if (c != '\r') {
-			line.push_back(c);
+			line.push_back(char(c));
 		}
 
 		c = get_8();
@@ -786,7 +786,7 @@ bool FileAccess::store_var(const Variant &p_var, bool p_full_objects) {
 	err = encode_variant(p_var, &w[0], len, p_full_objects);
 	ERR_FAIL_COND_V_MSG(err != OK, false, "Error when trying to encode Variant.");
 
-	return store_32(len) && store_buffer(buff);
+	return store_32(uint32_t(len)) && store_buffer(buff);
 }
 
 Vector<uint8_t> FileAccess::get_file_as_bytes(const String &p_path, Error *r_error) {

+ 4 - 4
core/io/file_access.h

@@ -46,7 +46,7 @@ class FileAccess : public RefCounted {
 	GDCLASS(FileAccess, RefCounted);
 
 public:
-	enum AccessType {
+	enum AccessType : int32_t {
 		ACCESS_RESOURCES,
 		ACCESS_USERDATA,
 		ACCESS_FILESYSTEM,
@@ -54,14 +54,14 @@ public:
 		ACCESS_MAX
 	};
 
-	enum ModeFlags {
+	enum ModeFlags : int32_t {
 		READ = 1,
 		WRITE = 2,
 		READ_WRITE = 3,
 		WRITE_READ = 7,
 	};
 
-	enum UnixPermissionFlags {
+	enum UnixPermissionFlags : int32_t {
 		UNIX_EXECUTE_OTHER = 0x001,
 		UNIX_WRITE_OTHER = 0x002,
 		UNIX_READ_OTHER = 0x004,
@@ -76,7 +76,7 @@ public:
 		UNIX_SET_USER_ID = 0x800,
 	};
 
-	enum CompressionMode {
+	enum CompressionMode : int32_t {
 		COMPRESSION_FASTLZ = Compression::MODE_FASTLZ,
 		COMPRESSION_DEFLATE = Compression::MODE_DEFLATE,
 		COMPRESSION_ZSTD = Compression::MODE_ZSTD,

+ 5 - 3
core/io/file_access_compressed.cpp

@@ -125,7 +125,7 @@ void FileAccessCompressed::_close() {
 		f->store_buffer((const uint8_t *)mgc.get_data(), mgc.length()); //write header 4
 		f->store_32(cmode); //write compression mode 4
 		f->store_32(block_size); //write block size 4
-		f->store_32(write_max); //max amount of data written 4
+		f->store_32(uint32_t(write_max)); //max amount of data written 4
 		uint32_t bc = (write_max / block_size) + 1;
 
 		for (uint32_t i = 0; i < bc; i++) {
@@ -147,7 +147,7 @@ void FileAccessCompressed::_close() {
 
 		f->seek(16); //ok write block sizes
 		for (uint32_t i = 0; i < bc; i++) {
-			f->store_32(block_sizes[i]);
+			f->store_32(uint32_t(block_sizes[i]));
 		}
 		f->seek_end();
 		f->store_buffer((const uint8_t *)mgc.get_data(), mgc.length()); //magic at the end too
@@ -310,7 +310,9 @@ bool FileAccessCompressed::store_buffer(const uint8_t *p_src, uint64_t p_length)
 		write_ptr = buffer.ptrw();
 	}
 
-	memcpy(write_ptr + write_pos, p_src, p_length);
+	if (p_length) {
+		memcpy(write_ptr + write_pos, p_src, p_length);
+	}
 
 	write_pos += p_length;
 	return true;

+ 14 - 2
core/io/file_access_encrypted.cpp

@@ -210,10 +210,16 @@ bool FileAccessEncrypted::eof_reached() const {
 }
 
 uint64_t FileAccessEncrypted::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
-	ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
 	ERR_FAIL_COND_V_MSG(writing, -1, "File has not been opened in read mode.");
 
+	if (!p_length) {
+		return 0;
+	}
+
+	ERR_FAIL_NULL_V(p_dst, -1);
+
 	uint64_t to_copy = MIN(p_length, get_length() - pos);
+
 	memcpy(p_dst, data.ptr() + pos, to_copy);
 	pos += to_copy;
 
@@ -230,7 +236,12 @@ Error FileAccessEncrypted::get_error() const {
 
 bool FileAccessEncrypted::store_buffer(const uint8_t *p_src, uint64_t p_length) {
 	ERR_FAIL_COND_V_MSG(!writing, false, "File has not been opened in write mode.");
-	ERR_FAIL_COND_V(!p_src && p_length > 0, false);
+
+	if (!p_length) {
+		return true;
+	}
+
+	ERR_FAIL_NULL_V(p_src, false);
 
 	if (pos + p_length >= get_length()) {
 		ERR_FAIL_COND_V(data.resize(pos + p_length) != OK, false);
@@ -238,6 +249,7 @@ bool FileAccessEncrypted::store_buffer(const uint8_t *p_src, uint64_t p_length)
 
 	memcpy(data.ptrw() + pos, p_src, p_length);
 	pos += p_length;
+
 	return true;
 }
 

+ 1 - 1
core/io/file_access_encrypted.h

@@ -37,7 +37,7 @@
 
 class FileAccessEncrypted : public FileAccess {
 public:
-	enum Mode {
+	enum Mode : int32_t {
 		MODE_READ,
 		MODE_WRITE_AES256,
 		MODE_MAX

+ 10 - 2
core/io/file_access_memory.cpp

@@ -123,7 +123,11 @@ bool FileAccessMemory::eof_reached() const {
 }
 
 uint64_t FileAccessMemory::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
-	ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
+	if (!p_length) {
+		return 0;
+	}
+
+	ERR_FAIL_NULL_V(p_dst, -1);
 	ERR_FAIL_NULL_V(data, -1);
 
 	uint64_t left = length - pos;
@@ -148,7 +152,11 @@ void FileAccessMemory::flush() {
 }
 
 bool FileAccessMemory::store_buffer(const uint8_t *p_src, uint64_t p_length) {
-	ERR_FAIL_COND_V(!p_src && p_length > 0, false);
+	if (!p_length) {
+		return true;
+	}
+
+	ERR_FAIL_NULL_V(p_src, false);
 
 	uint64_t left = length - pos;
 	uint64_t write = MIN(p_length, left);

+ 1 - 1
core/io/image.h

@@ -70,7 +70,7 @@ public:
 		MAX_PIXELS = 268435456 // 16384 ^ 2
 	};
 
-	enum Format {
+	enum Format : int32_t {
 		FORMAT_L8, // Luminance
 		FORMAT_LA8, // Luminance-Alpha
 		FORMAT_R8,

+ 11 - 7
core/io/json.cpp

@@ -71,14 +71,18 @@ String JSON::_stringify(const Variant &p_var, const String &p_indent, int p_cur_
 			return itos(p_var);
 		case Variant::FLOAT: {
 			double num = p_var;
-			if (p_full_precision) {
-				// Store unreliable digits (17) instead of just reliable
-				// digits (14) so that the value can be decoded exactly.
-				return String::num(num, 17 - (int)floor(log10(num)));
-			} else {
-				// Store only reliable digits (14) by default.
-				return String::num(num, 14 - (int)floor(log10(num)));
+
+			// Only for exactly 0. If we have approximately 0 let the user decide how much
+			// precision they want.
+			if (num == double(0)) {
+				return String("0.0");
 			}
+
+			double magnitude = log10(Math::abs(num));
+			int total_digits = p_full_precision ? 17 : 14;
+			int precision = MAX(1, total_digits - (int)Math::floor(magnitude));
+
+			return String::num(num, precision);
 		}
 		case Variant::PACKED_INT32_ARRAY:
 		case Variant::PACKED_INT64_ARRAY:

+ 4 - 4
core/io/marshalls.cpp

@@ -217,7 +217,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
 		case Variant::INT: {
 			if (header & HEADER_DATA_FLAG_64) {
 				ERR_FAIL_COND_V(len < 8, ERR_INVALID_DATA);
-				int64_t val = decode_uint64(buf);
+				int64_t val = int64_t(decode_uint64(buf));
 				r_variant = val;
 				if (r_len) {
 					(*r_len) += 8;
@@ -225,7 +225,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
 
 			} else {
 				ERR_FAIL_COND_V(len < 4, ERR_INVALID_DATA);
-				int32_t val = decode_uint32(buf);
+				int32_t val = int32_t(decode_uint32(buf));
 				r_variant = val;
 				if (r_len) {
 					(*r_len) += 4;
@@ -1450,13 +1450,13 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
 			if (header & HEADER_DATA_FLAG_64) {
 				// 64 bits.
 				if (buf) {
-					encode_uint64(p_variant.operator int64_t(), buf);
+					encode_uint64(p_variant.operator uint64_t(), buf);
 				}
 
 				r_len += 8;
 			} else {
 				if (buf) {
-					encode_uint32(p_variant.operator int32_t(), buf);
+					encode_uint32(p_variant.operator uint32_t(), buf);
 				}
 
 				r_len += 4;

+ 2 - 2
core/io/net_socket.h

@@ -41,13 +41,13 @@ protected:
 public:
 	static NetSocket *create();
 
-	enum PollType {
+	enum PollType : int32_t {
 		POLL_TYPE_IN,
 		POLL_TYPE_OUT,
 		POLL_TYPE_IN_OUT
 	};
 
-	enum Type {
+	enum Type : int32_t {
 		TYPE_NONE,
 		TYPE_TCP,
 		TYPE_UDP,

+ 3 - 3
core/io/pck_packer.cpp

@@ -182,7 +182,7 @@ Error PCKPacker::flush(bool p_verbose) {
 	}
 
 	// write the index
-	file->store_32(files.size());
+	file->store_32(uint32_t(files.size()));
 
 	Ref<FileAccessEncrypted> fae;
 	Ref<FileAccess> fhead = file;
@@ -201,7 +201,7 @@ Error PCKPacker::flush(bool p_verbose) {
 		int string_len = files[i].path.utf8().length();
 		int pad = _get_pad(4, string_len);
 
-		fhead->store_32(string_len + pad);
+		fhead->store_32(uint32_t(string_len + pad));
 		fhead->store_buffer((const uint8_t *)files[i].path.utf8().get_data(), string_len);
 		for (int j = 0; j < pad; j++) {
 			fhead->store_8(0);
@@ -231,7 +231,7 @@ Error PCKPacker::flush(bool p_verbose) {
 		file->store_8(0);
 	}
 
-	int64_t file_base = file->get_position();
+	uint64_t file_base = file->get_position();
 	file->seek(file_base_ofs);
 	file->store_64(file_base); // update files base
 	file->seek(file_base);

+ 1 - 1
core/io/remote_filesystem_client.cpp

@@ -237,7 +237,7 @@ Error RemoteFilesystemClient::_synchronize_with_server(const String &p_host, int
 	tcp_client->poll();
 	ERR_FAIL_COND_V_MSG(tcp_client->get_status() != StreamPeerTCP::STATUS_CONNECTED, ERR_CONNECTION_ERROR, "Remote filesystem server disconnected after sending header.");
 
-	uint32_t file_count = tcp_client->get_32();
+	uint32_t file_count = tcp_client->get_u32();
 
 	ERR_FAIL_COND_V_MSG(tcp_client->get_status() != StreamPeerTCP::STATUS_CONNECTED, ERR_CONNECTION_ERROR, "Remote filesystem server disconnected while waiting for file list");
 

+ 45 - 45
core/io/resource_format_binary.cpp

@@ -911,7 +911,7 @@ void ResourceLoaderBinary::set_translation_remapped(bool p_remapped) {
 
 static void save_ustring(Ref<FileAccess> f, const String &p_string) {
 	CharString utf8 = p_string.utf8();
-	f->store_32(utf8.length() + 1);
+	f->store_32(uint32_t(utf8.length() + 1));
 	f->store_buffer((const uint8_t *)utf8.get_data(), utf8.length() + 1);
 }
 
@@ -1051,7 +1051,7 @@ void ResourceLoaderBinary::open(Ref<FileAccess> p_f, bool p_no_resources, bool p
 	f->real_is_double = (flags & ResourceFormatSaverBinaryInstance::FORMAT_FLAG_REAL_T_IS_DOUBLE) != 0;
 
 	if (using_uids) {
-		uid = f->get_64();
+		uid = ResourceUID::ID(f->get_64());
 	} else {
 		f->get_64(); // skip over uid field
 		uid = ResourceUID::INVALID_ID;
@@ -1084,7 +1084,7 @@ void ResourceLoaderBinary::open(Ref<FileAccess> p_f, bool p_no_resources, bool p
 		er.type = get_unicode_string();
 		er.path = get_unicode_string();
 		if (using_uids) {
-			er.uid = f->get_64();
+			er.uid = ResourceUID::ID(f->get_64());
 			if (!p_keep_uuid_paths && er.uid != ResourceUID::INVALID_ID) {
 				if (ResourceUID::get_singleton()->has_id(er.uid)) {
 					// If a UID is found and the path is valid, it will be used, otherwise, it falls back to the path.
@@ -1477,7 +1477,7 @@ Error ResourceFormatLoaderBinary::rename_dependencies(const String &p_path, cons
 
 		if (using_uids) {
 			ResourceUID::ID uid = ResourceSaver::get_resource_id_for_path(full_path);
-			fw->store_64(uid);
+			fw->store_64(uint64_t(uid));
 		}
 	}
 
@@ -1609,11 +1609,11 @@ void ResourceFormatSaverBinaryInstance::write_variant(Ref<FileAccess> f, const V
 			int64_t val = p_property;
 			if (val > 0x7FFFFFFF || val < -(int64_t)0x80000000) {
 				f->store_32(VARIANT_INT64);
-				f->store_64(val);
+				f->store_64(uint64_t(val));
 
 			} else {
 				f->store_32(VARIANT_INT);
-				f->store_32(int32_t(p_property));
+				f->store_32(uint32_t(p_property));
 			}
 
 		} break;
@@ -1645,8 +1645,8 @@ void ResourceFormatSaverBinaryInstance::write_variant(Ref<FileAccess> f, const V
 		case Variant::VECTOR2I: {
 			f->store_32(VARIANT_VECTOR2I);
 			Vector2i val = p_property;
-			f->store_32(val.x);
-			f->store_32(val.y);
+			f->store_32(uint32_t(val.x));
+			f->store_32(uint32_t(val.y));
 
 		} break;
 		case Variant::RECT2: {
@@ -1661,10 +1661,10 @@ void ResourceFormatSaverBinaryInstance::write_variant(Ref<FileAccess> f, const V
 		case Variant::RECT2I: {
 			f->store_32(VARIANT_RECT2I);
 			Rect2i val = p_property;
-			f->store_32(val.position.x);
-			f->store_32(val.position.y);
-			f->store_32(val.size.x);
-			f->store_32(val.size.y);
+			f->store_32(uint32_t(val.position.x));
+			f->store_32(uint32_t(val.position.y));
+			f->store_32(uint32_t(val.size.x));
+			f->store_32(uint32_t(val.size.y));
 
 		} break;
 		case Variant::VECTOR3: {
@@ -1678,9 +1678,9 @@ void ResourceFormatSaverBinaryInstance::write_variant(Ref<FileAccess> f, const V
 		case Variant::VECTOR3I: {
 			f->store_32(VARIANT_VECTOR3I);
 			Vector3i val = p_property;
-			f->store_32(val.x);
-			f->store_32(val.y);
-			f->store_32(val.z);
+			f->store_32(uint32_t(val.x));
+			f->store_32(uint32_t(val.y));
+			f->store_32(uint32_t(val.z));
 
 		} break;
 		case Variant::VECTOR4: {
@@ -1695,10 +1695,10 @@ void ResourceFormatSaverBinaryInstance::write_variant(Ref<FileAccess> f, const V
 		case Variant::VECTOR4I: {
 			f->store_32(VARIANT_VECTOR4I);
 			Vector4i val = p_property;
-			f->store_32(val.x);
-			f->store_32(val.y);
-			f->store_32(val.z);
-			f->store_32(val.w);
+			f->store_32(uint32_t(val.x));
+			f->store_32(uint32_t(val.y));
+			f->store_32(uint32_t(val.z));
+			f->store_32(uint32_t(val.w));
 
 		} break;
 		case Variant::PLANE: {
@@ -1821,14 +1821,14 @@ void ResourceFormatSaverBinaryInstance::write_variant(Ref<FileAccess> f, const V
 			f->store_16(snc);
 			for (int i = 0; i < np.get_name_count(); i++) {
 				if (string_map.has(np.get_name(i))) {
-					f->store_32(string_map[np.get_name(i)]);
+					f->store_32(uint32_t(string_map[np.get_name(i)]));
 				} else {
 					save_unicode_string(f, np.get_name(i), true);
 				}
 			}
 			for (int i = 0; i < np.get_subname_count(); i++) {
 				if (string_map.has(np.get_subname(i))) {
-					f->store_32(string_map[np.get_subname(i)]);
+					f->store_32(uint32_t(string_map[np.get_subname(i)]));
 				} else {
 					save_unicode_string(f, np.get_subname(i), true);
 				}
@@ -1839,7 +1839,7 @@ void ResourceFormatSaverBinaryInstance::write_variant(Ref<FileAccess> f, const V
 			f->store_32(VARIANT_RID);
 			WARN_PRINT("Can't save RIDs.");
 			RID val = p_property;
-			f->store_32(val.get_id());
+			f->store_32(uint32_t(val.get_id()));
 		} break;
 		case Variant::OBJECT: {
 			f->store_32(VARIANT_OBJECT);
@@ -1851,7 +1851,7 @@ void ResourceFormatSaverBinaryInstance::write_variant(Ref<FileAccess> f, const V
 
 			if (!res->is_built_in()) {
 				f->store_32(OBJECT_EXTERNAL_RESOURCE_INDEX);
-				f->store_32(external_resources[res]);
+				f->store_32(uint32_t(external_resources[res]));
 			} else {
 				if (!resource_map.has(res)) {
 					f->store_32(OBJECT_EMPTY);
@@ -1859,7 +1859,7 @@ void ResourceFormatSaverBinaryInstance::write_variant(Ref<FileAccess> f, const V
 				}
 
 				f->store_32(OBJECT_INTERNAL_RESOURCE);
-				f->store_32(resource_map[res]);
+				f->store_32(uint32_t(resource_map[res]));
 				//internal resource
 			}
 
@@ -1900,7 +1900,7 @@ void ResourceFormatSaverBinaryInstance::write_variant(Ref<FileAccess> f, const V
 			f->store_32(VARIANT_PACKED_BYTE_ARRAY);
 			Vector<uint8_t> arr = p_property;
 			int len = arr.size();
-			f->store_32(len);
+			f->store_32(uint32_t(len));
 			const uint8_t *r = arr.ptr();
 			f->store_buffer(r, len);
 			_pad_buffer(f, len);
@@ -1910,10 +1910,10 @@ void ResourceFormatSaverBinaryInstance::write_variant(Ref<FileAccess> f, const V
 			f->store_32(VARIANT_PACKED_INT32_ARRAY);
 			Vector<int32_t> arr = p_property;
 			int len = arr.size();
-			f->store_32(len);
+			f->store_32(uint32_t(len));
 			const int32_t *r = arr.ptr();
 			for (int i = 0; i < len; i++) {
-				f->store_32(r[i]);
+				f->store_32(uint32_t(r[i]));
 			}
 
 		} break;
@@ -1921,10 +1921,10 @@ void ResourceFormatSaverBinaryInstance::write_variant(Ref<FileAccess> f, const V
 			f->store_32(VARIANT_PACKED_INT64_ARRAY);
 			Vector<int64_t> arr = p_property;
 			int len = arr.size();
-			f->store_32(len);
+			f->store_32(uint32_t(len));
 			const int64_t *r = arr.ptr();
 			for (int i = 0; i < len; i++) {
-				f->store_64(r[i]);
+				f->store_64(uint64_t(r[i]));
 			}
 
 		} break;
@@ -1932,7 +1932,7 @@ void ResourceFormatSaverBinaryInstance::write_variant(Ref<FileAccess> f, const V
 			f->store_32(VARIANT_PACKED_FLOAT32_ARRAY);
 			Vector<float> arr = p_property;
 			int len = arr.size();
-			f->store_32(len);
+			f->store_32(uint32_t(len));
 			const float *r = arr.ptr();
 			for (int i = 0; i < len; i++) {
 				f->store_float(r[i]);
@@ -1943,7 +1943,7 @@ void ResourceFormatSaverBinaryInstance::write_variant(Ref<FileAccess> f, const V
 			f->store_32(VARIANT_PACKED_FLOAT64_ARRAY);
 			Vector<double> arr = p_property;
 			int len = arr.size();
-			f->store_32(len);
+			f->store_32(uint32_t(len));
 			const double *r = arr.ptr();
 			for (int i = 0; i < len; i++) {
 				f->store_double(r[i]);
@@ -1954,7 +1954,7 @@ void ResourceFormatSaverBinaryInstance::write_variant(Ref<FileAccess> f, const V
 			f->store_32(VARIANT_PACKED_STRING_ARRAY);
 			Vector<String> arr = p_property;
 			int len = arr.size();
-			f->store_32(len);
+			f->store_32(uint32_t(len));
 			const String *r = arr.ptr();
 			for (int i = 0; i < len; i++) {
 				save_unicode_string(f, r[i]);
@@ -1965,7 +1965,7 @@ void ResourceFormatSaverBinaryInstance::write_variant(Ref<FileAccess> f, const V
 			f->store_32(VARIANT_PACKED_VECTOR2_ARRAY);
 			Vector<Vector2> arr = p_property;
 			int len = arr.size();
-			f->store_32(len);
+			f->store_32(uint32_t(len));
 			const Vector2 *r = arr.ptr();
 			for (int i = 0; i < len; i++) {
 				f->store_real(r[i].x);
@@ -1977,7 +1977,7 @@ void ResourceFormatSaverBinaryInstance::write_variant(Ref<FileAccess> f, const V
 			f->store_32(VARIANT_PACKED_VECTOR3_ARRAY);
 			Vector<Vector3> arr = p_property;
 			int len = arr.size();
-			f->store_32(len);
+			f->store_32(uint32_t(len));
 			const Vector3 *r = arr.ptr();
 			for (int i = 0; i < len; i++) {
 				f->store_real(r[i].x);
@@ -1990,7 +1990,7 @@ void ResourceFormatSaverBinaryInstance::write_variant(Ref<FileAccess> f, const V
 			f->store_32(VARIANT_PACKED_COLOR_ARRAY);
 			Vector<Color> arr = p_property;
 			int len = arr.size();
-			f->store_32(len);
+			f->store_32(uint32_t(len));
 			const Color *r = arr.ptr();
 			for (int i = 0; i < len; i++) {
 				f->store_float(r[i].r);
@@ -2004,7 +2004,7 @@ void ResourceFormatSaverBinaryInstance::write_variant(Ref<FileAccess> f, const V
 			f->store_32(VARIANT_PACKED_VECTOR4_ARRAY);
 			Vector<Vector4> arr = p_property;
 			int len = arr.size();
-			f->store_32(len);
+			f->store_32(uint32_t(len));
 			const Vector4 *r = arr.ptr();
 			for (int i = 0; i < len; i++) {
 				f->store_real(r[i].x);
@@ -2115,9 +2115,9 @@ void ResourceFormatSaverBinaryInstance::_find_resources(const Variant &p_variant
 void ResourceFormatSaverBinaryInstance::save_unicode_string(Ref<FileAccess> p_f, const String &p_string, bool p_bit_on_len) {
 	CharString utf8 = p_string.utf8();
 	if (p_bit_on_len) {
-		p_f->store_32((utf8.length() + 1) | 0x80000000);
+		p_f->store_32(uint32_t((utf8.length() + 1) | 0x80000000));
 	} else {
-		p_f->store_32(utf8.length() + 1);
+		p_f->store_32(uint32_t(utf8.length() + 1));
 	}
 	p_f->store_buffer((const uint8_t *)utf8.get_data(), utf8.length() + 1);
 }
@@ -2218,7 +2218,7 @@ Error ResourceFormatSaverBinaryInstance::save(const String &p_path, const Ref<Re
 		f->store_32(format_flags);
 	}
 	ResourceUID::ID uid = ResourceSaver::get_resource_id_for_path(p_path, true);
-	f->store_64(uid);
+	f->store_64(uint64_t(uid));
 	if (!script_class.is_empty()) {
 		save_unicode_string(f, script_class);
 	}
@@ -2284,7 +2284,7 @@ Error ResourceFormatSaverBinaryInstance::save(const String &p_path, const Ref<Re
 		}
 	}
 
-	f->store_32(strings.size()); //string table size
+	f->store_32(uint32_t(strings.size())); //string table size
 	for (int i = 0; i < strings.size(); i++) {
 		save_unicode_string(f, strings[i]);
 	}
@@ -2304,10 +2304,10 @@ Error ResourceFormatSaverBinaryInstance::save(const String &p_path, const Ref<Re
 		res_path = relative_paths ? local_path.path_to_file(res_path) : res_path;
 		save_unicode_string(f, res_path);
 		ResourceUID::ID ruid = ResourceSaver::get_resource_id_for_path(save_order[i]->get_path(), false);
-		f->store_64(ruid);
+		f->store_64(uint64_t(ruid));
 	}
 	// save internal resource table
-	f->store_32(saved_resources.size()); //amount of internal resources
+	f->store_32(uint32_t(saved_resources.size())); //amount of internal resources
 	Vector<uint64_t> ofs_pos;
 	HashSet<String> used_unique_ids;
 
@@ -2362,10 +2362,10 @@ Error ResourceFormatSaverBinaryInstance::save(const String &p_path, const Ref<Re
 	for (const ResourceData &rd : resources) {
 		ofs_table.push_back(f->get_position());
 		save_unicode_string(f, rd.type);
-		f->store_32(rd.properties.size());
+		f->store_32(uint32_t(rd.properties.size()));
 
 		for (const Property &p : rd.properties) {
-			f->store_32(p.name_idx);
+			f->store_32(uint32_t(p.name_idx));
 			write_variant(f, p.value, resource_map, external_resources, string_map, p.pi);
 		}
 	}
@@ -2473,7 +2473,7 @@ Error ResourceFormatSaverBinaryInstance::set_uid(const String &p_path, ResourceU
 	f->get_64(); // Skip previous UID
 
 	fw->store_32(flags);
-	fw->store_64(p_uid);
+	fw->store_64(uint64_t(p_uid));
 
 	if (flags & ResourceFormatSaverBinaryInstance::FORMAT_FLAG_HAS_SCRIPT_CLASS) {
 		save_ustring(fw, get_ustring(f));

+ 1 - 1
core/io/resource_importer.h

@@ -46,7 +46,7 @@ class ResourceFormatImporter : public ResourceFormatLoader {
 		String importer;
 		String group_file;
 		Variant metadata;
-		uint64_t uid = ResourceUID::INVALID_ID;
+		ResourceUID::ID uid = ResourceUID::INVALID_ID;
 	};
 
 	Error _get_path_and_type(const String &p_path, PathAndType &r_path_and_type, bool *r_valid = nullptr) const;

+ 2 - 2
core/io/resource_uid.cpp

@@ -178,7 +178,7 @@ Error ResourceUID::save_to_cache() {
 	cache_entries = 0;
 
 	for (KeyValue<ID, Cache> &E : unique_ids) {
-		f->store_64(E.key);
+		f->store_64(uint64_t(E.key));
 		uint32_t s = E.value.cs.length();
 		f->store_32(s);
 		f->store_buffer((const uint8_t *)E.value.cs.ptr(), s);
@@ -241,7 +241,7 @@ Error ResourceUID::update_cache() {
 				}
 				f->seek_end();
 			}
-			f->store_64(E.key);
+			f->store_64(uint64_t(E.key));
 			uint32_t s = E.value.cs.length();
 			f->store_32(s);
 			f->store_buffer((const uint8_t *)E.value.cs.ptr(), s);

+ 1 - 3
core/io/resource_uid.h

@@ -39,9 +39,7 @@ class ResourceUID : public Object {
 	GDCLASS(ResourceUID, Object)
 public:
 	typedef int64_t ID;
-	enum {
-		INVALID_ID = -1
-	};
+	constexpr const static ID INVALID_ID = -1;
 
 	static String get_cache_file();
 

+ 27 - 8
core/io/stream_peer.cpp

@@ -204,11 +204,13 @@ void StreamPeer::put_float(float p_val) {
 
 void StreamPeer::put_double(double p_val) {
 	uint8_t buf[8];
+
 	encode_double(p_val, buf);
 	if (big_endian) {
 		uint64_t *p64 = (uint64_t *)buf;
 		*p64 = BSWAP64(*p64);
 	}
+
 	put_data(buf, 8);
 }
 
@@ -243,75 +245,87 @@ uint8_t StreamPeer::get_u8() {
 int8_t StreamPeer::get_8() {
 	uint8_t buf[1] = {};
 	get_data(buf, 1);
-	return buf[0];
+	return int8_t(buf[0]);
 }
 
 uint16_t StreamPeer::get_u16() {
 	uint8_t buf[2];
 	get_data(buf, 2);
+
 	uint16_t r = decode_uint16(buf);
 	if (big_endian) {
 		r = BSWAP16(r);
 	}
+
 	return r;
 }
 
 int16_t StreamPeer::get_16() {
 	uint8_t buf[2];
 	get_data(buf, 2);
+
 	uint16_t r = decode_uint16(buf);
 	if (big_endian) {
 		r = BSWAP16(r);
 	}
-	return r;
+
+	return int16_t(r);
 }
 
 uint32_t StreamPeer::get_u32() {
 	uint8_t buf[4];
 	get_data(buf, 4);
+
 	uint32_t r = decode_uint32(buf);
 	if (big_endian) {
 		r = BSWAP32(r);
 	}
+
 	return r;
 }
 
 int32_t StreamPeer::get_32() {
 	uint8_t buf[4];
 	get_data(buf, 4);
+
 	uint32_t r = decode_uint32(buf);
 	if (big_endian) {
 		r = BSWAP32(r);
 	}
-	return r;
+
+	return int32_t(r);
 }
 
 uint64_t StreamPeer::get_u64() {
 	uint8_t buf[8];
 	get_data(buf, 8);
+
 	uint64_t r = decode_uint64(buf);
 	if (big_endian) {
 		r = BSWAP64(r);
 	}
+
 	return r;
 }
 
 int64_t StreamPeer::get_64() {
 	uint8_t buf[8];
 	get_data(buf, 8);
+
 	uint64_t r = decode_uint64(buf);
 	if (big_endian) {
 		r = BSWAP64(r);
 	}
-	return r;
+
+	return int64_t(r);
 }
 
 float StreamPeer::get_half() {
 	uint8_t buf[2];
 	get_data(buf, 2);
 
-	uint16_t *p16 = (uint16_t *)buf;
 	if (big_endian) {
+		uint16_t *p16 = (uint16_t *)buf;
 		*p16 = BSWAP16(*p16);
 	}
 
@@ -344,7 +358,7 @@ double StreamPeer::get_double() {
 
 String StreamPeer::get_string(int p_bytes) {
 	if (p_bytes < 0) {
-		p_bytes = get_u32();
+		p_bytes = get_32();
 	}
 	ERR_FAIL_COND_V(p_bytes < 0, String());
 
@@ -359,7 +373,7 @@ String StreamPeer::get_string(int p_bytes) {
 
 String StreamPeer::get_utf8_string(int p_bytes) {
 	if (p_bytes < 0) {
-		p_bytes = get_u32();
+		p_bytes = get_32();
 	}
 	ERR_FAIL_COND_V(p_bytes < 0, String());
 
@@ -498,7 +512,7 @@ void StreamPeerBuffer::_bind_methods() {
 }
 
 Error StreamPeerBuffer::put_data(const uint8_t *p_data, int p_bytes) {
-	if (p_bytes <= 0) {
+	if (p_bytes <= 0 || !p_data) {
 		return OK;
 	}
 
@@ -529,6 +543,11 @@ Error StreamPeerBuffer::get_data(uint8_t *p_buffer, int p_bytes) {
 }
 
 Error StreamPeerBuffer::get_partial_data(uint8_t *p_buffer, int p_bytes, int &r_received) {
+	if (!p_bytes) {
+		r_received = 0;
+		return OK;
+	}
+
 	if (pointer + p_bytes > data.size()) {
 		r_received = data.size() - pointer;
 		if (r_received <= 0) {

+ 1 - 1
core/math/bvh_pair.inc

@@ -37,7 +37,7 @@ struct ItemPairs {
 				return n;
 			}
 		}
-		return -1;
+		return uint32_t(-1);
 	}
 
 	bool contains_pair_to(BVHHandle h) const {

+ 1 - 3
core/math/bvh_tree.h

@@ -109,9 +109,7 @@ struct BVHHandle {
 template <typename T>
 class BVH_IterativeInfo {
 public:
-	enum {
-		ALLOCA_STACK_SIZE = 128
-	};
+	constexpr static const size_t ALLOCA_STACK_SIZE = 128;
 
 	int32_t depth = 1;
 	int32_t threshold = ALLOCA_STACK_SIZE - 2;

+ 2 - 2
core/math/color.h

@@ -147,7 +147,7 @@ struct [[nodiscard]] Color {
 		// of the mantissa, rounding the truncated bits.
 		union {
 			float f;
-			int32_t i;
+			uint32_t i;
 		} R, G, B, E;
 
 		E.f = MaxChannel;
@@ -168,7 +168,7 @@ struct [[nodiscard]] Color {
 		// Combine the fields. RGB floats have unwanted data in the upper 9
 		// bits. Only red needs to mask them off because green and blue shift
 		// it out to the left.
-		return E.i | (B.i << 18) | (G.i << 9) | (R.i & 511);
+		return E.i | (B.i << 18U) | (G.i << 9U) | (R.i & 511U);
 	}
 
 	_FORCE_INLINE_ Color blend(const Color &p_over) const {

+ 1 - 1
core/math/random_pcg.cpp

@@ -80,5 +80,5 @@ int RandomPCG::random(int p_from, int p_to) {
 	if (p_from == p_to) {
 		return p_from;
 	}
-	return rand(abs(p_from - p_to) + 1) + MIN(p_from, p_to);
+	return int(rand(uint32_t(Math::abs(p_from - p_to)) + 1U)) + MIN(p_from, p_to);
 }

+ 1 - 1
core/object/class_db.cpp

@@ -454,7 +454,7 @@ uint32_t ClassDB::get_api_hash(APIType p_api) {
 
 			for (const StringName &F : snames) {
 				hash = hash_murmur3_one_64(F.hash(), hash);
-				hash = hash_murmur3_one_64(t->constant_map[F], hash);
+				hash = hash_murmur3_one_64(uint64_t(t->constant_map[F]), hash);
 			}
 		}
 

+ 1 - 1
core/object/object_id.h

@@ -46,7 +46,7 @@ public:
 	_ALWAYS_INLINE_ bool is_valid() const { return id != 0; }
 	_ALWAYS_INLINE_ bool is_null() const { return id == 0; }
 	_ALWAYS_INLINE_ operator uint64_t() const { return id; }
-	_ALWAYS_INLINE_ operator int64_t() const { return id; }
+	_ALWAYS_INLINE_ operator int64_t() const { return (int64_t)id; }
 
 	_ALWAYS_INLINE_ bool operator==(const ObjectID &p_id) const { return id == p_id.id; }
 	_ALWAYS_INLINE_ bool operator!=(const ObjectID &p_id) const { return id != p_id.id; }

+ 3 - 1
core/os/memory.cpp

@@ -87,7 +87,9 @@ void *Memory::realloc_aligned_static(void *p_memory, size_t p_bytes, size_t p_pr
 	}
 
 	void *ret = alloc_aligned_static(p_bytes, p_alignment);
-	memcpy(ret, p_memory, p_prev_bytes);
+	if (ret) {
+		memcpy(ret, p_memory, p_prev_bytes);
+	}
 	free_aligned_static(p_memory);
 	return ret;
 }

+ 1 - 1
core/string/string_buffer.h

@@ -118,7 +118,7 @@ StringBuffer<SHORT_BUFFER_SIZE> &StringBuffer<SHORT_BUFFER_SIZE>::append(const c
 
 template <int SHORT_BUFFER_SIZE>
 StringBuffer<SHORT_BUFFER_SIZE> &StringBuffer<SHORT_BUFFER_SIZE>::reserve(int p_size) {
-	if (p_size < SHORT_BUFFER_SIZE || p_size < buffer.size()) {
+	if (p_size < SHORT_BUFFER_SIZE || p_size < buffer.size() || !p_size) {
 		return *this;
 	}
 

+ 57 - 72
core/string/ustring.cpp

@@ -42,10 +42,6 @@
 #include "core/variant/variant.h"
 #include "core/version_generated.gen.h"
 
-#include <stdio.h>
-#include <stdlib.h>
-#include <cstdint>
-
 #ifdef _MSC_VER
 #define _CRT_SECURE_NO_WARNINGS // to disable build-time warning which suggested to use strcpy_s instead strcpy
 #endif
@@ -1804,6 +1800,10 @@ String String::num_uint64(uint64_t p_num, int base, bool capitalize_hex) {
 }
 
 String String::num_real(double p_num, bool p_trailing) {
+	if (Math::is_nan(p_num) || Math::is_inf(p_num)) {
+		return num(p_num, 0);
+	}
+
 	if (p_num == (double)(int64_t)p_num) {
 		if (p_trailing) {
 			return num_int64((int64_t)p_num) + ".0";
@@ -1811,6 +1811,7 @@ String String::num_real(double p_num, bool p_trailing) {
 			return num_int64((int64_t)p_num);
 		}
 	}
+
 	int decimals = 14;
 	// We want to align the digits to the above sane default, so we only need
 	// to subtract log10 for numbers with a positive power of ten magnitude.
@@ -1818,10 +1819,15 @@ String String::num_real(double p_num, bool p_trailing) {
 	if (abs_num > 10) {
 		decimals -= (int)floor(log10(abs_num));
 	}
+
 	return num(p_num, decimals);
 }
 
 String String::num_real(float p_num, bool p_trailing) {
+	if (Math::is_nan(p_num) || Math::is_inf(p_num)) {
+		return num(p_num, 0);
+	}
+
 	if (p_num == (float)(int64_t)p_num) {
 		if (p_trailing) {
 			return num_int64((int64_t)p_num) + ".0";
@@ -1840,16 +1846,8 @@ String String::num_real(float p_num, bool p_trailing) {
 }
 
 String String::num_scientific(double p_num) {
-	if (Math::is_nan(p_num)) {
-		return "nan";
-	}
-
-	if (Math::is_inf(p_num)) {
-		if (signbit(p_num)) {
-			return "-inf";
-		} else {
-			return "inf";
-		}
+	if (Math::is_nan(p_num) || Math::is_inf(p_num)) {
+		return num(p_num, 0);
 	}
 
 	char buf[256];
@@ -1947,7 +1945,7 @@ CharString String::ascii(bool p_allow_extended) const {
 	for (int i = 0; i < size(); i++) {
 		char32_t c = this_ptr[i];
 		if ((c <= 0x7f) || (c <= 0xff && p_allow_extended)) {
-			cs_ptrw[i] = c;
+			cs_ptrw[i] = char(c);
 		} else {
 			print_unicode_error(vformat("Invalid unicode codepoint (%x), cannot represent as ASCII/Latin-1", (uint32_t)c));
 			cs_ptrw[i] = 0x20; // ASCII doesn't have a replacement character like unicode, 0x1a is sometimes used but is kinda arcane.
@@ -2487,30 +2485,50 @@ int64_t String::bin_to_int() const {
 	return binary * sign;
 }
 
-int64_t String::to_int() const {
-	if (length() == 0) {
-		return 0;
-	}
-
-	int to = (find_char('.') >= 0) ? find_char('.') : length();
-
-	int64_t integer = 0;
-	int64_t sign = 1;
+template <typename C, typename T>
+_ALWAYS_INLINE_ int64_t _to_int(const T &p_in, int to) {
+	// Accumulate the total number in an unsigned integer as the range is:
+	// +9223372036854775807 to -9223372036854775808 and the smallest negative
+	// number does not fit inside an int64_t. So we accumulate the positive
+	// number in an unsigned, and then at the very end convert to its signed
+	// form.
+	uint64_t integer = 0;
+	uint8_t digits = 0;
+	bool positive = true;
 
 	for (int i = 0; i < to; i++) {
-		char32_t c = operator[](i);
+		C c = p_in[i];
 		if (is_digit(c)) {
-			bool overflow = (integer > INT64_MAX / 10) || (integer == INT64_MAX / 10 && ((sign == 1 && c > '7') || (sign == -1 && c > '8')));
-			ERR_FAIL_COND_V_MSG(overflow, sign == 1 ? INT64_MAX : INT64_MIN, "Cannot represent " + *this + " as a 64-bit signed integer, since the value is " + (sign == 1 ? "too large." : "too small."));
+			// No need to do expensive checks unless we're approaching INT64_MAX / INT64_MIN.
+			if (unlikely(digits > 18)) {
+				bool overflow = (integer > INT64_MAX / 10) || (integer == INT64_MAX / 10 && ((positive && c > '7') || (!positive && c > '8')));
+				ERR_FAIL_COND_V_MSG(overflow, positive ? INT64_MAX : INT64_MIN, "Cannot represent " + String(p_in) + " as a 64-bit signed integer, since the value is " + (positive ? "too large." : "too small."));
+			}
+
 			integer *= 10;
 			integer += c - '0';
+			++digits;
 
 		} else if (integer == 0 && c == '-') {
-			sign = -sign;
+			positive = !positive;
 		}
 	}
 
-	return integer * sign;
+	if (positive) {
+		return int64_t(integer);
+	} else {
+		return int64_t(integer * uint64_t(-1));
+	}
+}
+
+int64_t String::to_int() const {
+	if (length() == 0) {
+		return 0;
+	}
+
+	int to = (find_char('.') >= 0) ? find_char('.') : length();
+
+	return _to_int<char32_t>(*this, to);
 }
 
 int64_t String::to_int(const char *p_str, int p_len) {
@@ -2523,25 +2541,7 @@ int64_t String::to_int(const char *p_str, int p_len) {
 		}
 	}
 
-	int64_t integer = 0;
-	int64_t sign = 1;
-
-	for (int i = 0; i < to; i++) {
-		char c = p_str[i];
-		if (is_digit(c)) {
-			bool overflow = (integer > INT64_MAX / 10) || (integer == INT64_MAX / 10 && ((sign == 1 && c > '7') || (sign == -1 && c > '8')));
-			ERR_FAIL_COND_V_MSG(overflow, sign == 1 ? INT64_MAX : INT64_MIN, "Cannot represent " + String(p_str).substr(0, to) + " as a 64-bit signed integer, since the value is " + (sign == 1 ? "too large." : "too small."));
-			integer *= 10;
-			integer += c - '0';
-
-		} else if (c == '-' && integer == 0) {
-			sign = -sign;
-		} else if (c != ' ') {
-			break;
-		}
-	}
-
-	return integer * sign;
+	return _to_int<char>(p_str, to);
 }
 
 int64_t String::to_int(const wchar_t *p_str, int p_len) {
@@ -2554,25 +2554,7 @@ int64_t String::to_int(const wchar_t *p_str, int p_len) {
 		}
 	}
 
-	int64_t integer = 0;
-	int64_t sign = 1;
-
-	for (int i = 0; i < to; i++) {
-		wchar_t c = p_str[i];
-		if (is_digit(c)) {
-			bool overflow = (integer > INT64_MAX / 10) || (integer == INT64_MAX / 10 && ((sign == 1 && c > '7') || (sign == -1 && c > '8')));
-			ERR_FAIL_COND_V_MSG(overflow, sign == 1 ? INT64_MAX : INT64_MIN, "Cannot represent " + String(p_str).substr(0, to) + " as a 64-bit signed integer, since the value is " + (sign == 1 ? "too large." : "too small."));
-			integer *= 10;
-			integer += c - '0';
-
-		} else if (c == '-' && integer == 0) {
-			sign = -sign;
-		} else if (c != ' ') {
-			break;
-		}
-	}
-
-	return integer * sign;
+	return _to_int<wchar_t>(p_str, to);
 }
 
 bool String::is_numeric() const {
@@ -3969,7 +3951,7 @@ static String _replace_common(const String &p_this, const String &p_key, const S
 		return p_this;
 	}
 
-	const int key_length = p_key.length();
+	const size_t key_length = p_key.length();
 
 	int search_from = 0;
 	int result = 0;
@@ -3978,6 +3960,7 @@ static String _replace_common(const String &p_this, const String &p_key, const S
 
 	while ((result = (p_case_insensitive ? p_this.findn(p_key, search_from) : p_this.find(p_key, search_from))) >= 0) {
 		found.push_back(result);
+		ERR_FAIL_COND_V_MSG((result + key_length) > INT32_MAX, p_this, "Key length too long");
 		search_from = result + key_length;
 	}
 
@@ -3990,7 +3973,7 @@ static String _replace_common(const String &p_this, const String &p_key, const S
 	const int with_length = p_with.length();
 	const int old_length = p_this.length();
 
-	new_string.resize(old_length + found.size() * (with_length - key_length) + 1);
+	new_string.resize(old_length + int(found.size()) * (with_length - key_length) + 1);
 
 	char32_t *new_ptrw = new_string.ptrw();
 	const char32_t *old_ptr = p_this.ptr();
@@ -4021,7 +4004,7 @@ static String _replace_common(const String &p_this, const String &p_key, const S
 }
 
 static String _replace_common(const String &p_this, char const *p_key, char const *p_with, bool p_case_insensitive) {
-	int key_length = strlen(p_key);
+	size_t key_length = strlen(p_key);
 
 	if (key_length == 0 || p_this.is_empty()) {
 		return p_this;
@@ -4034,6 +4017,7 @@ static String _replace_common(const String &p_this, char const *p_key, char cons
 
 	while ((result = (p_case_insensitive ? p_this.findn(p_key, search_from) : p_this.find(p_key, search_from))) >= 0) {
 		found.push_back(result);
+		ERR_FAIL_COND_V_MSG((result + key_length) > INT32_MAX, p_this, "Key length too long");
 		search_from = result + key_length;
 	}
 
@@ -4048,7 +4032,7 @@ static String _replace_common(const String &p_this, char const *p_key, char cons
 	const int with_length = with_string.length();
 	const int old_length = p_this.length();
 
-	new_string.resize(old_length + found.size() * (with_length - key_length) + 1);
+	new_string.resize(old_length + int(found.size()) * (with_length - key_length) + 1);
 
 	char32_t *new_ptrw = new_string.ptrw();
 	const char32_t *old_ptr = p_this.ptr();
@@ -4639,8 +4623,9 @@ bool String::is_valid_string() const {
 String String::uri_encode() const {
 	const CharString temp = utf8();
 	String res;
+
 	for (int i = 0; i < temp.length(); ++i) {
-		uint8_t ord = temp[i];
+		uint8_t ord = uint8_t(temp[i]);
 		if (ord == '.' || ord == '-' || ord == '~' || is_ascii_identifier_char(ord)) {
 			res += ord;
 		} else {

+ 3 - 1
core/templates/command_queue_mt.h

@@ -339,7 +339,9 @@ class CommandQueueMT {
 	template <typename T>
 	T *allocate() {
 		// alloc size is size+T+safeguard
-		uint32_t alloc_size = ((sizeof(T) + 8 - 1) & ~(8 - 1));
+		static_assert(sizeof(T) < UINT32_MAX, "Type too large to fit in the command queue.");
+
+		uint32_t alloc_size = ((sizeof(T) + 8U - 1U) & ~(8U - 1U));
 		uint64_t size = command_mem.size();
 		command_mem.resize(size + alloc_size + 8);
 		*(uint64_t *)&command_mem[size] = alloc_size;

+ 22 - 22
core/templates/hashfuncs.h

@@ -323,9 +323,9 @@ struct HashMapHasherDefault {
 
 	static _FORCE_INLINE_ uint32_t hash(const String &p_string) { return p_string.hash(); }
 	static _FORCE_INLINE_ uint32_t hash(const char *p_cstr) { return hash_djb2(p_cstr); }
-	static _FORCE_INLINE_ uint32_t hash(const wchar_t p_wchar) { return hash_fmix32(p_wchar); }
-	static _FORCE_INLINE_ uint32_t hash(const char16_t p_uchar) { return hash_fmix32(p_uchar); }
-	static _FORCE_INLINE_ uint32_t hash(const char32_t p_uchar) { return hash_fmix32(p_uchar); }
+	static _FORCE_INLINE_ uint32_t hash(const wchar_t p_wchar) { return hash_fmix32(uint32_t(p_wchar)); }
+	static _FORCE_INLINE_ uint32_t hash(const char16_t p_uchar) { return hash_fmix32(uint32_t(p_uchar)); }
+	static _FORCE_INLINE_ uint32_t hash(const char32_t p_uchar) { return hash_fmix32(uint32_t(p_uchar)); }
 	static _FORCE_INLINE_ uint32_t hash(const RID &p_rid) { return hash_one_uint64(p_rid.get_id()); }
 	static _FORCE_INLINE_ uint32_t hash(const CharString &p_char_string) { return hash_djb2(p_char_string.get_data()); }
 	static _FORCE_INLINE_ uint32_t hash(const StringName &p_string_name) { return p_string_name.hash(); }
@@ -333,31 +333,31 @@ struct HashMapHasherDefault {
 	static _FORCE_INLINE_ uint32_t hash(const ObjectID &p_id) { return hash_one_uint64(p_id); }
 
 	static _FORCE_INLINE_ uint32_t hash(const uint64_t p_int) { return hash_one_uint64(p_int); }
-	static _FORCE_INLINE_ uint32_t hash(const int64_t p_int) { return hash_one_uint64(p_int); }
+	static _FORCE_INLINE_ uint32_t hash(const int64_t p_int) { return hash_one_uint64(uint64_t(p_int)); }
 	static _FORCE_INLINE_ uint32_t hash(const float p_float) { return hash_murmur3_one_float(p_float); }
 	static _FORCE_INLINE_ uint32_t hash(const double p_double) { return hash_murmur3_one_double(p_double); }
 	static _FORCE_INLINE_ uint32_t hash(const uint32_t p_int) { return hash_fmix32(p_int); }
-	static _FORCE_INLINE_ uint32_t hash(const int32_t p_int) { return hash_fmix32(p_int); }
-	static _FORCE_INLINE_ uint32_t hash(const uint16_t p_int) { return hash_fmix32(p_int); }
-	static _FORCE_INLINE_ uint32_t hash(const int16_t p_int) { return hash_fmix32(p_int); }
-	static _FORCE_INLINE_ uint32_t hash(const uint8_t p_int) { return hash_fmix32(p_int); }
-	static _FORCE_INLINE_ uint32_t hash(const int8_t p_int) { return hash_fmix32(p_int); }
+	static _FORCE_INLINE_ uint32_t hash(const int32_t p_int) { return hash_fmix32(uint32_t(p_int)); }
+	static _FORCE_INLINE_ uint32_t hash(const uint16_t p_int) { return hash_fmix32(uint32_t(p_int)); }
+	static _FORCE_INLINE_ uint32_t hash(const int16_t p_int) { return hash_fmix32(uint32_t(p_int)); }
+	static _FORCE_INLINE_ uint32_t hash(const uint8_t p_int) { return hash_fmix32(uint32_t(p_int)); }
+	static _FORCE_INLINE_ uint32_t hash(const int8_t p_int) { return hash_fmix32(uint32_t(p_int)); }
 	static _FORCE_INLINE_ uint32_t hash(const Vector2i &p_vec) {
-		uint32_t h = hash_murmur3_one_32(p_vec.x);
-		h = hash_murmur3_one_32(p_vec.y, h);
+		uint32_t h = hash_murmur3_one_32(uint32_t(p_vec.x));
+		h = hash_murmur3_one_32(uint32_t(p_vec.y), h);
 		return hash_fmix32(h);
 	}
 	static _FORCE_INLINE_ uint32_t hash(const Vector3i &p_vec) {
-		uint32_t h = hash_murmur3_one_32(p_vec.x);
-		h = hash_murmur3_one_32(p_vec.y, h);
-		h = hash_murmur3_one_32(p_vec.z, h);
+		uint32_t h = hash_murmur3_one_32(uint32_t(p_vec.x));
+		h = hash_murmur3_one_32(uint32_t(p_vec.y), h);
+		h = hash_murmur3_one_32(uint32_t(p_vec.z), h);
 		return hash_fmix32(h);
 	}
 	static _FORCE_INLINE_ uint32_t hash(const Vector4i &p_vec) {
-		uint32_t h = hash_murmur3_one_32(p_vec.x);
-		h = hash_murmur3_one_32(p_vec.y, h);
-		h = hash_murmur3_one_32(p_vec.z, h);
-		h = hash_murmur3_one_32(p_vec.w, h);
+		uint32_t h = hash_murmur3_one_32(uint32_t(p_vec.x));
+		h = hash_murmur3_one_32(uint32_t(p_vec.y), h);
+		h = hash_murmur3_one_32(uint32_t(p_vec.z), h);
+		h = hash_murmur3_one_32(uint32_t(p_vec.w), h);
 		return hash_fmix32(h);
 	}
 	static _FORCE_INLINE_ uint32_t hash(const Vector2 &p_vec) {
@@ -379,10 +379,10 @@ struct HashMapHasherDefault {
 		return hash_fmix32(h);
 	}
 	static _FORCE_INLINE_ uint32_t hash(const Rect2i &p_rect) {
-		uint32_t h = hash_murmur3_one_32(p_rect.position.x);
-		h = hash_murmur3_one_32(p_rect.position.y, h);
-		h = hash_murmur3_one_32(p_rect.size.x, h);
-		h = hash_murmur3_one_32(p_rect.size.y, h);
+		uint32_t h = hash_murmur3_one_32(uint32_t(p_rect.position.x));
+		h = hash_murmur3_one_32(uint32_t(p_rect.position.y), h);
+		h = hash_murmur3_one_32(uint32_t(p_rect.size.x), h);
+		h = hash_murmur3_one_32(uint32_t(p_rect.size.y), h);
 		return hash_fmix32(h);
 	}
 	static _FORCE_INLINE_ uint32_t hash(const Rect2 &p_rect) {

+ 6 - 2
core/templates/local_vector.h

@@ -297,7 +297,9 @@ public:
 		Vector<T> ret;
 		ret.resize(size());
 		T *w = ret.ptrw();
-		memcpy(w, data, sizeof(T) * count);
+		if (w) {
+			memcpy(w, data, sizeof(T) * count);
+		}
 		return ret;
 	}
 
@@ -305,7 +307,9 @@ public:
 		Vector<uint8_t> ret;
 		ret.resize(count * sizeof(T));
 		uint8_t *w = ret.ptrw();
-		memcpy(w, data, sizeof(T) * count);
+		if (w) {
+			memcpy(w, data, sizeof(T) * count);
+		}
 		return ret;
 	}
 

+ 5 - 2
core/templates/vector.h

@@ -156,8 +156,11 @@ public:
 		if (is_empty()) {
 			return ret;
 		}
-		ret.resize(size() * sizeof(T));
-		memcpy(ret.ptrw(), ptr(), sizeof(T) * size());
+		size_t alloc_size = size() * sizeof(T);
+		ret.resize(alloc_size);
+		if (alloc_size) {
+			memcpy(ret.ptrw(), ptr(), alloc_size);
+		}
 		return ret;
 	}
 

+ 29 - 29
core/variant/variant.cpp

@@ -1494,11 +1494,11 @@ Variant::operator int64_t() const {
 		case BOOL:
 			return _data._bool ? 1 : 0;
 		case INT:
-			return _data._int;
+			return int64_t(_data._int);
 		case FLOAT:
-			return _data._float;
+			return int64_t(_data._float);
 		case STRING:
-			return operator String().to_int();
+			return int64_t(operator String().to_int());
 		default: {
 			return 0;
 		}
@@ -1512,11 +1512,11 @@ Variant::operator int32_t() const {
 		case BOOL:
 			return _data._bool ? 1 : 0;
 		case INT:
-			return _data._int;
+			return int32_t(_data._int);
 		case FLOAT:
-			return _data._float;
+			return int32_t(_data._float);
 		case STRING:
-			return operator String().to_int();
+			return int32_t(operator String().to_int());
 		default: {
 			return 0;
 		}
@@ -1530,11 +1530,11 @@ Variant::operator int16_t() const {
 		case BOOL:
 			return _data._bool ? 1 : 0;
 		case INT:
-			return _data._int;
+			return int16_t(_data._int);
 		case FLOAT:
-			return _data._float;
+			return int16_t(_data._float);
 		case STRING:
-			return operator String().to_int();
+			return int16_t(operator String().to_int());
 		default: {
 			return 0;
 		}
@@ -1548,11 +1548,11 @@ Variant::operator int8_t() const {
 		case BOOL:
 			return _data._bool ? 1 : 0;
 		case INT:
-			return _data._int;
+			return int8_t(_data._int);
 		case FLOAT:
-			return _data._float;
+			return int8_t(_data._float);
 		case STRING:
-			return operator String().to_int();
+			return int8_t(operator String().to_int());
 		default: {
 			return 0;
 		}
@@ -1566,11 +1566,11 @@ Variant::operator uint64_t() const {
 		case BOOL:
 			return _data._bool ? 1 : 0;
 		case INT:
-			return _data._int;
+			return uint64_t(_data._int);
 		case FLOAT:
-			return _data._float;
+			return uint64_t(_data._float);
 		case STRING:
-			return operator String().to_int();
+			return uint64_t(operator String().to_int());
 		default: {
 			return 0;
 		}
@@ -1584,11 +1584,11 @@ Variant::operator uint32_t() const {
 		case BOOL:
 			return _data._bool ? 1 : 0;
 		case INT:
-			return _data._int;
+			return uint32_t(_data._int);
 		case FLOAT:
-			return _data._float;
+			return uint32_t(_data._float);
 		case STRING:
-			return operator String().to_int();
+			return uint32_t(operator String().to_int());
 		default: {
 			return 0;
 		}
@@ -1602,11 +1602,11 @@ Variant::operator uint16_t() const {
 		case BOOL:
 			return _data._bool ? 1 : 0;
 		case INT:
-			return _data._int;
+			return uint16_t(_data._int);
 		case FLOAT:
-			return _data._float;
+			return uint16_t(_data._float);
 		case STRING:
-			return operator String().to_int();
+			return uint16_t(operator String().to_int());
 		default: {
 			return 0;
 		}
@@ -1620,11 +1620,11 @@ Variant::operator uint8_t() const {
 		case BOOL:
 			return _data._bool ? 1 : 0;
 		case INT:
-			return _data._int;
+			return uint8_t(_data._int);
 		case FLOAT:
-			return _data._float;
+			return uint8_t(_data._float);
 		case STRING:
-			return operator String().to_int();
+			return uint8_t(operator String().to_int());
 		default: {
 			return 0;
 		}
@@ -2484,22 +2484,22 @@ Variant::Variant(int8_t p_int8) :
 
 Variant::Variant(uint64_t p_uint64) :
 		type(INT) {
-	_data._int = p_uint64;
+	_data._int = int64_t(p_uint64);
 }
 
 Variant::Variant(uint32_t p_uint32) :
 		type(INT) {
-	_data._int = p_uint32;
+	_data._int = int64_t(p_uint32);
 }
 
 Variant::Variant(uint16_t p_uint16) :
 		type(INT) {
-	_data._int = p_uint16;
+	_data._int = int64_t(p_uint16);
 }
 
 Variant::Variant(uint8_t p_uint8) :
 		type(INT) {
-	_data._int = p_uint8;
+	_data._int = int64_t(p_uint8);
 }
 
 Variant::Variant(float p_float) :
@@ -2514,7 +2514,7 @@ Variant::Variant(double p_double) :
 
 Variant::Variant(const ObjectID &p_id) :
 		type(INT) {
-	_data._int = p_id;
+	_data._int = int64_t(p_id);
 }
 
 Variant::Variant(const StringName &p_string) :

+ 1 - 0
core/variant/variant_parser.cpp

@@ -99,6 +99,7 @@ bool VariantParser::StreamString::_is_eof() const {
 uint32_t VariantParser::StreamString::_read_buffer(char32_t *p_buffer, uint32_t p_num_chars) {
 	// The buffer is assumed to include at least one character (for null terminator)
 	ERR_FAIL_COND_V(!p_num_chars, 0);
+	ERR_FAIL_NULL_V(p_buffer, 0);
 
 	int available = MAX(s.length() - pos, 0);
 	if (available >= (int)p_num_chars) {

+ 85 - 0
tests/core/io/test_json.h

@@ -232,6 +232,91 @@ TEST_CASE("[JSON] Parsing escape sequences") {
 		ERR_PRINT_ON
 	}
 }
+
+TEST_CASE("[JSON] Serialization") {
+	JSON json;
+
+	struct FpTestCase {
+		double number;
+		String json;
+	};
+
+	struct IntTestCase {
+		int64_t number;
+		String json;
+	};
+
+	struct UIntTestCase {
+		uint64_t number;
+		String json;
+	};
+
+	static FpTestCase fp_tests_default_precision[] = {
+		{ 0.0, "0.0" },
+		{ 1000.1234567890123456789, "1000.12345678901" },
+		{ -1000.1234567890123456789, "-1000.12345678901" },
+		{ DBL_MAX, "179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.0" },
+		{ DBL_MAX - 1, "179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.0" },
+		{ pow(2, 53), "9007199254740992.0" },
+		{ -pow(2, 53), "-9007199254740992.0" },
+		{ 0.00000000000000011, "0.00000000000000011" },
+		{ -0.00000000000000011, "-0.00000000000000011" },
+		{ 1.0 / 3.0, "0.333333333333333" },
+		{ 0.9999999999999999, "1.0" },
+		{ 1.0000000000000001, "1.0" },
+	};
+
+	static FpTestCase fp_tests_full_precision[] = {
+		{ 0.0, "0.0" },
+		{ 1000.1234567890123456789, "1000.12345678901238" },
+		{ -1000.1234567890123456789, "-1000.12345678901238" },
+		{ DBL_MAX, "179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.0" },
+		{ DBL_MAX - 1, "179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.0" },
+		{ pow(2, 53), "9007199254740992.0" },
+		{ -pow(2, 53), "-9007199254740992.0" },
+		{ 0.00000000000000011, "0.00000000000000011" },
+		{ -0.00000000000000011, "-0.00000000000000011" },
+		{ 1.0 / 3.0, "0.333333333333333315" },
+		{ 0.9999999999999999, "0.999999999999999889" },
+		{ 1.0000000000000001, "1.0" },
+	};
+
+	static IntTestCase int_tests[] = {
+		{ 0, "0" },
+		{ INT64_MAX, "9223372036854775807" },
+		{ INT64_MIN, "-9223372036854775808" },
+	};
+
+	SUBCASE("Floating point default precision") {
+		for (FpTestCase &test : fp_tests_default_precision) {
+			String json_value = json.stringify(test.number, "", true, false);
+
+			CHECK_MESSAGE(
+					json_value == test.json,
+					vformat("Serializing `%.20d` to JSON should return the expected value.", test.number));
+		}
+	}
+
+	SUBCASE("Floating point full precision") {
+		for (FpTestCase &test : fp_tests_full_precision) {
+			String json_value = json.stringify(test.number, "", true, true);
+
+			CHECK_MESSAGE(
+					json_value == test.json,
+					vformat("Serializing `%20f` to JSON should return the expected value.", test.number));
+		}
+	}
+
+	SUBCASE("Signed integer") {
+		for (IntTestCase &test : int_tests) {
+			String json_value = json.stringify(test.number, "", true, true);
+
+			CHECK_MESSAGE(
+					json_value == test.json,
+					vformat("Serializing `%d` to JSON should return the expected value.", test.number));
+		}
+	}
+}
 } // namespace TestJSON
 
 #endif // TEST_JSON_H