Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
f0e1ba0
Add VDOM fuzzing harness
ealmloff May 19, 2026
bbb1778
use the test case builder
ealmloff May 19, 2026
69a490c
move over more tests
ealmloff May 19, 2026
d265fbe
fuzzing passes
ealmloff May 20, 2026
7c8d1ab
cache templates
ealmloff May 20, 2026
7e58391
simplify oracle
ealmloff May 20, 2026
45d36a1
normalize dynamic nodes instead of matching many times
ealmloff May 20, 2026
1e530e0
ignore fuzz logs
ealmloff May 20, 2026
13da663
simplify op model
ealmloff May 20, 2026
09fcce5
add ci workflow
ealmloff May 20, 2026
e43d87a
capture fuzz panics
ealmloff May 20, 2026
277163c
100% fuzzing code coverage for core diffing
ealmloff May 20, 2026
c0148dc
trim strategies from fuzzer
ealmloff May 20, 2026
9f7df13
fix component drop order
ealmloff May 20, 2026
785a936
fix component drop order
ealmloff May 20, 2026
c8f97d7
minimize the diff a bit
ealmloff May 21, 2026
7d4e474
restore oracle as a dev dep
ealmloff May 21, 2026
d654677
fuzzing passes
ealmloff May 21, 2026
c8e4aca
remove pending_* states from fuzzer
ealmloff May 21, 2026
6943bde
cover more event surface
ealmloff May 21, 2026
54935c5
ignore recursive event calls for now
ealmloff May 21, 2026
b8c2fff
clarify debug assert
ealmloff May 21, 2026
474c32f
simplify attribute merging
ealmloff May 21, 2026
3ef3d18
simplify node diff
ealmloff May 21, 2026
7260909
remove dynamic_node_is_rendered_in_dom
ealmloff May 21, 2026
84dead8
move oracle
ealmloff May 21, 2026
0030137
wip new attribute diff
ealmloff May 21, 2026
0260035
split out attribute diffing
ealmloff May 21, 2026
38ebaa3
clean up diff
ealmloff May 21, 2026
7461a01
add a note about the new debug assert
ealmloff May 21, 2026
af38b30
clean up crash files
ealmloff May 21, 2026
a5813ad
ignore crash files
ealmloff May 21, 2026
1d8ca2c
documentation for the new attribute diffing behavior
ealmloff May 21, 2026
cc99931
remove OptimizedStrategy
ealmloff May 22, 2026
1f86e9a
simplify suspense
ealmloff May 22, 2026
149d03d
more trims
ealmloff May 22, 2026
76dbf6b
fix raw attribute sorting
ealmloff May 22, 2026
198a9aa
sort with runtime names
ealmloff May 22, 2026
2c2e5ee
remove Sequence and thread locals
ealmloff May 22, 2026
fc9bf05
move listener tracking logic out of oracle
ealmloff May 22, 2026
4cfb2e1
revert scope_should_render
ealmloff May 22, 2026
1c20008
rely on sorted spread attributes
ealmloff May 22, 2026
2de66a7
remove sorted range
ealmloff May 22, 2026
0b2a133
remove dead case
ealmloff May 22, 2026
9e632e5
move panic_message into fuzz
ealmloff May 22, 2026
9d6f5e9
remove VNodeEdit
ealmloff May 22, 2026
19b6ff3
move over the rest of the core tests
ealmloff May 22, 2026
ecb8d97
switch suspense to use the oracle
ealmloff May 22, 2026
cc14d36
add more comments to attributes
ealmloff May 22, 2026
5cfdb2b
fix fuzzer
ealmloff May 22, 2026
154ebdb
symbolic ignores
ealmloff May 22, 2026
ef83a9d
fuzz CI: probe llvm-symbolizer location
ealmloff May 22, 2026
1b52cc4
fuzz CI: gitignore .claude session state
ealmloff May 22, 2026
c64b2a0
WIP: merge ealmloff/fuzzing into fix-textarea-hydration
ealmloff May 22, 2026
692820f
Add ealmloff's attribute sort + sort_template_attributes
ealmloff May 22, 2026
03f569a
Drop unused Attribute/AttributeValue imports from diff/node.rs
ealmloff May 22, 2026
0ff8755
Swap to old branch's slot-aware oracle, update test assertions
ealmloff May 22, 2026
a2784a0
Port ealmloff's suspense rerun-start to drain queued descendants
ealmloff May 22, 2026
959fd4c
more async native
ealmloff May 22, 2026
4f32856
internal mutations
ealmloff May 22, 2026
77fd42b
closer to passing fuzz
ealmloff May 23, 2026
e447717
full passing fuzzing coverage
ealmloff May 24, 2026
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
214 changes: 214 additions & 0 deletions .github/workflows/vdom-fuzz.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
name: VDOM Fuzz

on:
push:
branches:
- main
paths:
- ".github/workflows/vdom-fuzz.yml"
- "Cargo.lock"
- "Cargo.toml"
- "codecov.yml"
- "packages/fuzz/**"
- "packages/oracle/**"
- "packages/core/**"
- "packages/core-types/**"
- "packages/dioxus/**"
- "packages/ssr/**"

pull_request:
types: [opened, synchronize, reopened, ready_for_review]
branches:
- main
paths:
- ".github/workflows/vdom-fuzz.yml"
- "Cargo.lock"
- "Cargo.toml"
- "codecov.yml"
- "packages/fuzz/**"
- "packages/oracle/**"
- "packages/core/**"
- "packages/core-types/**"
- "packages/dioxus/**"
- "packages/ssr/**"

workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

env:
CARGO_INCREMENTAL: 0
CARGO_TERM_COLOR: always
FUZZ_DIR: packages/fuzz/fuzz
FUZZ_TARGET: vdom_ops
RUST_BACKTRACE: 1
rust_nightly: nightly-2025-10-05

jobs:
test-and-coverage:
if: github.event.pull_request.draft == false
name: "Fuzz | Test and coverage"
runs-on: warp-ubuntu-latest-x64-4x
timeout-minutes: 45
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/checkout@v5

- name: Install Rust ${{ env.rust_nightly }}
uses: dtolnay/rust-toolchain@nightly
with:
toolchain: ${{ env.rust_nightly }}
components: llvm-tools-preview

- uses: dtolnay/install@cargo-fuzz

- uses: Swatinem/rust-cache@v2
with:
cache-all-crates: "true"
cache-workspace-crates: "true"
cache-provider: "warpbuild"

- name: Test fuzz support crate
run: cargo test -p dioxus-vdom-fuzz --lib --examples

- name: Smoke test fuzz target
env:
LSAN_OPTIONS: suppressions=${{ github.workspace }}/packages/fuzz/fuzz/lsan.supp,print_suppressions=1
run: |
mkdir -p "$RUNNER_TEMP/fuzz-corpus" "$RUNNER_TEMP/fuzz-artifacts"
# LSan suppressions match against demangled symbols, so the
# sanitizer needs an `llvm-symbolizer` it can run. Try the one
# shipped with `llvm-tools-preview` first, then fall back to
# whatever the runner has on PATH.
target_triple="$(rustc +${{ env.rust_nightly }} -vV | sed -n 's/^host: //p')"
rustup_symbolizer="$(rustc +${{ env.rust_nightly }} --print sysroot)/lib/rustlib/$target_triple/bin/llvm-symbolizer"
if [ -x "$rustup_symbolizer" ]; then
export ASAN_SYMBOLIZER_PATH="$rustup_symbolizer"
elif command -v llvm-symbolizer >/dev/null; then
export ASAN_SYMBOLIZER_PATH="$(command -v llvm-symbolizer)"
else
# Pick the highest-versioned symbolizer Ubuntu ships, if any.
ASAN_SYMBOLIZER_PATH="$(ls /usr/bin/llvm-symbolizer-* 2>/dev/null | sort -V | tail -n1)"
export ASAN_SYMBOLIZER_PATH
fi
echo "ASAN_SYMBOLIZER_PATH=${ASAN_SYMBOLIZER_PATH:-<unset>}"
cargo +${{ env.rust_nightly }} fuzz run --fuzz-dir "$FUZZ_DIR" "$FUZZ_TARGET" "$RUNNER_TEMP/fuzz-corpus" -- \
-runs=256 \
-artifact_prefix="$RUNNER_TEMP/fuzz-artifacts/"

- name: Generate fuzz coverage
id: coverage
run: |
cargo +${{ env.rust_nightly }} fuzz coverage --fuzz-dir "$FUZZ_DIR" "$FUZZ_TARGET" "$RUNNER_TEMP/fuzz-corpus" -- -runs=0

target_triple="$(rustc +${{ env.rust_nightly }} -vV | sed -n 's/^host: //p')"
llvm_cov="$(rustc +${{ env.rust_nightly }} --print sysroot)/lib/rustlib/$target_triple/bin/llvm-cov"
coverage_binary="$FUZZ_DIR/target/$target_triple/coverage/$target_triple/release/$FUZZ_TARGET"
coverage_profile="$FUZZ_DIR/coverage/$FUZZ_TARGET/coverage.profdata"
coverage_lcov="$RUNNER_TEMP/fuzz.lcov"
coverage_report="$RUNNER_TEMP/fuzz-coverage.txt"
coverage_comment="$RUNNER_TEMP/fuzz-coverage.md"

test -x "$coverage_binary"
test -s "$coverage_profile"

"$llvm_cov" report \
--instr-profile="$coverage_profile" \
"$coverage_binary" \
--sources packages/fuzz/src \
| tee "$coverage_report"

"$llvm_cov" export \
--format=lcov \
--instr-profile="$coverage_profile" \
"$coverage_binary" \
--sources packages/fuzz/src \
> "$coverage_lcov"

test -s "$coverage_lcov"
test -s "$coverage_report"

COVERAGE_REPORT="$coverage_report" \
COVERAGE_COMMENT="$coverage_comment" \
python3 - <<'PY'
import os
import sys
from pathlib import Path

report_path = Path(os.environ["COVERAGE_REPORT"])
comment_path = Path(os.environ["COVERAGE_COMMENT"])
output_path = Path(os.environ["GITHUB_OUTPUT"])

total = next(
(line for line in report_path.read_text(encoding="utf-8").splitlines() if line.startswith("TOTAL")),
None,
)
if total is None:
print("llvm-cov report did not include a TOTAL row", file=sys.stderr)
sys.exit(1)

fields = total.split()
if len(fields) < 10:
print(f"Unexpected llvm-cov TOTAL row: {total}", file=sys.stderr)
sys.exit(1)

comment = f"""## Dioxus VDOM fuzz coverage

Coverage generated from `cargo fuzz coverage` for `packages/fuzz/src` after the `{os.environ["FUZZ_TARGET"]}` smoke corpus run.

| Metric | Coverage |
| --- | ---: |
| Regions | {fields[3]} |
| Functions | {fields[6]} |
| Lines | {fields[9]} |
"""

comment_path.write_text(comment, encoding="utf-8")

with output_path.open("a", encoding="utf-8") as output:
output.write("comment<<EOF\n")
output.write(comment)
output.write("\nEOF\n")
PY

- name: Comment fuzz coverage on PR
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository
uses: thollander/actions-comment-pull-request@24bffb9b452ba05a4f3f77933840a6a841d1b32b # v3
with:
pr-number: ${{ github.event.pull_request.number }}
message: ${{ steps.coverage.outputs.comment }}
comment-tag: fuzz-coverage

- name: Upload fuzz coverage to Codecov
uses: codecov/codecov-action@v5
with:
fail_ci_if_error: true
files: ${{ runner.temp }}/fuzz.lcov
flags: fuzz
name: fuzz
token: ${{ secrets.CODECOV_TOKEN }}

- name: Upload fuzz coverage artifact
if: always()
uses: actions/upload-artifact@v6
with:
name: fuzz-coverage
path: |
${{ runner.temp }}/fuzz.lcov
${{ runner.temp }}/fuzz-coverage.txt
${{ runner.temp }}/fuzz-coverage.md
if-no-files-found: ignore
retention-days: 7

- name: Upload fuzz failure artifacts
if: failure()
uses: actions/upload-artifact@v6
with:
name: fuzz-artifacts
path: ${{ runner.temp }}/fuzz-artifacts
if-no-files-found: ignore
retention-days: 7
13 changes: 12 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,20 @@ node_modules/

# ignore the output of tmps
tmp/
.tmp**/

# in debugging we frequently dump wasm to wat with `wasm-tools print`
*.wat

# External macos drives have extra ._ files
._*
._*

# Fuzzing logs
fuzz-*.log

# LibFuzzer failure artifacts
/crash-*
/timeout-*
/oom-*
/leak-*
.claude/
64 changes: 64 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ resolver = "2"
members = [
"packages/dioxus",
"packages/core",
"packages/oracle",
"packages/fuzz",
"packages/fuzz/fuzz",
"packages/core-types",
"packages/cli",
"packages/cli-config",
Expand Down Expand Up @@ -97,6 +100,7 @@ members = [
"examples/01-app-demos/file-explorer",
"examples/01-app-demos/hotdog",
"examples/01-app-demos/geolocation-native-plugin",
"examples/01-app-demos/concurrent-scheduler",

# Fullstack examples
"examples/07-fullstack/hello-world",
Expand All @@ -121,6 +125,7 @@ version = "0.8.0-alpha.0"
[workspace.dependencies]
dioxus = { path = "packages/dioxus", version = "0.8.0-alpha.0" }
dioxus-core = { path = "packages/core", version = "0.8.0-alpha.0" }
dioxus-renderer-oracle = { path = "packages/oracle", version = "0.8.0-alpha.0" }
dioxus-core-types = { path = "packages/core-types", version = "0.8.0-alpha.0" }
dioxus-core-macro = { path = "packages/core-macro", version = "0.8.0-alpha.0" }
dioxus-config-macro = { path = "packages/config-macro", version = "0.8.0-alpha.0" }
Expand Down Expand Up @@ -400,6 +405,14 @@ incremental = true
[profile.dev.package.walrus]
opt-level = 3

# Keep debug assertions for fuzzing, but compile the fuzz harness and reusable
# fuzzer crate with release-style optimizations.
[profile.dev.package.dioxus-fuzz]
opt-level = 3

[profile.dev.package.dioxus-vdom-fuzz]
opt-level = 3

# ensure we have adversarial setup for tls
[profile.dev.package.cross-tls-crate]
opt-level = 2
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/target
Loading