151 lines
4.4 KiB
C++
151 lines
4.4 KiB
C++
#include "check-structures-sanity.h"
|
|
|
|
#include "LuaTools.h"
|
|
#include "LuaWrapper.h"
|
|
|
|
DFHACK_PLUGIN("check-structures-sanity");
|
|
|
|
static command_result command(color_ostream &, std::vector<std::string> &);
|
|
|
|
DFhackCExport command_result plugin_init(color_ostream &, std::vector<PluginCommand> & commands)
|
|
{
|
|
commands.push_back(PluginCommand(
|
|
"check-structures-sanity",
|
|
"performs a sanity check on df-structures",
|
|
command,
|
|
false,
|
|
"check-structures-sanity [-enums] [-sizes] [-lowmem] [-maxerrors n] [-failfast] [starting_point]\n"
|
|
"\n"
|
|
"-enums: report unexpected or unnamed enum or bitfield values.\n"
|
|
"-sizes: report struct and class sizes that don't match structures. (requires sizecheck)\n"
|
|
"-unnamed: report unnamed enum/bitfield values, not just undefined ones.\n"
|
|
"-maxerrors n: set the maximum number of errors before bailing out.\n"
|
|
"-failfast: crash if any error is encountered. useful only for debugging.\n"
|
|
"-maybepointer: report integers that might actually be pointers.\n"
|
|
"starting_point: a lua expression or a word like 'screen', 'item', or 'building'. (defaults to df.global)\n"
|
|
"\n"
|
|
"by default, check-structures-sanity reports invalid pointers, vectors, strings, and vtables."
|
|
));
|
|
|
|
known_types_by_size.clear();
|
|
build_size_table();
|
|
|
|
return CR_OK;
|
|
}
|
|
|
|
bool check_malloc_perturb()
|
|
{
|
|
struct T_test {
|
|
uint32_t data[1024];
|
|
};
|
|
auto test = new T_test;
|
|
bool ret = (test->data[0] == 0xd2d2d2d2);
|
|
delete test;
|
|
return ret;
|
|
}
|
|
|
|
static command_result command(color_ostream & out, std::vector<std::string> & parameters)
|
|
{
|
|
if (!check_malloc_perturb())
|
|
{
|
|
out.printerr("check-structures-sanity: MALLOC_PERTURB_ not set, cannot continue\n");
|
|
return CR_FAILURE;
|
|
}
|
|
|
|
CoreSuspender suspend;
|
|
|
|
Checker checker(out);
|
|
|
|
// check parameters with values first
|
|
#define VAL_PARAM(name, expr_using_value) \
|
|
auto name ## _idx = std::find(parameters.begin(), parameters.end(), "-" #name); \
|
|
if (name ## _idx != parameters.end()) \
|
|
{ \
|
|
if (name ## _idx + 1 == parameters.end()) \
|
|
{ \
|
|
return CR_WRONG_USAGE; \
|
|
} \
|
|
try \
|
|
{ \
|
|
auto value = std::move(*(name ## _idx + 1)); \
|
|
parameters.erase((name ## _idx + 1)); \
|
|
parameters.erase(name ## _idx); \
|
|
checker.name = (expr_using_value); \
|
|
} \
|
|
catch (std::exception & ex) \
|
|
{ \
|
|
out.printerr("check-structures-sanity: argument to -%s: %s\n", #name, ex.what()); \
|
|
return CR_WRONG_USAGE; \
|
|
} \
|
|
}
|
|
VAL_PARAM(maxerrors, std::stoul(value));
|
|
#undef VAL_PARAM
|
|
|
|
#define BOOL_PARAM(name) \
|
|
auto name ## _idx = std::find(parameters.begin(), parameters.end(), "-" #name); \
|
|
if (name ## _idx != parameters.end()) \
|
|
{ \
|
|
checker.name = true; \
|
|
parameters.erase(name ## _idx); \
|
|
}
|
|
BOOL_PARAM(enums);
|
|
BOOL_PARAM(sizes);
|
|
BOOL_PARAM(unnamed);
|
|
BOOL_PARAM(failfast);
|
|
BOOL_PARAM(noprogress);
|
|
BOOL_PARAM(maybepointer);
|
|
#undef BOOL_PARAM
|
|
|
|
if (parameters.size() > 1)
|
|
{
|
|
return CR_WRONG_USAGE;
|
|
}
|
|
|
|
if (parameters.empty())
|
|
{
|
|
checker.queue_globals();
|
|
}
|
|
else
|
|
{
|
|
using namespace DFHack::Lua;
|
|
using namespace DFHack::Lua::Core;
|
|
using namespace DFHack::LuaWrapper;
|
|
|
|
StackUnwinder unwinder(State);
|
|
PushModulePublic(out, "utils", "df_expr_to_ref");
|
|
Push(parameters.at(0));
|
|
if (!SafeCall(out, 1, 1))
|
|
{
|
|
return CR_FAILURE;
|
|
}
|
|
if (!lua_touserdata(State, -1))
|
|
{
|
|
return CR_WRONG_USAGE;
|
|
}
|
|
|
|
QueueItem item(parameters.at(0), get_object_ref(State, -1));
|
|
lua_getfield(State, -1, "_type");
|
|
lua_getfield(State, -1, "_identity");
|
|
auto identity = reinterpret_cast<type_identity *>(lua_touserdata(State, -1));
|
|
if (!identity)
|
|
{
|
|
out.printerr("could not determine type identity\n");
|
|
return CR_FAILURE;
|
|
}
|
|
|
|
checker.queue_item(item, CheckedStructure(identity));
|
|
}
|
|
|
|
while (checker.process_queue())
|
|
{
|
|
if (!checker.noprogress)
|
|
{
|
|
out << "checked " << checker.checked_count << " fields\r" << std::flush;
|
|
}
|
|
}
|
|
|
|
out << "checked " << checker.checked_count << " fields" << std::endl;
|
|
|
|
return checker.error_count ? CR_FAILURE : CR_OK;
|
|
}
|