Repository navigation
Conversation
A handler can return Err(SuspensionRequest) to park the current rewritable unit on the heap and exit write()/end() with the non-poisoning RewritingError::Suspended; HtmlRewriter::resume() continues from a StateMachineBookmark. - take_owned() on each rewritable unit deep-copies borrowed byte slices so the unit outlives the write() stack frame - Arena::shift advances a start offset instead of memmoving the tail (a suspended rewrite re-feeds its unconsumed tail on every resume) - the pending captured-text flush is hoisted from Dispatcher::handle_tag into the lexer actions so every suspension point has a uniform shape - end_mut() is the non-consuming end() for a rewriter that may suspend in a document-end handler Bun uses this so an async JS content handler can suspend the rewrite and resume it from a promise reaction instead of spinning a nested event loop inside write(). See oven-sh/bun#36733.
The upstream ci.yml triggers on push: branches: [master] only, so an additive bun.yml runs cargo test --features=integration_test on pushes/PRs to bun without touching ci.yml (keeps rebases onto upstream clean).
…epth
The selector-VM open-element stack had two per-token scans that go
quadratic (or worse) on pathological nesting:
* Stack::pop_up_to did a full rposition() over the open stack for
every end tag. A stray end tag that matches nothing still walked
the whole stack, so N unclosed opens plus N stray closes was O(N^2):
"<a></b>".repeat(100_000) (~700 KB) burned ~25s of CPU.
* try_exec_hereditary_jumps_without_attrs walked every ancestor and
re-executed its hereditary_jumps for every start tag. For a selector
like "div div" over N nested <div>s every item carries the same
address range, so the walk is O(depth) per tag and O(N^2) overall;
k-arm chains ("div div div") compound to O(N^k).
Both paths are reachable from HtmlRewriter with any element-handler
registration, so untrusted HTML can pin a CPU core via sub-MB input.
Memory stays linear so max_allowed_memory_usage does not bound it.
Changes:
* Stack now keeps open_name_counts: HashMap<LocalName, usize> so
pop_up_to rejects a name not on the stack in O(1). When the name is
present the rposition scan length equals the subsequent drain
length, so total scan work over a document is bounded by total
pushes.
* Stack now keeps active_hereditary_jumps: Vec<(AddressRange, usize)>,
the distinct hereditary-jump ranges currently contributed by any
open item, each paired with the shallowest depth that introduced it.
Descendant matching iterates this flat list (bounded by program
size, not depth) and pop_up_to prunes it by depth. The per-item
has_ancestor_with_hereditary_jumps flag and the
HereditaryJumpPtr.stack_offset resume coordinate are removed.
* LocalName gets a manual Hash impl that ASCII-lowercases the Bytes
arm so it agrees with the existing case-insensitive PartialEq.
Without this, keying a HashMap on LocalName is unsound for any name
that falls off LocalNameHash (custom elements, >12 chars, chars
outside a-zA-Z1-6). This also fixes the same latent contract
violation in TypedChildCounterMap.
Re-executing the same address range against the same start tag was
already a no-op after the matched-id set union in add_execution_branch,
so dedup-once execution is observationally equivalent.
Collaborator
Author
|
Status
|
The oven-sh organization only allows actions that are pinned to a full-length commit SHA. The workflows use tag refs, so every job on this repository fails at "Set up job" in about 2 seconds: The actions actions/checkout@v4, dtolnay/rust-toolchain@stable, and actions/cache@v4 are not allowed in oven-sh/lol-html because all actions must be pinned to a full-length commit SHA. Pin each `uses:` in bun.yml, ci.yml and semgrep.yml to the commit that its tag points to today. The versions do not change. dtolnay/rust-toolchain is the exception. Its README requires a pinned commit to be in the history of `master`, because other commits get garbage-collected. The `stable` rev is one commit off `master` that only makes `stable` the default of the `toolchain` input. So pin the `v1` tag, which is on `master`, and pass `toolchain: stable`. Apart from that default, action.yml at `v1` is identical to action.yml at `stable`. Co-authored-by: SUZUKI Sosuke <sosuke@bun.com>
robobun
force-pushed
the
robobun/6d0316ee/pin-workflow-actions
branch
from
September 13, 2026 18:28
be95468 to
2151cac
Compare
This was referenced Sep 18, 2026
Collaborator
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
bunhave no CI signal. Example: Reject all comment-closing sequences in Comment::set_text (cherry-pick of upstream ba25359) #6, run 34771454178. The annotation:The actions actions/checkout@v4, dtolnay/rust-toolchain@stable, and actions/cache@v4 are not allowed in oven-sh/lol-html because all actions must be pinned to a full-length commit SHA.Fix
uses:inbun.yml,ci.ymlandsemgrep.ymlto the commit that its tag points to today, with the exact version in a trailing comment. The versions do not change.dtolnay/rust-toolchainto itsv1tag and passtoolchain: stable. Its README requires a pinned commit to be onmaster, because other commits get garbage-collected. The commit behind@stableis not onmaster.bun.ymlheader documents the rule for the next rebase onto upstream.cargo testpasses.semgrep/cipasses but scans nothing here (Notes).TestandClippy and formatfail on defects already onbun, fixed in Fix the defects that upstream's CI reports on the bun branch (cherry-pick from #3) #8.Background
lol_htmlcrate from a commit on thebunbranch of this fork. A workflow-only commit needs no pin bump there.bun.ymlis the workflow of the fork.ci.ymlandsemgrep.ymlcome from upstream and also run on every pull request here.Notes
Pins
actions/checkout11d5960a326750d5838078e36cf38b85af677262v4points to)actions/cache0057852bfaa89a56745cba8c7296529d2fc39830v4points to)actions/setup-node49933ea5288caeca8642d1e84afbd3f7d6820020v4points to)obi1kenobi/cargo-semver-checks-action6b69fcf40e9b5fb17adeb57e4b6ecd020649a239v2points to)dtolnay/rust-toolchain6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772toolchain: stableCheck of each
uses: owner/repo@sha # tagline againstrepos/<repo>/git/ref/tags/<tag>:dtolnay/rust-toolchain
@stableis6bed0761d98439e5a578e2877258200ad565ba87today. It is one commit on top ofmastercommitd1031067. That commit only changesaction.yml:toolchaingetsrequired: falseanddefault: stable.compare/master...6bed0761reportsdiverged, so it is not onmaster.v1(6c977a6c) is onmaster(compare/6c977a6c...master: ahead 6, behind 0).v1...d1031067changes onlyscripts/update-revs.sh. Soaction.ymlatv1plustoolchain: stabledoes the same as@stable.masterHEAD (02cb101e, 2026-09-12) adds--force-non-hostto therustup toolchain installandrustup defaultcalls. It is one day old, so this PR does not use it.dtolnay/rust-toolchainis a composite action withrun:steps only. So no pinned action pulls in a second action by tag.Relation to #3
@sosukesuzuki wrote the same pins in 6a170ff on the branch of #3 and reverted them in 6166e4c to keep that PR to the transform-layer changes ("can be handled separately"). This PR is that separate change. The commit credits him. The differences: the
dtolnay/rust-toolchainpin (6a170ff uses the@stablecommit), exact versions in the comments, and thebun.ymlheader.What the upstream jobs report after the pin
The runs on #3 with 6a170ff show what
ci.ymlreports when it can start (run 34204513554, run 34204956393, run 34295940690). All of these are onbuntoday. This PR does not change them:Check format: rustfmt drift insrc/selectors_vm/mod.rs:508(from ea6125b).Run Rust testswithRUSTFLAGS=-Dwarnings: unused importCellin the suspension tests (from 0f44e89), and an elided lifetime in thetext!macro. The macro is upstream code. 0f44e89 exposed the lint because its tests are the first to usetext!inside the crate.Run clippy on C APIand the C API tests:c-apidoes not compile, becauseset_user_datanow needsSend(from 0f44e89).Check semver:enum_variant_addedforRewritingError::Suspended. The fork adds API on top of the 2.7.2 release on purpose, so this check can only fail here.eeea10d, 4afc2ca and 021637b on the branch of #3 fix these four. With them all three workflows were green (run 34296114458). #8 cherry-picks them on top of this PR.
The run of this PR shows the first failure of each job: run 34774752554 (
Check format,Run Rust tests). Thecargo testjob is run 34774752540, on stable 1.98.1.semgrep/cion the forkThe job is green but it scans nothing.
SEMGREP_APP_TOKENis Cloudflare's secret, so it is empty here.semgrep ciprintsrun `semgrep login` before using `semgrep ci`and exits 0. Upstreammainreplaced the job withsemgrep scan --config=auto, which needs no token. The job becomes useful after the next rebase.Scope
This PR covers
bun. Themainbranch of the fork mirrors upstream and keeps the tag refs, so a run onmainstill fails at "Set up job".Rebase cost
Upstream
main(v3.0.1) still uses tag refs. It changed two lines ofci.ymlthat this PR does not touch, and it rewrotesemgrep.yml. At the nextgit rebase --onto <new-tag>, this commit conflicts insemgrep.ymlonly (checked against v3.0.1). Take the upstream file and pin itsuses:lines.Self-review
A review of #7 and #8 raised 11 points. 8 led to a change or a check: the tag and SHA checks above, the nested-action check, the
semgrep/cinote, the wording of thetext!lint origin, thebun.ymlheader wording, themainbranch scope note, and the merge and rebase notes in #8. 3 did not: an em dash inside a code comment of 4afc2ca (kept verbatim, it is the author's text), a Dependabot entry forgithub-actions(a separate change), and a move of theCheck semvercommit from #8 into this PR (it fixes a defect onbun, not the pin).Options not taken
bun.ymlonly. Then three of the four checks stay red on every pull request.CIandSemgrep configworkflows in the repository settings. That leaves no trace in git, and theCIjobs found real defects onbun(the list above).semgrep/cion the fork withif: github.repository == 'cloudflare/lol-html'. A skipped job needs no pin. It edits the same upstream file, so the rebase cost is the same, and one rule ("pin everyuses:") is easier to follow.