Skip to content
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

Implement runtime limits for loops #2857

Merged
merged 1 commit into from
May 5, 2023
Merged

Implement runtime limits for loops #2857

merged 1 commit into from
May 5, 2023

Conversation

HalidOdat
Copy link
Member

@HalidOdat HalidOdat commented Apr 21, 2023

Depends on #2866

Related to #2350

This PR adds runtime limits on all loops (while, do..while, for, for-in, and for-of) that can be controlled at runtime, this is feature gated under the "runtime-limits" feature flag because introduces some minor overhead due to needed iteration tracking.

By default when the feature is enabled it is set to u64::MAX, which means no runtime limit

It changes the following:

  • Add RuntimeLimits that contains the runtime limits.
  • Add $boa.limits.loop getter and setter for easier testing.
  • Make the error non-catchable.

Setting the limit from rust:

context.runtime_limits_mut().set_loop_iteration_limit(1024); // Set to an arbitrary number

Or using the $boa object for debugging:

$boa.limits.loop = 4 // Set max loop iteration through the debug object $boa

// All the following statements throw a range error with the message:
//
// RangeError: max loop iteration limit 4 exceeded

while (1) {}
do { } while(1)
for (let i = 0; 1; ++i) {}
for (let e of [1, 2, 3, 4, 5]) {} // One above max
for (let e in [1, 2, 3, 4, 5]) {} // One above max

@HalidOdat HalidOdat added enhancement New feature or request execution Issues or PRs related to code execution labels Apr 21, 2023
@github-actions
Copy link

github-actions bot commented Apr 21, 2023

Test262 conformance changes

Test result main count PR count difference
Total 94,601 94,601 0
Passed 73,293 73,293 0
Ignored 17,540 17,540 0
Failed 3,768 3,768 0
Panics 0 0 0
Conformance 77.48% 77.48% 0.00%

@codecov
Copy link

codecov bot commented Apr 21, 2023

Codecov Report

Merging #2857 (4ccc5e6) into main (f4ebb2b) will decrease coverage by 0.53%.
The diff coverage is 39.86%.

❗ Current head 4ccc5e6 differs from pull request most recent head 4f15682. Consider uploading reports for the commit 4f15682 to get more accurate results

@@            Coverage Diff             @@
##             main    #2857      +/-   ##
==========================================
- Coverage   51.77%   51.24%   -0.53%     
==========================================
  Files         427      430       +3     
  Lines       42610    42714     +104     
==========================================
- Hits        22061    21890     -171     
- Misses      20549    20824     +275     
Impacted Files Coverage Δ
boa_cli/src/debug/limits.rs 0.00% <0.00%> (ø)
boa_cli/src/debug/mod.rs 0.00% <0.00%> (ø)
boa_engine/src/bytecompiler/statement/loop.rs 81.32% <ø> (ø)
boa_engine/src/lib.rs 78.20% <ø> (ø)
boa_engine/src/vm/flowgraph/mod.rs 0.00% <ø> (ø)
boa_examples/src/bin/runtime_limits.rs 0.00% <0.00%> (ø)
boa_engine/src/error.rs 33.48% <16.00%> (-1.44%) ⬇️
boa_engine/src/context/mod.rs 47.48% <33.33%> (-0.40%) ⬇️
boa_engine/src/vm/opcode/call/mod.rs 52.12% <37.50%> (ø)
boa_engine/src/vm/opcode/new/mod.rs 75.67% <50.00%> (ø)
... and 5 more

... and 15 files with indirect coverage changes

@raskad
Copy link
Member

raskad commented Apr 22, 2023

I have not done a complete review, but I saw the new opcodes and though; can we not keep the limit counts in the EnvStackEntrys and manage them in the LoopStart, LoopContinue and LoopEnd opcodes?

@HalidOdat
Copy link
Member Author

I have not done a complete review, but I saw the new opcodes and though; can we not keep the limit counts in the EnvStackEntrys and manage them in the LoopStart, LoopContinue and LoopEnd opcodes?

Thanks for the suggestion! It's probably the best place to put it! I realized this when I was half way through implementing the initial POC 😆

@HalidOdat HalidOdat force-pushed the feature/runtime-limits branch from b43fc62 to b13e9a0 Compare April 24, 2023 05:50
bors bot pushed a commit that referenced this pull request Apr 24, 2023
While working on #2857 I discovered that while generating the bytecode for `do-while` loops we were emitting an orphan `LoopContinue` that is never executed, which was preventing the error to be thrown when max loop iteration is reached.

This can more easily be identified with it's flowgraph : (`do { 1; } while(0) `)

Main:

<details>
<img src="https://user-images.githubusercontent.com/8566042/233908011-247313bc-6435-4622-8ecb-f469d9eaf850.png">
</details>

With this PR:

<details>
<img src="https://user-images.githubusercontent.com/8566042/233908030-3552636e-f09c-4c5e-8c7c-1ecfa0024dfe.png">
</details>
@HalidOdat HalidOdat force-pushed the feature/runtime-limits branch 3 times, most recently from 983965b to b108f29 Compare April 26, 2023 06:15
@HalidOdat HalidOdat marked this pull request as ready for review April 26, 2023 06:16
@HalidOdat HalidOdat force-pushed the feature/runtime-limits branch 2 times, most recently from ca6cc9b to 8caeba5 Compare April 26, 2023 06:30
Copy link
Member

@raskad raskad left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks really good.

Can we set the stack_size_limit to 1024 (or another value that currently works) in our tests and the boa_tester? That way we use the feature ourselves and we may also catch bugs that spill the stack.

@HalidOdat
Copy link
Member Author

HalidOdat commented Apr 27, 2023

Can we set the stack_size_limit to 1024 (or another value that currently works) in our tests and the boa_tester? That way we use the feature ourselves and we may also catch bugs that spill the stack.

It set to 1024 but It's hidden under the feature flag, Maybe this should be a opt-out feature? So we also prevent infinite loops too (especially in the CI and boa_tester)

@Razican
Copy link
Member

Razican commented Apr 27, 2023

Can we set the stack_size_limit to 1024 (or another value that currently works) in our tests and the boa_tester? That way we use the feature ourselves and we may also catch bugs that spill the stack.

It set to 1024 but It's hidden under the feature flag, Maybe this should be a opt-out feature? So we also prevent infinite loops too (especially in the CI and boa_tester)

Does it affect performance? If it's negligible, I would say we should use an Option<NonZeroU64>> to handle it, and make it None by default (or even Some(NonZeroU64::MAX)). Then, we could have an unset() function, to disable runtime limits. I think it makes sense to limit it by default, and not have a feature on it.

Copy link
Member

@Razican Razican left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm missing a test, where we run a loop without limits for 10 iterations (as an example) and it works, then we set the limit to 5 iterations, try to run it again, and check that we get the error.

boa_engine/src/error.rs Outdated Show resolved Hide resolved
Loop {
/// This is used to keep track of how many iterations a loop has done.
#[cfg(feature = "runtime-limits")]
iteration_count: u64,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious on what your thoughts might be about setting this as a u16 vs. u64.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I set it because I felt like that was too restrictive [0, 65535]for u16, u32 with [0, 4294967295] is better but it is possible to iterate over it (takes like a second on node with jitting), but u64 should be more than enough for everyone [0, 18446744073709551615] :D

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😆 I'd agree that u64 is more than enough.

I mostly brought it up because of how large u64 is. It may be worth keeping track or maybe getting a poll/input to see if people are setting runtime limits past a certain point and see if we should lower to a u32 or something. But that could just be me being overly concerned and thinking about it completely wrong.

@HalidOdat
Copy link
Member Author

HalidOdat commented Apr 28, 2023

Does it affect performance? If it's negligible, I would say we should use an Option> to handle it, and make it None by default (or even Some(NonZeroU64::MAX)). Then, we could have an unset() function, to disable runtime limits. I think it makes sense to limit it by default, and not have a feature on it.

Yes, ran the benchmarks locally and it wasn't showing that it had any effect on performance, and it makes sense to allow limiting by default.

@HalidOdat HalidOdat force-pushed the feature/runtime-limits branch from 8caeba5 to 8e8a83d Compare April 28, 2023 21:18
@HalidOdat HalidOdat requested review from Razican and nekevss April 28, 2023 21:20
@HalidOdat
Copy link
Member Author

HalidOdat commented Apr 28, 2023

Added an example in boa_examples for loop limits also removed the feature flag made it default,

I stuck with using u64 instead of Option<NonZeroU64> for loop iteration count because it would introduce conditional jumps. The way we know if there is a limit set, is by checking if it's set to u64::MAX. Changed the condition to check for > instead of >=, this allows us to exploit a property of comparisons and unsigned integers that u64::MAX is not greater than u64::MAX , adding 1 to u64::MAX overflows to 0. Now when we increment the counter we do a wrapping_add.

With these changes the limit u64::MAX will never satisfy the condition, as if it was not set.

Copy link
Member

@Razican Razican left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me :) check my suggestions and see if they make sense

boa_engine/src/context/mod.rs Outdated Show resolved Hide resolved
boa_engine/src/context/mod.rs Show resolved Hide resolved
boa_engine/src/vm/runtime_limits.rs Show resolved Hide resolved
boa_examples/src/bin/runtime_limits.rs Outdated Show resolved Hide resolved
@HalidOdat HalidOdat force-pushed the feature/runtime-limits branch from 8e8a83d to 4ccc5e6 Compare May 3, 2023 21:40
Copy link
Member

@nekevss nekevss left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everything looks just about good to me 😄

boa_cli/src/debug/limits.rs Outdated Show resolved Hide resolved
@HalidOdat HalidOdat requested a review from nekevss May 4, 2023 10:11
@HalidOdat HalidOdat force-pushed the feature/runtime-limits branch from 55f9a66 to 4f15682 Compare May 5, 2023 14:17
@jedel1043 jedel1043 requested a review from raskad May 5, 2023 14:58
Copy link
Member

@raskad raskad left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice addition!

@raskad raskad added this pull request to the merge queue May 5, 2023
Merged via the queue into main with commit 802d796 May 5, 2023
@HalidOdat HalidOdat deleted the feature/runtime-limits branch May 5, 2023 16:38
@raskad raskad added this to the v0.17.0 milestone May 5, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request execution Issues or PRs related to code execution
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants