DebugLibrary.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603
  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.LocalVariables;
  71. var nextFrame = callStack[^level];
  72. var currentPc = nextFrame.CallerInstructionIndex;
  73. {
  74. int nextFrameBase = (closure.Proto.Code[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. var localId = index + 1;
  82. foreach (var l in locals)
  83. {
  84. if (currentPc < l.StartPc) break;
  85. if (l.EndPc <= currentPc) continue;
  86. localId--;
  87. if (localId == 0)
  88. {
  89. name = l.Name;
  90. return ref thread.Stack.Get(frameBase + index);
  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, 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.LocalVariables[index].Name;
  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. return new(context.Return(GetParam(f, index - 1)));
  125. }
  126. var level = context.GetArgument<int>(argOffset);
  127. if (level < 0 || level >= thread.GetCallStackFrames().Length)
  128. {
  129. context.ThrowBadArgument(1, "level out of range");
  130. }
  131. ref var local = ref FindLocal(thread, level, index, out var name);
  132. if (name is null)
  133. {
  134. return new(context.Return(LuaValue.Nil));
  135. }
  136. return new(context.Return(name, local));
  137. }
  138. public ValueTask<int> SetLocal(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
  139. {
  140. var thread = GetLuaThread(context, out var argOffset);
  141. var value = context.GetArgument(argOffset + 2);
  142. var index = context.GetArgument<int>(argOffset + 1);
  143. var level = context.GetArgument<int>(argOffset);
  144. if (level < 0 || level >= thread.GetCallStackFrames().Length)
  145. {
  146. context.ThrowBadArgument(1, "level out of range");
  147. }
  148. ref var local = ref FindLocal(thread, level, index, out var name);
  149. if (name is null)
  150. {
  151. return new(context.Return(LuaValue.Nil));
  152. }
  153. local = value;
  154. return new(context.Return(name));
  155. }
  156. public ValueTask<int> GetUpValue(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
  157. {
  158. var func = context.GetArgument<LuaFunction>(0);
  159. var index = context.GetArgument<int>(1) - 1;
  160. if (func is not LuaClosure closure)
  161. {
  162. if (func is CSharpClosure csClosure)
  163. {
  164. var upValues = csClosure.UpValues;
  165. if (index < 0 || index >= upValues.Length)
  166. {
  167. return new(context.Return());
  168. }
  169. return new(context.Return("", upValues[index]));
  170. }
  171. return new(context.Return());
  172. }
  173. {
  174. var upValues = closure.UpValues;
  175. var descriptions = closure.Proto.UpValues;
  176. if (index < 0 || index >= descriptions.Length)
  177. {
  178. return new(context.Return());
  179. }
  180. var description = descriptions[index];
  181. return new(context.Return(description.Name.ToString(), upValues[index].GetValue()));
  182. }
  183. }
  184. public ValueTask<int> SetUpValue(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
  185. {
  186. var func = context.GetArgument<LuaFunction>(0);
  187. var index = context.GetArgument<int>(1) - 1;
  188. var value = context.GetArgument(2);
  189. if (func is not LuaClosure closure)
  190. {
  191. if (func is CSharpClosure csClosure)
  192. {
  193. var upValues = csClosure.UpValues;
  194. if (index >= 0 && index < upValues.Length)
  195. {
  196. upValues[index] = value;
  197. return new(context.Return(""));
  198. }
  199. }
  200. return new(context.Return());
  201. }
  202. {
  203. var upValues = closure.UpValues;
  204. var descriptions = closure.Proto.UpValues;
  205. if (index < 0 || index >= descriptions.Length)
  206. {
  207. return new(context.Return());
  208. }
  209. var description = descriptions[index];
  210. upValues[index].SetValue(value);
  211. return new(context.Return(description.Name.ToString()));
  212. }
  213. }
  214. public ValueTask<int> GetMetatable(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
  215. {
  216. var arg0 = context.GetArgument(0);
  217. if (context.State.TryGetMetatable(arg0, out var table))
  218. {
  219. return new(context.Return(table));
  220. }
  221. else
  222. {
  223. return new(context.Return(LuaValue.Nil));
  224. }
  225. }
  226. public ValueTask<int> SetMetatable(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
  227. {
  228. var arg0 = context.GetArgument(0);
  229. var arg1 = context.GetArgument(1);
  230. if (arg1.Type is not (LuaValueType.Nil or LuaValueType.Table))
  231. {
  232. LuaRuntimeException.BadArgument(context.Thread.GetTraceback(), 2, "setmetatable", [LuaValueType.Nil, LuaValueType.Table]);
  233. }
  234. context.State.SetMetatable(arg0, arg1.UnsafeRead<LuaTable>());
  235. return new(context.Return(arg0));
  236. }
  237. public ValueTask<int> GetUserValue(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
  238. {
  239. if (!context.GetArgumentOrDefault(0).TryRead<ILuaUserData>(out var iUserData))
  240. {
  241. return new(context.Return(LuaValue.Nil));
  242. }
  243. var index = 1; // context.GetArgument<int>(1); //for lua 5.4
  244. var userValues = iUserData.UserValues;
  245. if (index > userValues.Length
  246. //index < 1 || // for lua 5.4
  247. )
  248. {
  249. return new(context.Return(LuaValue.Nil));
  250. }
  251. return new(context.Return(userValues[index - 1]));
  252. }
  253. public ValueTask<int> SetUserValue(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
  254. {
  255. var iUserData = context.GetArgument<ILuaUserData>(0);
  256. var value = context.GetArgument(1);
  257. var index = 1; // context.GetArgument<int>(2);// for lua 5.4
  258. var userValues = iUserData.UserValues;
  259. if (index > userValues.Length
  260. //|| index < 1 // for lua 5.4
  261. )
  262. {
  263. return new(context.Return(LuaValue.Nil));
  264. }
  265. userValues[index - 1] = value;
  266. return new(context.Return(new LuaValue(iUserData)));
  267. }
  268. public ValueTask<int> Traceback(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
  269. {
  270. var thread = GetLuaThread(context, out var argOffset);
  271. var message = context.GetArgumentOrDefault(argOffset);
  272. var level = context.GetArgumentOrDefault<int>(argOffset + 1, argOffset == 0 ? 1 : 0);
  273. if (message.Type is not (LuaValueType.Nil or LuaValueType.String or LuaValueType.Number))
  274. {
  275. return new(context.Return(message));
  276. }
  277. if (level < 0)
  278. {
  279. return new(context.Return(LuaValue.Nil));
  280. }
  281. if (thread is LuaCoroutine coroutine)
  282. {
  283. if (coroutine.LuaTraceback is not null)
  284. {
  285. return new(context.Return(coroutine.LuaTraceback.ToString(level)));
  286. }
  287. }
  288. var callStack = thread.GetCallStackFrames();
  289. if (callStack.Length == 0)
  290. {
  291. return new(context.Return("stack traceback:"));
  292. }
  293. var skipCount = Math.Min(Math.Max(level - 1, 0), callStack.Length - 1);
  294. var frames = callStack[1..^skipCount];
  295. return new(context.Return(Runtime.Traceback.GetTracebackString(context.State, callStack[0].Function, frames, message, level == 1)));
  296. }
  297. public ValueTask<int> GetRegistry(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
  298. {
  299. return new(context.Return(context.State.Registry));
  300. }
  301. public ValueTask<int> UpValueId(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
  302. {
  303. var n1 = context.GetArgument<int>(1);
  304. var f1 = context.GetArgument<LuaFunction>(0);
  305. if (f1 is not LuaClosure closure)
  306. {
  307. return new(context.Return(LuaValue.Nil));
  308. }
  309. var upValues = closure.GetUpValuesSpan();
  310. if (n1 <= 0 || n1 > upValues.Length)
  311. {
  312. return new(context.Return(LuaValue.Nil));
  313. }
  314. return new(context.Return(new LuaValue(upValues[n1 - 1])));
  315. }
  316. public ValueTask<int> UpValueJoin(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
  317. {
  318. var n2 = context.GetArgument<int>(3);
  319. var f2 = context.GetArgument<LuaFunction>(2);
  320. var n1 = context.GetArgument<int>(1);
  321. var f1 = context.GetArgument<LuaFunction>(0);
  322. if (f1 is not LuaClosure closure1 || f2 is not LuaClosure closure2)
  323. {
  324. return new(context.Return(LuaValue.Nil));
  325. }
  326. var upValues1 = closure1.GetUpValuesSpan();
  327. var upValues2 = closure2.GetUpValuesSpan();
  328. if (n1 <= 0 || n1 > upValues1.Length)
  329. {
  330. context.ThrowBadArgument(1, "invalid upvalue index");
  331. }
  332. if (n2 < 0 || n2 > upValues2.Length)
  333. {
  334. context.ThrowBadArgument(3, "invalid upvalue index");
  335. }
  336. upValues1[n1 - 1] = upValues2[n2 - 1];
  337. return new(0);
  338. }
  339. public async ValueTask<int> SetHook(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
  340. {
  341. var thread = GetLuaThread(context, out var argOffset);
  342. LuaFunction? hook = context.GetArgumentOrDefault<LuaFunction?>(argOffset);
  343. if (hook is null)
  344. {
  345. thread.HookCount = -1;
  346. thread.BaseHookCount = 0;
  347. thread.IsCountHookEnabled = false;
  348. thread.Hook = null;
  349. thread.IsLineHookEnabled = false;
  350. thread.IsCallHookEnabled = false;
  351. thread.IsReturnHookEnabled = false;
  352. return 0;
  353. }
  354. var mask = context.GetArgument<string>(argOffset + 1);
  355. if (context.HasArgument(argOffset + 2))
  356. {
  357. var count = context.GetArgument<int>(argOffset + 2);
  358. thread.BaseHookCount = count;
  359. thread.HookCount = count;
  360. if (count > 0)
  361. {
  362. thread.IsCountHookEnabled = true;
  363. }
  364. }
  365. else
  366. {
  367. thread.HookCount = 0;
  368. thread.BaseHookCount = 0;
  369. thread.IsCountHookEnabled = false;
  370. }
  371. thread.IsLineHookEnabled = (mask.Contains('l'));
  372. thread.IsCallHookEnabled = (mask.Contains('c'));
  373. thread.IsReturnHookEnabled = (mask.Contains('r'));
  374. if (thread.IsLineHookEnabled)
  375. {
  376. thread.LastPc = thread.CallStack.Count > 0 ? thread.GetCurrentFrame().CallerInstructionIndex : -1;
  377. }
  378. thread.Hook = hook;
  379. if (thread.IsReturnHookEnabled && context.Thread == thread)
  380. {
  381. var stack = thread.Stack;
  382. stack.Push("return");
  383. stack.Push(LuaValue.Nil);
  384. var funcContext = new LuaFunctionExecutionContext { Thread = context.Thread, ArgumentCount = 2, ReturnFrameBase = stack.Count - 2, };
  385. var frame = new CallStackFrame
  386. {
  387. Base = funcContext.FrameBase, ReturnBase = funcContext.ReturnFrameBase, VariableArgumentCount = hook.GetVariableArgumentCount(2), Function = hook,
  388. };
  389. frame.Flags |= CallStackFrameFlags.InHook;
  390. thread.PushCallStackFrame(frame);
  391. try
  392. {
  393. thread.IsInHook = true;
  394. await hook.Func(funcContext, cancellationToken);
  395. }
  396. finally
  397. {
  398. thread.IsInHook = false;
  399. }
  400. thread.PopCallStackFrameWithStackPop();
  401. }
  402. return 0;
  403. }
  404. public ValueTask<int> GetHook(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
  405. {
  406. var thread = GetLuaThread(context, out var argOffset);
  407. if (thread.Hook is null)
  408. {
  409. return new(context.Return(LuaValue.Nil, LuaValue.Nil, LuaValue.Nil));
  410. }
  411. return new(context.Return(thread.Hook,
  412. (
  413. (thread.IsCallHookEnabled ? "c" : "") +
  414. (thread.IsReturnHookEnabled ? "r" : "") +
  415. (thread.IsLineHookEnabled ? "l" : "")
  416. )
  417. , thread.BaseHookCount));
  418. }
  419. public ValueTask<int> GetInfo(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
  420. {
  421. //return new(0);
  422. var thread = GetLuaThread(context, out var argOffset);
  423. string what = context.GetArgumentOrDefault<string>(argOffset + 1, "flnStu");
  424. CallStackFrame? previousFrame = null;
  425. CallStackFrame? currentFrame = null;
  426. int pc = 0;
  427. var arg1 = context.GetArgument(argOffset);
  428. if (arg1.TryReadFunction(out var functionToInspect))
  429. {
  430. //what = ">" + what;
  431. }
  432. else if (arg1.TryReadNumber(out _))
  433. {
  434. var level = context.GetArgument<int>(argOffset) + 1;
  435. var callStack = thread.GetCallStackFrames();
  436. if (level <= 0 || level > callStack.Length)
  437. {
  438. return new(context.Return(LuaValue.Nil));
  439. }
  440. currentFrame = thread.GetCallStackFrames()[^(level)];
  441. previousFrame = level + 1 <= callStack.Length ? callStack[^(level + 1)] : null;
  442. if (level != 1)
  443. {
  444. pc = thread.GetCallStackFrames()[^(level - 1)].CallerInstructionIndex;
  445. }
  446. functionToInspect = currentFrame.Value.Function;
  447. }
  448. else
  449. {
  450. context.ThrowBadArgument(argOffset, "function or level expected");
  451. }
  452. using var debug = LuaDebug.Create(context.State, previousFrame, currentFrame, functionToInspect, pc, what, out var isValid);
  453. if (!isValid)
  454. {
  455. context.ThrowBadArgument(argOffset + 1, "invalid option");
  456. }
  457. var table = new LuaTable(0, 1);
  458. if (what.Contains('S'))
  459. {
  460. table["source"] = debug.Source ?? LuaValue.Nil;
  461. table["short_src"] = debug.ShortSource.ToString();
  462. table["linedefined"] = debug.LineDefined;
  463. table["lastlinedefined"] = debug.LastLineDefined;
  464. table["what"] = debug.What ?? LuaValue.Nil;
  465. ;
  466. }
  467. if (what.Contains('l'))
  468. {
  469. table["currentline"] = debug.CurrentLine;
  470. }
  471. if (what.Contains('u'))
  472. {
  473. table["nups"] = debug.UpValueCount;
  474. table["nparams"] = debug.ParameterCount;
  475. table["isvararg"] = debug.IsVarArg;
  476. }
  477. if (what.Contains('n'))
  478. {
  479. table["name"] = debug.Name ?? LuaValue.Nil;
  480. table["namewhat"] = debug.NameWhat ?? LuaValue.Nil;
  481. }
  482. if (what.Contains('t'))
  483. {
  484. table["istailcall"] = debug.IsTailCall;
  485. }
  486. if (what.Contains('f'))
  487. {
  488. table["func"] = functionToInspect;
  489. }
  490. if (what.Contains('L'))
  491. {
  492. if (functionToInspect is LuaClosure closure)
  493. {
  494. var activeLines = new LuaTable(0, 8);
  495. foreach (var line in closure.Proto.LineInfo)
  496. {
  497. activeLines[line] = true;
  498. }
  499. table["activelines"] = activeLines;
  500. }
  501. }
  502. return new(context.Return(table));
  503. }
  504. }