| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693 |
- using Lua.Runtime;
- using static Lua.Internal.OpMode;
- using static Lua.Internal.OpArgMask;
- namespace Lua.Internal;
- internal readonly struct LuaDebug : IDisposable
- {
- readonly LuaDebugBuffer buffer;
- readonly uint version;
- LuaDebug(LuaDebugBuffer buffer, uint version)
- {
- this.buffer = buffer;
- this.version = version;
- }
- public string? Name
- {
- get
- {
- CheckVersion();
- return buffer.Name;
- }
- }
- public string? NameWhat
- {
- get
- {
- CheckVersion();
- return buffer.NameWhat;
- }
- }
- public string? What
- {
- get
- {
- CheckVersion();
- return buffer.What;
- }
- }
- public string? Source
- {
- get
- {
- CheckVersion();
- return buffer.Source;
- }
- }
- public int CurrentLine
- {
- get
- {
- CheckVersion();
- return buffer.CurrentLine;
- }
- }
- public int LineDefined
- {
- get
- {
- CheckVersion();
- return buffer.LineDefined;
- }
- }
- public int LastLineDefined
- {
- get
- {
- CheckVersion();
- return buffer.LastLineDefined;
- }
- }
- public int UpValueCount
- {
- get
- {
- CheckVersion();
- return buffer.UpValueCount;
- }
- }
- public int ParameterCount
- {
- get
- {
- CheckVersion();
- return buffer.ParameterCount;
- }
- }
- public bool IsVarArg
- {
- get
- {
- CheckVersion();
- return buffer.IsVarArg;
- }
- }
- public bool IsTailCall
- {
- get
- {
- CheckVersion();
- return buffer.IsTailCall;
- }
- }
- public ReadOnlySpan<char> ShortSource
- {
- get
- {
- CheckVersion();
- return buffer.ShortSource.AsSpan(0, buffer.ShortSourceLength);
- }
- }
- public static LuaDebug Create(LuaState state, CallStackFrame? prevFrame, CallStackFrame? frame, LuaFunction function, int pc, ReadOnlySpan<char> what, out bool isValid)
- {
- if (!state.DebugBufferPool.TryPop(out var buffer))
- {
- buffer = new(state);
- }
- isValid = buffer.GetInfo(prevFrame, frame, function, pc, what);
- return new(buffer, buffer.version);
- }
- public void CheckVersion()
- {
- if (buffer.version != version) ThrowObjectDisposedException();
- }
- public void Dispose()
- {
- if (buffer.version != version) ThrowObjectDisposedException();
- buffer.Return(version);
- }
- void ThrowObjectDisposedException()
- {
- throw new ObjectDisposedException("This has been disposed");
- }
- internal class LuaDebugBuffer(LuaState state)
- {
- internal uint version;
- LuaState state = state;
- public string? Name;
- public string? NameWhat;
- public string? What;
- public string? Source;
- public int CurrentLine;
- public int LineDefined;
- public int LastLineDefined;
- public int UpValueCount;
- public int ParameterCount;
- public bool IsVarArg;
- public bool IsTailCall;
- public readonly char[] ShortSource = new char[59];
- public int ShortSourceLength;
- internal void Return(uint version)
- {
- if (this.version != version) throw new ObjectDisposedException("Buffer has been modified");
- Name = null;
- NameWhat = null;
- What = null;
- Source = null;
- CurrentLine = 0;
- LineDefined = 0;
- LastLineDefined = 0;
- UpValueCount = 0;
- ParameterCount = 0;
- IsVarArg = false;
- IsTailCall = false;
- if (version < uint.MaxValue)
- {
- this.version++;
- state.DebugBufferPool.Push(this);
- }
- }
- internal bool GetInfo(CallStackFrame? prevFrame, CallStackFrame? frame, LuaFunction function, int pc, ReadOnlySpan<char> what)
- {
- LuaClosure? closure = function as LuaClosure;
- int status = 1;
- foreach (var c in what)
- {
- switch (c)
- {
- case 'S':
- {
- GetFuncInfo(function);
- break;
- }
- case 'l':
- {
- CurrentLine = (pc >= 0 && closure is not null) ? closure.Proto.LineInfo[pc] : -1;
- break;
- }
- case 'u':
- {
- UpValueCount = (closure is null) ? 0 : closure.UpValues.Length;
- if (closure is null)
- {
- IsVarArg = true;
- ParameterCount = 0;
- }
- else
- {
- IsVarArg = closure.Proto.HasVariableArguments;
- ParameterCount = closure.Proto.ParameterCount;
- }
- break;
- }
- case 't':
- {
- IsTailCall = frame.HasValue && (frame.Value.Flags | CallStackFrameFlags.TailCall) == frame.Value.Flags;
- break;
- }
- case 'n':
- {
- /* calling function is a known Lua function? */
- if (prevFrame is { Function: LuaClosure prevFrameClosure })
- NameWhat = GetFuncName(prevFrameClosure.Proto, frame?.CallerInstructionIndex ?? 0, out Name);
- else
- NameWhat = null;
- if (NameWhat is null)
- {
- NameWhat = ""; /* not found */
- Name = null;
- }
- else if (NameWhat != null && Name is "?")
- {
- Name = function.Name;
- }
- break;
- }
- case 'L':
- case 'f': /* handled by lua_getinfo */
- break;
- default:
- status = 0; /* invalid option */
- break;
- }
- }
- return status == 1;
- }
- void GetFuncInfo(LuaFunction f)
- {
- if (f is not LuaClosure cl)
- {
- Source = "=[C#]";
- LineDefined = -1;
- LastLineDefined = -1;
- What = "C#";
- }
- else
- {
- var p = cl.Proto;
- Source = p.ChunkName;
- LineDefined = p.LineDefined;
- LastLineDefined = p.LastLineDefined;
- What = (LineDefined == 0) ? "main" : "Lua";
- }
- ShortSourceLength = WriteShortSource(Source, ShortSource);
- }
- }
- internal static string? GetLocalName(Prototype prototype, int register, int pc)
- {
- var locals = prototype.LocalVariables;
- var localId = register + 1;
- foreach (var l in locals)
- {
- if (pc < l.StartPc) break;
- if (l.EndPc <= pc) continue;
- localId--;
- if (localId == 0)
- {
- return l.Name;
- }
- }
- return null;
- }
- static int FilterPc(int pc, int jmpTarget)
- {
- if (pc < jmpTarget) /* is code conditional (inside a jump)? */
- return -1; /* cannot know who sets that register */
- else return pc; /* current position sets that register */
- }
- internal static int FindSetRegister(Prototype prototype, int lastPc, int reg)
- {
- int pc;
- int setReg = -1; /* keep last instruction that changed 'reg' */
- int jmpTarget = 0; /* any code before this address is conditional */
- var instructions = prototype.Code;
- for (pc = 0; pc < lastPc; pc++)
- {
- Instruction i = instructions[pc];
- OpCode op = i.OpCode;
- int a = i.A;
- switch (op)
- {
- case OpCode.LoadNil:
- {
- int b = i.B;
- if (a <= reg && reg <= a + b) /* set registers from 'a' to 'a+b' */
- setReg = FilterPc(pc, jmpTarget);
- break;
- }
- case OpCode.TForCall:
- {
- if (reg >= a + 2) /* affect all regs above its base */
- setReg = FilterPc(pc, jmpTarget);
- break;
- }
- case OpCode.Call:
- case OpCode.TailCall:
- {
- if (reg >= a) /* affect all registers above base */
- setReg = FilterPc(pc, jmpTarget);
- break;
- }
- case OpCode.Jmp:
- {
- int b = i.SBx;
- int dest = pc + 1 + b;
- /* jump is forward and do not skip `lastpc'? */
- if (pc < dest && dest <= lastPc)
- {
- if (dest > jmpTarget)
- jmpTarget = dest; /* update 'jmptarget' */
- }
- break;
- }
- case OpCode.Test:
- {
- if (reg == a) /* jumped code can change 'a' */
- setReg = FilterPc(pc, jmpTarget);
- break;
- }
- default:
- if (TestAMode(op) && reg == a) /* any instruction that set A */
- setReg = FilterPc(pc, jmpTarget);
- break;
- }
- }
- return setReg;
- }
- static void GetConstantName(Prototype p, int pc, int c, out string name)
- {
- if (c >= 256)
- {
- /* is 'c' a constant? */
- var kvalue = p.Constants[c - 256];
- if (kvalue.TryReadString(out name))
- {
- /* literal constant? */
- /* it is its own name */
- return;
- }
- /* else no reasonable name found */
- }
- else
- {
- /* 'c' is a register */
- var what = GetName(p, pc, c, out name!); /* search for 'c' */
- if (what != null && what[0] == 'c')
- {
- /* found a constant name? */
- return; /* 'name' already filled */
- }
- /* else no reasonable name found */
- }
- name = "?"; /* no reasonable name found */
- }
- internal static string? GetName(Prototype prototype, int lastPc, int reg, out string? name)
- {
- name = GetLocalName(prototype, reg, lastPc);
- if (name != null)
- {
- return "local";
- }
- var pc = FindSetRegister(prototype, lastPc, reg);
- if (pc != -1)
- {
- /* could find instruction? */
- Instruction i = prototype.Code[pc];
- OpCode op = i.OpCode;
- switch (op)
- {
- case OpCode.Move:
- {
- int b = i.B; /* move from 'b' to 'a' */
- if (b < i.A)
- return GetName(prototype, pc, b, out name); /* get name for 'b' */
- break;
- }
- case OpCode.GetTabUp:
- case OpCode.GetTable:
- {
- int k = i.C; /* key index */
- int t = i.B; /* table index */
- var vn = (op == OpCode.GetTable) /* name of indexed variable */
- ? GetLocalName(prototype, t + 1, pc)
- : prototype.UpValues[t].Name.ToString();
- GetConstantName(prototype, pc, k, out name);
- return vn is "_ENV" ? "global" : "field";
- }
- case OpCode.GetUpVal:
- {
- name = prototype.UpValues[i.B].Name.ToString();
- return "upvalue";
- }
- case OpCode.LoadK:
- case OpCode.LoadKX:
- {
- int b = (op != OpCode.LoadKX)
- ? i.Bx
- : (prototype.Code[pc + 1].Ax);
- if (prototype.Constants[b].TryReadString(out name))
- {
- return "constant";
- }
- break;
- }
- case OpCode.Self:
- {
- int k = i.C; /* key index */
- GetConstantName(prototype, pc, k, out name);
- return "method";
- }
- default: break; /* go through to return NULL */
- }
- }
- return null; /* could not find reasonable name */
- }
- internal static string? GetFuncName(Prototype prototype, int pc, out string? name)
- {
- Instruction i = prototype.Code[pc]; /* calling instruction */
- switch (i.OpCode)
- {
- case OpCode.Call:
- case OpCode.TailCall: /* get function name */
- return GetName(prototype, pc, i.A, out name);
- case OpCode.TForCall:
- {
- /* for iterator */
- name = "for iterator";
- return "for iterator";
- }
- case OpCode.Self:
- case OpCode.GetTabUp:
- case OpCode.GetTable:
- name = "index";
- break;
- case OpCode.SetTabUp:
- case OpCode.SetTable:
- name = "newindex";
- break;
- case OpCode.Add:
- name = "add";
- break;
- case OpCode.Sub:
- name = "sub";
- break;
- case OpCode.Mul:
- name = "mul";
- break;
- case OpCode.Div:
- name = "div";
- break;
- case OpCode.Mod:
- name = "mod";
- break;
- case OpCode.Pow:
- name = "pow";
- break;
- case OpCode.Unm:
- name = "unm";
- break;
- case OpCode.Len:
- name = "len";
- break;
- case OpCode.Concat:
- name = "concat";
- break;
- case OpCode.Eq:
- name = "eq";
- break;
- case OpCode.Lt:
- name = "lt";
- break;
- case OpCode.Le:
- name = "le";
- break;
- default:
- name = null;
- return null;
- }
- return "metamethod";
- }
- internal static int WriteShortSource(ReadOnlySpan<char> source, Span<char> dest)
- {
- const string PRE = "[string \"";
- const int PRE_LEN = 9;
- const string POS = "\"]";
- const int POS_LEN = 2;
- const string RETS = "...";
- const int RETS_LEN = 3;
- const string PREPOS = "[string \"\"]";
- const int BUFFER_LEN = 59;
- if (dest.Length != BUFFER_LEN) throw new ArgumentException("dest must be 60 chars long");
- if (source.Length == 0)
- {
- PREPOS.AsSpan().CopyTo(dest);
- return PREPOS.Length;
- }
- if (source[0] == '=')
- {
- source = source[1..]; /* skip the '=' */
- /* 'literal' source */
- if (source.Length < BUFFER_LEN) /* small enough? */
- {
- source.CopyTo(dest);
- return source.Length;
- }
- else
- {
- /* truncate it */
- source[..BUFFER_LEN].CopyTo(dest);
- return BUFFER_LEN;
- }
- }
- else if (source[0] == '@')
- {
- /* file name */
- source = source[1..]; /* skip the '@' */
- if (source.Length <= BUFFER_LEN) /* small enough? */
- {
- source.CopyTo(dest);
- return source.Length;
- }
- else
- {
- /* add '...' before rest of name */
- RETS.AsSpan().CopyTo(dest);
- source[^(BUFFER_LEN - RETS_LEN)..].CopyTo(dest[RETS_LEN..]);
- return BUFFER_LEN;
- }
- }
- else
- {
- /* string; format as [string "source"] */
- PRE.AsSpan().CopyTo(dest);
- int newLine = source.IndexOfAny("\r\n");
- if (newLine == -1 && source.Length < BUFFER_LEN - (PRE_LEN + RETS_LEN + POS_LEN))
- {
- source.CopyTo(dest[PRE_LEN..]);
- POS.AsSpan().CopyTo(dest[(PRE_LEN + source.Length)..]);
- return PRE_LEN + source.Length + POS_LEN;
- }
- if (newLine != -1)
- {
- source = source[..newLine]; /* stop at first newline */
- }
- if (BUFFER_LEN - (PRE_LEN + RETS_LEN + POS_LEN) < source.Length)
- {
- source = source[..(BUFFER_LEN - PRE_LEN - RETS_LEN - POS_LEN)];
- }
- /* add '...' before rest of name */
- source.CopyTo(dest[PRE_LEN..]);
- RETS.AsSpan().CopyTo(dest[(PRE_LEN + source.Length)..]);
- POS.AsSpan().CopyTo(dest[(PRE_LEN + source.Length + RETS_LEN)..]);
- return PRE_LEN + source.Length + RETS_LEN + POS_LEN;
- }
- }
- static int GetOpMode(byte t, byte a, OpArgMask b, OpArgMask c, OpMode m) => (((t) << 7) | ((a) << 6) | (((byte)b) << 4) | (((byte)c) << 2) | ((byte)m));
- static readonly int[] OpModes =
- [
- GetOpMode(0, 1, OpArgR, OpArgN, iABC), /* OP_MOVE */
- GetOpMode(0, 1, OpArgK, OpArgN, iABx), /* OP_LOADK */
- GetOpMode(0, 1, OpArgN, OpArgN, iABx), /* OP_LOADKX */
- GetOpMode(0, 1, OpArgU, OpArgU, iABC), /* OP_LOADBOOL */
- GetOpMode(0, 1, OpArgU, OpArgN, iABC), /* OP_LOADNIL */
- GetOpMode(0, 1, OpArgU, OpArgN, iABC), /* OP_GETUPVAL */
- GetOpMode(0, 1, OpArgU, OpArgK, iABC), /* OP_GETTABUP */
- GetOpMode(0, 1, OpArgR, OpArgK, iABC), /* OP_GETTABLE */
- GetOpMode(0, 0, OpArgK, OpArgK, iABC), /* OP_SETTABUP */
- GetOpMode(0, 0, OpArgU, OpArgN, iABC), /* OP_SETUPVAL */
- GetOpMode(0, 0, OpArgK, OpArgK, iABC), /* OP_SETTABLE */
- GetOpMode(0, 1, OpArgU, OpArgU, iABC), /* OP_NEWTABLE */
- GetOpMode(0, 1, OpArgR, OpArgK, iABC), /* OP_SELF */
- GetOpMode(0, 1, OpArgK, OpArgK, iABC), /* OP_ADD */
- GetOpMode(0, 1, OpArgK, OpArgK, iABC), /* OP_SUB */
- GetOpMode(0, 1, OpArgK, OpArgK, iABC), /* OP_MUL */
- GetOpMode(0, 1, OpArgK, OpArgK, iABC), /* OP_DIV */
- GetOpMode(0, 1, OpArgK, OpArgK, iABC), /* OP_MOD */
- GetOpMode(0, 1, OpArgK, OpArgK, iABC), /* OP_POW */
- GetOpMode(0, 1, OpArgR, OpArgN, iABC), /* OP_UNM */
- GetOpMode(0, 1, OpArgR, OpArgN, iABC), /* OP_NOT */
- GetOpMode(0, 1, OpArgR, OpArgN, iABC), /* OP_LEN */
- GetOpMode(0, 1, OpArgR, OpArgR, iABC), /* OP_CONCAT */
- GetOpMode(0, 0, OpArgR, OpArgN, iAsBx), /* OP_JMP */
- GetOpMode(1, 0, OpArgK, OpArgK, iABC), /* OP_EQ */
- GetOpMode(1, 0, OpArgK, OpArgK, iABC), /* OP_LT */
- GetOpMode(1, 0, OpArgK, OpArgK, iABC), /* OP_LE */
- GetOpMode(1, 0, OpArgN, OpArgU, iABC), /* OP_TEST */
- GetOpMode(1, 1, OpArgR, OpArgU, iABC), /* OP_TESTSET */
- GetOpMode(0, 1, OpArgU, OpArgU, iABC), /* OP_CALL */
- GetOpMode(0, 1, OpArgU, OpArgU, iABC), /* OP_TAILCALL */
- GetOpMode(0, 0, OpArgU, OpArgN, iABC), /* OP_RETURN */
- GetOpMode(0, 1, OpArgR, OpArgN, iAsBx), /* OP_FORLOOP */
- GetOpMode(0, 1, OpArgR, OpArgN, iAsBx), /* OP_FORPREP */
- GetOpMode(0, 0, OpArgN, OpArgU, iABC), /* OP_TFORCALL */
- GetOpMode(0, 1, OpArgR, OpArgN, iAsBx), /* OP_TFORLOOP */
- GetOpMode(0, 0, OpArgU, OpArgU, iABC), /* OP_SETLIST */
- GetOpMode(0, 1, OpArgU, OpArgN, iABx), /* OP_CLOSURE */
- GetOpMode(0, 1, OpArgU, OpArgN, iABC), /* OP_VARARG */
- GetOpMode(0, 0, OpArgU, OpArgU, iAx), /* OP_EXTRAARG */
- ];
- internal static OpMode GetOpMode(OpCode m) => (OpMode)(OpModes[(int)m] & 3);
- internal static OpArgMask GetBMode(OpCode m) => (OpArgMask)((OpModes[(int)m] >> 4) & 3);
- internal static OpArgMask GetCMode(OpCode m) => (OpArgMask)((OpModes[(int)m] >> 2) & 3);
- internal static bool TestAMode(OpCode m) => (OpModes[(int)m] & (1 << 6)) != 0;
- internal static bool TestTMode(OpCode m) => (OpModes[(int)m] & (1 << 7)) != 0;
- }
- internal enum OpMode : byte
- {
- iABC,
- iABx,
- iAsBx,
- iAx
- }
- internal enum OpArgMask : byte
- {
- OpArgN, /* argument is not used */
- OpArgU, /* argument is used */
- OpArgR, /* argument is a register or a jump offset */
- OpArgK /* argument is a constant or register/constant */
- }
|