aboutsummaryrefslogtreecommitdiff
path: root/src/debug.hpp
blob: 9ebea1f1cc28b663408f323bce3f723c594f0ca0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
#pragma once

#include "lanesconf.h"
#include "luaerrors.hpp"
#include "unique.hpp"

// #################################################################################################

#if HAVE_LUA_ASSERT()

// file name, line number, function name
using SourceLocation = std::tuple<std::string_view, uint_least32_t, std::string_view>;
inline SourceLocation Where(std::source_location const& where_ = std::source_location::current())
{
    std::string_view const _path{ where_.file_name() };
    // drop the directory structure
    std::string_view const _fileName{ _path.substr(_path.find_last_of("\\/")+1) };
    std::string_view const _func{ where_.function_name() };
    return std::make_tuple(_fileName, where_.line(), _func);
}

inline void LUA_ASSERT_IMPL(lua_State* const L_, bool const cond_, std::string_view const& txt_, SourceLocation const& where_ = Where())
{
    if (!cond_) {
        auto const& [_file, _line, _func] = where_;
        raise_luaL_error(L_, "%s:%d: LUA_ASSERT '%s' IN %s", _file.data(), _line, txt_.data(), _func.data());
    }
}

#define LUA_ASSERT(L_, cond_) LUA_ASSERT_IMPL(L_, (cond_) ? true : false, #cond_)
#define LUA_ASSERT_CODE(code_) code_

class StackChecker final
{
    private:
    lua_State* const L;
    int oldtop;

    public:
    DECLARE_UNIQUE_TYPE(Relative, int);
    DECLARE_UNIQUE_TYPE(Absolute, int);

    // offer a way to bypass C assert during unit testing
    static inline bool CallsCassert{ true };

    StackChecker(lua_State* const L_, Relative const offset_, SourceLocation const& where_ = Where())
    : L{ L_ }
    , oldtop{ lua_gettop(L_) - offset_ }
    {
        if ((offset_ < 0) || (oldtop < 0)) {
            assert(!CallsCassert);
            auto const& [_file, _line, _func] = where_;
            raise_luaL_error(L, "%s:%d: STACK INIT ASSERT (%d not %d) IN %s", _file.data(), _line, lua_gettop(L), offset_, _func.data());
        }
    }

    StackChecker(lua_State* const L_, Absolute const pos_, SourceLocation const& where_ = Where())
    : L{ L_ }
    , oldtop{ 0 }
    {
        if (lua_gettop(L) != pos_) {
            assert(!CallsCassert);
            auto const& [_file, _line, _func] = where_;
            raise_luaL_error(L, "%s:%d: STACK INIT ASSERT (%d not %d) IN %s", _file.data(), _line, lua_gettop(L), pos_, _func.data());
        }
    }

    StackChecker& operator=(StackChecker const& rhs_)
    {
        assert(L == rhs_.L);
        oldtop = rhs_.oldtop;
        return *this;
    }

    // verify if the distance between the current top and the initial one is what we expect
    void check(int const expected_, SourceLocation const& where_ = Where())
    {
        if (expected_ != LUA_MULTRET) {
            int const _actual{ lua_gettop(L) - oldtop };
            if (_actual != expected_) {
                assert(!CallsCassert);
                auto const& [_file, _line, _func] = where_;
                raise_luaL_error(L, "%s:%d: STACK CHECK ASSERT (%d not %d) IN %s", _file.data(), _line, _actual, expected_, _func.data());
            }
        }
    }
};

#define STACK_CHECK_START_REL(L, offset_) \
    StackChecker _stackChecker_##L \
    { \
        L, StackChecker::Relative{ offset_ }, \
    }
#define STACK_CHECK_START_ABS(L, offset_) \
    StackChecker _stackChecker_##L \
    { \
        L, StackChecker::Absolute{ offset_ }, \
    }
#define STACK_CHECK_RESET_REL(L, offset_) \
    _stackChecker_##L = StackChecker \
    { \
        L, StackChecker::Relative{ offset_ }, \
    }
#define STACK_CHECK_RESET_ABS(L, offset_) \
    _stackChecker_##L = StackChecker \
    { \
        L, StackChecker::Absolute{ offset_ }, \
    }
#define STACK_CHECK(L, offset_) _stackChecker_##L.check(offset_)

#else // HAVE_LUA_ASSERT()

#define LUA_ASSERT(L_, c) ((void) 0) // nothing
#define LUA_ASSERT_CODE(code_) ((void) 0)

#define STACK_CHECK_START_REL(L_, offset_)
#define STACK_CHECK_START_ABS(L_, offset_)
#define STACK_CHECK_RESET_REL(L_, offset_)
#define STACK_CHECK_RESET_ABS(L_, offset_)
#define STACK_CHECK(L_, offset_)

#endif // HAVE_LUA_ASSERT()