Exceptions.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419
  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 interface ILuaTracebackBuildable
  58. {
  59. Traceback? BuildOrGet();
  60. }
  61. public class LuaRuntimeException : Exception, ILuaTracebackBuildable
  62. {
  63. public LuaRuntimeException(LuaThread? thread, Exception innerException) : base(innerException.Message, innerException)
  64. {
  65. if (thread != null)
  66. {
  67. thread.CurrentException?.BuildOrGet();
  68. thread.ExceptionTrace.Clear();
  69. thread.CurrentException = this;
  70. }
  71. Thread = thread;
  72. }
  73. public LuaRuntimeException(LuaThread? thread, LuaValue errorObject)
  74. {
  75. if (thread != null)
  76. {
  77. thread.CurrentException?.BuildOrGet();
  78. thread.ExceptionTrace.Clear();
  79. thread.CurrentException = this;
  80. }
  81. Thread = thread;
  82. ErrorObject = errorObject;
  83. }
  84. Traceback? luaTraceback;
  85. public Traceback? LuaTraceback
  86. {
  87. get
  88. {
  89. if (luaTraceback == null)
  90. {
  91. ((ILuaTracebackBuildable)this).BuildOrGet();
  92. }
  93. return luaTraceback;
  94. }
  95. }
  96. internal LuaThread? Thread { get; private set; } = default!;
  97. public LuaValue ErrorObject { get; }
  98. public static void AttemptInvalidOperation(LuaThread? thread, string op, LuaValue a, LuaValue b)
  99. {
  100. var typeA = a.TypeToString();
  101. var typeB = b.TypeToString();
  102. if (typeA == typeB)
  103. {
  104. throw new LuaRuntimeException(thread, $"attempt to {op} two {typeA} values");
  105. }
  106. throw new LuaRuntimeException(thread, $"attempt to {op} a {typeA} value with a {typeB} value");
  107. }
  108. public static void AttemptInvalidOperation(LuaThread? thread, string op, LuaValue a)
  109. {
  110. throw new LuaRuntimeException(thread, $"attempt to {op} a {a.TypeToString()} value");
  111. }
  112. internal static void AttemptInvalidOperationOnLuaStack(LuaThread thread, string op, int lastPc, int regA, int regB)
  113. {
  114. var caller = thread.GetCurrentFrame();
  115. var luaValueA = regA < 255 ? thread.Stack[caller.Base + regA] : ((LuaClosure)caller.Function).Proto.Constants[regA - 256];
  116. var luaValueB = regB < 255 ? thread.Stack[caller.Base + regB] : ((LuaClosure)caller.Function).Proto.Constants[regB - 256];
  117. var function = caller.Function;
  118. var tA = LuaDebug.GetName(((LuaClosure)function).Proto, lastPc, regA, out string? nameA);
  119. var tB = LuaDebug.GetName(((LuaClosure)function).Proto, lastPc, regB, out string? nameB);
  120. using var builder = new PooledList<char>(64);
  121. builder.Clear();
  122. builder.AddRange("attempt to ");
  123. builder.AddRange(op);
  124. builder.AddRange(" a ");
  125. builder.AddRange(luaValueA.TypeToString());
  126. builder.AddRange(" value");
  127. if (tA != null && nameA != null)
  128. {
  129. builder.AddRange($" ({tA} '{nameA}')");
  130. }
  131. builder.AddRange(" with a ");
  132. builder.AddRange(luaValueB.TypeToString());
  133. builder.AddRange(" value");
  134. if (tB != null && nameB != null)
  135. {
  136. builder.AddRange($" ({tB} '{nameB}')");
  137. }
  138. throw new LuaRuntimeException(thread, builder.AsSpan().ToString());
  139. }
  140. internal static void AttemptInvalidOperationOnLuaStack(LuaThread thread, string op, int lastPc, int reg)
  141. {
  142. var caller = thread.GetCurrentFrame();
  143. var luaValue = reg < 255 ? thread.Stack[caller.Base + reg] : ((LuaClosure)caller.Function).Proto.Constants[reg - 256];
  144. var function = caller.Function;
  145. var t = LuaDebug.GetName(((LuaClosure)function).Proto, lastPc, reg, out string? name);
  146. using var builder = new PooledList<char>(64);
  147. builder.Clear();
  148. builder.AddRange("attempt to ");
  149. builder.AddRange(op);
  150. builder.AddRange(" a ");
  151. builder.AddRange(luaValue.TypeToString());
  152. builder.AddRange(" value");
  153. if (t != null && name != null)
  154. {
  155. builder.AddRange($" ({t} '{name}')");
  156. }
  157. throw new LuaRuntimeException(thread, builder.AsSpan().ToString());
  158. }
  159. internal static void AttemptInvalidOperationOnUpValues(LuaThread thread, string op, int reg)
  160. {
  161. var caller = thread.GetCurrentFrame();
  162. var closure = (LuaClosure)caller.Function;
  163. var proto = closure.Proto;
  164. var upValue = proto.UpValues[reg];
  165. var luaValue = closure.UpValues[upValue.Index].GetValue();
  166. var name = upValue.Name;
  167. throw new LuaRuntimeException(thread, $"attempt to {op} a {luaValue.TypeToString()} value (global '{name}')");
  168. }
  169. internal static (string NameWhat, string Name) GetCurrentFunctionName(LuaThread thread)
  170. {
  171. var current = thread.GetCurrentFrame();
  172. var pc = current.CallerInstructionIndex;
  173. LuaFunction callerFunction;
  174. if (current.IsTailCall)
  175. {
  176. pc = thread.LastPc;
  177. callerFunction = thread.LastCallerFunction!;
  178. }
  179. else
  180. {
  181. var caller = thread.GetCallStackFrames()[^2];
  182. callerFunction = caller.Function;
  183. }
  184. if (callerFunction is not LuaClosure callerClosure)
  185. {
  186. return ("function", callerFunction.Name);
  187. }
  188. return (LuaDebug.GetFuncName(callerClosure.Proto, pc, out var name) ?? "", name ?? current.Function.Name);
  189. }
  190. public static void BadArgument(LuaThread thread, int argumentId)
  191. {
  192. BadArgument(thread, argumentId, "value expected");
  193. }
  194. public static void BadArgument(LuaThread thread, int argumentId, LuaValueType expected, LuaValueType actual)
  195. {
  196. BadArgument(thread, argumentId, $"{LuaValue.ToString(expected)} expected, got {LuaValue.ToString(actual)})");
  197. }
  198. public static void BadArgument(LuaThread thread, int argumentId, LuaValueType[] expected, LuaValueType actual)
  199. {
  200. BadArgument(thread, argumentId, $"({string.Join(" or ", expected.Select(LuaValue.ToString))} expected, got {LuaValue.ToString(actual)})");
  201. }
  202. public static void BadArgument(LuaThread thread, int argumentId, string expected, string actual)
  203. {
  204. BadArgument(thread, argumentId, $"({expected} expected, got {actual})");
  205. }
  206. public static void BadArgument(LuaThread thread, int argumentId, string[] expected, string actual)
  207. {
  208. if (expected.Length == 0)
  209. {
  210. throw new ArgumentException("Expected array must not be empty", nameof(expected));
  211. }
  212. BadArgument(thread, argumentId, $"({string.Join(" or ", expected)} expected, got {actual})");
  213. }
  214. public static void BadArgument(LuaThread thread, int argumentId, string message)
  215. {
  216. var (nameWhat, name) = GetCurrentFunctionName(thread);
  217. if (nameWhat == "method")
  218. {
  219. argumentId--;
  220. if (argumentId == 0)
  221. {
  222. throw new LuaRuntimeException(thread, $"calling '{name}' on bad self ({message})");
  223. }
  224. }
  225. throw new LuaRuntimeException(thread, $"bad argument #{argumentId} to '{name}' ({message})");
  226. }
  227. public static void BadArgumentNumberIsNotInteger(LuaThread thread, int argumentId)
  228. {
  229. BadArgument(thread, argumentId, "number has no integer representation");
  230. }
  231. public static void ThrowBadArgumentIfNumberIsNotInteger(LuaThread thread, int argumentId, double value)
  232. {
  233. if (!MathEx.IsInteger(value))
  234. {
  235. BadArgumentNumberIsNotInteger(thread, argumentId);
  236. }
  237. }
  238. static string CreateMessage(Traceback traceback, LuaValue errorObject)
  239. {
  240. var pooledList = new PooledList<char>(64);
  241. pooledList.Clear();
  242. try
  243. {
  244. pooledList.AddRange("Lua-CSharp: ");
  245. traceback.WriteLastLuaTrace(ref pooledList);
  246. pooledList.AddRange(": ");
  247. pooledList.AddRange($"{errorObject}");
  248. return pooledList.AsSpan().ToString();
  249. }
  250. finally
  251. {
  252. pooledList.Dispose();
  253. }
  254. }
  255. [MethodImpl(MethodImplOptions.NoInlining)]
  256. Traceback? ILuaTracebackBuildable.BuildOrGet()
  257. {
  258. if (luaTraceback != null) return luaTraceback;
  259. if (Thread != null)
  260. {
  261. var callStack = Thread.ExceptionTrace.AsSpan();
  262. if (callStack.IsEmpty) return null;
  263. luaTraceback = new Traceback(Thread.State, callStack);
  264. Thread.ExceptionTrace.Clear();
  265. Thread = null;
  266. }
  267. return luaTraceback;
  268. }
  269. internal void Forget()
  270. {
  271. Thread?.ExceptionTrace.Clear();
  272. Thread = null;
  273. }
  274. public override string Message
  275. {
  276. get
  277. {
  278. if (InnerException != null) return InnerException.Message;
  279. if (LuaTraceback == null)
  280. {
  281. return ErrorObject.ToString();
  282. }
  283. return CreateMessage(LuaTraceback, ErrorObject);
  284. }
  285. }
  286. public override string ToString()
  287. {
  288. if (LuaTraceback == null)
  289. {
  290. return base.ToString();
  291. }
  292. var pooledList = new PooledList<char>(64);
  293. pooledList.Clear();
  294. try
  295. {
  296. pooledList.AddRange(Message);
  297. pooledList.Add('\n');
  298. pooledList.AddRange(LuaTraceback.ToString());
  299. pooledList.Add('\n');
  300. pooledList.AddRange(StackTrace);
  301. return pooledList.AsSpan().ToString();
  302. }
  303. finally
  304. {
  305. pooledList.Dispose();
  306. }
  307. }
  308. }
  309. public class LuaAssertionException(LuaThread? traceback, string message) : LuaRuntimeException(traceback, message);
  310. public class LuaModuleNotFoundException(string moduleName) : Exception($"module '{moduleName}' not found");
  311. public sealed class LuaCanceledException : OperationCanceledException, ILuaTracebackBuildable
  312. {
  313. Traceback? luaTraceback;
  314. public Traceback? LuaTraceback
  315. {
  316. get
  317. {
  318. if (luaTraceback == null)
  319. {
  320. ((ILuaTracebackBuildable)this).BuildOrGet();
  321. }
  322. return luaTraceback;
  323. }
  324. }
  325. internal LuaThread? Thread { get; private set; }
  326. internal LuaCanceledException(LuaThread thread, CancellationToken cancellationToken, Exception? innerException = null) : base("The operation was cancelled during execution on Lua.", innerException, cancellationToken)
  327. {
  328. thread.CurrentException?.BuildOrGet();
  329. thread.ExceptionTrace.Clear();
  330. thread.CurrentException = this;
  331. Thread = thread;
  332. }
  333. [MethodImpl(MethodImplOptions.NoInlining)]
  334. Traceback? ILuaTracebackBuildable.BuildOrGet()
  335. {
  336. if (luaTraceback != null) return luaTraceback;
  337. if (Thread != null)
  338. {
  339. var callStack = Thread.ExceptionTrace.AsSpan();
  340. if (callStack.IsEmpty) return null;
  341. luaTraceback = new Traceback(Thread.State, callStack);
  342. Thread.ExceptionTrace.Clear();
  343. Thread = null!;
  344. }
  345. return luaTraceback;
  346. }
  347. }