DisposeCapability.cs 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. using Jint.Runtime;
  2. namespace Jint.Native.Disposable;
  3. internal enum DisposeHint
  4. {
  5. Normal,
  6. Sync,
  7. Async,
  8. }
  9. internal sealed class DisposeCapability
  10. {
  11. private readonly Engine _engine;
  12. private readonly List<DisposableResource> _disposableResourceStack = [];
  13. public DisposeCapability(Engine engine)
  14. {
  15. _engine = engine;
  16. }
  17. public void AddDisposableResource(JsValue v, DisposeHint hint, ICallable? method = null)
  18. {
  19. DisposableResource resource;
  20. if (method is null)
  21. {
  22. if (v.IsNullOrUndefined() && hint == DisposeHint.Sync)
  23. {
  24. return;
  25. }
  26. resource = CreateDisposableResource(v, hint);
  27. }
  28. else
  29. {
  30. resource = CreateDisposableResource(JsValue.Undefined, hint, method);
  31. }
  32. _disposableResourceStack.Add(resource);
  33. }
  34. private DisposableResource CreateDisposableResource(JsValue v, DisposeHint hint, ICallable? method = null)
  35. {
  36. if (method is null)
  37. {
  38. if (v.IsNullOrUndefined())
  39. {
  40. v = JsValue.Undefined;
  41. method = null;
  42. }
  43. else
  44. {
  45. if (!v.IsObject())
  46. {
  47. ExceptionHelper.ThrowTypeError(_engine.Realm, "Expected an object for disposable resource.");
  48. return default;
  49. }
  50. method = v.AsObject().GetDisposeMethod(hint);
  51. if (method is null)
  52. {
  53. ExceptionHelper.ThrowTypeError(_engine.Realm, "No dispose method found for the resource.");
  54. return default;
  55. }
  56. }
  57. }
  58. return new DisposableResource(v, hint, method);
  59. }
  60. public Completion DisposeResources(Completion c)
  61. {
  62. var needsAwait = false;
  63. var hasAwaited = false;
  64. for (var i = _disposableResourceStack.Count - 1; i >= 0; i--)
  65. {
  66. var (value, hint, method) = _disposableResourceStack[i];
  67. if (hint == DisposeHint.Sync && needsAwait && !hasAwaited)
  68. {
  69. _engine.RunAvailableContinuations();
  70. needsAwait = false;
  71. }
  72. if (method is not null)
  73. {
  74. var result = JsValue.Undefined;
  75. JavaScriptException? exception = null;
  76. try
  77. {
  78. result = method.Call(value);
  79. if (hint == DisposeHint.Async)
  80. {
  81. hasAwaited = true;
  82. try
  83. {
  84. result = result.UnwrapIfPromise();
  85. }
  86. catch (JavaScriptException e)
  87. {
  88. exception = e;
  89. }
  90. }
  91. }
  92. catch (JavaScriptException e)
  93. {
  94. exception = e;
  95. }
  96. if (exception is not null)
  97. {
  98. if (c.Type == CompletionType.Throw)
  99. {
  100. var error = _engine.Intrinsics.SuppressedError.Construct(_engine.Intrinsics.SuppressedError, "", exception.Error, c.Value);
  101. c = new Completion(CompletionType.Throw, error, c._source);
  102. }
  103. else
  104. {
  105. c = new Completion(CompletionType.Throw, exception.Error, c._source);
  106. }
  107. }
  108. }
  109. else
  110. {
  111. // Assert: hint is "async-dispose"
  112. needsAwait = true;
  113. }
  114. }
  115. if (needsAwait && !hasAwaited)
  116. {
  117. _engine.RunAvailableContinuations();
  118. }
  119. _disposableResourceStack.Clear();
  120. return c;
  121. }
  122. private readonly record struct DisposableResource(JsValue ResourceValue, DisposeHint Hint, ICallable? DisposeMethod);
  123. }