mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-07 16:18:19 -10:00
* avoid undefined behavior from UNREACHABLE on reachable code paths
UNREACHABLE expands to __assume(false) / __builtin_unreachable() in
release builds, which is undefined behavior if the code is ever actually
reached. Many UNREACHABLE sites only guard against unexpected-but-
possible runtime states (values from tables, mission/save files, network
packets, scripting, or user input), so reaching them should degrade
gracefully rather than invoke UB.
Replace UNREACHABLE with Assertion(false, ...) at all such sites. It
still pops an error in debug builds, but in release it is a harmless
no-op that simply falls through to the following code. Sites that are
genuinely unreachable by local invariants (e.g. a switch over a bounded
random number) are left as UNREACHABLE.
A few converted sites had no safe fall-through -- an uninitialized read,
null dereference, or null call would have followed -- so they also get a
minimal recovery: initialize the value, guard the dereference, return
early, or skip the offending item.
Where the converted site was a simple guard of the form
if (x) { Assertion(false, ...); return; }
hoist the check onto the invariant instead:
Assertion(!x, ...);
if (x) { return; }
This states the expectation directly and shows the offending value in
the debug popup, while keeping the same graceful runtime degradation.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* addendum
After consultation with Lafiel, use UNREACHABLE in situations that are effectively impossible even if not provably impossible. UNREACHABLE = "this shouldn't ever happen"; Assertion = "if this ever happens, it's a coding error". Also fix a few other sites and change a couple to Warnings.
---------
Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
317 lines
9.4 KiB
C++
317 lines
9.4 KiB
C++
|
|
#include "doc_json.h"
|
|
|
|
#include "libs/jansson.h"
|
|
|
|
namespace scripting {
|
|
|
|
static void json_doc_generate_class(json_t* elObj, const DocumentationElementClass* lib)
|
|
{
|
|
json_object_set_new(elObj, "superClass", json_string(lib->superClass.c_str()));
|
|
}
|
|
static json_t* json_doc_generate_return_type(const scripting::ade_type_info& type_info)
|
|
{
|
|
switch (type_info.getType()) {
|
|
case ade_type_info_type::Empty:
|
|
return json_string("void");
|
|
case ade_type_info_type::Simple:
|
|
return json_string(type_info.getIdentifier());
|
|
case ade_type_info_type::Tuple: {
|
|
json_t* tupleTypes = json_array();
|
|
|
|
for (const auto& type : type_info.elements()) {
|
|
json_array_append_new(tupleTypes, json_doc_generate_return_type(type));
|
|
}
|
|
|
|
return json_pack("{ssso}", "type", "tuple", "elements", tupleTypes);
|
|
}
|
|
case ade_type_info_type::Array: {
|
|
return json_pack("{ssso}",
|
|
"type",
|
|
"list",
|
|
"element",
|
|
json_doc_generate_return_type(type_info.elements().front()));
|
|
}
|
|
case ade_type_info_type::Map: {
|
|
return json_pack("{sssoso}",
|
|
"type",
|
|
"map",
|
|
"key",
|
|
json_doc_generate_return_type(type_info.elements()[0]),
|
|
"value",
|
|
json_doc_generate_return_type(type_info.elements()[1]));
|
|
}
|
|
case ade_type_info_type::Iterator: {
|
|
return json_pack("{ssso}",
|
|
"type",
|
|
"iterator",
|
|
"element",
|
|
json_doc_generate_return_type(type_info.elements().front()));
|
|
}
|
|
case ade_type_info_type::Alternative: {
|
|
json_t* alternativeTypes = json_array();
|
|
|
|
for (const auto& type : type_info.elements()) {
|
|
json_array_append_new(alternativeTypes, json_doc_generate_return_type(type));
|
|
}
|
|
|
|
return json_pack("{ssso}", "type", "alternative", "elements", alternativeTypes);
|
|
}
|
|
case ade_type_info_type::Function: {
|
|
const auto& elements = type_info.elements();
|
|
|
|
json_t* parameterTypes = json_array();
|
|
|
|
if (elements.size() > 1) {
|
|
for (auto iter = elements.begin() + 1; iter != elements.end(); ++iter) {
|
|
json_array_append_new(parameterTypes,
|
|
json_pack("{ssso}", "name", iter->getName().c_str(), "type", json_doc_generate_return_type(*iter)));
|
|
}
|
|
}
|
|
|
|
return json_pack("{sssoso}",
|
|
"type",
|
|
"function",
|
|
"returnType",
|
|
json_doc_generate_return_type(elements.front()),
|
|
"parameters",
|
|
parameterTypes);
|
|
}
|
|
case ade_type_info_type::Generic: {
|
|
const auto& elements = type_info.elements();
|
|
|
|
json_t* parameterTypes = json_array();
|
|
|
|
if (elements.size() > 1) {
|
|
for (auto iter = elements.begin() + 1; iter != elements.end(); ++iter) {
|
|
json_array_append_new(parameterTypes, json_doc_generate_return_type(*iter));
|
|
}
|
|
}
|
|
|
|
return json_pack("{sssoso}",
|
|
"type",
|
|
"generic",
|
|
"baseType",
|
|
json_doc_generate_return_type(elements.front()),
|
|
"parameters",
|
|
parameterTypes);
|
|
}
|
|
case ade_type_info_type::Varargs: {
|
|
return json_pack("{ssso}",
|
|
"type",
|
|
"varargs",
|
|
"baseType",
|
|
json_doc_generate_return_type(type_info.elements().front()));
|
|
}
|
|
}
|
|
|
|
UNREACHABLE("Unknown type type %d!", static_cast<int>(type_info.getType()));
|
|
return nullptr;
|
|
}
|
|
static json_t* json_doc_function_signature(const SCP_vector<scripting::argument_def>& args)
|
|
{
|
|
json_t* arr = json_array();
|
|
|
|
for (const auto& arg : args) {
|
|
json_array_append_new(arr,
|
|
json_pack("{sosssssbss}",
|
|
"type",
|
|
json_doc_generate_return_type(arg.type),
|
|
"name",
|
|
arg.name.c_str(),
|
|
"default",
|
|
arg.def_val.c_str(),
|
|
"optional",
|
|
arg.optional,
|
|
"description",
|
|
arg.comment.c_str()));
|
|
}
|
|
|
|
return arr;
|
|
}
|
|
static void json_doc_generate_function(json_t* elObj, const DocumentationElementFunction* lib)
|
|
{
|
|
json_object_set_new(elObj, "returnType", json_doc_generate_return_type(lib->returnType));
|
|
json_object_set_new(elObj, "returnDocumentation", json_string(lib->returnDocumentation.c_str()));
|
|
|
|
if (lib->overloads.size() == 1 && !lib->overloads.front().simple.empty()) {
|
|
// Legacy argument list not filled with argument data
|
|
json_object_set_new(elObj, "parameters", json_string(lib->overloads.front().simple.c_str()));
|
|
} else {
|
|
json_t* arr = json_array();
|
|
|
|
for (const auto& overload : lib->overloads) {
|
|
if (!overload.simple.empty()) {
|
|
json_array_append_new(arr, json_string(overload.simple.c_str()));
|
|
} else {
|
|
json_array_append_new(arr, json_doc_function_signature(overload.arguments));
|
|
}
|
|
}
|
|
|
|
json_object_set_new(elObj, "parameters", arr);
|
|
}
|
|
}
|
|
static void json_doc_generate_property(json_t* elObj, const DocumentationElementProperty* lib)
|
|
{
|
|
json_object_set_new(elObj, "getterType", json_doc_generate_return_type(lib->getterType));
|
|
json_object_set_new(elObj, "setterType", json_doc_generate_return_type(lib->setterType));
|
|
json_object_set_new(elObj, "returnDocumentation", json_string(lib->returnDocumentation.c_str()));
|
|
}
|
|
static json_t* json_doc_generate_elements(const SCP_vector<std::unique_ptr<DocumentationElement>>& elements);
|
|
static json_t* json_doc_generate_element(const std::unique_ptr<DocumentationElement>& element)
|
|
{
|
|
json_t* elementsObj = json_object();
|
|
|
|
json_object_set_new(elementsObj, "name", json_string(element->name.c_str()));
|
|
json_object_set_new(elementsObj, "shortName", json_string(element->shortName.c_str()));
|
|
json_object_set_new(elementsObj, "description", json_string(element->description.c_str()));
|
|
|
|
switch (element->type) {
|
|
case ElementType::Library:
|
|
json_object_set_new(elementsObj, "type", json_string("library"));
|
|
break;
|
|
case ElementType::Class:
|
|
json_object_set_new(elementsObj, "type", json_string("class"));
|
|
json_doc_generate_class(elementsObj, static_cast<DocumentationElementClass*>(element.get()));
|
|
break;
|
|
case ElementType::Function:
|
|
json_object_set_new(elementsObj, "type", json_string("function"));
|
|
json_doc_generate_function(elementsObj, static_cast<DocumentationElementFunction*>(element.get()));
|
|
break;
|
|
case ElementType::Operator:
|
|
json_object_set_new(elementsObj, "type", json_string("operator"));
|
|
json_doc_generate_function(elementsObj, static_cast<DocumentationElementFunction*>(element.get()));
|
|
break;
|
|
case ElementType::Property:
|
|
json_object_set_new(elementsObj, "type", json_string("property"));
|
|
json_doc_generate_property(elementsObj, static_cast<DocumentationElementProperty*>(element.get()));
|
|
break;
|
|
default:
|
|
json_object_set_new(elementsObj, "type", json_string("unknown"));
|
|
break;
|
|
}
|
|
|
|
json_object_set_new(elementsObj, "children", json_doc_generate_elements(element->children));
|
|
|
|
return elementsObj;
|
|
}
|
|
|
|
static json_t* json_doc_generate_elements(const SCP_vector<std::unique_ptr<DocumentationElement>>& elements)
|
|
{
|
|
json_t* elementsArray = json_array();
|
|
|
|
for (const auto& el : elements) {
|
|
json_array_append_new(elementsArray, json_doc_generate_element(el));
|
|
}
|
|
|
|
return elementsArray;
|
|
}
|
|
|
|
static json_t* json_doc_generate_param_element(const HookVariableDocumentation& param)
|
|
{
|
|
return json_pack("{s:s,s:s,s:o}",
|
|
"name",
|
|
param.name,
|
|
"description",
|
|
param.description,
|
|
"type",
|
|
json_doc_generate_return_type(param.type));
|
|
}
|
|
|
|
static json_t* json_doc_generate_action_element(const DocumentationAction& action)
|
|
{
|
|
json_t* paramArray = json_array();
|
|
json_t* conditionArray = json_array();
|
|
|
|
for (const auto& param : action.parameters) {
|
|
json_array_append_new(paramArray, json_doc_generate_param_element(param));
|
|
}
|
|
|
|
for (const auto& condition : action.conditions) {
|
|
json_array_append_new(conditionArray, json_pack("{s:s,s:s}",
|
|
"name",
|
|
condition.first.c_str(),
|
|
"description",
|
|
condition.second->documentation.c_str()));
|
|
}
|
|
|
|
return json_pack("{s:s,s:s,s:o,s:o,s:b}",
|
|
"name",
|
|
action.name.c_str(),
|
|
"description",
|
|
action.description.c_str(),
|
|
"hookVars",
|
|
paramArray,
|
|
"conditions",
|
|
conditionArray,
|
|
"overridable",
|
|
action.overridable);
|
|
}
|
|
|
|
void output_json_doc(const ScriptingDocumentation& doc, const SCP_string& filename)
|
|
{
|
|
std::unique_ptr<json_t> root(json_object());
|
|
// Should be incremented every time an incompatible change is made
|
|
json_object_set_new(root.get(), "version", json_integer(2));
|
|
|
|
{
|
|
json_t* actionArray = json_array();
|
|
|
|
for (const auto& action : doc.actions) {
|
|
json_array_append_new(actionArray, json_doc_generate_action_element(action));
|
|
}
|
|
|
|
json_object_set_new(root.get(), "actions", actionArray);
|
|
}
|
|
{
|
|
json_t* conditionArray = json_array();
|
|
|
|
for (const auto& cond : doc.conditions) {
|
|
json_array_append_new(conditionArray, json_string(cond.c_str()));
|
|
}
|
|
|
|
json_object_set_new(root.get(), "conditions", conditionArray);
|
|
}
|
|
{
|
|
json_t* optionsArray = json_array();
|
|
|
|
for (const auto& option : doc.options) {
|
|
json_t* optionsObject = json_object();
|
|
|
|
json_object_set_new(optionsObject, "title", json_string(option.title.c_str()));
|
|
json_object_set_new(optionsObject, "key", json_string(option.key.c_str()));
|
|
json_object_set_new(optionsObject, "description", json_string(option.description.c_str()));
|
|
|
|
json_array_append_new(optionsArray, optionsObject);
|
|
}
|
|
|
|
json_object_set_new(root.get(), "options", optionsArray);
|
|
}
|
|
{
|
|
json_t* enumObject = json_object();
|
|
|
|
for (const auto& enumVal : doc.enumerations) {
|
|
json_object_set_new(enumObject, enumVal.name.c_str(), json_integer(enumVal.value));
|
|
}
|
|
|
|
json_object_set_new(root.get(), "enums", enumObject);
|
|
}
|
|
{
|
|
json_t* globalVariables = json_array();
|
|
|
|
for (const auto& param : doc.globalVariables) {
|
|
json_array_append_new(globalVariables, json_doc_generate_param_element(param));
|
|
}
|
|
|
|
json_object_set_new(root.get(), "globalVars", globalVariables);
|
|
}
|
|
json_object_set_new(root.get(), "elements", json_doc_generate_elements(doc.elements));
|
|
|
|
const auto jsonStr = json_dump_string(root.get(), JSON_INDENT(2) | JSON_SORT_KEYS);
|
|
|
|
std::ofstream outStr(filename, std::ios::binary);
|
|
outStr << jsonStr;
|
|
}
|
|
|
|
} // namespace scripting
|