@hew-lang/sandbox-vm 0.10.0 → 0.11.0-rc3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -0
- package/dist/interpreter/v1/exec.js +215 -48
- package/dist/interpreter/v1/failure-limits.generated.d.ts +2 -0
- package/dist/interpreter/v1/failure-limits.generated.js +3 -0
- package/dist/interpreter/v1/instructions.generated.d.ts +5 -1
- package/dist/interpreter/v1/package-graph.js +6 -0
- package/dist/interpreter/v1/package-validator.generated.js +1547 -1177
- package/dist/interpreter/v1/shims.js +43 -0
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -61,6 +61,23 @@ A `sandbox_rejected` result includes rejection entries with `category`, `code`,
|
|
|
61
61
|
checked traps, VM failures and step-budget exhaustion have distinct statuses.
|
|
62
62
|
Consumers can offer explicit remote execution for native capabilities.
|
|
63
63
|
|
|
64
|
+
## Crash details
|
|
65
|
+
|
|
66
|
+
Actor callers and crash hooks receive the same originating `CrashInfo`, with
|
|
67
|
+
owned kind, trap, message, site and trace fields. The VM captures up to 48 real
|
|
68
|
+
Hew activations before unwind and folds consecutive identical functions into
|
|
69
|
+
`StackFrame.repeat`. Distinct instruction sites remain separate. Diagnostic
|
|
70
|
+
messages are bounded to 4096 UTF-8 bytes, including an explicit ` [truncated]`
|
|
71
|
+
suffix when needed; oversized function names are omitted. Capture is always enabled; there is no VM trace-control
|
|
72
|
+
option. Frames describe the failing execution rather than its observer.
|
|
73
|
+
|
|
74
|
+
Current bytecode carries function names and byte-offset spans, but no source
|
|
75
|
+
file and line table. `CrashInfo.site` and each frame's `site` are therefore
|
|
76
|
+
`None`. A failure outside an executing Hew activation also has no trace.
|
|
77
|
+
Diagnostic materialization respects the receiving actor's managed heap budget:
|
|
78
|
+
it omits the trace first, then uses an empty message if necessary, preserving
|
|
79
|
+
the original kind and trap when diagnostic storage cannot fit.
|
|
80
|
+
|
|
64
81
|
## Validation
|
|
65
82
|
|
|
66
83
|
From the repository root:
|
|
@@ -21,6 +21,7 @@ import { Semaphores } from "./semaphores.js";
|
|
|
21
21
|
import { FrameScheduler } from "./scheduler.js";
|
|
22
22
|
import { DeterministicIds } from "../../scheduler/ids.js";
|
|
23
23
|
import { Mt19937 } from "./mt19937.js";
|
|
24
|
+
import { FAILURE_TEXT_LIMIT, FAILURE_TRUNCATED_SUFFIX, } from "./failure-limits.generated.js";
|
|
24
25
|
import { ShimFault, CODEC_REGISTRATION, decodeUtf8Checked, resolveExtern, resolveRuntime, resolveSuspension, } from "./shims.js";
|
|
25
26
|
import { AdmittedPackage } from "./validate.js";
|
|
26
27
|
import { HeapExceeded, ManagedBudget, ManagedLayouts, } from "./managed-budget.js";
|
|
@@ -311,6 +312,7 @@ class ExecutorV1 {
|
|
|
311
312
|
if (op.op !== "coverage.hit")
|
|
312
313
|
this.commitStep();
|
|
313
314
|
try {
|
|
315
|
+
act.instruction = op;
|
|
314
316
|
this.execute(act, op);
|
|
315
317
|
}
|
|
316
318
|
catch (error) {
|
|
@@ -325,6 +327,7 @@ class ExecutorV1 {
|
|
|
325
327
|
return;
|
|
326
328
|
this.commitStep();
|
|
327
329
|
try {
|
|
330
|
+
act.instruction = act.block.term;
|
|
328
331
|
this.terminate(act, act.block.term);
|
|
329
332
|
}
|
|
330
333
|
catch (error) {
|
|
@@ -725,8 +728,7 @@ class ExecutorV1 {
|
|
|
725
728
|
previous?.budget?.changed();
|
|
726
729
|
actor.budget?.changed();
|
|
727
730
|
}
|
|
728
|
-
failActorBudget(actor) {
|
|
729
|
-
const fault = { kind: "trap", trap: "heap_exceeded" };
|
|
731
|
+
failActorBudget(actor, fault = { kind: "trap", trap: "heap_exceeded" }) {
|
|
730
732
|
if (!actor.active)
|
|
731
733
|
this.crashActor(actor, fault);
|
|
732
734
|
else {
|
|
@@ -1944,6 +1946,7 @@ class ExecutorV1 {
|
|
|
1944
1946
|
message: renderMessage(message),
|
|
1945
1947
|
...(assertion ? { assertion } : {}),
|
|
1946
1948
|
};
|
|
1949
|
+
this.captureFault(act, act.fault);
|
|
1947
1950
|
if (act.context.actor)
|
|
1948
1951
|
act.context.actor.crashing ??= act.fault;
|
|
1949
1952
|
this.takeEdge(act, term.cleanup);
|
|
@@ -1951,6 +1954,7 @@ class ExecutorV1 {
|
|
|
1951
1954
|
}
|
|
1952
1955
|
case "checked_raise":
|
|
1953
1956
|
act.fault = { kind: "trap", trap: term.trap };
|
|
1957
|
+
this.captureFault(act, act.fault);
|
|
1954
1958
|
if (act.context.actor)
|
|
1955
1959
|
act.context.actor.crashing ??= act.fault;
|
|
1956
1960
|
this.takeEdge(act, term.cleanup);
|
|
@@ -2644,6 +2648,7 @@ class ExecutorV1 {
|
|
|
2644
2648
|
/// Raise a fault at a call site: enter the named unwind edge, or end the
|
|
2645
2649
|
/// program when the call names none.
|
|
2646
2650
|
raiseFault(act, fault, unwind) {
|
|
2651
|
+
this.captureFault(act, fault);
|
|
2647
2652
|
if (!unwind) {
|
|
2648
2653
|
this.running = false;
|
|
2649
2654
|
act.context.failed(fault);
|
|
@@ -2653,6 +2658,8 @@ class ExecutorV1 {
|
|
|
2653
2658
|
this.takeEdge(act, unwind);
|
|
2654
2659
|
}
|
|
2655
2660
|
resumeUnwind(act, handback) {
|
|
2661
|
+
if (act.fault)
|
|
2662
|
+
this.captureFault(act, act.fault);
|
|
2656
2663
|
if (this.finishConsumedCallable(act, null, () => this.resumeUnwind(act, handback)))
|
|
2657
2664
|
return;
|
|
2658
2665
|
if (this.finishConstruction(act, () => this.resumeUnwind(act, handback)))
|
|
@@ -2974,9 +2981,9 @@ class ExecutorV1 {
|
|
|
2974
2981
|
const wake = this.park(act, term);
|
|
2975
2982
|
let target;
|
|
2976
2983
|
let checkWait;
|
|
2977
|
-
const cancelRequest = this.ask(protocol, inputs, (value, error, closeFault) => {
|
|
2984
|
+
const cancelRequest = this.ask(protocol, inputs, (value, error, closeFault, _envelope, actorFault) => {
|
|
2978
2985
|
act.fault ??= closeFault ?? null;
|
|
2979
|
-
return wake(this.completion(term.result_shape, term.error_shape, value, error));
|
|
2986
|
+
return wake(this.completion(term.result_shape, term.error_shape, value, error, actorFault, act.accountActor?.budget));
|
|
2980
2987
|
}, term.request_shapes, (actor) => {
|
|
2981
2988
|
target = actor;
|
|
2982
2989
|
checkWait?.();
|
|
@@ -3056,6 +3063,7 @@ class ExecutorV1 {
|
|
|
3056
3063
|
act.depth > MAX_FRAME_DEPTH &&
|
|
3057
3064
|
act.block.id === act.fn.entry) {
|
|
3058
3065
|
act.fault = { kind: "trap", trap: "stack_overflow" };
|
|
3066
|
+
this.captureFault(act, act.fault);
|
|
3059
3067
|
this.takeEdge(act, term.cancel);
|
|
3060
3068
|
return;
|
|
3061
3069
|
}
|
|
@@ -3882,8 +3890,10 @@ class ExecutorV1 {
|
|
|
3882
3890
|
},
|
|
3883
3891
|
};
|
|
3884
3892
|
const frame = this.activate(this.functionAt(callable), args, null, null, undefined, null, context);
|
|
3885
|
-
if (invoker)
|
|
3893
|
+
if (invoker) {
|
|
3886
3894
|
frame.depth = invoker.depth + 1;
|
|
3895
|
+
frame.traceCaller = invoker;
|
|
3896
|
+
}
|
|
3887
3897
|
retainedBy?.managedChildren.add(frame);
|
|
3888
3898
|
let cancelled = null;
|
|
3889
3899
|
context.cancel = (fault = { kind: "panic", message: "task cancelled", cancelled: true }) => {
|
|
@@ -3919,7 +3929,161 @@ class ExecutorV1 {
|
|
|
3919
3929
|
}
|
|
3920
3930
|
return value;
|
|
3921
3931
|
}
|
|
3922
|
-
|
|
3932
|
+
/// Bytecode has real function identities but no file/line table. Capture
|
|
3933
|
+
/// up to the native snapshot's 48 physical activations at the origin, before
|
|
3934
|
+
/// unwind replaces them. The VM always captures available frames; it has no
|
|
3935
|
+
/// environment-variable trace policy. Repeated frames retain their count.
|
|
3936
|
+
captureFault(act, fault) {
|
|
3937
|
+
const origin = fault.primary ?? fault;
|
|
3938
|
+
if (origin.frames !== undefined)
|
|
3939
|
+
return;
|
|
3940
|
+
origin.frames = [];
|
|
3941
|
+
let previous;
|
|
3942
|
+
let previousInstruction;
|
|
3943
|
+
let previousBlock;
|
|
3944
|
+
let previousIndex;
|
|
3945
|
+
for (let frame = act, count = 0; frame && count < 48; frame = frame.caller ?? frame.traceCaller ?? null, count++) {
|
|
3946
|
+
if (!failureTextPrefix(frame.fn.name).complete) {
|
|
3947
|
+
previous = undefined;
|
|
3948
|
+
previousInstruction = undefined;
|
|
3949
|
+
continue;
|
|
3950
|
+
}
|
|
3951
|
+
if (previous === frame.fn.id &&
|
|
3952
|
+
frame.instruction !== undefined &&
|
|
3953
|
+
previousInstruction === frame.instruction &&
|
|
3954
|
+
previousBlock === frame.block.id &&
|
|
3955
|
+
previousIndex === frame.opIndex)
|
|
3956
|
+
origin.frames.at(-1).repeat++;
|
|
3957
|
+
else
|
|
3958
|
+
origin.frames.push({ function: frame.fn.name, repeat: 1 });
|
|
3959
|
+
previous = frame.fn.id;
|
|
3960
|
+
previousInstruction = frame.instruction;
|
|
3961
|
+
previousBlock = frame.block.id;
|
|
3962
|
+
previousIndex = frame.opIndex;
|
|
3963
|
+
}
|
|
3964
|
+
}
|
|
3965
|
+
trappedError(errorShape, fault, budget) {
|
|
3966
|
+
const row = (this.pkg.managed_types ?? []).find((row) => row.kind === "variant" && row.shape === errorShape);
|
|
3967
|
+
if (!row || row.kind !== "variant")
|
|
3968
|
+
throw new Error("actor error lacks its managed variant descriptor");
|
|
3969
|
+
const tag = runtimeTag(this.pkg.variants[errorShape], "ActorErrorTrapped");
|
|
3970
|
+
const type = row.cases[tag]?.[0];
|
|
3971
|
+
if (type == null)
|
|
3972
|
+
throw new Error("trapped actor completion lacks its crash payload descriptor");
|
|
3973
|
+
return this.variant(errorShape, "ActorErrorTrapped", [
|
|
3974
|
+
this.crashInfo(type, fault, budget),
|
|
3975
|
+
]);
|
|
3976
|
+
}
|
|
3977
|
+
crashInfo(type, fault, budget) {
|
|
3978
|
+
const origin = fault.primary ?? fault;
|
|
3979
|
+
const info = this.managed.row(type);
|
|
3980
|
+
if (info.kind !== "aggregate" || info.fields.length !== 5)
|
|
3981
|
+
throw new Error("crash payload lacks its five-field managed descriptor");
|
|
3982
|
+
const variant = (type) => {
|
|
3983
|
+
const row = this.managed.row(type);
|
|
3984
|
+
if (row.kind !== "variant")
|
|
3985
|
+
throw new Error("crash payload child is not a variant");
|
|
3986
|
+
return row;
|
|
3987
|
+
};
|
|
3988
|
+
const option = (type, value) => this.variant(variant(type).shape, value === undefined ? "OptionNone" : "OptionSome", value === undefined ? [] : [value]);
|
|
3989
|
+
const someType = (type) => {
|
|
3990
|
+
const row = variant(type);
|
|
3991
|
+
const child = row.cases[runtimeTag(this.pkg.variants[row.shape], "OptionSome")]?.[0];
|
|
3992
|
+
if (child == null)
|
|
3993
|
+
throw new Error("crash payload option lacks its child descriptor");
|
|
3994
|
+
return child;
|
|
3995
|
+
};
|
|
3996
|
+
const aggregateShape = (type) => {
|
|
3997
|
+
const row = this.managed.row(type);
|
|
3998
|
+
if (row.kind !== "aggregate")
|
|
3999
|
+
throw new Error("crash payload record lacks its descriptor");
|
|
4000
|
+
const shape = row.shape;
|
|
4001
|
+
if (shape == null)
|
|
4002
|
+
throw new Error("crash payload record lacks its aggregate identity");
|
|
4003
|
+
return shape;
|
|
4004
|
+
};
|
|
4005
|
+
const record = (type, fields) => {
|
|
4006
|
+
const shape = aggregateShape(type);
|
|
4007
|
+
return {
|
|
4008
|
+
kind: "record",
|
|
4009
|
+
typeId: this.aggregateName(shape),
|
|
4010
|
+
shape,
|
|
4011
|
+
fields,
|
|
4012
|
+
};
|
|
4013
|
+
};
|
|
4014
|
+
const [kindType, trapType, , siteType, traceType] = info.fields;
|
|
4015
|
+
const [code, reason] = faultInfo(origin);
|
|
4016
|
+
const kind = this.variant(variant(kindType).shape, code === 200
|
|
4017
|
+
? "CrashKindHeapExceeded"
|
|
4018
|
+
: code === 214
|
|
4019
|
+
? "CrashKindTimerOverdue"
|
|
4020
|
+
: code === 213
|
|
4021
|
+
? "CrashKindHandlerFailed"
|
|
4022
|
+
: code === 215
|
|
4023
|
+
? "CrashKindStackOverflow"
|
|
4024
|
+
: "CrashKindFault");
|
|
4025
|
+
const trap = variant(someType(trapType));
|
|
4026
|
+
const trapTag = this.pkg.variants[trap.shape].cases.findIndex((item) => item.name === reason);
|
|
4027
|
+
const trapValue = trapTag < 0
|
|
4028
|
+
? undefined
|
|
4029
|
+
: {
|
|
4030
|
+
kind: "enum",
|
|
4031
|
+
typeId: this.variantName(trap.shape),
|
|
4032
|
+
shape: trap.shape,
|
|
4033
|
+
tag: trapTag,
|
|
4034
|
+
payload: [],
|
|
4035
|
+
};
|
|
4036
|
+
const vector = this.managed.row(someType(traceType));
|
|
4037
|
+
if (vector.kind !== "vector")
|
|
4038
|
+
throw new Error("crash trace lacks its vector descriptor");
|
|
4039
|
+
const frame = this.managed.row(vector.element);
|
|
4040
|
+
if (frame.kind !== "aggregate" || frame.fields.length !== 3)
|
|
4041
|
+
throw new Error("crash frame lacks its managed descriptor");
|
|
4042
|
+
const frames = {
|
|
4043
|
+
kind: "vector",
|
|
4044
|
+
elementType: this.aggregateName(aggregateShape(vector.element)),
|
|
4045
|
+
items: (origin.frames ?? []).map((item) => record(vector.element, [
|
|
4046
|
+
stringValue(item.function),
|
|
4047
|
+
option(frame.fields[1]),
|
|
4048
|
+
{ kind: "i64", value: BigInt(item.repeat) },
|
|
4049
|
+
])),
|
|
4050
|
+
};
|
|
4051
|
+
const value = record(type, [
|
|
4052
|
+
kind,
|
|
4053
|
+
option(trapType, trapValue),
|
|
4054
|
+
stringValue(boundedFailureMessage(faultApplicationMessage(origin) ?? reason)),
|
|
4055
|
+
option(siteType),
|
|
4056
|
+
option(traceType, origin.frames === undefined ? undefined : frames),
|
|
4057
|
+
]);
|
|
4058
|
+
if (value.kind !== "record")
|
|
4059
|
+
throw new Error("crash payload is not a record");
|
|
4060
|
+
// Diagnostics must not replace the original failure with an allocation
|
|
4061
|
+
// failure. Drop trace storage first, then the message (site is unavailable).
|
|
4062
|
+
for (const omit of [null, 4, 2]) {
|
|
4063
|
+
if (omit === 4)
|
|
4064
|
+
value.fields[4] = option(traceType);
|
|
4065
|
+
if (omit === 2)
|
|
4066
|
+
value.fields[2] = stringValue("");
|
|
4067
|
+
try {
|
|
4068
|
+
this.managed.stampGraph({ value, type });
|
|
4069
|
+
budget?.check([{ value, type }]);
|
|
4070
|
+
return value;
|
|
4071
|
+
}
|
|
4072
|
+
catch (error) {
|
|
4073
|
+
if (!(error instanceof HeapExceeded))
|
|
4074
|
+
throw error;
|
|
4075
|
+
}
|
|
4076
|
+
}
|
|
4077
|
+
return value;
|
|
4078
|
+
}
|
|
4079
|
+
completion(result, errorShape, value, error, fault, budget) {
|
|
4080
|
+
if (error === "Trapped") {
|
|
4081
|
+
if (!fault)
|
|
4082
|
+
throw new Error("trapped actor completion lost its originating fault");
|
|
4083
|
+
return this.variant(result, "ResultErr", [
|
|
4084
|
+
this.trappedError(errorShape, fault, budget),
|
|
4085
|
+
]);
|
|
4086
|
+
}
|
|
3923
4087
|
return error
|
|
3924
4088
|
? this.variant(result, "ResultErr", [
|
|
3925
4089
|
this.variant(errorShape, actorErrorRole(error), (error === "Failed" || error === "Rejected") && value
|
|
@@ -4261,7 +4425,8 @@ class ExecutorV1 {
|
|
|
4261
4425
|
const task = this.newTask(act.context.actor?.budget, term.result && term.result !== "never"
|
|
4262
4426
|
? (term.result.managed_type ?? undefined)
|
|
4263
4427
|
: undefined);
|
|
4264
|
-
const cancelRequest = this.ask(operation.protocol, args, (value, error, closeFault, envelope) => {
|
|
4428
|
+
const cancelRequest = this.ask(operation.protocol, args, (value, error, closeFault, envelope, actorFault) => {
|
|
4429
|
+
task.actorFault = actorFault;
|
|
4265
4430
|
const reply = this.managedCompletion(task, value, error, envelope);
|
|
4266
4431
|
if (reply && !closeFault && act.context.actor?.budget) {
|
|
4267
4432
|
try {
|
|
@@ -4270,12 +4435,12 @@ class ExecutorV1 {
|
|
|
4270
4435
|
catch (failure) {
|
|
4271
4436
|
if (!(failure instanceof HeapExceeded))
|
|
4272
4437
|
throw failure;
|
|
4438
|
+
const fault = { kind: "trap", trap: "heap_exceeded" };
|
|
4439
|
+
this.captureFault(act, fault);
|
|
4273
4440
|
task.actorError = "Trapped";
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
});
|
|
4278
|
-
this.failActorBudget(act.context.actor);
|
|
4441
|
+
task.actorFault = fault;
|
|
4442
|
+
this.settleTask(task, UNIT, fault);
|
|
4443
|
+
this.failActorBudget(act.context.actor, fault);
|
|
4279
4444
|
return false;
|
|
4280
4445
|
}
|
|
4281
4446
|
}
|
|
@@ -4308,7 +4473,7 @@ class ExecutorV1 {
|
|
|
4308
4473
|
task.value = UNIT;
|
|
4309
4474
|
task.taken = true;
|
|
4310
4475
|
const result = task.managedResult ??
|
|
4311
|
-
this.completion(term.result_shape, term.error_shape, value, task.actorError);
|
|
4476
|
+
this.completion(term.result_shape, term.error_shape, value, task.actorError, task.actorFault, act.accountActor?.budget);
|
|
4312
4477
|
task.managedResult = undefined;
|
|
4313
4478
|
task.managedReply = undefined;
|
|
4314
4479
|
this.completeShim(act, term, result);
|
|
@@ -4372,7 +4537,7 @@ class ExecutorV1 {
|
|
|
4372
4537
|
this.closeValueAsync(payload, "actor heap limit exceeded", (fault) => {
|
|
4373
4538
|
act.fault ??= fault;
|
|
4374
4539
|
finish(this.variant(term.result_shape, "ResultErr", [
|
|
4375
|
-
this.
|
|
4540
|
+
this.trappedError(term.error_shape, actor.crashing ?? { kind: "trap", trap: "heap_exceeded" }, act.accountActor?.budget),
|
|
4376
4541
|
]));
|
|
4377
4542
|
}, act.context.actor, act.rcOwner);
|
|
4378
4543
|
return;
|
|
@@ -4692,12 +4857,12 @@ class ExecutorV1 {
|
|
|
4692
4857
|
? { kind: "Actor", identity: owner.identity }
|
|
4693
4858
|
: { kind: "Task" };
|
|
4694
4859
|
let receiver = null;
|
|
4695
|
-
const finish = (value, error, drained, envelope) => {
|
|
4860
|
+
const finish = (value, error, drained, envelope, actorFault) => {
|
|
4696
4861
|
const alreadySettled = settled;
|
|
4697
4862
|
settled = true;
|
|
4698
4863
|
cancelTimer?.();
|
|
4699
4864
|
if (alreadySettled ||
|
|
4700
|
-
complete(value, error, undefined, envelope) === false) {
|
|
4865
|
+
complete(value, error, undefined, envelope, actorFault) === false) {
|
|
4701
4866
|
// The finishing receiver still owns a reply that its caller no
|
|
4702
4867
|
// longer accepts. Its next turn must wait for this release.
|
|
4703
4868
|
this.closeValueAsync(value ?? UNIT, null, (fault) => {
|
|
@@ -4733,7 +4898,7 @@ class ExecutorV1 {
|
|
|
4733
4898
|
waiter(fault);
|
|
4734
4899
|
}, owner, rcOwner);
|
|
4735
4900
|
};
|
|
4736
|
-
const reject = (reason) => {
|
|
4901
|
+
const reject = (reason, actorFault) => {
|
|
4737
4902
|
if (settled || closing)
|
|
4738
4903
|
return;
|
|
4739
4904
|
closeRequest((fault) => {
|
|
@@ -4741,7 +4906,7 @@ class ExecutorV1 {
|
|
|
4741
4906
|
return;
|
|
4742
4907
|
settled = true;
|
|
4743
4908
|
cancelTimer?.();
|
|
4744
|
-
complete(null, reason, fault);
|
|
4909
|
+
complete(null, reason, fault, undefined, actorFault);
|
|
4745
4910
|
});
|
|
4746
4911
|
};
|
|
4747
4912
|
const attempt = () => {
|
|
@@ -4812,7 +4977,7 @@ class ExecutorV1 {
|
|
|
4812
4977
|
if (!(error instanceof HeapExceeded))
|
|
4813
4978
|
throw error;
|
|
4814
4979
|
this.failActorBudget(actor);
|
|
4815
|
-
reject("Trapped");
|
|
4980
|
+
reject("Trapped", actor.crashing ?? { kind: "trap", trap: "heap_exceeded" });
|
|
4816
4981
|
return;
|
|
4817
4982
|
}
|
|
4818
4983
|
admitted = true;
|
|
@@ -5141,7 +5306,7 @@ class ExecutorV1 {
|
|
|
5141
5306
|
this.closeValueAsync(value, this.faultText(fault), (closeFault) => {
|
|
5142
5307
|
released();
|
|
5143
5308
|
this.crashActor(actor, combineFaults(fault, closeFault), message.handler);
|
|
5144
|
-
message.complete(null, "Trapped");
|
|
5309
|
+
message.complete(null, "Trapped", undefined, undefined, fault);
|
|
5145
5310
|
}, actor, actor.rcOwner);
|
|
5146
5311
|
return;
|
|
5147
5312
|
}
|
|
@@ -5163,11 +5328,13 @@ class ExecutorV1 {
|
|
|
5163
5328
|
if (message.reply)
|
|
5164
5329
|
message.complete(value.payload[0] ?? UNIT, "Failed", drained, value);
|
|
5165
5330
|
else {
|
|
5166
|
-
|
|
5331
|
+
const fault = {
|
|
5167
5332
|
kind: "panic",
|
|
5168
5333
|
handlerFailed: true,
|
|
5169
5334
|
message: "unhandled declared handler failure",
|
|
5170
|
-
}
|
|
5335
|
+
};
|
|
5336
|
+
this.captureFault(frame, fault);
|
|
5337
|
+
this.crashActor(actor, fault, message.handler);
|
|
5171
5338
|
return;
|
|
5172
5339
|
}
|
|
5173
5340
|
}
|
|
@@ -5196,7 +5363,7 @@ class ExecutorV1 {
|
|
|
5196
5363
|
}
|
|
5197
5364
|
else {
|
|
5198
5365
|
this.crashActor(actor, fault, message.handler);
|
|
5199
|
-
message.complete(null, "Trapped");
|
|
5366
|
+
message.complete(null, "Trapped", undefined, undefined, fault);
|
|
5200
5367
|
}
|
|
5201
5368
|
},
|
|
5202
5369
|
});
|
|
@@ -5276,31 +5443,11 @@ class ExecutorV1 {
|
|
|
5276
5443
|
};
|
|
5277
5444
|
const runHook = () => {
|
|
5278
5445
|
if (actor.layout.crash != null) {
|
|
5279
|
-
const
|
|
5280
|
-
|
|
5281
|
-
|
|
5282
|
-
|
|
5283
|
-
|
|
5284
|
-
shape: actor.layout.crash_info,
|
|
5285
|
-
fields: [
|
|
5286
|
-
{
|
|
5287
|
-
kind: "enum",
|
|
5288
|
-
typeId: this.variantName(actor.layout.crash_kind),
|
|
5289
|
-
shape: actor.layout.crash_kind,
|
|
5290
|
-
tag: runtimeTag(this.pkg.variants[actor.layout.crash_kind], code === 200
|
|
5291
|
-
? "CrashKindHeapExceeded"
|
|
5292
|
-
: code === 214
|
|
5293
|
-
? "CrashKindTimerOverdue"
|
|
5294
|
-
: code === 213
|
|
5295
|
-
? "CrashKindHandlerFailed"
|
|
5296
|
-
: code === 215
|
|
5297
|
-
? "CrashKindStackOverflow"
|
|
5298
|
-
: "CrashKindFault"),
|
|
5299
|
-
payload: [],
|
|
5300
|
-
},
|
|
5301
|
-
stringValue(String(message)),
|
|
5302
|
-
],
|
|
5303
|
-
};
|
|
5446
|
+
const type = this.functionAt(actor.layout.crash).params[1]
|
|
5447
|
+
?.managed_type;
|
|
5448
|
+
if (type == null)
|
|
5449
|
+
throw new Error("crash hook lacks its crash payload descriptor");
|
|
5450
|
+
const info = this.crashInfo(type, fault, actor.budget);
|
|
5304
5451
|
let cancelTimer;
|
|
5305
5452
|
const complete = (action) => {
|
|
5306
5453
|
cancelTimer?.();
|
|
@@ -5850,6 +5997,7 @@ class ExecutorV1 {
|
|
|
5850
5997
|
});
|
|
5851
5998
|
}
|
|
5852
5999
|
haltWithFault(fault) {
|
|
6000
|
+
this.captureFault(this.current, fault);
|
|
5853
6001
|
const isPanic = fault.kind === "panic";
|
|
5854
6002
|
const status = isPanic ? "panic" : "trap";
|
|
5855
6003
|
const trapKind = isPanic ? "panic" : fault.trap;
|
|
@@ -5866,6 +6014,25 @@ class ExecutorV1 {
|
|
|
5866
6014
|
throw new Halt(status);
|
|
5867
6015
|
}
|
|
5868
6016
|
}
|
|
6017
|
+
/// Retain only bounded scratch space even when raw fault text or a compiler
|
|
6018
|
+
/// function name is much larger than the public diagnostic limit.
|
|
6019
|
+
function failureTextPrefix(text) {
|
|
6020
|
+
const bytes = new Uint8Array(FAILURE_TEXT_LIMIT);
|
|
6021
|
+
const { read, written } = new TextEncoder().encodeInto(text, bytes);
|
|
6022
|
+
return { bytes: bytes.subarray(0, written), complete: read === text.length };
|
|
6023
|
+
}
|
|
6024
|
+
/// Match the owned native snapshot without changing raw fault reporting.
|
|
6025
|
+
/// The suffix counts inside the UTF-8 limit and never splits a character.
|
|
6026
|
+
function boundedFailureMessage(message) {
|
|
6027
|
+
const { bytes, complete } = failureTextPrefix(message);
|
|
6028
|
+
if (complete)
|
|
6029
|
+
return message;
|
|
6030
|
+
let end = Math.min(bytes.length, FAILURE_TEXT_LIMIT -
|
|
6031
|
+
new TextEncoder().encode(FAILURE_TRUNCATED_SUFFIX).length);
|
|
6032
|
+
while ((bytes[end] & 0xc0) === 0x80)
|
|
6033
|
+
end--;
|
|
6034
|
+
return (new TextDecoder().decode(bytes.subarray(0, end)) + FAILURE_TRUNCATED_SUFFIX);
|
|
6035
|
+
}
|
|
5869
6036
|
function combineFaults(primary, secondary) {
|
|
5870
6037
|
if (!primary)
|
|
5871
6038
|
return secondary;
|
|
@@ -308,6 +308,10 @@ export type ManagedType = {
|
|
|
308
308
|
element: number;
|
|
309
309
|
} | {
|
|
310
310
|
kind: "aggregate";
|
|
311
|
+
/**
|
|
312
|
+
* The canonical record shape when this layout represents a record.
|
|
313
|
+
*/
|
|
314
|
+
shape?: number | null;
|
|
311
315
|
fields: Array<number>;
|
|
312
316
|
} | {
|
|
313
317
|
kind: "variant";
|
|
@@ -749,7 +753,7 @@ export type RuntimeCallFamily = {
|
|
|
749
753
|
IntArith: [IntArithKind, IntMethodWidth];
|
|
750
754
|
} | {
|
|
751
755
|
FloatMethod: FloatMethodOp;
|
|
752
|
-
} | "NodeConnect" | "NodeId" | "NodeIdDisplay" | "LocationNodeId" | "LocationIncarnation" | "LocationDisplay" | "NodeIdentityKey" | "NodeLoadKeys" | "NodeLookup" | "NodeRegister" | "NodeSetTransport" | "NodeShutdown" | "NodeStart" | "MetricCounterRegister" | "MetricCounterInc" | "MetricCounterAdd" | "MetricGaugeRegister" | "MetricGaugeSet" | "MetricGaugeInc" | "MetricGaugeDec" | "MetricGaugeAdd" | "MetricHistogramRegister" | "MetricHistogramRegisterSimple" | "MetricHistogramRecord" | "MetricVecRegister" | "MetricVecWith" | "ObserveReadU64" | "ObserveScrape" | "ObserveSeries" | "ObserveBarrier" | "RcClone" | "RcDowngrade" | "RcDrop" | "RcGet" | "RcIsUnique" | "RcNew" | "RcSet" | "RcStrongCount" | "RcWeakCount" | "WeakCloneRc" | "WeakDropRc" | "WeakUpgradeRc" | "RegexCapture" | "RegexCompile" | "RegexFreeCapture" | "RegexHandle" | "RegexMatch" | "ReplyChannelCancel" | "ReplyChannelFree" | "ReplyChannelNew" | "ReplyPayloadFree" | "ReplyWait" | "SelectFirst" | "SinkClone" | "SinkClose" | "StreamClose" | "StreamForward" | "StreamPipe" | "StreamFromGenerator" | "SinkFromEncoder" | "StreamNextLayout" | "StreamPairSink" | "StreamPairStream" | "StreamSendLayout" | "StreamTake" | "StreamTryNextLayout" | "StreamTrySendLayout" | "CharIsDigit" | "CharIsLetter" | "CharIsAlphanumeric" | "CharIsWhitespace" | "CharIsUpper" | "CharIsLower" | "CharIsPunctuation" | "CharIsControl" | "CharIsAscii" | "CharToUpper" | "CharToLower" | "CharToTitle" | "CharLenUtf8" | "CharToDigit" | "CharFromI64" | "CharFromU64" | "StringGet" | "StringGetRange" | "StringSliceRange" | "StringAppend" | "StringPush" | "StringClear" | "StringFindFrom" | "StringByteLen" | "StringConcat" | "StringEquals" | "StringCompare" | "StringStartsWith" | "StringEndsWith" | "StringContains" | "StringIsEmpty" | "StructuralFormat" | "StringFind" | "StringIndex" | "StringLen" | "StringRepeat" | "StringReplace" | "StringClone" | "StringSplit" | "StringLines" | "StringSliceCodepoints" | "StringSliceCodepointsFrom" | "StringToLowercase" | "StringToBytes" | "StringToUppercase" | "StringTrim" | "U8ToString" | "I32ToString" | "I64ToString" | "U32ToString" | "U64ToString" | "F64ToString" | "CharToString" | {
|
|
756
|
+
} | "NodeConnect" | "NodeId" | "NodeIdDisplay" | "LocationNodeId" | "LocationIncarnation" | "LocationDisplay" | "NodeIdentityKey" | "NodeLoadKeys" | "NodeLookup" | "NodeRegister" | "NodeSetTransport" | "NodeShutdown" | "NodeStart" | "MetricCounterRegister" | "MetricCounterInc" | "MetricCounterAdd" | "MetricGaugeRegister" | "MetricGaugeSet" | "MetricGaugeInc" | "MetricGaugeDec" | "MetricGaugeAdd" | "MetricHistogramRegister" | "MetricHistogramRegisterSimple" | "MetricHistogramRecord" | "MetricVecRegister" | "MetricVecWith" | "ObserveReadU64" | "ObserveScrape" | "ObserveSeries" | "ObserveBarrier" | "RcClone" | "RcDowngrade" | "RcDrop" | "RcGet" | "RcIsUnique" | "RcNew" | "RcSet" | "RcStrongCount" | "RcWeakCount" | "WeakCloneRc" | "WeakDropRc" | "WeakUpgradeRc" | "RegexCapture" | "RegexCompile" | "RegexFreeCapture" | "RegexHandle" | "RegexMatch" | "ReplyChannelCancel" | "ReplyChannelFree" | "ReplyChannelNew" | "ReplyPayloadFree" | "ReplyWait" | "SelectFirst" | "SinkClone" | "SinkClose" | "StreamClose" | "StreamForward" | "StreamPipe" | "StreamFromGenerator" | "SinkFromEncoder" | "StreamNextLayout" | "StreamPairSink" | "StreamPairStream" | "StreamSendLayout" | "StreamTake" | "StreamTryNextLayout" | "StreamTrySendLayout" | "CharIsDigit" | "CharIsLetter" | "CharIsAlphanumeric" | "CharIsWhitespace" | "CharIsUpper" | "CharIsLower" | "CharIsPunctuation" | "CharIsControl" | "CharIsAscii" | "CharToUpper" | "CharToLower" | "CharToTitle" | "CharLenUtf8" | "CharToDigit" | "CharFromI64" | "CharFromU64" | "StringGet" | "StringGetRange" | "StringSliceRange" | "StringAppend" | "StringPush" | "StringClear" | "StringAppendI64" | "StringAppendU64" | "StringAppendF64" | "StringAppendBool" | "StringAppendLiteral" | "StringFindFrom" | "StringByteLen" | "StringConcat" | "StringEquals" | "StringCompare" | "StringStartsWith" | "StringEndsWith" | "StringContains" | "StringIsEmpty" | "StructuralFormat" | "StringFind" | "StringIndex" | "StringLen" | "StringRepeat" | "StringReplace" | "StringClone" | "StringSplit" | "StringLines" | "StringSliceCodepoints" | "StringSliceCodepointsFrom" | "StringToLowercase" | "StringToBytes" | "StringToUppercase" | "StringTrim" | "U8ToString" | "I32ToString" | "I64ToString" | "U32ToString" | "U64ToString" | "F64ToString" | "CharToString" | {
|
|
753
757
|
Print: {
|
|
754
758
|
kind: PrintKind;
|
|
755
759
|
newline: boolean;
|
|
@@ -54,6 +54,12 @@ export function validatePackageGraph(pkg) {
|
|
|
54
54
|
managedType(row.value);
|
|
55
55
|
break;
|
|
56
56
|
case "aggregate":
|
|
57
|
+
if (row.shape != null) {
|
|
58
|
+
const shape = at(pkg.aggregates, row.shape, "managed aggregate shape");
|
|
59
|
+
require(shape.fields.length ===
|
|
60
|
+
row.fields
|
|
61
|
+
.length, "managed aggregate fields differ from its shape");
|
|
62
|
+
}
|
|
57
63
|
row.fields.forEach(managedType);
|
|
58
64
|
break;
|
|
59
65
|
case "variant":
|