DebugLibrary.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652
  1. using System.Runtime.CompilerServices;
  2. using Lua.Runtime;
  3. using Lua.Internal;
  4. namespace Lua.Standard;
  5. public class DebugLibrary
  6. {
  7. public static readonly DebugLibrary Instance = new();
  8. public DebugLibrary()
  9. {
  10. Functions =
  11. [
  12. new("getlocal", GetLocal),
  13. new("setlocal", SetLocal),
  14. new("getupvalue", GetUpValue),
  15. new("setupvalue", SetUpValue),
  16. new("getmetatable", GetMetatable),
  17. new("setmetatable", SetMetatable),
  18. new("getuservalue", GetUserValue),
  19. new("setuservalue", SetUserValue),
  20. new("traceback", Traceback),
  21. new("getregistry", GetRegistry),
  22. new("upvalueid", UpValueId),
  23. new("upvaluejoin", UpValueJoin),
  24. new("gethook", GetHook),
  25. new("sethook", SetHook),
  26. new("getinfo", GetInfo),
  27. ];
  28. }
  29. public readonly LuaFunction[] Functions;
  30. static LuaThread GetLuaThread(in LuaFunctionExecutionContext context, out int argOffset)
  31. {
  32. if (context.ArgumentCount < 1)
  33. {
  34. argOffset = 0;
  35. return context.Thread;
  36. }
  37. if (context.GetArgument(0).TryRead<LuaThread>(out var thread))
  38. {
  39. argOffset = 1;
  40. return thread;
  41. }
  42. argOffset = 0;
  43. return context.Thread;
  44. }
  45. static ref LuaValue FindLocal(LuaThread thread, int level, int index, out string? name)
  46. {
  47. if (index == 0)
  48. {
  49. name = null;
  50. return ref Unsafe.NullRef<LuaValue>();
  51. }
  52. var callStack = thread.GetCallStackFrames();
  53. var frame = callStack[^(level + 1)];
  54. if (index < 0)
  55. {
  56. index = -index - 1;
  57. var frameVariableArgumentCount = frame.VariableArgumentCount;
  58. if (frameVariableArgumentCount > 0 && index < frameVariableArgumentCount)
  59. {
  60. name = "(*vararg)";
  61. return ref thread.Stack.Get(frame.Base - frameVariableArgumentCount + index);
  62. }
  63. name = null;
  64. return ref Unsafe.NullRef<LuaValue>();
  65. }
  66. index -= 1;
  67. var frameBase = frame.Base;
  68. if (frame.Function is LuaClosure closure)
  69. {
  70. var locals = closure.Proto.Locals;
  71. var nextFrame = callStack[^level];
  72. var currentPc = nextFrame.CallerInstructionIndex;
  73. {
  74. int nextFrameBase = (closure.Proto.Instructions[currentPc].OpCode is OpCode.Call or OpCode.TailCall) ? nextFrame.Base - 1 : nextFrame.Base;
  75. if (nextFrameBase - 1 < frameBase + index)
  76. {
  77. name = null;
  78. return ref Unsafe.NullRef<LuaValue>();
  79. }
  80. }
  81. foreach (var local in locals)
  82. {
  83. if (local.Index == index && currentPc >= local.StartPc && currentPc < local.EndPc)
  84. {
  85. name = local.Name.ToString();
  86. return ref thread.Stack.Get(frameBase + local.Index);
  87. }
  88. if (local.Index > index)
  89. {
  90. break;
  91. }
  92. }
  93. }
  94. else
  95. {
  96. int nextFrameBase = level != 0 ? callStack[^level].Base : thread.Stack.Count;
  97. if (nextFrameBase - 1 < frameBase + index)
  98. {
  99. name = null;
  100. return ref Unsafe.NullRef<LuaValue>();
  101. }
  102. }
  103. name = "(*temporary)";
  104. return ref thread.Stack.Get(frameBase + index);
  105. }
  106. public ValueTask<int> GetLocal(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
  107. {
  108. static LuaValue GetParam(LuaFunction function, int index)
  109. {
  110. if (function is LuaClosure closure)
  111. {
  112. var paramCount = closure.Proto.ParameterCount;
  113. if (0 <= index && index < paramCount)
  114. {
  115. return closure.Proto.Locals[index].Name.ToString();
  116. }
  117. }
  118. return LuaValue.Nil;
  119. }
  120. var thread = GetLuaThread(context, out var argOffset);
  121. var index = context.GetArgument<int>(argOffset + 1);
  122. if (context.GetArgument(argOffset).TryReadFunction(out var f))
  123. {
  124. buffer.Span[0] = GetParam(f, index - 1);
  125. return new(1);
  126. }
  127. var level = context.GetArgument<int>(argOffset);
  128. if (level < 0 || level >= thread.GetCallStackFrames().Length)
  129. {
  130. context.ThrowBadArgument(1, "level out of range");
  131. }
  132. ref var local = ref FindLocal(thread, level, index, out var name);
  133. if (name is null)
  134. {
  135. buffer.Span[0] = LuaValue.Nil;
  136. return new(1);
  137. }
  138. buffer.Span[0] = name;
  139. buffer.Span[1] = local;
  140. return new(2);
  141. }
  142. public ValueTask<int> SetLocal(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
  143. {
  144. var thread = GetLuaThread(context, out var argOffset);
  145. var value = context.GetArgument(argOffset + 2);
  146. var index = context.GetArgument<int>(argOffset + 1);
  147. var level = context.GetArgument<int>(argOffset);
  148. if (level < 0 || level >= thread.GetCallStackFrames().Length)
  149. {
  150. context.ThrowBadArgument(1, "level out of range");
  151. }
  152. ref var local = ref FindLocal(thread, level, index, out var name);
  153. if (name is null)
  154. {
  155. buffer.Span[0] = LuaValue.Nil;
  156. return new(1);
  157. }
  158. buffer.Span[0] = name;
  159. local = value;
  160. return new(1);
  161. }
  162. public ValueTask<int> GetUpValue(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
  163. {
  164. var func = context.GetArgument<LuaFunction>(0);
  165. var index = context.GetArgument<int>(1) - 1;
  166. if (func is not LuaClosure closure)
  167. {
  168. if (func is CSharpClosure csClosure)
  169. {
  170. var upValues = csClosure.UpValues;
  171. if (index < 0 || index >= upValues.Length)
  172. {
  173. return new(0);
  174. }
  175. buffer.Span[0] = "";
  176. buffer.Span[1] = upValues[index];
  177. return new(1);
  178. }
  179. return new(0);
  180. }
  181. {
  182. var upValues = closure.UpValues;
  183. var descriptions = closure.Proto.UpValues;
  184. if (index < 0 || index >= descriptions.Length)
  185. {
  186. return new(0);
  187. }
  188. var description = descriptions[index];
  189. buffer.Span[0] = description.Name.ToString();
  190. buffer.Span[1] = upValues[index].GetValue();
  191. return new(2);
  192. }
  193. }
  194. public ValueTask<int> SetUpValue(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
  195. {
  196. var func = context.GetArgument<LuaFunction>(0);
  197. var index = context.GetArgument<int>(1) - 1;
  198. var value = context.GetArgument(2);
  199. if (func is not LuaClosure closure)
  200. {
  201. if (func is CSharpClosure csClosure)
  202. {
  203. var upValues = csClosure.UpValues;
  204. if (index >= 0 && index < upValues.Length)
  205. {
  206. buffer.Span[0] = "";
  207. upValues[index] = value;
  208. return new(0);
  209. }
  210. return new(0);
  211. }
  212. return new(0);
  213. }
  214. {
  215. var upValues = closure.UpValues;
  216. var descriptions = closure.Proto.UpValues;
  217. if (index < 0 || index >= descriptions.Length)
  218. {
  219. return new(0);
  220. }
  221. var description = descriptions[index];
  222. buffer.Span[0] = description.Name.ToString();
  223. upValues[index].SetValue(value);
  224. return new(1);
  225. }
  226. }
  227. public ValueTask<int> GetMetatable(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
  228. {
  229. var arg0 = context.GetArgument(0);
  230. if (context.State.TryGetMetatable(arg0, out var table))
  231. {
  232. buffer.Span[0] = table;
  233. }
  234. else
  235. {
  236. buffer.Span[0] = LuaValue.Nil;
  237. }
  238. return new(1);
  239. }
  240. public ValueTask<int> SetMetatable(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
  241. {
  242. var arg0 = context.GetArgument(0);
  243. var arg1 = context.GetArgument(1);
  244. if (arg1.Type is not (LuaValueType.Nil or LuaValueType.Table))
  245. {
  246. LuaRuntimeException.BadArgument(context.State.GetTraceback(), 2, "setmetatable", [LuaValueType.Nil, LuaValueType.Table]);
  247. }
  248. context.State.SetMetatable(arg0, arg1.UnsafeRead<LuaTable>());
  249. buffer.Span[0] = arg0;
  250. return new(1);
  251. }
  252. public ValueTask<int> GetUserValue(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
  253. {
  254. if (!context.GetArgumentOrDefault(0).TryRead<ILuaUserData>(out var iUserData))
  255. {
  256. buffer.Span[0] = LuaValue.Nil;
  257. return new(1);
  258. }
  259. var index = 1; // context.GetArgument<int>(1); //for lua 5.4
  260. var userValues = iUserData.UserValues;
  261. if (index > userValues.Length
  262. //index < 1 || // for lua 5.4
  263. )
  264. {
  265. buffer.Span[0] = LuaValue.Nil;
  266. return new(1);
  267. }
  268. buffer.Span[0] = userValues[index - 1];
  269. return new(1);
  270. }
  271. public ValueTask<int> SetUserValue(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
  272. {
  273. var iUserData = context.GetArgument<ILuaUserData>(0);
  274. var value = context.GetArgument(1);
  275. var index = 1; // context.GetArgument<int>(2);// for lua 5.4
  276. var userValues = iUserData.UserValues;
  277. if (index > userValues.Length
  278. //|| index < 1 // for lua 5.4
  279. )
  280. {
  281. buffer.Span[0] = LuaValue.Nil;
  282. return new(1);
  283. }
  284. userValues[index - 1] = value;
  285. buffer.Span[0] = new LuaValue(iUserData);
  286. return new(1);
  287. }
  288. public ValueTask<int> Traceback(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
  289. {
  290. var thread = GetLuaThread(context, out var argOffset);
  291. var message = context.GetArgumentOrDefault(argOffset);
  292. var level = context.GetArgumentOrDefault<int>(argOffset + 1, argOffset == 0 ? 1 : 0);
  293. if (message.Type is not (LuaValueType.Nil or LuaValueType.String or LuaValueType.Number))
  294. {
  295. buffer.Span[0] = message;
  296. return new(1);
  297. }
  298. if (level < 0)
  299. {
  300. buffer.Span[0] = LuaValue.Nil;
  301. return new(1);
  302. }
  303. if (thread is LuaCoroutine coroutine)
  304. {
  305. if (coroutine.LuaTraceback is not null)
  306. {
  307. buffer.Span[0] = coroutine.LuaTraceback.ToString(level);
  308. return new(1);
  309. }
  310. }
  311. var callStack = thread.GetCallStackFrames();
  312. if (callStack.Length == 0)
  313. {
  314. buffer.Span[0] = "stack traceback:";
  315. return new(1);
  316. }
  317. var skipCount = Math.Min(Math.Max(level - 1, 0), callStack.Length - 1);
  318. var frames = callStack[1..^skipCount];
  319. buffer.Span[0] = Runtime.Traceback.GetTracebackString(context.State, (LuaClosure)callStack[0].Function, frames, message, level == 1);
  320. return new(1);
  321. }
  322. public ValueTask<int> GetRegistry(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
  323. {
  324. buffer.Span[0] = context.State.Registry;
  325. return new(1);
  326. }
  327. public ValueTask<int> UpValueId(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
  328. {
  329. var n1 = context.GetArgument<int>(1);
  330. var f1 = context.GetArgument<LuaFunction>(0);
  331. if (f1 is not LuaClosure closure)
  332. {
  333. buffer.Span[0] = LuaValue.Nil;
  334. return new(1);
  335. }
  336. var upValues = closure.GetUpValuesSpan();
  337. if (n1 <= 0 || n1 > upValues.Length)
  338. {
  339. buffer.Span[0] = LuaValue.Nil;
  340. return new(1);
  341. }
  342. buffer.Span[0] = new LuaValue(upValues[n1 - 1]);
  343. return new(1);
  344. }
  345. public ValueTask<int> UpValueJoin(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
  346. {
  347. var n2 = context.GetArgument<int>(3);
  348. var f2 = context.GetArgument<LuaFunction>(2);
  349. var n1 = context.GetArgument<int>(1);
  350. var f1 = context.GetArgument<LuaFunction>(0);
  351. if (f1 is not LuaClosure closure1 || f2 is not LuaClosure closure2)
  352. {
  353. buffer.Span[0] = LuaValue.Nil;
  354. return new(1);
  355. }
  356. var upValues1 = closure1.GetUpValuesSpan();
  357. var upValues2 = closure2.GetUpValuesSpan();
  358. if (n1 <= 0 || n1 > upValues1.Length)
  359. {
  360. context.ThrowBadArgument(1, "invalid upvalue index");
  361. }
  362. if (n2 < 0 || n2 > upValues2.Length)
  363. {
  364. context.ThrowBadArgument(3, "invalid upvalue index");
  365. }
  366. upValues1[n1 - 1] = upValues2[n2 - 1];
  367. return new(0);
  368. }
  369. public async ValueTask<int> SetHook(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
  370. {
  371. var thread = GetLuaThread(context, out var argOffset);
  372. LuaFunction? hook = context.GetArgumentOrDefault<LuaFunction?>(argOffset);
  373. if (hook is null)
  374. {
  375. thread.HookCount = -1;
  376. thread.BaseHookCount = 0;
  377. thread.IsCountHookEnabled = false;
  378. thread.Hook = null;
  379. thread.IsLineHookEnabled = false;
  380. thread.IsCallHookEnabled = false;
  381. thread.IsReturnHookEnabled = false;
  382. return 0;
  383. }
  384. var mask = context.GetArgument<string>(argOffset + 1);
  385. if (context.HasArgument(argOffset + 2))
  386. {
  387. var count = context.GetArgument<int>(argOffset + 2);
  388. thread.BaseHookCount = count;
  389. thread.HookCount = count;
  390. if (count > 0)
  391. {
  392. thread.IsCountHookEnabled = true;
  393. }
  394. }
  395. else
  396. {
  397. thread.HookCount = 0;
  398. thread.BaseHookCount = 0;
  399. thread.IsCountHookEnabled = false;
  400. }
  401. thread.IsLineHookEnabled = (mask.Contains('l'));
  402. thread.IsCallHookEnabled = (mask.Contains('c'));
  403. thread.IsReturnHookEnabled = (mask.Contains('r'));
  404. if (thread.IsLineHookEnabled)
  405. {
  406. thread.LastPc = thread.CallStack.Count > 0 ? thread.GetCurrentFrame().CallerInstructionIndex : -1;
  407. }
  408. thread.Hook = hook;
  409. if (thread.IsReturnHookEnabled && context.Thread == thread)
  410. {
  411. var stack = thread.Stack;
  412. stack.Push("return");
  413. stack.Push(LuaValue.Nil);
  414. var funcContext = new LuaFunctionExecutionContext
  415. {
  416. State = context.State,
  417. Thread = context.Thread,
  418. ArgumentCount = 2,
  419. FrameBase = stack.Count - 2,
  420. };
  421. var frame = new CallStackFrame
  422. {
  423. Base = funcContext.FrameBase,
  424. VariableArgumentCount = hook.GetVariableArgumentCount(2),
  425. Function = hook,
  426. };
  427. frame.Flags |= CallStackFrameFlags.InHook;
  428. thread.PushCallStackFrame(frame);
  429. try
  430. {
  431. thread.IsInHook = true;
  432. await hook.Func(funcContext, Memory<LuaValue>.Empty, cancellationToken);
  433. }
  434. finally
  435. {
  436. thread.IsInHook = false;
  437. }
  438. thread.PopCallStackFrame();
  439. }
  440. return 0;
  441. }
  442. public ValueTask<int> GetHook(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
  443. {
  444. var thread = GetLuaThread(context, out var argOffset);
  445. if (thread.Hook is null)
  446. {
  447. buffer.Span[0] = LuaValue.Nil;
  448. buffer.Span[1] = LuaValue.Nil;
  449. buffer.Span[2] = LuaValue.Nil;
  450. return new(3);
  451. }
  452. buffer.Span[0] = thread.Hook;
  453. buffer.Span[1] = (
  454. (thread.IsCallHookEnabled ? "c" : "") +
  455. (thread.IsReturnHookEnabled ? "r" : "") +
  456. (thread.IsLineHookEnabled ? "l" : "")
  457. );
  458. buffer.Span[2] = thread.BaseHookCount;
  459. return new(3);
  460. }
  461. public ValueTask<int> GetInfo(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
  462. {
  463. //return new(0);
  464. var thread = GetLuaThread(context, out var argOffset);
  465. string what = context.GetArgumentOrDefault<string>(argOffset + 1, "flnStu");
  466. CallStackFrame? previousFrame = null;
  467. CallStackFrame? currentFrame = null;
  468. int pc = 0;
  469. var arg1 = context.GetArgument(argOffset);
  470. if (arg1.TryReadFunction(out var functionToInspect))
  471. {
  472. //what = ">" + what;
  473. }
  474. else if (arg1.TryReadNumber(out _))
  475. {
  476. var level = context.GetArgument<int>(argOffset) + 1;
  477. var callStack = thread.GetCallStackFrames();
  478. if (level <= 0 || level > callStack.Length)
  479. {
  480. buffer.Span[0] = LuaValue.Nil;
  481. return new(1);
  482. }
  483. currentFrame = thread.GetCallStackFrames()[^(level)];
  484. previousFrame = level + 1 <= callStack.Length ? callStack[^(level + 1)] : null;
  485. if (level != 1)
  486. {
  487. pc = thread.GetCallStackFrames()[^(level - 1)].CallerInstructionIndex;
  488. }
  489. functionToInspect = currentFrame.Value.Function;
  490. }
  491. else
  492. {
  493. context.ThrowBadArgument(argOffset, "function or level expected");
  494. }
  495. using var debug = LuaDebug.Create(context.State, previousFrame, currentFrame, functionToInspect, pc, what, out var isValid);
  496. if (!isValid)
  497. {
  498. context.ThrowBadArgument(argOffset + 1, "invalid option");
  499. }
  500. var table = new LuaTable(0, 1);
  501. if (what.Contains('S'))
  502. {
  503. table["source"] = debug.Source ?? LuaValue.Nil;
  504. table["short_src"] = debug.ShortSource.ToString();
  505. table["linedefined"] = debug.LineDefined;
  506. table["lastlinedefined"] = debug.LastLineDefined;
  507. table["what"] = debug.What ?? LuaValue.Nil;
  508. ;
  509. }
  510. if (what.Contains('l'))
  511. {
  512. table["currentline"] = debug.CurrentLine;
  513. }
  514. if (what.Contains('u'))
  515. {
  516. table["nups"] = debug.UpValueCount;
  517. table["nparams"] = debug.ParameterCount;
  518. table["isvararg"] = debug.IsVarArg;
  519. }
  520. if (what.Contains('n'))
  521. {
  522. table["name"] = debug.Name ?? LuaValue.Nil;
  523. ;
  524. table["namewhat"] = debug.NameWhat ?? LuaValue.Nil;
  525. ;
  526. }
  527. if (what.Contains('t'))
  528. {
  529. table["istailcall"] = debug.IsTailCall;
  530. }
  531. if (what.Contains('f'))
  532. {
  533. table["func"] = functionToInspect;
  534. }
  535. if (what.Contains('L'))
  536. {
  537. if (functionToInspect is LuaClosure closure)
  538. {
  539. var activeLines = new LuaTable(0, 8);
  540. foreach (var pos in closure.Proto.SourcePositions)
  541. {
  542. activeLines[pos.Line] = true;
  543. }
  544. table["activelines"] = activeLines;
  545. }
  546. }
  547. buffer.Span[0] = table;
  548. return new(1);
  549. }
  550. }