Exceptions.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  1. using Lua.CodeAnalysis;
  2. using Lua.CodeAnalysis.Syntax;
  3. using Lua.Internal;
  4. using Lua.Runtime;
  5. using System.Diagnostics;
  6. using System.Runtime.CompilerServices;
  7. namespace Lua;
  8. public class LuaParseException(string? chunkName, SourcePosition position, string message) : Exception(message)
  9. {
  10. public string? ChunkName { get; } = chunkName;
  11. public SourcePosition Position { get; } = position;
  12. public static void UnexpectedToken(string? chunkName, SourcePosition position, SyntaxToken token)
  13. {
  14. throw new LuaParseException(chunkName, position, $"unexpected symbol <{token.Type}> near '{token.Text}'");
  15. }
  16. public static void ExpectedToken(string? chunkName, SourcePosition position, SyntaxTokenType token)
  17. {
  18. throw new LuaParseException(chunkName, position, $"'{token}' expected");
  19. }
  20. public static void UnfinishedLongComment(string? chunkName, SourcePosition position)
  21. {
  22. throw new LuaParseException(chunkName, position, $"unfinished long comment (starting at line {position.Line})");
  23. }
  24. public static void SyntaxError(string? chunkName, SourcePosition position, SyntaxToken? token)
  25. {
  26. throw new LuaParseException(chunkName, position, $"syntax error {(token == null ? "" : $"near '{token.Value.Text}'")}");
  27. }
  28. public static void NoVisibleLabel(string label, string? chunkName, SourcePosition position)
  29. {
  30. throw new LuaParseException(chunkName, position, $"no visible label '{label}' for <goto>");
  31. }
  32. public static void BreakNotInsideALoop(string? chunkName, SourcePosition position)
  33. {
  34. throw new LuaParseException(chunkName, position, "<break> not inside a loop");
  35. }
  36. public override string Message => $"{ChunkName}:{Position.Line}: {base.Message}";
  37. }
  38. public class LuaCompileException(string chunkName, SourcePosition position, int offset, string message, string? nearToken) : Exception(GetMessageWithNearToken(message, nearToken))
  39. {
  40. public string ChunkName { get; } = chunkName;
  41. public int OffSet { get; } = offset;
  42. public SourcePosition Position => position;
  43. public string MainMessage => message;
  44. public string? NearToken => nearToken;
  45. public string MessageWithNearToken => base.Message;
  46. public override string Message => $"{ChunkName}:{Position.Line}: {base.Message}";
  47. static string GetMessageWithNearToken(string message, string? nearToken)
  48. {
  49. if (string.IsNullOrEmpty(nearToken))
  50. {
  51. return message;
  52. }
  53. return $"{message} near {nearToken}";
  54. }
  55. }
  56. public class LuaUnDumpException(string message) : Exception(message);
  57. internal class LuaStackOverflowException() : Exception("stack overflow")
  58. {
  59. public override string ToString()
  60. {
  61. return "stack overflow";
  62. }
  63. }
  64. internal interface ILuaTracebackBuildable
  65. {
  66. Traceback? BuildOrGet();
  67. }
  68. public class LuaRuntimeException : Exception, ILuaTracebackBuildable
  69. {
  70. public LuaRuntimeException(LuaThread? thread, Exception innerException) : base(innerException.Message, innerException)
  71. {
  72. if (thread != null)
  73. {
  74. thread.CurrentException?.BuildOrGet();
  75. thread.ExceptionTrace.Clear();
  76. thread.CurrentException = this;
  77. }
  78. Thread = thread;
  79. }
  80. public LuaRuntimeException(LuaThread? thread, LuaValue errorObject, int level = 1)
  81. {
  82. if (thread != null)
  83. {
  84. thread.CurrentException?.BuildOrGet();
  85. thread.ExceptionTrace.Clear();
  86. thread.CurrentException = this;
  87. }
  88. Thread = thread;
  89. ErrorObject = errorObject;
  90. this.level = level;
  91. }
  92. int level = 1;
  93. Traceback? luaTraceback;
  94. public Traceback? LuaTraceback
  95. {
  96. get
  97. {
  98. if (luaTraceback == null)
  99. {
  100. ((ILuaTracebackBuildable)this).BuildOrGet();
  101. }
  102. return luaTraceback;
  103. }
  104. }
  105. internal LuaThread? Thread { get; private set; } = default!;
  106. public LuaValue ErrorObject { get; }
  107. public static void AttemptInvalidOperation(LuaThread? thread, string op, LuaValue a, LuaValue b)
  108. {
  109. var typeA = a.TypeToString();
  110. var typeB = b.TypeToString();
  111. if (typeA == typeB)
  112. {
  113. throw new LuaRuntimeException(thread, $"attempt to {op} two {typeA} values");
  114. }
  115. throw new LuaRuntimeException(thread, $"attempt to {op} a {typeA} value with a {typeB} value");
  116. }
  117. public static void AttemptInvalidOperation(LuaThread? thread, string op, LuaValue a)
  118. {
  119. throw new LuaRuntimeException(thread, $"attempt to {op} a {a.TypeToString()} value");
  120. }
  121. internal static void AttemptInvalidOperationOnLuaStack(LuaThread thread, string op, int lastPc, int regA, int regB)
  122. {
  123. var caller = thread.GetCurrentFrame();
  124. var luaValueA = regA < 255 ? thread.Stack[caller.Base + regA] : ((LuaClosure)caller.Function).Proto.Constants[regA - 256];
  125. var luaValueB = regB < 255 ? thread.Stack[caller.Base + regB] : ((LuaClosure)caller.Function).Proto.Constants[regB - 256];
  126. var function = caller.Function;
  127. var tA = LuaDebug.GetName(((LuaClosure)function).Proto, lastPc, regA, out string? nameA);
  128. var tB = LuaDebug.GetName(((LuaClosure)function).Proto, lastPc, regB, out string? nameB);
  129. using var builder = new PooledList<char>(64);
  130. builder.Clear();
  131. builder.AddRange("attempt to ");
  132. builder.AddRange(op);
  133. builder.AddRange(" a ");
  134. builder.AddRange(luaValueA.TypeToString());
  135. builder.AddRange(" value");
  136. if (tA != null && nameA != null)
  137. {
  138. builder.AddRange($" ({tA} '{nameA}')");
  139. }
  140. builder.AddRange(" with a ");
  141. builder.AddRange(luaValueB.TypeToString());
  142. builder.AddRange(" value");
  143. if (tB != null && nameB != null)
  144. {
  145. builder.AddRange($" ({tB} '{nameB}')");
  146. }
  147. throw new LuaRuntimeException(thread, builder.AsSpan().ToString());
  148. }
  149. internal static void AttemptInvalidOperationOnLuaStack(LuaThread thread, string op, int lastPc, int reg)
  150. {
  151. var caller = thread.GetCurrentFrame();
  152. var luaValue = reg < 255 ? thread.Stack[caller.Base + reg] : ((LuaClosure)caller.Function).Proto.Constants[reg - 256];
  153. var function = caller.Function;
  154. var t = LuaDebug.GetName(((LuaClosure)function).Proto, lastPc, reg, out string? name);
  155. using var builder = new PooledList<char>(64);
  156. builder.Clear();
  157. builder.AddRange("attempt to ");
  158. builder.AddRange(op);
  159. builder.AddRange(" a ");
  160. builder.AddRange(luaValue.TypeToString());
  161. builder.AddRange(" value");
  162. if (t != null && name != null)
  163. {
  164. builder.AddRange($" ({t} '{name}')");
  165. }
  166. throw new LuaRuntimeException(thread, builder.AsSpan().ToString());
  167. }
  168. internal static void AttemptInvalidOperationOnUpValues(LuaThread thread, string op, int reg)
  169. {
  170. var caller = thread.GetCurrentFrame();
  171. var closure = (LuaClosure)caller.Function;
  172. var proto = closure.Proto;
  173. var upValue = proto.UpValues[reg];
  174. var luaValue = closure.UpValues[upValue.Index].GetValue();
  175. var name = upValue.Name;
  176. throw new LuaRuntimeException(thread, $"attempt to {op} a {luaValue.TypeToString()} value (upvalue '{name}')");
  177. }
  178. internal static (string NameWhat, string Name) GetCurrentFunctionName(LuaThread thread)
  179. {
  180. var current = thread.GetCurrentFrame();
  181. var pc = current.CallerInstructionIndex;
  182. LuaFunction callerFunction;
  183. if (current.IsTailCall)
  184. {
  185. pc = thread.LastPc;
  186. callerFunction = thread.LastCallerFunction!;
  187. }
  188. else
  189. {
  190. var caller = thread.GetCallStackFrames()[^2];
  191. callerFunction = caller.Function;
  192. }
  193. if (callerFunction is not LuaClosure callerClosure)
  194. {
  195. return ("function", current.Function.Name);
  196. }
  197. return (LuaDebug.GetFuncName(callerClosure.Proto, pc, out var name) ?? "", name ?? current.Function.Name);
  198. }
  199. public static void BadArgument(LuaThread thread, int argumentId)
  200. {
  201. BadArgument(thread, argumentId, "value expected");
  202. }
  203. public static void BadArgument(LuaThread thread, int argumentId, LuaValueType expected, LuaValueType actual)
  204. {
  205. BadArgument(thread, argumentId, $"{LuaValue.ToString(expected)} expected, got {LuaValue.ToString(actual)})");
  206. }
  207. public static void BadArgument(LuaThread thread, int argumentId, LuaValueType[] expected, LuaValueType actual)
  208. {
  209. BadArgument(thread, argumentId, $"({string.Join(" or ", expected.Select(LuaValue.ToString))} expected, got {LuaValue.ToString(actual)})");
  210. }
  211. public static void BadArgument(LuaThread thread, int argumentId, string expected, string actual)
  212. {
  213. BadArgument(thread, argumentId, $"({expected} expected, got {actual})");
  214. }
  215. public static void BadArgument(LuaThread thread, int argumentId, string[] expected, string actual)
  216. {
  217. if (expected.Length == 0)
  218. {
  219. throw new ArgumentException("Expected array must not be empty", nameof(expected));
  220. }
  221. BadArgument(thread, argumentId, $"({string.Join(" or ", expected)} expected, got {actual})");
  222. }
  223. public static void BadArgument(LuaThread thread, int argumentId, string message)
  224. {
  225. var (nameWhat, name) = GetCurrentFunctionName(thread);
  226. if (nameWhat == "method")
  227. {
  228. argumentId--;
  229. if (argumentId == 0)
  230. {
  231. throw new LuaRuntimeException(thread, $"calling '{name}' on bad self ({message})");
  232. }
  233. }
  234. throw new LuaRuntimeException(thread, $"bad argument #{argumentId} to '{name}' ({message})");
  235. }
  236. public static void BadArgumentNumberIsNotInteger(LuaThread thread, int argumentId)
  237. {
  238. BadArgument(thread, argumentId, "number has no integer representation");
  239. }
  240. public static void ThrowBadArgumentIfNumberIsNotInteger(LuaThread thread, int argumentId, double value)
  241. {
  242. if (!MathEx.IsInteger(value))
  243. {
  244. BadArgumentNumberIsNotInteger(thread, argumentId);
  245. }
  246. }
  247. static string CreateMessage(Traceback traceback, LuaValue errorObject, int level)
  248. {
  249. var pooledList = new PooledList<char>(64);
  250. pooledList.Clear();
  251. try
  252. {
  253. pooledList.AddRange("Lua-CSharp: ");
  254. traceback.WriteLastLuaTrace(ref pooledList, level);
  255. pooledList.AddRange($"{errorObject}");
  256. return pooledList.AsSpan().ToString();
  257. }
  258. finally
  259. {
  260. pooledList.Dispose();
  261. }
  262. }
  263. [MethodImpl(MethodImplOptions.NoInlining)]
  264. Traceback? ILuaTracebackBuildable.BuildOrGet()
  265. {
  266. if (luaTraceback != null) return luaTraceback;
  267. if (Thread != null)
  268. {
  269. var callStack = Thread.ExceptionTrace.AsSpan();
  270. if (callStack.IsEmpty) return null;
  271. luaTraceback = new Traceback(Thread.State, callStack);
  272. Thread.ExceptionTrace.Clear();
  273. Thread = null;
  274. }
  275. return luaTraceback;
  276. }
  277. internal void Forget()
  278. {
  279. Thread?.ExceptionTrace.Clear();
  280. Thread = null;
  281. }
  282. internal string MinimalMessage()
  283. {
  284. var message = InnerException?.ToString() ?? ErrorObject.ToString();
  285. if (level <= 0)
  286. {
  287. return message;
  288. }
  289. if (luaTraceback == null)
  290. {
  291. if (Thread != null)
  292. {
  293. var callStack = Thread.ExceptionTrace.AsSpan();
  294. level = Math.Min(level, callStack.Length + 1);
  295. callStack = callStack[..^(level - 1)];
  296. if (callStack.IsEmpty)
  297. {
  298. return ErrorObject.ToString();
  299. }
  300. {
  301. var pooledList = new PooledList<char>(64);
  302. pooledList.Clear();
  303. try
  304. {
  305. Traceback.WriteLastLuaTrace(callStack, ref pooledList);
  306. pooledList.AddRange(message);
  307. return pooledList.AsSpan().ToString();
  308. }
  309. finally
  310. {
  311. pooledList.Dispose();
  312. }
  313. }
  314. }
  315. return message;
  316. }
  317. {
  318. var pooledList = new PooledList<char>(64);
  319. pooledList.Clear();
  320. try
  321. {
  322. luaTraceback.WriteLastLuaTrace(ref pooledList, level);
  323. pooledList.AddRange(message);
  324. return pooledList.AsSpan().ToString();
  325. }
  326. finally
  327. {
  328. pooledList.Dispose();
  329. }
  330. }
  331. }
  332. public override string Message
  333. {
  334. get
  335. {
  336. if (InnerException != null) return InnerException.Message;
  337. if (LuaTraceback == null)
  338. {
  339. return ErrorObject.ToString();
  340. }
  341. return CreateMessage(LuaTraceback, ErrorObject, level);
  342. }
  343. }
  344. public override string ToString()
  345. {
  346. if (LuaTraceback == null)
  347. {
  348. return base.ToString();
  349. }
  350. var pooledList = new PooledList<char>(64);
  351. pooledList.Clear();
  352. try
  353. {
  354. pooledList.AddRange(Message);
  355. pooledList.Add('\n');
  356. pooledList.AddRange(LuaTraceback.ToString());
  357. pooledList.Add('\n');
  358. pooledList.AddRange(StackTrace);
  359. return pooledList.AsSpan().ToString();
  360. }
  361. finally
  362. {
  363. pooledList.Dispose();
  364. }
  365. }
  366. }
  367. public class LuaAssertionException(LuaThread? traceback, string message) : LuaRuntimeException(traceback, message);
  368. public class LuaModuleNotFoundException(string moduleName) : Exception($"module '{moduleName}' not found");
  369. public sealed class LuaCanceledException : OperationCanceledException, ILuaTracebackBuildable
  370. {
  371. Traceback? luaTraceback;
  372. public Traceback? LuaTraceback
  373. {
  374. get
  375. {
  376. if (luaTraceback == null)
  377. {
  378. ((ILuaTracebackBuildable)this).BuildOrGet();
  379. }
  380. return luaTraceback;
  381. }
  382. }
  383. internal LuaThread? Thread { get; private set; }
  384. internal LuaCanceledException(LuaThread thread, CancellationToken cancellationToken, Exception? innerException = null) : base("The operation was cancelled during execution on Lua.", innerException, cancellationToken)
  385. {
  386. thread.CurrentException?.BuildOrGet();
  387. thread.ExceptionTrace.Clear();
  388. thread.CurrentException = this;
  389. Thread = thread;
  390. }
  391. [MethodImpl(MethodImplOptions.NoInlining)]
  392. Traceback? ILuaTracebackBuildable.BuildOrGet()
  393. {
  394. if (luaTraceback != null) return luaTraceback;
  395. if (Thread != null)
  396. {
  397. var callStack = Thread.ExceptionTrace.AsSpan();
  398. if (callStack.IsEmpty) return null;
  399. luaTraceback = new Traceback(Thread.State, callStack);
  400. Thread.ExceptionTrace.Clear();
  401. Thread = null!;
  402. }
  403. return luaTraceback;
  404. }
  405. }