mirror of
https://github.com/brues-code/ClassicAPI.git
synced 2026-11-10 20:07:37 +00:00
feat(baselib): debuglocals, and the error window shows locals
debuglocals([level [, skipFunctionsAndUserdata]]) returns the locals and
then the upvalues of a call level as text, one name=value line each.
It runs only while an error handler runs, as in 4.3.4. 1.12.1's error
function sets no flag for that, but while a handler runs that function
(FUN_FRAMESCRIPT_CALL_ERROR_HANDLER) is a C closure level on the stack;
ErrorCallstack's search for it moves into a header so both modules use
it. level counts as lua_getstack and debugstack do.
Each local comes from lua_getlocal (0x6FBB20, fastcall L/ar + n), then
each upvalue of the level's function (lua_getinfo "f") from
lua_getupvalue (0x6F4660, fastcall L/funcindex + n). Values are
formatted as 4.3.4's FrameScript_PackVariable does, with retail's
spacing (probed: n=7, s="hi", t=<table>{ / k=1 / }): a frame table shows
its name, read from t[0] as FrameScript_GetObject does; a table's fields
show one space in at the top level only; a function shows where it is
defined (lua_getinfo ">nS"). The text stops at 4095 bytes. The second
argument, which retail's error handler passes as true, leaves out
function and userdata values.
Where 4.3.4 would fail inside the handler, this does not: a number key
outside int range is formatted from a copy, not converted in place
(which breaks lua_next), and a key that is not a number or string prints
as its type.
Retail also lists (*temporary) lines, 5.1's names for a level's stack
slots past its locals, including a C level's arguments. 1.12.1's
lua_getlocal is 5.0's and names declared locals only; those slots are
compiler registers that 5.0 and 5.1 allocate differently, so they are
not imitated.
The error window takes the locals as retail's GetErrorData does,
debuglocals(level, true) at the stack's level, and shows them under
Locals ("<none>" when there are none).
LuaCAPI.md had lua_pushupvalues, lua_getupvalue and aux_upvalue one row
off; corrected.
This commit is contained in:
+27
@@ -678,6 +678,7 @@ build instructions.
|
||||
- [`difftime(time2 [, time1])`](#difftimetime2--time1)
|
||||
- [`xpcall(f, msgh [, arg1, ...])`](#xpcallf-msgh--arg1-)
|
||||
- [`GetCallstackHeight()` / `GetErrorCallstackHeight()` / `SetErrorCallstackHeight([height])`](#getcallstackheight--geterrorcallstackheight--seterrorcallstackheightheight)
|
||||
- [`debuglocals([level [, skipFunctionsAndUserdata]])`](#debuglocalslevel--skipfunctionsanduserdata)
|
||||
- [`collectgarbage(opt [, arg])`](#collectgarbageopt--arg)
|
||||
- [`table.wipe(t)` / `wipe(t)`](#tablewipet--wipet)
|
||||
- [`table.count(tbl)`](#tablecounttbl)
|
||||
@@ -16136,6 +16137,32 @@ end
|
||||
|
||||
Works in the world and on the login screen.
|
||||
|
||||
### `debuglocals([level [, skipFunctionsAndUserdata]])`
|
||||
|
||||
Returns the local variables and the upvalues of a call level as one string.
|
||||
An error handler uses it to show the values in the code that caused the
|
||||
error. `level` counts the same as in `debugstack`, and is 1 by default.
|
||||
|
||||
Each variable is one `name=value` line. A string value is in quotes. A
|
||||
table shows the name of its frame, or `<table>`, then its fields one space in,
|
||||
then `}`. Fields of tables inside a table do not show. A function shows the
|
||||
file and line where it is defined.
|
||||
|
||||
If `skipFunctionsAndUserdata` is `true`, function and userdata values do not
|
||||
show. The text stops at 4095 bytes.
|
||||
|
||||
`debuglocals` works only while an error handler runs. At other times it
|
||||
returns nothing.
|
||||
|
||||
```lua
|
||||
seterrorhandler(function(message)
|
||||
local level = GetCallstackHeight() - GetErrorCallstackHeight() + 1
|
||||
print(debuglocals(level, true))
|
||||
end)
|
||||
```
|
||||
|
||||
The error window shows these values under **Locals**.
|
||||
|
||||
### `collectgarbage(opt [, arg])`
|
||||
|
||||
Accepts Lua 5.1's string options. The stock 5.0 collector takes only an
|
||||
|
||||
+3
-3
@@ -178,9 +178,9 @@ behaviour the comment describes (`push 0; mov edx, 1; mov ecx, L`) is
|
||||
| `0x006F4450` | `lua_next` | fastcall | calls `luaH_next` at `0x006FA2A0` | ★ |
|
||||
| `0x006F44E0` | `lua_concat` | fastcall | calls `luaV_concat` at `0x006F84D0` | ★ |
|
||||
| `0x006F4560` | `lua_newuserdata` | fastcall | tag 7; calls `luaS_newudata` at `0x006F9E30` | ★ |
|
||||
| `0x006F45B0` | `lua_getupvalue` | fastcall | reads upvalue from C closure | ◦ |
|
||||
| `0x006F4660` | `lua_setupvalue` | fastcall | | ◦ |
|
||||
| `0x006F46E0` | *internal upvalue helper*| | shared by get/set above | ◦ |
|
||||
| `0x006F45B0` | `lua_pushupvalues` | fastcall | pushes the running C closure's upvalues, returns how many | ★ |
|
||||
| `0x006F4660` | `lua_getupvalue` | fastcall | `(L, funcindex, n)`: pushes upvalue n, returns its name or null | ★ |
|
||||
| `0x006F46E0` | `aux_upvalue` | | finds upvalue n of a function, returns its name | ★ |
|
||||
|
||||
## Auxiliary library (`luaL_*`)
|
||||
|
||||
|
||||
@@ -8795,8 +8795,20 @@ enum Offsets {
|
||||
LUA_GET_STACK = 0x006FBAA0,
|
||||
LUA_GET_INFO = 0x006FBC70,
|
||||
SIZEOF_LUA_DEBUG = 0x60,
|
||||
OFF_LUA_DEBUG_NAME = 0x04,
|
||||
OFF_LUA_DEBUG_SOURCE = 0x10,
|
||||
OFF_LUA_DEBUG_CURRENTLINE = 0x14,
|
||||
OFF_LUA_DEBUG_LINEDEFINED = 0x1C,
|
||||
// `lua_getlocal(L, ar, n)` — `const char *__fastcall` (n on the stack):
|
||||
// pushes local n of the call level ar names (ar+0x5C, i_ci) and returns
|
||||
// its name, or returns null and pushes nothing (no such local, or a C
|
||||
// level). No engine callers.
|
||||
LUA_GET_LOCAL = 0x006FBB20,
|
||||
// `lua_getupvalue(L, funcindex, n)` — `const char *__fastcall` (n on the
|
||||
// stack): pushes upvalue n of the function at funcindex and returns its
|
||||
// name ("" for a C closure), or null for no such upvalue or a value that
|
||||
// is not a function. (0x006F45B0 is lua_pushupvalues.)
|
||||
LUA_GET_UPVALUE = 0x006F4660,
|
||||
// `luaO_chunkid(out, source, bufflen)` — `void __fastcall(char *out /*ecx*/,
|
||||
// const char *source /*edx*/, int bufflen /*stack*/)`, RET 4: the name a
|
||||
// chunk has in error messages. `=name` is copied as is, `@file` is the
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
// This file is part of ClassicAPI.
|
||||
//
|
||||
// ClassicAPI is free software: you can redistribute it and/or modify it under the terms
|
||||
// of the GNU General Public License as published by the Free Software Foundation, either
|
||||
// version 3 of the License, or (at your option) any later version.
|
||||
//
|
||||
// ClassicAPI is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
// PURPOSE. See the GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along with
|
||||
// ClassicAPI. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// `debuglocals([level [, skipFunctionsAndUserdata]])` — the locals and then
|
||||
// the upvalues of a call level as text, one `name=value` line each, for an
|
||||
// error report. 4.3.4's Script_debuglocals and the FrameScript_PackVariable
|
||||
// it formats with, in retail's spacing (probed: `n=7`, `s="hi"`,
|
||||
// `t=<table>{`, ` k=1`, `}`; 4.3.4 writes `n = 7` and `t = <table> {`):
|
||||
// - Only while an error handler runs; otherwise it returns nothing. 4.3.4
|
||||
// tests a flag its error function sets around the handler call. 1.12's
|
||||
// error function sets none, but while a handler runs that function's
|
||||
// level is on the stack (ErrorCallstack::ErrorFunctionLevel).
|
||||
// - `level` counts as lua_getstack and debugstack do: 1 is the caller.
|
||||
// Less than 1 is 1.
|
||||
// - A table prints as `name=<table>{`, or with the frame's name for a
|
||||
// frame (t[0], as FrameScript_GetObject reads it), then its fields one
|
||||
// space in at the top level only, then `}`. A function prints where it
|
||||
// is defined. The text stops at 4095 bytes, each line at 1023.
|
||||
// Retail's second argument, which its Blizzard_ScriptErrors passes as true,
|
||||
// leaves out the function and userdata values, at the top level and in
|
||||
// tables.
|
||||
//
|
||||
// A number key in int range prints truncated with %d, as 4.3.4's
|
||||
// IsIntegerNumber tests only the range: 2.5 prints as 2. Two departures from
|
||||
// 4.3.4, both where it would fail inside the handler: a number key outside
|
||||
// that range is formatted from a copy, not converted in place (that breaks
|
||||
// lua_next), and a key that is not a number or string prints as its type in
|
||||
// angle brackets (4.3.4 formats a null string).
|
||||
//
|
||||
// Retail also lists `(*temporary)` lines: 5.1's lua_getlocal names each stack
|
||||
// slot past a level's locals so, a C level's arguments included (an
|
||||
// `error("x")` report shows `(*temporary)="x"`). 1.12's lua_getlocal
|
||||
// (0x6FBB20) is 5.0's, which names declared locals only. Those slots are
|
||||
// the compiler's registers, which 5.0 and 5.1 allocate differently, so they
|
||||
// are left out rather than imitated.
|
||||
//
|
||||
// Registered on both Lua states, like the base-library helpers.
|
||||
|
||||
#include "Game.h"
|
||||
#include "Offsets.h"
|
||||
#include "baselib/ErrorCallstack.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
namespace BaseLib::DebugLocals {
|
||||
|
||||
namespace {
|
||||
|
||||
namespace Lua = Game::Lua;
|
||||
|
||||
using lua_getlocal_t = const char *(__fastcall *)(void *L, const void *ar, int n);
|
||||
using lua_getupvalue_t = const char *(__fastcall *)(void *L, int funcindex, int n);
|
||||
|
||||
const auto GetLocal = reinterpret_cast<lua_getlocal_t>(Offsets::LUA_GET_LOCAL);
|
||||
const auto GetUpvalue = reinterpret_cast<lua_getupvalue_t>(Offsets::LUA_GET_UPVALUE);
|
||||
|
||||
constexpr size_t kTextMax = 4095; // 4.3.4's 4096-byte buffer, less its NUL
|
||||
constexpr size_t kLineSize = 1024;
|
||||
|
||||
struct Packer {
|
||||
void *L;
|
||||
bool skipFunctionsAndUserdata;
|
||||
std::string text;
|
||||
|
||||
void Line(const char *fmt, const char *indent, const char *name, const char *value = "",
|
||||
int number = 0) {
|
||||
char line[kLineSize];
|
||||
std::snprintf(line, sizeof line, fmt, indent, name, value, number);
|
||||
const size_t room = kTextMax - text.size();
|
||||
text.append(line, std::min(room, std::strlen(line)));
|
||||
}
|
||||
|
||||
// The value on the stack top, as 4.3.4's FrameScript_PackVariable.
|
||||
void Pack(const char *name, int depth) {
|
||||
const char *indent = depth > 0 ? " " : "";
|
||||
switch (Lua::Type(L, -1)) {
|
||||
case Lua::TYPE_NIL:
|
||||
Line("%s%s=nil\n", indent, name);
|
||||
break;
|
||||
case Lua::TYPE_BOOLEAN:
|
||||
Line("%s%s=%s\n", indent, name, Lua::ToBoolean(L, -1) ? "true" : "false");
|
||||
break;
|
||||
case Lua::TYPE_LIGHTUSERDATA:
|
||||
case Lua::TYPE_USERDATA:
|
||||
if (!skipFunctionsAndUserdata)
|
||||
Line("%s%s=<userdata>\n", indent, name);
|
||||
break;
|
||||
case Lua::TYPE_NUMBER:
|
||||
Line("%s%s=%s\n", indent, name, Lua::ToString(L, -1)); // a copy: safe to convert
|
||||
break;
|
||||
case Lua::TYPE_STRING:
|
||||
Line("%s%s=\"%s\"\n", indent, name, Lua::ToString(L, -1));
|
||||
break;
|
||||
case Lua::TYPE_TABLE:
|
||||
PackTable(name, depth, indent);
|
||||
break;
|
||||
case Lua::TYPE_FUNCTION:
|
||||
if (!skipFunctionsAndUserdata)
|
||||
PackFunction(name, indent);
|
||||
break;
|
||||
default:
|
||||
Line("%s%s=<%s>\n", indent, name, Lua::TypeName(L, Lua::Type(L, -1)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void PackTable(const char *name, int depth, const char *indent) {
|
||||
void *object = Lua::ResolveObject(L, -1);
|
||||
Line("%s%s=%s{\n", indent, name, object ? Lua::ObjectDisplayName(object) : "<table>");
|
||||
if (depth == 0) {
|
||||
Lua::PushNil(L);
|
||||
while (Lua::Next(L, -2) != 0) {
|
||||
char key[32];
|
||||
const char *keyName = key;
|
||||
const int keyType = Lua::Type(L, -2);
|
||||
const double number = keyType == Lua::TYPE_NUMBER ? Lua::ToNumber(L, -2) : 0;
|
||||
if (keyType == Lua::TYPE_NUMBER && std::isfinite(number) && number >= -2147483648.0 &&
|
||||
number <= 2147483647.0) {
|
||||
std::snprintf(key, sizeof key, "%d", static_cast<int>(number));
|
||||
} else if (keyType == Lua::TYPE_STRING) {
|
||||
keyName = Lua::ToString(L, -2);
|
||||
} else if (keyType == Lua::TYPE_NUMBER) {
|
||||
Lua::PushValue(L, -2);
|
||||
std::snprintf(key, sizeof key, "%s", Lua::ToString(L, -1));
|
||||
Lua::SetTop(L, -2);
|
||||
} else {
|
||||
std::snprintf(key, sizeof key, "<%s>", Lua::TypeName(L, keyType));
|
||||
}
|
||||
Pack(keyName, 1);
|
||||
Lua::SetTop(L, -2); // the value; the key stays for lua_next
|
||||
}
|
||||
}
|
||||
Line("%s}\n", indent, "");
|
||||
}
|
||||
|
||||
void PackFunction(const char *name, const char *indent) {
|
||||
alignas(4) uint8_t ar[Offsets::SIZEOF_LUA_DEBUG];
|
||||
Lua::PushValue(L, -1);
|
||||
Lua::GetInfo(L, ">nS", ar); // pops the copy
|
||||
const auto field = [&ar](uintptr_t offset) { return Game::Read<const char *>(ar, offset); };
|
||||
const char *source = field(Offsets::OFF_LUA_DEBUG_SOURCE);
|
||||
const int line = Game::Read<int>(ar, Offsets::OFF_LUA_DEBUG_LINEDEFINED);
|
||||
if (const char *fn = field(Offsets::OFF_LUA_DEBUG_NAME)) {
|
||||
char named[kLineSize];
|
||||
std::snprintf(named, sizeof named, "%s() defined %s", fn, source ? source : "?");
|
||||
Line("%s%s=%s:%d\n", indent, name, named, line);
|
||||
} else {
|
||||
Line("%s%s=<function> defined %s:%d\n", indent, name, source ? source : "?", line);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
int __fastcall Script_debuglocals(void *L) {
|
||||
if (ErrorCallstack::ErrorFunctionLevel(L) == 0)
|
||||
return 0;
|
||||
int level = static_cast<int>(Lua::ToNumber(L, 1));
|
||||
if (level <= 0)
|
||||
level = 1;
|
||||
Packer packer{L, Lua::ToBoolean(L, 2) != 0, {}};
|
||||
|
||||
alignas(4) uint8_t ar[Offsets::SIZEOF_LUA_DEBUG];
|
||||
if (Lua::GetStack(L, level, ar)) {
|
||||
const char *name;
|
||||
for (int i = 1; (name = GetLocal(L, ar, i)) != nullptr; i++) {
|
||||
packer.Pack(name, 0);
|
||||
Lua::SetTop(L, -2);
|
||||
}
|
||||
Lua::GetInfo(L, "f", ar); // the level's function
|
||||
for (int i = 1; (name = GetUpvalue(L, -1, i)) != nullptr; i++) {
|
||||
packer.Pack(name, 0);
|
||||
Lua::SetTop(L, -2);
|
||||
}
|
||||
Lua::SetTop(L, -2);
|
||||
}
|
||||
Lua::PushLString(L, packer.text.data(), static_cast<unsigned int>(packer.text.size()));
|
||||
return 1;
|
||||
}
|
||||
|
||||
const Game::Doc::Field kDebuglocalsArgs[] = {
|
||||
Game::Doc::Opt("level", "number", "1", "The call level, as debugstack counts it."),
|
||||
Game::Doc::Opt("skipFunctionsAndUserdata", "boolean", "false",
|
||||
"Leaves out function and userdata values."),
|
||||
};
|
||||
const Game::Doc::Field kDebuglocalsRets[] = {
|
||||
Game::Doc::Opt("locals", "string", nullptr,
|
||||
"One `name=value` line for each local and upvalue. Nothing outside an "
|
||||
"error handler."),
|
||||
};
|
||||
const Game::Doc::Function kDebuglocals{
|
||||
"The local variables and upvalues of a call level as text, for an error report.",
|
||||
kDebuglocalsArgs, kDebuglocalsRets, "LuaGlobals"};
|
||||
|
||||
void RegisterInGame() {
|
||||
Game::Lua::RegisterGlobalFunction("debuglocals", &Script_debuglocals, &kDebuglocals);
|
||||
}
|
||||
|
||||
void RegisterGlue() {
|
||||
Game::Lua::RegisterGlueFunction("debuglocals", &Script_debuglocals, &kDebuglocals);
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterInGame};
|
||||
const Game::GlueModuleAutoRegister _autoregGlue{&RegisterGlue};
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace BaseLib::DebugLocals
|
||||
@@ -40,12 +40,26 @@
|
||||
|
||||
#include "Game.h"
|
||||
#include "Offsets.h"
|
||||
#include "baselib/ErrorCallstack.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
namespace BaseLib::ErrorCallstack {
|
||||
|
||||
int ErrorFunctionLevel(void *L) {
|
||||
const auto errorFunction = reinterpret_cast<Game::Lua::CFunction>(Offsets::FUN_FRAMESCRIPT_CALL_ERROR_HANDLER);
|
||||
alignas(4) uint8_t ar[Offsets::SIZEOF_LUA_DEBUG];
|
||||
for (int level = 1; Game::Lua::GetStack(L, level, ar); ++level) {
|
||||
Game::Lua::GetInfo(L, "f", ar); // the level's function; nil for a lost tail call
|
||||
const bool found = Game::Lua::ToCFunction(L, -1) == errorFunction;
|
||||
Game::Lua::SetTop(L, -2);
|
||||
if (found)
|
||||
return level;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
std::optional<int> g_setHeight;
|
||||
@@ -59,21 +73,6 @@ int Height(void *L) {
|
||||
return height;
|
||||
}
|
||||
|
||||
// The innermost level whose function is the engine's error function, or 0
|
||||
// when no error is being reported.
|
||||
int ErrorFunctionLevel(void *L) {
|
||||
const auto errorFunction = reinterpret_cast<Game::Lua::CFunction>(Offsets::FUN_FRAMESCRIPT_CALL_ERROR_HANDLER);
|
||||
alignas(4) uint8_t ar[Offsets::SIZEOF_LUA_DEBUG];
|
||||
for (int level = 1; Game::Lua::GetStack(L, level, ar); ++level) {
|
||||
Game::Lua::GetInfo(L, "f", ar); // the level's function; nil for a lost tail call
|
||||
const bool found = Game::Lua::ToCFunction(L, -1) == errorFunction;
|
||||
Game::Lua::SetTop(L, -2);
|
||||
if (found)
|
||||
return level;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int __fastcall Script_GetCallstackHeight(void *L) {
|
||||
Game::Lua::PushNumber(L, Height(L));
|
||||
return 1;
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
// This file is part of ClassicAPI.
|
||||
//
|
||||
// ClassicAPI is free software: you can redistribute it and/or modify it under the terms
|
||||
// of the GNU General Public License as published by the Free Software Foundation, either
|
||||
// version 3 of the License, or (at your option) any later version.
|
||||
//
|
||||
// ClassicAPI is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
// PURPOSE. See the GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along with
|
||||
// ClassicAPI. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace BaseLib::ErrorCallstack {
|
||||
|
||||
// The innermost call level whose function is the engine's error function
|
||||
// (FUN_FRAMESCRIPT_CALL_ERROR_HANDLER), or 0 when no error is being
|
||||
// reported. Nonzero exactly while an error handler runs.
|
||||
int ErrorFunctionLevel(void *L);
|
||||
|
||||
} // namespace BaseLib::ErrorCallstack
|
||||
@@ -23,15 +23,15 @@
|
||||
-- - The text is a ScrollingEditBoxTemplate with a MinimalScrollBar, the
|
||||
-- ScrollBox pair retail uses for scrolling edit boxes, not retail's
|
||||
-- ScrollFrameTemplate, which ClassicAPI does not port.
|
||||
-- - The stack is retail's GetErrorData without the locals, taken in the
|
||||
-- SetText slot: GetErrorCallstackHeight finds the code that failed
|
||||
-- however many handlers sit between it and the slot. 1.12 has no
|
||||
-- debuglocals.
|
||||
-- - The stack and locals are retail's GetErrorData, taken in the SetText
|
||||
-- slot: GetErrorCallstackHeight finds the code that failed however many
|
||||
-- handlers sit between it and the slot. No |K strings to remove, and no
|
||||
-- C_StringUtil.EscapeDecimalNonPrintables.
|
||||
-- - No scriptErrors cvar (1.12 always shows errors), no LUA_WARNING, no
|
||||
-- gamepad legend.
|
||||
-- - Lua 5.0: no `...` expression, no # operator.
|
||||
|
||||
local function GetErrorStack()
|
||||
local function GetErrorData()
|
||||
-- Example of how debug stack level is calculated
|
||||
-- Current stack: [1, 2, 3, 4, 5] (current function is at 1, total current height is 5)
|
||||
-- Stack at time of error: [1, 2] (these are currently now index 4 and 5, but at the time of error the stack height is 2)
|
||||
@@ -40,13 +40,20 @@ local function GetErrorStack()
|
||||
local errorCallStackHeight = GetErrorCallstackHeight();
|
||||
local errorStackOffset = errorCallStackHeight and (errorCallStackHeight - 1);
|
||||
local debugStackLevel = currentStackHeight - (errorStackOffset or 0);
|
||||
return debugstack(debugStackLevel);
|
||||
local skipFunctionsAndUserdata = true;
|
||||
|
||||
local stack = debugstack(debugStackLevel);
|
||||
local locals = debuglocals(debugStackLevel, skipFunctionsAndUserdata);
|
||||
|
||||
return stack, locals;
|
||||
end
|
||||
|
||||
local ERROR_FORMAT = "|cffffd200Message:|r|cffffffff %s|r\n"
|
||||
.. "|cffffd200Time:|r|cffffffff %s|r\n"
|
||||
.. "|cffffd200Count:|r|cffffffff %s|r\n"
|
||||
.. "|cffffd200Stack:|r\n"
|
||||
.. "|cffffffff%s|r\n"
|
||||
.. "|cffffd200Locals:|r\n"
|
||||
.. "|cffffffff%s|r";
|
||||
|
||||
local INDEX_ORDER_FORMAT = "%d / %d";
|
||||
@@ -68,15 +75,16 @@ function ScriptErrorsFrame_OnLoad(self)
|
||||
ScrollUtil.RegisterScrollBoxWithScrollBar(scrollBox, self.ScrollBar);
|
||||
|
||||
ScriptErrors_Message.SetText = function(_, message)
|
||||
ScriptErrorsFrame_OnError(message, GetErrorStack());
|
||||
ScriptErrorsFrame_OnError(message, GetErrorData());
|
||||
end
|
||||
ScriptErrors.Show = function() end
|
||||
end
|
||||
|
||||
function ScriptErrorsFrame_OnError(message, stack)
|
||||
function ScriptErrorsFrame_OnError(message, stack, locals)
|
||||
local self = ScriptErrorsFrame;
|
||||
message = tostring(message);
|
||||
stack = stack or "";
|
||||
locals = locals or "<none>";
|
||||
|
||||
local messageKey = message .. "\n" .. stack;
|
||||
local index = self.seen[messageKey];
|
||||
@@ -84,7 +92,7 @@ function ScriptErrorsFrame_OnError(message, stack)
|
||||
local errorData = self.errorData[index];
|
||||
errorData.count = errorData.count + 1;
|
||||
else
|
||||
table.insert(self.errorData, { count = 1, message = message, time = date(), stack = stack });
|
||||
table.insert(self.errorData, { count = 1, message = message, time = date(), stack = stack, locals = locals });
|
||||
index = table.getn(self.errorData);
|
||||
self.seen[messageKey] = index;
|
||||
end
|
||||
@@ -106,7 +114,7 @@ function ScriptErrorsFrame_Update()
|
||||
-- SetText puts the cursor and the scroll back at the top.
|
||||
local errorData = self.errorData[self.index];
|
||||
if ( errorData ) then
|
||||
self.ScrollingEditBox:SetText(string.format(ERROR_FORMAT, errorData.message, errorData.time, errorData.count, errorData.stack));
|
||||
self.ScrollingEditBox:SetText(string.format(ERROR_FORMAT, errorData.message, errorData.time, errorData.count, errorData.stack, errorData.locals));
|
||||
self.ScrollingEditBox:GetEditBox():HighlightText(0);
|
||||
else
|
||||
self.ScrollingEditBox:ClearText();
|
||||
|
||||
Reference in New Issue
Block a user