Skip to content

Commit ac3f89d

Browse files
authored
Add explicit resource management supporting types (#2143)
1 parent 7b6b733 commit ac3f89d

16 files changed

+715
-26
lines changed

Jint.Repl/Program.cs

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
using Jint.Native.Json;
55
using Jint.Runtime;
66

7+
// ReSharper disable LocalizableElement
8+
79
#pragma warning disable IL2026
810
#pragma warning disable IL2111
911

@@ -13,6 +15,7 @@
1315

1416
engine
1517
.SetValue("print", new Action<object>(Console.WriteLine))
18+
.SetValue("console", new JsConsole())
1619
.SetValue("load", new Func<string, object>(
1720
path => engine.Evaluate(File.ReadAllText(path)))
1821
);
@@ -22,7 +25,7 @@
2225
{
2326
if (!File.Exists(filename))
2427
{
25-
Console.WriteLine("Could not find file: {0}", filename);
28+
Console.WriteLine($"Could not find file: {filename}");
2629
}
2730

2831
var script = File.ReadAllText(filename);
@@ -33,10 +36,8 @@
3336
var assembly = Assembly.GetExecutingAssembly();
3437
var version = assembly.GetName().Version?.ToString();
3538

36-
Console.WriteLine("Welcome to Jint ({0})", version);
37-
Console.WriteLine("Type 'exit' to leave, " +
38-
"'print()' to write on the console, " +
39-
"'load()' to load scripts.");
39+
Console.WriteLine($"Welcome to Jint ({version})");
40+
Console.WriteLine("Type 'exit' to leave, 'print()' to write on the console, 'load()' to load scripts.");
4041
Console.WriteLine();
4142

4243
var defaultColor = Console.ForegroundColor;
@@ -86,3 +87,18 @@
8687
Console.WriteLine(e.Message);
8788
}
8889
}
90+
91+
file sealed class JsConsole
92+
{
93+
public void Log(object value)
94+
{
95+
Console.WriteLine(value?.ToString() ?? "null");
96+
}
97+
98+
public void Error(object value)
99+
{
100+
Console.ForegroundColor = ConsoleColor.Red;
101+
Console.WriteLine(value?.ToString() ?? "null");
102+
Console.ResetColor();
103+
}
104+
}

Jint.Tests/Runtime/TypedArrayInteropTests.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
using Jint.Native;
2-
31
namespace Jint.Tests.Runtime;
42

53
public class TypedArrayInteropTests
@@ -136,7 +134,7 @@ public void CanInteropWithFloat16()
136134
Assert.True(fromEngine.IsFloat16Array());
137135
Assert.Equal(source, fromEngine.AsFloat16Array());
138136

139-
engine.SetValue("testFunc", new Func<JsTypedArray, JsTypedArray>(v => v));
137+
engine.SetValue("testFunc", new Func<Native.JsTypedArray, Native.JsTypedArray>(v => v));
140138
Assert.Equal(source, engine.Evaluate("testFunc(testSubject)").AsFloat16Array());
141139
}
142140
#endif
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using Jint.Native.Object;
2+
using Jint.Runtime;
3+
using Jint.Runtime.Descriptors;
4+
5+
namespace Jint.Native.Disposable;
6+
7+
internal sealed class AsyncDisposableStackConstructor : Constructor
8+
{
9+
private static readonly JsString _name = new("AsyncDisposableStack");
10+
11+
public AsyncDisposableStackConstructor(Engine engine, Realm realm) : base(engine, realm, _name)
12+
{
13+
PrototypeObject = new AsyncDisposableStackPrototype(engine, realm, this, engine.Intrinsics.Object.PrototypeObject);
14+
_length = new PropertyDescriptor(0, PropertyFlag.Configurable);
15+
_prototypeDescriptor = new PropertyDescriptor(PrototypeObject, PropertyFlag.AllForbidden);
16+
}
17+
18+
internal AsyncDisposableStackPrototype PrototypeObject { get; }
19+
20+
public override ObjectInstance Construct(JsCallArguments arguments, JsValue newTarget)
21+
{
22+
if (newTarget.IsUndefined())
23+
{
24+
ExceptionHelper.ThrowTypeError(_realm);
25+
}
26+
27+
var stack = OrdinaryCreateFromConstructor(
28+
newTarget,
29+
static intrinsics => intrinsics.AsyncDisposableStack.PrototypeObject,
30+
static (Engine engine, Realm _, object? _) => new DisposableStack(engine, DisposeHint.Async));
31+
32+
return stack;
33+
}
34+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
using Jint.Native.Object;
2+
using Jint.Native.Symbol;
3+
using Jint.Runtime;
4+
using Jint.Runtime.Descriptors;
5+
using Jint.Runtime.Interop;
6+
7+
namespace Jint.Native.Disposable;
8+
9+
internal sealed class AsyncDisposableStackPrototype : Prototype
10+
{
11+
private readonly AsyncDisposableStackConstructor _constructor;
12+
13+
internal AsyncDisposableStackPrototype(
14+
Engine engine,
15+
Realm realm,
16+
AsyncDisposableStackConstructor constructor,
17+
ObjectPrototype objectPrototype) : base(engine, realm)
18+
{
19+
_prototype = objectPrototype;
20+
_constructor = constructor;
21+
}
22+
23+
protected override void Initialize()
24+
{
25+
var disposeFunction = new ClrFunction(Engine, "disposeAsync", Dispose, 0, PropertyFlag.Configurable);
26+
27+
const PropertyFlag PropertyFlags = PropertyFlag.Configurable | PropertyFlag.Writable;
28+
var properties = new PropertyDictionary(8, checkExistingKeys: false)
29+
{
30+
["length"] = new PropertyDescriptor(0, PropertyFlag.Configurable),
31+
["constructor"] = new PropertyDescriptor(_constructor, PropertyFlag.NonEnumerable),
32+
["adopt"] = new PropertyDescriptor(new ClrFunction(Engine, "adopt", Adopt, 2, PropertyFlag.Configurable), PropertyFlags),
33+
["defer"] = new PropertyDescriptor(new ClrFunction(Engine, "defer", Defer, 1, PropertyFlag.Configurable), PropertyFlags),
34+
["disposeAsync"] = new PropertyDescriptor(disposeFunction, PropertyFlags),
35+
["disposed"] = new GetSetPropertyDescriptor(get: new ClrFunction(Engine, "get disposed", Disposed, 0, PropertyFlag.Configurable), set: Undefined, PropertyFlags),
36+
["move"] = new PropertyDescriptor(new ClrFunction(Engine, "move", Move, 0, PropertyFlag.Configurable), PropertyFlags),
37+
["use"] = new PropertyDescriptor(new ClrFunction(Engine, "use", Use, 1, PropertyFlag.Configurable), PropertyFlags),
38+
};
39+
SetProperties(properties);
40+
41+
var symbols = new SymbolDictionary(2)
42+
{
43+
[GlobalSymbolRegistry.AsyncDispose] = new PropertyDescriptor(disposeFunction, PropertyFlags),
44+
[GlobalSymbolRegistry.ToStringTag] = new PropertyDescriptor("AsyncDisposableStack", PropertyFlag.Configurable),
45+
};
46+
SetSymbols(symbols);
47+
}
48+
49+
private JsValue Adopt(JsValue thisObject, JsCallArguments arguments)
50+
{
51+
var stack = AssertDisposableStack(thisObject);
52+
return stack.Adopt(arguments.At(0), arguments.At(1));
53+
}
54+
55+
private JsValue Defer(JsValue thisObject, JsCallArguments arguments)
56+
{
57+
var stack = AssertDisposableStack(thisObject);
58+
stack.Defer(arguments.At(0));
59+
return Undefined;
60+
}
61+
62+
private JsValue Dispose(JsValue thisObject, JsCallArguments arguments)
63+
{
64+
var stack = AssertDisposableStack(thisObject);
65+
return stack.Dispose();
66+
}
67+
68+
private JsValue Disposed(JsValue thisObject, JsCallArguments arguments)
69+
{
70+
var stack = AssertDisposableStack(thisObject);
71+
return stack.State == DisposableState.Disposed;
72+
}
73+
74+
private JsValue Move(JsValue thisObject, JsCallArguments arguments)
75+
{
76+
var stack = AssertDisposableStack(thisObject);
77+
var newDisposableStack = _engine.Intrinsics.Function.OrdinaryCreateFromConstructor(
78+
_engine.Intrinsics.AsyncDisposableStack,
79+
static intrinsics => intrinsics.AsyncDisposableStack.PrototypeObject,
80+
static (Engine engine, Realm _, object? _) => new DisposableStack(engine, DisposeHint.Async));
81+
82+
return stack.Move(newDisposableStack);
83+
}
84+
85+
private JsValue Use(JsValue thisObject, JsCallArguments arguments)
86+
{
87+
var stack = AssertDisposableStack(thisObject);
88+
return stack.Use(arguments.At(0));
89+
}
90+
91+
private DisposableStack AssertDisposableStack(JsValue thisObject)
92+
{
93+
if (thisObject is not DisposableStack { _hint: DisposeHint.Async } stack)
94+
{
95+
ExceptionHelper.ThrowTypeError(_engine.Realm, "This is not a AsyncDisposableStack instance.");
96+
return null!;
97+
}
98+
99+
return stack;
100+
}
101+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
using Jint.Native.Object;
2+
using Jint.Runtime;
3+
using Jint.Runtime.Interop;
4+
5+
namespace Jint.Native.Disposable;
6+
7+
internal enum DisposableState
8+
{
9+
Pending,
10+
Disposed,
11+
}
12+
13+
internal sealed class DisposableStack : ObjectInstance
14+
{
15+
internal readonly DisposeHint _hint;
16+
private DisposeCapability _disposeCapability;
17+
18+
public DisposableStack(Engine engine, DisposeHint hint) : base(engine)
19+
{
20+
_hint = hint;
21+
State = DisposableState.Pending;
22+
_disposeCapability = new DisposeCapability(engine);
23+
}
24+
25+
public DisposableState State { get; private set; }
26+
27+
public JsValue Dispose()
28+
{
29+
if (State == DisposableState.Disposed)
30+
{
31+
return Undefined;
32+
}
33+
34+
State = DisposableState.Disposed;
35+
var completion = _disposeCapability.DisposeResources(new Completion(CompletionType.Normal, Undefined, _engine.GetLastSyntaxElement()));
36+
if (completion.Type == CompletionType.Throw)
37+
{
38+
ExceptionHelper.ThrowJavaScriptException(_engine, completion.Value, completion);
39+
}
40+
return completion.Value;
41+
}
42+
43+
public void Defer(JsValue onDispose)
44+
{
45+
AddDisposableResource(Undefined, _hint, onDispose.GetCallable(_engine.Realm));
46+
}
47+
48+
public JsValue Use(JsValue value)
49+
{
50+
AddDisposableResource(value, _hint);
51+
return value;
52+
}
53+
54+
public JsValue Adopt(JsValue value, JsValue onDispose)
55+
{
56+
AssertNotDisposed();
57+
58+
var callable = onDispose.GetCallable(_engine.Realm);
59+
JsCallDelegate closure = (_, _) =>
60+
{
61+
callable.Call(Undefined, value);
62+
return Undefined;
63+
};
64+
65+
var f = new ClrFunction(_engine, string.Empty, closure);
66+
AddDisposableResource(Undefined, DisposeHint.Sync, f);
67+
68+
return value;
69+
}
70+
71+
public JsValue Move(DisposableStack newDisposableStack)
72+
{
73+
AssertNotDisposed();
74+
75+
newDisposableStack.State = DisposableState.Pending;
76+
newDisposableStack._disposeCapability = this._disposeCapability;
77+
this._disposeCapability = new DisposeCapability(_engine);
78+
State = DisposableState.Disposed;
79+
return newDisposableStack;
80+
}
81+
82+
private void AddDisposableResource(JsValue v, DisposeHint hint, ICallable? method = null)
83+
{
84+
AssertNotDisposed();
85+
_disposeCapability.AddDisposableResource(v, hint, method);
86+
}
87+
88+
private void AssertNotDisposed()
89+
{
90+
if (State == DisposableState.Disposed)
91+
{
92+
ExceptionHelper.ThrowReferenceError(_engine.Realm, "Stack already disposed.");
93+
}
94+
}
95+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using Jint.Native.Object;
2+
using Jint.Runtime;
3+
using Jint.Runtime.Descriptors;
4+
5+
namespace Jint.Native.Disposable;
6+
7+
internal sealed class DisposableStackConstructor : Constructor
8+
{
9+
private static readonly JsString _name = new("DisposableStack");
10+
11+
public DisposableStackConstructor(Engine engine, Realm realm) : base(engine, realm, _name)
12+
{
13+
PrototypeObject = new DisposableStackPrototype(engine, realm, this, engine.Intrinsics.Object.PrototypeObject);
14+
_length = new PropertyDescriptor(0, PropertyFlag.Configurable);
15+
_prototypeDescriptor = new PropertyDescriptor(PrototypeObject, PropertyFlag.AllForbidden);
16+
}
17+
18+
internal DisposableStackPrototype PrototypeObject { get; }
19+
20+
public override ObjectInstance Construct(JsCallArguments arguments, JsValue newTarget)
21+
{
22+
if (newTarget.IsUndefined())
23+
{
24+
ExceptionHelper.ThrowTypeError(_realm);
25+
}
26+
27+
var stack = OrdinaryCreateFromConstructor(
28+
newTarget,
29+
static intrinsics => intrinsics.DisposableStack.PrototypeObject,
30+
static (Engine engine, Realm _, object? _) => new DisposableStack(engine, DisposeHint.Sync));
31+
32+
return stack;
33+
}
34+
}

0 commit comments

Comments
 (0)