Skip to content

Trap Result #151

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 34 additions & 13 deletions src/Function.cs
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,22 @@ public static Function FromCallback<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11
/// <returns>Returns true if the type signature of the function is valid or false if not.</returns>
public bool CheckTypeSignature(Type? returnType = null, params Type[] parameters)
{
// Check if the return type is Result<T>, Result, ResultWithBacktrace<T> or ResultWithBacktrace
if (returnType != null && returnType.IsResult())
{
// Try to get the type the result wraps (may be null if it's one of the non-generic result types)
var wrappedReturnType = returnType.IsGenericType ? returnType.GetGenericArguments()[0] : null;

// Check that the result does not attempt to wrap another result (e.g. Result<Result<int>>)
if (wrappedReturnType != null && wrappedReturnType.IsResult())
{
return false;
}

// Type check with the wrapped value instead of the result
return CheckTypeSignature(wrappedReturnType, parameters);
}

// Check if the func returns no values if that's expected
if (Results.Count == 0 && returnType != null)
{
Expand Down Expand Up @@ -2007,10 +2023,10 @@ private unsafe TR InvokeWithReturn<TR>(ReadOnlySpan<Value> arguments, IReturnTyp

try
{
Invoke(arguments, output);
var trap = Invoke(arguments, output);

// Note: null suppression is safe because `Invoke` checks that `store` is not null
return factory.Create(store!, output);
return factory.Create(store!, trap, output);
}
finally
{
Expand All @@ -2037,7 +2053,11 @@ private unsafe void InvokeWithoutReturn(ReadOnlySpan<Value> arguments)
{
try
{
Invoke(arguments, stackalloc Value[0]);
var trap = Invoke(arguments, stackalloc Value[0]);
if (trap != IntPtr.Zero)
{
throw TrapException.FromOwnedTrap(trap);
}
}
finally
{
Expand Down Expand Up @@ -2103,19 +2123,23 @@ private unsafe void InvokeWithoutReturn(ReadOnlySpan<Value> arguments)
throw new ArgumentNullException(nameof(store));
}

var context = store.Context;

// Convert arguments (ValueBox) into a form wasm can consume (Value)
Span<Value> args = stackalloc Value[Parameters.Count];
for (int i = 0; i < arguments.Length; ++i)
for (var i = 0; i < arguments.Length; ++i)
{
args[i] = arguments[i].ToValue(Parameters[i]);
}

// Make some space to store the return results
Span<Value> resultsSpan = stackalloc Value[Results.Count];

try
{
Invoke(args, resultsSpan);
var trap = Invoke(args, resultsSpan);
if (trap != IntPtr.Zero)
{
throw TrapException.FromOwnedTrap(trap);
}

if (Results.Count == 0)
{
Expand Down Expand Up @@ -2161,9 +2185,9 @@ private unsafe void InvokeWithoutReturn(ReadOnlySpan<Value> arguments)
/// <param name="arguments">The arguments to pass to the function, wrapped as `Value`</param>
/// <param name="resultsOut">Output span to store the results in, must be the correct length</param>
/// <returns>
/// Returns null if the function has no return value.
/// Returns the trap ptr or zero
/// </returns>
private unsafe void Invoke(ReadOnlySpan<Value> arguments, Span<Value> resultsOut)
private unsafe IntPtr Invoke(ReadOnlySpan<Value> arguments, Span<Value> resultsOut)
{
if (IsNull)
{
Expand All @@ -2190,10 +2214,7 @@ private unsafe void Invoke(ReadOnlySpan<Value> arguments, Span<Value> resultsOut
throw WasmtimeException.FromOwnedError(error);
}

if (trap != IntPtr.Zero)
{
throw TrapException.FromOwnedTrap(trap);
}
return trap;
}

Extern IExternal.AsExtern()
Expand Down
287 changes: 287 additions & 0 deletions src/Result.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,287 @@
using System;

namespace Wasmtime
{
/// <summary>
/// Indicates what type of result this is
/// </summary>
public enum ResultType
{
/// <summary>
/// Excecution succeeded
/// </summary>
Ok = 0,

/// <summary>
/// Result contains a trap
/// </summary>
Trap = 1,
}

/// <summary>
/// A result from a function call which may represent a Value or a Trap. If a trap happens the full backtrace is captured.
/// </summary>
public readonly struct ResultWithBacktrace
{
/// <summary>
/// Indicates what type of result this contains
/// </summary>
public ResultType Type { get; }

private readonly TrapException? _trap;

internal ResultWithBacktrace(IntPtr trap)
{
Type = ResultType.Trap;
_trap = TrapException.FromOwnedTrap(trap);
}

/// <summary>
/// Get the trap associated with this result
/// </summary>
/// <exception cref="InvalidOperationException">Thrown if this Type != Types.Trap</exception>
public TrapException Trap
{
get
{
if (Type != ResultType.Trap)
{
throw new InvalidOperationException($"Cannot get 'Trap' from '{Type}' type result");
}

return _trap!;
}
}
}

/// <summary>
/// A result from a function call which may represent a Value or a Trap
/// </summary>
public readonly struct Result
{
/// <summary>
/// Indicates what type of result this contains
/// </summary>
public ResultType Type { get; }

private readonly TrapCode _trap;

internal Result(IntPtr trap)
{
Type = ResultType.Trap;
_trap = TrapException.GetTrapCode(trap);
TrapException.Native.wasm_trap_delete(trap);
}

/// <summary>
/// Get the trap associated with this result
/// </summary>
/// <exception cref="InvalidOperationException">Thrown if this Type != Types.Trap</exception>
public TrapCode TrapCode
{
get
{
if (Type != ResultType.Trap)
{
throw new InvalidOperationException($"Cannot get 'Trap' from '{Type}' type result");
}

return _trap;
}
}
}

/// <summary>
/// A result from a function call which may represent a Value or a Trap. If a trap happens the full backtrace is captured.
/// </summary>
/// <typeparam name="T">Type of the return value contained in this result</typeparam>
public readonly struct ResultWithBacktrace<T>
{
/// <summary>
/// Indicates what type of result this contains
/// </summary>
public ResultType Type { get; }

private readonly T? _value;
private readonly TrapException? _trap;

internal ResultWithBacktrace(T value)
{
Type = ResultType.Ok;
_value = value;
_trap = null;
}

internal ResultWithBacktrace(IntPtr trap)
{
Type = ResultType.Trap;
_value = default;
_trap = TrapException.FromOwnedTrap(trap);
}

/// <summary>
/// Convert this result into a value, throw if it is a Trap
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
/// <exception cref="TrapException">Thrown if Type == Trap</exception>
/// <exception cref="ArgumentOutOfRangeException">Thrown if Type property contains an unknown value</exception>
public static explicit operator T?(ResultWithBacktrace<T> value)
{
switch (value.Type)
{
case ResultType.Ok:
return value._value;

case ResultType.Trap:
throw value._trap!;

default:
throw new ArgumentOutOfRangeException(nameof(value), $"Unknown Result Type property value '{value.Type}'");
}
}

/// <summary>
/// Get the value associated with this result
/// </summary>
/// <exception cref="InvalidOperationException">Thrown if this Type != Types.Value</exception>
public T? Value
{
get
{
if (Type != ResultType.Ok)
{
throw new InvalidOperationException($"Cannot get 'Value' from '{Type}' type result");
}

return _value;
}
}

/// <summary>
/// Get the trap associated with this result
/// </summary>
/// <exception cref="InvalidOperationException">Thrown if this Type != Types.Trap</exception>
public TrapException Trap
{
get
{
if (Type != ResultType.Trap)
{
throw new InvalidOperationException($"Cannot get 'Trap' from '{Type}' type result");
}

return _trap!;
}
}
}

/// <summary>
/// A result from a function call which may represent a Value or a Trap
/// </summary>
/// <typeparam name="T">Type of the return value contained in this result</typeparam>
public readonly struct Result<T>
{
/// <summary>
/// Indicates what type of result this contains
/// </summary>
public ResultType Type { get; }

private readonly T? _value;
private readonly TrapCode _trap;

internal Result(T value)
{
Type = ResultType.Ok;

_value = value;
_trap = default;
}

internal Result(IntPtr trap)
{
Type = ResultType.Trap;

_value = default;

_trap = TrapException.GetTrapCode(trap);
TrapException.Native.wasm_trap_delete(trap);

}

/// <summary>
/// Convert this result into a value, throw if it is a Trap
/// </summary>
/// <param name="value"></param>
/// <returns>The value contained within this result if Type == ResultType.Ok</returns>
/// <exception cref="TrapException">Thrown if Type == Trap</exception>
/// <exception cref="ArgumentOutOfRangeException">Thrown if Type property contains an unknoown value</exception>
public static explicit operator T?(Result<T> value)
{
switch (value.Type)
{
case ResultType.Ok:
return value._value;

case ResultType.Trap:
throw new TrapException($"{value._trap} trap", null, value._trap);

default:
throw new ArgumentOutOfRangeException(nameof(value), $"Unknown Result Type property value '{value.Type}'");
}
}

/// <summary>
/// Get the value associated with this result
/// </summary>
/// <exception cref="InvalidOperationException">Thrown if this Type != Types.Value</exception>
public T? Value
{
get
{
if (Type != ResultType.Ok)
{
throw new InvalidOperationException($"Cannot get 'Value' from '{Type}' type result");
}

return _value;
}
}

/// <summary>
/// Get the trap associated with this result
/// </summary>
/// <exception cref="InvalidOperationException">Thrown if this Type != Types.Trap</exception>
public TrapCode TrapCode
{
get
{
if (Type != ResultType.Trap)
{
throw new InvalidOperationException($"Cannot get 'Trap' from '{Type}' type result");
}

return _trap;
}
}
}

internal static class TypeExtensions
{
internal static bool IsResult(this Type type)
{
if (type == typeof(Result) || type == typeof(ResultWithBacktrace))
{
return true;
}

if (!type.IsGenericType)
{
return false;
}

var gtd = type.GetGenericTypeDefinition();
return typeof(Result<>) == gtd || typeof(ResultWithBacktrace<>) == gtd;
}
}
}
Loading