summaryrefslogtreecommitdiff
path: root/indra/llcommon/lua_function.h
blob: 12e74b5d04ceb4f05fe51fbc48d6c9205213f718 (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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
/**
 * @file   lua_function.h
 * @author Nat Goodspeed
 * @date   2024-02-05
 * @brief  Definitions useful for coding a new Luau entry point into C++
 *
 * $LicenseInfo:firstyear=2024&license=viewerlgpl$
 * Copyright (c) 2024, Linden Research, Inc.
 * $/LicenseInfo$
 */

#if ! defined(LL_LUA_FUNCTION_H)
#define LL_LUA_FUNCTION_H

#include "luau/luacode.h"
#include "luau/lua.h"
#include "luau/luaconf.h"
#include "luau/lualib.h"
#include "fsyspath.h"
#include "llerror.h"
#include "llsd.h"
#include "stringize.h"
#include <exception>                // std::uncaught_exceptions()
#include <memory>                   // std::shared_ptr
#include <typeindex>
#include <typeinfo>
#include <unordered_map>
#include <utility>                  // std::pair

class LuaListener;

/*****************************************************************************
*   lluau namespace utility functions
*****************************************************************************/
namespace lluau
{
    // luau defines luaL_error() as void, but we want to use the Lua idiom of
    // 'return error(...)'. Wrap luaL_error() in an int function.
#if __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wformat-security"
#endif // __clang__
    template<typename... Args>
    int error(lua_State* L, const char* format, Args&&... args)
    {
        luaL_error(L, format, std::forward<Args>(args)...);
#ifndef LL_MSVC
        return 0;
#endif
    }
#if __clang__
#pragma clang diagnostic pop
#endif // __clang__

    // luau removed lua_dostring(), but since we perform the equivalent luau
    // sequence in multiple places, encapsulate it. desc and text are strings
    // rather than string_views because dostring() needs pointers to nul-
    // terminated char arrays.
    int dostring(lua_State* L, const std::string& desc, const std::string& text);
    int loadstring(lua_State* L, const std::string& desc, const std::string& text);

    fsyspath source_path(lua_State* L);
} // namespace lluau

// must be a macro because LL_PRETTY_FUNCTION is context-sensitive
#define lluau_checkstack(L, n) luaL_checkstack((L), (n), LL_PRETTY_FUNCTION)

std::string lua_tostdstring(lua_State* L, int index);
void lua_pushstdstring(lua_State* L, const std::string& str);
LLSD lua_tollsd(lua_State* L, int index);
void lua_pushllsd(lua_State* L, const LLSD& data);

/*****************************************************************************
*   LuaState
*****************************************************************************/
/**
 * RAII class to manage the lifespan of a lua_State
 */
class LuaState
{
public:
    typedef std::function<void(std::string msg)> script_finished_fn;

    LuaState(script_finished_fn cb={});

    LuaState(const LuaState&) = delete;
    LuaState& operator=(const LuaState&) = delete;

    ~LuaState();

    bool checkLua(const std::string& desc, int r);

    // expr() is for when we want to capture any results left on the stack
    // by a Lua expression, possibly including multiple return values.
    // int <  0 means error, and LLSD::asString() is the error message.
    // int == 0 with LLSD::isUndefined() means the Lua expression returned no
    //          results.
    // int == 1 means the Lua expression returned one result.
    // int >  1 with LLSD::isArray() means the Lua expression returned
    //          multiple results, represented as the entries of the array.
    std::pair<int, LLSD> expr(const std::string& desc, const std::string& text);

    operator lua_State*() const { return mState; }

    // Find or create LuaListener for this LuaState.
    LuaListener& obtainListener() { return obtainListener(mState); }
    // Find or create LuaListener for passed lua_State.
    static LuaListener& obtainListener(lua_State* L);

    // Given lua_State* L, return the LuaState object managing (the main Lua
    // thread for) L.
    static LuaState& getParent(lua_State* L);

    void set_interrupts_counter(S32 counter);
    void check_interrupts_counter();

private:
    /*---------------------------- feature flag ----------------------------*/
    bool mFeature{ false };
    /*---------------------------- feature flag ----------------------------*/
    script_finished_fn mCallback;
    lua_State* mState{ nullptr };
    std::string mError;
    S32 mInterrupts{ 0 };
};

/*****************************************************************************
*   LuaPopper
*****************************************************************************/
/**
 * LuaPopper is an RAII class whose role is to pop some number of entries
 * from the Lua stack if the calling function exits early.
 */
class LuaPopper
{
public:
    LuaPopper(lua_State* L, int count):
        mState(L),
        mCount(count)
    {}

    LuaPopper(const LuaPopper&) = delete;
    LuaPopper& operator=(const LuaPopper&) = delete;

    ~LuaPopper();

    void disarm() { set(0); }
    void set(int count) { mCount = count; }

private:
    lua_State* mState;
    int mCount;
};

/*****************************************************************************
*   LuaRemover
*****************************************************************************/
/**
 * Remove a particular stack index on exit from enclosing scope.
 * If you pass a negative index (meaning relative to the current stack top),
 * converts to an absolute index. The point of LuaRemover is to remove the
 * entry at the specified index regardless of subsequent pushes to the stack.
 */
class LuaRemover
{
public:
    LuaRemover(lua_State* L, int index):
        mState(L),
        mIndex(lua_absindex(L, index))
    {}
    LuaRemover(const LuaRemover&) = delete;
    LuaRemover& operator=(const LuaRemover&) = delete;
    ~LuaRemover()
    {
        lua_remove(mState, mIndex);
    }

private:
    lua_State* mState;
    int mIndex;
};

/*****************************************************************************
*   LuaStackDelta
*****************************************************************************/
/**
 * Instantiate LuaStackDelta in a block to compare the Lua data stack depth on
 * entry (LuaStackDelta construction) and exit. Optionally, pass the expected
 * depth increment. (But be aware that LuaStackDelta cannot observe the effect
 * of a LuaPopper or LuaRemover declared previously in the same block.)
 */
class LuaStackDelta
{
public:
    LuaStackDelta(lua_State* L, const std::string& where, int delta=0);
    LuaStackDelta(const LuaStackDelta&) = delete;
    LuaStackDelta& operator=(const LuaStackDelta&) = delete;

    ~LuaStackDelta();

private:
    lua_State* L;
    std::string mWhere;
    int mDepth, mDelta;
};

#define lua_checkdelta(L, ...) LuaStackDelta delta(L, LL_PRETTY_FUNCTION, ##__VA_ARGS__)

/*****************************************************************************
*   lua_push() wrappers for generic code
*****************************************************************************/
inline
void lua_push(lua_State* L, bool b)
{
    lua_pushboolean(L, int(b));
}

inline
void lua_push(lua_State* L, lua_CFunction fn)
{
    lua_pushcfunction(L, fn, "");
}

inline
void lua_push(lua_State* L, lua_Integer n)
{
    lua_pushinteger(L, n);
}

inline
void lua_push(lua_State* L, void* p)
{
    lua_pushlightuserdata(L, p);
}

inline
void lua_push(lua_State* L, const LLSD& data)
{
    lua_pushllsd(L, data);
}

inline
void lua_push(lua_State* L, const char* s, size_t len)
{
    lua_pushlstring(L, s, len);
}

inline
void lua_push(lua_State* L)
{
    lua_pushnil(L);
}

inline
void lua_push(lua_State* L, lua_Number n)
{
    lua_pushnumber(L, n);
}

inline
void lua_push(lua_State* L, const std::string& s)
{
    lua_pushstdstring(L, s);
}

inline
void lua_push(lua_State* L, const char* s)
{
    lua_pushstring(L, s);
}

/*****************************************************************************
*   lua_to() wrappers for generic code
*****************************************************************************/
template <typename T>
auto lua_to(lua_State* L, int index);

template <>
inline
auto lua_to<bool>(lua_State* L, int index)
{
    return lua_toboolean(L, index);
}

template <>
inline
auto lua_to<lua_CFunction>(lua_State* L, int index)
{
    return lua_tocfunction(L, index);
}

template <>
inline
auto lua_to<lua_Integer>(lua_State* L, int index)
{
    return lua_tointeger(L, index);
}

template <>
inline
auto lua_to<LLSD>(lua_State* L, int index)
{
    return lua_tollsd(L, index);
}

template <>
inline
auto lua_to<lua_Number>(lua_State* L, int index)
{
    return lua_tonumber(L, index);
}

template <>
inline
auto lua_to<std::string>(lua_State* L, int index)
{
    return lua_tostdstring(L, index);
}

template <>
inline
auto lua_to<void*>(lua_State* L, int index)
{
    return lua_touserdata(L, index);
}

/*****************************************************************************
*   field operations
*****************************************************************************/
// return to C++, from table at index, the value of field k
template <typename T>
auto lua_getfieldv(lua_State* L, int index, const char* k)
{
    lua_checkdelta(L);
    lluau_checkstack(L, 1);
    lua_getfield(L, index, k);
    LuaPopper pop(L, 1);
    return lua_to<T>(L, -1);
}

// set in table at index, as field k, the specified C++ value
template <typename T>
auto lua_setfieldv(lua_State* L, int index, const char* k, const T& value)
{
    index = lua_absindex(L, index);
    lua_checkdelta(L);
    lluau_checkstack(L, 1);
    lua_push(L, value);
    lua_setfield(L, index, k);
}

// return to C++, from table at index, the value of field k (without metamethods)
template <typename T>
auto lua_rawgetfield(lua_State* L, int index, std::string_view k)
{
    index = lua_absindex(L, index);
    lua_checkdelta(L);
    lluau_checkstack(L, 1);
    lua_pushlstring(L, k.data(), k.length());
    lua_rawget(L, index);
    LuaPopper pop(L, 1);
    return lua_to<T>(L, -1);
}

// set in table at index, as field k, the specified C++ value (without metamethods)
template <typename T>
void lua_rawsetfield(lua_State* L, int index, std::string_view k, const T& value)
{
    index = lua_absindex(L, index);
    lua_checkdelta(L);
    lluau_checkstack(L, 2);
    lua_pushlstring(L, k.data(), k.length());
    lua_push(L, value);
    lua_rawset(L, index);
}

/*****************************************************************************
*   lua_function (and helper class LuaFunction)
*****************************************************************************/
/**
 * LuaFunction is a base class containing a static registry of its static
 * subclass call() methods. call() is NOT virtual: instead, each subclass
 * constructor passes a pointer to its distinct call() method to the base-
 * class constructor, along with a name by which to register that method.
 *
 * The init() method walks the registry and registers each such name with the
 * passed lua_State.
 */
class LuaFunction
{
public:
    LuaFunction(std::string_view name, lua_CFunction function,
                std::string_view helptext);

    static void init(lua_State* L);

    static lua_CFunction get(const std::string& key);

protected:
    using Registry = std::map<std::string, std::pair<lua_CFunction, std::string>>;
    using Lookup = std::map<lua_CFunction, std::string>;
    static std::pair<const Registry&, const Lookup&> getRState() { return getState(); }

private:
    static std::pair<Registry&, Lookup&> getState();
};

/**
 * lua_function(name, helptext) is a macro to facilitate defining C++ functions
 * available to Lua. It defines a subclass of LuaFunction and declares a
 * static instance of that subclass, thereby forcing the compiler to call its
 * constructor at module initialization time. The constructor passes the
 * stringized instance name to its LuaFunction base-class constructor, along
 * with a pointer to the static subclass call() method. It then emits the
 * call() method definition header, to be followed by a method body enclosed
 * in curly braces as usual.
 */
#define lua_function(name, helptext)                        \
static struct name##_luasub : public LuaFunction            \
{                                                           \
    name##_luasub(): LuaFunction(#name, &call, helptext) {} \
    static int call(lua_State* L);                          \
} name##_lua;                                               \
int name##_luasub::call(lua_State* L)
// {
//     ... supply method body here, referencing 'L' ...
// }

/*****************************************************************************
*   lua_emplace<T>(), lua_toclass<T>()
*****************************************************************************/
// Every instance of DistinctInt has a different int value, barring int
// wraparound.
class DistinctInt
{
public:
    DistinctInt(): mValue(++mValues) {}
    int get() const { return mValue; }
    operator int() const { return mValue; }
private:
    static int mValues;
    int mValue;
};

namespace {

template <typename T>
struct TypeTag
{
    // For   (std::is_same<T, U>), &TypeTag<T>::value == &TypeTag<U>::value.
    // For (! std::is_same<T, U>), &TypeTag<T>::value != &TypeTag<U>::value.
    // And every distinct instance of DistinctInt has a distinct value.
    // Therefore, TypeTag<T>::value is an int uniquely associated with each
    // distinct T.
    static DistinctInt value;
};

template <typename T>
DistinctInt TypeTag<T>::value;

} // anonymous namespace

/**
 * On the stack belonging to the passed lua_State, push a Lua userdata object
 * containing a newly-constructed C++ object T(args...). The userdata has a
 * Luau destructor guaranteeing that the new T instance is destroyed when the
 * userdata is garbage-collected, no later than when the LuaState is
 * destroyed. It may be destroyed explicitly by calling lua_destroyuserdata().
 *
 * Usage:
 * lua_emplace<T>(L, T constructor args...);
 * // L's Lua stack top is now a userdata containing T
 */
template <class T, typename... ARGS>
void lua_emplace(lua_State* L, ARGS&&... args)
{
    lua_checkdelta(L, 1);
    lluau_checkstack(L, 1);
    int tag{ TypeTag<T>::value };
    if (! lua_getuserdatadtor(L, tag))
    {
        // We haven't yet told THIS lua_State the destructor to use for this tag.
        lua_setuserdatadtor(
            L, tag,
            [](lua_State*, void* ptr)
            {
                // destroy the contained T instance
                static_cast<T*>(ptr)->~T();
            });
    }
    auto ptr = lua_newuserdatatagged(L, sizeof(T), tag);
    // stack is uninitialized userdata
    // For now, assume (but verify) that lua_newuserdata() returns a
    // conservatively-aligned ptr. If that turns out not to be the case, we
    // might have to discard the new userdata, overallocate its successor and
    // perform manual alignment -- but only if we must.
    llassert((uintptr_t(ptr) % alignof(T)) == 0);
    // Construct our T there using placement new
    new (ptr) T(std::forward<ARGS>(args)...);
    // stack is now initialized userdata containing our T instance -- return
    // that
}

/**
 * If the value at the passed acceptable index is a full userdata created by
 * lua_emplace<T>(), return a pointer to the contained T instance. Otherwise
 * (index is not a full userdata; userdata is not of type T) return nullptr.
 */
template <class T>
T* lua_toclass(lua_State* L, int index)
{
    lua_checkdelta(L);
    // get void* pointer to userdata (if that's what it is)
    void* ptr{ lua_touserdatatagged(L, index, TypeTag<T>::value) };
    // Derive the T* from ptr. If in future lua_emplace() must manually
    // align our T* within the Lua-provided void*, adjust accordingly.
    return static_cast<T*>(ptr);
}

/**
 * Call lua_destroyuserdata() with the doomed userdata on the stack top.
 * It must have been created by lua_emplace().
 */
int lua_destroyuserdata(lua_State* L);

/**
 * Call lua_pushcclosure(L, lua_destroybounduserdata, 1) with the target
 * userdata on the stack top. When the resulting C closure is called with no
 * arguments, the bound userdata is destroyed by lua_destroyuserdata().
 */
int lua_destroybounduserdata(lua_State *L);

/*****************************************************************************
*   lua_what()
*****************************************************************************/
// Usage:  std::cout << lua_what(L, stackindex) << ...;
// Reports on the Lua value found at the passed stackindex.
// If cast to std::string, returns the corresponding string value.
class lua_what
{
public:
    lua_what(lua_State* state, int idx):
        L(state),
        index(idx)
    {}

    friend std::ostream& operator<<(std::ostream& out, const lua_what& self);

    operator std::string() const { return stringize(*this); }

private:
    lua_State* L;
    int index;
};

/*****************************************************************************
*   lua_stack()
*****************************************************************************/
// Usage:  std::cout << lua_stack(L) << ...;
// Reports on the contents of the Lua stack.
// If cast to std::string, returns the corresponding string value.
class lua_stack
{
public:
    lua_stack(lua_State* state):
        L(state)
    {}

    friend std::ostream& operator<<(std::ostream& out, const lua_stack& self);

    operator std::string() const { return stringize(*this); }

private:
    lua_State* L;
};

/*****************************************************************************
*   LuaLog
*****************************************************************************/
// adapted from indra/test/debug.h
// can't generalize Debug::operator() target because it's a variadic template
class LuaLog
{
public:
    template <typename... ARGS>
    LuaLog(lua_State* L, ARGS&&... args):
        L(L),
        mBlock(stringize(std::forward<ARGS>(args)...))
    {
        (*this)("entry ", lua_stack(L));
    }

    // non-copyable
    LuaLog(const LuaLog&) = delete;
    LuaLog& operator=(const LuaLog&) = delete;

    ~LuaLog()
    {
        auto exceptional{ std::uncaught_exceptions()? "exceptional " : "" };
        (*this)(exceptional, "exit ", lua_stack(L));
    }

    template <typename... ARGS>
    void operator()(ARGS&&... args)
    {
        LL_DEBUGS("Lua") << mBlock << ' ';
        stream_to(LL_CONT, std::forward<ARGS>(args)...);
        LL_ENDL;
    }

private:
    lua_State* L;
    const std::string mBlock;
};

#endif /* ! defined(LL_LUA_FUNCTION_H) */