rewriting check-structures-sanity to improve performance and remove the need for the lowmem option
							parent
							
								
									b9841110c3
								
							
						
					
					
						commit
						e5de783c58
					
				
											
												
													File diff suppressed because it is too large
													Load Diff
												
											
										
									
								@ -0,0 +1,8 @@
 | 
			
		||||
set(PLUGIN_SRCS
 | 
			
		||||
    dispatch.cpp
 | 
			
		||||
    main.cpp
 | 
			
		||||
    types.cpp
 | 
			
		||||
    validate.cpp
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
dfhack_plugin(check-structures-sanity ${PLUGIN_SRCS} LINK_LIBRARIES lua)
 | 
			
		||||
@ -0,0 +1,117 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "Console.h"
 | 
			
		||||
#include "PluginManager.h"
 | 
			
		||||
#include "MemAccess.h"
 | 
			
		||||
#include "DataDefs.h"
 | 
			
		||||
#include "DataIdentity.h"
 | 
			
		||||
 | 
			
		||||
#include <deque>
 | 
			
		||||
#include <map>
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
using namespace DFHack;
 | 
			
		||||
 | 
			
		||||
#ifdef WIN32
 | 
			
		||||
#define UNEXPECTED __debugbreak()
 | 
			
		||||
#else
 | 
			
		||||
#define UNEXPECTED __asm__ volatile ("int $0x03")
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#define PTR_ADD(ptr, offset) reinterpret_cast<const void *>(uintptr_t(ptr) + (offset))
 | 
			
		||||
 | 
			
		||||
struct QueueItem
 | 
			
		||||
{
 | 
			
		||||
    QueueItem(const std::string &, const void *);
 | 
			
		||||
    QueueItem(const QueueItem &, const std::string &, const void *);
 | 
			
		||||
    QueueItem(const QueueItem &, size_t, const void *);
 | 
			
		||||
 | 
			
		||||
    std::string path;
 | 
			
		||||
    const void *ptr;
 | 
			
		||||
};
 | 
			
		||||
struct CheckedStructure
 | 
			
		||||
{
 | 
			
		||||
    type_identity *identity;
 | 
			
		||||
    size_t count;
 | 
			
		||||
 | 
			
		||||
    CheckedStructure();
 | 
			
		||||
    explicit CheckedStructure(type_identity *, size_t = 0);
 | 
			
		||||
    CheckedStructure(const struct_field_info *);
 | 
			
		||||
 | 
			
		||||
    size_t full_size() const;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
namespace
 | 
			
		||||
{
 | 
			
		||||
    template<typename T, bool is_pointer = std::is_pointer<T>::value>
 | 
			
		||||
    struct safe_t
 | 
			
		||||
    {
 | 
			
		||||
        typedef T type;
 | 
			
		||||
    };
 | 
			
		||||
    template<typename T>
 | 
			
		||||
    struct safe_t<T, true>
 | 
			
		||||
    {
 | 
			
		||||
        typedef void *type;
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class Checker
 | 
			
		||||
{
 | 
			
		||||
    color_ostream & out;
 | 
			
		||||
    std::vector<t_memrange> mapped;
 | 
			
		||||
    std::map<const void *, CheckedStructure> data;
 | 
			
		||||
    std::deque<QueueItem> queue;
 | 
			
		||||
public:
 | 
			
		||||
    size_t checked_count;
 | 
			
		||||
    size_t error_count;
 | 
			
		||||
    size_t maxerrors;
 | 
			
		||||
    bool maxerrors_reported;
 | 
			
		||||
    bool enums;
 | 
			
		||||
    bool sizes;
 | 
			
		||||
    bool unnamed;
 | 
			
		||||
    bool failfast;
 | 
			
		||||
 | 
			
		||||
    Checker(color_ostream & out);
 | 
			
		||||
    void queue_item(const QueueItem & item, const CheckedStructure & cs);
 | 
			
		||||
    void queue_globals();
 | 
			
		||||
    bool process_queue();
 | 
			
		||||
 | 
			
		||||
    bool is_valid_dereference(const QueueItem & item, const CheckedStructure & cs, bool quiet = false);
 | 
			
		||||
    template<typename T>
 | 
			
		||||
    const T validate_and_dereference(const QueueItem & item, bool quiet = false)
 | 
			
		||||
    {
 | 
			
		||||
        CheckedStructure cs;
 | 
			
		||||
        cs.identity = df::identity_traits<typename safe_t<T>::type>::get();
 | 
			
		||||
        if (!is_valid_dereference(item, cs, quiet))
 | 
			
		||||
            return T();
 | 
			
		||||
 | 
			
		||||
        return *reinterpret_cast<const T *>(item.ptr);
 | 
			
		||||
    }
 | 
			
		||||
    const char *get_vtable_name(const QueueItem & item, const CheckedStructure & cs, bool quiet = false);
 | 
			
		||||
    std::pair<const void *, size_t> validate_vector_size(const QueueItem & item, const CheckedStructure & cs, bool quiet = false);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    color_ostream & fail(int, const QueueItem &, const CheckedStructure &);
 | 
			
		||||
    void dispatch_item(const QueueItem &, const CheckedStructure &);
 | 
			
		||||
    void dispatch_single_item(const QueueItem &, const CheckedStructure &);
 | 
			
		||||
    void dispatch_primitive(const QueueItem &, const CheckedStructure &);
 | 
			
		||||
    void dispatch_pointer(const QueueItem &, const CheckedStructure &);
 | 
			
		||||
    void dispatch_container(const QueueItem &, const CheckedStructure &);
 | 
			
		||||
    void dispatch_ptr_container(const QueueItem &, const CheckedStructure &);
 | 
			
		||||
    void dispatch_bit_container(const QueueItem &, const CheckedStructure &);
 | 
			
		||||
    void dispatch_bitfield(const QueueItem &, const CheckedStructure &);
 | 
			
		||||
    void dispatch_enum(const QueueItem &, const CheckedStructure &);
 | 
			
		||||
    void dispatch_struct(const QueueItem &, const CheckedStructure &);
 | 
			
		||||
    void dispatch_field(const QueueItem &, const CheckedStructure &, const struct_field_info *, const struct_field_info *);
 | 
			
		||||
    void dispatch_class(const QueueItem &, const CheckedStructure &);
 | 
			
		||||
    void dispatch_buffer(const QueueItem &, const CheckedStructure &);
 | 
			
		||||
    void dispatch_stl_ptr_vector(const QueueItem &, const CheckedStructure &);
 | 
			
		||||
    void dispatch_untagged_union(const QueueItem &, const CheckedStructure &);
 | 
			
		||||
    void check_stl_vector(const QueueItem &, type_identity *);
 | 
			
		||||
 | 
			
		||||
    friend struct CheckedStructure;
 | 
			
		||||
    static type_identity *wrap_in_pointer(type_identity *);
 | 
			
		||||
    static type_identity *wrap_in_stl_ptr_vector(type_identity *);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#define FAIL(message) (static_cast<color_ostream &>(fail(__LINE__, item, cs) << message) << COLOR_RESET << std::endl)
 | 
			
		||||
@ -0,0 +1,389 @@
 | 
			
		||||
#include "check-structures-sanity.h"
 | 
			
		||||
 | 
			
		||||
#include "df/large_integer.h"
 | 
			
		||||
 | 
			
		||||
Checker::Checker(color_ostream & out) :
 | 
			
		||||
    out(out),
 | 
			
		||||
    checked_count(0),
 | 
			
		||||
    error_count(0),
 | 
			
		||||
    maxerrors(~size_t(0)),
 | 
			
		||||
    maxerrors_reported(false),
 | 
			
		||||
    enums(false),
 | 
			
		||||
    sizes(false),
 | 
			
		||||
    unnamed(false),
 | 
			
		||||
    failfast(false)
 | 
			
		||||
{
 | 
			
		||||
    Core::getInstance().p->getMemRanges(mapped);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
color_ostream & Checker::fail(int line, const QueueItem & item, const CheckedStructure & cs)
 | 
			
		||||
{
 | 
			
		||||
    error_count++;
 | 
			
		||||
    out << COLOR_LIGHTRED << "sanity check failed (line " << line << "): ";
 | 
			
		||||
    out << COLOR_RESET << (cs.identity ? cs.identity->getFullName() : "?");
 | 
			
		||||
    out << " (accessed as " << item.path << "): ";
 | 
			
		||||
    out << COLOR_YELLOW;
 | 
			
		||||
    if (maxerrors && maxerrors != ~size_t(0))
 | 
			
		||||
        maxerrors--;
 | 
			
		||||
    if (failfast)
 | 
			
		||||
        UNEXPECTED;
 | 
			
		||||
    return out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Checker::queue_item(const QueueItem & item, const CheckedStructure & cs)
 | 
			
		||||
{
 | 
			
		||||
    if (data.count(item.ptr) && data.at(item.ptr).full_size() == cs.full_size())
 | 
			
		||||
    {
 | 
			
		||||
        // already checked
 | 
			
		||||
        // TODO: make sure types are equal
 | 
			
		||||
        UNEXPECTED;
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    auto ptr_end = PTR_ADD(item.ptr, cs.full_size());
 | 
			
		||||
 | 
			
		||||
    auto prev = data.lower_bound(item.ptr);
 | 
			
		||||
    if (prev != data.cbegin())
 | 
			
		||||
    {
 | 
			
		||||
        prev--;
 | 
			
		||||
        if (uintptr_t(prev->first) + prev->second.full_size() > uintptr_t(item.ptr))
 | 
			
		||||
        {
 | 
			
		||||
            // TODO
 | 
			
		||||
            UNEXPECTED;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    auto overlap = data.lower_bound(item.ptr);
 | 
			
		||||
    auto overlap_end = data.lower_bound(ptr_end);
 | 
			
		||||
    while (overlap != overlap_end)
 | 
			
		||||
    {
 | 
			
		||||
        // TODO
 | 
			
		||||
        UNEXPECTED;
 | 
			
		||||
        overlap++;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    data[item.ptr] = cs;
 | 
			
		||||
    queue.push_back(item);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Checker::queue_globals()
 | 
			
		||||
{
 | 
			
		||||
    auto fields = df::global::_identity.getFields();
 | 
			
		||||
    for (auto field = fields; field->mode != struct_field_info::END; field++)
 | 
			
		||||
    {
 | 
			
		||||
        if (!field->offset)
 | 
			
		||||
        {
 | 
			
		||||
            UNEXPECTED;
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // offset is the position of the DFHack pointer to this global.
 | 
			
		||||
        auto ptr = *reinterpret_cast<const void **>(field->offset);
 | 
			
		||||
 | 
			
		||||
        QueueItem item(stl_sprintf("df.global.%s", field->name), ptr);
 | 
			
		||||
        CheckedStructure cs(field);
 | 
			
		||||
 | 
			
		||||
        if (!ptr)
 | 
			
		||||
        {
 | 
			
		||||
            FAIL("unknown global address");
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        queue_item(item, cs);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool Checker::process_queue()
 | 
			
		||||
{
 | 
			
		||||
    if (queue.empty())
 | 
			
		||||
    {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    auto item = std::move(queue.front());
 | 
			
		||||
    queue.pop_front();
 | 
			
		||||
 | 
			
		||||
    auto cs = data.find(item.ptr);
 | 
			
		||||
    if (cs == data.end())
 | 
			
		||||
    {
 | 
			
		||||
        // happens if pointer is determined to be part of a larger structure
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    dispatch_item(item, cs->second);
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
void Checker::dispatch_item(const QueueItem & base, const CheckedStructure & cs)
 | 
			
		||||
{
 | 
			
		||||
    if (!is_valid_dereference(base, cs))
 | 
			
		||||
    {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!cs.count)
 | 
			
		||||
    {
 | 
			
		||||
        dispatch_single_item(base, cs);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    auto ptr = base.ptr;
 | 
			
		||||
    auto size = cs.identity->byte_size();
 | 
			
		||||
    for (size_t i = 0; i < cs.count; i++)
 | 
			
		||||
    {
 | 
			
		||||
        QueueItem item(base, i, ptr);
 | 
			
		||||
        dispatch_single_item(item, cs);
 | 
			
		||||
        ptr = PTR_ADD(ptr, size);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Checker::dispatch_single_item(const QueueItem & item, const CheckedStructure & cs)
 | 
			
		||||
{
 | 
			
		||||
    checked_count++;
 | 
			
		||||
 | 
			
		||||
    if (!maxerrors)
 | 
			
		||||
    {
 | 
			
		||||
        if (!maxerrors_reported)
 | 
			
		||||
        {
 | 
			
		||||
            FAIL("error limit reached. bailing out with " << (queue.size() + 1) << " items remaining in the queue.");
 | 
			
		||||
            maxerrors_reported = true;
 | 
			
		||||
        }
 | 
			
		||||
        queue.clear();
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    switch (cs.identity->type())
 | 
			
		||||
    {
 | 
			
		||||
        case IDTYPE_GLOBAL:
 | 
			
		||||
        case IDTYPE_FUNCTION:
 | 
			
		||||
            UNEXPECTED;
 | 
			
		||||
            break;
 | 
			
		||||
        case IDTYPE_PRIMITIVE:
 | 
			
		||||
            dispatch_primitive(item, cs);
 | 
			
		||||
            break;
 | 
			
		||||
        case IDTYPE_POINTER:
 | 
			
		||||
            dispatch_pointer(item, cs);
 | 
			
		||||
            break;
 | 
			
		||||
        case IDTYPE_CONTAINER:
 | 
			
		||||
            dispatch_container(item, cs);
 | 
			
		||||
            break;
 | 
			
		||||
        case IDTYPE_PTR_CONTAINER:
 | 
			
		||||
            dispatch_ptr_container(item, cs);
 | 
			
		||||
            break;
 | 
			
		||||
        case IDTYPE_BIT_CONTAINER:
 | 
			
		||||
            dispatch_bit_container(item, cs);
 | 
			
		||||
            break;
 | 
			
		||||
        case IDTYPE_BITFIELD:
 | 
			
		||||
            dispatch_bitfield(item, cs);
 | 
			
		||||
            break;
 | 
			
		||||
        case IDTYPE_ENUM:
 | 
			
		||||
            dispatch_enum(item, cs);
 | 
			
		||||
            break;
 | 
			
		||||
        case IDTYPE_STRUCT:
 | 
			
		||||
            dispatch_struct(item, cs);
 | 
			
		||||
            break;
 | 
			
		||||
        case IDTYPE_CLASS:
 | 
			
		||||
            dispatch_class(item, cs);
 | 
			
		||||
            break;
 | 
			
		||||
        case IDTYPE_BUFFER:
 | 
			
		||||
            dispatch_buffer(item, cs);
 | 
			
		||||
            break;
 | 
			
		||||
        case IDTYPE_STL_PTR_VECTOR:
 | 
			
		||||
            dispatch_stl_ptr_vector(item, cs);
 | 
			
		||||
            break;
 | 
			
		||||
        case IDTYPE_OPAQUE:
 | 
			
		||||
            UNEXPECTED;
 | 
			
		||||
            break;
 | 
			
		||||
        case IDTYPE_UNION:
 | 
			
		||||
            dispatch_untagged_union(item, cs);
 | 
			
		||||
            break;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Checker::dispatch_primitive(const QueueItem & item, const CheckedStructure & cs)
 | 
			
		||||
{
 | 
			
		||||
    if (cs.identity->isConstructed())
 | 
			
		||||
    {
 | 
			
		||||
        if (cs.identity == df::identity_traits<std::string>::get())
 | 
			
		||||
        {
 | 
			
		||||
            // TODO check std::string
 | 
			
		||||
            UNEXPECTED;
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            UNEXPECTED;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // TODO: check primitives
 | 
			
		||||
    UNEXPECTED;
 | 
			
		||||
}
 | 
			
		||||
void Checker::dispatch_pointer(const QueueItem & item, const CheckedStructure & cs)
 | 
			
		||||
{
 | 
			
		||||
    auto identity = static_cast<pointer_identity *>(cs.identity);
 | 
			
		||||
    // TODO: check pointer
 | 
			
		||||
    UNEXPECTED;
 | 
			
		||||
}
 | 
			
		||||
void Checker::dispatch_container(const QueueItem & item, const CheckedStructure & cs)
 | 
			
		||||
{
 | 
			
		||||
    auto identity = static_cast<container_identity *>(cs.identity);
 | 
			
		||||
    auto base_container = identity->getFullName(nullptr);
 | 
			
		||||
    if (base_container == "vector<void>")
 | 
			
		||||
    {
 | 
			
		||||
        if (identity->getIndexEnumType())
 | 
			
		||||
        {
 | 
			
		||||
            UNEXPECTED;
 | 
			
		||||
        }
 | 
			
		||||
        check_stl_vector(item, identity->getItemType());
 | 
			
		||||
    }
 | 
			
		||||
    else if (base_container == "deque<void>")
 | 
			
		||||
    {
 | 
			
		||||
        // TODO: check deque?
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
        UNEXPECTED;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
void Checker::dispatch_ptr_container(const QueueItem & item, const CheckedStructure & cs)
 | 
			
		||||
{
 | 
			
		||||
    auto identity = static_cast<container_identity *>(cs.identity);
 | 
			
		||||
    auto base_container = identity->getFullName(nullptr);
 | 
			
		||||
    {
 | 
			
		||||
        UNEXPECTED;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
void Checker::dispatch_bit_container(const QueueItem & item, const CheckedStructure & cs)
 | 
			
		||||
{
 | 
			
		||||
    auto identity = static_cast<container_identity *>(cs.identity);
 | 
			
		||||
    auto base_container = identity->getFullName(nullptr);
 | 
			
		||||
    if (base_container == "BitArray<>")
 | 
			
		||||
    {
 | 
			
		||||
        // TODO: check DF bit array
 | 
			
		||||
        UNEXPECTED;
 | 
			
		||||
    }
 | 
			
		||||
    else if (base_container == "vector<bool>")
 | 
			
		||||
    {
 | 
			
		||||
        // TODO: check stl bit vector
 | 
			
		||||
        UNEXPECTED;
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
        UNEXPECTED;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
void Checker::dispatch_bitfield(const QueueItem & item, const CheckedStructure & cs)
 | 
			
		||||
{
 | 
			
		||||
    if (!enums)
 | 
			
		||||
    {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // TODO: check bitfields
 | 
			
		||||
    UNEXPECTED;
 | 
			
		||||
}
 | 
			
		||||
void Checker::dispatch_enum(const QueueItem & item, const CheckedStructure & cs)
 | 
			
		||||
{
 | 
			
		||||
    if (!enums)
 | 
			
		||||
    {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // TODO: check enums
 | 
			
		||||
    UNEXPECTED;
 | 
			
		||||
}
 | 
			
		||||
void Checker::dispatch_struct(const QueueItem & item, const CheckedStructure & cs)
 | 
			
		||||
{
 | 
			
		||||
    auto identity = static_cast<struct_identity *>(cs.identity);
 | 
			
		||||
    for (auto p = identity; p; p = p->getParent())
 | 
			
		||||
    {
 | 
			
		||||
        auto fields = p->getFields();
 | 
			
		||||
        for (auto field = fields; field->mode != struct_field_info::END; field++)
 | 
			
		||||
        {
 | 
			
		||||
            dispatch_field(item, cs, fields, field);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
void Checker::dispatch_field(const QueueItem & item, const CheckedStructure & cs, const struct_field_info *fields, const struct_field_info *field)
 | 
			
		||||
{
 | 
			
		||||
    if (field->mode == struct_field_info::OBJ_METHOD ||
 | 
			
		||||
        field->mode == struct_field_info::CLASS_METHOD)
 | 
			
		||||
    {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    auto tag_field = find_union_tag(fields, field);
 | 
			
		||||
    if (tag_field)
 | 
			
		||||
    {
 | 
			
		||||
        UNEXPECTED;
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    auto field_ptr = PTR_ADD(item.ptr, field->offset);
 | 
			
		||||
    CheckedStructure field_cs(field);
 | 
			
		||||
    dispatch_item(QueueItem(item, field->name, field_ptr), field_cs);
 | 
			
		||||
}
 | 
			
		||||
void Checker::dispatch_class(const QueueItem & item, const CheckedStructure & cs)
 | 
			
		||||
{
 | 
			
		||||
    auto vtable_name = get_vtable_name(item, cs);
 | 
			
		||||
    if (!vtable_name)
 | 
			
		||||
    {
 | 
			
		||||
        // bail out now because virtual_identity::get will crash
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    auto base_identity = static_cast<virtual_identity *>(cs.identity);
 | 
			
		||||
    auto vptr = static_cast<virtual_ptr>(const_cast<void *>(item.ptr));
 | 
			
		||||
    if (!base_identity->is_instance(vptr))
 | 
			
		||||
    {
 | 
			
		||||
        FAIL("expected subclass of " << base_identity->getFullName() << ", but got " << vtable_name);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    auto identity = virtual_identity::get(vptr);
 | 
			
		||||
    dispatch_struct(QueueItem(item.path + "<" + identity->getFullName() + ">", item.ptr), CheckedStructure(identity));
 | 
			
		||||
}
 | 
			
		||||
void Checker::dispatch_buffer(const QueueItem & item, const CheckedStructure & cs)
 | 
			
		||||
{
 | 
			
		||||
    auto identity = static_cast<container_identity *>(cs.identity);
 | 
			
		||||
    if (identity->getIndexEnumType())
 | 
			
		||||
    {
 | 
			
		||||
        UNEXPECTED;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    auto item_identity = identity->getItemType();
 | 
			
		||||
    dispatch_item(item, CheckedStructure(item_identity, identity->byte_size() / item_identity->byte_size()));
 | 
			
		||||
}
 | 
			
		||||
void Checker::dispatch_stl_ptr_vector(const QueueItem & item, const CheckedStructure & cs)
 | 
			
		||||
{
 | 
			
		||||
    auto identity = static_cast<container_identity *>(cs.identity);
 | 
			
		||||
    if (identity->getIndexEnumType())
 | 
			
		||||
    {
 | 
			
		||||
        UNEXPECTED;
 | 
			
		||||
    }
 | 
			
		||||
    auto ptr_type = wrap_in_pointer(identity->getItemType());
 | 
			
		||||
    check_stl_vector(item, ptr_type);
 | 
			
		||||
}
 | 
			
		||||
void Checker::dispatch_untagged_union(const QueueItem & item, const CheckedStructure & cs)
 | 
			
		||||
{
 | 
			
		||||
    if (cs.identity == df::identity_traits<df::large_integer>::get())
 | 
			
		||||
    {
 | 
			
		||||
        dispatch_primitive(item, CheckedStructure(df::identity_traits<int64_t>::get()));
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    UNEXPECTED;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Checker::check_stl_vector(const QueueItem & item, type_identity *item_identity)
 | 
			
		||||
{
 | 
			
		||||
    auto vec_items = validate_vector_size(item, CheckedStructure(item_identity));
 | 
			
		||||
    if (vec_items.first && vec_items.second)
 | 
			
		||||
    {
 | 
			
		||||
        QueueItem items_item(item.path, vec_items.first);
 | 
			
		||||
        CheckedStructure items_cs(item_identity, vec_items.second);
 | 
			
		||||
        queue_item(items_item, items_cs);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,126 @@
 | 
			
		||||
#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"
 | 
			
		||||
        "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;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static command_result command(color_ostream & out, std::vector<std::string> & parameters)
 | 
			
		||||
{
 | 
			
		||||
    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);
 | 
			
		||||
#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 (out.is_console())
 | 
			
		||||
        {
 | 
			
		||||
            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;
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,131 @@
 | 
			
		||||
#include "check-structures-sanity.h"
 | 
			
		||||
 | 
			
		||||
QueueItem::QueueItem(const std::string & path, const void *ptr) :
 | 
			
		||||
    path(path),
 | 
			
		||||
    ptr(ptr)
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
QueueItem::QueueItem(const QueueItem & parent, const std::string & member, const void *ptr) :
 | 
			
		||||
    QueueItem(parent.path + "." + member, ptr)
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
QueueItem::QueueItem(const QueueItem & parent, size_t index, const void *ptr) :
 | 
			
		||||
    QueueItem(parent.path + stl_sprintf("[%zu]", index), ptr)
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CheckedStructure::CheckedStructure() :
 | 
			
		||||
    CheckedStructure(nullptr, 0)
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
CheckedStructure::CheckedStructure(type_identity *identity, size_t count) :
 | 
			
		||||
    identity(identity),
 | 
			
		||||
    count(count)
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
CheckedStructure::CheckedStructure(const struct_field_info *field) :
 | 
			
		||||
    CheckedStructure()
 | 
			
		||||
{
 | 
			
		||||
    if (!field || field->mode == struct_field_info::END)
 | 
			
		||||
    {
 | 
			
		||||
        UNEXPECTED;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (field->mode == struct_field_info::STATIC_ARRAY && field->eid)
 | 
			
		||||
    {
 | 
			
		||||
        UNEXPECTED;
 | 
			
		||||
    }
 | 
			
		||||
    else if (field->type && field->type->isContainer())
 | 
			
		||||
    {
 | 
			
		||||
        auto expected_eid = static_cast<container_identity *>(field->type)->getIndexEnumType();
 | 
			
		||||
        if (field->eid != expected_eid)
 | 
			
		||||
        {
 | 
			
		||||
            UNEXPECTED;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    else if (field->eid)
 | 
			
		||||
    {
 | 
			
		||||
        UNEXPECTED;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    identity = field->type;
 | 
			
		||||
    switch (field->mode)
 | 
			
		||||
    {
 | 
			
		||||
        case struct_field_info::END:
 | 
			
		||||
            UNEXPECTED;
 | 
			
		||||
            break;
 | 
			
		||||
        case struct_field_info::PRIMITIVE:
 | 
			
		||||
            if (field->count || !field->type)
 | 
			
		||||
            {
 | 
			
		||||
                UNEXPECTED;
 | 
			
		||||
            }
 | 
			
		||||
            break;
 | 
			
		||||
        case struct_field_info::STATIC_STRING:
 | 
			
		||||
            if (!field->count || field->type)
 | 
			
		||||
            {
 | 
			
		||||
                UNEXPECTED;
 | 
			
		||||
            }
 | 
			
		||||
            identity = df::identity_traits<char>::get();
 | 
			
		||||
            count = field->count;
 | 
			
		||||
            break;
 | 
			
		||||
        case struct_field_info::POINTER:
 | 
			
		||||
            // TODO: check flags (stored in field->count)
 | 
			
		||||
            identity = Checker::wrap_in_pointer(field->type);
 | 
			
		||||
            break;
 | 
			
		||||
        case struct_field_info::STATIC_ARRAY:
 | 
			
		||||
            if (!field->count || !field->type)
 | 
			
		||||
            {
 | 
			
		||||
                UNEXPECTED;
 | 
			
		||||
            }
 | 
			
		||||
            count = field->count;
 | 
			
		||||
            break;
 | 
			
		||||
        case struct_field_info::SUBSTRUCT:
 | 
			
		||||
        case struct_field_info::CONTAINER:
 | 
			
		||||
            if (field->count || !field->type)
 | 
			
		||||
            {
 | 
			
		||||
                UNEXPECTED;
 | 
			
		||||
            }
 | 
			
		||||
            break;
 | 
			
		||||
        case struct_field_info::STL_VECTOR_PTR:
 | 
			
		||||
            if (field->count)
 | 
			
		||||
            {
 | 
			
		||||
                UNEXPECTED;
 | 
			
		||||
            }
 | 
			
		||||
            identity = Checker::wrap_in_stl_ptr_vector(field->type);
 | 
			
		||||
            break;
 | 
			
		||||
        case struct_field_info::OBJ_METHOD:
 | 
			
		||||
        case struct_field_info::CLASS_METHOD:
 | 
			
		||||
            UNEXPECTED;
 | 
			
		||||
            break;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
size_t CheckedStructure::full_size() const
 | 
			
		||||
{
 | 
			
		||||
    size_t size = identity->byte_size();
 | 
			
		||||
    if (count)
 | 
			
		||||
    {
 | 
			
		||||
        size *= count;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return size;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#define RETURN_CACHED_WRAPPER(T, base, ...) \
 | 
			
		||||
    static std::map<type_identity *, std::unique_ptr<T>> wrappers; \
 | 
			
		||||
    auto it = wrappers.find(base); \
 | 
			
		||||
    if (it != wrappers.end()) \
 | 
			
		||||
    { \
 | 
			
		||||
        return it->second.get(); \
 | 
			
		||||
    } \
 | 
			
		||||
    return (wrappers[base] = dts::make_unique<T>(base __VA_OPT__(,) __VA_ARGS__)).get()
 | 
			
		||||
 | 
			
		||||
type_identity *Checker::wrap_in_stl_ptr_vector(type_identity *base)
 | 
			
		||||
{
 | 
			
		||||
    RETURN_CACHED_WRAPPER(df::stl_ptr_vector_identity, base, nullptr);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type_identity *Checker::wrap_in_pointer(type_identity *base)
 | 
			
		||||
{
 | 
			
		||||
    RETURN_CACHED_WRAPPER(df::pointer_identity, base);
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,237 @@
 | 
			
		||||
#include "check-structures-sanity.h"
 | 
			
		||||
 | 
			
		||||
bool Checker::is_valid_dereference(const QueueItem & item, const CheckedStructure & cs, bool quiet)
 | 
			
		||||
{
 | 
			
		||||
    auto base = const_cast<void *>(item.ptr);
 | 
			
		||||
    auto size = cs.full_size();
 | 
			
		||||
    if (!base)
 | 
			
		||||
    {
 | 
			
		||||
        // cannot dereference null pointer, but not an error
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // assumes MALLOC_PERTURB_=45
 | 
			
		||||
#ifdef DFHACK64
 | 
			
		||||
#define UNINIT_PTR 0xd2d2d2d2d2d2d2d2
 | 
			
		||||
#define FAIL_PTR(message) FAIL(stl_sprintf("0x%016zx: ", reinterpret_cast<uintptr_t>(base)) << message)
 | 
			
		||||
#else
 | 
			
		||||
#define UNINIT_PTR 0xd2d2d2d2
 | 
			
		||||
#define FAIL_PTR(message) FAIL(stl_sprintf("0x%08zx: ", reinterpret_cast<uintptr_t>(base)) << message)
 | 
			
		||||
#endif
 | 
			
		||||
    if (uintptr_t(base) == UNINIT_PTR)
 | 
			
		||||
    {
 | 
			
		||||
        if (!quiet)
 | 
			
		||||
        {
 | 
			
		||||
            FAIL_PTR("uninitialized pointer");
 | 
			
		||||
        }
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool found = true;
 | 
			
		||||
    auto expected_start = base;
 | 
			
		||||
    size_t remaining_size = size;
 | 
			
		||||
    while (found)
 | 
			
		||||
    {
 | 
			
		||||
        found = false;
 | 
			
		||||
 | 
			
		||||
        for (auto & range : mapped)
 | 
			
		||||
        {
 | 
			
		||||
            if (!range.isInRange(expected_start))
 | 
			
		||||
            {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            found = true;
 | 
			
		||||
 | 
			
		||||
            if (!range.valid || !range.read)
 | 
			
		||||
            {
 | 
			
		||||
                if (!quiet)
 | 
			
		||||
                {
 | 
			
		||||
                    FAIL_PTR("pointer to invalid memory range");
 | 
			
		||||
                }
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            auto expected_end = const_cast<void *>(PTR_ADD(expected_start, remaining_size - 1));
 | 
			
		||||
            if (size && !range.isInRange(expected_end))
 | 
			
		||||
            {
 | 
			
		||||
                auto next_start = PTR_ADD(range.end, 1);
 | 
			
		||||
                remaining_size -= ptrdiff_t(next_start) - ptrdiff_t(expected_start);
 | 
			
		||||
                expected_start = const_cast<void *>(next_start);
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (quiet)
 | 
			
		||||
    {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (expected_start == base)
 | 
			
		||||
    {
 | 
			
		||||
        FAIL_PTR("pointer not in any mapped range");
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
        FAIL_PTR("pointer exceeds mapped memory bounds (size " << size << ")");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return false;
 | 
			
		||||
#undef FAIL_PTR
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const char *Checker::get_vtable_name(const QueueItem & item, const CheckedStructure & cs, bool quiet)
 | 
			
		||||
{
 | 
			
		||||
    auto vtable = validate_and_dereference<const void *const*>(QueueItem(item, "?vtable?", item.ptr), quiet);
 | 
			
		||||
    if (!vtable)
 | 
			
		||||
        return nullptr;
 | 
			
		||||
 | 
			
		||||
    auto info = validate_and_dereference<const char *const*>(QueueItem(item, "?vtable?.info", vtable - 1), quiet);
 | 
			
		||||
    if (!info)
 | 
			
		||||
        return nullptr;
 | 
			
		||||
 | 
			
		||||
#ifdef WIN32
 | 
			
		||||
#ifdef DFHACK64
 | 
			
		||||
    void *base;
 | 
			
		||||
    if (!RtlPcToFileHeader(info, &base))
 | 
			
		||||
        return nullptr;
 | 
			
		||||
 | 
			
		||||
    const char *typeinfo = reinterpret_cast<const char *>(base) + reinterpret_cast<int32_t *>(info)[3];
 | 
			
		||||
    const char *name = typeinfo + 16;
 | 
			
		||||
#else
 | 
			
		||||
    const char *name = reinterpret_cast<const char *>(info) + 8;
 | 
			
		||||
#endif
 | 
			
		||||
#else
 | 
			
		||||
    auto name = validate_and_dereference<const char *>(QueueItem(item, "?vtable?.info.name", info + 1), quiet);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
    for (auto & range : mapped)
 | 
			
		||||
    {
 | 
			
		||||
        if (!range.isInRange(const_cast<char *>(name)))
 | 
			
		||||
        {
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!range.valid || !range.read)
 | 
			
		||||
        {
 | 
			
		||||
            if (!quiet)
 | 
			
		||||
            {
 | 
			
		||||
                FAIL("pointer to invalid memory range");
 | 
			
		||||
            }
 | 
			
		||||
            return nullptr;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const char *first_letter = nullptr;
 | 
			
		||||
        bool letter = false;
 | 
			
		||||
        for (const char *p = name; ; p++)
 | 
			
		||||
        {
 | 
			
		||||
            if (!range.isInRange(const_cast<char *>(p)))
 | 
			
		||||
            {
 | 
			
		||||
                return nullptr;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if ((*p >= 'a' && *p <= 'z') || *p == '_')
 | 
			
		||||
            {
 | 
			
		||||
                if (!letter)
 | 
			
		||||
                {
 | 
			
		||||
                    first_letter = p;
 | 
			
		||||
                }
 | 
			
		||||
                letter = true;
 | 
			
		||||
            }
 | 
			
		||||
            else if (!*p)
 | 
			
		||||
            {
 | 
			
		||||
                return first_letter;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return nullptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::pair<const void *, size_t> Checker::validate_vector_size(const QueueItem & item, const CheckedStructure & cs, bool quiet)
 | 
			
		||||
{
 | 
			
		||||
    struct vector_data
 | 
			
		||||
    {
 | 
			
		||||
        uintptr_t start;
 | 
			
		||||
        uintptr_t finish;
 | 
			
		||||
        uintptr_t end_of_storage;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    vector_data vector = *reinterpret_cast<const vector_data *>(item.ptr);
 | 
			
		||||
 | 
			
		||||
    ptrdiff_t length = vector.finish - vector.start;
 | 
			
		||||
    ptrdiff_t capacity = vector.end_of_storage - vector.start;
 | 
			
		||||
 | 
			
		||||
    bool local_ok = true;
 | 
			
		||||
    auto item_size = cs.full_size();
 | 
			
		||||
    if (!item_size)
 | 
			
		||||
    {
 | 
			
		||||
        item_size = 1;
 | 
			
		||||
        local_ok = false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (vector.start > vector.finish)
 | 
			
		||||
    {
 | 
			
		||||
        local_ok = false;
 | 
			
		||||
        if (!quiet)
 | 
			
		||||
        {
 | 
			
		||||
            FAIL("vector length is negative (" << (length / ptrdiff_t(item_size)) << ")");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    if (vector.start > vector.end_of_storage)
 | 
			
		||||
    {
 | 
			
		||||
        local_ok = false;
 | 
			
		||||
        if (!quiet)
 | 
			
		||||
        {
 | 
			
		||||
            FAIL("vector capacity is negative (" << (capacity / ptrdiff_t(item_size)) << ")");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    else if (vector.finish > vector.end_of_storage)
 | 
			
		||||
    {
 | 
			
		||||
        local_ok = false;
 | 
			
		||||
        if (!quiet)
 | 
			
		||||
        {
 | 
			
		||||
            FAIL("vector capacity (" << (capacity / ptrdiff_t(item_size)) << ") is less than its length (" << (length / ptrdiff_t(item_size)) << ")");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    size_t ulength = size_t(length);
 | 
			
		||||
    size_t ucapacity = size_t(capacity);
 | 
			
		||||
    if (ulength % item_size != 0)
 | 
			
		||||
    {
 | 
			
		||||
        local_ok = false;
 | 
			
		||||
        if (!quiet)
 | 
			
		||||
        {
 | 
			
		||||
            FAIL("vector length is non-integer (" << (ulength / item_size) << " items plus " << (ulength % item_size) << " bytes)");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    if (ucapacity % item_size != 0)
 | 
			
		||||
    {
 | 
			
		||||
        local_ok = false;
 | 
			
		||||
        if (!quiet)
 | 
			
		||||
        {
 | 
			
		||||
            FAIL("vector capacity is non-integer (" << (ucapacity / item_size) << " items plus " << (ucapacity % item_size) << " bytes)");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (local_ok && capacity && !vector.start)
 | 
			
		||||
    {
 | 
			
		||||
        if (!quiet)
 | 
			
		||||
        {
 | 
			
		||||
            FAIL("vector has null pointer but capacity " << (capacity / item_size));
 | 
			
		||||
        }
 | 
			
		||||
        return std::make_pair(nullptr, 0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    auto start_ptr = reinterpret_cast<const void *>(vector.start);
 | 
			
		||||
 | 
			
		||||
    if (capacity && !is_valid_dereference(QueueItem(item, "?items?", start_ptr), CheckedStructure(cs.identity, capacity / item_size), quiet))
 | 
			
		||||
    {
 | 
			
		||||
        local_ok = false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return local_ok ? std::make_pair(start_ptr, ulength / item_size) : std::make_pair(nullptr, 0);
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in New Issue