add -enums, -sizes, and specific starting point flags to check-structures-sanity

develop
Ben Lubar 2020-02-14 19:47:26 -06:00
parent 4e69b50bb3
commit 9caf6e97e6
No known key found for this signature in database
GPG Key ID: 92939677AB59EDA4
3 changed files with 179 additions and 22 deletions

@ -132,7 +132,7 @@ namespace LuaWrapper {
* Push the pointer as DF object ref using metatable on the stack. * Push the pointer as DF object ref using metatable on the stack.
*/ */
void push_object_ref(lua_State *state, void *ptr); void push_object_ref(lua_State *state, void *ptr);
void *get_object_ref(lua_State *state, int val_index); DFHACK_EXPORT void *get_object_ref(lua_State *state, int val_index);
/* /*
* The system might be extended to carry some simple * The system might be extended to carry some simple
@ -165,6 +165,7 @@ namespace LuaWrapper {
bool is_type_compatible(lua_State *state, type_identity *type1, int meta1, bool is_type_compatible(lua_State *state, type_identity *type1, int meta1,
type_identity *type2, int meta2, bool exact_equal); type_identity *type2, int meta2, bool exact_equal);
DFHACK_EXPORT
type_identity *get_object_identity(lua_State *state, int objidx, type_identity *get_object_identity(lua_State *state, int objidx,
const char *ctx, bool allow_type = false, const char *ctx, bool allow_type = false,
bool keep_metatable = false); bool keep_metatable = false);

@ -6,7 +6,7 @@ include(FindThreads)
add_definitions(-DDEV_PLUGIN) add_definitions(-DDEV_PLUGIN)
dfhack_plugin(buildprobe buildprobe.cpp) dfhack_plugin(buildprobe buildprobe.cpp)
dfhack_plugin(check-structures-sanity check-structures-sanity.cpp) dfhack_plugin(check-structures-sanity check-structures-sanity.cpp LINK_LIBRARIES lua)
dfhack_plugin(color-dfhack-text color-dfhack-text.cpp) dfhack_plugin(color-dfhack-text color-dfhack-text.cpp)
dfhack_plugin(counters counters.cpp) dfhack_plugin(counters counters.cpp)
dfhack_plugin(dumpmats dumpmats.cpp) dfhack_plugin(dumpmats dumpmats.cpp)

@ -3,6 +3,8 @@
#include "MemAccess.h" #include "MemAccess.h"
#include "DataDefs.h" #include "DataDefs.h"
#include "DataIdentity.h" #include "DataIdentity.h"
#include "LuaTools.h"
#include "LuaWrapper.h"
#if defined(WIN32) && defined(DFHACK64) #if defined(WIN32) && defined(DFHACK64)
#define _WIN32_WINNT 0x0501 #define _WIN32_WINNT 0x0501
@ -28,14 +30,20 @@ static command_result command(color_ostream &, std::vector<std::string> &);
#define UNEXPECTED __asm__ volatile ("int $0x03") #define UNEXPECTED __asm__ volatile ("int $0x03")
#endif #endif
DFhackCExport command_result plugin_init(color_ostream & out, std::vector<PluginCommand> & commands) DFhackCExport command_result plugin_init(color_ostream &, std::vector<PluginCommand> & commands)
{ {
commands.push_back(PluginCommand( commands.push_back(PluginCommand(
"check-structures-sanity", "check-structures-sanity",
"performs a sanity check on df-structures", "performs a sanity check on df-structures",
command, command,
false, false,
"checks structures to make sure vectors aren't misidentified" "usage: check-structures-sanity [-enums] [-sizes] [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"
"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"
)); ));
return CR_OK; return CR_OK;
} }
@ -72,6 +80,8 @@ class Checker
std::set<void *> seen_addr; std::set<void *> seen_addr;
public: public:
std::queue<ToCheck> queue; std::queue<ToCheck> queue;
bool enums;
bool sizes;
private: private:
bool ok; bool ok;
@ -102,21 +112,60 @@ public:
static command_result command(color_ostream & out, std::vector<std::string> & parameters) static command_result command(color_ostream & out, std::vector<std::string> & parameters)
{ {
if (!parameters.empty())
{
return CR_WRONG_USAGE;
}
CoreSuspender suspend; CoreSuspender suspend;
Checker checker(out); Checker checker(out);
#define BOOL_PARAM(name) \
auto name ## _idx = std::find(parameters.cbegin(), parameters.cend(), "-" #name); \
if (name ## _idx != parameters.cend()) \
{ \
checker.name = true; \
parameters.erase(name ## _idx); \
}
BOOL_PARAM(enums);
BOOL_PARAM(sizes);
#undef BOOL_PARAM
if (parameters.size() > 1)
{
return CR_WRONG_USAGE;
}
if (parameters.empty())
{
ToCheck global; ToCheck global;
global.path.push_back("df::global::"); global.path.push_back("df::global::");
global.ptr = nullptr; global.ptr = nullptr;
global.identity = &df::global::_identity; global.identity = &df::global::_identity;
checker.queue.push(std::move(global)); checker.queue.push(std::move(global));
}
else
{
using namespace DFHack::Lua;
using namespace DFHack::Lua::Core;
using namespace DFHack::LuaWrapper;
StackUnwinder unwinder(State);
Push(parameters.at(0));
PushModulePublic(out, "utils", "df_expr_to_ref");
if (!SafeCall(out, 1, 1))
{
return CR_FAILURE;
}
ToCheck ref;
ref.path.push_back(parameters.at(0));
ref.ptr = get_object_ref(State, -1);
ref.identity = get_object_identity(State, -1, "check-structures-sanity command argument", false, false);
if (!ref.identity)
{
return CR_FAILURE;
}
checker.queue.push(std::move(ref));
}
return checker.check() ? CR_OK : CR_FAILURE; return checker.check() ? CR_OK : CR_FAILURE;
} }
@ -125,6 +174,8 @@ Checker::Checker(color_ostream & out) :
out(out) out(out)
{ {
Core::getInstance().p->getMemRanges(mapped); Core::getInstance().p->getMemRanges(mapped);
enums = false;
sizes = false;
} }
bool Checker::check() bool Checker::check()
@ -332,11 +383,7 @@ void Checker::queue_field(ToCheck && item, const struct_field_info *field)
void Checker::queue_static_array(const ToCheck & array, void *base, type_identity *type, size_t count, bool pointer, enum_identity *ienum) void Checker::queue_static_array(const ToCheck & array, void *base, type_identity *type, size_t count, bool pointer, enum_identity *ienum)
{ {
size_t size = type->byte_size(); size_t size = pointer ? sizeof(void *) : type->byte_size();
if (pointer)
{
size = sizeof(void *);
}
for (size_t i = 0; i < count; i++, base = PTR_ADD(base, size)) for (size_t i = 0; i < count; i++, base = PTR_ADD(base, size))
{ {
@ -568,18 +615,109 @@ void Checker::check_pointer(const ToCheck & item)
return; return;
} }
auto identity = static_cast<pointer_identity *>(item.identity); queue.push(ToCheck(item, "", *reinterpret_cast<void **>(item.ptr), static_cast<pointer_identity *>(item.identity)->getTarget()));
queue.push(ToCheck(item, "", *reinterpret_cast<void **>(item.ptr), identity->getTarget()));
} }
void Checker::check_bitfield(const ToCheck & item) void Checker::check_bitfield(const ToCheck & item)
{ {
// TODO: check bitfields? if (!enums)
{
return;
}
auto identity = static_cast<bitfield_identity *>(item.identity);
uint64_t val = 0;
for (size_t offset = 0; offset < identity->byte_size(); offset++)
{
val |= uint64_t(*reinterpret_cast<uint8_t *>(PTR_ADD(item.ptr, offset))) << (8 * offset);
}
size_t num_bits = identity->getNumBits();
auto bits = identity->getBits();
for (size_t i = 0; i < num_bits; i++)
{
if (bits[i].size < 0)
continue;
if (bits[i].name)
continue;
if (!(val & (1 << i)))
continue;
if (bits[i].size)
{
FAIL("bitfield bit " << i << " is unnamed");
}
else
{
FAIL("bitfield bit " << i << " past the defined end of the bitfield");
}
}
} }
void Checker::check_enum(const ToCheck & item) void Checker::check_enum(const ToCheck & item)
{ {
// TODO: check enums? if (!enums)
{
return;
}
auto identity = static_cast<enum_identity *>(item.identity);
int64_t value;
switch (identity->byte_size())
{
case 1:
if (identity->getFirstItem() < 0)
value = *reinterpret_cast<int8_t *>(item.ptr);
else
value = *reinterpret_cast<uint8_t *>(item.ptr);
break;
case 2:
if (identity->getFirstItem() < 0)
value = *reinterpret_cast<int16_t *>(item.ptr);
else
value = *reinterpret_cast<uint16_t *>(item.ptr);
break;
case 4:
if (identity->getFirstItem() < 0)
value = *reinterpret_cast<int32_t *>(item.ptr);
else
value = *reinterpret_cast<uint32_t *>(item.ptr);
break;
case 8:
value = *reinterpret_cast<int64_t *>(item.ptr);
break;
default:
UNEXPECTED;
return;
}
size_t index;
if (auto cplx = identity->getComplex())
{
auto it = cplx->value_index_map.find(value);
if (it == cplx->value_index_map.cend())
{
FAIL("enum value (" << value << ") is not defined (complex enum)");
return;
}
index = it->second;
}
else
{
if (value < identity->getFirstItem() || value > identity->getLastItem())
{
FAIL("enum value (" << value << ") outside of defined range (" << identity->getFirstItem() << " to " << identity->getLastItem() << ")");
return;
}
index = value - identity->getFirstItem();
}
if (!identity->getKeys()[index])
{
FAIL("enum value (" << value << ") is unnamed");
}
} }
void Checker::check_container(const ToCheck & item) void Checker::check_container(const ToCheck & item)
@ -756,6 +894,23 @@ void Checker::check_bitvector(const ToCheck & item)
void Checker::check_struct(const ToCheck & item) void Checker::check_struct(const ToCheck & item)
{ {
bool is_pointer = item.path.back().empty();
bool is_virtual = !item.path.back().empty() && item.path.back().at(0) == '<';
if (sizes && uintptr_t(item.ptr) % 32 == 16 && (is_pointer || is_virtual))
{
uint32_t tag = *reinterpret_cast<uint32_t *>(PTR_ADD(item.ptr, -8));
if (tag == 0xdfdf4ac8)
{
size_t allocated_size = *reinterpret_cast<size_t *>(PTR_ADD(item.ptr, -16));
size_t expected_size = item.identity->byte_size();
if (allocated_size != expected_size)
{
FAIL("allocated structure size (" << allocated_size << ") does not match expected size (" << expected_size << ")");
}
}
}
for (auto identity = static_cast<struct_identity *>(item.identity); identity; identity = identity->getParent()) for (auto identity = static_cast<struct_identity *>(item.identity); identity; identity = identity->getParent())
{ {
auto fields = identity->getFields(); auto fields = identity->getFields();
@ -800,6 +955,7 @@ void Checker::check_virtual(const ToCheck & item)
return; return;
} }
ToCheck virtual_item(item, "", item.ptr, virtual_identity::get(reinterpret_cast<virtual_ptr>(item.ptr))); auto vident = virtual_identity::get(reinterpret_cast<virtual_ptr>(item.ptr));
ToCheck virtual_item(item, "<" + vident->getFullName() + ">", item.ptr, vident);
check_struct(virtual_item); check_struct(virtual_item);
} }