diff --git a/Jint/Engine.Constraints.cs b/Jint/Engine.Constraints.cs index fd6143f9f..5fec993fd 100644 --- a/Jint/Engine.Constraints.cs +++ b/Jint/Engine.Constraints.cs @@ -10,14 +10,24 @@ public partial class Engine private readonly record struct ConstraintPartition(Constraint[] Exact, Constraint[] Amortized); /// - /// Splits the registered constraints by required check frequency. The built-in time, - /// cancellation and memory-limit constraints only observe external state (a timer, a token, - /// an allocation counter), so checking them every N statements is semantically equivalent to + /// Splits the registered constraints by required check frequency. The built-in time and + /// cancellation constraints only observe external state that a check reads without consuming + /// (a timer, a token), so checking them every N statements is semantically equivalent to /// checking per statement — only the detection latency is bounded instead of immediate (the /// same reasoning bulk built-ins apply via ). Everything - /// else stays exact: counts statements, so its call - /// frequency IS its semantics, and user-derived constraints may depend on being called once - /// per statement — silently amortizing them would be a breaking behavior change. + /// else stays exact: + /// + /// counts statements, so its call frequency IS its + /// semantics. + /// reads an allocation counter, but unlike a clock + /// that only advances, allocation between checks is irreversible and unbounded per statement + /// (a single iteration can allocate arbitrarily much, e.g. exponential string growth), so + /// amortizing it would let the process overshoot the configured cap — potentially to a real + /// OutOfMemoryException — before the next check. Keeping it exact preserves the memory bound + /// as a hard-ish guarantee for sandboxing untrusted code (matching pre-tight-lane behavior). + /// User-derived constraints may depend on being called once per statement — silently + /// amortizing them would be a breaking behavior change. + /// /// private static ConstraintPartition PartitionConstraints(Constraint[] constraints) { @@ -30,8 +40,8 @@ private static ConstraintPartition PartitionConstraints(Constraint[] constraints var amortized = new List(constraints.Length); foreach (var constraint in constraints) { - // All three types are sealed, so the checks cannot match a user-derived subclass. - if (constraint is TimeConstraint or CancellationConstraint or MemoryLimitConstraint) + // Both types are sealed, so the check cannot match a user-derived subclass. + if (constraint is TimeConstraint or CancellationConstraint) { amortized.Add(constraint); }