diff --git a/common/YAML.cpp b/common/YAML.cpp index 52c0a2aec3..3687e54512 100644 --- a/common/YAML.cpp +++ b/common/YAML.cpp @@ -17,7 +17,7 @@ struct RapidYAMLContext Error* error = nullptr; }; -std::optional ParseYAMLFromString(ryml::csubstr yaml, ryml::csubstr file_name, Error* error) +std::optional ParseYAMLFromString(ryml::csubstr yaml, ryml::csubstr file_name, Error* error, bool resolve_anchors) { RapidYAMLContext context; context.error = error; @@ -28,40 +28,93 @@ std::optional ParseYAMLFromString(ryml::csubstr yaml, ryml::csubstr callbacks.set_user_data(static_cast(&context)); callbacks.set_error_basic([](ryml::csubstr msg, const ryml::ErrorDataBasic& errdata, void* user_data) { - std::string description; - auto callback = [&description](ryml::csubstr string) { - description.append(string.str, string.len); - }; - ryml::err_basic_format(std::move(callback), msg, errdata); + RapidYAMLContext* context = static_cast(user_data); + // This scope needs to stay, so all objects destruct before std::longjump + { + std::string description; + auto callback = [&description](ryml::csubstr string) { + description.append(string.str, string.len); + }; + ryml::err_basic_format(std::move(callback), msg, errdata); - // We might have already returned, so don't try to recover. - pxFailRel(description.c_str()); - std::abort(); + if (context != nullptr) + { + Error::SetString(context->error, std::move(description)); + } + else + { + pxFailRel(description.c_str()); + } + } + + if (context != nullptr) + { + std::longjmp(context->env, 1); + } + else + { + std::terminate(); + } }); callbacks.set_error_parse([](ryml::csubstr msg, const ryml::ErrorDataParse& errdata, void* user_data) { RapidYAMLContext* context = static_cast(user_data); + // This scope needs to stay, so all objects destruct before std::longjump + { + std::string description; + auto callback = [&description](ryml::csubstr string) { + description.append(string.str, string.len); + }; + ryml::err_parse_format(std::move(callback), msg, errdata); - std::string description; - auto callback = [&description](ryml::csubstr string) { - description.append(string.str, string.len); - }; - ryml::err_parse_format(std::move(callback), msg, errdata); + if (context != nullptr) + { + Error::SetString(context->error, std::move(description)); + } + else + { + pxFailRel(description.c_str()); + } + } - Error::SetString(context->error, std::move(description)); - std::longjmp(context->env, 1); + if (context != nullptr) + { + std::longjmp(context->env, 2); + } + else + { + std::terminate(); + } }); callbacks.set_error_visit([](ryml::csubstr msg, const ryml::ErrorDataVisit& errdata, void* user_data) { - std::string description; - auto callback = [&description](ryml::csubstr string) { - description.append(string.str, string.len); - }; - ryml::err_visit_format(std::move(callback), msg, errdata); + RapidYAMLContext* context = static_cast(user_data); + // This scope needs to stay, so all objects destruct before std::longjump + { + std::string description; + auto callback = [&description](ryml::csubstr string) { + description.append(string.str, string.len); + }; + ryml::err_visit_format(std::move(callback), msg, errdata); - // We've probably already returned, so don't try to recover. - pxFailRel(description.c_str()); - std::abort(); + if (context != nullptr) + { + Error::SetString(context->error, std::move(description)); + } + else + { + pxFailRel(description.c_str()); + } + } + + if (context != nullptr) + { + std::longjmp(context->env, 3); + } + else + { + std::terminate(); + } }); #else callbacks.m_user_data = static_cast(&context); @@ -76,15 +129,24 @@ std::optional ParseYAMLFromString(ryml::csubstr yaml, ryml::csubstr ryml::EventHandlerTree event_handler(callbacks); ryml::Parser parser(&event_handler); - ryml::Tree tree; + ryml::Tree tree(callbacks); // The only options RapidYAML provides for recovering from errors are // throwing an exception or using setjmp/longjmp. Since we have exceptions // disabled we have to use the latter option. - if (setjmp(context.env)) + if (setjmp(context.env) != 0) return std::nullopt; ryml::parse_in_arena(&parser, file_name, yaml, &tree); + if (resolve_anchors) + { + tree.resolve(); + } + + // Callbacks passed to ryml::Tree are used for value parsing errors later, + // so we need to clear the context before it goes out of scope. + callbacks.set_user_data(nullptr); + tree.callbacks(callbacks); return tree; } diff --git a/common/YAML.h b/common/YAML.h index fe5a8cb4cd..11ba68a289 100644 --- a/common/YAML.h +++ b/common/YAML.h @@ -14,4 +14,6 @@ /// parsing errors (as is recommended by the documentation for cases where /// exceptions are disabled). The file_name parameter is only used for error /// messages, which are returned via the error parameter. -std::optional ParseYAMLFromString(ryml::csubstr yaml, ryml::csubstr file_name, Error* error); +/// If resolve_anchors is set to true, YAML anchors and aliases will be resolved. +/// It's a potentially slow operation, so it's opt-in. +std::optional ParseYAMLFromString(ryml::csubstr yaml, ryml::csubstr file_name, Error* error, bool resolve_anchors = false); diff --git a/pcsx2/GameDatabase.cpp b/pcsx2/GameDatabase.cpp index dd08741344..0ad9a91c3d 100644 --- a/pcsx2/GameDatabase.cpp +++ b/pcsx2/GameDatabase.cpp @@ -1030,7 +1030,7 @@ void GameDatabase::initDatabase() const ryml::csubstr yaml = ryml::to_csubstr(*buffer); Error error; - std::optional tree = ParseYAMLFromString(yaml, ryml::to_csubstr(name), &error); + std::optional tree = ParseYAMLFromString(yaml, ryml::to_csubstr(name), &error, true); if (!tree.has_value()) { Console.ErrorFmt("GameDB: Failed to parse game database file {}:", path);