From 76343f4055d44b7eb273876a1a11b444451f6fa3 Mon Sep 17 00:00:00 2001 From: Haoyu Qiu Date: Tue, 26 Apr 2022 20:03:11 +0800 Subject: [PATCH] Validate theme type/item names --- main/tests/test_main.cpp | 6 + main/tests/test_theme.cpp | 295 ++++++++++++++++++++++++++++++++++++++ main/tests/test_theme.h | 41 ++++++ main/tests/test_tools.h | 61 ++++++++ scene/resources/theme.cpp | 58 ++++++++ scene/resources/theme.h | 3 + 6 files changed, 464 insertions(+) create mode 100644 main/tests/test_theme.cpp create mode 100644 main/tests/test_theme.h create mode 100644 main/tests/test_tools.h diff --git a/main/tests/test_main.cpp b/main/tests/test_main.cpp index a2b69160804..9dab7ee3a46 100644 --- a/main/tests/test_main.cpp +++ b/main/tests/test_main.cpp @@ -47,6 +47,7 @@ #include "test_render.h" #include "test_shader_lang.h" #include "test_string.h" +#include "test_theme.h" #include "test_transform.h" #include "test_xml_parser.h" @@ -69,6 +70,7 @@ const char **tests_get_names() { "ordered_hash_map", "astar", "xml_parser", + "theme", nullptr }; @@ -150,6 +152,10 @@ MainLoop *test_main(String p_test, const List &p_args) { return TestXMLParser::test(); } + if (p_test == "theme") { + return TestTheme::test(); + } + print_line("Unknown test: " + p_test); return nullptr; } diff --git a/main/tests/test_theme.cpp b/main/tests/test_theme.cpp new file mode 100644 index 00000000000..48ac60939d3 --- /dev/null +++ b/main/tests/test_theme.cpp @@ -0,0 +1,295 @@ +/*************************************************************************/ +/* test_theme.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "test_theme.h" + +#include "core/os/os.h" +#include "main/tests/test_tools.h" +#include "scene/resources/theme.h" + +#define CHECK(X) \ + if (!(X)) { \ + OS::get_singleton()->print("\tFAIL at line %d: %s\n", __LINE__, #X); \ + return false; \ + } else { \ + OS::get_singleton()->print("\tPASS\n"); \ + } + +namespace TestTheme { + +class Fixture { +public: + struct DataEntry { + Theme::DataType type; + Variant value; + } const valid_data[Theme::DATA_TYPE_MAX] = { + { Theme::DATA_TYPE_COLOR, Color() }, + { Theme::DATA_TYPE_CONSTANT, 42 }, + { Theme::DATA_TYPE_FONT, Ref(memnew(BitmapFont)) }, + { Theme::DATA_TYPE_ICON, Ref(memnew(ImageTexture)) }, + { Theme::DATA_TYPE_STYLEBOX, Ref(memnew(StyleBoxFlat)) }, + }; + + const StringName valid_item_name = "valid_item_name"; + const StringName valid_type_name = "ValidTypeName"; +}; + +bool test_good_theme_type_names() { + Fixture fixture; + StringName names[] = { + "", // Empty name. + "CapitalizedName", + "snake_cased_name", + "42", + "_Underscore_", + }; + + // add_type + for (const StringName &name : names) { + Ref theme = memnew(Theme); + + ErrorDetector ed; + theme->add_type(name); + CHECK(!ed.has_error); + } + + // set_theme_item + for (const StringName &name : names) { + for (const Fixture::DataEntry &entry : fixture.valid_data) { + Ref theme = memnew(Theme); + + ErrorDetector ed; + theme->set_theme_item(entry.type, fixture.valid_item_name, name, entry.value); + CHECK(!ed.has_error); + } + } + + // add_theme_item_type + for (const StringName &name : names) { + for (const Fixture::DataEntry &entry : fixture.valid_data) { + Ref theme = memnew(Theme); + + ErrorDetector ed; + theme->add_theme_item_type(entry.type, name); + CHECK(!ed.has_error); + } + } + + // set_type_variation + for (const StringName &name : names) { + Ref theme = memnew(Theme); + + ErrorDetector ed; + theme->set_type_variation(fixture.valid_type_name, name); + CHECK(ed.has_error == (name == StringName())); + } + for (const StringName &name : names) { + Ref theme = memnew(Theme); + + ErrorDetector ed; + theme->set_type_variation(name, fixture.valid_type_name); + CHECK(ed.has_error == (name == StringName())); + } + + return true; +} + +bool test_bad_theme_type_names() { + Fixture fixture; + StringName names[] = { + "With/Slash", + "With Space", + "With@various$symbols!", + String::utf8("contains_汉字"), + }; + + // add_type + for (const StringName &name : names) { + Ref theme = memnew(Theme); + + ErrorDetector ed; + theme->add_type(name); + CHECK(ed.has_error); + } + + // set_theme_item + for (const StringName &name : names) { + for (const Fixture::DataEntry &entry : fixture.valid_data) { + Ref theme = memnew(Theme); + + ErrorDetector ed; + theme->set_theme_item(entry.type, fixture.valid_item_name, name, entry.value); + CHECK(ed.has_error); + } + } + + // add_theme_item_type + for (const StringName &name : names) { + for (const Fixture::DataEntry &entry : fixture.valid_data) { + Ref theme = memnew(Theme); + + ErrorDetector ed; + theme->add_theme_item_type(entry.type, name); + CHECK(ed.has_error); + } + } + + // set_type_variation + for (const StringName &name : names) { + Ref theme = memnew(Theme); + + ErrorDetector ed; + theme->set_type_variation(fixture.valid_type_name, name); + CHECK(ed.has_error); + } + for (const StringName &name : names) { + Ref theme = memnew(Theme); + + ErrorDetector ed; + theme->set_type_variation(name, fixture.valid_type_name); + CHECK(ed.has_error); + } + + return true; +} + +bool test_good_theme_item_names() { + Fixture fixture; + StringName names[] = { + "CapitalizedName", + "snake_cased_name", + "42", + "_Underscore_", + }; + + // set_theme_item + for (const StringName &name : names) { + for (const Fixture::DataEntry &entry : fixture.valid_data) { + Ref theme = memnew(Theme); + + ErrorDetector ed; + theme->set_theme_item(entry.type, name, fixture.valid_type_name, entry.value); + CHECK(!ed.has_error); + CHECK(theme->has_theme_item(entry.type, name, fixture.valid_type_name)); + } + } + + // rename_theme_item + for (const StringName &name : names) { + for (const Fixture::DataEntry &entry : fixture.valid_data) { + Ref theme = memnew(Theme); + theme->set_theme_item(entry.type, fixture.valid_item_name, fixture.valid_type_name, entry.value); + + ErrorDetector ed; + theme->rename_theme_item(entry.type, fixture.valid_item_name, name, fixture.valid_type_name); + CHECK(!ed.has_error); + CHECK(!theme->has_theme_item(entry.type, fixture.valid_item_name, fixture.valid_type_name)); + CHECK(theme->has_theme_item(entry.type, name, fixture.valid_type_name)); + } + } + + return true; +} + +bool test_bad_theme_item_names() { + Fixture fixture; + StringName names[] = { + "", // Empty name. + "With/Slash", + "With Space", + "With@various$symbols!", + String::utf8("contains_汉字"), + }; + + // set_theme_item + for (const StringName &name : names) { + for (const Fixture::DataEntry &entry : fixture.valid_data) { + Ref theme = memnew(Theme); + + ErrorDetector ed; + theme->set_theme_item(entry.type, name, fixture.valid_type_name, entry.value); + CHECK(ed.has_error); + CHECK(!theme->has_theme_item(entry.type, name, fixture.valid_type_name)); + } + } + + // rename_theme_item + for (const StringName &name : names) { + for (const Fixture::DataEntry &entry : fixture.valid_data) { + Ref theme = memnew(Theme); + theme->set_theme_item(entry.type, fixture.valid_item_name, fixture.valid_type_name, entry.value); + + ErrorDetector ed; + theme->rename_theme_item(entry.type, fixture.valid_item_name, name, fixture.valid_type_name); + CHECK(ed.has_error); + CHECK(theme->has_theme_item(entry.type, fixture.valid_item_name, fixture.valid_type_name)); + CHECK(!theme->has_theme_item(entry.type, name, fixture.valid_type_name)); + } + } + + return true; +} + +typedef bool (*TestFunc)(); +TestFunc test_funcs[] = { + test_good_theme_type_names, + test_bad_theme_type_names, + test_good_theme_item_names, + test_bad_theme_item_names, + nullptr +}; + +MainLoop *test() { + int count = 0; + int passed = 0; + + while (true) { + if (!test_funcs[count]) { + break; + } + bool pass = test_funcs[count](); + if (pass) { + passed++; + } + OS::get_singleton()->print("\t%s\n", pass ? "PASS" : "FAILED"); + + count++; + } + + OS::get_singleton()->print("\n\n\n"); + OS::get_singleton()->print("*************\n"); + OS::get_singleton()->print("***TOTALS!***\n"); + OS::get_singleton()->print("*************\n"); + + OS::get_singleton()->print("Passed %i of %i tests\n", passed, count); + + return nullptr; +} +} // namespace TestTheme diff --git a/main/tests/test_theme.h b/main/tests/test_theme.h new file mode 100644 index 00000000000..e7da0b3b6d8 --- /dev/null +++ b/main/tests/test_theme.h @@ -0,0 +1,41 @@ +/*************************************************************************/ +/* test_theme.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef TEST_THEME_H +#define TEST_THEME_H + +#include "core/os/main_loop.h" + +namespace TestTheme { + +MainLoop *test(); + +} +#endif // TEST_THEME_H diff --git a/main/tests/test_tools.h b/main/tests/test_tools.h new file mode 100644 index 00000000000..b94b71f7d6a --- /dev/null +++ b/main/tests/test_tools.h @@ -0,0 +1,61 @@ +/*************************************************************************/ +/* test_tools.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef TEST_TOOLS_H +#define TEST_TOOLS_H + +#include "core/error_macros.h" + +struct ErrorDetector { + ErrorDetector() { + eh.errfunc = _detect_error; + eh.userdata = this; + + add_error_handler(&eh); + } + + ~ErrorDetector() { + remove_error_handler(&eh); + } + + void clear() { + has_error = false; + } + + static void _detect_error(void *p_self, const char *p_func, const char *p_file, int p_line, const char *p_error, const char *p_message, ErrorHandlerType p_type) { + ErrorDetector *self = (ErrorDetector *)p_self; + self->has_error = true; + } + + ErrorHandlerList eh; + bool has_error = false; +}; + +#endif // TEST_TOOLS_H diff --git a/scene/resources/theme.cpp b/scene/resources/theme.cpp index d3cb1157595..9c47543c180 100644 --- a/scene/resources/theme.cpp +++ b/scene/resources/theme.cpp @@ -214,6 +214,29 @@ void Theme::set_default_font(const Ref &p_font) { default_font = p_font; } +bool Theme::is_valid_type_name(const String &p_name) { + for (int i = 0; i < p_name.length(); i++) { + const CharType c = p_name[i]; + if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_')) { + return false; + } + } + return true; +} + +bool Theme::is_valid_item_name(const String &p_name) { + if (p_name.empty()) { + return false; + } + for (int i = 0; i < p_name.length(); i++) { + const CharType c = p_name[i]; + if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_')) { + return false; + } + } + return true; +} + // Fallback values for theme item types, configurable per theme. void Theme::set_default_theme_font(const Ref &p_default_font) { if (default_theme_font == p_default_font) { @@ -243,6 +266,9 @@ bool Theme::has_default_theme_font() const { // Icons. void Theme::set_icon(const StringName &p_name, const StringName &p_theme_type, const Ref &p_icon) { + ERR_FAIL_COND_MSG(!is_valid_item_name(p_name), vformat("Invalid item name: '%s'", p_name)); + ERR_FAIL_COND_MSG(!is_valid_type_name(p_theme_type), vformat("Invalid type name: '%s'", p_theme_type)); + bool existing = false; if (icon_map[p_theme_type].has(p_name) && icon_map[p_theme_type][p_name].is_valid()) { existing = true; @@ -275,6 +301,7 @@ bool Theme::has_icon_nocheck(const StringName &p_name, const StringName &p_theme } void Theme::rename_icon(const StringName &p_old_name, const StringName &p_name, const StringName &p_theme_type) { + ERR_FAIL_COND_MSG(!is_valid_item_name(p_name), vformat("Invalid item name: '%s'", p_name)); ERR_FAIL_COND_MSG(!icon_map.has(p_theme_type), "Cannot rename the icon '" + String(p_old_name) + "' because the node type '" + String(p_theme_type) + "' does not exist."); ERR_FAIL_COND_MSG(icon_map[p_theme_type].has(p_name), "Cannot rename the icon '" + String(p_old_name) + "' because the new name '" + String(p_name) + "' already exists."); ERR_FAIL_COND_MSG(!icon_map[p_theme_type].has(p_old_name), "Cannot rename the icon '" + String(p_old_name) + "' because it does not exist."); @@ -313,6 +340,8 @@ void Theme::get_icon_list(StringName p_theme_type, List *p_list) con } void Theme::add_icon_type(const StringName &p_theme_type) { + ERR_FAIL_COND_MSG(!is_valid_type_name(p_theme_type), vformat("Invalid type name: '%s'", p_theme_type)); + if (icon_map.has(p_theme_type)) { return; } @@ -350,6 +379,9 @@ void Theme::get_icon_types(List *p_list) const { // Shaders. void Theme::set_shader(const StringName &p_name, const StringName &p_theme_type, const Ref &p_shader) { + ERR_FAIL_COND_MSG(!is_valid_item_name(p_name), vformat("Invalid item name: '%s'", p_name)); + ERR_FAIL_COND_MSG(!is_valid_type_name(p_theme_type), vformat("Invalid type name: '%s'", p_theme_type)); + bool existing = (shader_map.has(p_theme_type) && shader_map[p_theme_type].has(p_name)); shader_map[p_theme_type][p_name] = p_shader; @@ -393,6 +425,9 @@ void Theme::get_shader_list(const StringName &p_theme_type, List *p_ // Styleboxes. void Theme::set_stylebox(const StringName &p_name, const StringName &p_theme_type, const Ref &p_style) { + ERR_FAIL_COND_MSG(!is_valid_item_name(p_name), vformat("Invalid item name: '%s'", p_name)); + ERR_FAIL_COND_MSG(!is_valid_type_name(p_theme_type), vformat("Invalid type name: '%s'", p_theme_type)); + bool existing = false; if (style_map[p_theme_type].has(p_name) && style_map[p_theme_type][p_name].is_valid()) { existing = true; @@ -425,6 +460,7 @@ bool Theme::has_stylebox_nocheck(const StringName &p_name, const StringName &p_t } void Theme::rename_stylebox(const StringName &p_old_name, const StringName &p_name, const StringName &p_theme_type) { + ERR_FAIL_COND_MSG(!is_valid_item_name(p_name), vformat("Invalid item name: '%s'", p_name)); ERR_FAIL_COND_MSG(!style_map.has(p_theme_type), "Cannot rename the stylebox '" + String(p_old_name) + "' because the node type '" + String(p_theme_type) + "' does not exist."); ERR_FAIL_COND_MSG(style_map[p_theme_type].has(p_name), "Cannot rename the stylebox '" + String(p_old_name) + "' because the new name '" + String(p_name) + "' already exists."); ERR_FAIL_COND_MSG(!style_map[p_theme_type].has(p_old_name), "Cannot rename the stylebox '" + String(p_old_name) + "' because it does not exist."); @@ -463,6 +499,8 @@ void Theme::get_stylebox_list(StringName p_theme_type, List *p_list) } void Theme::add_stylebox_type(const StringName &p_theme_type) { + ERR_FAIL_COND_MSG(!is_valid_type_name(p_theme_type), vformat("Invalid type name: '%s'", p_theme_type)); + if (style_map.has(p_theme_type)) { return; } @@ -500,6 +538,9 @@ void Theme::get_stylebox_types(List *p_list) const { // Fonts. void Theme::set_font(const StringName &p_name, const StringName &p_theme_type, const Ref &p_font) { + ERR_FAIL_COND_MSG(!is_valid_item_name(p_name), vformat("Invalid item name: '%s'", p_name)); + ERR_FAIL_COND_MSG(!is_valid_type_name(p_theme_type), vformat("Invalid type name: '%s'", p_theme_type)); + bool existing = false; if (font_map[p_theme_type][p_name].is_valid()) { existing = true; @@ -534,6 +575,7 @@ bool Theme::has_font_nocheck(const StringName &p_name, const StringName &p_theme } void Theme::rename_font(const StringName &p_old_name, const StringName &p_name, const StringName &p_theme_type) { + ERR_FAIL_COND_MSG(!is_valid_item_name(p_name), vformat("Invalid item name: '%s'", p_name)); ERR_FAIL_COND_MSG(!font_map.has(p_theme_type), "Cannot rename the font '" + String(p_old_name) + "' because the node type '" + String(p_theme_type) + "' does not exist."); ERR_FAIL_COND_MSG(font_map[p_theme_type].has(p_name), "Cannot rename the font '" + String(p_old_name) + "' because the new name '" + String(p_name) + "' already exists."); ERR_FAIL_COND_MSG(!font_map[p_theme_type].has(p_old_name), "Cannot rename the font '" + String(p_old_name) + "' because it does not exist."); @@ -572,6 +614,8 @@ void Theme::get_font_list(StringName p_theme_type, List *p_list) con } void Theme::add_font_type(const StringName &p_theme_type) { + ERR_FAIL_COND_MSG(!is_valid_type_name(p_theme_type), vformat("Invalid type name: '%s'", p_theme_type)); + if (font_map.has(p_theme_type)) { return; } @@ -609,6 +653,9 @@ void Theme::get_font_types(List *p_list) const { // Colors. void Theme::set_color(const StringName &p_name, const StringName &p_theme_type, const Color &p_color) { + ERR_FAIL_COND_MSG(!is_valid_item_name(p_name), vformat("Invalid item name: '%s'", p_name)); + ERR_FAIL_COND_MSG(!is_valid_type_name(p_theme_type), vformat("Invalid type name: '%s'", p_theme_type)); + bool existing = has_color_nocheck(p_name, p_theme_type); color_map[p_theme_type][p_name] = p_color; @@ -632,6 +679,7 @@ bool Theme::has_color_nocheck(const StringName &p_name, const StringName &p_them } void Theme::rename_color(const StringName &p_old_name, const StringName &p_name, const StringName &p_theme_type) { + ERR_FAIL_COND_MSG(!is_valid_item_name(p_name), vformat("Invalid item name: '%s'", p_name)); ERR_FAIL_COND_MSG(!color_map.has(p_theme_type), "Cannot rename the color '" + String(p_old_name) + "' because the node type '" + String(p_theme_type) + "' does not exist."); ERR_FAIL_COND_MSG(color_map[p_theme_type].has(p_name), "Cannot rename the color '" + String(p_old_name) + "' because the new name '" + String(p_name) + "' already exists."); ERR_FAIL_COND_MSG(!color_map[p_theme_type].has(p_old_name), "Cannot rename the color '" + String(p_old_name) + "' because it does not exist."); @@ -666,6 +714,8 @@ void Theme::get_color_list(StringName p_theme_type, List *p_list) co } void Theme::add_color_type(const StringName &p_theme_type) { + ERR_FAIL_COND_MSG(!is_valid_type_name(p_theme_type), vformat("Invalid type name: '%s'", p_theme_type)); + if (color_map.has(p_theme_type)) { return; } @@ -691,6 +741,9 @@ void Theme::get_color_types(List *p_list) const { // Theme constants. void Theme::set_constant(const StringName &p_name, const StringName &p_theme_type, int p_constant) { + ERR_FAIL_COND_MSG(!is_valid_item_name(p_name), vformat("Invalid item name: '%s'", p_name)); + ERR_FAIL_COND_MSG(!is_valid_type_name(p_theme_type), vformat("Invalid type name: '%s'", p_theme_type)); + bool existing = has_constant_nocheck(p_name, p_theme_type); constant_map[p_theme_type][p_name] = p_constant; @@ -714,6 +767,7 @@ bool Theme::has_constant_nocheck(const StringName &p_name, const StringName &p_t } void Theme::rename_constant(const StringName &p_old_name, const StringName &p_name, const StringName &p_theme_type) { + ERR_FAIL_COND_MSG(!is_valid_item_name(p_name), vformat("Invalid item name: '%s'", p_name)); ERR_FAIL_COND_MSG(!constant_map.has(p_theme_type), "Cannot rename the constant '" + String(p_old_name) + "' because the node type '" + String(p_theme_type) + "' does not exist."); ERR_FAIL_COND_MSG(constant_map[p_theme_type].has(p_name), "Cannot rename the constant '" + String(p_old_name) + "' because the new name '" + String(p_name) + "' already exists."); ERR_FAIL_COND_MSG(!constant_map[p_theme_type].has(p_old_name), "Cannot rename the constant '" + String(p_old_name) + "' because it does not exist."); @@ -748,6 +802,8 @@ void Theme::get_constant_list(StringName p_theme_type, List *p_list) } void Theme::add_constant_type(const StringName &p_theme_type) { + ERR_FAIL_COND_MSG(!is_valid_type_name(p_theme_type), vformat("Invalid type name: '%s'", p_theme_type)); + if (constant_map.has(p_theme_type)) { return; } @@ -1000,6 +1056,8 @@ void Theme::get_theme_item_types(DataType p_data_type, List *p_list) // Theme type variations. void Theme::set_type_variation(const StringName &p_theme_type, const StringName &p_base_type) { + ERR_FAIL_COND_MSG(!is_valid_type_name(p_theme_type), vformat("Invalid type name: '%s'", p_theme_type)); + ERR_FAIL_COND_MSG(!is_valid_type_name(p_base_type), vformat("Invalid type name: '%s'", p_base_type)); ERR_FAIL_COND_MSG(p_theme_type == StringName(), "An empty theme type cannot be marked as a variation of another type."); ERR_FAIL_COND_MSG(ClassDB::class_exists(p_theme_type), "A type associated with a built-in class cannot be marked as a variation of another type."); ERR_FAIL_COND_MSG(p_base_type == StringName(), "An empty theme type cannot be the base type of a variation. Use clear_type_variation() instead if you want to unmark '" + String(p_theme_type) + "' as a variation."); diff --git a/scene/resources/theme.h b/scene/resources/theme.h index d0c8ec5727c..6cec7ccdb9e 100644 --- a/scene/resources/theme.h +++ b/scene/resources/theme.h @@ -122,6 +122,9 @@ public: static void set_default_style(const Ref &p_style); static void set_default_font(const Ref &p_font); + static bool is_valid_type_name(const String &p_name); + static bool is_valid_item_name(const String &p_name); + void set_default_theme_font(const Ref &p_default_font); Ref get_default_theme_font() const; bool has_default_theme_font() const;